From 4685961c1edc1c3776281de1eb105afbc057ea4c Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Wed, 6 Sep 2017 13:36:49 +0100 Subject: [PATCH 01/33] add animatable mixin --- src/mixins/Animatable.ts | 236 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 src/mixins/Animatable.ts diff --git a/src/mixins/Animatable.ts b/src/mixins/Animatable.ts new file mode 100644 index 00000000..d21f589c --- /dev/null +++ b/src/mixins/Animatable.ts @@ -0,0 +1,236 @@ +import 'web-animations-js/web-animations-next-lite.min'; +import { Constructor, DNode, HNode } from '../interfaces'; +import { WidgetBase, afterRender } from '../WidgetBase'; +import { isHNode, decorate } from '../d'; +import Map from '@dojo/shim/Map'; +import MetaBase from '../meta/Base'; + +/** + * KeyframeEffect and Animation types required + */ +declare const KeyframeEffect: any; +declare const Animation: any; + +/** + * The controls for the animation player which can + * be changed after the animation has been created + */ +export interface AnimationControls { + play?: boolean; + onFinish?: () => void; + reverse?: boolean; + cancel?: boolean; + finish?: boolean; + playbackRate?: number; + startTime?: number; + currentTime?: number; +} + +/** + * Timing properties for the animation. + * These are fixed once the aimation has been created + */ +export interface AnimationTimingProperties { + duration?: number; + delay?: number; + direction?: string; + easing?: string; + endDelay?: number; + fill?: string; + iterations?: number; + iterationStart?: number; +} + +/** + * The anmiations property to be passed as + * 'animations' in vNode properties + */ +export interface AnimationProperties { + id: string; + effects: any[]; + controls?: AnimationControls; + timing?: AnimationTimingProperties; +} + +/** + * The animation player class. + * Has access to meta to require nodes and apply animations + */ +class AnimationPlayer extends MetaBase { + + /** + * The map of animations + */ + private _animationMap = new Map(); + + /** + * Function that creates an animation player + * + * @param node The node to apply the animation to + * @param properties The animation properties + */ + private _createPlayer(node: HTMLElement, properties: AnimationProperties) { + const { + effects, + timing = {} + } = properties; + + const fx = typeof effects === 'function' ? effects() : effects; + + const keyframeEffect = new KeyframeEffect( + node, + fx, + timing + ); + + return new Animation(keyframeEffect, (document as any).timeline); + } + + /** + * Updates a current player based on the AnimationControls passed + * + * @param player the Animation object + * @param controls The controls to be set on the Animation + */ + private _updatePlayer(player: any, controls: AnimationControls) { + const { + play, + reverse, + cancel, + finish, + onFinish, + playbackRate, + startTime, + currentTime + } = controls; + + if (playbackRate !== undefined) { + player.playbackRate = playbackRate; + } + + if (finish) { + player.finish(); + } + + if (reverse) { + player.reverse(); + } + + if (cancel) { + player.cancel(); + } + + if (finish) { + player.finish(); + } + + if (startTime !== undefined) { + player.startTime(startTime); + } + + if (currentTime !== undefined) { + player.currentTime(currentTime); + } + + if (play) { + player.play(); + } + else { + player.pause(); + } + + if (onFinish) { + player.onfinish = onFinish; + } + } + + /** + * Adds a new Animation to the animation map on next animation frame and + * sets the given player controls against the new Animation. + * + * @param key The VNode key for the node to be animated + * @param animateProperties The Animation properties to be applied to the generated HTMLElement + */ + add(key: string, animateProperties: AnimationProperties[]): Promise { + return new Promise((resolve) => { + requestAnimationFrame(() => { + this.requireNode(key, function(this: AnimationPlayer, node: HTMLElement) { + + animateProperties.forEach((properties) => { + properties = typeof properties === 'function' ? properties() : properties; + + if (properties) { + const { id } = properties; + if (!this._animationMap.has(id)) { + this._animationMap.set(id, { + player: this._createPlayer(node, properties), + used: true + }); + } + + const { player } = this._animationMap.get(id); + const { controls = {} } = properties; + + this._updatePlayer(player, controls); + + this._animationMap.set(id, { + player, + used: true + }); + } + }); + + resolve(); + + }.bind(this)); + }); + }); + } + + /** + * Clears out any animations that have not been used + */ + clearAnimations() { + this._animationMap.forEach((animation, key) => { + if (!animation.used) { + animation.player.cancel(); + this._animationMap.delete(key); + } + animation.used = false; + }); + } +} + +/** + * Function that returns a class decorated with Animatable + */ +export function AnimatableMixin>(Base: T): T { + class Animatable extends Base { + + /** + * Finds animation properties in the result of afterRender and creates Animation + * Players per node before clearing down any unused animations. + * + * @param result + */ + @afterRender() + myAfterRender(result: DNode): DNode { + const promises: Promise[] = []; + decorate(result, + (node: HNode) => { + const { animate, key } = node.properties; + promises.push(this.meta(AnimationPlayer).add(key as string, animate)); + }, + (node: DNode) => { + return !!(isHNode(node) && node.properties.animate && node.properties.key); + } + ); + Promise.all(promises).then(() => this.meta(AnimationPlayer).clearAnimations()); + return result; + } + } + + return Animatable; +} + +export default AnimatableMixin; From f85b6520ac41126460be4b962c98cd3be2736f5c Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Tue, 3 Oct 2017 15:09:24 +0100 Subject: [PATCH 02/33] startign to add tests --- src/mixins/Animatable.ts | 126 +++++++++----------------------- tests/unit/mixins/Animatable.ts | 37 ++++++++++ 2 files changed, 71 insertions(+), 92 deletions(-) create mode 100644 tests/unit/mixins/Animatable.ts diff --git a/src/mixins/Animatable.ts b/src/mixins/Animatable.ts index d21f589c..86d3ed5c 100644 --- a/src/mixins/Animatable.ts +++ b/src/mixins/Animatable.ts @@ -1,20 +1,14 @@ import 'web-animations-js/web-animations-next-lite.min'; import { Constructor, DNode, HNode } from '../interfaces'; -import { WidgetBase, afterRender } from '../WidgetBase'; +import { WidgetBase } from '../WidgetBase'; +import { afterRender } from '../decorators/afterRender'; import { isHNode, decorate } from '../d'; import Map from '@dojo/shim/Map'; import MetaBase from '../meta/Base'; -/** - * KeyframeEffect and Animation types required - */ declare const KeyframeEffect: any; declare const Animation: any; -/** - * The controls for the animation player which can - * be changed after the animation has been created - */ export interface AnimationControls { play?: boolean; onFinish?: () => void; @@ -26,10 +20,6 @@ export interface AnimationControls { currentTime?: number; } -/** - * Timing properties for the animation. - * These are fixed once the aimation has been created - */ export interface AnimationTimingProperties { duration?: number; delay?: number; @@ -41,10 +31,6 @@ export interface AnimationTimingProperties { iterationStart?: number; } -/** - * The anmiations property to be passed as - * 'animations' in vNode properties - */ export interface AnimationProperties { id: string; effects: any[]; @@ -52,23 +38,10 @@ export interface AnimationProperties { timing?: AnimationTimingProperties; } -/** - * The animation player class. - * Has access to meta to require nodes and apply animations - */ class AnimationPlayer extends MetaBase { - /** - * The map of animations - */ private _animationMap = new Map(); - /** - * Function that creates an animation player - * - * @param node The node to apply the animation to - * @param properties The animation properties - */ private _createPlayer(node: HTMLElement, properties: AnimationProperties) { const { effects, @@ -86,12 +59,6 @@ class AnimationPlayer extends MetaBase { return new Animation(keyframeEffect, (document as any).timeline); } - /** - * Updates a current player based on the AnimationControls passed - * - * @param player the Animation object - * @param controls The controls to be set on the Animation - */ private _updatePlayer(player: any, controls: AnimationControls) { const { play, @@ -144,52 +111,36 @@ class AnimationPlayer extends MetaBase { } } - /** - * Adds a new Animation to the animation map on next animation frame and - * sets the given player controls against the new Animation. - * - * @param key The VNode key for the node to be animated - * @param animateProperties The Animation properties to be applied to the generated HTMLElement - */ - add(key: string, animateProperties: AnimationProperties[]): Promise { - return new Promise((resolve) => { - requestAnimationFrame(() => { - this.requireNode(key, function(this: AnimationPlayer, node: HTMLElement) { - - animateProperties.forEach((properties) => { - properties = typeof properties === 'function' ? properties() : properties; - - if (properties) { - const { id } = properties; - if (!this._animationMap.has(id)) { - this._animationMap.set(id, { - player: this._createPlayer(node, properties), - used: true - }); - } - - const { player } = this._animationMap.get(id); - const { controls = {} } = properties; - - this._updatePlayer(player, controls); - - this._animationMap.set(id, { - player, - used: true - }); - } - }); + add(key: string, animateProperties: AnimationProperties[]) { + const node = this.getNode(key); + + if (node) { + animateProperties.forEach((properties) => { + properties = typeof properties === 'function' ? properties() : properties; + + if (properties) { + const { id } = properties; + if (!this._animationMap.has(id)) { + this._animationMap.set(id, { + player: this._createPlayer(node, properties), + used: true + }); + } - resolve(); + const { player } = this._animationMap.get(id); + const { controls = {} } = properties; - }.bind(this)); + this._updatePlayer(player, controls); + + this._animationMap.set(id, { + player, + used: true + }); + } }); - }); + } } - /** - * Clears out any animations that have not been used - */ clearAnimations() { this._animationMap.forEach((animation, key) => { if (!animation.used) { @@ -199,38 +150,29 @@ class AnimationPlayer extends MetaBase { animation.used = false; }); } + } -/** - * Function that returns a class decorated with Animatable - */ export function AnimatableMixin>(Base: T): T { - class Animatable extends Base { - - /** - * Finds animation properties in the result of afterRender and creates Animation - * Players per node before clearing down any unused animations. - * - * @param result - */ + class Animated extends Base { + @afterRender() - myAfterRender(result: DNode): DNode { - const promises: Promise[] = []; + protected decorateAfterRender(result: DNode): DNode { decorate(result, (node: HNode) => { const { animate, key } = node.properties; - promises.push(this.meta(AnimationPlayer).add(key as string, animate)); + this.meta(AnimationPlayer).add(key as string, animate); }, (node: DNode) => { return !!(isHNode(node) && node.properties.animate && node.properties.key); } ); - Promise.all(promises).then(() => this.meta(AnimationPlayer).clearAnimations()); + this.meta(AnimationPlayer).clearAnimations(); return result; } } - return Animatable; + return Animated; } export default AnimatableMixin; diff --git a/tests/unit/mixins/Animatable.ts b/tests/unit/mixins/Animatable.ts new file mode 100644 index 00000000..fa209226 --- /dev/null +++ b/tests/unit/mixins/Animatable.ts @@ -0,0 +1,37 @@ +// import { VNode } from '@dojo/interfaces/vdom'; +import * as registerSuite from 'intern!object'; +// import * as assert from 'intern/chai!assert'; +import { + AnimatableMixin +} from '../../../src/mixins/Animatable'; +import { WidgetBase } from '../../../src/WidgetBase'; +import { v } from '../../../src/d'; +// import { stub, SinonStub } from 'sinon'; + +class TestWidget extends AnimatableMixin(WidgetBase) { + render() { + return v('div', { + key: 'root', + animate: { + id: 'testAnimation', + effects: [ + { height: '0px' }, + { height: '10px' } + ] + } + }); + } +} + +registerSuite({ + name: 'animatable', + beforeEach() { + + }, + afterEach() { + + }, + ''() { + + } +}); From 71f1b262b437383d3a2f2d0364adf094332a0c9b Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 9 Oct 2017 11:25:57 +0100 Subject: [PATCH 03/33] starting tests --- package.json | 3 +- src/mixins/Animatable.ts | 2 +- tests/unit/mixins/Animatable.ts | 78 ++++++++++++++++++++++++--------- tests/unit/mixins/all.ts | 1 + 4 files changed, 62 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 7d0f316f..d6f79588 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ }, "dependencies": { "intersection-observer": "^0.4.2", - "pepjs": "^0.4.2" + "pepjs": "^0.4.2", + "web-animations-js": "^2.3.1" } } diff --git a/src/mixins/Animatable.ts b/src/mixins/Animatable.ts index 86d3ed5c..56b633db 100644 --- a/src/mixins/Animatable.ts +++ b/src/mixins/Animatable.ts @@ -38,7 +38,7 @@ export interface AnimationProperties { timing?: AnimationTimingProperties; } -class AnimationPlayer extends MetaBase { +export class AnimationPlayer extends MetaBase { private _animationMap = new Map(); diff --git a/tests/unit/mixins/Animatable.ts b/tests/unit/mixins/Animatable.ts index fa209226..9ed37c06 100644 --- a/tests/unit/mixins/Animatable.ts +++ b/tests/unit/mixins/Animatable.ts @@ -1,37 +1,75 @@ -// import { VNode } from '@dojo/interfaces/vdom'; +import { VNode } from '@dojo/interfaces/vdom'; import * as registerSuite from 'intern!object'; -// import * as assert from 'intern/chai!assert'; -import { - AnimatableMixin -} from '../../../src/mixins/Animatable'; +import * as assert from 'intern/chai!assert'; +import { AnimatableMixin, AnimationPlayer } from '../../../src/mixins/Animatable'; import { WidgetBase } from '../../../src/WidgetBase'; import { v } from '../../../src/d'; -// import { stub, SinonStub } from 'sinon'; +import { spy } from 'sinon'; + +const staticAnimateProperties = { + id: 'staticAnimation', + effects: [ + { height: '0px' }, + { height: '10px' } + ] +}; class TestWidget extends AnimatableMixin(WidgetBase) { render() { - return v('div', { - key: 'root', - animate: { - id: 'testAnimation', - effects: [ - { height: '0px' }, - { height: '10px' } - ] - } - }); + return v('div', {}, [ + v('div', { + key: 'animated', + animate: staticAnimateProperties + }), + v('div', { + key: 'nonAnimated' + }) + ]); + } + + getMeta() { + return this.meta(AnimationPlayer); } } registerSuite({ name: 'animatable', - beforeEach() { + 'animatable mixin': { + 'adds an animatable player for each node with animations'() { + const widget = new TestWidget(); + const meta = widget.getMeta(); - }, - afterEach() { + const addSpy = spy(meta, 'add'); + + widget.__render__(); + assert.isTrue(addSpy.calledOnce); + assert.isTrue(addSpy.firstCall.calledWith('animated', staticAnimateProperties)); + }, + 'clears animations after new animations have been added'() { + const widget = new TestWidget(); + const meta = widget.getMeta(); + + const addSpy = spy(meta, 'add'); + const clearSpy = spy(meta, 'clearAnimations'); + + widget.__render__(); + assert.isTrue(addSpy.calledOnce); + assert.isTrue(clearSpy.calledOnce); + assert.isTrue(clearSpy.calledAfter(addSpy)); + }, + 'only calls add on nodes with key and animate properties'() { + const widget = new TestWidget(); + const meta = widget.getMeta(); + + const addSpy = spy(meta, 'add'); + + const renderedWidget = (widget.__render__() as VNode); + assert.equal(renderedWidget.children!.length, 2); + assert.isTrue(addSpy.calledOnce); + } }, - ''() { + 'animation player': { } }); diff --git a/tests/unit/mixins/all.ts b/tests/unit/mixins/all.ts index fd106fd3..9826bcec 100644 --- a/tests/unit/mixins/all.ts +++ b/tests/unit/mixins/all.ts @@ -1,3 +1,4 @@ import './Themed'; +import './Animatable'; import './Projector'; import './I18n'; From 39d41b3de475ed4807fdc3f0a6e924682487d824 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 9 Oct 2017 16:21:08 +0100 Subject: [PATCH 04/33] added tests --- src/mixins/Animatable.ts | 7 +- tests/unit/mixins/Animatable.ts | 263 ++++++++++++++++++++++++++++++-- 2 files changed, 254 insertions(+), 16 deletions(-) diff --git a/src/mixins/Animatable.ts b/src/mixins/Animatable.ts index 56b633db..0cc57004 100644 --- a/src/mixins/Animatable.ts +++ b/src/mixins/Animatable.ts @@ -75,10 +75,6 @@ export class AnimationPlayer extends MetaBase { player.playbackRate = playbackRate; } - if (finish) { - player.finish(); - } - if (reverse) { player.reverse(); } @@ -115,6 +111,9 @@ export class AnimationPlayer extends MetaBase { const node = this.getNode(key); if (node) { + if (!Array.isArray(animateProperties)) { + animateProperties = [ animateProperties ]; + } animateProperties.forEach((properties) => { properties = typeof properties === 'function' ? properties() : properties; diff --git a/tests/unit/mixins/Animatable.ts b/tests/unit/mixins/Animatable.ts index 9ed37c06..fc201184 100644 --- a/tests/unit/mixins/Animatable.ts +++ b/tests/unit/mixins/Animatable.ts @@ -1,25 +1,40 @@ +import global from '@dojo/shim/global'; import { VNode } from '@dojo/interfaces/vdom'; import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; -import { AnimatableMixin, AnimationPlayer } from '../../../src/mixins/Animatable'; +import { WidgetProperties } from '../../../src/interfaces'; +import { AnimatableMixin, AnimationPlayer, AnimationControls, AnimationTimingProperties } from '../../../src/mixins/Animatable'; import { WidgetBase } from '../../../src/WidgetBase'; import { v } from '../../../src/d'; -import { spy } from 'sinon'; +import { spy, stub } from 'sinon'; -const staticAnimateProperties = { - id: 'staticAnimation', - effects: [ - { height: '0px' }, - { height: '10px' } - ] -}; +interface TestWidgetProperties extends WidgetProperties { + controls?: AnimationControls; + effects?: any; + timing?: AnimationTimingProperties; + animate?: boolean; +} -class TestWidget extends AnimatableMixin(WidgetBase) { +class TestWidget extends AnimatableMixin(WidgetBase) { render() { + const { + animate = true, + controls = {}, + timing = {}, + effects = [ + { height: '0px' }, + { height: '10px' } + ] } = this.properties; + return v('div', {}, [ v('div', { key: 'animated', - animate: staticAnimateProperties + animate: animate ? { + id: 'animation', + effects, + controls, + timing + } : undefined }), v('div', { key: 'nonAnimated' @@ -32,6 +47,17 @@ class TestWidget extends AnimatableMixin(WidgetBase) { } } +const keyframeCtorStub = stub(); +const animationCtorStub = stub(); +const pauseStub = stub(); +const playStub = stub(); +const reverseStub = stub(); +const cancelStub = stub(); +const finishStub = stub(); +const startStub = stub(); +const currentStub = stub(); +let metaNode: HTMLElement; + registerSuite({ name: 'animatable', 'animatable mixin': { @@ -43,7 +69,6 @@ registerSuite({ widget.__render__(); assert.isTrue(addSpy.calledOnce); - assert.isTrue(addSpy.firstCall.calledWith('animated', staticAnimateProperties)); }, 'clears animations after new animations have been added'() { const widget = new TestWidget(); @@ -70,6 +95,220 @@ registerSuite({ } }, 'animation player': { + beforeAll() { + class KeyframeEffectMock { + constructor(...args: any[]) { + keyframeCtorStub(...args); + } + } + class AnimationMock { + constructor(...args: any[]) { + animationCtorStub(...args); + } + pause() { + pauseStub(); + } + play() { + playStub(); + } + reverse() { + reverseStub(); + } + cancel() { + cancelStub(); + } + finish() { + finishStub(); + } + startTime(time: number) { + startStub(time); + } + currentTime(time: number) { + currentStub(time); + } + set onfinish(onFinish: () => {}) { + onFinish(); + } + } + global.KeyframeEffect = KeyframeEffectMock; + global.Animation = AnimationMock; + }, + beforeEach() { + keyframeCtorStub.reset(); + animationCtorStub.reset(); + pauseStub.reset(); + playStub.reset(); + reverseStub.reset(); + cancelStub.reset(); + finishStub.reset(); + startStub.reset(); + currentStub.reset(); + metaNode = document.createElement('div'); + }, + afterEach() { + + }, + 'creates new KeyframeEffect and Animation for each animated node'() { + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(keyframeCtorStub.calledOnce); + assert.isTrue(animationCtorStub.calledOnce); + }, + 'passed timing and node info to keyframe effect'() { + const widget = new TestWidget(); + widget.__setProperties__({ + timing: { + duration: 2 + } + }); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(keyframeCtorStub.calledOnce); + assert.isTrue(keyframeCtorStub.firstCall.calledWithMatch( + metaNode, + [ + { height: '0px' }, + { height: '10px' } + ], + { + duration: 2 + } + )); + }, + 'starts animations paused'() { + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(pauseStub.calledOnce); + assert.isTrue(playStub.notCalled); + }, + 'plays when play set to true'() { + const widget = new TestWidget(); + widget.__setProperties__({ + controls: { + play: true + } + }); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(playStub.calledOnce); + assert.isTrue(pauseStub.notCalled); + }, + 'reverses when reverse set to true'() { + const widget = new TestWidget(); + widget.__setProperties__({ + controls: { + reverse: true + } + }); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(reverseStub.calledOnce); + }, + 'cancels when cancel set to true'() { + const widget = new TestWidget(); + widget.__setProperties__({ + controls: { + cancel: true + } + }); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + widget.__render__(); + assert.isTrue(cancelStub.calledOnce); + }, + 'finishes when finish set to true'() { + const widget = new TestWidget(); + widget.__setProperties__({ + controls: { + finish: true + } + }); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(finishStub.calledOnce); + }, + 'can set start time'() { + const widget = new TestWidget(); + widget.__setProperties__({ + controls: { + startTime: 2 + } + }); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(startStub.calledOnce); + assert.isTrue(startStub.firstCall.calledWith(2)); + }, + 'can set current time'() { + const widget = new TestWidget(); + widget.__setProperties__({ + controls: { + currentTime: 2 + } + }); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(currentStub.calledOnce); + assert.isTrue(currentStub.firstCall.calledWith(2)); + }, + 'will execute effects function if one is passed'() { + const widget = new TestWidget(); + const fx = stub().returns([]); + widget.__setProperties__({ + effects: fx + }); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(fx.calledOnce); + }, + 'clears down used animations on next render if theyve been removed'() { + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(cancelStub.notCalled); + widget.__setProperties__({ + animate: false + }); + widget.__render__(); + assert.isTrue(cancelStub.calledOnce); + }, + 'will call onfinish function if passed'() { + const onFinishStub = stub(); + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__setProperties__({ + controls: { + onFinish: onFinishStub + } + }); + + widget.__render__(); + assert.isTrue(onFinishStub.calledOnce); + } } }); From b52717a21af835f4f7e09a1fa00d0691fd3313fe Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Wed, 11 Oct 2017 13:23:43 +0100 Subject: [PATCH 05/33] coverage --- tests/unit/mixins/Animatable.ts | 57 +++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/tests/unit/mixins/Animatable.ts b/tests/unit/mixins/Animatable.ts index fc201184..192daa19 100644 --- a/tests/unit/mixins/Animatable.ts +++ b/tests/unit/mixins/Animatable.ts @@ -16,9 +16,8 @@ interface TestWidgetProperties extends WidgetProperties { } class TestWidget extends AnimatableMixin(WidgetBase) { - render() { - const { - animate = true, + protected getAnimation(): {} | undefined | (() => {}) { + const { animate = true, controls = {}, timing = {}, effects = [ @@ -26,15 +25,19 @@ class TestWidget extends AnimatableMixin(WidgetBase) { { height: '10px' } ] } = this.properties; + return animate ? { + id: 'animation', + effects, + controls, + timing + } : undefined; + } + + render() { return v('div', {}, [ v('div', { key: 'animated', - animate: animate ? { - id: 'animation', - effects, - controls, - timing - } : undefined + animate: this.getAnimation() }), v('div', { key: 'nonAnimated' @@ -47,6 +50,26 @@ class TestWidget extends AnimatableMixin(WidgetBase) { } } +class PropertyFunctionWidget extends TestWidget { + getAnimation() { + const { controls = {}, + timing = {}, + effects = [ + { height: '0px' }, + { height: '10px' } + ] } = this.properties; + + return function () { + return { + id: 'animation', + effects, + controls, + timing + }; + }; + } +} + const keyframeCtorStub = stub(); const animationCtorStub = stub(); const pauseStub = stub(); @@ -309,6 +332,22 @@ registerSuite({ widget.__render__(); assert.isTrue(onFinishStub.calledOnce); + }, + 'can return a function instead of properties object'() { + const widget = new PropertyFunctionWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(keyframeCtorStub.calledOnce); + assert.isTrue(keyframeCtorStub.firstCall.calledWithMatch( + metaNode, + [ + { height: '0px' }, + { height: '10px' } + ], + {} + )); } } }); From 787c418e5730cfa11f0773fab6f7a013550fc1eb Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Thu, 19 Oct 2017 16:27:00 +0100 Subject: [PATCH 06/33] bumped test coverage --- tests/unit/mixins/Animatable.ts | 213 ++++++++++++++++++-------------- 1 file changed, 118 insertions(+), 95 deletions(-) diff --git a/tests/unit/mixins/Animatable.ts b/tests/unit/mixins/Animatable.ts index 192daa19..be6475a4 100644 --- a/tests/unit/mixins/Animatable.ts +++ b/tests/unit/mixins/Animatable.ts @@ -2,42 +2,22 @@ import global from '@dojo/shim/global'; import { VNode } from '@dojo/interfaces/vdom'; import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; -import { WidgetProperties } from '../../../src/interfaces'; import { AnimatableMixin, AnimationPlayer, AnimationControls, AnimationTimingProperties } from '../../../src/mixins/Animatable'; import { WidgetBase } from '../../../src/WidgetBase'; import { v } from '../../../src/d'; import { spy, stub } from 'sinon'; -interface TestWidgetProperties extends WidgetProperties { - controls?: AnimationControls; - effects?: any; - timing?: AnimationTimingProperties; - animate?: boolean; -} - -class TestWidget extends AnimatableMixin(WidgetBase) { - protected getAnimation(): {} | undefined | (() => {}) { - const { animate = true, - controls = {}, - timing = {}, - effects = [ - { height: '0px' }, - { height: '10px' } - ] } = this.properties; - - return animate ? { - id: 'animation', - effects, - controls, - timing - } : undefined; - } +let effects: any; +let controls: AnimationControls; +let timing: AnimationTimingProperties; +let animate: any; +class TestWidget extends AnimatableMixin(WidgetBase) { render() { return v('div', {}, [ v('div', { key: 'animated', - animate: this.getAnimation() + animate }), v('div', { key: 'nonAnimated' @@ -45,28 +25,12 @@ class TestWidget extends AnimatableMixin(WidgetBase) { ]); } - getMeta() { - return this.meta(AnimationPlayer); + callInvalidate() { + this.invalidate(); } -} -class PropertyFunctionWidget extends TestWidget { - getAnimation() { - const { controls = {}, - timing = {}, - effects = [ - { height: '0px' }, - { height: '10px' } - ] } = this.properties; - - return function () { - return { - id: 'animation', - effects, - controls, - timing - }; - }; + getMeta() { + return this.meta(AnimationPlayer); } } @@ -79,10 +43,23 @@ const cancelStub = stub(); const finishStub = stub(); const startStub = stub(); const currentStub = stub(); +const playbackRateStub = stub(); let metaNode: HTMLElement; registerSuite({ name: 'animatable', + 'beforeEach'() { + effects = [ + { height: '0px' }, + { height: '10px' } + ]; + controls = {}; + timing = {}; + animate = { + id: 'animation', + effects + }; + }, 'animatable mixin': { 'adds an animatable player for each node with animations'() { const widget = new TestWidget(); @@ -152,6 +129,9 @@ registerSuite({ set onfinish(onFinish: () => {}) { onFinish(); } + set playbackRate(rate: number) { + playbackRateStub(rate); + } } global.KeyframeEffect = KeyframeEffectMock; global.Animation = AnimationMock; @@ -166,6 +146,7 @@ registerSuite({ finishStub.reset(); startStub.reset(); currentStub.reset(); + playbackRateStub.reset(); metaNode = document.createElement('div'); }, afterEach() { @@ -180,13 +161,22 @@ registerSuite({ assert.isTrue(keyframeCtorStub.calledOnce); assert.isTrue(animationCtorStub.calledOnce); }, + 'reuses previous KeyframeEffect and Player when animation is still valid'() { + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + widget.callInvalidate(); + widget.__render__(); + assert.isTrue(keyframeCtorStub.calledOnce); + assert.isTrue(animationCtorStub.calledOnce); + }, 'passed timing and node info to keyframe effect'() { + animate.timing = { + duration: 2 + }; const widget = new TestWidget(); - widget.__setProperties__({ - timing: { - duration: 2 - } - }); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); @@ -213,12 +203,10 @@ registerSuite({ assert.isTrue(playStub.notCalled); }, 'plays when play set to true'() { + animate.controls = { + play: true + }; const widget = new TestWidget(); - widget.__setProperties__({ - controls: { - play: true - } - }); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); @@ -227,12 +215,10 @@ registerSuite({ assert.isTrue(pauseStub.notCalled); }, 'reverses when reverse set to true'() { + animate.controls = { + reverse: true + }; const widget = new TestWidget(); - widget.__setProperties__({ - controls: { - reverse: true - } - }); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); @@ -240,12 +226,10 @@ registerSuite({ assert.isTrue(reverseStub.calledOnce); }, 'cancels when cancel set to true'() { + animate.controls = { + cancel: true + }; const widget = new TestWidget(); - widget.__setProperties__({ - controls: { - cancel: true - } - }); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); @@ -253,25 +237,32 @@ registerSuite({ assert.isTrue(cancelStub.calledOnce); }, 'finishes when finish set to true'() { + animate.controls = { + finish: true + }; const widget = new TestWidget(); - widget.__setProperties__({ - controls: { - finish: true - } - }); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); widget.__render__(); assert.isTrue(finishStub.calledOnce); }, + 'sets playback rate when passed'() { + animate.controls = { + playbackRate: 2 + }; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(playbackRateStub.calledOnce); + }, 'can set start time'() { + animate.controls = { + startTime: 2 + }; const widget = new TestWidget(); - widget.__setProperties__({ - controls: { - startTime: 2 - } - }); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); @@ -280,12 +271,10 @@ registerSuite({ assert.isTrue(startStub.firstCall.calledWith(2)); }, 'can set current time'() { + animate.controls = { + currentTime: 2 + }; const widget = new TestWidget(); - widget.__setProperties__({ - controls: { - currentTime: 2 - } - }); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); @@ -294,11 +283,9 @@ registerSuite({ assert.isTrue(currentStub.firstCall.calledWith(2)); }, 'will execute effects function if one is passed'() { - const widget = new TestWidget(); const fx = stub().returns([]); - widget.__setProperties__({ - effects: fx - }); + animate.effects = fx; + const widget = new TestWidget(); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); @@ -312,29 +299,35 @@ registerSuite({ widget.__render__(); assert.isTrue(cancelStub.notCalled); - widget.__setProperties__({ - animate: false - }); + + widget.callInvalidate(); + animate = undefined; + widget.__render__(); assert.isTrue(cancelStub.calledOnce); }, 'will call onfinish function if passed'() { const onFinishStub = stub(); + animate.controls = { + onFinish: onFinishStub + }; const widget = new TestWidget(); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); - widget.__setProperties__({ - controls: { - onFinish: onFinishStub - } - }); - widget.__render__(); assert.isTrue(onFinishStub.calledOnce); }, 'can return a function instead of properties object'() { - const widget = new PropertyFunctionWidget(); + const animateReturn = { + id: 'animation', + effects, + controls, + timing + }; + animate = () => animateReturn; + + const widget = new TestWidget(); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); @@ -348,6 +341,36 @@ registerSuite({ ], {} )); + }, + 'does not create animation if function does not return properties'() { + animate = () => undefined; + + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(keyframeCtorStub.notCalled); + }, + 'can have multiple animations on a single node'() { + animate = [{ + id: 'animation1', + effects, + controls, + timing + }, + { + id: 'animation2', + effects, + controls, + timing + }]; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(keyframeCtorStub.calledTwice); } } }); From 577f895331ffc09ee4fddcfbc351e1b439779ce4 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Thu, 19 Oct 2017 16:27:50 +0100 Subject: [PATCH 07/33] reinstated tests --- tests/unit/all.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/all.ts b/tests/unit/all.ts index 97a14368..86877e0a 100644 --- a/tests/unit/all.ts +++ b/tests/unit/all.ts @@ -1,3 +1,4 @@ +import 'dojo/has!host-node?../support/loadJsdom'; import './Container'; import './WidgetBase'; import './Registry'; From 17abf43e312bc89e37dee4520eb8996b29f8fa77 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Thu, 19 Oct 2017 17:40:26 +0100 Subject: [PATCH 08/33] added typings for animate --- src/interfaces.d.ts | 39 +++++++++++++++++++++++++++++++++ src/mixins/Animatable.ts | 37 +++++-------------------------- tests/unit/mixins/Animatable.ts | 3 ++- 3 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index 155ae159..639ba1e0 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -86,6 +86,45 @@ export type SupportedClassName = string | null | undefined; export type DeferredVirtualProperties = (inserted: boolean) => VirtualDomProperties; +/** + * Animation controls are used to control the web animation that has been applied + * to a vdom node. + */ +export interface AnimationControls { + play?: boolean; + onFinish?: () => void; + reverse?: boolean; + cancel?: boolean; + finish?: boolean; + playbackRate?: number; + startTime?: number; + currentTime?: number; +} + +/** + * Animation timing properties passed to a new KeyframeEffect. + */ +export interface AnimationTimingProperties { + duration?: number; + delay?: number; + direction?: string; + easing?: string; + endDelay?: number; + fill?: string; + iterations?: number; + iterationStart?: number; +} + +/** + * Animation propertiues that can be passed as vdom property `animate` + */ +export interface AnimationProperties { + id: string; + effects: any[]; + controls?: AnimationControls; + timing?: AnimationTimingProperties; +} + export interface VirtualDomProperties { /** * The animation to perform when this node is added to an already existing parent. diff --git a/src/mixins/Animatable.ts b/src/mixins/Animatable.ts index 0cc57004..dcf93179 100644 --- a/src/mixins/Animatable.ts +++ b/src/mixins/Animatable.ts @@ -1,5 +1,5 @@ import 'web-animations-js/web-animations-next-lite.min'; -import { Constructor, DNode, HNode } from '../interfaces'; +import { Constructor, DNode, HNode, AnimationControls, AnimationProperties } from '../interfaces'; import { WidgetBase } from '../WidgetBase'; import { afterRender } from '../decorators/afterRender'; import { isHNode, decorate } from '../d'; @@ -9,35 +9,6 @@ import MetaBase from '../meta/Base'; declare const KeyframeEffect: any; declare const Animation: any; -export interface AnimationControls { - play?: boolean; - onFinish?: () => void; - reverse?: boolean; - cancel?: boolean; - finish?: boolean; - playbackRate?: number; - startTime?: number; - currentTime?: number; -} - -export interface AnimationTimingProperties { - duration?: number; - delay?: number; - direction?: string; - easing?: string; - endDelay?: number; - fill?: string; - iterations?: number; - iterationStart?: number; -} - -export interface AnimationProperties { - id: string; - effects: any[]; - controls?: AnimationControls; - timing?: AnimationTimingProperties; -} - export class AnimationPlayer extends MetaBase { private _animationMap = new Map(); @@ -107,7 +78,7 @@ export class AnimationPlayer extends MetaBase { } } - add(key: string, animateProperties: AnimationProperties[]) { + add(key: string, animateProperties: AnimationProperties | AnimationProperties[]) { const node = this.getNode(key); if (node) { @@ -160,7 +131,9 @@ export function AnimatableMixin>(Base: T): T { decorate(result, (node: HNode) => { const { animate, key } = node.properties; - this.meta(AnimationPlayer).add(key as string, animate); + if (animate && key) { + this.meta(AnimationPlayer).add(key as string, animate); + } }, (node: DNode) => { return !!(isHNode(node) && node.properties.animate && node.properties.key); diff --git a/tests/unit/mixins/Animatable.ts b/tests/unit/mixins/Animatable.ts index be6475a4..e5dee336 100644 --- a/tests/unit/mixins/Animatable.ts +++ b/tests/unit/mixins/Animatable.ts @@ -2,7 +2,8 @@ import global from '@dojo/shim/global'; import { VNode } from '@dojo/interfaces/vdom'; import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; -import { AnimatableMixin, AnimationPlayer, AnimationControls, AnimationTimingProperties } from '../../../src/mixins/Animatable'; +import { AnimatableMixin, AnimationPlayer } from '../../../src/mixins/Animatable'; +import { AnimationControls, AnimationTimingProperties } from '../../../src/interfaces'; import { WidgetBase } from '../../../src/WidgetBase'; import { v } from '../../../src/d'; import { spy, stub } from 'sinon'; From 0842ec86c6a1ee556e7ff6a135db5e19a82a228e Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 20 Oct 2017 12:20:26 +0100 Subject: [PATCH 09/33] changed to bdd style --- tests/unit/mixins/Animatable.ts | 183 ++++++++++++++++---------------- 1 file changed, 92 insertions(+), 91 deletions(-) diff --git a/tests/unit/mixins/Animatable.ts b/tests/unit/mixins/Animatable.ts index e5dee336..a19ff3c0 100644 --- a/tests/unit/mixins/Animatable.ts +++ b/tests/unit/mixins/Animatable.ts @@ -1,55 +1,55 @@ import global from '@dojo/shim/global'; import { VNode } from '@dojo/interfaces/vdom'; -import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; import { AnimatableMixin, AnimationPlayer } from '../../../src/mixins/Animatable'; import { AnimationControls, AnimationTimingProperties } from '../../../src/interfaces'; import { WidgetBase } from '../../../src/WidgetBase'; import { v } from '../../../src/d'; import { spy, stub } from 'sinon'; +import { beforeEach, before, describe, it } from 'intern!bdd'; + +describe('animatable', () => { + + let effects: any; + let controls: AnimationControls; + let timing: AnimationTimingProperties; + let animate: any; + + class TestWidget extends AnimatableMixin(WidgetBase) { + render() { + return v('div', {}, [ + v('div', { + key: 'animated', + animate + }), + v('div', { + key: 'nonAnimated' + }) + ]); + } -let effects: any; -let controls: AnimationControls; -let timing: AnimationTimingProperties; -let animate: any; - -class TestWidget extends AnimatableMixin(WidgetBase) { - render() { - return v('div', {}, [ - v('div', { - key: 'animated', - animate - }), - v('div', { - key: 'nonAnimated' - }) - ]); - } + callInvalidate() { + this.invalidate(); + } - callInvalidate() { - this.invalidate(); + getMeta() { + return this.meta(AnimationPlayer); + } } - getMeta() { - return this.meta(AnimationPlayer); - } -} - -const keyframeCtorStub = stub(); -const animationCtorStub = stub(); -const pauseStub = stub(); -const playStub = stub(); -const reverseStub = stub(); -const cancelStub = stub(); -const finishStub = stub(); -const startStub = stub(); -const currentStub = stub(); -const playbackRateStub = stub(); -let metaNode: HTMLElement; - -registerSuite({ - name: 'animatable', - 'beforeEach'() { + const keyframeCtorStub = stub(); + const animationCtorStub = stub(); + const pauseStub = stub(); + const playStub = stub(); + const reverseStub = stub(); + const cancelStub = stub(); + const finishStub = stub(); + const startStub = stub(); + const currentStub = stub(); + const playbackRateStub = stub(); + let metaNode: HTMLElement; + + beforeEach(() => { effects = [ { height: '0px' }, { height: '10px' } @@ -60,9 +60,10 @@ registerSuite({ id: 'animation', effects }; - }, - 'animatable mixin': { - 'adds an animatable player for each node with animations'() { + }); + + describe('mixin', () => { + it('adds an animatable player for each node with animations', () => { const widget = new TestWidget(); const meta = widget.getMeta(); @@ -70,8 +71,8 @@ registerSuite({ widget.__render__(); assert.isTrue(addSpy.calledOnce); - }, - 'clears animations after new animations have been added'() { + }); + it('clears animations after new animations have been added', () => { const widget = new TestWidget(); const meta = widget.getMeta(); @@ -82,8 +83,8 @@ registerSuite({ assert.isTrue(addSpy.calledOnce); assert.isTrue(clearSpy.calledOnce); assert.isTrue(clearSpy.calledAfter(addSpy)); - }, - 'only calls add on nodes with key and animate properties'() { + }); + it('only calls add on nodes with key and animate properties', () => { const widget = new TestWidget(); const meta = widget.getMeta(); @@ -93,10 +94,11 @@ registerSuite({ assert.equal(renderedWidget.children!.length, 2); assert.isTrue(addSpy.calledOnce); - } - }, - 'animation player': { - beforeAll() { + }); + }); + + describe('player', () => { + before(() => { class KeyframeEffectMock { constructor(...args: any[]) { keyframeCtorStub(...args); @@ -136,8 +138,9 @@ registerSuite({ } global.KeyframeEffect = KeyframeEffectMock; global.Animation = AnimationMock; - }, - beforeEach() { + }); + + beforeEach(() => { keyframeCtorStub.reset(); animationCtorStub.reset(); pauseStub.reset(); @@ -149,11 +152,9 @@ registerSuite({ currentStub.reset(); playbackRateStub.reset(); metaNode = document.createElement('div'); - }, - afterEach() { + }); - }, - 'creates new KeyframeEffect and Animation for each animated node'() { + it('creates new KeyframeEffect and Animation for each animated node', () => { const widget = new TestWidget(); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); @@ -161,8 +162,8 @@ registerSuite({ widget.__render__(); assert.isTrue(keyframeCtorStub.calledOnce); assert.isTrue(animationCtorStub.calledOnce); - }, - 'reuses previous KeyframeEffect and Player when animation is still valid'() { + }); + it('reuses previous KeyframeEffect and Player when animation is still valid', () => { const widget = new TestWidget(); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); @@ -172,8 +173,8 @@ registerSuite({ widget.__render__(); assert.isTrue(keyframeCtorStub.calledOnce); assert.isTrue(animationCtorStub.calledOnce); - }, - 'passed timing and node info to keyframe effect'() { + }); + it('passed timing and node info to keyframe effect', () => { animate.timing = { duration: 2 }; @@ -193,8 +194,8 @@ registerSuite({ duration: 2 } )); - }, - 'starts animations paused'() { + }); + it('starts animations paused', () => { const widget = new TestWidget(); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); @@ -202,8 +203,8 @@ registerSuite({ widget.__render__(); assert.isTrue(pauseStub.calledOnce); assert.isTrue(playStub.notCalled); - }, - 'plays when play set to true'() { + }); + it('plays when play set to true', () => { animate.controls = { play: true }; @@ -214,8 +215,8 @@ registerSuite({ widget.__render__(); assert.isTrue(playStub.calledOnce); assert.isTrue(pauseStub.notCalled); - }, - 'reverses when reverse set to true'() { + }); + it('reverses when reverse set to true', () => { animate.controls = { reverse: true }; @@ -225,8 +226,8 @@ registerSuite({ widget.__render__(); assert.isTrue(reverseStub.calledOnce); - }, - 'cancels when cancel set to true'() { + }); + it('cancels when cancel set to true', () => { animate.controls = { cancel: true }; @@ -236,8 +237,8 @@ registerSuite({ widget.__render__(); assert.isTrue(cancelStub.calledOnce); - }, - 'finishes when finish set to true'() { + }); + it('finishes when finish set to true', () => { animate.controls = { finish: true }; @@ -247,8 +248,8 @@ registerSuite({ widget.__render__(); assert.isTrue(finishStub.calledOnce); - }, - 'sets playback rate when passed'() { + }); + it('sets playback rate when passed', () => { animate.controls = { playbackRate: 2 }; @@ -258,8 +259,8 @@ registerSuite({ widget.__render__(); assert.isTrue(playbackRateStub.calledOnce); - }, - 'can set start time'() { + }); + it('can set start time', () => { animate.controls = { startTime: 2 }; @@ -270,8 +271,8 @@ registerSuite({ widget.__render__(); assert.isTrue(startStub.calledOnce); assert.isTrue(startStub.firstCall.calledWith(2)); - }, - 'can set current time'() { + }); + it('can set current time', () => { animate.controls = { currentTime: 2 }; @@ -282,8 +283,8 @@ registerSuite({ widget.__render__(); assert.isTrue(currentStub.calledOnce); assert.isTrue(currentStub.firstCall.calledWith(2)); - }, - 'will execute effects function if one is passed'() { + }); + it('will execute effects function if one is passed', () => { const fx = stub().returns([]); animate.effects = fx; const widget = new TestWidget(); @@ -292,8 +293,8 @@ registerSuite({ widget.__render__(); assert.isTrue(fx.calledOnce); - }, - 'clears down used animations on next render if theyve been removed'() { + }); + it('clears down used animations on next render if theyve been removed', () => { const widget = new TestWidget(); const meta = widget.getMeta(); stub(meta, 'getNode').returns(metaNode); @@ -306,8 +307,8 @@ registerSuite({ widget.__render__(); assert.isTrue(cancelStub.calledOnce); - }, - 'will call onfinish function if passed'() { + }); + it('will call onfinish function if passed', () => { const onFinishStub = stub(); animate.controls = { onFinish: onFinishStub @@ -318,8 +319,8 @@ registerSuite({ widget.__render__(); assert.isTrue(onFinishStub.calledOnce); - }, - 'can return a function instead of properties object'() { + }); + it('can return a function instead of properties object', () => { const animateReturn = { id: 'animation', effects, @@ -342,8 +343,8 @@ registerSuite({ ], {} )); - }, - 'does not create animation if function does not return properties'() { + }); + it('does not create animation if function does not return properties', () => { animate = () => undefined; const widget = new TestWidget(); @@ -352,8 +353,8 @@ registerSuite({ widget.__render__(); assert.isTrue(keyframeCtorStub.notCalled); - }, - 'can have multiple animations on a single node'() { + }); + it('can have multiple animations on a single node', () => { animate = [{ id: 'animation1', effects, @@ -372,6 +373,6 @@ registerSuite({ widget.__render__(); assert.isTrue(keyframeCtorStub.calledTwice); - } - } + }); + }); }); From 8c0d3dac697acb5087789c481636e2caeef2865e Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 20 Oct 2017 16:33:35 +0100 Subject: [PATCH 10/33] adding animations readme info --- README.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/README.md b/README.md index d43238d2..a443c752 100644 --- a/README.md +++ b/README.md @@ -302,6 +302,101 @@ function StateMixin WidgetBase>(Base: T): T & ( Examples of Dojo 2 mixins can be seen with `ThemedMixin` and `I18nMixin` that are described in [Classes & theming](#classes--theming) and [Internationalization](#internationalization) sections. +### Animation + +Dojo 2 widget-core provides a `Animatable` mixin to decorate a widget with the functionality to apply web animations to the nodes that it creates. + +To specify the web animations pass an `animate` property to the node you wish to animate. This can be a single animation or an array or animations. + +## Basic Example + +```ts +export default class AnimatedWidget extends AnimatableMixin(WidgetBase) { + protected render() { + return v('div', { + key: 'root', + animate: { + id: 'rootAnimation', + effects: [ + { height: `10px` }, + { height: `100px` } + ], + controls: { + play: true + } + } + }) + } +} +``` + +`controls` and `timing` are optional properties and are used to setup and control the animation. The `timing` property can only be set once, but the `controls` can be changed to stop / start / reverse etc... the web animation. + +## Changing Animation + +Animations can be changed on each widget render in a reactive pattern, for example changing the animation from `slideUp` to `slideDown` on a title pane depending of if the titlepane is open or not. + +```ts +export default class AnimatedWidget extends AnimatableMixin(WidgetBase) { + private _open = false; + + protected render() { + return v('div', { + key: 'root', + animate: this._open ? { + id: 'upAnimation', + effects: [ + { height: `100px` }, + { height: `0px` } + ], + controls: { + play: true + } + } : { + id: 'downAnimation', + effects: [ + { height: `0px` }, + { height: `100px` } + ], + controls: { + play: true + } + } + }) + } +} +``` + +## Passing an effects function + +an `effects` function can be passed to the animation and evaluated at render time. This allows you to create programatic effects such as those depending on measurements from the `Dimensions` `Meta`. + +```ts +export default class AnimatedWidget extends AnimatableMixin(WidgetBase) { + private _getEffect() { + const { scroll } = this.meta(Dimensions).get('content'); + + return [ + { height: `0px` }, + { height: `${scroll.height}px` } + ]; + } + + protected render() { + return v('div', { + key: 'root', + animate: { + id: 'upAnimation', + effects: this._getEffect(), + controls: { + play: true + } + } + }) + } +} +``` + ### Styling & Theming #### Overview From 1a1ab07fa0066f5707f244513df630c46138864b Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 20 Oct 2017 16:35:24 +0100 Subject: [PATCH 11/33] fix headings --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a443c752..26eb3eb7 100644 --- a/README.md +++ b/README.md @@ -308,7 +308,7 @@ Dojo 2 widget-core provides a `Animatable` mixin to decorate a widget with the f To specify the web animations pass an `animate` property to the node you wish to animate. This can be a single animation or an array or animations. -## Basic Example +#### Basic Example ```ts export default class AnimatedWidget extends AnimatableMixin(WidgetBase) { @@ -332,7 +332,7 @@ export default class AnimatedWidget extends AnimatableMixin(WidgetBase) { `controls` and `timing` are optional properties and are used to setup and control the animation. The `timing` property can only be set once, but the `controls` can be changed to stop / start / reverse etc... the web animation. -## Changing Animation +#### Changing Animation Animations can be changed on each widget render in a reactive pattern, for example changing the animation from `slideUp` to `slideDown` on a title pane depending of if the titlepane is open or not. @@ -367,7 +367,7 @@ export default class AnimatedWidget extends AnimatableMixin(WidgetBase) { } ``` -## Passing an effects function +#### Passing an effects function an `effects` function can be passed to the animation and evaluated at render time. This allows you to create programatic effects such as those depending on measurements from the `Dimensions` `Meta`. From da7d027136d151014aae785e21514f4a997edb0e Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 20 Oct 2017 16:36:03 +0100 Subject: [PATCH 12/33] add index --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 26eb3eb7..f7ad68be 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ widget-core is a library to create powerful, composable user interface widgets. - [Composing Widgets](#composing-widgets) - [Decomposing Widgets](#decomposing-widgets) - [Mixins](#mixins) + - [Animations](#animations) - [Styling & Theming](#styling--theming) - [Internationalization](#internationalization) - [Key Principles](#key-principles) From 4ce0f742a27d57e1f1cbb1b0f2552ddee3de5d1f Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 20 Oct 2017 16:37:06 +0100 Subject: [PATCH 13/33] add index --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f7ad68be..65db2845 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ widget-core is a library to create powerful, composable user interface widgets. - [Composing Widgets](#composing-widgets) - [Decomposing Widgets](#decomposing-widgets) - [Mixins](#mixins) - - [Animations](#animations) + - [Animation](#animation) - [Styling & Theming](#styling--theming) - [Internationalization](#internationalization) - [Key Principles](#key-principles) From bfac7b7977b3a46b5f4ad9335af15e298754edff Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Tue, 24 Oct 2017 17:01:15 +0100 Subject: [PATCH 14/33] auto bind onFinish and onCancel --- src/interfaces.d.ts | 1 + src/mixins/Animatable.ts | 26 +++++++++++++++++++++++--- tests/unit/mixins/Animatable.ts | 15 +++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index 639ba1e0..10d0971d 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -93,6 +93,7 @@ export type DeferredVirtualProperties = (inserted: boolean) => VirtualDomPropert export interface AnimationControls { play?: boolean; onFinish?: () => void; + onCancel?: () => void; reverse?: boolean; cancel?: boolean; finish?: boolean; diff --git a/src/mixins/Animatable.ts b/src/mixins/Animatable.ts index dcf93179..2ed9ca7c 100644 --- a/src/mixins/Animatable.ts +++ b/src/mixins/Animatable.ts @@ -37,6 +37,7 @@ export class AnimationPlayer extends MetaBase { cancel, finish, onFinish, + onCancel, playbackRate, startTime, currentTime @@ -76,9 +77,27 @@ export class AnimationPlayer extends MetaBase { if (onFinish) { player.onfinish = onFinish; } + + if (onCancel) { + player.oncancel = onCancel; + } + } + + private _bindControlCallbacks(controls: AnimationControls, bindScope: any): AnimationControls { + + const { + onFinish, + onCancel + } = controls; + + return { + ...controls, + onFinish: onFinish ? onFinish.bind(bindScope) : null, + onCancel: onCancel ? onCancel.bind(bindScope) : null + }; } - add(key: string, animateProperties: AnimationProperties | AnimationProperties[]) { + add(key: string, animateProperties: AnimationProperties | AnimationProperties[], bindScope: any) { const node = this.getNode(key); if (node) { @@ -91,6 +110,7 @@ export class AnimationPlayer extends MetaBase { if (properties) { const { id } = properties; if (!this._animationMap.has(id)) { + this._animationMap.set(id, { player: this._createPlayer(node, properties), used: true @@ -100,7 +120,7 @@ export class AnimationPlayer extends MetaBase { const { player } = this._animationMap.get(id); const { controls = {} } = properties; - this._updatePlayer(player, controls); + this._updatePlayer(player, this._bindControlCallbacks(controls, bindScope)); this._animationMap.set(id, { player, @@ -132,7 +152,7 @@ export function AnimatableMixin>(Base: T): T { (node: HNode) => { const { animate, key } = node.properties; if (animate && key) { - this.meta(AnimationPlayer).add(key as string, animate); + this.meta(AnimationPlayer).add(key as string, animate, this); } }, (node: DNode) => { diff --git a/tests/unit/mixins/Animatable.ts b/tests/unit/mixins/Animatable.ts index a19ff3c0..cf929f76 100644 --- a/tests/unit/mixins/Animatable.ts +++ b/tests/unit/mixins/Animatable.ts @@ -132,6 +132,9 @@ describe('animatable', () => { set onfinish(onFinish: () => {}) { onFinish(); } + set oncancel(onCancel: () => {}) { + onCancel(); + } set playbackRate(rate: number) { playbackRateStub(rate); } @@ -320,6 +323,18 @@ describe('animatable', () => { widget.__render__(); assert.isTrue(onFinishStub.calledOnce); }); + it('will call oncancel function if passed', () => { + const onCancelStub = stub(); + animate.controls = { + onCancel: onCancelStub + }; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(onCancelStub.calledOnce); + }); it('can return a function instead of properties object', () => { const animateReturn = { id: 'animation', From 75e2b7b367198ea64f70cbc09f671e08e7957df6 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 30 Oct 2017 16:52:58 +0000 Subject: [PATCH 15/33] fix animatable --- tests/unit/mixins/Animatable.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unit/mixins/Animatable.ts b/tests/unit/mixins/Animatable.ts index cf929f76..75e058cb 100644 --- a/tests/unit/mixins/Animatable.ts +++ b/tests/unit/mixins/Animatable.ts @@ -1,8 +1,7 @@ import global from '@dojo/shim/global'; -import { VNode } from '@dojo/interfaces/vdom'; import * as assert from 'intern/chai!assert'; import { AnimatableMixin, AnimationPlayer } from '../../../src/mixins/Animatable'; -import { AnimationControls, AnimationTimingProperties } from '../../../src/interfaces'; +import { HNode, AnimationControls, AnimationTimingProperties } from '../../../src/interfaces'; import { WidgetBase } from '../../../src/WidgetBase'; import { v } from '../../../src/d'; import { spy, stub } from 'sinon'; @@ -90,7 +89,7 @@ describe('animatable', () => { const addSpy = spy(meta, 'add'); - const renderedWidget = (widget.__render__() as VNode); + const renderedWidget = widget.__render__() as HNode; assert.equal(renderedWidget.children!.length, 2); assert.isTrue(addSpy.calledOnce); From 7bf09931875e0b806efb6e6e8b03c881d28466b1 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 30 Oct 2017 17:21:59 +0000 Subject: [PATCH 16/33] add package lock again --- package-lock.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package-lock.json b/package-lock.json index 662b0a41..f87f7143 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7703,6 +7703,11 @@ "replace-ext": "0.0.1" } }, + "web-animations-js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/web-animations-js/-/web-animations-js-2.3.1.tgz", + "integrity": "sha1-Om2bwVGWN3qQ+OKAP6UmIWWwRRA=" + }, "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", From d9b3cae83869b24ba808c9659489f2706070aaba Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Tue, 31 Oct 2017 17:19:43 +0000 Subject: [PATCH 17/33] changed to animated --- README.md | 8 ++++---- src/mixins/{Animatable.ts => Animated.ts} | 4 ++-- tests/unit/mixins/{Animatable.ts => Animated.ts} | 12 ++++++------ tests/unit/mixins/Themed.ts | 1 - tests/unit/mixins/all.ts | 2 +- 5 files changed, 13 insertions(+), 14 deletions(-) rename src/mixins/{Animatable.ts => Animated.ts} (96%) rename tests/unit/mixins/{Animatable.ts => Animated.ts} (96%) diff --git a/README.md b/README.md index 65db2845..89bf479c 100644 --- a/README.md +++ b/README.md @@ -305,14 +305,14 @@ Examples of Dojo 2 mixins can be seen with `ThemedMixin` and `I18nMixin` that ar ### Animation -Dojo 2 widget-core provides a `Animatable` mixin to decorate a widget with the functionality to apply web animations to the nodes that it creates. +Dojo 2 widget-core provides a `Animated` mixin to decorate a widget with the functionality to apply web animations to the nodes that it creates. To specify the web animations pass an `animate` property to the node you wish to animate. This can be a single animation or an array or animations. #### Basic Example ```ts -export default class AnimatedWidget extends AnimatableMixin(WidgetBase) { +export default class AnimatedWidget extends AnimatedMixin(WidgetBase) { protected render() { return v('div', { key: 'root', @@ -338,7 +338,7 @@ export default class AnimatedWidget extends AnimatableMixin(WidgetBase) { Animations can be changed on each widget render in a reactive pattern, for example changing the animation from `slideUp` to `slideDown` on a title pane depending of if the titlepane is open or not. ```ts -export default class AnimatedWidget extends AnimatableMixin(WidgetBase) { +export default class AnimatedWidget extends AnimatedMixin(WidgetBase) { private _open = false; protected render() { @@ -373,7 +373,7 @@ export default class AnimatedWidget extends AnimatableMixin(WidgetBase) { an `effects` function can be passed to the animation and evaluated at render time. This allows you to create programatic effects such as those depending on measurements from the `Dimensions` `Meta`. ```ts -export default class AnimatedWidget extends AnimatableMixin(WidgetBase) { +export default class AnimatedWidget extends AnimatedMixin(WidgetBase) { private _getEffect() { const { scroll } = this.meta(Dimensions).get('content'); diff --git a/src/mixins/Animatable.ts b/src/mixins/Animated.ts similarity index 96% rename from src/mixins/Animatable.ts rename to src/mixins/Animated.ts index 2ed9ca7c..76ff0675 100644 --- a/src/mixins/Animatable.ts +++ b/src/mixins/Animated.ts @@ -143,7 +143,7 @@ export class AnimationPlayer extends MetaBase { } -export function AnimatableMixin>(Base: T): T { +export function AnimatedMixin>(Base: T): T { class Animated extends Base { @afterRender() @@ -167,4 +167,4 @@ export function AnimatableMixin>(Base: T): T { return Animated; } -export default AnimatableMixin; +export default AnimatedMixin; diff --git a/tests/unit/mixins/Animatable.ts b/tests/unit/mixins/Animated.ts similarity index 96% rename from tests/unit/mixins/Animatable.ts rename to tests/unit/mixins/Animated.ts index 75e058cb..0bd6c047 100644 --- a/tests/unit/mixins/Animatable.ts +++ b/tests/unit/mixins/Animated.ts @@ -1,20 +1,20 @@ import global from '@dojo/shim/global'; -import * as assert from 'intern/chai!assert'; -import { AnimatableMixin, AnimationPlayer } from '../../../src/mixins/Animatable'; +const { assert } = intern.getPlugin('chai'); +const { beforeEach, before, describe, it} = intern.getInterface('bdd'); +import { AnimatedMixin, AnimationPlayer } from '../../../src/mixins/Animated'; import { HNode, AnimationControls, AnimationTimingProperties } from '../../../src/interfaces'; import { WidgetBase } from '../../../src/WidgetBase'; import { v } from '../../../src/d'; import { spy, stub } from 'sinon'; -import { beforeEach, before, describe, it } from 'intern!bdd'; -describe('animatable', () => { +describe('animated', () => { let effects: any; let controls: AnimationControls; let timing: AnimationTimingProperties; let animate: any; - class TestWidget extends AnimatableMixin(WidgetBase) { + class TestWidget extends AnimatedMixin(WidgetBase) { render() { return v('div', {}, [ v('div', { @@ -62,7 +62,7 @@ describe('animatable', () => { }); describe('mixin', () => { - it('adds an animatable player for each node with animations', () => { + it('adds an animation player for each node with animations', () => { const widget = new TestWidget(); const meta = widget.getMeta(); diff --git a/tests/unit/mixins/Themed.ts b/tests/unit/mixins/Themed.ts index 5233a7da..be55649c 100644 --- a/tests/unit/mixins/Themed.ts +++ b/tests/unit/mixins/Themed.ts @@ -1,4 +1,3 @@ - const { registerSuite } = intern.getInterface('object'); const { assert } = intern.getPlugin('chai'); import { diff --git a/tests/unit/mixins/all.ts b/tests/unit/mixins/all.ts index 9826bcec..97972cb3 100644 --- a/tests/unit/mixins/all.ts +++ b/tests/unit/mixins/all.ts @@ -1,4 +1,4 @@ import './Themed'; -import './Animatable'; +import './Animated'; import './Projector'; import './I18n'; From 2b734c63b02db104603fe3f98ed87d56684cccdd Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Tue, 31 Oct 2017 17:33:41 +0000 Subject: [PATCH 18/33] fixed test --- tests/unit/all.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/all.ts b/tests/unit/all.ts index 86877e0a..97a14368 100644 --- a/tests/unit/all.ts +++ b/tests/unit/all.ts @@ -1,4 +1,3 @@ -import 'dojo/has!host-node?../support/loadJsdom'; import './Container'; import './WidgetBase'; import './Registry'; From be94fc5be5780d9755cc888a67b81e495b82bdbd Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Thu, 2 Nov 2017 10:50:29 +0000 Subject: [PATCH 19/33] pr comments --- README.md | 14 +++++++------- src/mixins/Animated.ts | 11 ++++++++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 89bf479c..316f3903 100644 --- a/README.md +++ b/README.md @@ -319,8 +319,8 @@ export default class AnimatedWidget extends AnimatedMixin(WidgetBase) { animate: { id: 'rootAnimation', effects: [ - { height: `10px` }, - { height: `100px` } + { height: '10px' }, + { height: '100px' } ], controls: { play: true @@ -347,8 +347,8 @@ export default class AnimatedWidget extends AnimatedMixin(WidgetBase) { animate: this._open ? { id: 'upAnimation', effects: [ - { height: `100px` }, - { height: `0px` } + { height: '100px' }, + { height: '0px' } ], controls: { play: true @@ -356,8 +356,8 @@ export default class AnimatedWidget extends AnimatedMixin(WidgetBase) { } : { id: 'downAnimation', effects: [ - { height: `0px` }, - { height: `100px` } + { height: '0px' }, + { height: '100px' } ], controls: { play: true @@ -378,7 +378,7 @@ export default class AnimatedWidget extends AnimatedMixin(WidgetBase) { const { scroll } = this.meta(Dimensions).get('content'); return [ - { height: `0px` }, + { height: '0px' }, { height: `${scroll.height}px` } ]; } diff --git a/src/mixins/Animated.ts b/src/mixins/Animated.ts index 76ff0675..b0222762 100644 --- a/src/mixins/Animated.ts +++ b/src/mixins/Animated.ts @@ -151,12 +151,17 @@ export function AnimatedMixin>(Base: T): T { decorate(result, (node: HNode) => { const { animate, key } = node.properties; - if (animate && key) { - this.meta(AnimationPlayer).add(key as string, animate, this); + if (animate) { + if (key) { + this.meta(AnimationPlayer).add(key as string, animate, this); + } + else { + console.warn('Animate properties can only be used on a node with a key'); + } } }, (node: DNode) => { - return !!(isHNode(node) && node.properties.animate && node.properties.key); + return !!(isHNode(node) && node.properties.animate); } ); this.meta(AnimationPlayer).clearAnimations(); From c0566c5f177b2b4b9b8cf4e30f8d8333d3b29f5c Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Thu, 2 Nov 2017 16:38:26 +0000 Subject: [PATCH 20/33] moved animated to meta --- package-lock.json | 8 +- package.json | 1 + src/WidgetBase.ts | 6 + src/interfaces.d.ts | 5 +- src/meta/Base.ts | 4 + .../Animated.ts => meta/WebAnimation.ts} | 72 +--- tests/unit/meta/WebAnimations.ts | 392 ++++++++++++++++++ tests/unit/mixins/Animated.ts | 392 ------------------ tests/unit/mixins/all.ts | 1 - tsconfig.json | 2 +- 10 files changed, 433 insertions(+), 450 deletions(-) rename src/{mixins/Animated.ts => meta/WebAnimation.ts} (59%) create mode 100644 tests/unit/meta/WebAnimations.ts delete mode 100644 tests/unit/mixins/Animated.ts diff --git a/package-lock.json b/package-lock.json index f87f7143..691b2f23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@dojo/widget-core", - "version": "0.1.3-pre", + "version": "0.1.3-alpha.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -381,6 +381,12 @@ "integrity": "sha1-zF8Z0haUFtVWzcoFtZsp5F+kl+I=", "dev": true }, + "@types/web-animations-js": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@types/web-animations-js/-/web-animations-js-2.2.5.tgz", + "integrity": "sha512-3kjO6yvLt1e673wtcKEz0lgLKqPkBiuwxQj0DQ1jj+48HB03emIlTQYcqKAvB9UwOXq09QrWy/Dm6ZU8xMZVTw==", + "dev": true + }, "@types/ws": { "version": "0.0.42", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-0.0.42.tgz", diff --git a/package.json b/package.json index d6f79588..7ddc5890 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@types/grunt": "0.4.*", "@types/jsdom": "2.0.*", "@types/sinon": "^1.16.31", + "@types/web-animations-js": "^2.2.5", "@webcomponents/custom-elements": "^v1.0.0-rc.3", "codecov.io": "0.1.6", "glob": "^7.0.6", diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index d6b692cf..7f8d270b 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -114,6 +114,8 @@ export class WidgetBase

extends E private _nodeHandler: NodeHandler; + private _metaAfterRenders: (() => void)[] = []; + /** * @constructor */ @@ -157,6 +159,7 @@ export class WidgetBase

extends E invalidate: this._boundInvalidate, nodeHandler: this._nodeHandler }); + this._metaAfterRenders.push(cached.afterRender.bind(cached)); this._metaMap.set(MetaType, cached); this.own(cached); } @@ -474,6 +477,9 @@ export class WidgetBase

extends E return afterRenderFunction.call(this, dNode); }, dNode); } + if (this._metaAfterRenders.length) { + this._metaAfterRenders.forEach(afterRender => afterRender()); + } return dNode; } } diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index 10d0971d..faab3a8b 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -108,10 +108,10 @@ export interface AnimationControls { export interface AnimationTimingProperties { duration?: number; delay?: number; - direction?: string; + direction?: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse'; easing?: string; endDelay?: number; - fill?: string; + fill?: 'none' | 'forwards' | 'backwards' | 'both' | 'auto'; iterations?: number; iterationStart?: number; } @@ -431,6 +431,7 @@ export interface WidgetBaseInterface< */ export interface WidgetMetaBase extends Destroyable { has(key: string | number): boolean; + afterRender(): void; } /** diff --git a/src/meta/Base.ts b/src/meta/Base.ts index 456188df..9d7cb46f 100644 --- a/src/meta/Base.ts +++ b/src/meta/Base.ts @@ -40,6 +40,10 @@ export class Base extends Destroyable implements WidgetMetaBase { protected invalidate(): void { this._invalidate(); } + + public afterRender(): void { + // No nothing by default + } } export default Base; diff --git a/src/mixins/Animated.ts b/src/meta/WebAnimation.ts similarity index 59% rename from src/mixins/Animated.ts rename to src/meta/WebAnimation.ts index b0222762..36471035 100644 --- a/src/mixins/Animated.ts +++ b/src/meta/WebAnimation.ts @@ -1,15 +1,9 @@ import 'web-animations-js/web-animations-next-lite.min'; -import { Constructor, DNode, HNode, AnimationControls, AnimationProperties } from '../interfaces'; -import { WidgetBase } from '../WidgetBase'; -import { afterRender } from '../decorators/afterRender'; -import { isHNode, decorate } from '../d'; +import { Base } from './Base'; +import { AnimationControls, AnimationProperties } from '../interfaces'; import Map from '@dojo/shim/Map'; -import MetaBase from '../meta/Base'; -declare const KeyframeEffect: any; -declare const Animation: any; - -export class AnimationPlayer extends MetaBase { +export class WebAnimations extends Base { private _animationMap = new Map(); @@ -83,19 +77,19 @@ export class AnimationPlayer extends MetaBase { } } - private _bindControlCallbacks(controls: AnimationControls, bindScope: any): AnimationControls { + // private _bindControlCallbacks(controls: AnimationControls, bindScope: any): AnimationControls { - const { - onFinish, - onCancel - } = controls; + // const { + // onFinish, + // onCancel + // } = controls; - return { - ...controls, - onFinish: onFinish ? onFinish.bind(bindScope) : null, - onCancel: onCancel ? onCancel.bind(bindScope) : null - }; - } + // return { + // ...controls, + // onFinish: onFinish ? onFinish.bind(bindScope) : null, + // onCancel: onCancel ? onCancel.bind(bindScope) : null + // }; + // } add(key: string, animateProperties: AnimationProperties | AnimationProperties[], bindScope: any) { const node = this.getNode(key); @@ -120,7 +114,7 @@ export class AnimationPlayer extends MetaBase { const { player } = this._animationMap.get(id); const { controls = {} } = properties; - this._updatePlayer(player, this._bindControlCallbacks(controls, bindScope)); + this._updatePlayer(player, controls); // this._bindControlCallbacks(controls, bindScope)); this._animationMap.set(id, { player, @@ -131,7 +125,9 @@ export class AnimationPlayer extends MetaBase { } } - clearAnimations() { + afterRender() { + super.afterRender(); + this._animationMap.forEach((animation, key) => { if (!animation.used) { animation.player.cancel(); @@ -140,36 +136,6 @@ export class AnimationPlayer extends MetaBase { animation.used = false; }); } - -} - -export function AnimatedMixin>(Base: T): T { - class Animated extends Base { - - @afterRender() - protected decorateAfterRender(result: DNode): DNode { - decorate(result, - (node: HNode) => { - const { animate, key } = node.properties; - if (animate) { - if (key) { - this.meta(AnimationPlayer).add(key as string, animate, this); - } - else { - console.warn('Animate properties can only be used on a node with a key'); - } - } - }, - (node: DNode) => { - return !!(isHNode(node) && node.properties.animate); - } - ); - this.meta(AnimationPlayer).clearAnimations(); - return result; - } - } - - return Animated; } -export default AnimatedMixin; +export default WebAnimations; diff --git a/tests/unit/meta/WebAnimations.ts b/tests/unit/meta/WebAnimations.ts new file mode 100644 index 00000000..d499cc32 --- /dev/null +++ b/tests/unit/meta/WebAnimations.ts @@ -0,0 +1,392 @@ +// import global from '@dojo/shim/global'; +// const { assert } = intern.getPlugin('chai'); +// const { beforeEach, before, describe, it} = intern.getInterface('bdd'); +// import { AnimatedMixin, AnimationPlayer } from '../../../src/mixins/Animated'; +// import { HNode, AnimationControls, AnimationTimingProperties } from '../../../src/interfaces'; +// import { WidgetBase } from '../../../src/WidgetBase'; +// import { v } from '../../../src/d'; +// import { spy, stub } from 'sinon'; + +// describe('animated', () => { + +// let effects: any; +// let controls: AnimationControls; +// let timing: AnimationTimingProperties; +// let animate: any; + +// class TestWidget extends AnimatedMixin(WidgetBase) { +// render() { +// return v('div', {}, [ +// v('div', { +// key: 'animated', +// animate +// }), +// v('div', { +// key: 'nonAnimated' +// }) +// ]); +// } + +// callInvalidate() { +// this.invalidate(); +// } + +// getMeta() { +// return this.meta(AnimationPlayer); +// } +// } + +// const keyframeCtorStub = stub(); +// const animationCtorStub = stub(); +// const pauseStub = stub(); +// const playStub = stub(); +// const reverseStub = stub(); +// const cancelStub = stub(); +// const finishStub = stub(); +// const startStub = stub(); +// const currentStub = stub(); +// const playbackRateStub = stub(); +// let metaNode: HTMLElement; + +// beforeEach(() => { +// effects = [ +// { height: '0px' }, +// { height: '10px' } +// ]; +// controls = {}; +// timing = {}; +// animate = { +// id: 'animation', +// effects +// }; +// }); + +// describe('mixin', () => { +// it('adds an animation player for each node with animations', () => { +// const widget = new TestWidget(); +// const meta = widget.getMeta(); + +// const addSpy = spy(meta, 'add'); + +// widget.__render__(); +// assert.isTrue(addSpy.calledOnce); +// }); +// it('clears animations after new animations have been added', () => { +// const widget = new TestWidget(); +// const meta = widget.getMeta(); + +// const addSpy = spy(meta, 'add'); +// const clearSpy = spy(meta, 'clearAnimations'); + +// widget.__render__(); +// assert.isTrue(addSpy.calledOnce); +// assert.isTrue(clearSpy.calledOnce); +// assert.isTrue(clearSpy.calledAfter(addSpy)); +// }); +// it('only calls add on nodes with key and animate properties', () => { +// const widget = new TestWidget(); +// const meta = widget.getMeta(); + +// const addSpy = spy(meta, 'add'); + +// const renderedWidget = widget.__render__() as HNode; + +// assert.equal(renderedWidget.children!.length, 2); +// assert.isTrue(addSpy.calledOnce); +// }); +// }); + +// describe('player', () => { +// before(() => { +// class KeyframeEffectMock { +// constructor(...args: any[]) { +// keyframeCtorStub(...args); +// } +// } +// class AnimationMock { +// constructor(...args: any[]) { +// animationCtorStub(...args); +// } +// pause() { +// pauseStub(); +// } +// play() { +// playStub(); +// } +// reverse() { +// reverseStub(); +// } +// cancel() { +// cancelStub(); +// } +// finish() { +// finishStub(); +// } +// startTime(time: number) { +// startStub(time); +// } +// currentTime(time: number) { +// currentStub(time); +// } +// set onfinish(onFinish: () => {}) { +// onFinish(); +// } +// set oncancel(onCancel: () => {}) { +// onCancel(); +// } +// set playbackRate(rate: number) { +// playbackRateStub(rate); +// } +// } +// global.KeyframeEffect = KeyframeEffectMock; +// global.Animation = AnimationMock; +// }); + +// beforeEach(() => { +// keyframeCtorStub.reset(); +// animationCtorStub.reset(); +// pauseStub.reset(); +// playStub.reset(); +// reverseStub.reset(); +// cancelStub.reset(); +// finishStub.reset(); +// startStub.reset(); +// currentStub.reset(); +// playbackRateStub.reset(); +// metaNode = document.createElement('div'); +// }); + +// it('creates new KeyframeEffect and Animation for each animated node', () => { +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(keyframeCtorStub.calledOnce); +// assert.isTrue(animationCtorStub.calledOnce); +// }); +// it('reuses previous KeyframeEffect and Player when animation is still valid', () => { +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// widget.callInvalidate(); +// widget.__render__(); +// assert.isTrue(keyframeCtorStub.calledOnce); +// assert.isTrue(animationCtorStub.calledOnce); +// }); +// it('passed timing and node info to keyframe effect', () => { +// animate.timing = { +// duration: 2 +// }; +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(keyframeCtorStub.calledOnce); +// assert.isTrue(keyframeCtorStub.firstCall.calledWithMatch( +// metaNode, +// [ +// { height: '0px' }, +// { height: '10px' } +// ], +// { +// duration: 2 +// } +// )); +// }); +// it('starts animations paused', () => { +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(pauseStub.calledOnce); +// assert.isTrue(playStub.notCalled); +// }); +// it('plays when play set to true', () => { +// animate.controls = { +// play: true +// }; +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(playStub.calledOnce); +// assert.isTrue(pauseStub.notCalled); +// }); +// it('reverses when reverse set to true', () => { +// animate.controls = { +// reverse: true +// }; +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(reverseStub.calledOnce); +// }); +// it('cancels when cancel set to true', () => { +// animate.controls = { +// cancel: true +// }; +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(cancelStub.calledOnce); +// }); +// it('finishes when finish set to true', () => { +// animate.controls = { +// finish: true +// }; +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(finishStub.calledOnce); +// }); +// it('sets playback rate when passed', () => { +// animate.controls = { +// playbackRate: 2 +// }; +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(playbackRateStub.calledOnce); +// }); +// it('can set start time', () => { +// animate.controls = { +// startTime: 2 +// }; +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(startStub.calledOnce); +// assert.isTrue(startStub.firstCall.calledWith(2)); +// }); +// it('can set current time', () => { +// animate.controls = { +// currentTime: 2 +// }; +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(currentStub.calledOnce); +// assert.isTrue(currentStub.firstCall.calledWith(2)); +// }); +// it('will execute effects function if one is passed', () => { +// const fx = stub().returns([]); +// animate.effects = fx; +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(fx.calledOnce); +// }); +// it('clears down used animations on next render if theyve been removed', () => { +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(cancelStub.notCalled); + +// widget.callInvalidate(); +// animate = undefined; + +// widget.__render__(); +// assert.isTrue(cancelStub.calledOnce); +// }); +// it('will call onfinish function if passed', () => { +// const onFinishStub = stub(); +// animate.controls = { +// onFinish: onFinishStub +// }; +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(onFinishStub.calledOnce); +// }); +// it('will call oncancel function if passed', () => { +// const onCancelStub = stub(); +// animate.controls = { +// onCancel: onCancelStub +// }; +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(onCancelStub.calledOnce); +// }); +// it('can return a function instead of properties object', () => { +// const animateReturn = { +// id: 'animation', +// effects, +// controls, +// timing +// }; +// animate = () => animateReturn; + +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(keyframeCtorStub.calledOnce); +// assert.isTrue(keyframeCtorStub.firstCall.calledWithMatch( +// metaNode, +// [ +// { height: '0px' }, +// { height: '10px' } +// ], +// {} +// )); +// }); +// it('does not create animation if function does not return properties', () => { +// animate = () => undefined; + +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(keyframeCtorStub.notCalled); +// }); +// it('can have multiple animations on a single node', () => { +// animate = [{ +// id: 'animation1', +// effects, +// controls, +// timing +// }, +// { +// id: 'animation2', +// effects, +// controls, +// timing +// }]; +// const widget = new TestWidget(); +// const meta = widget.getMeta(); +// stub(meta, 'getNode').returns(metaNode); + +// widget.__render__(); +// assert.isTrue(keyframeCtorStub.calledTwice); +// }); +// }); +// }); diff --git a/tests/unit/mixins/Animated.ts b/tests/unit/mixins/Animated.ts deleted file mode 100644 index 0bd6c047..00000000 --- a/tests/unit/mixins/Animated.ts +++ /dev/null @@ -1,392 +0,0 @@ -import global from '@dojo/shim/global'; -const { assert } = intern.getPlugin('chai'); -const { beforeEach, before, describe, it} = intern.getInterface('bdd'); -import { AnimatedMixin, AnimationPlayer } from '../../../src/mixins/Animated'; -import { HNode, AnimationControls, AnimationTimingProperties } from '../../../src/interfaces'; -import { WidgetBase } from '../../../src/WidgetBase'; -import { v } from '../../../src/d'; -import { spy, stub } from 'sinon'; - -describe('animated', () => { - - let effects: any; - let controls: AnimationControls; - let timing: AnimationTimingProperties; - let animate: any; - - class TestWidget extends AnimatedMixin(WidgetBase) { - render() { - return v('div', {}, [ - v('div', { - key: 'animated', - animate - }), - v('div', { - key: 'nonAnimated' - }) - ]); - } - - callInvalidate() { - this.invalidate(); - } - - getMeta() { - return this.meta(AnimationPlayer); - } - } - - const keyframeCtorStub = stub(); - const animationCtorStub = stub(); - const pauseStub = stub(); - const playStub = stub(); - const reverseStub = stub(); - const cancelStub = stub(); - const finishStub = stub(); - const startStub = stub(); - const currentStub = stub(); - const playbackRateStub = stub(); - let metaNode: HTMLElement; - - beforeEach(() => { - effects = [ - { height: '0px' }, - { height: '10px' } - ]; - controls = {}; - timing = {}; - animate = { - id: 'animation', - effects - }; - }); - - describe('mixin', () => { - it('adds an animation player for each node with animations', () => { - const widget = new TestWidget(); - const meta = widget.getMeta(); - - const addSpy = spy(meta, 'add'); - - widget.__render__(); - assert.isTrue(addSpy.calledOnce); - }); - it('clears animations after new animations have been added', () => { - const widget = new TestWidget(); - const meta = widget.getMeta(); - - const addSpy = spy(meta, 'add'); - const clearSpy = spy(meta, 'clearAnimations'); - - widget.__render__(); - assert.isTrue(addSpy.calledOnce); - assert.isTrue(clearSpy.calledOnce); - assert.isTrue(clearSpy.calledAfter(addSpy)); - }); - it('only calls add on nodes with key and animate properties', () => { - const widget = new TestWidget(); - const meta = widget.getMeta(); - - const addSpy = spy(meta, 'add'); - - const renderedWidget = widget.__render__() as HNode; - - assert.equal(renderedWidget.children!.length, 2); - assert.isTrue(addSpy.calledOnce); - }); - }); - - describe('player', () => { - before(() => { - class KeyframeEffectMock { - constructor(...args: any[]) { - keyframeCtorStub(...args); - } - } - class AnimationMock { - constructor(...args: any[]) { - animationCtorStub(...args); - } - pause() { - pauseStub(); - } - play() { - playStub(); - } - reverse() { - reverseStub(); - } - cancel() { - cancelStub(); - } - finish() { - finishStub(); - } - startTime(time: number) { - startStub(time); - } - currentTime(time: number) { - currentStub(time); - } - set onfinish(onFinish: () => {}) { - onFinish(); - } - set oncancel(onCancel: () => {}) { - onCancel(); - } - set playbackRate(rate: number) { - playbackRateStub(rate); - } - } - global.KeyframeEffect = KeyframeEffectMock; - global.Animation = AnimationMock; - }); - - beforeEach(() => { - keyframeCtorStub.reset(); - animationCtorStub.reset(); - pauseStub.reset(); - playStub.reset(); - reverseStub.reset(); - cancelStub.reset(); - finishStub.reset(); - startStub.reset(); - currentStub.reset(); - playbackRateStub.reset(); - metaNode = document.createElement('div'); - }); - - it('creates new KeyframeEffect and Animation for each animated node', () => { - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(keyframeCtorStub.calledOnce); - assert.isTrue(animationCtorStub.calledOnce); - }); - it('reuses previous KeyframeEffect and Player when animation is still valid', () => { - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - widget.callInvalidate(); - widget.__render__(); - assert.isTrue(keyframeCtorStub.calledOnce); - assert.isTrue(animationCtorStub.calledOnce); - }); - it('passed timing and node info to keyframe effect', () => { - animate.timing = { - duration: 2 - }; - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(keyframeCtorStub.calledOnce); - assert.isTrue(keyframeCtorStub.firstCall.calledWithMatch( - metaNode, - [ - { height: '0px' }, - { height: '10px' } - ], - { - duration: 2 - } - )); - }); - it('starts animations paused', () => { - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(pauseStub.calledOnce); - assert.isTrue(playStub.notCalled); - }); - it('plays when play set to true', () => { - animate.controls = { - play: true - }; - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(playStub.calledOnce); - assert.isTrue(pauseStub.notCalled); - }); - it('reverses when reverse set to true', () => { - animate.controls = { - reverse: true - }; - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(reverseStub.calledOnce); - }); - it('cancels when cancel set to true', () => { - animate.controls = { - cancel: true - }; - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(cancelStub.calledOnce); - }); - it('finishes when finish set to true', () => { - animate.controls = { - finish: true - }; - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(finishStub.calledOnce); - }); - it('sets playback rate when passed', () => { - animate.controls = { - playbackRate: 2 - }; - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(playbackRateStub.calledOnce); - }); - it('can set start time', () => { - animate.controls = { - startTime: 2 - }; - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(startStub.calledOnce); - assert.isTrue(startStub.firstCall.calledWith(2)); - }); - it('can set current time', () => { - animate.controls = { - currentTime: 2 - }; - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(currentStub.calledOnce); - assert.isTrue(currentStub.firstCall.calledWith(2)); - }); - it('will execute effects function if one is passed', () => { - const fx = stub().returns([]); - animate.effects = fx; - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(fx.calledOnce); - }); - it('clears down used animations on next render if theyve been removed', () => { - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(cancelStub.notCalled); - - widget.callInvalidate(); - animate = undefined; - - widget.__render__(); - assert.isTrue(cancelStub.calledOnce); - }); - it('will call onfinish function if passed', () => { - const onFinishStub = stub(); - animate.controls = { - onFinish: onFinishStub - }; - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(onFinishStub.calledOnce); - }); - it('will call oncancel function if passed', () => { - const onCancelStub = stub(); - animate.controls = { - onCancel: onCancelStub - }; - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(onCancelStub.calledOnce); - }); - it('can return a function instead of properties object', () => { - const animateReturn = { - id: 'animation', - effects, - controls, - timing - }; - animate = () => animateReturn; - - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(keyframeCtorStub.calledOnce); - assert.isTrue(keyframeCtorStub.firstCall.calledWithMatch( - metaNode, - [ - { height: '0px' }, - { height: '10px' } - ], - {} - )); - }); - it('does not create animation if function does not return properties', () => { - animate = () => undefined; - - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(keyframeCtorStub.notCalled); - }); - it('can have multiple animations on a single node', () => { - animate = [{ - id: 'animation1', - effects, - controls, - timing - }, - { - id: 'animation2', - effects, - controls, - timing - }]; - const widget = new TestWidget(); - const meta = widget.getMeta(); - stub(meta, 'getNode').returns(metaNode); - - widget.__render__(); - assert.isTrue(keyframeCtorStub.calledTwice); - }); - }); -}); diff --git a/tests/unit/mixins/all.ts b/tests/unit/mixins/all.ts index 97972cb3..fd106fd3 100644 --- a/tests/unit/mixins/all.ts +++ b/tests/unit/mixins/all.ts @@ -1,4 +1,3 @@ import './Themed'; -import './Animated'; import './Projector'; import './I18n'; diff --git a/tsconfig.json b/tsconfig.json index 56175516..f652d78c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,7 +24,7 @@ "sourceMap": true, "strictNullChecks": true, "target": "es5", - "types": [ "intern" ] + "types": [ "intern", "web-animations-js" ] }, "include": [ "./src/**/*.ts", From 24041c85452865a77dd20e16163472ee5b94e732 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 3 Nov 2017 15:05:36 +0000 Subject: [PATCH 21/33] reinstated tests --- src/WidgetBase.ts | 16 +- src/interfaces.d.ts | 1 + src/meta/Base.ts | 5 +- src/meta/WebAnimation.ts | 22 +- tests/unit/meta/Dimensions.ts | 15 +- tests/unit/meta/Intersection.ts | 27 ++- tests/unit/meta/WebAnimation.ts | 379 ++++++++++++++++++++++++++++++ tests/unit/meta/WebAnimations.ts | 392 ------------------------------- tests/unit/meta/all.ts | 1 + tests/unit/meta/meta.ts | 18 +- 10 files changed, 437 insertions(+), 439 deletions(-) create mode 100644 tests/unit/meta/WebAnimation.ts delete mode 100644 tests/unit/meta/WebAnimations.ts diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index 7f8d270b..b1d8a670 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -106,7 +106,7 @@ export class WidgetBase

extends E private _renderState: WidgetRenderState = WidgetRenderState.IDLE; - private _metaMap = new WeakMap, WidgetMetaBase>(); + private _metaMap = new Map, WidgetMetaBase>(); private _boundRenderFunc: Render; @@ -114,8 +114,6 @@ export class WidgetBase

extends E private _nodeHandler: NodeHandler; - private _metaAfterRenders: (() => void)[] = []; - /** * @constructor */ @@ -157,9 +155,9 @@ export class WidgetBase

extends E if (!cached) { cached = new MetaType({ invalidate: this._boundInvalidate, - nodeHandler: this._nodeHandler + nodeHandler: this._nodeHandler, + bind: this }); - this._metaAfterRenders.push(cached.afterRender.bind(cached)); this._metaMap.set(MetaType, cached); this.own(cached); } @@ -477,9 +475,11 @@ export class WidgetBase

extends E return afterRenderFunction.call(this, dNode); }, dNode); } - if (this._metaAfterRenders.length) { - this._metaAfterRenders.forEach(afterRender => afterRender()); - } + + this._metaMap.forEach(meta => { + meta.afterRender(); + }); + return dNode; } } diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index faab3a8b..9e9e20de 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -456,6 +456,7 @@ export interface NodeHandlerInterface extends Evented { export interface WidgetMetaProperties { invalidate: () => void; nodeHandler: NodeHandlerInterface; + bind: any; } export interface Render { diff --git a/src/meta/Base.ts b/src/meta/Base.ts index 9d7cb46f..aacc78a3 100644 --- a/src/meta/Base.ts +++ b/src/meta/Base.ts @@ -8,11 +8,14 @@ export class Base extends Destroyable implements WidgetMetaBase { private _requestedNodeKeys = new Set(); + protected _bind: any; + constructor(properties: WidgetMetaProperties) { super(); this._invalidate = properties.invalidate; this.nodeHandler = properties.nodeHandler; + this._bind = properties.bind; } public has(key: string | number): boolean { @@ -42,7 +45,7 @@ export class Base extends Destroyable implements WidgetMetaBase { } public afterRender(): void { - // No nothing by default + // Do nothing by default. } } diff --git a/src/meta/WebAnimation.ts b/src/meta/WebAnimation.ts index 36471035..bae0f4f9 100644 --- a/src/meta/WebAnimation.ts +++ b/src/meta/WebAnimation.ts @@ -69,29 +69,15 @@ export class WebAnimations extends Base { } if (onFinish) { - player.onfinish = onFinish; + player.onfinish = onFinish.bind(this._bind); } if (onCancel) { - player.oncancel = onCancel; + player.oncancel = onCancel.bind(this._bind); } } - // private _bindControlCallbacks(controls: AnimationControls, bindScope: any): AnimationControls { - - // const { - // onFinish, - // onCancel - // } = controls; - - // return { - // ...controls, - // onFinish: onFinish ? onFinish.bind(bindScope) : null, - // onCancel: onCancel ? onCancel.bind(bindScope) : null - // }; - // } - - add(key: string, animateProperties: AnimationProperties | AnimationProperties[], bindScope: any) { + add(key: string, animateProperties: AnimationProperties | AnimationProperties[]) { const node = this.getNode(key); if (node) { @@ -114,7 +100,7 @@ export class WebAnimations extends Base { const { player } = this._animationMap.get(id); const { controls = {} } = properties; - this._updatePlayer(player, controls); // this._bindControlCallbacks(controls, bindScope)); + this._updatePlayer(player, controls); this._animationMap.set(id, { player, diff --git a/tests/unit/meta/Dimensions.ts b/tests/unit/meta/Dimensions.ts index 4d824f58..97c5dc48 100644 --- a/tests/unit/meta/Dimensions.ts +++ b/tests/unit/meta/Dimensions.ts @@ -54,7 +54,8 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: this }); assert.deepEqual(dimensions.get('foo'), defaultDimensions); @@ -64,7 +65,8 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: this }); assert.deepEqual(dimensions.get(1234), defaultDimensions); @@ -75,7 +77,8 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: this }); dimensions.get('foo'); @@ -89,7 +92,8 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: invalidateStub, - nodeHandler + nodeHandler, + bind: this }); dimensions.get('foo'); @@ -131,7 +135,8 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: this }); assert.deepEqual(dimensions.get('foo'), { diff --git a/tests/unit/meta/Intersection.ts b/tests/unit/meta/Intersection.ts index 0dc855ed..cc9fe0cd 100644 --- a/tests/unit/meta/Intersection.ts +++ b/tests/unit/meta/Intersection.ts @@ -33,7 +33,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: this }); const hasIntersectionInfo = intersection.has('root'); @@ -44,7 +45,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: this }); const hasIntersectionInfo = intersection.has('root', { root: 'root' }); @@ -55,7 +57,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: this }); const element = document.createElement('div'); nodeHandler.add(element, 'root'); @@ -80,7 +83,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: this }); intersection.get('root'); @@ -93,7 +97,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: this }); intersection.get(1234); @@ -107,7 +112,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: invalidateStub, - nodeHandler + nodeHandler, + bind: this }); intersection.get('root'); @@ -128,7 +134,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: invalidateStub, - nodeHandler + nodeHandler, + bind: this }); intersection.get('root'); @@ -174,7 +181,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: invalidateStub, - nodeHandler + nodeHandler, + bind: this }); intersection.get('foo', { root: 'root' }); @@ -213,7 +221,8 @@ registerSuite('meta - Intersection', { const nodeHandler = new NodeHandler(); const intersection = new Intersection({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: this }); const root = document.createElement('div'); diff --git a/tests/unit/meta/WebAnimation.ts b/tests/unit/meta/WebAnimation.ts new file mode 100644 index 00000000..66967dc4 --- /dev/null +++ b/tests/unit/meta/WebAnimation.ts @@ -0,0 +1,379 @@ +import global from '@dojo/shim/global'; +const { assert } = intern.getPlugin('chai'); +const { beforeEach, before, describe, it} = intern.getInterface('bdd'); +import WebAnimation from '../../../src/meta/WebAnimation'; +import { AnimationControls, AnimationTimingProperties } from '../../../src/interfaces'; +import { WidgetBase } from '../../../src/WidgetBase'; +import { v } from '../../../src/d'; +import { spy, stub } from 'sinon'; + +describe('WebAnimation', () => { + + let effects: any; + let controls: AnimationControls; + let timing: AnimationTimingProperties; + let animate: any; + + class TestWidget extends WidgetBase { + render() { + this.meta(WebAnimation).add('animated', animate); + + return v('div', {}, [ + v('div', { + key: 'animated' + }) + ]); + } + + callInvalidate() { + this.invalidate(); + } + + getMeta() { + return this.meta(WebAnimation); + } + } + + const keyframeCtorStub = stub(); + const animationCtorStub = stub(); + const pauseStub = stub(); + const playStub = stub(); + const reverseStub = stub(); + const cancelStub = stub(); + const finishStub = stub(); + const startStub = stub(); + const currentStub = stub(); + const playbackRateStub = stub(); + let metaNode: HTMLElement; + + beforeEach(() => { + effects = [ + { height: '0px' }, + { height: '10px' } + ]; + controls = {}; + timing = {}; + animate = { + id: 'animation', + effects + }; + }); + + describe('integration', () => { + it('creates an animation player for each node with animations', () => { + const widget = new TestWidget(); + const meta = widget.getMeta(); + + const addSpy = spy(meta, 'add'); + + widget.__render__(); + assert.isTrue(addSpy.calledOnce); + }); + it('clears animations after new animations have been added', () => { + const widget = new TestWidget(); + const meta = widget.getMeta(); + + const addSpy = spy(meta, 'add'); + const clearSpy = spy(meta, 'afterRender'); + + widget.__render__(); + assert.isTrue(addSpy.calledOnce); + assert.isTrue(clearSpy.calledOnce); + assert.isTrue(clearSpy.calledAfter(addSpy)); + }); + }); + + describe('player', () => { + before(() => { + class KeyframeEffectMock { + constructor(...args: any[]) { + keyframeCtorStub(...args); + } + } + class AnimationMock { + constructor(...args: any[]) { + animationCtorStub(...args); + } + pause() { + pauseStub(); + } + play() { + playStub(); + } + reverse() { + reverseStub(); + } + cancel() { + cancelStub(); + } + finish() { + finishStub(); + } + startTime(time: number) { + startStub(time); + } + currentTime(time: number) { + currentStub(time); + } + set onfinish(onFinish: () => {}) { + onFinish(); + } + set oncancel(onCancel: () => {}) { + onCancel(); + } + set playbackRate(rate: number) { + playbackRateStub(rate); + } + } + global.KeyframeEffect = KeyframeEffectMock; + global.Animation = AnimationMock; + }); + + beforeEach(() => { + keyframeCtorStub.reset(); + animationCtorStub.reset(); + pauseStub.reset(); + playStub.reset(); + reverseStub.reset(); + cancelStub.reset(); + finishStub.reset(); + startStub.reset(); + currentStub.reset(); + playbackRateStub.reset(); + metaNode = document.createElement('div'); + }); + + it('creates new KeyframeEffect and Animation for each WebAnimation node', () => { + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(keyframeCtorStub.calledOnce); + assert.isTrue(animationCtorStub.calledOnce); + }); + it('reuses previous KeyframeEffect and Player when animation is still valid', () => { + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + widget.callInvalidate(); + widget.__render__(); + assert.isTrue(keyframeCtorStub.calledOnce); + assert.isTrue(animationCtorStub.calledOnce); + }); + it('passed timing and node info to keyframe effect', () => { + animate.timing = { + duration: 2 + }; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(keyframeCtorStub.calledOnce); + assert.isTrue(keyframeCtorStub.firstCall.calledWithMatch( + metaNode, + [ + { height: '0px' }, + { height: '10px' } + ], + { + duration: 2 + } + )); + }); + it('starts animations paused', () => { + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(pauseStub.calledOnce); + assert.isTrue(playStub.notCalled); + }); + it('plays when play set to true', () => { + animate.controls = { + play: true + }; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(playStub.calledOnce); + assert.isTrue(pauseStub.notCalled); + }); + it('reverses when reverse set to true', () => { + animate.controls = { + reverse: true + }; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(reverseStub.calledOnce); + }); + it('cancels when cancel set to true', () => { + animate.controls = { + cancel: true + }; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(cancelStub.calledOnce); + }); + it('finishes when finish set to true', () => { + animate.controls = { + finish: true + }; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(finishStub.calledOnce); + }); + it('sets playback rate when passed', () => { + animate.controls = { + playbackRate: 2 + }; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(playbackRateStub.calledOnce); + }); + it('can set start time', () => { + animate.controls = { + startTime: 2 + }; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(startStub.calledOnce); + assert.isTrue(startStub.firstCall.calledWith(2)); + }); + it('can set current time', () => { + animate.controls = { + currentTime: 2 + }; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(currentStub.calledOnce); + assert.isTrue(currentStub.firstCall.calledWith(2)); + }); + it('will execute effects function if one is passed', () => { + const fx = stub().returns([]); + animate.effects = fx; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(fx.calledOnce); + }); + it('clears down used animations on next render if theyve been removed', () => { + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(cancelStub.notCalled); + + widget.callInvalidate(); + animate = undefined; + + widget.__render__(); + assert.isTrue(cancelStub.calledOnce); + }); + it('will call onfinish function if passed', () => { + const onFinishStub = stub(); + animate.controls = { + onFinish: onFinishStub + }; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(onFinishStub.calledOnce); + }); + it('will call oncancel function if passed', () => { + const onCancelStub = stub(); + animate.controls = { + onCancel: onCancelStub + }; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(onCancelStub.calledOnce); + }); + it('can return a function instead of properties object', () => { + const animateReturn = { + id: 'animation', + effects, + controls, + timing + }; + animate = () => animateReturn; + + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(keyframeCtorStub.calledOnce); + assert.isTrue(keyframeCtorStub.firstCall.calledWithMatch( + metaNode, + [ + { height: '0px' }, + { height: '10px' } + ], + {} + )); + }); + it('does not create animation if function does not return properties', () => { + animate = () => undefined; + + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(keyframeCtorStub.notCalled); + }); + it('can have multiple animations on a single node', () => { + animate = [{ + id: 'animation1', + effects, + controls, + timing + }, + { + id: 'animation2', + effects, + controls, + timing + }]; + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.__render__(); + assert.isTrue(keyframeCtorStub.calledTwice); + }); + }); +}); diff --git a/tests/unit/meta/WebAnimations.ts b/tests/unit/meta/WebAnimations.ts deleted file mode 100644 index d499cc32..00000000 --- a/tests/unit/meta/WebAnimations.ts +++ /dev/null @@ -1,392 +0,0 @@ -// import global from '@dojo/shim/global'; -// const { assert } = intern.getPlugin('chai'); -// const { beforeEach, before, describe, it} = intern.getInterface('bdd'); -// import { AnimatedMixin, AnimationPlayer } from '../../../src/mixins/Animated'; -// import { HNode, AnimationControls, AnimationTimingProperties } from '../../../src/interfaces'; -// import { WidgetBase } from '../../../src/WidgetBase'; -// import { v } from '../../../src/d'; -// import { spy, stub } from 'sinon'; - -// describe('animated', () => { - -// let effects: any; -// let controls: AnimationControls; -// let timing: AnimationTimingProperties; -// let animate: any; - -// class TestWidget extends AnimatedMixin(WidgetBase) { -// render() { -// return v('div', {}, [ -// v('div', { -// key: 'animated', -// animate -// }), -// v('div', { -// key: 'nonAnimated' -// }) -// ]); -// } - -// callInvalidate() { -// this.invalidate(); -// } - -// getMeta() { -// return this.meta(AnimationPlayer); -// } -// } - -// const keyframeCtorStub = stub(); -// const animationCtorStub = stub(); -// const pauseStub = stub(); -// const playStub = stub(); -// const reverseStub = stub(); -// const cancelStub = stub(); -// const finishStub = stub(); -// const startStub = stub(); -// const currentStub = stub(); -// const playbackRateStub = stub(); -// let metaNode: HTMLElement; - -// beforeEach(() => { -// effects = [ -// { height: '0px' }, -// { height: '10px' } -// ]; -// controls = {}; -// timing = {}; -// animate = { -// id: 'animation', -// effects -// }; -// }); - -// describe('mixin', () => { -// it('adds an animation player for each node with animations', () => { -// const widget = new TestWidget(); -// const meta = widget.getMeta(); - -// const addSpy = spy(meta, 'add'); - -// widget.__render__(); -// assert.isTrue(addSpy.calledOnce); -// }); -// it('clears animations after new animations have been added', () => { -// const widget = new TestWidget(); -// const meta = widget.getMeta(); - -// const addSpy = spy(meta, 'add'); -// const clearSpy = spy(meta, 'clearAnimations'); - -// widget.__render__(); -// assert.isTrue(addSpy.calledOnce); -// assert.isTrue(clearSpy.calledOnce); -// assert.isTrue(clearSpy.calledAfter(addSpy)); -// }); -// it('only calls add on nodes with key and animate properties', () => { -// const widget = new TestWidget(); -// const meta = widget.getMeta(); - -// const addSpy = spy(meta, 'add'); - -// const renderedWidget = widget.__render__() as HNode; - -// assert.equal(renderedWidget.children!.length, 2); -// assert.isTrue(addSpy.calledOnce); -// }); -// }); - -// describe('player', () => { -// before(() => { -// class KeyframeEffectMock { -// constructor(...args: any[]) { -// keyframeCtorStub(...args); -// } -// } -// class AnimationMock { -// constructor(...args: any[]) { -// animationCtorStub(...args); -// } -// pause() { -// pauseStub(); -// } -// play() { -// playStub(); -// } -// reverse() { -// reverseStub(); -// } -// cancel() { -// cancelStub(); -// } -// finish() { -// finishStub(); -// } -// startTime(time: number) { -// startStub(time); -// } -// currentTime(time: number) { -// currentStub(time); -// } -// set onfinish(onFinish: () => {}) { -// onFinish(); -// } -// set oncancel(onCancel: () => {}) { -// onCancel(); -// } -// set playbackRate(rate: number) { -// playbackRateStub(rate); -// } -// } -// global.KeyframeEffect = KeyframeEffectMock; -// global.Animation = AnimationMock; -// }); - -// beforeEach(() => { -// keyframeCtorStub.reset(); -// animationCtorStub.reset(); -// pauseStub.reset(); -// playStub.reset(); -// reverseStub.reset(); -// cancelStub.reset(); -// finishStub.reset(); -// startStub.reset(); -// currentStub.reset(); -// playbackRateStub.reset(); -// metaNode = document.createElement('div'); -// }); - -// it('creates new KeyframeEffect and Animation for each animated node', () => { -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(keyframeCtorStub.calledOnce); -// assert.isTrue(animationCtorStub.calledOnce); -// }); -// it('reuses previous KeyframeEffect and Player when animation is still valid', () => { -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// widget.callInvalidate(); -// widget.__render__(); -// assert.isTrue(keyframeCtorStub.calledOnce); -// assert.isTrue(animationCtorStub.calledOnce); -// }); -// it('passed timing and node info to keyframe effect', () => { -// animate.timing = { -// duration: 2 -// }; -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(keyframeCtorStub.calledOnce); -// assert.isTrue(keyframeCtorStub.firstCall.calledWithMatch( -// metaNode, -// [ -// { height: '0px' }, -// { height: '10px' } -// ], -// { -// duration: 2 -// } -// )); -// }); -// it('starts animations paused', () => { -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(pauseStub.calledOnce); -// assert.isTrue(playStub.notCalled); -// }); -// it('plays when play set to true', () => { -// animate.controls = { -// play: true -// }; -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(playStub.calledOnce); -// assert.isTrue(pauseStub.notCalled); -// }); -// it('reverses when reverse set to true', () => { -// animate.controls = { -// reverse: true -// }; -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(reverseStub.calledOnce); -// }); -// it('cancels when cancel set to true', () => { -// animate.controls = { -// cancel: true -// }; -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(cancelStub.calledOnce); -// }); -// it('finishes when finish set to true', () => { -// animate.controls = { -// finish: true -// }; -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(finishStub.calledOnce); -// }); -// it('sets playback rate when passed', () => { -// animate.controls = { -// playbackRate: 2 -// }; -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(playbackRateStub.calledOnce); -// }); -// it('can set start time', () => { -// animate.controls = { -// startTime: 2 -// }; -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(startStub.calledOnce); -// assert.isTrue(startStub.firstCall.calledWith(2)); -// }); -// it('can set current time', () => { -// animate.controls = { -// currentTime: 2 -// }; -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(currentStub.calledOnce); -// assert.isTrue(currentStub.firstCall.calledWith(2)); -// }); -// it('will execute effects function if one is passed', () => { -// const fx = stub().returns([]); -// animate.effects = fx; -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(fx.calledOnce); -// }); -// it('clears down used animations on next render if theyve been removed', () => { -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(cancelStub.notCalled); - -// widget.callInvalidate(); -// animate = undefined; - -// widget.__render__(); -// assert.isTrue(cancelStub.calledOnce); -// }); -// it('will call onfinish function if passed', () => { -// const onFinishStub = stub(); -// animate.controls = { -// onFinish: onFinishStub -// }; -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(onFinishStub.calledOnce); -// }); -// it('will call oncancel function if passed', () => { -// const onCancelStub = stub(); -// animate.controls = { -// onCancel: onCancelStub -// }; -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(onCancelStub.calledOnce); -// }); -// it('can return a function instead of properties object', () => { -// const animateReturn = { -// id: 'animation', -// effects, -// controls, -// timing -// }; -// animate = () => animateReturn; - -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(keyframeCtorStub.calledOnce); -// assert.isTrue(keyframeCtorStub.firstCall.calledWithMatch( -// metaNode, -// [ -// { height: '0px' }, -// { height: '10px' } -// ], -// {} -// )); -// }); -// it('does not create animation if function does not return properties', () => { -// animate = () => undefined; - -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(keyframeCtorStub.notCalled); -// }); -// it('can have multiple animations on a single node', () => { -// animate = [{ -// id: 'animation1', -// effects, -// controls, -// timing -// }, -// { -// id: 'animation2', -// effects, -// controls, -// timing -// }]; -// const widget = new TestWidget(); -// const meta = widget.getMeta(); -// stub(meta, 'getNode').returns(metaNode); - -// widget.__render__(); -// assert.isTrue(keyframeCtorStub.calledTwice); -// }); -// }); -// }); diff --git a/tests/unit/meta/all.ts b/tests/unit/meta/all.ts index 9b81a602..45146af2 100644 --- a/tests/unit/meta/all.ts +++ b/tests/unit/meta/all.ts @@ -3,3 +3,4 @@ import './Dimensions'; import './Drag'; import './Intersection'; import './Matches'; +import './WebAnimation'; diff --git a/tests/unit/meta/meta.ts b/tests/unit/meta/meta.ts index febf14ca..9691f58f 100644 --- a/tests/unit/meta/meta.ts +++ b/tests/unit/meta/meta.ts @@ -25,7 +25,8 @@ registerSuite('meta base', { nodeHandler.add(element, 'foo'); const meta = new MetaBase({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: this }); assert.isTrue(meta.has('foo')); @@ -45,7 +46,8 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler + nodeHandler, + bind: this }); const node = meta.callGetNode('foo'); @@ -64,7 +66,8 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler + nodeHandler, + bind: this }); meta.callGetNode('foo'); @@ -84,7 +87,8 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler + nodeHandler, + bind: this }); meta.callGetNode('foo'); @@ -116,7 +120,8 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler + nodeHandler, + bind: this }); meta.callGetNode('foo'); @@ -139,7 +144,8 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler + nodeHandler, + bind: this }); meta.callInvalidate(); From 9197e8685c15d5ce7734141ddbd75bbed741339d Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 3 Nov 2017 15:26:46 +0000 Subject: [PATCH 22/33] fix readme --- README.md | 99 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 316f3903..66dd8d2f 100644 --- a/README.md +++ b/README.md @@ -305,28 +305,31 @@ Examples of Dojo 2 mixins can be seen with `ThemedMixin` and `I18nMixin` that ar ### Animation -Dojo 2 widget-core provides a `Animated` mixin to decorate a widget with the functionality to apply web animations to the nodes that it creates. +Dojo 2 widget-core provides a `WebAnimation` meta to apply web animations to VNodes. -To specify the web animations pass an `animate` property to the node you wish to animate. This can be a single animation or an array or animations. +To specify the web animations pass an `AnimationProperties` object to the `WebAnimation` meta along with the key of the node you wish to animate. This can be a single animation or an array or animations. #### Basic Example ```ts -export default class AnimatedWidget extends AnimatedMixin(WidgetBase) { +export default class AnimatedWidget extends WidgetBase { protected render() { - return v('div', { - key: 'root', - animate: { - id: 'rootAnimation', - effects: [ - { height: '10px' }, - { height: '100px' } - ], - controls: { - play: true - } + const animate = { + id: 'rootAnimation', + effects: [ + { height: '10px' }, + { height: '100px' } + ], + controls: { + play: true } - }) + }; + + this.meta(WebAnimation).add('root', animate); + + return v('div', { + key: 'root' + }); } } ``` @@ -338,31 +341,34 @@ export default class AnimatedWidget extends AnimatedMixin(WidgetBase) { Animations can be changed on each widget render in a reactive pattern, for example changing the animation from `slideUp` to `slideDown` on a title pane depending of if the titlepane is open or not. ```ts -export default class AnimatedWidget extends AnimatedMixin(WidgetBase) { +export default class AnimatedWidget extends WidgetBase { private _open = false; protected render() { - return v('div', { - key: 'root', - animate: this._open ? { - id: 'upAnimation', - effects: [ - { height: '100px' }, - { height: '0px' } - ], - controls: { - play: true - } - } : { - id: 'downAnimation', - effects: [ - { height: '0px' }, - { height: '100px' } - ], - controls: { - play: true - } + const animate = this._open ? { + id: 'upAnimation', + effects: [ + { height: '100px' }, + { height: '0px' } + ], + controls: { + play: true + } + } : { + id: 'downAnimation', + effects: [ + { height: '0px' }, + { height: '100px' } + ], + controls: { + play: true } + }; + + this.meta(WebAnimation).add('root', animate); + + return v('div', { + key: 'root' }) } } @@ -373,7 +379,7 @@ export default class AnimatedWidget extends AnimatedMixin(WidgetBase) { an `effects` function can be passed to the animation and evaluated at render time. This allows you to create programatic effects such as those depending on measurements from the `Dimensions` `Meta`. ```ts -export default class AnimatedWidget extends AnimatedMixin(WidgetBase) { +export default class AnimatedWidget extends WidgetBase { private _getEffect() { const { scroll } = this.meta(Dimensions).get('content'); @@ -384,15 +390,18 @@ export default class AnimatedWidget extends AnimatedMixin(WidgetBase) { } protected render() { - return v('div', { - key: 'root', - animate: { - id: 'upAnimation', - effects: this._getEffect(), - controls: { - play: true - } + const animate = { + id: 'upAnimation', + effects: this._getEffect(), + controls: { + play: true } + }; + + this.meta(WebAnimation).add('root', animate); + + return v('div', { + key: 'root' }) } } From fdfea2878991ec07078cb698c2c1ac2cd1454a7c Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 3 Nov 2017 16:45:21 +0000 Subject: [PATCH 23/33] fix functional test json --- intern.json | 3 ++- package-lock.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/intern.json b/intern.json index 2c336e8e..6df2a782 100644 --- a/intern.json +++ b/intern.json @@ -24,7 +24,8 @@ { "name": "globalize", "location": "node_modules/globalize", "main": "dist/globalize" }, { "name": "pepjs", "location": "node_modules/pepjs/dist", "main": "pep" }, { "name": "intersection-observer", "location": "node_modules/intersection-observer", "main": "intersection-observer" }, - { "name": "sinon", "location": "node_modules/sinon/pkg", "main": "sinon" } + { "name": "sinon", "location": "node_modules/sinon/pkg", "main": "sinon" }, + { "name": "web-animations-js", "location": "node_modules/web-animations-js" } ], "map": { "globalize": { diff --git a/package-lock.json b/package-lock.json index 691b2f23..2f860597 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@dojo/widget-core", - "version": "0.1.3-alpha.1", + "version": "0.1.3-pre", "lockfileVersion": 1, "requires": true, "dependencies": { From eb06216a7457610dcff12da0be02a87bf33410e4 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 6 Nov 2017 11:48:10 +0000 Subject: [PATCH 24/33] rename add to animate --- src/meta/WebAnimation.ts | 2 +- tests/unit/meta/WebAnimation.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/meta/WebAnimation.ts b/src/meta/WebAnimation.ts index bae0f4f9..368cbe09 100644 --- a/src/meta/WebAnimation.ts +++ b/src/meta/WebAnimation.ts @@ -77,7 +77,7 @@ export class WebAnimations extends Base { } } - add(key: string, animateProperties: AnimationProperties | AnimationProperties[]) { + animate(key: string, animateProperties: AnimationProperties | AnimationProperties[]) { const node = this.getNode(key); if (node) { diff --git a/tests/unit/meta/WebAnimation.ts b/tests/unit/meta/WebAnimation.ts index 66967dc4..9503a35f 100644 --- a/tests/unit/meta/WebAnimation.ts +++ b/tests/unit/meta/WebAnimation.ts @@ -16,7 +16,7 @@ describe('WebAnimation', () => { class TestWidget extends WidgetBase { render() { - this.meta(WebAnimation).add('animated', animate); + this.meta(WebAnimation).animate('animated', animate); return v('div', {}, [ v('div', { @@ -64,7 +64,7 @@ describe('WebAnimation', () => { const widget = new TestWidget(); const meta = widget.getMeta(); - const addSpy = spy(meta, 'add'); + const addSpy = spy(meta, 'animate'); widget.__render__(); assert.isTrue(addSpy.calledOnce); @@ -73,7 +73,7 @@ describe('WebAnimation', () => { const widget = new TestWidget(); const meta = widget.getMeta(); - const addSpy = spy(meta, 'add'); + const addSpy = spy(meta, 'animate'); const clearSpy = spy(meta, 'afterRender'); widget.__render__(); From 13c51364686808305bdd67c9ecfc2a9530977506 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 6 Nov 2017 14:01:14 +0000 Subject: [PATCH 25/33] adding info test --- src/meta/WebAnimation.ts | 49 +++++++++++++++++++++++++++------ tests/unit/all.ts | 32 ++++++++++----------- tests/unit/meta/WebAnimation.ts | 9 ++++++ tests/unit/meta/all.ts | 10 +++---- 4 files changed, 71 insertions(+), 29 deletions(-) diff --git a/src/meta/WebAnimation.ts b/src/meta/WebAnimation.ts index 368cbe09..0edd7981 100644 --- a/src/meta/WebAnimation.ts +++ b/src/meta/WebAnimation.ts @@ -3,11 +3,23 @@ import { Base } from './Base'; import { AnimationControls, AnimationProperties } from '../interfaces'; import Map from '@dojo/shim/Map'; +export interface AnimationPlayer { + player: Animation; + used: boolean; +} + +export interface AnimationInfo { + currentTime: number; + playState: AnimationPlayState; + playbackRate: number; + startTime: number; +} + export class WebAnimations extends Base { - private _animationMap = new Map(); + private _animationMap = new Map(); - private _createPlayer(node: HTMLElement, properties: AnimationProperties) { + private _createPlayer(node: HTMLElement, properties: AnimationProperties): Animation { const { effects, timing = {} @@ -97,20 +109,41 @@ export class WebAnimations extends Base { }); } - const { player } = this._animationMap.get(id); + const animation = this._animationMap.get(id); const { controls = {} } = properties; - this._updatePlayer(player, controls); + if (animation) { + this._updatePlayer(animation.player, controls); - this._animationMap.set(id, { - player, - used: true - }); + this._animationMap.set(id, { + player: animation.player, + used: true + }); + } } }); } } + get(id: string): Readonly | undefined { + const animation = this._animationMap.get(id); + if (animation) { + const { + currentTime, + playState, + playbackRate, + startTime + } = animation.player; + + return { + currentTime, + playState, + playbackRate, + startTime + }; + } + } + afterRender() { super.afterRender(); diff --git a/tests/unit/all.ts b/tests/unit/all.ts index 97a14368..4ac597d4 100644 --- a/tests/unit/all.ts +++ b/tests/unit/all.ts @@ -1,17 +1,17 @@ -import './Container'; -import './WidgetBase'; -import './Registry'; -import './customElements'; -import './d'; -import './decorators/all'; -import './mixins/all'; -import './util/all'; -import './main'; -import './diff'; -import './RegistryHandler'; -import './Injector'; -import './tsx'; -import './tsxIntegration'; -import './NodeHandler'; +// import './Container'; +// import './WidgetBase'; +// import './Registry'; +// import './customElements'; +// import './d'; +// import './decorators/all'; +// import './mixins/all'; +// import './util/all'; +// import './main'; +// import './diff'; +// import './RegistryHandler'; +// import './Injector'; +// import './tsx'; +// import './tsxIntegration'; +// import './NodeHandler'; import './meta/all'; -import './vdom'; +// import './vdom'; diff --git a/tests/unit/meta/WebAnimation.ts b/tests/unit/meta/WebAnimation.ts index 9503a35f..eec24c57 100644 --- a/tests/unit/meta/WebAnimation.ts +++ b/tests/unit/meta/WebAnimation.ts @@ -375,5 +375,14 @@ describe('WebAnimation', () => { widget.__render__(); assert.isTrue(keyframeCtorStub.calledTwice); }); + it('returns animation info when get is called', () => { + const widget = new TestWidget(); + const meta = widget.getMeta(); + + // widget.render(); + + const info = meta.get('animation'); + console.dir(info); + }); }); }); diff --git a/tests/unit/meta/all.ts b/tests/unit/meta/all.ts index 45146af2..87535609 100644 --- a/tests/unit/meta/all.ts +++ b/tests/unit/meta/all.ts @@ -1,6 +1,6 @@ -import './meta'; -import './Dimensions'; -import './Drag'; -import './Intersection'; -import './Matches'; +// import './meta'; +// import './Dimensions'; +// import './Drag'; +// import './Intersection'; +// import './Matches'; import './WebAnimation'; From 47aac32cd462645fd7b918ff3776529a193401d6 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 6 Nov 2017 15:33:29 +0000 Subject: [PATCH 26/33] added tests for get info --- tests/unit/all.ts | 32 +++++++++---------- tests/unit/meta/WebAnimation.ts | 54 ++++++++++++++++++++++++++++----- tests/unit/meta/all.ts | 10 +++--- 3 files changed, 68 insertions(+), 28 deletions(-) diff --git a/tests/unit/all.ts b/tests/unit/all.ts index 4ac597d4..97a14368 100644 --- a/tests/unit/all.ts +++ b/tests/unit/all.ts @@ -1,17 +1,17 @@ -// import './Container'; -// import './WidgetBase'; -// import './Registry'; -// import './customElements'; -// import './d'; -// import './decorators/all'; -// import './mixins/all'; -// import './util/all'; -// import './main'; -// import './diff'; -// import './RegistryHandler'; -// import './Injector'; -// import './tsx'; -// import './tsxIntegration'; -// import './NodeHandler'; +import './Container'; +import './WidgetBase'; +import './Registry'; +import './customElements'; +import './d'; +import './decorators/all'; +import './mixins/all'; +import './util/all'; +import './main'; +import './diff'; +import './RegistryHandler'; +import './Injector'; +import './tsx'; +import './tsxIntegration'; +import './NodeHandler'; import './meta/all'; -// import './vdom'; +import './vdom'; diff --git a/tests/unit/meta/WebAnimation.ts b/tests/unit/meta/WebAnimation.ts index eec24c57..c02377cd 100644 --- a/tests/unit/meta/WebAnimation.ts +++ b/tests/unit/meta/WebAnimation.ts @@ -375,14 +375,54 @@ describe('WebAnimation', () => { widget.__render__(); assert.isTrue(keyframeCtorStub.calledTwice); }); - it('returns animation info when get is called', () => { - const widget = new TestWidget(); - const meta = widget.getMeta(); - // widget.render(); - - const info = meta.get('animation'); - console.dir(info); + describe('get info', () => { + beforeEach(() => { + global.Animation = class { + constructor() {} + play() {} + pause() {} + get startTime() { return 0; } + get currentTime() { return 500; } + get playState() { return 'running'; } + get playbackRate() { return 1; } + }; + }); + + it('returns undefined for non existant animation', () => { + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.render(); + + const info = meta.get('nonAnimation'); + + assert.isUndefined(info); + }); + + it('returns animation info when get is called', () => { + animate.duration = 1000; + animate.controls = { + play: true + }; + + const widget = new TestWidget(); + const meta = widget.getMeta(); + stub(meta, 'getNode').returns(metaNode); + + widget.render(); + + const info = meta.get('animation'); + + assert.deepEqual(info, { + startTime: 0, + currentTime: 500, + playState: 'running', + playbackRate: 1 + }); + }); }); + }); }); diff --git a/tests/unit/meta/all.ts b/tests/unit/meta/all.ts index 87535609..45146af2 100644 --- a/tests/unit/meta/all.ts +++ b/tests/unit/meta/all.ts @@ -1,6 +1,6 @@ -// import './meta'; -// import './Dimensions'; -// import './Drag'; -// import './Intersection'; -// import './Matches'; +import './meta'; +import './Dimensions'; +import './Drag'; +import './Intersection'; +import './Matches'; import './WebAnimation'; From e156912c119d8e6642600f26df00733a267d4c33 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 6 Nov 2017 15:38:32 +0000 Subject: [PATCH 27/33] change type --- src/meta/WebAnimation.ts | 6 +++--- tests/unit/meta/WebAnimation.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/meta/WebAnimation.ts b/src/meta/WebAnimation.ts index 0edd7981..3a25bfda 100644 --- a/src/meta/WebAnimation.ts +++ b/src/meta/WebAnimation.ts @@ -36,7 +36,7 @@ export class WebAnimations extends Base { return new Animation(keyframeEffect, (document as any).timeline); } - private _updatePlayer(player: any, controls: AnimationControls) { + private _updatePlayer(player: Animation, controls: AnimationControls) { const { play, reverse, @@ -66,11 +66,11 @@ export class WebAnimations extends Base { } if (startTime !== undefined) { - player.startTime(startTime); + player.startTime = startTime; } if (currentTime !== undefined) { - player.currentTime(currentTime); + player.currentTime = currentTime; } if (play) { diff --git a/tests/unit/meta/WebAnimation.ts b/tests/unit/meta/WebAnimation.ts index c02377cd..ca8edbfd 100644 --- a/tests/unit/meta/WebAnimation.ts +++ b/tests/unit/meta/WebAnimation.ts @@ -109,10 +109,10 @@ describe('WebAnimation', () => { finish() { finishStub(); } - startTime(time: number) { + set startTime(time: number) { startStub(time); } - currentTime(time: number) { + set currentTime(time: number) { currentStub(time); } set onfinish(onFinish: () => {}) { From 97d0e961b885aaaef0982fd1b6f5570cf5831413 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 6 Nov 2017 15:45:13 +0000 Subject: [PATCH 28/33] update readme, change interface --- README.md | 36 +++++++++++++++++++++++++++++++++--- src/interfaces.d.ts | 10 ++++++++++ src/meta/WebAnimation.ts | 9 +-------- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 66dd8d2f..adc734aa 100644 --- a/README.md +++ b/README.md @@ -325,7 +325,7 @@ export default class AnimatedWidget extends WidgetBase { } }; - this.meta(WebAnimation).add('root', animate); + this.meta(WebAnimation).animate('root', animate); return v('div', { key: 'root' @@ -365,7 +365,7 @@ export default class AnimatedWidget extends WidgetBase { } }; - this.meta(WebAnimation).add('root', animate); + this.meta(WebAnimation).animate('root', animate); return v('div', { key: 'root' @@ -398,7 +398,7 @@ export default class AnimatedWidget extends WidgetBase { } }; - this.meta(WebAnimation).add('root', animate); + this.meta(WebAnimation).animate('root', animate); return v('div', { key: 'root' @@ -407,6 +407,36 @@ export default class AnimatedWidget extends WidgetBase { } ``` +#### Get animation info + +The `WebAnimation` meta provides a `get` function that can be used to retrieve information about an animation via it's `id`. +This info contains the currentTime, playState, playbackRate and startTime of the animation. If no animation is found or the animation has been cleared this will return undefined. + +```ts +export default class AnimatedWidget extends WidgetBase { + protected render() { + const animate = { + id: 'rootAnimation', + effects: [ + { height: '10px' }, + { height: '100px' } + ], + controls: { + play: true + } + }; + + this.meta(WebAnimation).animate('root', animate); + + const info = this.meta(WebAnimation).get('rootAnimation'); + + return v('div', { + key: 'root' + }); + } +} +``` + ### Styling & Theming #### Overview diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index 9e9e20de..d1058926 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -126,6 +126,16 @@ export interface AnimationProperties { timing?: AnimationTimingProperties; } +/** + * Info returned by the `get` function on WebAnimation meta + */ +export interface AnimationInfo { + currentTime: number; + playState: 'idle' | 'pending' | 'running' | 'paused' | 'finished'; + playbackRate: number; + startTime: number; +} + export interface VirtualDomProperties { /** * The animation to perform when this node is added to an already existing parent. diff --git a/src/meta/WebAnimation.ts b/src/meta/WebAnimation.ts index 3a25bfda..249d8c8d 100644 --- a/src/meta/WebAnimation.ts +++ b/src/meta/WebAnimation.ts @@ -1,6 +1,6 @@ import 'web-animations-js/web-animations-next-lite.min'; import { Base } from './Base'; -import { AnimationControls, AnimationProperties } from '../interfaces'; +import { AnimationControls, AnimationProperties, AnimationInfo } from '../interfaces'; import Map from '@dojo/shim/Map'; export interface AnimationPlayer { @@ -8,13 +8,6 @@ export interface AnimationPlayer { used: boolean; } -export interface AnimationInfo { - currentTime: number; - playState: AnimationPlayState; - playbackRate: number; - startTime: number; -} - export class WebAnimations extends Base { private _animationMap = new Map(); From 57a721c056f7b1378666879aefce671a06818e3e Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 6 Nov 2017 16:05:36 +0000 Subject: [PATCH 29/33] remove super call --- src/meta/WebAnimation.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/meta/WebAnimation.ts b/src/meta/WebAnimation.ts index 249d8c8d..3739f3a0 100644 --- a/src/meta/WebAnimation.ts +++ b/src/meta/WebAnimation.ts @@ -2,6 +2,7 @@ import 'web-animations-js/web-animations-next-lite.min'; import { Base } from './Base'; import { AnimationControls, AnimationProperties, AnimationInfo } from '../interfaces'; import Map from '@dojo/shim/Map'; +import global from '@dojo/shim/global'; export interface AnimationPlayer { player: Animation; @@ -26,7 +27,7 @@ export class WebAnimations extends Base { timing ); - return new Animation(keyframeEffect, (document as any).timeline); + return new Animation(keyframeEffect, global.document.timeline); } private _updatePlayer(player: Animation, controls: AnimationControls) { @@ -138,8 +139,6 @@ export class WebAnimations extends Base { } afterRender() { - super.afterRender(); - this._animationMap.forEach((animation, key) => { if (!animation.used) { animation.player.cancel(); From 48bef93cc3f0236191956b137d9cf8e8ea2f8ffb Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Tue, 7 Nov 2017 12:00:47 +0000 Subject: [PATCH 30/33] updated readme and bind type --- README.md | 2 +- src/interfaces.d.ts | 2 +- src/meta/Base.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index adc734aa..52c9efb3 100644 --- a/README.md +++ b/README.md @@ -376,7 +376,7 @@ export default class AnimatedWidget extends WidgetBase { #### Passing an effects function -an `effects` function can be passed to the animation and evaluated at render time. This allows you to create programatic effects such as those depending on measurements from the `Dimensions` `Meta`. +An `effects` function can be passed to the animation and evaluated at render time. This allows you to create programatic effects such as those depending on measurements from the `Dimensions` `Meta`. ```ts export default class AnimatedWidget extends WidgetBase { diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index d1058926..8fb75504 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -466,7 +466,7 @@ export interface NodeHandlerInterface extends Evented { export interface WidgetMetaProperties { invalidate: () => void; nodeHandler: NodeHandlerInterface; - bind: any; + bind: WidgetBaseInterface; } export interface Render { diff --git a/src/meta/Base.ts b/src/meta/Base.ts index aacc78a3..f8fb3210 100644 --- a/src/meta/Base.ts +++ b/src/meta/Base.ts @@ -1,6 +1,6 @@ import { Destroyable } from '@dojo/core/Destroyable'; import Set from '@dojo/shim/Set'; -import { WidgetMetaBase, WidgetMetaProperties, NodeHandlerInterface } from '../interfaces'; +import { WidgetMetaBase, WidgetMetaProperties, NodeHandlerInterface, WidgetBaseInterface } from '../interfaces'; export class Base extends Destroyable implements WidgetMetaBase { private _invalidate: () => void; @@ -8,7 +8,7 @@ export class Base extends Destroyable implements WidgetMetaBase { private _requestedNodeKeys = new Set(); - protected _bind: any; + protected _bind: WidgetBaseInterface; constructor(properties: WidgetMetaProperties) { super(); From f28f82bd6e34222dfbba57a35814edb775144def Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Tue, 7 Nov 2017 12:02:26 +0000 Subject: [PATCH 31/33] updated package lock --- package-lock.json | 141 +++++++++++++++++++++++++--------------------- 1 file changed, 76 insertions(+), 65 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2f860597..b2c02e3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,18 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@dojo/core": { - "version": "2.0.0-beta2.4", - "resolved": "https://registry.npmjs.org/@dojo/core/-/core-2.0.0-beta2.4.tgz", - "integrity": "sha1-AjZimDJ2QxZrtUITk2hJW1CLzBQ=", - "dev": true - }, - "@dojo/has": { - "version": "2.0.0-beta2.3", - "resolved": "https://registry.npmjs.org/@dojo/has/-/has-2.0.0-beta2.3.tgz", - "integrity": "sha1-faojdQXj7KN2EG1zmMTH3tg2oOE=", - "dev": true - }, "@dojo/interfaces": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@dojo/interfaces/-/interfaces-0.1.0.tgz", @@ -28,78 +16,83 @@ "integrity": "sha512-3qoMKkewhIUqV99JzGiAkXdJuT3HUQkSviYut9828Cv4uKOqKqnb6znE8ofZhaAuv25ZU9anHTPiC8fepONfVQ==", "dev": true }, - "@dojo/shim": { - "version": "2.0.0-beta2.4", - "resolved": "https://registry.npmjs.org/@dojo/shim/-/shim-2.0.0-beta2.4.tgz", - "integrity": "sha1-TUjz2MMmxF5k3TRq+tAK61gmPZo=", - "dev": true - }, "@theintern/digdug": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@theintern/digdug/-/digdug-2.0.1.tgz", - "integrity": "sha512-MCF9jwQWAbBhy6NTQiaLeOrPOOtijYX9/LfyDyJJqnq6c0icBL4XfV58eudIXNiZtTYFiVwFh104dlCgL7+Auw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@theintern/digdug/-/digdug-2.0.0.tgz", + "integrity": "sha512-PDcflahjSPdnJnChtWhej3l5jknGDdDl15p1+7rNxjOpb2gpgnhoWzXCfkUFJvUL/q1DuAowRQNNh2hdUk5MGw==", "dev": true, "requires": { - "@dojo/core": "0.1.0", - "@dojo/has": "0.1.0", - "@dojo/interfaces": "0.1.0", - "@dojo/shim": "0.1.0", + "@dojo/core": "2.0.0-beta2.4", + "@dojo/has": "2.0.0-beta2.3", + "@dojo/interfaces": "2.0.0-beta2.4", + "@dojo/shim": "2.0.0-beta2.4", "decompress": "4.2.0", - "semver": "5.4.1", - "tslib": "1.8.0" + "semver": "5.4.1" }, "dependencies": { "@dojo/core": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@dojo/core/-/core-0.1.0.tgz", - "integrity": "sha512-boiwQHfV7idOZfZnDzgLrofS2LA7ELGKjd6tl0/hLBunJ3psozAd4CpNcT7XC00/OPYFIxVHFEpI+FZNlpUgfw==", + "version": "2.0.0-beta2.4", + "resolved": "https://registry.npmjs.org/@dojo/core/-/core-2.0.0-beta2.4.tgz", + "integrity": "sha1-AjZimDJ2QxZrtUITk2hJW1CLzBQ=", "dev": true }, "@dojo/has": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@dojo/has/-/has-0.1.0.tgz", - "integrity": "sha512-orYLbYVTcqNpZBmPNRlidUyCn/WuV4jV4JvTAy4Je/zQq9m9Nb8gK+8X7/iOUjSJbP1+vv1ld9Q3932IGC1IyA==", + "version": "2.0.0-beta2.3", + "resolved": "https://registry.npmjs.org/@dojo/has/-/has-2.0.0-beta2.3.tgz", + "integrity": "sha1-faojdQXj7KN2EG1zmMTH3tg2oOE=", + "dev": true + }, + "@dojo/interfaces": { + "version": "2.0.0-beta2.4", + "resolved": "https://registry.npmjs.org/@dojo/interfaces/-/interfaces-2.0.0-beta2.4.tgz", + "integrity": "sha1-OGXj9EQJMuCNJNJPgj5UFwaiKNM=", "dev": true }, "@dojo/shim": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@dojo/shim/-/shim-0.1.0.tgz", - "integrity": "sha512-008RP8DB175ib26dde7wQWFiYIbSACFaArLdLHYdY/cQLN9s3yVj2Gtp5C/9YoY3Ziy9wA241myOjy6QcVHcWw==", + "version": "2.0.0-beta2.4", + "resolved": "https://registry.npmjs.org/@dojo/shim/-/shim-2.0.0-beta2.4.tgz", + "integrity": "sha1-TUjz2MMmxF5k3TRq+tAK61gmPZo=", "dev": true } } }, "@theintern/leadfoot": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@theintern/leadfoot/-/leadfoot-2.0.1.tgz", - "integrity": "sha512-6aWQP7Qf5pvh8iEvDS+5gCbazqmb40PXADsGLBOZmKorC3WLELpP4Dw0GkeWKf4UYJ/4xKPwUX4gNTbYlF74kA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@theintern/leadfoot/-/leadfoot-2.0.0.tgz", + "integrity": "sha512-kcC/tpbdaO8DplWdZ1BeOXwII5HCwiVk3Dck0dnmm/vdqtzRE4yApY5um062PAJh8TqzT1cdR/zy4k4HWo8dBQ==", "dev": true, "requires": { - "@dojo/core": "0.1.0", - "@dojo/has": "0.1.0", - "@dojo/interfaces": "0.1.0", - "@dojo/shim": "0.1.0", + "@dojo/core": "2.0.0-beta2.4", + "@dojo/has": "2.0.0-beta2.3", + "@dojo/interfaces": "2.0.0-beta2.4", + "@dojo/shim": "2.0.0-beta2.4", "@types/jszip": "0.0.33", "jszip": "3.1.4", - "tslib": "1.8.0" + "tslib": "1.7.1" }, "dependencies": { "@dojo/core": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@dojo/core/-/core-0.1.0.tgz", - "integrity": "sha512-boiwQHfV7idOZfZnDzgLrofS2LA7ELGKjd6tl0/hLBunJ3psozAd4CpNcT7XC00/OPYFIxVHFEpI+FZNlpUgfw==", + "version": "2.0.0-beta2.4", + "resolved": "https://registry.npmjs.org/@dojo/core/-/core-2.0.0-beta2.4.tgz", + "integrity": "sha1-AjZimDJ2QxZrtUITk2hJW1CLzBQ=", "dev": true }, "@dojo/has": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@dojo/has/-/has-0.1.0.tgz", - "integrity": "sha512-orYLbYVTcqNpZBmPNRlidUyCn/WuV4jV4JvTAy4Je/zQq9m9Nb8gK+8X7/iOUjSJbP1+vv1ld9Q3932IGC1IyA==", + "version": "2.0.0-beta2.3", + "resolved": "https://registry.npmjs.org/@dojo/has/-/has-2.0.0-beta2.3.tgz", + "integrity": "sha1-faojdQXj7KN2EG1zmMTH3tg2oOE=", + "dev": true + }, + "@dojo/interfaces": { + "version": "2.0.0-beta2.4", + "resolved": "https://registry.npmjs.org/@dojo/interfaces/-/interfaces-2.0.0-beta2.4.tgz", + "integrity": "sha1-OGXj9EQJMuCNJNJPgj5UFwaiKNM=", "dev": true }, "@dojo/shim": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@dojo/shim/-/shim-0.1.0.tgz", - "integrity": "sha512-008RP8DB175ib26dde7wQWFiYIbSACFaArLdLHYdY/cQLN9s3yVj2Gtp5C/9YoY3Ziy9wA241myOjy6QcVHcWw==", + "version": "2.0.0-beta2.4", + "resolved": "https://registry.npmjs.org/@dojo/shim/-/shim-2.0.0-beta2.4.tgz", + "integrity": "sha1-TUjz2MMmxF5k3TRq+tAK61gmPZo=", "dev": true } } @@ -288,9 +281,9 @@ "dev": true }, "@types/lodash": { - "version": "4.14.82", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.82.tgz", - "integrity": "sha512-fhoasvjS6qO4nPEfCD0fonL+BHS3b9ge0LpHxumuDtOuKMvs85OmQldDQzmlGEOZIfyYeo5/sas07+hWWzSJlw==", + "version": "4.14.80", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.80.tgz", + "integrity": "sha512-FumgRtCaxilKUcgMnZCzH6K3gntIwLiLLIaR+UBGNZpT/N3ne2dKrDSGoGIxSHYpAjnq6kIVV0r51U+kLXX59A==", "dev": true }, "@types/marked": { @@ -3282,8 +3275,8 @@ "@dojo/has": "2.0.0-beta2.3", "@dojo/interfaces": "2.0.0-beta2.4", "@dojo/shim": "2.0.0-beta2.4", - "@theintern/digdug": "2.0.1", - "@theintern/leadfoot": "2.0.1", + "@theintern/digdug": "2.0.0", + "@theintern/leadfoot": "2.0.0", "@types/benchmark": "1.0.30", "@types/chai": "4.0.4", "@types/charm": "1.0.1", @@ -3296,7 +3289,7 @@ "@types/istanbul-lib-report": "1.1.0", "@types/istanbul-lib-source-maps": "1.2.0", "@types/istanbul-reports": "1.1.0", - "@types/lodash": "4.14.82", + "@types/lodash": "4.14.80", "@types/mime-types": "2.1.0", "@types/platform": "1.3.1", "@types/resolve": "0.0.4", @@ -3329,12 +3322,30 @@ "ws": "2.3.1" }, "dependencies": { + "@dojo/core": { + "version": "2.0.0-beta2.4", + "resolved": "https://registry.npmjs.org/@dojo/core/-/core-2.0.0-beta2.4.tgz", + "integrity": "sha1-AjZimDJ2QxZrtUITk2hJW1CLzBQ=", + "dev": true + }, + "@dojo/has": { + "version": "2.0.0-beta2.3", + "resolved": "https://registry.npmjs.org/@dojo/has/-/has-2.0.0-beta2.3.tgz", + "integrity": "sha1-faojdQXj7KN2EG1zmMTH3tg2oOE=", + "dev": true + }, "@dojo/interfaces": { "version": "2.0.0-beta2.4", "resolved": "https://registry.npmjs.org/@dojo/interfaces/-/interfaces-2.0.0-beta2.4.tgz", "integrity": "sha1-OGXj9EQJMuCNJNJPgj5UFwaiKNM=", "dev": true }, + "@dojo/shim": { + "version": "2.0.0-beta2.4", + "resolved": "https://registry.npmjs.org/@dojo/shim/-/shim-2.0.0-beta2.4.tgz", + "integrity": "sha1-TUjz2MMmxF5k3TRq+tAK61gmPZo=", + "dev": true + }, "@types/chai": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.0.4.tgz", @@ -7127,9 +7138,9 @@ "dev": true }, "tslib": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.8.0.tgz", - "integrity": "sha512-ymKWWZJST0/CkgduC2qkzjMOWr4bouhuURNXCn/inEX0L57BnRG6FhX76o7FOnsjHazCjfU2LKeSrlS2sIKQJg==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.7.1.tgz", + "integrity": "sha1-vIAEFkaRkjp5/oN4u+s9ogF1OOw=", "dev": true }, "tslint": { @@ -7147,7 +7158,7 @@ "minimatch": "3.0.4", "resolve": "1.5.0", "semver": "5.4.1", - "tslib": "1.8.0", + "tslib": "1.7.1", "tsutils": "2.12.2" }, "dependencies": { @@ -7213,7 +7224,7 @@ "integrity": "sha1-rVikhl0X7D3bZjG2ylO+FKVlb/M=", "dev": true, "requires": { - "tslib": "1.8.0" + "tslib": "1.7.1" } } } @@ -7288,7 +7299,7 @@ "@types/fs-extra": "0.0.33", "@types/handlebars": "4.0.36", "@types/highlight.js": "9.1.10", - "@types/lodash": "4.14.82", + "@types/lodash": "4.14.80", "@types/marked": "0.0.28", "@types/minimatch": "2.0.29", "@types/shelljs": "0.3.33", From aa37df95cdb3cc3cb6dc58272f80cc627137d935 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Tue, 7 Nov 2017 12:22:01 +0000 Subject: [PATCH 32/33] make bind optional --- src/interfaces.d.ts | 2 +- src/meta/Base.ts | 4 +++- tests/unit/meta/Dimensions.ts | 15 +++++---------- tests/unit/meta/Intersection.ts | 27 +++++++++------------------ tests/unit/meta/meta.ts | 18 ++++++------------ 5 files changed, 24 insertions(+), 42 deletions(-) diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index 8fb75504..9ec45e64 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -466,7 +466,7 @@ export interface NodeHandlerInterface extends Evented { export interface WidgetMetaProperties { invalidate: () => void; nodeHandler: NodeHandlerInterface; - bind: WidgetBaseInterface; + bind?: WidgetBaseInterface; } export interface Render { diff --git a/src/meta/Base.ts b/src/meta/Base.ts index f8fb3210..6439f27d 100644 --- a/src/meta/Base.ts +++ b/src/meta/Base.ts @@ -15,7 +15,9 @@ export class Base extends Destroyable implements WidgetMetaBase { this._invalidate = properties.invalidate; this.nodeHandler = properties.nodeHandler; - this._bind = properties.bind; + if (properties.bind) { + this._bind = properties.bind; + } } public has(key: string | number): boolean { diff --git a/tests/unit/meta/Dimensions.ts b/tests/unit/meta/Dimensions.ts index 97c5dc48..4d824f58 100644 --- a/tests/unit/meta/Dimensions.ts +++ b/tests/unit/meta/Dimensions.ts @@ -54,8 +54,7 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: () => {}, - nodeHandler, - bind: this + nodeHandler }); assert.deepEqual(dimensions.get('foo'), defaultDimensions); @@ -65,8 +64,7 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: () => {}, - nodeHandler, - bind: this + nodeHandler }); assert.deepEqual(dimensions.get(1234), defaultDimensions); @@ -77,8 +75,7 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: () => {}, - nodeHandler, - bind: this + nodeHandler }); dimensions.get('foo'); @@ -92,8 +89,7 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: invalidateStub, - nodeHandler, - bind: this + nodeHandler }); dimensions.get('foo'); @@ -135,8 +131,7 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: () => {}, - nodeHandler, - bind: this + nodeHandler }); assert.deepEqual(dimensions.get('foo'), { diff --git a/tests/unit/meta/Intersection.ts b/tests/unit/meta/Intersection.ts index cc9fe0cd..0dc855ed 100644 --- a/tests/unit/meta/Intersection.ts +++ b/tests/unit/meta/Intersection.ts @@ -33,8 +33,7 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler, - bind: this + nodeHandler }); const hasIntersectionInfo = intersection.has('root'); @@ -45,8 +44,7 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler, - bind: this + nodeHandler }); const hasIntersectionInfo = intersection.has('root', { root: 'root' }); @@ -57,8 +55,7 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler, - bind: this + nodeHandler }); const element = document.createElement('div'); nodeHandler.add(element, 'root'); @@ -83,8 +80,7 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler, - bind: this + nodeHandler }); intersection.get('root'); @@ -97,8 +93,7 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler, - bind: this + nodeHandler }); intersection.get(1234); @@ -112,8 +107,7 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: invalidateStub, - nodeHandler, - bind: this + nodeHandler }); intersection.get('root'); @@ -134,8 +128,7 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: invalidateStub, - nodeHandler, - bind: this + nodeHandler }); intersection.get('root'); @@ -181,8 +174,7 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: invalidateStub, - nodeHandler, - bind: this + nodeHandler }); intersection.get('foo', { root: 'root' }); @@ -221,8 +213,7 @@ registerSuite('meta - Intersection', { const nodeHandler = new NodeHandler(); const intersection = new Intersection({ invalidate: () => {}, - nodeHandler, - bind: this + nodeHandler }); const root = document.createElement('div'); diff --git a/tests/unit/meta/meta.ts b/tests/unit/meta/meta.ts index 9691f58f..febf14ca 100644 --- a/tests/unit/meta/meta.ts +++ b/tests/unit/meta/meta.ts @@ -25,8 +25,7 @@ registerSuite('meta base', { nodeHandler.add(element, 'foo'); const meta = new MetaBase({ invalidate: () => {}, - nodeHandler, - bind: this + nodeHandler }); assert.isTrue(meta.has('foo')); @@ -46,8 +45,7 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler, - bind: this + nodeHandler }); const node = meta.callGetNode('foo'); @@ -66,8 +64,7 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler, - bind: this + nodeHandler }); meta.callGetNode('foo'); @@ -87,8 +84,7 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler, - bind: this + nodeHandler }); meta.callGetNode('foo'); @@ -120,8 +116,7 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler, - bind: this + nodeHandler }); meta.callGetNode('foo'); @@ -144,8 +139,7 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler, - bind: this + nodeHandler }); meta.callInvalidate(); From 3f983753444d7b7dd09cb6192ea772def5da76c9 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Tue, 7 Nov 2017 13:35:26 +0000 Subject: [PATCH 33/33] reinstate type, add instances to tests --- src/interfaces.d.ts | 2 +- tests/unit/meta/Dimensions.ts | 17 ++++++++++++----- tests/unit/meta/Intersection.ts | 29 ++++++++++++++++++++--------- tests/unit/meta/meta.ts | 19 +++++++++++++------ 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index 9ec45e64..8fb75504 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -466,7 +466,7 @@ export interface NodeHandlerInterface extends Evented { export interface WidgetMetaProperties { invalidate: () => void; nodeHandler: NodeHandlerInterface; - bind?: WidgetBaseInterface; + bind: WidgetBaseInterface; } export interface Render { diff --git a/tests/unit/meta/Dimensions.ts b/tests/unit/meta/Dimensions.ts index 4d824f58..3b0383b1 100644 --- a/tests/unit/meta/Dimensions.ts +++ b/tests/unit/meta/Dimensions.ts @@ -4,8 +4,10 @@ import global from '@dojo/shim/global'; import { stub, spy } from 'sinon'; import Dimensions from '../../../src/meta/Dimensions'; import NodeHandler from '../../../src/NodeHandler'; +import WidgetBase from '../../../src/WidgetBase'; let rAF: any; +const bindInstance = new WidgetBase(); const defaultDimensions = { offset: { height: 0, @@ -54,7 +56,8 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: bindInstance }); assert.deepEqual(dimensions.get('foo'), defaultDimensions); @@ -64,7 +67,8 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: bindInstance }); assert.deepEqual(dimensions.get(1234), defaultDimensions); @@ -75,7 +79,8 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: bindInstance }); dimensions.get('foo'); @@ -89,7 +94,8 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: invalidateStub, - nodeHandler + nodeHandler, + bind: bindInstance }); dimensions.get('foo'); @@ -131,7 +137,8 @@ registerSuite('meta - Dimensions', { const dimensions = new Dimensions({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: bindInstance }); assert.deepEqual(dimensions.get('foo'), { diff --git a/tests/unit/meta/Intersection.ts b/tests/unit/meta/Intersection.ts index 0dc855ed..09f9f7b3 100644 --- a/tests/unit/meta/Intersection.ts +++ b/tests/unit/meta/Intersection.ts @@ -4,9 +4,11 @@ import global from '@dojo/shim/global'; import { stub, spy } from 'sinon'; import Intersection from '../../../src/meta/Intersection'; import { NodeHandler } from './../../../src/NodeHandler'; +import WidgetBase from '../../../src/WidgetBase'; let intersectionObserver: any; const observers: ([ object, Function ])[] = []; +const bindInstance = new WidgetBase(); registerSuite('meta - Intersection', { @@ -33,7 +35,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: bindInstance }); const hasIntersectionInfo = intersection.has('root'); @@ -44,7 +47,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: bindInstance }); const hasIntersectionInfo = intersection.has('root', { root: 'root' }); @@ -55,7 +59,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: bindInstance }); const element = document.createElement('div'); nodeHandler.add(element, 'root'); @@ -80,7 +85,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: bindInstance }); intersection.get('root'); @@ -93,7 +99,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: bindInstance }); intersection.get(1234); @@ -107,7 +114,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: invalidateStub, - nodeHandler + nodeHandler, + bind: bindInstance }); intersection.get('root'); @@ -128,7 +136,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: invalidateStub, - nodeHandler + nodeHandler, + bind: bindInstance }); intersection.get('root'); @@ -174,7 +183,8 @@ registerSuite('meta - Intersection', { const intersection = new Intersection({ invalidate: invalidateStub, - nodeHandler + nodeHandler, + bind: bindInstance }); intersection.get('foo', { root: 'root' }); @@ -213,7 +223,8 @@ registerSuite('meta - Intersection', { const nodeHandler = new NodeHandler(); const intersection = new Intersection({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: bindInstance }); const root = document.createElement('div'); diff --git a/tests/unit/meta/meta.ts b/tests/unit/meta/meta.ts index febf14ca..49522996 100644 --- a/tests/unit/meta/meta.ts +++ b/tests/unit/meta/meta.ts @@ -9,6 +9,7 @@ import { ProjectorMixin } from '../../../src/main'; import { WidgetBase } from '../../../src/WidgetBase'; const resolvers = createResolvers(); +const bindInstance = new WidgetBase(); registerSuite('meta base', { beforeEach() { @@ -25,7 +26,8 @@ registerSuite('meta base', { nodeHandler.add(element, 'foo'); const meta = new MetaBase({ invalidate: () => {}, - nodeHandler + nodeHandler, + bind: bindInstance }); assert.isTrue(meta.has('foo')); @@ -45,7 +47,8 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler + nodeHandler, + bind: bindInstance }); const node = meta.callGetNode('foo'); @@ -64,7 +67,8 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler + nodeHandler, + bind: bindInstance }); meta.callGetNode('foo'); @@ -84,7 +88,8 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler + nodeHandler, + bind: bindInstance }); meta.callGetNode('foo'); @@ -116,7 +121,8 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler + nodeHandler, + bind: bindInstance }); meta.callGetNode('foo'); @@ -139,7 +145,8 @@ registerSuite('meta base', { const meta = new MyMeta({ invalidate, - nodeHandler + nodeHandler, + bind: bindInstance }); meta.callInvalidate();