Git Product home page Git Product logo

mithril-query's Introduction

mithril-query

Gitter Build Status rethink.js js-standard-style

Query mithril virtual dom for testing purposes

Installation

npm install mithril-query --save-dev

Setup

In order to run tests in mithril 2.x we need to do some dom-mocking for the renderer. mithril-query will try to do this mocking for you, if it can't find the required globals, but this might not work properly due to module loading order. If you load mithril-query before everything else it should work as expected.

In any other case, this can be done manually by calling the ensureGlobals helper upfront (e. G. by adding if into a 'setup' file in your 'mocha' tests).

require('mithril-query').ensureGlobals()

Changes from version 3.x to 4.x

Root state access

... is gone, since mithril does not provide a way to access it

Booleans

... are now rendered as empty strings, like mithril does, because, well, mithril renders

Lifecycles

... are now fully supported, including synthetic DOM elements 🎉

find/first

... are now returning DOM elements instead of vdom nodes.

Custom events

... aren't supported anymore. Feel free to file a ticket, if you want them back.

Usage

You can run this tests server side or use browserify and run them in browsers.

const m = require('mithril')

module.exports = {
  view: function() {
    return m('div', [
      m('span', 'spanContent'),
      m('#fooId', 'fooContent'),
      m('.barClass', 'barContent'),
    ])
  },
}
/* eslint-env mocha */
const mq = require('mithril-query')
const simpleModule = require('./simple')

describe('simple module', function() {
  it('should generate appropriate output', function() {
    var output = mq(simpleModule)
    output.should.have('span')
    output.should.have('div > span')
    output.should.have('#fooId')
    output.should.have('.barClass')
    output.should.have(':contains(barContent)')
    output.should.contain('barContent')
  })
})

Run the test with

mocha simple.test.js

API

Initialise

First call mithril-query with either a vnode or a component. You can call it with one extra argument which will be used as attrs in the component case.

var mq = require('mithril-query')

// plain vnode
var out = mq(m('div'))

// object component
var myComponent = {
  view: function({ attrs }) {
    return m('div', attrs.text)
  },
}
var out = mq(myComponent, { text: 'huhu' })

// closure component
function myComponent() {
  return {
    view: function({ attrs }) {
      return m('div', attrs.text)
    },
  }
}
var out = mq(myComponent, { text: 'huhu' })

Query API

As you can see mq returns an out-Object which has the following test-API.

  • out.first(selector) – Returns the first element that matches the selector (think document.querySelector).
  • out.find(selector) – Returns all elements that match the selector (think document.querySelectorAll).
  • out.has(selector) –  Returns true if any element in tree matches the selector, otherwise false.
  • out.contains(string) – Returns true if any element in tree contains the string, otherwise false.
  • out.log(selector, [logFN]) – Small helper function to log out what was selected. Mainly for debugging purposes. You can give an optional function which is called with the result. It defaults to HTML-Pretty-Printer (pretty-html-log] that logs the HTML-representation to stdout.

You can use these nice assertions. They throw errors if they're not fulfilled. See the example in the example folder.

  • out.should.have([count], selector)

Throws if no element is found with selector. If count is given, it throws if count does not match.

  • out.should.not.have(selector) – Throws if an element is found with selector.
  • out.should.have.at.least(count, selector) – Throws if there a fewer than count elements matching the selector
  • out.should.have([selector0, selector1, selector2]) – Throws there aren't at least one element for each selector.
  • out.should.contain(string) – Throws if no element contains string.
  • out.should.not.contain(string) - Throws if any element contains string.

Event triggering

It is also possible to trigger element events like onfocus and onclick and set values on <input>-fields. This allows you to write "integration tests" that run also on server side.

Attention: Currently there is no event bubbling supported.

  • out.click(selector, [eventData]) – Runs onclick for first element that matches selector. Optional eventData is given as to the event constructor. eventData.redraw = false is respected.
  • out.setValue(selector, string, [eventData]) – Runs oninput and onchange for first element that matches selector.
  • out.trigger(selector, eventname, [eventData]) – General purpose event triggerer. Calls eventname on first matching element.

It also supports key events

  • out.keydown(selector, keycode, [eventData]) – calls onkeydown with keycode
  • out.keydown(selector, keyname, [eventData]) – calls onkeydown with keycode mapped from name. Mapping is done with this lib.

