├── .eslintrc.js ├── .github ├── CODEOWNERS └── FUNDING.yml ├── .gitignore ├── .prettierrc ├── .travis.yml ├── LICENSE ├── README.md ├── example ├── simple.js └── simple.test.js ├── fixtures ├── babel-class-component-with-destructuring.js ├── babel-class-component.js ├── webpack-babel-transform-class-component-esmodules-with-destructuring.js ├── webpack-babel-transform-class-component-esmodules.js ├── webpack-babel-transform-class-component-with-destructuring.js └── webpack-babel-transform-class-component.js ├── index.js ├── mithril-query.d.ts ├── package-lock.json ├── package.json └── test.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "rules": { 10 | "indent": [ 11 | "error", 12 | 2 13 | ], 14 | "linebreak-style": [ 15 | "error", 16 | "unix" 17 | ], 18 | "quotes": [ 19 | "error", 20 | "single" 21 | ], 22 | "semi": [ 23 | "error", 24 | "never" 25 | ] 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @MithrilJS/Committers 2 | /.github/ @MithrilJS/Admins 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | open_collective: mithriljs 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "printWidth": 80, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "trailingComma": "es5", 7 | "semi": false 8 | } 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - 8 5 | - node 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Stephan Hoyer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mithril-query 2 | 3 | [](https://gitter.im/MithrilJS/mithril-query?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | [](https://travis-ci.org/MithrilJS/mithril-query) 5 | [](https://github.com/rethinkjs/manifest) 6 | [](http://standardjs.com/) 7 | 8 | Query mithril virtual dom for testing purposes 9 | 10 | ## Installation 11 | 12 | npm install mithril-query --save-dev 13 | 14 | ## Setup 15 | 16 | In order to run tests in mithril 2.x we need to do some dom-mocking for the renderer. 17 | `mithril-query` will try to do this mocking for you, if it can't find the required globals, but this 18 | might not work properly due to module loading order. If you load mithril-query before everything else 19 | it should work as expected. 20 | 21 | 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). 22 | 23 | ```js 24 | require('mithril-query').ensureGlobals() 25 | ``` 26 | 27 | ## Changes from version 3.x to 4.x 28 | 29 | #### Root state access 30 | 31 | ... is gone, since `mithril` does not provide a way to access it 32 | 33 | #### Booleans 34 | 35 | ... are now rendered as empty strings, like mithril does, because, well, mithril renders 36 | 37 | #### Lifecycles 38 | 39 | ... are now fully supported, including synthetic DOM elements 🎉 40 | 41 | #### find/first 42 | 43 | ... are now returning DOM elements instead of vdom nodes. 44 | 45 | #### Custom events 46 | 47 | ... aren't supported anymore. Feel free to file a ticket, if you want them back. 48 | 49 | ## Usage 50 | 51 | You can run this tests server side or use browserify and run them in browsers. 52 | 53 | ```js 54 | const m = require('mithril') 55 | 56 | module.exports = { 57 | view: function() { 58 | return m('div', [ 59 | m('span', 'spanContent'), 60 | m('#fooId', 'fooContent'), 61 | m('.barClass', 'barContent'), 62 | ]) 63 | }, 64 | } 65 | ``` 66 | 67 | ```js 68 | /* eslint-env mocha */ 69 | const mq = require('mithril-query') 70 | const simpleModule = require('./simple') 71 | 72 | describe('simple module', function() { 73 | it('should generate appropriate output', function() { 74 | var output = mq(simpleModule) 75 | output.should.have('span') 76 | output.should.have('div > span') 77 | output.should.have('#fooId') 78 | output.should.have('.barClass') 79 | output.should.have(':contains(barContent)') 80 | output.should.contain('barContent') 81 | }) 82 | }) 83 | ``` 84 | 85 | Run the test with 86 | 87 | mocha simple.test.js 88 | 89 | ## API 90 | 91 | ### Initialise 92 | 93 | First call `mithril-query` with either a vnode or a component. You can call it 94 | with one extra argument which will be used as `attrs` in the component case. 95 | 96 | ```js 97 | var mq = require('mithril-query') 98 | 99 | // plain vnode 100 | var out = mq(m('div')) 101 | 102 | // object component 103 | var myComponent = { 104 | view: function({ attrs }) { 105 | return m('div', attrs.text) 106 | }, 107 | } 108 | var out = mq(myComponent, { text: 'huhu' }) 109 | 110 | // closure component 111 | function myComponent() { 112 | return { 113 | view: function({ attrs }) { 114 | return m('div', attrs.text) 115 | }, 116 | } 117 | } 118 | var out = mq(myComponent, { text: 'huhu' }) 119 | ``` 120 | 121 | ### Query API 122 | 123 | As you can see `mq` returns an `out`-Object which has the following test-API. 124 | 125 | - `out.first(selector)` – Returns the first element that matches the selector (think `document.querySelector`). 126 | - `out.find(selector)` – Returns all elements that match the selector (think `document.querySelectorAll`). 127 | - `out.has(selector)` – Returns `true` if any element in tree matches the selector, otherwise `false`. 128 | - `out.contains(string)` – Returns `true` if any element in tree contains the string, otherwise `false`. 129 | - `out.log(selector, [logFN])` – Small helper function to log out what was selected. Mainly for debugging 130 | purposes. You can give an optional function which is called with the result. 131 | It defaults to HTML-Pretty-Printer ([pretty-html-log](https://www.npmjs.com/package/pretty-html-log)] that logs the HTML-representation to `stdout`. 132 | 133 | You can use these nice assertions. They throw errors if they're not fulfilled. 134 | See the example in the example folder. 135 | 136 | - `out.should.have([count], selector)` 137 | 138 | Throws if no element is found with selector. If `count` is given, it throws if 139 | count does not match. 140 | 141 | - `out.should.not.have(selector)` – Throws if an element is found with selector. 142 | - `out.should.have.at.least(count, selector)` – Throws if there a fewer than `count` elements matching the selector 143 | - `out.should.have([selector0, selector1, selector2])` – Throws there aren't at least one element for each selector. 144 | - `out.should.contain(string)` – Throws if no element contains `string`. 145 | - `out.should.not.contain(string)` - Throws if any element contains `string`. 146 | 147 | ### Event triggering 148 | 149 | It is also possible to trigger element events like `onfocus` and `onclick` and set values on ``-fields. This allows you to write "integration tests" that run also on server side. 150 | 151 | Attention: Currently there is no event bubbling supported. 152 | 153 | - `out.click(selector, [eventData])` – Runs `onclick` for first element that matches selector. Optional `eventData` is given 154 | as to the event constructor. `eventData.redraw = false` is respected. 155 | - `out.setValue(selector, string, [eventData])` – Runs `oninput` and `onchange` for first element that matches selector. 156 | - `out.trigger(selector, eventname, [eventData])` – General purpose event triggerer. Calls `eventname` on first matching element. 157 | 158 | It also supports key events 159 | 160 | - `out.keydown(selector, keycode, [eventData])` – calls `onkeydown` with `keycode` 161 | - `out.keydown(selector, keyname, [eventData])` – calls `onkeydown` with keycode mapped from name. Mapping is done with [this lib](https://github.com/npmcomponent/yields-keycode). 162 | 163 | `keyup`, `keypress` are supported as well. 164 | 165 | ### Auto "Redrawing" 166 | 167 | Since `mithril-query` uses `mithril` on a fake DOM, auto rendering works as expected. 168 | 169 | Example: 170 | 171 | ```js 172 | // module code 173 | const component = { 174 | visible: true 175 | oninit({ state }) { 176 | state.toggleMe = () => (state.visible = !state.visible) 177 | }, 178 | view({ state }) { 179 | return m( 180 | state.visible ? '.visible' : '.hidden', 181 | { onclick: state.toggleMe}, 182 | 'Test' 183 | ) 184 | }, 185 | } 186 | 187 | 188 | // actual test 189 | out = mq(component) 190 | out.should.have('.visible') 191 | out.click('.visible') 192 | out.should.not.have('.visible') 193 | out.should.have('.hidden') 194 | out.click('.hidden', { redraw: false }) 195 | out.should.have('.hidden') 196 | ``` 197 | 198 | As you can see, you can prevent auto redraw by providing a `redraw: false` as last 199 | argument to `click` method. 200 | 201 | You can also manually trigger redraw: 202 | 203 | ```javascript 204 | var out = mq(module) 205 | out.should.have('.visible') 206 | out.redraw() 207 | ``` 208 | 209 | ### helpers 210 | 211 | If you need to access the rendered root element you can simply access it with 212 | 213 | ```javascript 214 | out.rootEl 215 | ``` 216 | 217 | ### `onremove` handling 218 | 219 | To trigger `onremove`-handlers of all initialized components, just call `out.onremove()` 220 | -------------------------------------------------------------------------------- /example/simple.js: -------------------------------------------------------------------------------- 1 | const m = require('mithril') 2 | 3 | module.exports = { 4 | view: function() { 5 | return m('div', [ 6 | m('span', 'spanContent'), 7 | m('#fooId', 'fooContent'), 8 | m('.barClass', 'barContent'), 9 | ]) 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /example/simple.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | global.window = Object.assign( 3 | require('mithril/test-utils/domMock.js')(), 4 | require('mithril/test-utils/pushStateMock')() 5 | ) 6 | global.requestAnimationFrame = callback => 7 | global.setTimeout(callback, 1000 / 60) 8 | 9 | const simpleModule = require('./simple') 10 | const mq = require('../') 11 | 12 | describe('simple module', function() { 13 | it('should generate appropriate output', function() { 14 | var output = mq(simpleModule) 15 | output.should.have('span') 16 | output.should.have('div > span') 17 | output.should.have('#fooId') 18 | output.should.have('.barClass') 19 | output.should.have(':contains(barContent)') 20 | output.should.contain('barContent') 21 | output.log('div'); 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /fixtures/babel-class-component-with-destructuring.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const m = require('mithril/hyperscript') 3 | 4 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 5 | 6 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 7 | 8 | var _babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0___default = function () { return _classCallCheck; }; 9 | 10 | var BabelClassComponent = function () { 11 | function BabelClassComponent(_ref) { 12 | var model = _ref.attrs; 13 | 14 | _babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0___default()(this, BabelClassComponent); 15 | 16 | this.model = model; 17 | } 18 | 19 | _createClass(BabelClassComponent, [{ 20 | key: 'view', 21 | value: function view() { 22 | return m('div', ['hello']); 23 | } 24 | }]); 25 | 26 | return BabelClassComponent; 27 | }(); 28 | 29 | module.exports = BabelClassComponent; 30 | -------------------------------------------------------------------------------- /fixtures/babel-class-component.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const m = require('mithril/hyperscript') 3 | 4 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 5 | 6 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 7 | 8 | var BabelClassComponent = function () { 9 | function BabelClassComponent(vnode) { 10 | _classCallCheck(this, BabelClassComponent); 11 | 12 | this.vnode = vnode; 13 | } 14 | 15 | _createClass(BabelClassComponent, [{ 16 | key: 'view', 17 | value: function view() { 18 | return m('div', ['hello']); 19 | } 20 | }]); 21 | 22 | return BabelClassComponent; 23 | }(); 24 | 25 | module.exports = BabelClassComponent; 26 | -------------------------------------------------------------------------------- /fixtures/webpack-babel-transform-class-component-esmodules-with-destructuring.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const m = require('mithril/hyperscript') 3 | 4 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 5 | 6 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 7 | 8 | var _babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_0__ = { default: _classCallCheck }; 9 | 10 | var BabelClassComponent = function () { 11 | function BabelClassComponent(_ref) { 12 | var model = _ref.attrs; 13 | 14 | Object(_babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_0__["default"])(this, BabelClassComponent); 15 | 16 | this.model = model; 17 | } 18 | 19 | _createClass(BabelClassComponent, [{ 20 | key: 'view', 21 | value: function view() { 22 | return m('div', ['hello']); 23 | } 24 | }]); 25 | 26 | return BabelClassComponent; 27 | }(); 28 | 29 | module.exports = BabelClassComponent; 30 | -------------------------------------------------------------------------------- /fixtures/webpack-babel-transform-class-component-esmodules.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const m = require('mithril/hyperscript') 3 | 4 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 5 | 6 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 7 | 8 | var _babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_0__ = { default: _classCallCheck }; 9 | 10 | var BabelClassComponent = function () { 11 | function BabelClassComponent(vnode) { 12 | Object(_babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_0__["default"])(this, BabelClassComponent); 13 | 14 | this.vnode = vnode; 15 | } 16 | 17 | _createClass(BabelClassComponent, [{ 18 | key: 'view', 19 | value: function view() { 20 | return m('div', ['hello']); 21 | } 22 | }]); 23 | 24 | return BabelClassComponent; 25 | }(); 26 | 27 | module.exports = BabelClassComponent; 28 | -------------------------------------------------------------------------------- /fixtures/webpack-babel-transform-class-component-with-destructuring.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const m = require('mithril/hyperscript') 3 | 4 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 5 | 6 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 7 | 8 | var _babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0___default = function () { return _classCallCheck; }; 9 | 10 | var BabelClassComponent = function () { 11 | function BabelClassComponent(_ref) { 12 | var model = _ref.attrs; 13 | 14 | _babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0___default()(this, BabelClassComponent); 15 | 16 | this.model = model; 17 | } 18 | 19 | _createClass(BabelClassComponent, [{ 20 | key: 'view', 21 | value: function view() { 22 | return m('div', ['hello']); 23 | } 24 | }]); 25 | 26 | return BabelClassComponent; 27 | }(); 28 | 29 | module.exports = BabelClassComponent; 30 | -------------------------------------------------------------------------------- /fixtures/webpack-babel-transform-class-component.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const m = require('mithril/hyperscript') 3 | 4 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 5 | 6 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 7 | 8 | var _babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0___default = function () { return _classCallCheck; }; 9 | 10 | var BabelClassComponent = function () { 11 | function BabelClassComponent(vnode) { 12 | _babel_runtime_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0___default()(this, BabelClassComponent); 13 | 14 | this.vnode = vnode; 15 | } 16 | 17 | _createClass(BabelClassComponent, [{ 18 | key: 'view', 19 | value: function view() { 20 | return m('div', ['hello']); 21 | } 22 | }]); 23 | 24 | return BabelClassComponent; 25 | }(); 26 | 27 | module.exports = BabelClassComponent; 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const domino = require('domino') 4 | ensureGlobals() 5 | const m = require('mithril') 6 | const Event = require('domino/lib/Event') 7 | const code = require('yields-keycode') 8 | const Vnode = require('mithril/render/vnode') 9 | const formatHtml = require('pretty-html-log').highlight 10 | 11 | function isString(thing) { 12 | return Object.prototype.toString.call(thing) === '[object String]' 13 | } 14 | 15 | function isArray(thing) { 16 | return Object.prototype.toString.call(thing) === '[object Array]' 17 | } 18 | 19 | function isComponent(thing) { 20 | return !!( 21 | (thing && (typeof thing === 'object' && thing.view)) || 22 | isFunction(thing) || 23 | isClass(thing) 24 | ) 25 | } 26 | 27 | function isFunction(thing) { 28 | return typeof thing === 'function' && !isClass(thing) 29 | } 30 | 31 | function isBabelTranspiledClass(thing) { 32 | const code = thing.toString().replace(/^[^{]+{/, '') 33 | 34 | return ( 35 | // Regular Babel transpiled class 36 | /(?:^|\s+)_classCallCheck\(/.test(code) || 37 | // Babel with @babel/transform-runtime and Webpack 38 | /(?:^|\s+)_[^\s]+_classCallCheck__[^\s()]+\(/.test(code) || 39 | // Babel with @babel/transform-runtime (useESModules: true) and Webpack 40 | /(?:^|\s+)Object\(_[^\s]+_classCallCheck__[^\s()]+\)\(/.test(code) 41 | ) 42 | } 43 | 44 | function isClass(thing) { 45 | return ( 46 | typeof thing === 'function' && 47 | (/^\s*class\s/.test(thing.toString()) || // ES6 class 48 | isBabelTranspiledClass(thing)) // Babel class 49 | ) 50 | } 51 | 52 | function consoleLogHtml(els) { 53 | // eslint-disable-next-line no-console 54 | console.log(els.map(el => formatHtml(el.outerHTML)).join('---------\n')) 55 | } 56 | 57 | function scan(api) { 58 | const rootEl = api.rootEl 59 | 60 | function find(selectorString, node) { 61 | return Array.from(node.querySelectorAll(selectorString)) 62 | } 63 | 64 | function first(selector) { 65 | const node = rootEl.querySelector(selector) 66 | if (!node) { 67 | throw new Error('No element matches ' + selector) 68 | } 69 | return node 70 | } 71 | 72 | function has(selector) { 73 | return find(selector, rootEl).length > 0 74 | } 75 | 76 | function contains(value, node) { 77 | return !!find(':contains(' + value + ')', node).length 78 | } 79 | 80 | function shouldHaveAtLeast(minCount, selector) { 81 | const actualCount = find(selector, rootEl).length 82 | if (actualCount < minCount) { 83 | throw new Error( 84 | 'Wrong count of elements that matches "' + 85 | selector + 86 | '"\n expected: >=' + 87 | minCount + 88 | '\n actual: ' + 89 | actualCount 90 | ) 91 | } 92 | return true 93 | } 94 | 95 | function shouldHave(expectedCount, selector) { 96 | if (!selector) { 97 | return isArray(expectedCount) 98 | ? shouldHaveCollection(expectedCount) 99 | : shouldHaveAtLeast(1, expectedCount) 100 | } 101 | 102 | const actualCount = find(selector, rootEl).length 103 | if (actualCount !== expectedCount) { 104 | throw new Error( 105 | 'Wrong count of elements that matches "' + 106 | selector + 107 | '"\n expected: ' + 108 | expectedCount + 109 | '\n actual: ' + 110 | actualCount 111 | ) 112 | } 113 | return true 114 | } 115 | 116 | function shouldHaveCollection(selectors) { 117 | selectors.forEach(function(selector) { 118 | shouldHaveAtLeast(1, selector) 119 | }) 120 | return true 121 | } 122 | 123 | function shouldNotHave(selector) { 124 | shouldHave(0, selector) 125 | return true 126 | } 127 | 128 | function shouldContain(string) { 129 | if (!contains(string, rootEl)) { 130 | throw new Error('Expected "' + string + '" not found!') 131 | } 132 | return true 133 | } 134 | 135 | function shouldNotContain(string) { 136 | if (contains(string, rootEl)) { 137 | throw new Error('Unexpected "' + string + '" found!') 138 | } 139 | return true 140 | } 141 | 142 | function setValue(selector, string, eventData = {}) { 143 | const el = first(selector) 144 | el.value = string 145 | const inputEvent = new Event('input', eventData) 146 | const changeEvent = new Event('change', eventData) 147 | const keyupEvent = new Event('keyup', eventData) 148 | el.dispatchEvent(inputEvent) 149 | el.dispatchEvent(changeEvent) 150 | el.dispatchEvent(keyupEvent) 151 | if ( 152 | inputEvent.redraw !== false && 153 | changeEvent.redraw !== false && 154 | keyupEvent.redraw !== false 155 | ) { 156 | api.redraw() 157 | } 158 | } 159 | 160 | function trigger(eventName) { 161 | return function(selector, eventData) { 162 | const event = new Event(eventName, eventData) 163 | const el = first(selector) 164 | el.dispatchEvent(event) 165 | if (event.redraw !== false) { 166 | api.redraw() 167 | } 168 | } 169 | } 170 | 171 | function triggerKey(eventName) { 172 | const fire = trigger(eventName) 173 | return function handleEvent(selector, key, eventData = {}) { 174 | const keyCode = isString(key) ? code(key) : key 175 | const defaultEvent = { 176 | altKey: false, 177 | shiftKey: false, 178 | ctrlKey: false, 179 | type: eventName, 180 | keyCode, 181 | which: keyCode, 182 | } 183 | fire(selector, { ...defaultEvent, ...eventData }) 184 | } 185 | } 186 | 187 | shouldHave.at = { 188 | least: shouldHaveAtLeast, 189 | } 190 | 191 | api.first = first 192 | api.has = has 193 | api.contains = function(value) { 194 | return contains(value, rootEl) 195 | } 196 | api.find = function(selector) { 197 | return find(selector, rootEl) 198 | } 199 | api.setValue = setValue 200 | ;[ 201 | 'focus', 202 | 'click', 203 | 'blur', 204 | 'mousedown', 205 | 'mouseup', 206 | 'mouseover', 207 | 'mouseout', 208 | 'mouseenter', 209 | 'mouseleave', 210 | 'mousemove', 211 | 'pointerdown', 212 | 'pointerup', 213 | 'pointerover', 214 | 'pointerout', 215 | 'pointerenter', 216 | 'pointerleave', 217 | 'pointermove', 218 | 'pointercancel', 219 | 'contextmenu', 220 | ].map(function(eventName) { 221 | api[eventName] = trigger(eventName) 222 | }) 223 | api.keydown = triggerKey('keydown') 224 | api.keypress = triggerKey('keypress') 225 | api.keyup = triggerKey('keyup') 226 | api.trigger = function(selector, eventName, event, silent) { 227 | trigger(eventName)(selector, event, silent) 228 | } 229 | api.should = { 230 | not: { 231 | have: shouldNotHave, 232 | contain: shouldNotContain, 233 | }, 234 | have: shouldHave, 235 | contain: shouldContain, 236 | } 237 | api.log = function(selector, logFn = consoleLogHtml) { 238 | logFn(api.find(selector)) 239 | return api 240 | } 241 | 242 | return api 243 | } 244 | 245 | module.exports = function init(componentOrRootNode, nodeOrAttrs) { 246 | const $window = global.window = domino.createWindow('') 247 | const render = require('mithril/render/render')($window) 248 | let rootNode = { 249 | view: () => { 250 | return isComponent(componentOrRootNode) 251 | ? m(componentOrRootNode, nodeOrAttrs) 252 | : componentOrRootNode 253 | }, 254 | } 255 | 256 | const redraw = () => render($window.document.body, Vnode(rootNode)) 257 | 258 | redraw() 259 | 260 | const onremove = () => { 261 | componentOrRootNode = null 262 | redraw() 263 | } 264 | return scan({ 265 | redraw, 266 | onremove, 267 | rootEl: $window.document.body, 268 | }) 269 | } 270 | 271 | function ensureGlobals() { 272 | global.window = global.window || domino.createWindow('') 273 | global.requestAnimationFrame = global.requestAnimationFrame || global.window.requestAnimationFrame 274 | } 275 | 276 | module.exports.ensureGlobals = ensureGlobals 277 | -------------------------------------------------------------------------------- /mithril-query.d.ts: -------------------------------------------------------------------------------- 1 | declare var MithrilQuery: (...options: any[]) => MithrilQueryInstance 2 | 3 | interface KeyEventOptions { 4 | target?: any 5 | value?: any 6 | altKey?: boolean 7 | shiftKey?: boolean 8 | ctrlKey?: boolean 9 | silent?: boolean 10 | } 11 | 12 | interface MithrilQueryInstance { 13 | rootNode: any 14 | redraw: () => void 15 | first: (selector: string) => any 16 | has: (selector: string) => boolean 17 | contains: (value: string) => string 18 | find: (selector: string) => any[] 19 | setValue: (selector: string, value: string, silent?: boolean) => void 20 | focus: (selector: string, event?: Event, silent?: boolean) => void 21 | click: (selector: string, event?: Event, silent?: boolean) => void 22 | blur: (selector: string, event: Event, silent?: boolean) => void 23 | mousedown: (selector: string, event: Event, silent?: boolean) => void 24 | mouseup: (selector: string, event: Event, silent?: boolean) => void 25 | mouseover: (selector: string, event: Event, silent?: boolean) => void 26 | mouseout: (selector: string, event: Event, silent?: boolean) => void 27 | mouseenter: (selector: string, event: Event, silent?: boolean) => void 28 | mouseleave: (selector: string, event: Event, silent?: boolean) => void 29 | contextmenu: (selector: string, event: Event, silent?: boolean) => void 30 | keydown: (selector: string, key: string, event: Event, silent?: boolean) => void 31 | keypress: (selector: string, key: string, event: Event, silent?: boolean) => void 32 | keyup: (selector: string, key: string, event: Event, silent?: boolean) => void 33 | trigger: (selector: string, eventName: string, event: Event, silent?: boolean) => void 34 | shouldHave: { 35 | at: { 36 | least: (minCount: number, selector: string) => boolean 37 | } 38 | } 39 | should: { 40 | not: { 41 | have: (selector: string) => boolean 42 | contain: (value: string) => boolean 43 | } 44 | have: { 45 | (expectedCount: number, selector: string): boolean 46 | (selector: string): boolean 47 | (selectors: string[]): boolean 48 | 49 | at: { 50 | least: (minCount: number, selector: string) => boolean 51 | } 52 | } 53 | 54 | contain: (value: string) => boolean 55 | } 56 | } 57 | 58 | declare module 'mithril-query' { 59 | export = MithrilQuery 60 | } 61 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mithril-query", 3 | "version": "4.0.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "mithril-query", 9 | "version": "4.0.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "domino": "2.1.6", 13 | "pretty-html-log": "1.1.1", 14 | "yields-keycode": "1.1.0" 15 | }, 16 | "devDependencies": { 17 | "expect": "1.20.2", 18 | "mithril": "2.0.4", 19 | "mocha": "5.2.0", 20 | "ospec": "4.1.1", 21 | "prettier": "1.13.7" 22 | } 23 | }, 24 | "node_modules/ansi-styles": { 25 | "version": "3.2.1", 26 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 27 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 28 | "dependencies": { 29 | "color-convert": "^1.9.0" 30 | }, 31 | "engines": { 32 | "node": ">=4" 33 | } 34 | }, 35 | "node_modules/balanced-match": { 36 | "version": "1.0.0", 37 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 38 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 39 | "dev": true 40 | }, 41 | "node_modules/brace-expansion": { 42 | "version": "1.1.11", 43 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 44 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 45 | "dev": true, 46 | "dependencies": { 47 | "balanced-match": "^1.0.0", 48 | "concat-map": "0.0.1" 49 | } 50 | }, 51 | "node_modules/browser-stdout": { 52 | "version": "1.3.1", 53 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 54 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 55 | "dev": true 56 | }, 57 | "node_modules/chalk": { 58 | "version": "2.4.2", 59 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 60 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 61 | "dependencies": { 62 | "ansi-styles": "^3.2.1", 63 | "escape-string-regexp": "^1.0.5", 64 | "supports-color": "^5.3.0" 65 | }, 66 | "engines": { 67 | "node": ">=4" 68 | } 69 | }, 70 | "node_modules/color-convert": { 71 | "version": "1.9.3", 72 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 73 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 74 | "dependencies": { 75 | "color-name": "1.1.3" 76 | } 77 | }, 78 | "node_modules/color-name": { 79 | "version": "1.1.3", 80 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 81 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 82 | }, 83 | "node_modules/commander": { 84 | "version": "2.15.1", 85 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 86 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", 87 | "dev": true 88 | }, 89 | "node_modules/concat-map": { 90 | "version": "0.0.1", 91 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 92 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 93 | "dev": true 94 | }, 95 | "node_modules/define-properties": { 96 | "version": "1.1.2", 97 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", 98 | "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", 99 | "dev": true, 100 | "dependencies": { 101 | "foreach": "^2.0.5", 102 | "object-keys": "^1.0.8" 103 | }, 104 | "engines": { 105 | "node": ">= 0.4" 106 | } 107 | }, 108 | "node_modules/diff": { 109 | "version": "3.5.0", 110 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 111 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 112 | "dev": true, 113 | "engines": { 114 | "node": ">=0.3.1" 115 | } 116 | }, 117 | "node_modules/domino": { 118 | "version": "2.1.6", 119 | "resolved": "https://registry.npmjs.org/domino/-/domino-2.1.6.tgz", 120 | "integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==" 121 | }, 122 | "node_modules/es-abstract": { 123 | "version": "1.5.0", 124 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.5.0.tgz", 125 | "integrity": "sha1-fDKeMVSJABjeg1KxuYBnPk+486o=", 126 | "dev": true, 127 | "dependencies": { 128 | "es-to-primitive": "^1.1.0", 129 | "function-bind": "^1.0.2", 130 | "is-callable": "^1.1.1", 131 | "is-regex": "^1.0.3" 132 | }, 133 | "engines": { 134 | "node": ">= 0.4" 135 | } 136 | }, 137 | "node_modules/es-to-primitive": { 138 | "version": "1.1.1", 139 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", 140 | "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", 141 | "dev": true, 142 | "dependencies": { 143 | "is-callable": "^1.1.1", 144 | "is-date-object": "^1.0.1", 145 | "is-symbol": "^1.0.1" 146 | }, 147 | "engines": { 148 | "node": ">= 0.4" 149 | } 150 | }, 151 | "node_modules/escape-string-regexp": { 152 | "version": "1.0.5", 153 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 154 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 155 | "engines": { 156 | "node": ">=0.8.0" 157 | } 158 | }, 159 | "node_modules/expect": { 160 | "version": "1.20.2", 161 | "resolved": "https://registry.npmjs.org/expect/-/expect-1.20.2.tgz", 162 | "integrity": "sha1-1Fj+TFYAQDa64yMkFqP2Nh8E+WU=", 163 | "dev": true, 164 | "dependencies": { 165 | "define-properties": "~1.1.2", 166 | "has": "^1.0.1", 167 | "is-equal": "^1.5.1", 168 | "is-regex": "^1.0.3", 169 | "object-inspect": "^1.1.0", 170 | "object-keys": "^1.0.9", 171 | "tmatch": "^2.0.1" 172 | } 173 | }, 174 | "node_modules/foreach": { 175 | "version": "2.0.5", 176 | "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", 177 | "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", 178 | "dev": true 179 | }, 180 | "node_modules/fs.realpath": { 181 | "version": "1.0.0", 182 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 183 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 184 | "dev": true 185 | }, 186 | "node_modules/function-bind": { 187 | "version": "1.1.0", 188 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", 189 | "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=", 190 | "dev": true 191 | }, 192 | "node_modules/glob": { 193 | "version": "7.1.6", 194 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 195 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 196 | "dev": true, 197 | "dependencies": { 198 | "fs.realpath": "^1.0.0", 199 | "inflight": "^1.0.4", 200 | "inherits": "2", 201 | "minimatch": "^3.0.4", 202 | "once": "^1.3.0", 203 | "path-is-absolute": "^1.0.0" 204 | }, 205 | "engines": { 206 | "node": "*" 207 | }, 208 | "funding": { 209 | "url": "https://github.com/sponsors/isaacs" 210 | } 211 | }, 212 | "node_modules/growl": { 213 | "version": "1.10.5", 214 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 215 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 216 | "dev": true, 217 | "engines": { 218 | "node": ">=4.x" 219 | } 220 | }, 221 | "node_modules/has": { 222 | "version": "1.0.1", 223 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", 224 | "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", 225 | "dev": true, 226 | "dependencies": { 227 | "function-bind": "^1.0.2" 228 | }, 229 | "engines": { 230 | "node": ">= 0.8.0" 231 | } 232 | }, 233 | "node_modules/has-flag": { 234 | "version": "3.0.0", 235 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 236 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 237 | "engines": { 238 | "node": ">=4" 239 | } 240 | }, 241 | "node_modules/he": { 242 | "version": "1.1.1", 243 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 244 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 245 | "dev": true, 246 | "bin": { 247 | "he": "bin/he" 248 | } 249 | }, 250 | "node_modules/inflight": { 251 | "version": "1.0.5", 252 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.5.tgz", 253 | "integrity": "sha1-2zIEzVqd4ubNiQuFxuL2a89PYgo=", 254 | "dev": true, 255 | "dependencies": { 256 | "once": "^1.3.0", 257 | "wrappy": "1" 258 | } 259 | }, 260 | "node_modules/inherits": { 261 | "version": "2.0.1", 262 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", 263 | "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", 264 | "dev": true 265 | }, 266 | "node_modules/is-arrow-function": { 267 | "version": "2.0.3", 268 | "resolved": "https://registry.npmjs.org/is-arrow-function/-/is-arrow-function-2.0.3.tgz", 269 | "integrity": "sha1-Kb4sLY2UUIUri7r7Y1unuNjofsI=", 270 | "dev": true, 271 | "dependencies": { 272 | "is-callable": "^1.0.4" 273 | }, 274 | "engines": { 275 | "node": ">= 0.4" 276 | } 277 | }, 278 | "node_modules/is-boolean-object": { 279 | "version": "1.0.0", 280 | "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.0.tgz", 281 | "integrity": "sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=", 282 | "dev": true, 283 | "engines": { 284 | "node": ">= 0.4" 285 | } 286 | }, 287 | "node_modules/is-callable": { 288 | "version": "1.1.3", 289 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", 290 | "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", 291 | "dev": true, 292 | "engines": { 293 | "node": ">= 0.4" 294 | } 295 | }, 296 | "node_modules/is-date-object": { 297 | "version": "1.0.1", 298 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 299 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 300 | "dev": true, 301 | "engines": { 302 | "node": ">= 0.4" 303 | } 304 | }, 305 | "node_modules/is-equal": { 306 | "version": "1.5.3", 307 | "resolved": "https://registry.npmjs.org/is-equal/-/is-equal-1.5.3.tgz", 308 | "integrity": "sha1-Bbf6OhEiy8ccHvQc4BQtVTIBOyk=", 309 | "dev": true, 310 | "dependencies": { 311 | "has": "^1.0.1", 312 | "is-arrow-function": "^2.0.3", 313 | "is-boolean-object": "^1.0.0", 314 | "is-callable": "^1.1.3", 315 | "is-date-object": "^1.0.1", 316 | "is-generator-function": "^1.0.3", 317 | "is-number-object": "^1.0.3", 318 | "is-regex": "^1.0.3", 319 | "is-string": "^1.0.4", 320 | "is-symbol": "^1.0.1", 321 | "object.entries": "^1.0.3" 322 | }, 323 | "engines": { 324 | "node": ">= 0.4" 325 | } 326 | }, 327 | "node_modules/is-generator-function": { 328 | "version": "1.0.3", 329 | "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.3.tgz", 330 | "integrity": "sha1-03TKV+gHRE+iZYvjco7WsXSzJrE=", 331 | "dev": true, 332 | "engines": { 333 | "node": ">= 0.4" 334 | } 335 | }, 336 | "node_modules/is-number-object": { 337 | "version": "1.0.3", 338 | "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.3.tgz", 339 | "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", 340 | "dev": true, 341 | "engines": { 342 | "node": ">= 0.4" 343 | } 344 | }, 345 | "node_modules/is-regex": { 346 | "version": "1.0.3", 347 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.3.tgz", 348 | "integrity": "sha1-DVUYK9358v3ieCIK7Dp1ZCyQhjc=", 349 | "dev": true, 350 | "engines": { 351 | "node": ">= 0.4" 352 | } 353 | }, 354 | "node_modules/is-string": { 355 | "version": "1.0.4", 356 | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.4.tgz", 357 | "integrity": "sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=", 358 | "dev": true, 359 | "engines": { 360 | "node": ">= 0.4" 361 | } 362 | }, 363 | "node_modules/is-symbol": { 364 | "version": "1.0.1", 365 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", 366 | "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", 367 | "dev": true, 368 | "engines": { 369 | "node": ">= 0.4" 370 | } 371 | }, 372 | "node_modules/minimatch": { 373 | "version": "3.0.4", 374 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 375 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 376 | "dev": true, 377 | "dependencies": { 378 | "brace-expansion": "^1.1.7" 379 | }, 380 | "engines": { 381 | "node": "*" 382 | } 383 | }, 384 | "node_modules/mithril": { 385 | "version": "2.0.4", 386 | "resolved": "https://registry.npmjs.org/mithril/-/mithril-2.0.4.tgz", 387 | "integrity": "sha512-mgw+DMZlhMS4PpprF6dl7ZoeZq5GGcAuWnrg5e12MvaGauc4jzWsDZtVGRCktsiQczOEUr2K5teKbE5k44RlOg==", 388 | "dev": true, 389 | "bin": { 390 | "ospec": "ospec/bin/ospec" 391 | } 392 | }, 393 | "node_modules/mkdirp": { 394 | "version": "0.5.1", 395 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 396 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 397 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", 398 | "dev": true, 399 | "dependencies": { 400 | "minimist": "0.0.8" 401 | }, 402 | "bin": { 403 | "mkdirp": "bin/cmd.js" 404 | } 405 | }, 406 | "node_modules/mkdirp/node_modules/minimist": { 407 | "version": "0.0.8", 408 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 409 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 410 | "dev": true 411 | }, 412 | "node_modules/mocha": { 413 | "version": "5.2.0", 414 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", 415 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", 416 | "dev": true, 417 | "dependencies": { 418 | "browser-stdout": "1.3.1", 419 | "commander": "2.15.1", 420 | "debug": "3.1.0", 421 | "diff": "3.5.0", 422 | "escape-string-regexp": "1.0.5", 423 | "glob": "7.1.2", 424 | "growl": "1.10.5", 425 | "he": "1.1.1", 426 | "minimatch": "3.0.4", 427 | "mkdirp": "0.5.1", 428 | "supports-color": "5.4.0" 429 | }, 430 | "bin": { 431 | "_mocha": "bin/_mocha", 432 | "mocha": "bin/mocha" 433 | }, 434 | "engines": { 435 | "node": ">= 4.0.0" 436 | } 437 | }, 438 | "node_modules/mocha/node_modules/debug": { 439 | "version": "3.1.0", 440 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 441 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 442 | "dev": true, 443 | "dependencies": { 444 | "ms": "2.0.0" 445 | } 446 | }, 447 | "node_modules/mocha/node_modules/glob": { 448 | "version": "7.1.2", 449 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 450 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 451 | "dev": true, 452 | "dependencies": { 453 | "fs.realpath": "^1.0.0", 454 | "inflight": "^1.0.4", 455 | "inherits": "2", 456 | "minimatch": "^3.0.4", 457 | "once": "^1.3.0", 458 | "path-is-absolute": "^1.0.0" 459 | }, 460 | "engines": { 461 | "node": "*" 462 | } 463 | }, 464 | "node_modules/mocha/node_modules/minimatch": { 465 | "version": "3.0.4", 466 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 467 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 468 | "dev": true, 469 | "dependencies": { 470 | "brace-expansion": "^1.1.7" 471 | }, 472 | "engines": { 473 | "node": "*" 474 | } 475 | }, 476 | "node_modules/ms": { 477 | "version": "2.0.0", 478 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 479 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 480 | "dev": true 481 | }, 482 | "node_modules/object-inspect": { 483 | "version": "1.2.1", 484 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.2.1.tgz", 485 | "integrity": "sha1-O2Iibrj21EF1HH2PIqIP+ArJ3D8=", 486 | "dev": true 487 | }, 488 | "node_modules/object-keys": { 489 | "version": "1.0.9", 490 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.9.tgz", 491 | "integrity": "sha1-yrsSAtmnrym1Dt+s6AlLtG2l6iE=", 492 | "dev": true, 493 | "engines": { 494 | "node": ">= 0.4" 495 | } 496 | }, 497 | "node_modules/object.entries": { 498 | "version": "1.0.3", 499 | "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.3.tgz", 500 | "integrity": "sha1-9CzHU2Ok+apwN7z7O6s75P/HgCc=", 501 | "dev": true, 502 | "dependencies": { 503 | "define-properties": "^1.1.1", 504 | "es-abstract": "^1.3.2", 505 | "function-bind": "^1.0.2", 506 | "has": "^1.0.1" 507 | }, 508 | "engines": { 509 | "node": ">= 0.4" 510 | } 511 | }, 512 | "node_modules/once": { 513 | "version": "1.3.3", 514 | "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", 515 | "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", 516 | "dev": true, 517 | "dependencies": { 518 | "wrappy": "1" 519 | } 520 | }, 521 | "node_modules/ospec": { 522 | "version": "4.1.1", 523 | "resolved": "https://registry.npmjs.org/ospec/-/ospec-4.1.1.tgz", 524 | "integrity": "sha512-fgWIk1eLDidfB+ixTo3PL3HuuO6iX2LhgWxdnWwqRu2rJdpTowgeAP0RHgt3DSUVyszs964fMuq1tB7Ov2C38A==", 525 | "dev": true, 526 | "dependencies": { 527 | "glob": "^7.1.3" 528 | }, 529 | "bin": { 530 | "ospec": "bin/ospec" 531 | } 532 | }, 533 | "node_modules/path-is-absolute": { 534 | "version": "1.0.0", 535 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz", 536 | "integrity": "sha1-Jj2tpmqz8vsQv3+dJN2PPlcO+RI=", 537 | "dev": true, 538 | "engines": { 539 | "node": ">=0.10.0" 540 | } 541 | }, 542 | "node_modules/prettier": { 543 | "version": "1.13.7", 544 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.13.7.tgz", 545 | "integrity": "sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==", 546 | "dev": true, 547 | "bin": { 548 | "prettier": "bin-prettier.js" 549 | }, 550 | "engines": { 551 | "node": ">=4" 552 | } 553 | }, 554 | "node_modules/pretty-html-log": { 555 | "version": "1.1.1", 556 | "resolved": "https://registry.npmjs.org/pretty-html-log/-/pretty-html-log-1.1.1.tgz", 557 | "integrity": "sha512-7Ai/cBk764SmU9vmJL/B7wzcWu7MIQflJ0Yx1cL/kPHZXkyn7AY1Tqtfgrjo9P750G7nsSDfk12FQ+mQKqQUvg==", 558 | "dependencies": { 559 | "chalk": "^2.4.2", 560 | "commander": "^3.0.2", 561 | "prettier": "^1.18.2" 562 | }, 563 | "bin": { 564 | "pretty-html-log": "src/bin/prettty-html-log.bin.js" 565 | } 566 | }, 567 | "node_modules/pretty-html-log/node_modules/commander": { 568 | "version": "3.0.2", 569 | "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", 570 | "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" 571 | }, 572 | "node_modules/pretty-html-log/node_modules/prettier": { 573 | "version": "1.19.1", 574 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", 575 | "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", 576 | "bin": { 577 | "prettier": "bin-prettier.js" 578 | }, 579 | "engines": { 580 | "node": ">=4" 581 | } 582 | }, 583 | "node_modules/supports-color": { 584 | "version": "5.4.0", 585 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 586 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 587 | "dependencies": { 588 | "has-flag": "^3.0.0" 589 | }, 590 | "engines": { 591 | "node": ">=4" 592 | } 593 | }, 594 | "node_modules/tmatch": { 595 | "version": "2.0.1", 596 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-2.0.1.tgz", 597 | "integrity": "sha1-DFYkbzPzDaG409colauvFmYPOM8=", 598 | "dev": true 599 | }, 600 | "node_modules/wrappy": { 601 | "version": "1.0.2", 602 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 603 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 604 | "dev": true 605 | }, 606 | "node_modules/yields-keycode": { 607 | "version": "1.1.0", 608 | "resolved": "https://registry.npmjs.org/yields-keycode/-/yields-keycode-1.1.0.tgz", 609 | "integrity": "sha1-A4qYWGbLtpEOQzrF4ouDmd/8Nd8=" 610 | } 611 | }, 612 | "dependencies": { 613 | "ansi-styles": { 614 | "version": "3.2.1", 615 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 616 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 617 | "requires": { 618 | "color-convert": "^1.9.0" 619 | } 620 | }, 621 | "balanced-match": { 622 | "version": "1.0.0", 623 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 624 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 625 | "dev": true 626 | }, 627 | "brace-expansion": { 628 | "version": "1.1.11", 629 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 630 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 631 | "dev": true, 632 | "requires": { 633 | "balanced-match": "^1.0.0", 634 | "concat-map": "0.0.1" 635 | } 636 | }, 637 | "browser-stdout": { 638 | "version": "1.3.1", 639 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 640 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 641 | "dev": true 642 | }, 643 | "chalk": { 644 | "version": "2.4.2", 645 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 646 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 647 | "requires": { 648 | "ansi-styles": "^3.2.1", 649 | "escape-string-regexp": "^1.0.5", 650 | "supports-color": "^5.3.0" 651 | } 652 | }, 653 | "color-convert": { 654 | "version": "1.9.3", 655 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 656 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 657 | "requires": { 658 | "color-name": "1.1.3" 659 | } 660 | }, 661 | "color-name": { 662 | "version": "1.1.3", 663 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 664 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 665 | }, 666 | "commander": { 667 | "version": "2.15.1", 668 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 669 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", 670 | "dev": true 671 | }, 672 | "concat-map": { 673 | "version": "0.0.1", 674 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 675 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 676 | "dev": true 677 | }, 678 | "define-properties": { 679 | "version": "1.1.2", 680 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", 681 | "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", 682 | "dev": true, 683 | "requires": { 684 | "foreach": "^2.0.5", 685 | "object-keys": "^1.0.8" 686 | } 687 | }, 688 | "diff": { 689 | "version": "3.5.0", 690 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 691 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 692 | "dev": true 693 | }, 694 | "domino": { 695 | "version": "2.1.6", 696 | "resolved": "https://registry.npmjs.org/domino/-/domino-2.1.6.tgz", 697 | "integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==" 698 | }, 699 | "es-abstract": { 700 | "version": "1.5.0", 701 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.5.0.tgz", 702 | "integrity": "sha1-fDKeMVSJABjeg1KxuYBnPk+486o=", 703 | "dev": true, 704 | "requires": { 705 | "es-to-primitive": "^1.1.0", 706 | "function-bind": "^1.0.2", 707 | "is-callable": "^1.1.1", 708 | "is-regex": "^1.0.3" 709 | } 710 | }, 711 | "es-to-primitive": { 712 | "version": "1.1.1", 713 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", 714 | "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", 715 | "dev": true, 716 | "requires": { 717 | "is-callable": "^1.1.1", 718 | "is-date-object": "^1.0.1", 719 | "is-symbol": "^1.0.1" 720 | } 721 | }, 722 | "escape-string-regexp": { 723 | "version": "1.0.5", 724 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 725 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 726 | }, 727 | "expect": { 728 | "version": "1.20.2", 729 | "resolved": "https://registry.npmjs.org/expect/-/expect-1.20.2.tgz", 730 | "integrity": "sha1-1Fj+TFYAQDa64yMkFqP2Nh8E+WU=", 731 | "dev": true, 732 | "requires": { 733 | "define-properties": "~1.1.2", 734 | "has": "^1.0.1", 735 | "is-equal": "^1.5.1", 736 | "is-regex": "^1.0.3", 737 | "object-inspect": "^1.1.0", 738 | "object-keys": "^1.0.9", 739 | "tmatch": "^2.0.1" 740 | } 741 | }, 742 | "foreach": { 743 | "version": "2.0.5", 744 | "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", 745 | "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", 746 | "dev": true 747 | }, 748 | "fs.realpath": { 749 | "version": "1.0.0", 750 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 751 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 752 | "dev": true 753 | }, 754 | "function-bind": { 755 | "version": "1.1.0", 756 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", 757 | "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=", 758 | "dev": true 759 | }, 760 | "glob": { 761 | "version": "7.1.6", 762 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 763 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 764 | "dev": true, 765 | "requires": { 766 | "fs.realpath": "^1.0.0", 767 | "inflight": "^1.0.4", 768 | "inherits": "2", 769 | "minimatch": "^3.0.4", 770 | "once": "^1.3.0", 771 | "path-is-absolute": "^1.0.0" 772 | } 773 | }, 774 | "growl": { 775 | "version": "1.10.5", 776 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 777 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 778 | "dev": true 779 | }, 780 | "has": { 781 | "version": "1.0.1", 782 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", 783 | "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", 784 | "dev": true, 785 | "requires": { 786 | "function-bind": "^1.0.2" 787 | } 788 | }, 789 | "has-flag": { 790 | "version": "3.0.0", 791 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 792 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 793 | }, 794 | "he": { 795 | "version": "1.1.1", 796 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 797 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 798 | "dev": true 799 | }, 800 | "inflight": { 801 | "version": "1.0.5", 802 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.5.tgz", 803 | "integrity": "sha1-2zIEzVqd4ubNiQuFxuL2a89PYgo=", 804 | "dev": true, 805 | "requires": { 806 | "once": "^1.3.0", 807 | "wrappy": "1" 808 | } 809 | }, 810 | "inherits": { 811 | "version": "2.0.1", 812 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", 813 | "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", 814 | "dev": true 815 | }, 816 | "is-arrow-function": { 817 | "version": "2.0.3", 818 | "resolved": "https://registry.npmjs.org/is-arrow-function/-/is-arrow-function-2.0.3.tgz", 819 | "integrity": "sha1-Kb4sLY2UUIUri7r7Y1unuNjofsI=", 820 | "dev": true, 821 | "requires": { 822 | "is-callable": "^1.0.4" 823 | } 824 | }, 825 | "is-boolean-object": { 826 | "version": "1.0.0", 827 | "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.0.tgz", 828 | "integrity": "sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=", 829 | "dev": true 830 | }, 831 | "is-callable": { 832 | "version": "1.1.3", 833 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", 834 | "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", 835 | "dev": true 836 | }, 837 | "is-date-object": { 838 | "version": "1.0.1", 839 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 840 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 841 | "dev": true 842 | }, 843 | "is-equal": { 844 | "version": "1.5.3", 845 | "resolved": "https://registry.npmjs.org/is-equal/-/is-equal-1.5.3.tgz", 846 | "integrity": "sha1-Bbf6OhEiy8ccHvQc4BQtVTIBOyk=", 847 | "dev": true, 848 | "requires": { 849 | "has": "^1.0.1", 850 | "is-arrow-function": "^2.0.3", 851 | "is-boolean-object": "^1.0.0", 852 | "is-callable": "^1.1.3", 853 | "is-date-object": "^1.0.1", 854 | "is-generator-function": "^1.0.3", 855 | "is-number-object": "^1.0.3", 856 | "is-regex": "^1.0.3", 857 | "is-string": "^1.0.4", 858 | "is-symbol": "^1.0.1", 859 | "object.entries": "^1.0.3" 860 | } 861 | }, 862 | "is-generator-function": { 863 | "version": "1.0.3", 864 | "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.3.tgz", 865 | "integrity": "sha1-03TKV+gHRE+iZYvjco7WsXSzJrE=", 866 | "dev": true 867 | }, 868 | "is-number-object": { 869 | "version": "1.0.3", 870 | "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.3.tgz", 871 | "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", 872 | "dev": true 873 | }, 874 | "is-regex": { 875 | "version": "1.0.3", 876 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.3.tgz", 877 | "integrity": "sha1-DVUYK9358v3ieCIK7Dp1ZCyQhjc=", 878 | "dev": true 879 | }, 880 | "is-string": { 881 | "version": "1.0.4", 882 | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.4.tgz", 883 | "integrity": "sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=", 884 | "dev": true 885 | }, 886 | "is-symbol": { 887 | "version": "1.0.1", 888 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", 889 | "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", 890 | "dev": true 891 | }, 892 | "minimatch": { 893 | "version": "3.0.4", 894 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 895 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 896 | "dev": true, 897 | "requires": { 898 | "brace-expansion": "^1.1.7" 899 | } 900 | }, 901 | "mithril": { 902 | "version": "2.0.4", 903 | "resolved": "https://registry.npmjs.org/mithril/-/mithril-2.0.4.tgz", 904 | "integrity": "sha512-mgw+DMZlhMS4PpprF6dl7ZoeZq5GGcAuWnrg5e12MvaGauc4jzWsDZtVGRCktsiQczOEUr2K5teKbE5k44RlOg==", 905 | "dev": true 906 | }, 907 | "mkdirp": { 908 | "version": "0.5.1", 909 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 910 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 911 | "dev": true, 912 | "requires": { 913 | "minimist": "0.0.8" 914 | }, 915 | "dependencies": { 916 | "minimist": { 917 | "version": "0.0.8", 918 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 919 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 920 | "dev": true 921 | } 922 | } 923 | }, 924 | "mocha": { 925 | "version": "5.2.0", 926 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", 927 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", 928 | "dev": true, 929 | "requires": { 930 | "browser-stdout": "1.3.1", 931 | "commander": "2.15.1", 932 | "debug": "3.1.0", 933 | "diff": "3.5.0", 934 | "escape-string-regexp": "1.0.5", 935 | "glob": "7.1.2", 936 | "growl": "1.10.5", 937 | "he": "1.1.1", 938 | "minimatch": "3.0.4", 939 | "mkdirp": "0.5.1", 940 | "supports-color": "5.4.0" 941 | }, 942 | "dependencies": { 943 | "debug": { 944 | "version": "3.1.0", 945 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 946 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 947 | "dev": true, 948 | "requires": { 949 | "ms": "2.0.0" 950 | } 951 | }, 952 | "glob": { 953 | "version": "7.1.2", 954 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 955 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 956 | "dev": true, 957 | "requires": { 958 | "fs.realpath": "^1.0.0", 959 | "inflight": "^1.0.4", 960 | "inherits": "2", 961 | "minimatch": "^3.0.4", 962 | "once": "^1.3.0", 963 | "path-is-absolute": "^1.0.0" 964 | } 965 | }, 966 | "minimatch": { 967 | "version": "3.0.4", 968 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 969 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 970 | "dev": true, 971 | "requires": { 972 | "brace-expansion": "^1.1.7" 973 | } 974 | } 975 | } 976 | }, 977 | "ms": { 978 | "version": "2.0.0", 979 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 980 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 981 | "dev": true 982 | }, 983 | "object-inspect": { 984 | "version": "1.2.1", 985 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.2.1.tgz", 986 | "integrity": "sha1-O2Iibrj21EF1HH2PIqIP+ArJ3D8=", 987 | "dev": true 988 | }, 989 | "object-keys": { 990 | "version": "1.0.9", 991 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.9.tgz", 992 | "integrity": "sha1-yrsSAtmnrym1Dt+s6AlLtG2l6iE=", 993 | "dev": true 994 | }, 995 | "object.entries": { 996 | "version": "1.0.3", 997 | "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.3.tgz", 998 | "integrity": "sha1-9CzHU2Ok+apwN7z7O6s75P/HgCc=", 999 | "dev": true, 1000 | "requires": { 1001 | "define-properties": "^1.1.1", 1002 | "es-abstract": "^1.3.2", 1003 | "function-bind": "^1.0.2", 1004 | "has": "^1.0.1" 1005 | } 1006 | }, 1007 | "once": { 1008 | "version": "1.3.3", 1009 | "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", 1010 | "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", 1011 | "dev": true, 1012 | "requires": { 1013 | "wrappy": "1" 1014 | } 1015 | }, 1016 | "ospec": { 1017 | "version": "4.1.1", 1018 | "resolved": "https://registry.npmjs.org/ospec/-/ospec-4.1.1.tgz", 1019 | "integrity": "sha512-fgWIk1eLDidfB+ixTo3PL3HuuO6iX2LhgWxdnWwqRu2rJdpTowgeAP0RHgt3DSUVyszs964fMuq1tB7Ov2C38A==", 1020 | "dev": true, 1021 | "requires": { 1022 | "glob": "^7.1.3" 1023 | } 1024 | }, 1025 | "path-is-absolute": { 1026 | "version": "1.0.0", 1027 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz", 1028 | "integrity": "sha1-Jj2tpmqz8vsQv3+dJN2PPlcO+RI=", 1029 | "dev": true 1030 | }, 1031 | "prettier": { 1032 | "version": "1.13.7", 1033 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.13.7.tgz", 1034 | "integrity": "sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==", 1035 | "dev": true 1036 | }, 1037 | "pretty-html-log": { 1038 | "version": "1.1.1", 1039 | "resolved": "https://registry.npmjs.org/pretty-html-log/-/pretty-html-log-1.1.1.tgz", 1040 | "integrity": "sha512-7Ai/cBk764SmU9vmJL/B7wzcWu7MIQflJ0Yx1cL/kPHZXkyn7AY1Tqtfgrjo9P750G7nsSDfk12FQ+mQKqQUvg==", 1041 | "requires": { 1042 | "chalk": "^2.4.2", 1043 | "commander": "^3.0.2", 1044 | "prettier": "^1.18.2" 1045 | }, 1046 | "dependencies": { 1047 | "commander": { 1048 | "version": "3.0.2", 1049 | "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", 1050 | "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" 1051 | }, 1052 | "prettier": { 1053 | "version": "1.19.1", 1054 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", 1055 | "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==" 1056 | } 1057 | } 1058 | }, 1059 | "supports-color": { 1060 | "version": "5.4.0", 1061 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 1062 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 1063 | "requires": { 1064 | "has-flag": "^3.0.0" 1065 | } 1066 | }, 1067 | "tmatch": { 1068 | "version": "2.0.1", 1069 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-2.0.1.tgz", 1070 | "integrity": "sha1-DFYkbzPzDaG409colauvFmYPOM8=", 1071 | "dev": true 1072 | }, 1073 | "wrappy": { 1074 | "version": "1.0.2", 1075 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1076 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1077 | "dev": true 1078 | }, 1079 | "yields-keycode": { 1080 | "version": "1.1.0", 1081 | "resolved": "https://registry.npmjs.org/yields-keycode/-/yields-keycode-1.1.0.tgz", 1082 | "integrity": "sha1-A4qYWGbLtpEOQzrF4ouDmd/8Nd8=" 1083 | } 1084 | } 1085 | } 1086 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mithril-query", 3 | "version": "4.0.1", 4 | "description": "Query mithril virtual dom for testing purposes", 5 | "main": "index.js", 6 | "types": "mithril-query.d.ts", 7 | "scripts": { 8 | "test": "mocha test.js && mocha example/simple.test.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:MithrilJS/mithril-query.git" 13 | }, 14 | "keywords": [ 15 | "mithril", 16 | "query", 17 | "test" 18 | ], 19 | "author": { 20 | "name": "Stephan Hoyer", 21 | "email": "ste.hoyer@gmail.com" 22 | }, 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/MithrilJS/mithril-query/issues" 26 | }, 27 | "homepage": "https://github.com/MithrilJS/mithril-query", 28 | "devDependencies": { 29 | "expect": "1.20.2", 30 | "mithril": "2.0.4", 31 | "mocha": "5.2.0", 32 | "ospec": "4.1.1", 33 | "prettier": "1.13.7" 34 | }, 35 | "dependencies": { 36 | "domino": "2.1.6", 37 | "pretty-html-log": "1.1.1", 38 | "yields-keycode": "1.1.0" 39 | }, 40 | "readmeFilename": "README.md", 41 | "gitHead": "b8930b06501d2083a31f674c5fd8d4f9ffe34eb4", 42 | "_id": "mithril-query@0.1.8", 43 | "_shasum": "2a0918669b7078b5de2126cc5ed3d6a478d0bf17", 44 | "_from": "mithril-query@^0.1.2" 45 | } 46 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const mq = require('./') 5 | const m = require('mithril') 6 | const keyCode = require('yields-keycode') 7 | const expect = require('expect') 8 | const ospec = require('ospec') 9 | const BabelClassComponent = require('./fixtures/babel-class-component') 10 | const BabelClassComponentWithDestructuring = require('./fixtures/babel-class-component-with-destructuring') 11 | const WebpackBabelClassComponent = require('./fixtures/webpack-babel-transform-class-component') 12 | const WebpackBabelClassComponentWithDestructuring = require('./fixtures/webpack-babel-transform-class-component-with-destructuring') 13 | const WebpackBabelClassEsComponent = require('./fixtures/webpack-babel-transform-class-component-esmodules') 14 | const WebpackBabelClassEsComponentWithDestructuring = require('./fixtures/webpack-babel-transform-class-component-esmodules-with-destructuring') 15 | 16 | describe('mithril query', function() { 17 | describe('basic selection of things', function() { 18 | let el, 19 | out, 20 | tagEl, 21 | concatClassEl, 22 | classEl, 23 | idEl, 24 | innerString, 25 | dataAttr, 26 | booleanEl, 27 | unselected, 28 | selected, 29 | devilEl, 30 | idClassEl, 31 | arrayOfArrays, 32 | rawHtml, 33 | numbah, 34 | disabled, 35 | contentAsArray, 36 | contentAsDoubleArray, 37 | msxOutput 38 | 39 | beforeEach(function() { 40 | tagEl = m('span', 123) 41 | concatClassEl = m('.onetwo') 42 | classEl = m('.one.two') 43 | idEl = m('#two') 44 | innerString = 'Inner String' 45 | devilEl = m('.three', 'DEVIL') 46 | idClassEl = m('#three.three') 47 | arrayOfArrays = m('#arrayArray') 48 | disabled = m('[disabled]') 49 | unselected = m('option', { selected: false }) 50 | selected = m('option', { selected: true }) 51 | dataAttr = m('[data-foo=bar]') 52 | contentAsArray = m('.contentAsArray', m('.inner', [123, 'foobar'])) 53 | contentAsDoubleArray = m('.contentAsDoubleArray', [['foobar']]) 54 | rawHtml = m.trust('
') 55 | numbah = 10 56 | el = m('.root', [ 57 | tagEl, 58 | concatClassEl, 59 | classEl, 60 | innerString, 61 | idEl, 62 | devilEl, 63 | idClassEl, 64 | [[arrayOfArrays]], 65 | undefined, 66 | dataAttr, 67 | numbah, 68 | booleanEl, 69 | rawHtml, 70 | disabled, 71 | msxOutput, 72 | contentAsArray, 73 | contentAsDoubleArray, 74 | unselected, 75 | selected, 76 | ]) 77 | out = mq(el) 78 | }) 79 | 80 | it('should allow to select by selectors', function() { 81 | out.should.have('span') 82 | out.should.have('.one') 83 | out.should.have('div > .one') 84 | out.should.have('.two.one') 85 | out.should.have('#two') 86 | out.should.have('div#two') 87 | out.should.have('.three#three') 88 | out.should.have(':contains(DEVIL)') 89 | out.should.have('#arrayArray') 90 | out.should.have(':contains(123)') 91 | out.should.have(':contains(Inner String)') 92 | out.should.have('.contentAsArray :contains(123foobar)') 93 | out.should.have('.contentAsDoubleArray:contains(foobar)') 94 | out.should.have('[disabled]') 95 | out.should.have('[data-foo=bar]') 96 | out.should.not.have('[data-foo=no]') 97 | out.should.have('option[selected]') 98 | out.should.have(2, 'option') 99 | }) 100 | 101 | it('Should be able to parse identifier', function() { 102 | var output = mq(m('div', m('span#three.three'))) 103 | output.should.have('span#three') 104 | output.should.have('span.three') 105 | output.should.have('.three#three') 106 | output.should.have('#three.three') 107 | output.should.have('div > #three') 108 | output.should.have('div > span#three') 109 | output.should.have('div > span#three.three') 110 | }) 111 | 112 | describe('Should be able to parse class', function() { 113 | it('Should be able to parse multiple classes', function() { 114 | var output = mq(m('div', m('span.one.two'))) 115 | output.should.have('.one') 116 | output.should.have('.two') 117 | output.should.have('.one.two') 118 | output.should.have('.two.one') 119 | output.should.have('div > .one') 120 | output.should.have('div > .two') 121 | }) 122 | }) 123 | 124 | describe('Should be able to parse content', function() { 125 | it('Should be able to parse basic content', function() { 126 | var output = mq(m('div', m('span', 'Some simple content'))) 127 | output.should.have('span') 128 | output.should.have(':contains(Some simple content)') 129 | output.should.have('span:contains(Some simple content)') 130 | output.should.have('div > span:contains(Some simple content)') 131 | }) 132 | 133 | it('Should be able to parse array content', function() { 134 | var output = mq( 135 | m('div', [m('.simple', [123, 'simple']), m('.double', [['double']])]) 136 | ) 137 | output.should.have('.simple:contains(123simple)') 138 | output.should.have(':contains(123simple)') 139 | output.should.have('div > .simple:contains(123simple)') 140 | 141 | output.should.have('.double:contains(double)') 142 | output.should.have(':contains(double)') 143 | output.should.have('div > .double:contains(double)') 144 | }) 145 | 146 | it('Should be able to parse number content', function() { 147 | var output = mq(m('div', m('span', 123))) 148 | output.should.have('span') 149 | output.should.have(':contains(123)') 150 | output.should.have('span:contains(123)') 151 | output.should.have('div > span:contains(123)') 152 | }) 153 | }) 154 | 155 | describe('Should be able to parse attribute', function() { 156 | it('Should be able to parse basic attribute', function() { 157 | var output = mq( 158 | m('div', [m('input[disabled]'), m('span[data-foo=bar]')]) 159 | ) 160 | 161 | output.should.have('[disabled]') 162 | output.should.have('input[disabled]') 163 | output.should.have('div > input[disabled]') 164 | 165 | output.should.have('[data-foo=bar]') 166 | output.should.have('span[data-foo=bar]') 167 | output.should.have('div > span[data-foo=bar]') 168 | }) 169 | 170 | it('Should be able to parse non-string attributes', function() { 171 | var output = mq( 172 | m( 173 | 'div', 174 | m('input', { 175 | checked: true, 176 | disabled: false, 177 | number: 1234, 178 | object: {}, 179 | array: [1, 2, 3, 4], 180 | }) 181 | ) 182 | ) 183 | 184 | output.should.have('input[checked]') 185 | output.should.have('input') 186 | output.should.not.have('input[disabled]') 187 | output.should.have('input[number=1234]') 188 | output.should.have('input[object="[object Object]"]') 189 | output.should.have('input[array="1,2,3,4"]') 190 | }) 191 | }) 192 | 193 | describe('traverse from a parent to its children for sibling selectors', function() { 194 | it('adjacent sibling combinator ', function() { 195 | let output = mq(m('div', [m('div.first'), m('div.second')])) 196 | 197 | output.should.have('.first + .second') 198 | output.should.not.have('.second + .first') 199 | }) 200 | 201 | it('general sibling combinator', function() { 202 | let output = mq( 203 | m('div', [m('span'), m('p'), m('span'), m('a'), m('span')]) 204 | ) 205 | expect(output.find('p ~ span').length).toEqual(2) 206 | }) 207 | }) 208 | }) 209 | 210 | describe('events', function() { 211 | let out, onclick, onfocus, oninput, currentTarget 212 | 213 | beforeEach(function() { 214 | onclick = ospec.spy() 215 | onfocus = ospec.spy() 216 | oninput = ospec.spy(evt => (currentTarget = evt.currentTarget)) 217 | out = mq( 218 | m('input#eventEl', { 219 | onclick, 220 | onfocus, 221 | oninput, 222 | }) 223 | ) 224 | }) 225 | 226 | it('should react on click events', function() { 227 | out.click('#eventEl') 228 | expect(onclick.callCount).toBe(1) 229 | }) 230 | 231 | it('should react on focus events', function() { 232 | out.focus('#eventEl') 233 | expect(onfocus.callCount).toBe(1) 234 | }) 235 | 236 | it('should react on input events', function() { 237 | out.setValue('#eventEl', 'huhu') 238 | expect(oninput.callCount).toBe(1) 239 | const evt = oninput.args[0] 240 | expect(evt.target.value).toBe('huhu') 241 | 242 | // evt.currentTarget seems to get garbage collected to early, 243 | // so we save it in the event triggering phase and check the reference here 244 | expect(currentTarget.value).toBe('huhu') 245 | }) 246 | }) 247 | 248 | describe('contains', function() { 249 | it('should allow to select by content', function() { 250 | const out = mq(m('.containstest', ['Inner String', null, 123])) 251 | expect(out.contains('Inner String')).toBe(true) 252 | expect(out.contains(123)).toBe(true) 253 | }) 254 | 255 | it('should return false if the content was not found', function() { 256 | const out = mq(m('.containstest', ['Inner String', null, 123])) 257 | expect(out.contains('Non Existent Inner String')).toBe(false) 258 | }) 259 | 260 | describe('trusted content', function() { 261 | it('should allow to select by content', function() { 262 | const out = mq( 263 | m('.containstest', [m.trust('Trusted String
'), 'Inner String']) 264 | ) 265 | expect(out.contains('Inner String')).toBe(true) 266 | expect(out.contains('Trusted String')).toBe(true) 267 | }) 268 | }) 269 | }) 270 | }) 271 | 272 | describe('should style assertions', function() { 273 | let out 274 | 275 | beforeEach(function() { 276 | out = mq(m('.shouldtest', [m('span'), m('.one'), m('.two', 'XXXXX')])) 277 | }) 278 | 279 | it('should not throw when as expected', function() { 280 | expect(out.should.have('span')).toBe(true) 281 | expect(() => out.should.have('span')).toNotThrow() 282 | expect(() => out.should.have('.one')).toNotThrow() 283 | }) 284 | 285 | it('should throw when no element matches', function() { 286 | expect(() => out.should.have('table')).toThrow() 287 | }) 288 | 289 | it('should throw when count is not exact', function() { 290 | expect(() => out.should.have(100, 'div')).toThrow() 291 | }) 292 | 293 | it('should not throw when count is exact', function() { 294 | expect(() => out.should.have(3, 'div')).toNotThrow() 295 | }) 296 | 297 | it('should not throw when containing string', function() { 298 | expect(() => out.should.contain('XXXXX')).toNotThrow() 299 | }) 300 | 301 | it('should not throw when expecting unpresence of unpresent', function() { 302 | expect(() => out.should.not.have('table')).toNotThrow() 303 | }) 304 | 305 | it('should throw when expecting unpresence of present', function() { 306 | expect(() => out.should.not.have('span')).toThrow() 307 | }) 308 | 309 | it('should throw when containing unexpected string', function() { 310 | expect(() => out.should.not.contain('XXXXX')).toThrow() 311 | }) 312 | 313 | it('should not throw when not containing string as expected', function() { 314 | expect(() => out.should.not.contain('FOOOO')).toNotThrow() 315 | }) 316 | 317 | it('should not throw when there are enough elements', function() { 318 | expect(() => out.should.have.at.least(3, 'div')).toNotThrow() 319 | }) 320 | 321 | it('should throw when not enough elements', function() { 322 | expect(() => out.should.have.at.least(40000, 'div')).toThrow() 323 | }) 324 | 325 | it('should not throw when an array of selectors is present', function() { 326 | expect(() => out.should.have(['div', '.one', '.two'])).toNotThrow() 327 | }) 328 | 329 | it('should not throw when matching an empty array of selectors', function() { 330 | expect(() => out.should.have([])).toNotThrow() 331 | }) 332 | 333 | it('should throw when at least a selector is not present', function() { 334 | expect(() => out.should.have(['.one', 'table'])).toThrow() 335 | }) 336 | }) 337 | 338 | describe('null objects', function() { 339 | it('should ignore null objects', function() { 340 | function view() { 341 | return m('div', [null, m('input'), null]) 342 | } 343 | mq({ view }).should.have('input') 344 | expect(() => mq({ view }).should.have('input')).toNotThrow() 345 | }) 346 | }) 347 | 348 | describe('autorender', function() { 349 | describe('autorerender component', function() { 350 | let out 351 | 352 | beforeEach(function() { 353 | const component = { 354 | visible: true, 355 | oninit({ state }) { 356 | state.toggleMe = () => (state.visible = !state.visible) 357 | }, 358 | view({ state }) { 359 | return m( 360 | state.visible ? '.visible' : '.hidden', 361 | { 362 | onclick: state.toggleMe, 363 | }, 364 | 'Test' 365 | ) 366 | }, 367 | } 368 | out = mq(component) 369 | }) 370 | 371 | it('should autorender', function() { 372 | out.should.have('.visible') 373 | out.click('.visible') 374 | out.should.not.have('.visible') 375 | out.should.have('.hidden') 376 | out.click('.hidden', { redraw: false }) 377 | out.should.have('.hidden') 378 | }) 379 | 380 | it('should update boolean attributes', function() { 381 | out = mq(m('select', [m('option', { value: 'foo', selected: true })])) 382 | out.should.have('option[selected]') 383 | }) 384 | }) 385 | 386 | describe('autorerender function', function() { 387 | it('should autorender function', function() { 388 | function view({ state }) { 389 | return m( 390 | state.visible ? '.visible' : '.hidden', 391 | { 392 | onclick() { 393 | state.visible = !state.visible 394 | }, 395 | }, 396 | 'Test' 397 | ) 398 | } 399 | 400 | const out = mq({ 401 | oninit: ({ state }) => (state.visible = true), 402 | view, 403 | }) 404 | out.should.have('.visible') 405 | out.click('.visible') 406 | out.should.have('.hidden') 407 | out.click('.hidden', { redraw: false }) 408 | out.should.have('.hidden') 409 | }) 410 | }) 411 | }) 412 | 413 | describe('access root element', function() { 414 | it('should be possible to access root element', function() { 415 | const out = mq(m('div', ['foo', 'bar'])) 416 | expect(out.rootEl.children[0].tagName).toEqual('DIV') 417 | expect(out.rootEl.children[0].textContent).toEqual('foobar') 418 | }) 419 | }) 420 | 421 | describe('trigger keyboard events', function() { 422 | it('should be possible to trigger keyboard events', function() { 423 | const updateSpy = ospec.spy() 424 | const component = { 425 | visible: true, 426 | oninit: ({ state }) => { 427 | state.update = evt => { 428 | if (evt.keyCode === 123) state.visible = false 429 | if (evt.keyCode === keyCode('esc')) state.visible = true 430 | updateSpy(evt) 431 | } 432 | }, 433 | view({ state }) { 434 | return m( 435 | state.visible ? '.visible' : '.hidden', 436 | { onkeydown: state.update }, 437 | 'describe' 438 | ) 439 | }, 440 | } 441 | const out = mq(component) 442 | out.keydown('div', 'esc', { 443 | altKey: true, 444 | shiftKey: true, 445 | }) 446 | expect(updateSpy.callCount).toBe(1) 447 | const evt = updateSpy.args[0] 448 | expect(evt.altKey).toBe(true) 449 | expect(evt.shiftKey).toBe(true) 450 | expect(evt.ctrlKey).toBe(false) 451 | out.should.have('.visible') 452 | out.keydown('div', 123) 453 | out.should.have('.hidden') 454 | }) 455 | }) 456 | 457 | describe('lifecycles', function() { 458 | describe('oncreate/onupdate of vnodes', function() { 459 | it('should run oncreate', function() { 460 | let i = 0 461 | const oncreate = ospec.spy() 462 | const onupdate = ospec.spy() 463 | const out = mq({ 464 | view: () => m('span', { oncreate, onupdate }, `random stuff ${i++}`), 465 | }) 466 | expect(oncreate.callCount).toBe(1) 467 | expect(oncreate.args[0].dom.tagName).toBe('SPAN') 468 | expect(oncreate.args[0].dom.textContent).toBe('random stuff 0') 469 | expect(oncreate.args[0].dom.parentElement.tagName).toBe('BODY') 470 | expect(onupdate.callCount).toBe(0) 471 | out.redraw() 472 | expect(oncreate.callCount).toBe(1) 473 | expect(onupdate.callCount).toBe(1) 474 | expect(onupdate.args[0].dom.textContent).toBe('random stuff 1') 475 | out.redraw() 476 | expect(oncreate.callCount).toBe(1) 477 | expect(onupdate.callCount).toBe(2) 478 | expect(onupdate.args[0].dom.textContent).toBe('random stuff 2') 479 | }) 480 | }) 481 | 482 | describe('oncreate/onupdate of components', function() { 483 | it('should run oncreate', function() { 484 | const oncreate = ospec.spy() 485 | const onupdate = ospec.spy() 486 | const component = { view: () => 'comp', oncreate, onupdate } 487 | const out = mq(m(component)) 488 | expect(oncreate.callCount).toBe(1) 489 | expect(onupdate.callCount).toBe(0) 490 | out.redraw() 491 | expect(oncreate.callCount).toBe(1) 492 | expect(onupdate.callCount).toBe(1) 493 | out.redraw() 494 | expect(oncreate.callCount).toBe(1) 495 | expect(onupdate.callCount).toBe(2) 496 | }) 497 | }) 498 | 499 | describe('onremove', function() { 500 | it('should not throw when init with rendered view', function() { 501 | const out = mq(m('span', 'random stuff')) 502 | expect(out.onremove).toNotThrow 503 | }) 504 | }) 505 | }) 506 | 507 | describe('components', function() { 508 | let out, myComponent, ES6Component 509 | 510 | beforeEach(function() { 511 | myComponent = { 512 | oninit({ state, attrs }) { 513 | state.foo = attrs.data || 'bar' 514 | state.firstRender = true 515 | }, 516 | onbeforeupdate({ state }) { 517 | state.firstRender = false 518 | }, 519 | view({ state, attrs }) { 520 | return m( 521 | 'aside', 522 | { 523 | className: state.firstRender ? 'firstRender' : '', 524 | }, 525 | [attrs.data, 'hello', state.foo] 526 | ) 527 | }, 528 | } 529 | 530 | ES6Component = class { 531 | oninit({ state, attrs }) { 532 | this.hello = 'hello' 533 | state.foo = attrs.data || 'bar' 534 | state.firstRender = true 535 | } 536 | onbeforeupdate({ state, attrs }) { 537 | state.firstRender = false 538 | } 539 | view({ state, attrs }) { 540 | return m( 541 | 'aside', 542 | { 543 | className: state.firstRender ? 'firstRender' : '', 544 | }, 545 | [attrs.data, this.hello, state.foo] 546 | ) 547 | } 548 | } 549 | }) 550 | 551 | describe('plain components', function() { 552 | it('should work without args', function() { 553 | out = mq(myComponent) 554 | out.should.have('aside') 555 | out.should.contain('hello') 556 | }) 557 | 558 | it('should work with directly injected components', function() { 559 | out = mq(myComponent, { data: 'my super data' }) 560 | out.should.have('aside') 561 | out.should.contain('my super data') 562 | }) 563 | 564 | it('should work without oninit', function() { 565 | const simpleComponent = { 566 | view({ attrs }) { 567 | return m('span', attrs.data) 568 | }, 569 | } 570 | out = mq(simpleComponent, { data: 'mega' }) 571 | out.should.have('span') 572 | out.should.contain('mega') 573 | }) 574 | 575 | it('should call onremove on globalonremove', function(done) { 576 | myComponent.onremove = function() { 577 | done() 578 | } 579 | const out = mq(myComponent) 580 | out.onremove() 581 | }) 582 | }) 583 | 584 | describe('closure components', function() { 585 | function closureComponent({ attrs }) { 586 | return { 587 | view() { 588 | return m('div', 'Hello from ' + attrs.name) 589 | }, 590 | } 591 | } 592 | 593 | it('should support it as arguments', function() { 594 | out = mq(closureComponent, { name: 'Homer' }) 595 | out.should.have('div:contains(Hello from Homer)') 596 | }) 597 | 598 | it('should support it if embedded', function() { 599 | out = mq(m('aside', m(closureComponent, { name: 'Homer' }))) 600 | out.should.have('div:contains(Hello from Homer)') 601 | }) 602 | }) 603 | 604 | describe('es6 components', function() { 605 | it('should work without args', function() { 606 | out = mq(ES6Component) 607 | out.should.have('aside') 608 | out.should.contain('hello') 609 | }) 610 | 611 | it('should work with directly injected components', function() { 612 | out = mq(ES6Component, { data: 'my super data' }) 613 | out.should.have('aside') 614 | out.should.contain('my super data') 615 | }) 616 | 617 | it('should work without oninit', function() { 618 | class SimpleES6Component { 619 | view({ attrs }) { 620 | return m('div', 'Hello from ' + attrs.name) 621 | } 622 | } 623 | out = mq(SimpleES6Component, { name: 'Homer' }) 624 | out.should.have('div:contains(Hello from Homer)') 625 | }) 626 | 627 | it('should call onremove on globalonremove', function(done) { 628 | ES6Component.prototype.onremove = function() { 629 | done() 630 | } 631 | const out = mq(ES6Component) 632 | out.onremove() 633 | }) 634 | }) 635 | 636 | describe('babel transpiled es6 class components', function() { 637 | it('should work with simple components', function() { 638 | const out = mq(BabelClassComponent) 639 | out.should.have('div:contains(hello)') 640 | }) 641 | 642 | it('should work with components with destructured options', function() { 643 | const out = mq(BabelClassComponentWithDestructuring) 644 | out.should.have('div:contains(hello)') 645 | }) 646 | 647 | it('should work with transformed components in Webpack', function() { 648 | const out = mq(WebpackBabelClassComponent) 649 | out.should.have('div:contains(hello)') 650 | }) 651 | 652 | it('should work with transformed components with destructured options in Webpack', function() { 653 | const out = mq(WebpackBabelClassComponentWithDestructuring) 654 | out.should.have('div:contains(hello)') 655 | }) 656 | 657 | it('should work with transformed (useESModules) components in Webpack', function() { 658 | const out = mq(WebpackBabelClassEsComponent) 659 | out.should.have('div:contains(hello)') 660 | }) 661 | 662 | it('should work with transformed (useESModules) components with destructured options in Webpack', function() { 663 | const out = mq(WebpackBabelClassEsComponentWithDestructuring) 664 | out.should.have('div:contains(hello)') 665 | }) 666 | }) 667 | 668 | describe('es6 instantiated component', function() { 669 | it('should work without args', function() { 670 | out = mq(new ES6Component()) 671 | out.should.have('aside') 672 | out.should.contain('hello') 673 | }) 674 | 675 | it('should work with directly injected components', function() { 676 | out = mq(new ES6Component(), { data: 'my super data' }) 677 | out.should.have('aside') 678 | out.should.contain('my super data') 679 | }) 680 | 681 | it('should work without oninit', function() { 682 | class SimpleES6Component { 683 | view({ attrs }) { 684 | return m('div', 'Hello from ' + attrs.name) 685 | } 686 | } 687 | out = mq(new SimpleES6Component(), { name: 'Homer' }) 688 | out.should.have('div:contains(Hello from Homer)') 689 | }) 690 | 691 | it('should call Ponremove on globalonremove', function(done) { 692 | ES6Component.prototype.onremove = function() { 693 | done() 694 | } 695 | const out = mq(new ES6Component()) 696 | out.onremove() 697 | }) 698 | }) 699 | 700 | describe('embedded components', function() { 701 | it('should work without args', function() { 702 | out = mq( 703 | m( 704 | 'div', 705 | m({ 706 | view() { 707 | return m('strong', 'bar') 708 | }, 709 | }) 710 | ) 711 | ) 712 | out.should.have('strong') 713 | out.should.contain('bar') 714 | }) 715 | 716 | it('should work with args', function() { 717 | out = mq(m('span', m(myComponent, { data: 'my little data' }))) 718 | out.should.have('aside') 719 | out.should.contain('my little data') 720 | }) 721 | 722 | it('should work without oninit', function() { 723 | const simpleComponent = { 724 | view({ attrs }) { 725 | return m('span', attrs.data) 726 | }, 727 | } 728 | out = mq(m('div', m(simpleComponent, { data: 'mega' }))) 729 | out.should.have('span') 730 | out.should.contain('mega') 731 | }) 732 | 733 | it('should call onremove on globalonremove', function(done) { 734 | myComponent.onremove = function() { 735 | done() 736 | } 737 | out = mq(m('span', m(myComponent))) 738 | out.onremove() 739 | }) 740 | }) 741 | 742 | describe('embedded es6 components', function() { 743 | it('should work without args', function() { 744 | out = mq( 745 | class { 746 | view() { 747 | return m( 748 | class { 749 | view() { 750 | return m('strong', 'bar') 751 | } 752 | } 753 | ) 754 | } 755 | } 756 | ) 757 | out.should.have('strong') 758 | out.should.contain('bar') 759 | }) 760 | 761 | it('should work with args', function() { 762 | out = mq(m('span', m(ES6Component, { data: 'test-data' }))) 763 | out.should.have('aside') 764 | out.should.contain('test-data') 765 | }) 766 | 767 | it('should work without oninit', function() { 768 | class SimpleES6Component { 769 | view({ attrs }) { 770 | return m('span', attrs.data) 771 | } 772 | } 773 | out = mq(m('div', m(SimpleES6Component, { data: 'mega' }))) 774 | out.should.have('span') 775 | out.should.contain('mega') 776 | }) 777 | 778 | it('should call onremove on globalonremove', function(done) { 779 | ES6Component.prototype.onremove = function() { 780 | done() 781 | } 782 | out = mq(m('span', m(ES6Component))) 783 | out.onremove() 784 | }) 785 | }) 786 | 787 | describe('embedded es6 instantiated components', function() { 788 | it('should work without args', function() { 789 | class C1 { 790 | view() { 791 | return m('strong', 'bar') 792 | } 793 | } 794 | class C2 { 795 | view() { 796 | return m(C1) 797 | } 798 | } 799 | out = mq(new C2()) 800 | out.should.have('strong') 801 | out.should.contain('bar') 802 | }) 803 | 804 | it('should work with args', function() { 805 | out = mq(m('span', m(new ES6Component(), { data: 'test-data' }))) 806 | out.should.have('aside') 807 | out.should.contain('test-data') 808 | }) 809 | 810 | it('should work without oninit', function() { 811 | class SimpleES6Component { 812 | view({ attrs }) { 813 | return m('span', attrs.data) 814 | } 815 | } 816 | out = mq(m('div', m(new SimpleES6Component(), { data: 'mega' }))) 817 | out.should.have('span') 818 | out.should.contain('mega') 819 | }) 820 | 821 | it('should call onremove on globalonremove', function(done) { 822 | ES6Component.prototype.onremove = function() { 823 | done() 824 | } 825 | out = mq(m('span', m(new ES6Component()))) 826 | out.onremove() 827 | }) 828 | }) 829 | 830 | describe('state', function() { 831 | it('should preserve components state', function() { 832 | out = mq({ view: () => m('div', m(myComponent, 'haha')) }) 833 | out.should.have('aside.firstRender') 834 | out.redraw() 835 | out.should.not.have('aside.firstRender') 836 | }) 837 | 838 | it('should preserve es6 component state', function() { 839 | out = mq({ view: () => m('div', m(ES6Component, 'haha')) }) 840 | out.should.have('aside.firstRender') 841 | out.redraw() 842 | out.should.not.have('aside.firstRender') 843 | }) 844 | }) 845 | 846 | describe('state with multiple of same elements', function() { 847 | it('should preserve components state for every used component', function() { 848 | out = mq({ view: () => m('div', [m(myComponent), m(myComponent)]) }) 849 | out.should.have(2, 'aside.firstRender') 850 | out.redraw() 851 | out.should.not.have('aside.firstRender') 852 | }) 853 | 854 | it('should preserve es6 component state with multiples of the same element', function() { 855 | out = mq({ view: () => m('div', [m(ES6Component), m(ES6Component)]) }) 856 | out.should.have(2, 'aside.firstRender') 857 | out.redraw() 858 | out.should.not.have('aside.firstRender') 859 | }) 860 | }) 861 | 862 | describe('components that return components', function() { 863 | it('should work', function() { 864 | out = mq( 865 | m( 866 | 'div', 867 | m({ 868 | view() { 869 | return m(myComponent) 870 | }, 871 | }) 872 | ) 873 | ) 874 | out.should.have('aside.firstRender') 875 | }) 876 | it('should work with child selectors', function() { 877 | out = mq( 878 | m( 879 | 'div', 880 | m({ 881 | view() { 882 | return m('.foo', m(myComponent, 'kiki')) 883 | }, 884 | }) 885 | ) 886 | ) 887 | out.should.have('.foo aside.firstRender') 888 | }) 889 | }) 890 | 891 | describe('component with leading array', function() { 892 | it('should be able to query children within leading array component', function() { 893 | let comp1 = { view: () => m('.comp1', m(comp2)) } 894 | let comp2 = { view: () => [m('.comp2')] } 895 | let output = mq(m(comp1)) 896 | output.should.have('.comp1 .comp2') // Nope! 897 | }) 898 | }) 899 | 900 | describe('initialization', function() { 901 | it('should copy init args to state', function() { 902 | const myComponent = { 903 | label: 'foobar', 904 | view({ state }) { 905 | return m('div', state.label) 906 | }, 907 | } 908 | out = mq(myComponent) 909 | out.should.contain('foobar') 910 | }) 911 | 912 | it('should initialize all nested components', function() { 913 | let oninit = 0 914 | let view = 0 915 | 916 | const myComponent = { 917 | oninit() { 918 | oninit++ 919 | }, 920 | view({ children }) { 921 | view++ 922 | return m('i', children) 923 | }, 924 | } 925 | 926 | mq(m(myComponent, m(myComponent, m(myComponent)))) 927 | 928 | expect(oninit).toBe(3) 929 | expect(view).toBe(3) 930 | }) 931 | 932 | it('should ignore components that returns null', function() { 933 | const nullComponent = { 934 | view() { 935 | return null 936 | }, 937 | } 938 | mq(m(nullComponent, m(myComponent))).should.not.have('aside.firstRender') 939 | }) 940 | }) 941 | 942 | it('should not confuse component instance index on redraw', function() { 943 | let showFirst = true 944 | 945 | const first = { view: () => m('div.first') } 946 | const second = { view: () => m('div.second') } 947 | 948 | var output = mq({ 949 | view: () => m('div', [showFirst ? m(first) : m(second)]), 950 | }) 951 | 952 | output.should.have('div.first') 953 | output.should.not.have('div.second') 954 | 955 | showFirst = false 956 | output.redraw() 957 | 958 | output.should.not.have('div.first') 959 | output.should.have('div.second') 960 | }) 961 | }) 962 | 963 | describe('Logging', function() { 964 | it('should log', function(done) { 965 | const span = m('span', m('strong.tick', 'huhu'), m('em#tack', 'haha')) 966 | function logFn(nodes) { 967 | expect(nodes.length).toEqual(1) 968 | done() 969 | } 970 | const out = mq({ view: () => m('div', span, m('.bla', 'blup')) }) 971 | out.log('span', logFn) 972 | }) 973 | }) 974 | 975 | describe('Elements with nested arrays', function() { 976 | it('should flatten', function() { 977 | mq(m('.foo', ['bar', [m('.baz')]])).should.have('.foo .baz') 978 | mq(m('.foo', [[m('bar')]])).should.have('.foo bar') 979 | }) 980 | }) 981 | 982 | describe('keys', function() { 983 | it('should distinguish components with different keys', function() { 984 | const firstComponent = { 985 | view() { 986 | return m('.first') 987 | }, 988 | } 989 | const secondComponent = { 990 | view() { 991 | return m('.second') 992 | }, 993 | } 994 | let i = 0 995 | const rootComponent = { 996 | view() { 997 | return m(i === 0 ? firstComponent : secondComponent, { key: i }) 998 | }, 999 | } 1000 | const out = mq(rootComponent) 1001 | out.should.have('.first') 1002 | 1003 | i = 1 1004 | out.redraw() 1005 | out.should.have('.second') 1006 | }) 1007 | }) 1008 | --------------------------------------------------------------------------------