From 649e20b9563744940e02691649c0c4c856bb459e Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Fri, 10 May 2019 17:46:32 +0200 Subject: [PATCH] [IMP] improve tests of transition directive --- src/qweb_core.ts | 8 +- src/qweb_extensions.ts | 10 +-- tests/__snapshots__/qweb.test.ts.snap | 35 ++++++--- tests/component.test.ts | 101 +++++++++++++++++++++++++- tests/helpers.ts | 13 +++- tests/qweb.test.ts | 70 +++++++++++++++--- 6 files changed, 210 insertions(+), 27 deletions(-) diff --git a/src/qweb_core.ts b/src/qweb_core.ts index 4bf478b5..fc417d4c 100644 --- a/src/qweb_core.ts +++ b/src/qweb_core.ts @@ -96,7 +96,13 @@ const NODE_HOOKS_PARAMS = { remove: "(vn, rm)" }; -export const UTILS = { +interface Utils { + h: typeof h; + objectToAttrString(obj: Object): string; + [key: string]: any; +}; + +export const UTILS:Utils = { h: h, objectToAttrString(obj: Object): string { let classes: string[] = []; diff --git a/src/qweb_extensions.ts b/src/qweb_extensions.ts index 0b0a9219..837493a9 100644 --- a/src/qweb_extensions.ts +++ b/src/qweb_extensions.ts @@ -90,16 +90,16 @@ QWeb.addDirective({ //------------------------------------------------------------------------------ // t-transition //------------------------------------------------------------------------------ -(UTILS).nextFrame = function(cb: () => void) { +UTILS.nextFrame = function(cb: () => void) { requestAnimationFrame(() => requestAnimationFrame(cb)); }; -(UTILS).transitionCreate = function(elm: HTMLElement, name: string) { +UTILS.transitionCreate = function(elm: HTMLElement, name: string) { elm.classList.add(name + "-enter"); elm.classList.add(name + "-enter-active"); }; -(UTILS).transitionInsert = function(elm: HTMLElement, name: string) { +UTILS.transitionInsert = function(elm: HTMLElement, name: string) { const finalize = () => { elm.classList.remove(name + "-enter-active"); elm.classList.remove(name + "-enter-to"); @@ -111,7 +111,7 @@ QWeb.addDirective({ }); }; -(UTILS).transitionRemove = function( +UTILS.transitionRemove = function( elm: HTMLElement, name: string, rm: () => void @@ -120,7 +120,7 @@ QWeb.addDirective({ elm.classList.add(name + "-leave-active"); const finalize = () => { elm.classList.remove(name + "-leave-active"); - elm.classList.remove(name + "-enter-to"); + elm.classList.remove(name + "-leave-to"); rm(); }; this.nextFrame(() => { diff --git a/tests/__snapshots__/qweb.test.ts.snap b/tests/__snapshots__/qweb.test.ts.snap index a4282814..fca14bf1 100644 --- a/tests/__snapshots__/qweb.test.ts.snap +++ b/tests/__snapshots__/qweb.test.ts.snap @@ -1,15 +1,34 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`animations t-transition, on a simple node 1`] = ` +exports[`animations t-transition with no delay/duration 1`] = ` "function anonymous(context,extra ) { var h = this.utils.h; let c1 = [], p1 = {key:1}; - var vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2}; - var vn2 = h('span', p2, c2); - c1.push(vn2); - p2.hook = { + var vn1 = h('span', p1, c1); + p1.hook = { + create: (_, n) => { + this.utils.transitionCreate(n.elm, 'jupiler'); + }, + insert: vn => { + this.utils.transitionInsert(vn.elm, 'jupiler'); + }, + remove: (vn, rm) => { + this.utils.transitionRemove(vn.elm, 'jupiler', rm); + }, + }; + c1.push({text: \`blue\`}); + return vn1; +}" +`; + +exports[`animations t-transition, on a simple node (insert) 1`] = ` +"function anonymous(context,extra +) { + var h = this.utils.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('span', p1, c1); + p1.hook = { create: (_, n) => { this.utils.transitionCreate(n.elm, 'chimay'); }, @@ -20,13 +39,11 @@ exports[`animations t-transition, on a simple node 1`] = ` this.utils.transitionRemove(vn.elm, 'chimay', rm); }, }; - c2.push({text: \`blue\`}); + c1.push({text: \`blue\`}); return vn1; }" `; -exports[`animations t-transition, on a simple node 2`] = `"blue"`; - exports[`attributes dynamic attribute falsy variable 1`] = ` "function anonymous(context,extra ) { diff --git a/tests/component.test.ts b/tests/component.test.ts index 69904ba6..cd7addef 100644 --- a/tests/component.test.ts +++ b/tests/component.test.ts @@ -5,7 +5,9 @@ import { makeTestEnv, nextMicroTick, nextTick, - normalize + normalize, + patchNextFrame, + unpatchNextFrame } from "./helpers"; //------------------------------------------------------------------------------ @@ -19,6 +21,7 @@ import { let fixture: HTMLElement; let env: Env; +let cssEl: HTMLElement; beforeEach(() => { fixture = makeTestFixture(); @@ -2277,6 +2280,8 @@ describe("t-mounted directive", () => { const widget = new TestWidget(env); widget.f = jest.fn(); await widget.mount(fixture); + + patchNextFrame((cb) => cb()); expect(widget.f).toHaveBeenCalledTimes(0); widget.state.flag = true; @@ -2328,3 +2333,97 @@ describe("can deduce template from name", () => { expect(fixture.innerHTML).toBe("Rochefort 10"); }); }); + +describe("animations", () => { + beforeEach(() => { + cssEl = document.createElement("style"); + let css = ` + .chimay-enter-active, .chimay-leave-active { + transition-property: opacity; + transition-duration: 0.1s; + } + .chimay-enter, .chimay-leave-to { + opacity: 0; + } + `; + cssEl.textContent = css.trim(); + document.head.appendChild(cssEl); + }); + + afterEach(() => { + document.head.removeChild(cssEl); + unpatchNextFrame(); + }); + + test("t-transition on a conditional node", async () => { + expect.assertions(7); + + env.qweb.addTemplate( + "TestWidget", + `
blue
` + ); + class TestWidget extends Widget { + state = { hide: false }; + } + const widget = new TestWidget(env); + + // insert widget into the DOM + let def = makeDeferred(); + var spanNode; + patchNextFrame(cb => { + expect(spanNode.className).toBe("chimay-enter chimay-enter-active"); + cb(); + expect(spanNode.className).toBe("chimay-enter-active chimay-enter-to"); + def.resolve(); + }); + await widget.mount(fixture); + spanNode = widget.el!.children[0]; + expect(spanNode.className).toBe("chimay-enter chimay-enter-active"); + await def; // wait for the mocked repaint to be done + spanNode.dispatchEvent(new Event("transitionend")); // mock end of css transition + expect(spanNode.className).toBe(""); + + // remove span from the DOM + def = makeDeferred(); + widget.state.hide = true; + patchNextFrame(cb => { + expect(spanNode.className).toBe("chimay-leave chimay-leave-active"); + cb(); + expect(spanNode.className).toBe("chimay-leave-active chimay-leave-to"); + def.resolve(); + }); + await def; // wait for the mocked repaint to be done + spanNode.dispatchEvent(new Event("transitionend")); // mock end of css transition + expect(spanNode.className).toBe(""); + }); + + test("t-transition combined with t-ref", async () => { + expect.assertions(5); + + env.qweb.addTemplate( + "TestWidget", + `
blue
` + ); + class TestWidget extends Widget { + state = { hide: false }; + } + const widget = new TestWidget(env); + + // insert widget into the DOM + let def = makeDeferred(); + var spanNode; + patchNextFrame(cb => { + expect(spanNode.className).toBe("chimay-enter chimay-enter-active"); + cb(); + expect(spanNode.className).toBe("chimay-enter-active chimay-enter-to"); + def.resolve(); + }); + await widget.mount(fixture); + spanNode = widget.el!.children[0]; + expect(widget.refs.span).toBe(spanNode); + expect(spanNode.className).toBe("chimay-enter chimay-enter-active"); + await def; // wait for the mocked repaint to be done + spanNode.dispatchEvent(new Event("transitionend")); // mock end of css transition + expect(spanNode.className).toBe(""); + }); +}); diff --git a/tests/helpers.ts b/tests/helpers.ts index a1898d6c..6c8b278a 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -1,5 +1,5 @@ import { Env } from "../src/component"; -import { QWeb } from "../src/qweb_core"; +import { QWeb, UTILS } from "../src/qweb_core"; import "../src/qweb_directives"; import "../src/qweb_extensions"; @@ -42,3 +42,14 @@ export function makeTestEnv(): Env { qweb: new QWeb() }; } + +let nextFrame = UTILS.nextFrame; +export function patchNextFrame(f: Function): void { + UTILS.nextFrame = (cb: () => void) => { + setTimeout(() => f(cb)); + }; +} + +export function unpatchNextFrame(): void { + UTILS.nextFrame = nextFrame; +} diff --git a/tests/qweb.test.ts b/tests/qweb.test.ts index 44e5cab9..7fb5dd61 100644 --- a/tests/qweb.test.ts +++ b/tests/qweb.test.ts @@ -1,6 +1,11 @@ import { EvalContext, QWeb } from "../src/qweb_core"; import { patch } from "../src/vdom"; -import { normalize } from "./helpers"; +import { + makeDeferred, + normalize, + patchNextFrame, + unpatchNextFrame +} from "./helpers"; //------------------------------------------------------------------------------ // Setup and helpers @@ -10,6 +15,7 @@ import { normalize } from "./helpers"; // - qweb: a new QWeb instance let qweb: QWeb; +let cssEl: HTMLElement; beforeEach(() => { qweb = new QWeb(); @@ -1281,14 +1287,58 @@ describe("debugging", () => { }); describe("animations", () => { - test("t-transition, on a simple node", async () => { - // this test does not test much, because it is not easy to test timing - // transitions... we should do a little more effort for these tests. - qweb.addTemplate( - "test", - `
blue
` - ); - let dom: HTMLElement = renderToDOM(qweb, "test"); - expect(dom.innerHTML).toMatchSnapshot(); + beforeEach(() => { + cssEl = document.createElement("style"); + let css = ` + .chimay-enter-active, .chimay-leave-active { + transition-property: opacity; + transition-duration: 0.1s; + } + .chimay-enter, .chimay-leave-to { + opacity: 0; + } + `; + cssEl.textContent = css.trim(); + document.head.appendChild(cssEl); + }); + + afterEach(() => { + document.head.removeChild(cssEl); + unpatchNextFrame(); + }); + + test("t-transition, on a simple node (insert)", async () => { + expect.assertions(5); + qweb.addTemplate("test", `blue`); + + let def = makeDeferred(); + patchNextFrame(cb => { + expect(node.className).toBe("chimay-enter chimay-enter-active"); + cb(); + expect(node.className).toBe("chimay-enter-active chimay-enter-to"); + def.resolve(); + }); + + let node: HTMLElement = renderToDOM(qweb, "test"); + expect(node.className).toBe("chimay-enter chimay-enter-active"); + await def; // wait for the mocked repaint to be done + node.dispatchEvent(new Event("transitionend")); // mock end of css transition + expect(node.className).toBe(""); + }); + + test("t-transition with no delay/duration", async () => { + expect.assertions(4); + qweb.addTemplate("test", `blue`); + + let def = makeDeferred(); + patchNextFrame(cb => { + expect(node.className).toBe("jupiler-enter jupiler-enter-active"); + cb(); + expect(node.className).toBe(""); + def.resolve(); + }); + let node: HTMLElement = renderToDOM(qweb, "test"); + expect(node.className).toBe("jupiler-enter jupiler-enter-active"); + await def; }); });