keyup, keypress are supported as well.

Auto "Redrawing"

Since mithril-query uses mithril on a fake DOM, auto rendering works as expected.

Example:

  // module code
  const component = {
    visible: true
    oninit({ state }) {
      state.toggleMe = () => (state.visible = !state.visible)
    },
    view({ state }) {
      return m(
        state.visible ? '.visible' : '.hidden',
        { onclick: state.toggleMe},
        'Test'
      )
    },
  }


  // actual test
  out = mq(component)
  out.should.have('.visible')
  out.click('.visible')
  out.should.not.have('.visible')
  out.should.have('.hidden')
  out.click('.hidden', { redraw: false })
  out.should.have('.hidden')

As you can see, you can prevent auto redraw by providing a redraw: false as last argument to click method.

You can also manually trigger redraw:

var out = mq(module)
out.should.have('.visible')
out.redraw()

helpers

If you need to access the rendered root element you can simply access it with

out.rootEl

onremove handling

To trigger onremove-handlers of all initialized components, just call out.onremove()

mithril-query's People

Contributors

alfredmachineuser avatar barneycarroll avatar der-on avatar fossamagna avatar gitter-badger avatar greenkeeperio-bot avatar iyegoroff avatar jmooradi avatar limdauto avatar magnetised avatar mike-ward avatar onlyskin avatar quafzi avatar richardivan avatar samueltilly avatar sl014066 avatar stephanhoyer avatar tombh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mithril-query's Issues

Closure components clarification

The readme states "Closure Components are currently only supported as part of the tree, not directly as argumend to mq function". Can you give an example of what is and isn't acceptable?

Could a browser bundle be published alongside the module?

It's currently a bit awkward to use in a browser without a bundler already set up, so I was wondering if you could start publishing a browser bundle distribution that I could point users to.

For context, I was going to include a "Running in browsers" section for the rewritten testing docs (soon to be published), but I don't want to include it with nothing to point to, and I want it to be easy to get started with.

Nothing to report

Just wanted to say I love this library. Thanks for writing it 😊

Unexpected behaviour for components with leading array

It's not possible to query the child with reference to it's parent. div.parent > div.child when the child node lives in a separate component that starts with a array.

const component1 = {
  view: function() {
    return m('div.comp1', m(component2))
  },
}

const component2 = {
  view: function() {
    return [
      m('div.comp2')
    ] // without this array all is good.
  },
}

describe('', function() {
  it('should be able to query children within leading array component', function() {
    let output = mq(m(component1))
    output.should.have('.comp1') // yep
    output.should.have('.comp2') // all good
    output.should.have('.comp1 .comp2') // Nope!
  })
})

I'll give it a shot on trying to solve this issue, but I'm not 100% confident i will be able to solve it right now.

Rewrite this on top of `m.render` + cssauron-html?

Hi @StephanHoyer I've been reviewing the source in order to implement class components, and the way m.render/m.redraw are implemented is not very accurate.

Rather than rewriting the whole thing, I was wondering if we couldn't instead rely on m.render(), some kind of DOM library (injectable, so that jsdom can be used optionally if the Mithril mocks don't cut it for someone) and cssauron-html?

After renderComponents() is removed, It looks like there's little code that's vdom specific beside the language() parser and the select() function.

That approach would ensure feature parity with Mithril, and allow to support more complex components that manipulate their DOM in hooks.

Edit: Looking a bit deeper into this, the removers feature couldn't be replicated, but we could instead add a api.remove() method that would m.render(root, []), thereby triggering the removal phase.

Edit2: the event handling code would also need some love. Another breaking change would be the fact that api.rootNode would be a DOM tree rather than a vDOM tree. Some workaround would be needed if the root component returns an array.

Missing test for traversing children for sibling

While i was logging inside the children function for the cssauroun implementation there looks to be nothing that tests that the sibling function behaves as expected.

Adjacent sibling: https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_selectors
General sibling: https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_selectors

describe('traverse from a parent to its children for sibling selectors', function() {
  it('adjacent sibling combinator ', function() {
    let output = mq(m('div', [
      m('div.first'),
      m('div.second')
    ]))

    output.should.have('.first + .second')
  })

  it('general sibling combinator', function() {
    let output = mq(m('div', [
      m('span'),
      m('p'),
      m('span'),
      m('a'),
      m('span')
    ]))

    expect(output.find('p ~ span').length).toEqual(2)
  })
})

