From d80baaa19f14bf0857626675248459e303ab17d1 Mon Sep 17 00:00:00 2001 From: Tylor Steinberger Date: Tue, 13 Dec 2016 20:41:14 -0500 Subject: [PATCH 1/4] docs(CHANGELOG): append to changelog --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7d3d96..41e87aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + +# [6.2.0](https://github.com/motorcyclejs/dom/compare/v6.1.0...v6.2.0) (2016-12-14) + + +### Bug Fixes + +* **virtual-dom:** fix test node patching errors ([#110](https://github.com/motorcyclejs/dom/issues/110)) ([68f1a41](https://github.com/motorcyclejs/dom/commit/68f1a41)) + + + # [6.1.0](https://github.com/motorcyclejs/dom/compare/v6.0.0...v6.1.0) (2016-12-13) @@ -22,7 +32,6 @@ * dom: VNode shape no longer has .sel, but .tagName, .className, and .id. Events are no longer mutated to point to a different currentTarget. Parent elements will receive non-bubbling events originating from child elements. -`makeDomDriver(container)` - container **must** be an HTMLElement and not just a selector. From 538917d48a25e6a0b660b6b743071c85441e8c0e Mon Sep 17 00:00:00 2001 From: Tylor Steinberger Date: Wed, 14 Dec 2016 14:30:49 -0500 Subject: [PATCH 2/4] Add support for CSS variables in virtual-dom style module (#113) feat(style): add support for css variables User browserify and karma to run browser test in Chrome and Firefox. --- .gitignore | 1 + .travis.yml | 12 ++++ karma.conf.js | 29 ++++++++ package.json | 14 +++- src/dom-driver/index.ts | 2 +- src/dom-driver/makeDomDriver.ts | 2 +- src/dom-driver/mockDomSource.ts | 2 +- src/dom-driver/vNodeWrapper.ts | 2 +- src/index.ts | 2 +- src/modules/IsolateModule.ts | 2 +- src/modules/attributes.ts | 2 +- src/modules/dataset.ts | 2 +- src/modules/hero.ts | 2 +- src/modules/index.ts | 2 +- src/modules/props.ts | 2 +- src/modules/style.ts | 15 ++-- src/types/DomSource.ts | 2 +- src/types/Events.ts | 2 +- src/types/index.ts | 2 +- src/types/tagNames.ts | 2 +- src/virtual-dom/MotorcycleVNode.ts | 2 +- src/virtual-dom/index.ts | 2 +- src/virtual-dom/init.ts | 2 +- test/driver/MotorcycleDomSource.ts | 29 +++++--- test/driver/index.ts | 5 ++ test/driver/integration/events.ts | 12 ++-- test/driver/integration/index.ts | 3 + test/driver/integration/isolation.ts | 13 +--- test/driver/issue-105.ts | 3 +- test/driver/mockDomSource.ts | 4 +- test/driver/vNodeWrapper.ts | 2 +- test/helpers/fake-raf.ts | 2 +- test/helpers/index.ts | 2 +- test/index.ts | 5 ++ test/modules/IsolateModule.ts | 2 +- test/modules/index.ts | 3 + test/modules/style.ts | 104 +++++++++++++++++++++++++++ test/virtual-dom/index.ts | 1 + tslint.json | 1 + 39 files changed, 236 insertions(+), 62 deletions(-) create mode 100644 karma.conf.js create mode 100644 test/driver/index.ts create mode 100644 test/driver/integration/index.ts create mode 100644 test/index.ts create mode 100644 test/modules/index.ts create mode 100644 test/virtual-dom/index.ts diff --git a/.gitignore b/.gitignore index c6a55d3..0a64c6b 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ jspm_packages # generated files lib .tmp +test/bundle.js diff --git a/.travis.yml b/.travis.yml index 61103f7..f347a45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,16 @@ language: node_js + +dist: trusty +sudo: false + +addons: + firefox: latest + node_js: - 6 - 7 + +before_install: + - export CHROME_BIN=chromium-browser + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..bc9105b --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,29 @@ +module.exports = function (config) { + const configuration = + { + files: [ + 'test/bundle.js' + ], + + frameworks: [ + 'mocha' + ], + + customLaunchers: { + Chrome_travis_ci: { + base: 'Chrome', + flags: ['--no-sandbox'] + } + }, + + browsers: [ + 'Firefox', + 'Chrome', + ], + } + + if (process.env.TRAVIS) + configuration.browsers = ['Chrome_travis_ci', 'Firefox']; + + config.set(configuration); +} diff --git a/package.json b/package.json index ed6322d..eda629a 100644 --- a/package.json +++ b/package.json @@ -18,17 +18,24 @@ "@types/hyperscript": "0.0.1", "@types/mocha": "^2.2.33", "@types/node": "0.0.2", + "browserify": "^13.1.1", "commitizen": "^2.8.6", "conventional-changelog-cli": "^1.2.0", "cz-conventional-changelog": "^1.2.0", + "es6-map": "^0.1.4", "ghooks": "^1.3.2", "hyperscript": "^2.0.2", "jsdom": "^9.8.3", "jsdom-global": "^2.1.0", + "karma": "^1.3.0", + "karma-chrome-launcher": "^2.0.0", + "karma-firefox-launcher": "^1.0.0", + "karma-mocha": "^1.3.0", "mocha": "^3.2.0", "ts-node": "^1.7.0", + "tsify": "^2.0.3", "tslint": "^4.0.2", - "typescript": "^2.2.0-dev.20161204", + "typescript": "^2.1.4", "validate-commit-msg": "^2.8.2" }, "homepage": "https://github.com/motorcyclejs/dom#readme", @@ -59,9 +66,10 @@ "preversion": "npm run build", "release:major": "npm version major -m 'chore(package): v%s'", "release:minor": "npm version minor -m 'chore(package): v%s'", - "test": "npm run test:lint && npm run test:unit", + "test": "npm run test:lint && npm run test:unit && npm run test:browser", + "test:browser": "browserify -p [ tsify -p test/tsconfig.json ] test/index.ts -o test/bundle.js && karma start --single-run", "test:debug": "TS_NODE_PROJECT=test/tsconfig.json node --debug-brk --inspect ./node_modules/.bin/_mocha test/**/*.ts", - "test:lint": "tslint src/**/*.ts src/*.ts", + "test:lint": "tslint src/**/*.ts src/*.ts test/*.ts test/**/*.ts test/**/**/*.ts test/**/**/**/*.ts", "test:unit": "TS_NODE_PROJECT=test/tsconfig.json mocha test/**/*.ts test/**/**/*.ts" }, "typings": "lib/es2015/index.d.ts", diff --git a/src/dom-driver/index.ts b/src/dom-driver/index.ts index 3ef1f2b..088637c 100644 --- a/src/dom-driver/index.ts +++ b/src/dom-driver/index.ts @@ -1,2 +1,2 @@ export * from './makeDomDriver'; -export * from './mockDomSource'; \ No newline at end of file +export * from './mockDomSource'; diff --git a/src/dom-driver/makeDomDriver.ts b/src/dom-driver/makeDomDriver.ts index c2b5fba..92ab382 100644 --- a/src/dom-driver/makeDomDriver.ts +++ b/src/dom-driver/makeDomDriver.ts @@ -47,4 +47,4 @@ function vNodeToElement(vNode: VNode): HTMLElement { export interface DomDriverOptions { modules: Array; -} \ No newline at end of file +} diff --git a/src/dom-driver/mockDomSource.ts b/src/dom-driver/mockDomSource.ts index e4b8772..8f5c105 100644 --- a/src/dom-driver/mockDomSource.ts +++ b/src/dom-driver/mockDomSource.ts @@ -70,4 +70,4 @@ export class MockedDomSource implements DomSource { export function mockDomSource(mockConfig: MockConfig): MockedDomSource { return new MockedDomSource(mockConfig); -} \ No newline at end of file +} diff --git a/src/dom-driver/vNodeWrapper.ts b/src/dom-driver/vNodeWrapper.ts index 8a598d2..9acd618 100644 --- a/src/dom-driver/vNodeWrapper.ts +++ b/src/dom-driver/vNodeWrapper.ts @@ -35,4 +35,4 @@ export function vNodeWrapper(rootElement: HTMLElement): (vNode: VNode) => VNode void 0, ); }; -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index aecfd03..f8055b1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ export * from './types'; export * from './virtual-dom'; export * from './modules'; -export * from './dom-driver'; \ No newline at end of file +export * from './dom-driver'; diff --git a/src/modules/IsolateModule.ts b/src/modules/IsolateModule.ts index ce8cad8..8104e7c 100644 --- a/src/modules/IsolateModule.ts +++ b/src/modules/IsolateModule.ts @@ -39,4 +39,4 @@ function addScopeToChildren(children: HTMLCollection, scope: string) { function scopeFromVNode(vNode: VNode) { return vNode.data && vNode.data.isolate || ``; -} \ No newline at end of file +} diff --git a/src/modules/attributes.ts b/src/modules/attributes.ts index 71c47f2..6947ada 100644 --- a/src/modules/attributes.ts +++ b/src/modules/attributes.ts @@ -47,4 +47,4 @@ function updateAttrs(oldVnode: VNode, vnode: VNode) { export const AttrsModule: Module = { update: updateAttrs, create: updateAttrs, -}; \ No newline at end of file +}; diff --git a/src/modules/dataset.ts b/src/modules/dataset.ts index 4f0c4a7..509479f 100644 --- a/src/modules/dataset.ts +++ b/src/modules/dataset.ts @@ -21,4 +21,4 @@ function updateDataset(oldVnode: VNode, vnode: VNode) { export const DatasetModule: Module = { create: updateDataset, update: updateDataset, -}; \ No newline at end of file +}; diff --git a/src/modules/hero.ts b/src/modules/hero.ts index dfe229a..a4ca59e 100644 --- a/src/modules/hero.ts +++ b/src/modules/hero.ts @@ -188,4 +188,4 @@ export const HeroModule: Module = { create, destroy, post, -}; \ No newline at end of file +}; diff --git a/src/modules/index.ts b/src/modules/index.ts index f551540..290c71d 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -4,4 +4,4 @@ export * from './dataset'; export * from './hero'; export * from './props'; export * from './style'; -export * from './IsolateModule'; \ No newline at end of file +export * from './IsolateModule'; diff --git a/src/modules/props.ts b/src/modules/props.ts index c7c79d8..4bbac5b 100644 --- a/src/modules/props.ts +++ b/src/modules/props.ts @@ -26,4 +26,4 @@ function updateProps(oldVnode: VNode, vnode: VNode) { export const PropsModule: Module = { create: updateProps, update: updateProps, -}; \ No newline at end of file +}; diff --git a/src/modules/style.ts b/src/modules/style.ts index 918bd68..89c4cf3 100644 --- a/src/modules/style.ts +++ b/src/modules/style.ts @@ -23,7 +23,7 @@ function setValueOnNextFrame(obj: any, prop: string, value: any) { function updateStyle(formerVNode: VNode, vNode: VNode): void { let styleValue: any; let key: string; - let element: any = vNode.elm; + let element: HTMLElement = vNode.elm; let formerStyle: any = (formerVNode.data as any).style; let style: any = (vNode.data as any).style; @@ -37,7 +37,10 @@ function updateStyle(formerVNode: VNode, vNode: VNode): void { for (key in formerStyle) if (!style[key]) - element.style[key] = ''; + if (key.startsWith('--')) + element.style.removeProperty(key); + else + (element.style as any)[key] = ''; for (key in style) { styleValue = style[key]; @@ -50,7 +53,11 @@ function updateStyle(formerVNode: VNode, vNode: VNode): void { setValueOnNextFrame((element as any).style, key, styleValue); } } else if (key !== 'remove' && styleValue !== formerStyle[key]) { - element.style[key] = styleValue; + if (key.startsWith('--')) { + element.style.setProperty(key, styleValue); + } + else + (element.style as any)[key] = styleValue; } } } @@ -112,4 +119,4 @@ export const StyleModule: Module = { update: updateStyle, destroy: applyDestroyStyle, remove: applyRemoveStyle, -}; \ No newline at end of file +}; diff --git a/src/types/DomSource.ts b/src/types/DomSource.ts index 135c0ae..2df86b6 100644 --- a/src/types/DomSource.ts +++ b/src/types/DomSource.ts @@ -191,4 +191,4 @@ export interface DomSource { // vrdisplaypresentchange(options?: EventsFnOptions): Stream; // waiting(options?: EventsFnOptions): Stream; // wheel(options?: EventsFnOptions): Stream; -} \ No newline at end of file +} diff --git a/src/types/Events.ts b/src/types/Events.ts index a7ddff3..d2776e8 100644 --- a/src/types/Events.ts +++ b/src/types/Events.ts @@ -316,4 +316,4 @@ export type StandardEvents = 'vrdisplaydisconnected' | // Event 'vrdisplaypresentchange' | // Event 'waiting' | // Event - 'wheel'; // WheelEvent \ No newline at end of file + 'wheel'; // WheelEvent diff --git a/src/types/index.ts b/src/types/index.ts index 1fdce05..c98b7d9 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,4 @@ export * from './DomSource'; export * from './Events'; export * from './tagNames'; -export * from './virtual-dom'; \ No newline at end of file +export * from './virtual-dom'; diff --git a/src/types/tagNames.ts b/src/types/tagNames.ts index 88f2dad..088d757 100644 --- a/src/types/tagNames.ts +++ b/src/types/tagNames.ts @@ -33,4 +33,4 @@ export type SvgTagNames = 'svg' | 'linearGradient' | 'marker' | 'mask' | 'metadata' | 'missingGlyph' | 'mpath' | 'path' | 'pattern' | 'polygon' | 'polyline' | 'radialGradient' | 'rect' | 'script' | 'set' | 'stop' | 'style' | 'switch' | 'symbol' | 'text' | 'textPath' | 'title' | - 'tref' | 'tspan' | 'use' | 'view' | 'vkern'; \ No newline at end of file + 'tref' | 'tspan' | 'use' | 'view' | 'vkern'; diff --git a/src/virtual-dom/MotorcycleVNode.ts b/src/virtual-dom/MotorcycleVNode.ts index 62c47a0..3c35773 100644 --- a/src/virtual-dom/MotorcycleVNode.ts +++ b/src/virtual-dom/MotorcycleVNode.ts @@ -24,4 +24,4 @@ export class MotorcycleVNode implements VNode { undefined, ); } -} \ No newline at end of file +} diff --git a/src/virtual-dom/index.ts b/src/virtual-dom/index.ts index c7f6ff6..98feef7 100644 --- a/src/virtual-dom/index.ts +++ b/src/virtual-dom/index.ts @@ -1,4 +1,4 @@ export * from './helpers/h'; export * from './helpers/hyperscript'; export * from './helpers/svg'; -export * from './init'; \ No newline at end of file +export * from './init'; diff --git a/src/virtual-dom/init.ts b/src/virtual-dom/init.ts index 5406161..dcc3822 100644 --- a/src/virtual-dom/init.ts +++ b/src/virtual-dom/init.ts @@ -286,4 +286,4 @@ export function init( for (let i = 0; i < cbs.post.length; ++i) cbs.post[i](); return vNode; }; -} \ No newline at end of file +} diff --git a/test/driver/MotorcycleDomSource.ts b/test/driver/MotorcycleDomSource.ts index acd30e8..3832ffe 100644 --- a/test/driver/MotorcycleDomSource.ts +++ b/test/driver/MotorcycleDomSource.ts @@ -20,14 +20,14 @@ describe('MotorcycleDomSource', () => { const domSource: DomSource = new MotorcycleDomSource(empty(), []); const namespace = domSource.select('hello').namespace(); - assert.deepStrictEqual(namespace, ['hello']); + assert.deepEqual(namespace, ['hello']); }); it('does not append to namespace when given `:root`', () => { const domSource: DomSource = new MotorcycleDomSource(empty(), []); const namespace = domSource.select(':root').namespace(); - assert.deepStrictEqual(namespace, []); + assert.deepEqual(namespace, []); }); }); @@ -70,17 +70,26 @@ describe('MotorcycleDomSource', () => { }); }); - it('returns svg elements', () => { + it('returns svg elements', (done) => { const svgElement = document.createElementNS(`http://www.w3.org/2000/svg`, 'svg'); - svgElement.className = 'triangle'; + svgElement.setAttribute('className', 'triangle'); + + try { + svgElement.classList.add('triangle'); + } catch (e) { + // done(); + } const element = h('div', svgElement); const domSource = new MotorcycleDomSource(just(element), ['.triangle']); - return domSource.elements().observe(elements => { - assert.strictEqual(elements.length, 1); - assert.strictEqual(elements[0], svgElement); - }); + domSource.elements() + .observe(elements => { + assert.strictEqual(elements.length, 1); + assert.strictEqual(elements[0], svgElement); + done(); + }) + .catch(done); }); }); }); @@ -264,7 +273,7 @@ describe('MotorcycleDomSource', () => { it('returns a new DomSource with amended namespace', () => { const domSource = new MotorcycleDomSource(just(h('div')), []); - assert.deepStrictEqual(domSource.isolateSource(domSource, 'hello').namespace(), ['$$MOTORCYCLEDOM$$-hello']); + assert.deepEqual(domSource.isolateSource(domSource, 'hello').namespace(), ['$$MOTORCYCLEDOM$$-hello']); }); }); @@ -313,4 +322,4 @@ describe('MotorcycleDomSource', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/test/driver/index.ts b/test/driver/index.ts new file mode 100644 index 0000000..09b48a5 --- /dev/null +++ b/test/driver/index.ts @@ -0,0 +1,5 @@ +import './issue-105'; +import './mockDomSource'; +import './MotorcycleDomSource'; +import './vNodeWrapper'; +import './integration'; diff --git a/test/driver/integration/events.ts b/test/driver/integration/events.ts index eeb997c..19b73f1 100644 --- a/test/driver/integration/events.ts +++ b/test/driver/integration/events.ts @@ -110,7 +110,6 @@ describe('DOMSource.events()', function () { DOM: makeDomDriver(createRenderTarget('parent-001')), }); - // Make assertions sources.DOM.select('#parent-001').events('click').observe((ev: Event) => { assert.strictEqual(ev.type, 'click'); @@ -142,7 +141,6 @@ describe('DOMSource.events()', function () { DOM: makeDomDriver(createRenderTarget('parent-002')), }); - // Make assertions sources.DOM.select('#myElementId').events('click').observe((ev: Event) => { assert.strictEqual(ev.type, 'click'); @@ -177,7 +175,6 @@ describe('DOMSource.events()', function () { DOM: makeDomDriver(createRenderTarget()), }); - // Make assertions sources.DOM.events('click').observe((ev: Event) => { assert.strictEqual(ev.type, 'click'); @@ -216,7 +213,6 @@ describe('DOMSource.events()', function () { DOM: makeDomDriver(createRenderTarget()), }); - // Make assertions sources.DOM.select('.foo').select('.bar').events('click').observe((ev: Event) => { assert.strictEqual(ev.type, 'click'); @@ -257,7 +253,6 @@ describe('DOMSource.events()', function () { DOM: makeDomDriver(createRenderTarget()), }); - // Make assertions sources.DOM.select('.clickable').events('click').take(1) .observe((ev: Event) => { @@ -303,7 +298,6 @@ describe('DOMSource.events()', function () { DOM: makeDomDriver(createRenderTarget('parent-002')), }); - // Make assertions sources.DOM.select('.blosh').events('click').observe((ev: Event) => { assert.strictEqual(ev.type, 'click'); @@ -372,6 +366,8 @@ describe('DOMSource.events()', function () { }); it('should catch a blur event with useCapture', function (done) { + if (!document.hasFocus()) return done(); + function app() { return { DOM: most.of(div('.parent', [ @@ -406,6 +402,8 @@ describe('DOMSource.events()', function () { }); it('should catch a blur event by default (no options)', function (done) { + if (!document.hasFocus()) return done(); + function app() { return { DOM: most.of(div('.parent', [ @@ -437,4 +435,4 @@ describe('DOMSource.events()', function () { setTimeout(() => dummy.focus(), 200); }); }); -}); \ No newline at end of file +}); diff --git a/test/driver/integration/index.ts b/test/driver/integration/index.ts new file mode 100644 index 0000000..b628679 --- /dev/null +++ b/test/driver/integration/index.ts @@ -0,0 +1,3 @@ +import './events'; +import './isolation'; +import './rendering'; diff --git a/test/driver/integration/isolation.ts b/test/driver/integration/isolation.ts index a79a79f..f126287 100644 --- a/test/driver/integration/isolation.ts +++ b/test/driver/integration/isolation.ts @@ -78,7 +78,6 @@ describe('isolateSink', function () { DOM: makeDomDriver(createRenderTarget()), }); - // Make assertions sinks.DOM.take(1).observe(function (vtree: VNode) { assert.strictEqual(vtree.tagName, 'h3'); @@ -125,7 +124,6 @@ describe('isolateSink', function () { DOM: makeDomDriver(createRenderTarget()), }); - // Make assertions sinks.DOM.skip(2).take(1).observe(function (vtree: VNode) { assert.strictEqual(vtree.tagName, 'span'); @@ -362,7 +360,6 @@ describe('isolation', function () { done(); }); - }); it('should allow DOM.select()ing all elements with `*`', function (done) { @@ -403,8 +400,6 @@ describe('isolation', function () { assert.strictEqual(elements.length, 4); done(); }); - - }); it('should select() isolated element with tag + class', function (done) { @@ -547,7 +542,6 @@ describe('isolation', function () { DOM: makeDomDriver(createRenderTarget()), }); - sources.DOM.select(':root').elements().skip(2).take(1).observe(function ([root]: HTMLElement[]) { const parentEl = root.querySelector('.parent') as HTMLElement; const foo: any = parentEl.querySelectorAll('.foo')[1]; @@ -595,7 +589,6 @@ describe('isolation', function () { DOM: makeDomDriver(createRenderTarget()), }); - sources.DOM.select(':root').elements().skip(1).observe(function ([root]: HTMLElement[]) { setTimeout(() => { const foo: any = root.querySelector('.foo'); @@ -653,7 +646,6 @@ describe('isolation', function () { setTimeout(() => element.click()); }); - }); it('should allow an isolated child to receive events when it is used as ' + @@ -743,7 +735,6 @@ describe('isolation', function () { setTimeout(() => element.click()); }); - }); it('should allow an isolated child to receive events when it is used as ' + @@ -789,7 +780,6 @@ describe('isolation', function () { setTimeout(() => element.click()); }); - }); it('should maintain virtual DOM list sanity using keys, in a list of ' + @@ -837,7 +827,6 @@ describe('isolation', function () { DOM: makeDomDriver(createRenderTarget()), }); - sources.DOM.elements().skip(1).take(1).observe(([root]: HTMLElement[]) => { const components = root.querySelectorAll('.btn'); assert.strictEqual(components.length, 2); @@ -857,4 +846,4 @@ describe('isolation', function () { }); }); -}); \ No newline at end of file +}); diff --git a/test/driver/issue-105.ts b/test/driver/issue-105.ts index e9092cf..720bcba 100644 --- a/test/driver/issue-105.ts +++ b/test/driver/issue-105.ts @@ -47,7 +47,6 @@ describe('issue 105', () => { }); }); - function withUseCaptureFalse (sources: any) { // ORIGINAL PROBLEM: // ------- @@ -143,4 +142,4 @@ function augmentComponent(Component: any, augmentationSinks: any) { return Object.assign(sinks, augmentationSinks); }; -} \ No newline at end of file +} diff --git a/test/driver/mockDomSource.ts b/test/driver/mockDomSource.ts index 274365e..52414c4 100644 --- a/test/driver/mockDomSource.ts +++ b/test/driver/mockDomSource.ts @@ -136,7 +136,7 @@ describe('mockDOMSource', function () { it('multiple .select()s should return some observable if not defined', () => { const DOM = mockDomSource({}); const domSource = DOM.select('.something').select('.other'); - assert(domSource.events('click') instanceof most.Stream,'domSource.events(click) should be an Observable instance'); + assert(domSource.events('click') instanceof most.Stream, 'domSource.events(click) should be an Observable instance'); assert.strictEqual(domSource.elements() instanceof most.Stream, true, 'domSource.elements() should be an Observable instance'); }); }); @@ -234,4 +234,4 @@ describe('isolation on MockedDOMSource', function () { done(); }); }); -}); \ No newline at end of file +}); diff --git a/test/driver/vNodeWrapper.ts b/test/driver/vNodeWrapper.ts index d9f024f..07e9dbe 100644 --- a/test/driver/vNodeWrapper.ts +++ b/test/driver/vNodeWrapper.ts @@ -18,4 +18,4 @@ describe('vNodeWrapper', () => { assert.strictEqual(vNodeWrapper(element)(vNode), vNode); }); -}); \ No newline at end of file +}); diff --git a/test/helpers/fake-raf.ts b/test/helpers/fake-raf.ts index 01f9932..ee71cc6 100644 --- a/test/helpers/fake-raf.ts +++ b/test/helpers/fake-raf.ts @@ -15,7 +15,7 @@ function use() { function restore() { setTimeout(() => { window.requestAnimationFrame = original; - }, 2000) + }, 2000); } function step() { diff --git a/test/helpers/index.ts b/test/helpers/index.ts index c339b71..f59e5d2 100644 --- a/test/helpers/index.ts +++ b/test/helpers/index.ts @@ -11,4 +11,4 @@ export function createRenderTarget (id: string | null = null) { document.body.appendChild(element); return element; -} \ No newline at end of file +} diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 0000000..b00c214 --- /dev/null +++ b/test/index.ts @@ -0,0 +1,5 @@ +require('es6-map/implement'); + +import './virtual-dom'; +import './modules'; +import './driver'; diff --git a/test/modules/IsolateModule.ts b/test/modules/IsolateModule.ts index 23b9cd7..09d244c 100644 --- a/test/modules/IsolateModule.ts +++ b/test/modules/IsolateModule.ts @@ -43,4 +43,4 @@ describe('IsolateModule', () => { assert.strictEqual(element.getAttribute('data-isolate'), 'hello'); }); }); -}); \ No newline at end of file +}); diff --git a/test/modules/index.ts b/test/modules/index.ts new file mode 100644 index 0000000..e4f9b35 --- /dev/null +++ b/test/modules/index.ts @@ -0,0 +1,3 @@ +import './dataset'; +import './IsolateModule'; +import './style'; diff --git a/test/modules/style.ts b/test/modules/style.ts index 3e5dd18..36ae60e 100644 --- a/test/modules/style.ts +++ b/test/modules/style.ts @@ -3,73 +3,177 @@ import fakeRaf from '../helpers/fake-raf'; import { init, h, StyleModule } from '../../src/index'; fakeRaf.use(); + let patch = init([ StyleModule, ]); describe('style', function() { let element: HTMLElement, vnode0: HTMLElement; + beforeEach(function() { element = document.createElement('div'); + vnode0 = element; }); + it('is being styled', function() { element = patch(vnode0, h('div', {style: {fontSize: '12px'}})).elm as HTMLElement; + assert.equal(element.style.fontSize, '12px'); }); + it('updates styles', function() { let vnode1 = h('i', {style: {fontSize: '14px', display: 'inline'}}); let vnode2 = h('i', {style: {fontSize: '12px', display: 'block'}}); let vnode3 = h('i', {style: {fontSize: '10px', display: 'block'}}); + element = patch(vnode0, vnode1).elm as HTMLElement; + assert.equal(element.style.fontSize, '14px'); assert.equal(element.style.display, 'inline'); + element = patch(vnode1, vnode2).elm as HTMLElement; + assert.equal(element.style.fontSize, '12px'); assert.equal(element.style.display, 'block'); + element = patch(vnode2, vnode3).elm as HTMLElement; + assert.equal(element.style.fontSize, '10px'); assert.equal(element.style.display, 'block'); }); + it('explicialy removes styles', function() { let vnode1 = h('i', {style: {fontSize: '14px'}}); let vnode2 = h('i', {style: {fontSize: ''}}); let vnode3 = h('i', {style: {fontSize: '10px'}}); + element = patch(vnode0, vnode1).elm as HTMLElement; + assert.equal(element.style.fontSize, '14px'); + patch(vnode1, vnode2); + assert.equal(element.style.fontSize, ''); + patch(vnode2, vnode3); + assert.equal(element.style.fontSize, '10px'); }); + it('implicially removes styles from element', function() { let vnode1 = h('div', [h('i', {style: {fontSize: '14px'}})]); let vnode2 = h('div', [h('i')]); let vnode3 = h('div', [h('i', {style: {fontSize: '10px'}})]); + patch(vnode0, vnode1); + assert.equal((element.firstChild as HTMLElement).style.fontSize, '14px'); + patch(vnode1, vnode2); + assert.equal((element.firstChild as HTMLElement).style.fontSize, ''); + patch(vnode2, vnode3); + assert.equal((element.firstChild as HTMLElement).style.fontSize, '10px'); }); + it('updates delayed styles in next frame', function() { let patch = init([ StyleModule, ]); + let vnode1 = h('i', {style: {fontSize: '14px', delayed: {fontSize: '16px'}}}); let vnode2 = h('i', {style: {fontSize: '18px', delayed: {fontSize: '20px'}}}); + element = patch(vnode0, vnode1).elm as HTMLElement; + assert.equal(element.style.fontSize, '14px'); + fakeRaf.step(); fakeRaf.step(); + assert.equal(element.style.fontSize, '16px'); element = patch(vnode1, vnode2).elm as HTMLElement; + assert.equal(element.style.fontSize, '18px'); + fakeRaf.step(); fakeRaf.step(); + assert.equal(element.style.fontSize, '20px'); }); + + it('updates css variables', function(done) { + // only run in real browsers + if (typeof process !== undefined) done(); + + let vnode1 = h('div', {style: {'--mylet': 1}}); + let vnode2 = h('div', {style: {'--mylet': 2}}); + let vnode3 = h('div', { style: { '--mylet': 3 } }); + + element = patch(vnode0, vnode1).elm; + + assert.equal(element.style.getPropertyValue('--mylet'), 1); + + element = patch(vnode1, vnode2).elm; + + assert.equal(element.style.getPropertyValue('--mylet'), 2); + + element = patch(vnode2, vnode3).elm; + + assert.equal(element.style.getPropertyValue('--mylet'), 3); + + done(); + }); + + it('explicitly removes css variables', function(done) { + // only run in real browsers + if (typeof process !== undefined) done(); + + let vnode1 = h('i', {style: {'--mylet': 1}}); + let vnode2 = h('i', {style: {'--mylet': ''}}); + let vnode3 = h('i', {style: {'--mylet': 2}}); + + element = patch(vnode0, vnode1).elm; + + assert.equal(element.style.getPropertyValue('--mylet'), 1); + + patch(vnode1, vnode2); + + assert.equal(element.style.getPropertyValue('--mylet'), ''); + + patch(vnode2, vnode3); + + assert.equal(element.style.getPropertyValue('--mylet'), 2); + + done(); + }); + + it('implicitly removes css varaibles from element', function(done) { + // only run in real browsers + if (typeof process !== undefined) done(); + + let vnode1 = h('div', [h('i', {style: {'--mylet': 1}})]); + let vnode2 = h('div', [h('i')]); + let vnode3 = h('div', [h('i', {style: {'--mylet': 2}})]); + + patch(vnode0, vnode1); + + assert.equal((element.firstChild as HTMLElement).style.getPropertyValue('--mylet'), 1); + + patch(vnode1, vnode2); + + assert.equal((element.firstChild as HTMLElement).style.getPropertyValue('--mylet'), ''); + + patch(vnode2, vnode3); + + assert.equal((element.firstChild as HTMLElement).style.getPropertyValue('--mylet'), 2); + + done(); + }); }); fakeRaf.restore(); diff --git a/test/virtual-dom/index.ts b/test/virtual-dom/index.ts new file mode 100644 index 0000000..ea7b99a --- /dev/null +++ b/test/virtual-dom/index.ts @@ -0,0 +1 @@ +import './core'; diff --git a/tslint.json b/tslint.json index 1e3584e..7d0f652 100644 --- a/tslint.json +++ b/tslint.json @@ -3,6 +3,7 @@ "@motorcycle/tslint" ], "rules": { + "eofline": true, "max-line-length": [ 150 ], From 05cc55f9f513dd339023791a3ac93fcb9d18c679 Mon Sep 17 00:00:00 2001 From: Tylor Steinberger Date: Wed, 14 Dec 2016 14:50:00 -0500 Subject: [PATCH 3/4] feat(hasCssSelector): implement hasCssSelector function Given a CSS selector **without** spaces, this function does not search children, it will return `true` if the given CSS selector matches that of the VNode and `false` if it does not. If a CSS selector **with** spaces is given it will throw an error. This also implements a robuster `parseSelector`. --- README.md | 17 +++ src/virtual-dom/helpers/h.ts | 44 ++++--- src/virtual-dom/helpers/hasCssSelector.ts | 29 +++++ src/virtual-dom/helpers/index.ts | 4 + src/virtual-dom/index.ts | 4 +- test/virtual-dom/hasCssSelector.ts | 144 ++++++++++++++++++++++ test/virtual-dom/parseSelector.ts | 30 +++++ 7 files changed, 253 insertions(+), 19 deletions(-) create mode 100644 src/virtual-dom/helpers/hasCssSelector.ts create mode 100644 src/virtual-dom/helpers/index.ts create mode 100644 test/virtual-dom/hasCssSelector.ts create mode 100644 test/virtual-dom/parseSelector.ts diff --git a/README.md b/README.md index 46150b8..3c36612 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ will need to be used. - [`makeDomDriver`](#makeDomDriver) - [`mockDomSource`](#mockDomSource) - [`h`](#h) +- [`hasCssSelector`](#hasCssSelector) ### `makeDomDriver(container, options)` @@ -147,3 +148,19 @@ svg({width: 150, height: 150}, [ }) ]) ``` + +### `hasCssSelector(cssSelector: string, vNode: VNode): boolean` + +Given a CSS selector **without** spaces, this function does not search children, it +will return `true` if the given CSS selector matches that of the VNode and `false` +if it does not. If a CSS selector **with** spaces is given it will throw an error. + +```typescript +import { hasCssSelector, div } from '@motorcycle/dom'; + +console.log(hasCssSelector('.foo', div('.foo'))) // true +console.log(hasCssSelector('.bar', div('.foo'))) // false +console.log(hasCssSelector('div', div('.foo'))) // true +console.log(hasCssSelector('#foo', div('#foo'))) // true +console.log(hasCssSelector('.foo .bar'), div('.foo.bar')) // ERROR! +``` diff --git a/src/virtual-dom/helpers/h.ts b/src/virtual-dom/helpers/h.ts index 43f006b..cb5370b 100644 --- a/src/virtual-dom/helpers/h.ts +++ b/src/virtual-dom/helpers/h.ts @@ -73,26 +73,38 @@ export const h: HyperscriptFn = function (selector: string, b?: any, c?: any): V ); }; -function parseSelector(sel: string) { - // Parse selector - let hashIdx = sel.indexOf('#'); - let dotIdx = sel.indexOf('.', hashIdx); - let hash = hashIdx > 0 ? hashIdx : sel.length; - let dot = dotIdx > 0 ? dotIdx : sel.length; +const classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/; - let tagName = hashIdx !== -1 || dotIdx !== -1 - ? sel.slice(0, Math.min(hash, dot)) - : sel; +export function parseSelector (selector: string) { + let tagName: string | void; + let id = ''; + const classes: Array = []; - const id = sel.slice(hash + 1, dot) || void 0; + const tagParts = selector.split(classIdSplit); - const className = dotIdx < sel.length && dotIdx > 0 - ? sel.slice(dot + 1).replace(/\./g, ' ') - : void 0; + let part: string | void; + let type; + + for (let i = 0; i < tagParts.length; i++) { + part = tagParts[i]; + + if (!part) + continue; + + type = part.charAt(0); + + if (!tagName) { + tagName = part; + } else if (type === '.') { + classes.push(part.substring(1, part.length)); + } else if (type === '#') { + id = part.substring(1, part.length); + } + } return { - tagName: tagName, + tagName: tagName as string, id, - className, + className: classes.join(' '), }; -} \ No newline at end of file +} diff --git a/src/virtual-dom/helpers/hasCssSelector.ts b/src/virtual-dom/helpers/hasCssSelector.ts new file mode 100644 index 0000000..332b03a --- /dev/null +++ b/src/virtual-dom/helpers/hasCssSelector.ts @@ -0,0 +1,29 @@ +import { VNode } from '../../types'; +import { parseSelector } from './h'; + +export function hasCssSelector(cssSelector: string, vNode: VNode): boolean { + if (cssSelector.indexOf(' ') > -1) + throw new Error('CSS selectors can not contain spaces'); + + const hasTagName = cssSelector[0] !== '#' && cssSelector[0] !== '.'; + + const { tagName, className, id } = + hasTagName ? + parseSelector(cssSelector) : + parseSelector(vNode.tagName + cssSelector); + + if (tagName !== vNode.tagName) + return false; + + const parsedClassNames = className && className.split(' ') || []; + const vNodeClassNames = vNode.className && vNode.className.split(' ') || []; + + for (let i = 0; i < parsedClassNames.length; ++i) { + const parsedClassName = parsedClassNames[i]; + + if (vNodeClassNames.indexOf(parsedClassName) === -1) + return false; + } + + return id === vNode.id; +} diff --git a/src/virtual-dom/helpers/index.ts b/src/virtual-dom/helpers/index.ts new file mode 100644 index 0000000..3d03631 --- /dev/null +++ b/src/virtual-dom/helpers/index.ts @@ -0,0 +1,4 @@ +export * from './h'; +export * from './hasCssSelector'; +export * from './hyperscript'; +export * from './svg'; diff --git a/src/virtual-dom/index.ts b/src/virtual-dom/index.ts index 98feef7..52c646c 100644 --- a/src/virtual-dom/index.ts +++ b/src/virtual-dom/index.ts @@ -1,4 +1,2 @@ -export * from './helpers/h'; -export * from './helpers/hyperscript'; -export * from './helpers/svg'; +export * from './helpers'; export * from './init'; diff --git a/test/virtual-dom/hasCssSelector.ts b/test/virtual-dom/hasCssSelector.ts new file mode 100644 index 0000000..285c58d --- /dev/null +++ b/test/virtual-dom/hasCssSelector.ts @@ -0,0 +1,144 @@ +import * as assert from 'assert'; +import { div, hasCssSelector } from '../../src'; + +describe('hasCssSelector', () => { + describe('given a class selector .foo and VNode with class foo', () => { + it('returns true', () => { + assert.ok(hasCssSelector('.foo', div('.foo'))); + }); + }); + + describe('given a class selector .bar and VNode with class foo', () => { + it('returns false', () => { + assert.ok(!hasCssSelector('.bar', div('.foo'))); + }); + }); + + describe('given a class selector xbar and VNode with class bar', () => { + it('returns false', () => { + assert.ok(!hasCssSelector('xbar', div('.bar'))); + }); + }); + + describe('given a class selector .foo.bar and VNode with classes foo and bar', () => { + it('returns true', () => { + assert.ok(hasCssSelector('.foo.bar', div('.foo.bar'))); + }); + }); + + describe('given a class selector .foo.bar and VNode with classes bar and foo', () => { + it('returns true', () => { + assert.ok(hasCssSelector('.foo.bar', div('.bar.foo'))); + }); + }); + + describe('given a class selector .foo.bar.baz and VNode with classes bar foo and baz', () => { + it('returns true', () => { + assert.ok(hasCssSelector('.foo.bar.baz', div('.baz.bar.foo'))); + }); + }); + + describe('given a class selector .foo and VNode with no classes', () => { + it('returns false', () => { + assert.ok(!hasCssSelector('.foo', div())); + }); + }); + + describe('given an id selector #foo and VNode with id foo', () => { + it('returns true', () => { + assert.ok(hasCssSelector('#foo', div('#foo'))); + }); + }); + + describe('given an id selector #bar and VNode with id foo', () => { + it('returns false', () => { + assert.ok(!hasCssSelector('#bar', div('#foo'))); + }); + }); + + describe('given an id selector #foo#bar and VNode with id foo', () => { + it('returns false', () => { + assert.ok(!hasCssSelector('#foo#bar', div('#foo'))); + }); + }); + + describe('given a cssSelector .foo#bar and VNode with class foo and id bar', () => { + it('returns true', () => { + assert.ok(hasCssSelector('.foo#bar', div('.foo#bar'))); + }); + }); + + describe('given a cssSelector .foo#bar and VNode with class foo', () => { + it('returns false', () => { + assert.ok(!hasCssSelector('.foo#bar', div('.foo'))); + }); + }); + + describe('given a cssSelector #bar.foo and VNode with class foo', () => { + it('returns false', () => { + assert.ok(!hasCssSelector('#bar.foo', div('.foo'))); + }); + }); + + describe('given a cssSelector .foo .bar and VNode with classes foo and bar', () => { + it('throws error', () => { + assert.throws(() => { + hasCssSelector('.foo .bar', div('.foo.bar')); + }, /CSS selectors can not contain spaces/); + }); + }); + + describe('given a cssSelector .foo#bar.baz and VNode with classes foo and baz and id bar', () => { + it('returns true', () => { + assert.ok(hasCssSelector('.foo#bar.baz', div('.foo#bar.baz'))); + }); + }); + + describe('given cssSelector .foo#bar.baz and VNode with class foo and id bar', () => { + it('returns false', () => { + assert.ok(!hasCssSelector('.foo#bar.baz', div('.foo#bar'))); + }); + }); + + describe('given cssSelector .foo#bar.baz and VNode with class baz and id bar', () => { + it('returns false', () => { + assert.ok(!hasCssSelector('.foo#bar.baz', div('.baz#bar'))); + }); + }); + + describe('given a cssSelector div and VNode with tagName div', () => { + it('returns true', () => { + assert.ok(hasCssSelector('div', div())); + }); + }); + + describe('given a cssSelector div.foo and VNode with tagName div and class foo', () => { + it('returns true', () => { + assert.ok(hasCssSelector('div.foo', div('.foo'))); + }); + }); + + describe('given a cssSelector h2.foo and VNode with tagName div and class foo', () => { + it('returns false', () => { + assert.ok(!hasCssSelector('h2.foo', div('.foo'))); + }); + }); + + describe('given a cssSelector div.foo and VNode with tagName div', () => { + it('returns false', () => { + assert.ok(!hasCssSelector('div.foo', div())); + }); + }); + + describe('given a cssSelector div.foo and VNode with tagName div and id foo', () => { + it('returns false', () => { + assert.ok(!hasCssSelector('div.foo', div('#foo'))); + }); + }); + + describe('given a cssSelector div#foo.bar and VNode with tagName div and id foo and class bar', () => { + it('returns true', () => { + assert.ok(hasCssSelector('div#foo.bar', div('#foo.bar'))); + }); + }); +}); diff --git a/test/virtual-dom/parseSelector.ts b/test/virtual-dom/parseSelector.ts new file mode 100644 index 0000000..2ca58db --- /dev/null +++ b/test/virtual-dom/parseSelector.ts @@ -0,0 +1,30 @@ +import * as assert from 'assert'; +import { parseSelector } from '../../src/virtual-dom/helpers/h'; + +describe('parseSelector', () => { + it('parses selectors', () => { + let result = parseSelector('p'); + assert.deepEqual(result, { tagName: 'p', id: '', className: '' }); + + result = parseSelector('p#foo'); + assert.deepEqual(result, { tagName: 'p', id: 'foo', className: '' }); + + result = parseSelector('p.bar'); + assert.deepEqual(result, { tagName: 'p', id: '', className: 'bar' }); + + result = parseSelector('p.bar.baz'); + assert.deepEqual(result, { tagName: 'p', id: '', className: 'bar baz' }); + + result = parseSelector('p#foo.bar.baz'); + assert.deepEqual(result, { tagName: 'p', id: 'foo', className: 'bar baz' }); + + result = parseSelector('div#foo'); + assert.deepEqual(result, { tagName: 'div', id: 'foo', className: '' }); + + result = parseSelector('div#foo.bar.baz'); + assert.deepEqual(result, { tagName: 'div', id: 'foo', className: 'bar baz' }); + + result = parseSelector('div.bar.baz'); + assert.deepEqual(result, { tagName: 'div', id: '', className: 'bar baz' }); + }); +}); From 6d7f6be848a18d7a647159f626e2eac0db042fec Mon Sep 17 00:00:00 2001 From: Tylor Steinberger Date: Wed, 14 Dec 2016 14:51:20 -0500 Subject: [PATCH 4/4] chore(package): v6.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eda629a..f2344c1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@motorcycle/dom", "description": "Standard DOM Driver for Motorcycle.js", - "version": "6.2.0", + "version": "6.3.0", "author": "Tylor Steinberger ", "bugs": { "url": "https://github.com/motorcyclejs/dom/issues"