This simple tests validate that the implementation indeed works as intended 👍

:contains is not working as expected

var el = mq(m('ul', [
  m('li', ['foo']),
]));

console.log(el.contains('foo')); // true
console.log(el.has('li:contains(foo)')); // false
console.log(el.has('li :contains(foo)')); // true

I would expect the second example to still resolve to true, this could be confusing for users.

var el = mq(m('ul', [
  m('li', [['foo']]),
]));

console.log(el.has('li > #:contains(foo)')); // true
console.log(el.has('li:contains(foo)')); // false

Looks like it thinks # is the parent, true for vnode but not for html.

Custom style properties prevent MQ from rendering components

As per title, mithril query fails to render components that are passed custom style properties via mithril js syntax. For instance, declaring a component with mq such as:

const someComp = mq(someComp, { attr1: true, attr2: false, style: {"--testVar": 12}});

or within the component view such as:

return m("some-name", { style: { "--testVar": 12}}, [childrenHere...]);

...will cause the following error when running tests

TypeError: Cannot read properties of undefined (reading 'type')

However, setting up the same functionality in the same way, but adding the properties via js inside the component view()/oncreate() causes no issues.

document.querySelector(".someComp").style.setProperty("--testVar", 12);

``

'first' throwing errors when no vdom nodes identified.

'first' is throwing errors when no results are found. This results in other functions not returning the expected result. For instance when using the 'contains' function, if the value is not contained in the vdom then an error is thrown rather than 'false' being returned.

out.contains(string) – Returns true if any element in tree contains the string, otherwise false.

Updating the contains function to utilize 'find' rather than 'first resolves this issue.

function contains (value, node) { return !!(find(':contains(' + value + ')', node).length); }

Key events don't allow passing a custom event

Documentation shows passing an event as the 3rd argument on key events, but that object is being treated as an option object and can only override certain props on the event object.

keypress should take an additional argument to pass event overrides:
out.keydown(selector, keycode, [event], [silent])

Cannot assign to read only property 'parent' of <tag>

Running on node v0.12.4 npm test gives:

17 passing
15 failing

tests. All the failing tests fail with the same error call stack:

TypeError: Cannot assign to read only property 'parent' of Test
  at index.js:108:22
  at Array.forEach (native)
  at matches (index.js:107:36)
  at find (index.js:82:44)

Types missing on NPM, need a new version pushed?

Hello, I am trying to use mithril-query with types but it looks like the latest version on NPM (4.0.1) doesn't include the types property in package.json. I'm not sure what commit built it, but could you push a new version to NPM with the current code in master?

For now, I'm using "mithril-query": "git+https://github.com/mithriljs/mithril-query.git#facf7ce" in my package.json and it works OK.

VNode children property of type boolean not handled

When searching through VNode children, a boolean value can cause unexpected results.

If the boolean is true (Ex. node.children === true), the short-circuit logic will interpret the children property as being truthy and continue past this check and then attempts to perform array function calls.
(line 179)
if (!node.children || isStringOrNumber(node.children)) { return foundNodes }

Adding a new isBoolean function and modifying the short-circuit logic should prevent this and return the expected results.

function isBoolean (thing) { return typeof thing === "boolean"; } ... if (isBoolean(node.children) || isStringOrNumber(node.children)|| !node.children) { return foundNodes }

PSA: I've added a bunch of default community health files org-wide

Community health files are stuff like CONTRIBUTING.md, FUNDING.yml, and so on, stuff that normally lives in .github, docs/, or the repo's root. I've added default files for everything on this list of supported file types except for a CONTRIBUTING.md file, as that process varies pretty greatly across repos. For issue templates, there's two templates: one for bugs and one for feature requests. The pull request template and issue templates are each derived from the core project's own core templates but with core-specific stuff omitted.

No action is required on your part, but you may wish to customize these appropriately and/or take other related action in light of this like adding/removing issue templates.


If you have any questions, comments, or concerns, please file an issue and I'd be more than willing to address them.

Click events don't fire anymore after npm update

Hey guys,

    const openClass = '.acc-open-item';

    test('should toggle item on click', () => {
        test(Accordion.should.not.have(openClass)).equals(true);
        Accordion.click('#acc-item-0 > a');
        test(Accordion.should.have(1, openClass)).equals(true);
        Accordion.click('#acc-item-0 > a');
        test(Accordion.should.not.have(openClass)).equals(true);
    });
Accordion > should toggle item on click:
Wrong count of elements that matches ".acc-open-item"
  expected: 1
  actual: 0
    at Object.shouldHave [as have] (/Users/fabian/Workspace/design-komponenten/accordion/node_modules/mithril-query/index.js:103:13)
    at /Users/fabian/Workspace/design-komponenten/accordion/tests/accordion.test.js:29:31

I just did a npm update and afterwards the click events in my testing code don't fire anymore. The changelog from version 3 to 4 does not say anything about changed behavior. What do I have to do to get my tests passing with success?

oninit is not running for nested components

When using the rewrite branch mq is not running component oninit function for nested components.

My simple guess is that in https://github.com/StephanHoyer/mithril-query/blob/rewrite/index.js#L106
states[treePath] is already defined and therefore oninit is never triggered.

test('component', function(t) {
    var result = {
        oninit: 0,
        view: 0,
    };

    var component = {
        oninit: function() {
            result.oninit++;
        },
        view: function(vnode) {
            result.view++;
            return m("div.test", vnode.children);
        },
    };

    mq(
        m(component, [
            m(component, [
                m(component), // <-- oninit is not running for this component.
            ]),
        ])
    );

    t.equal(result.oninit, result.view, 
        'oninit should run before every view');

    t.end();
});

Support ES6 class components

When using an ES6 class style component, I get an error like:

Sample > should have a div:
Class constructor Sample cannot be invoked without 'new'

Reproduction example:

global.window = require("mithril/test-utils/browserMock.js")()
global.document = window.document

var m = require("mithril") // version 1.1.1
var mq = require('mithril-query') // version 2.2.0
var o = require("mithril/ospec/ospec")

class Sample {
    constructor(vnode) {
	this._text = vnode.attrs.text
    }

    view() {
	return m("div", this._text)
    }
}


o.spec("Sample", function() {
    o("should have a div", function() {
	var output = mq(Sample, {text: "Text"})
	output.should.have(1, "div")
    })
})

undefined vnode.state

Description:

vnode.state is not copied from vnode.tag when Mithril 1.0.0 "rewrite" is running in node.

Steps to Reproduce:

Using this gist: https://gist.github.com/magnusleo/ac28737dd659db6465d9db47ab54495b

npm test

My test was run with node v5.9.1 on Mac OS X 10.11.5, installed using http://brew.sh/

Expected:

The test runs flawlessly.

Actual:

component.js:15
                return m('li', {onclick: vnode.state.clickCurry(i)}, item); // Fails in node
                                                    ^

TypeError: Cannot read property 'clickCurry' of undefined

Mithril throwing warnings after redraw

When I call redraw from mithril-query, mithril logs this error:

Don't reuse attrs object, use new object for every redraw, this will throw in next major

Restore unload handling

This was once in the docs

onunload-handling

If you use the auto-rendering feature mentioned above you might want to also
call onunload to on the controller response after testing. We added a
reference to the controller onunload function to the result of the mithril query
function.

var module = {
  controller: function () {
    return {
      onunload: function () {
        //clean up stuff
      }
    }
  },
  view: function (scope) {
    // do what ever
  }
}

var $out = mq(module)
$out.onunload() // calls upper defined onunload on the controller result

Can't access DOM of child VNodes

Hey! Here's my simple testing setup. It gives me an error TypeError: Cannot set property 'value' of undefined at Object.setValue. What am I missing?

const mq = require('mithril-query')
const m = require('mithril')

const Component = {
    view () {
      this.input = m('input')
      return m('div', this.input)
    },
    setValue (value) {
      this.input.dom.value = value
    }
}

describe('Component', function() {
  const out = mq(Component)
  it('displays correct value after `setValue` call', function() {
    out.vnode.state.setValue('test')
  });
});

should.have('... :contains') not always working

In some cases the :contains directive is not working as expected.

Example:

const out = mq(myComponent);
out.should.have('.button :contains(foo)'); // will return 0 elements, also the button is there
const out = mq(myComponent);
mq(out.first('.button')).should.contain('foo'); // will work however

In other case the :contains directive is working as expected.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.