[IMP] *: re-add a bunch of tests

This commit is contained in:
Samuel Degueldre
2021-10-21 10:13:08 +02:00
committed by Géry Debongnie
parent ba2fe3ff55
commit e01fe301c0
19 changed files with 1525 additions and 279 deletions
@@ -134,6 +134,34 @@ exports[`basics can handle empty props 2`] = `
}"
`;
exports[`basics can inject values in tagged templates 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['state'].n;
return block1([d1]);
}
}"
`;
exports[`basics can inject values in tagged templates 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
const callTemplate_2 = getTemplate(\`__template__9\`);
return function template(ctx, node, key = \\"\\") {
return callTemplate_2(ctx, node, key + \`__1\`);
}
}"
`;
exports[`basics can mount a component with just some text 1`] = `
"function anonymous(bdom, helpers
) {
@@ -1006,6 +1034,36 @@ exports[`basics updating a component with t-foreach as root 1`] = `
}"
`;
exports[`basics updating widget immediately 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<span>abc<block-child-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let b2;
if (ctx['props'].flag) {
b2 = text(\`def\`);
}
return block1([], [b2]);
}
}"
`;
exports[`basics updating widget immediately 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
return function template(ctx, node, key = \\"\\") {
return component(\`Child\`, {flag: ctx['state'].flag}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`basics widget after a t-foreach 1`] = `
"function anonymous(bdom, helpers
) {
@@ -1074,3 +1132,71 @@ exports[`basics zero or one child components 2`] = `
}
}"
`;
exports[`support svg components add proper namespace to svg 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<g><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/></g>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`support svg components add proper namespace to svg 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<svg><block-child-0/></svg>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`GComp\`, {}, key + \`__1\`, node, ctx);
return block1([], [b2]);
}
}"
`;
exports[`t-raw in components can render list of t-raw 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['state'].items);
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`item\`] = v_block2[i1];
let key1 = ctx['item'];
let b4 = text(ctx['item']);
let b5 = html(ctx['item']);
c_block2[i1] = withKey(multi([b4, b5]), key1);
}
let b2 = list(c_block2);
return block1([], [b2]);
}
}"
`;
exports[`t-raw in components update properly on state changes 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = html(ctx['state'].value);
return block1([], [b2]);
}
}"
`;
@@ -76,3 +76,28 @@ exports[`event handling support for callable expression in event handler 1`] = `
}
}"
`;
exports[`event handling t-on with handler bound to dynamic argument on a t-foreach 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<div class=\\"item\\" block-handler-0=\\"click\\"/>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['items']);
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`item\`] = v_block2[i1];
let key1 = ctx['item'];
const arg1 = [ctx['item']];
let d1 = [ctx, 'onClick', arg1];
c_block2[i1] = withKey(block3([d1]), key1);
}
let b2 = list(c_block2);
return block1([], [b2]);
}
}"
`;
@@ -0,0 +1,128 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`hooks autofocus hook input in a t-if 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div><input block-ref=\\"0\\"/><block-child-0/></div>\`);
let block2 = createBlock(\`<input block-ref=\\"0\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const refs = ctx.__owl__.refs;
let b2;
let d1 = (el) => refs[\`input1\`] = el;
if (ctx['state'].flag) {
let d2 = (el) => refs[\`input2\`] = el;
b2 = block2([d2]);
}
return block1([d1], [b2]);
}
}"
`;
exports[`hooks autofocus hook simple input 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div><input block-ref=\\"0\\"/><input block-ref=\\"1\\"/></div>\`);
return function template(ctx, node, key = \\"\\") {
const refs = ctx.__owl__.refs;
let d1 = (el) => refs[\`input1\`] = el;
let d2 = (el) => refs[\`input2\`] = el;
return block1([d1, d2]);
}
}"
`;
exports[`hooks can use onWillStart, onWillUpdateProps 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['props'].value;
return block1([d1]);
}
}"
`;
exports[`hooks can use onWillStart, onWillUpdateProps 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
return function template(ctx, node, key = \\"\\") {
return component(\`MyComponent\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
}
}"
`;
exports[`hooks can use useComponent 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div/>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`hooks mounted callbacks should be called in reverse order from willUnmount callbacks 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div>hey<block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['state'].value;
return block1([d1]);
}
}"
`;
exports[`hooks two different call to willPatch/patched should work 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div>hey<block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['state'].value;
return block1([d1]);
}
}"
`;
exports[`hooks useRef hook: basic use 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div><button block-ref=\\"0\\"><block-text-1/></button></div>\`);
return function template(ctx, node, key = \\"\\") {
const refs = ctx.__owl__.refs;
let d1 = (el) => refs[\`button\`] = el;
let d2 = ctx['value'];
return block1([d1, d2]);
}
}"
`;
@@ -14,6 +14,99 @@ exports[`lifecycle hooks basic checks for a component 1`] = `
}"
`;
exports[`lifecycle hooks component semantics 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div>B</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`lifecycle hooks component semantics 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div>D</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`lifecycle hooks component semantics 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div>E</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`lifecycle hooks component semantics 4`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div>F</div>\`);
return function template(ctx, node, key = \\"\\") {
return block1();
}
}"
`;
exports[`lifecycle hooks component semantics 5`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div>C<block-child-0/><block-child-1/><block-child-2/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2,b3,b4;
b2 = component(\`D\`, {}, key + \`__1\`, node, ctx);
if (ctx['state'].flag) {
b3 = component(\`E\`, {}, key + \`__2\`, node, ctx);
} else {
b4 = component(\`F\`, {}, key + \`__3\`, node, ctx);
}
return block1([], [b2, b3, b4]);
}
}"
`;
exports[`lifecycle hooks component semantics 6`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
let block1 = createBlock(\`<div>A<block-child-0/><block-child-1/></div>\`);
return function template(ctx, node, key = \\"\\") {
let b2 = component(\`B\`, {}, key + \`__1\`, node, ctx);
let b3 = component(\`C\`, {}, key + \`__2\`, node, ctx);
return block1([], [b2, b3]);
}
}"
`;
exports[`lifecycle hooks components are unmounted and destroyed if no longer in DOM 1`] = `
"function anonymous(bdom, helpers
) {
+217
View File
@@ -741,6 +741,165 @@ describe("basics", () => {
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div><div><span>asdf</span><span>asdf</span></div></div>");
});
// TODO: rename
test("updating widget immediately", async () => {
// in this situation, we protect against a bug that occurred: because of the
// interplay between components and vnodes, a sub widget vnode was patched
// twice.
class Child extends Component {
static template = xml`<span>abc<t t-if="props.flag">def</t></span>`;
}
class Parent extends Component {
static template = xml`<Child flag="state.flag"/>`
static components = { Child };
state = useState({ flag: false });
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<span>abc</span>");
parent.state.flag = true;
await nextTick();
expect(fixture.innerHTML).toBe("<span>abcdef</span>");
});
test("can inject values in tagged templates", async () => {
const SUBTEMPLATE = xml`<span><t t-esc="state.n"/></span>`;
class Parent extends Component {
static template = xml`<t t-call="${SUBTEMPLATE}"/>`;
state = useState({ n: 42 });
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<span>42</span>");
});
// Depends on t-props
test.skip("update props of component without concrete own node", async () => {
class Custom extends Component {
static template = xml`
<div class="widget-subkey">
<t t-esc="props.key"/>__<t t-esc="props.subKey"/>
</div>`;
}
class Child extends Component {
static components = { Custom };
static template = xml`
<Custom
t-key="props.subKey"
key="props.key"
subKey="props.subKey"/>`;
}
class Parent extends Component {
static components = { Child };
static template = xml`
<div>
<Child t-key="childProps.key" t-props="childProps"/>
</div>`;
childProps = {
key: 1,
subKey: 1,
};
}
const parent = await mount(Parent, fixture);
expect(fixture.textContent!.trim()).toBe("1__1");
// First step: change the Custom's instance
Object.assign(parent.childProps, {
subKey: 2,
});
parent.render();
await nextTick();
expect(fixture.textContent!.trim()).toBe("1__2");
// Second step, change both Child's and Custom's instance
Object.assign(parent.childProps, {
key: 2,
subKey: 3,
});
parent.render();
await nextTick();
expect(fixture.textContent!.trim()).toBe("2__3");
});
test.skip("subcomponents cannot change observable state received from parent", async () => {
const consoleError = console.error;
console.error = jest.fn();
class Child extends Component {
static template = xml`<div/>`;
setup() {
this.props.obj.coffee = 2;
}
}
class Parent extends Component {
static template = xml`<div><Child obj="state.obj"/></div>`;
static components = { Child };
state = useState({ obj: { coffee: 1 } });
}
let error;
try {
await mount(Parent, fixture);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe('Observed state cannot be changed here! (key: "coffee", val: "2")');
expect(console.error).toBeCalledTimes(0);
console.error = consoleError;
});
});
describe("dynamic t-props", () => {
test.skip("basic use", async () => {
expect.assertions(4);
class Child extends Component {
static template = xml`
<span>
<t t-esc="props.a + props.b"/>
</span>
`;
setup() {
expect(this.props).toEqual({ a: 1, b: 2 });
expect(this.props).not.toBe(parent.some.obj);
}
}
class Parent extends Component {
static template = xml`
<div>
<Child t-props="some.obj"/>
</div>
`;
static components = { Child };
some = { obj: { a: 1, b: 2 } };
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div><span>3</span></div>");
});
test.skip("t-props with props", async () => {
expect.assertions(1);
class Child extends Component {
static template = xml`<div />`;
setup() {
expect(this.props).toEqual({ a: 1, b: 2, c: "c" });
}
}
class Parent extends Component {
static template = xml`
<div>
<Child t-props="props" a="1" b="2" />
</div>
`;
static components = { Child };
props = { a: "a", c: "c" };
}
await mount(Parent, fixture);
});
});
describe.skip("mount targets", () => {
@@ -1012,3 +1171,61 @@ describe.skip("mount special cases", () => {
// expect(fixture.textContent).toBe("fixedsome text");
});
});
describe("support svg components", () => {
test("add proper namespace to svg", async () => {
class GComp extends Component {
static template = xml`
<g>
<circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/>
</g>`;
}
class Svg extends Component {
static template = xml`
<svg>
<GComp/>
</svg>`;
static components = { GComp };
}
await mount(Svg, fixture);
expect(fixture.innerHTML).toBe(
'<svg><g><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"></circle></g></svg>'
);
});
});
describe("t-raw in components", () => {
test("update properly on state changes", async () => {
class Test extends Component {
static template = xml`<div><t t-raw="state.value"/></div>`;
state = useState({ value: "<b>content</b>" });
}
const component = await mount(Test, fixture);
expect(fixture.innerHTML).toBe("<div><b>content</b></div>");
component.state.value = "<span>other content</span>";
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>other content</span></div>");
});
test("can render list of t-raw ", async () => {
class Test extends Component {
static template = xml`
<div>
<t t-foreach="state.items" t-as="item" t-key="item">
<t t-esc="item"/>
<t t-raw="item"/>
</t>
</div>`;
state = useState({ items: ["<b>one</b>", "<b>two</b>", "<b>tree</b>"] });
}
await mount(Test, fixture);
expect(fixture.innerHTML).toBe(
"<div>&lt;b&gt;one&lt;/b&gt;<b>one</b>&lt;b&gt;two&lt;/b&gt;<b>two</b>&lt;b&gt;tree&lt;/b&gt;<b>tree</b></div>"
);
});
});
+20
View File
@@ -302,4 +302,24 @@ describe("event handling", () => {
button.click();
expect(steps).toEqual(["captured", "normal"]);*/
});
test.only("t-on with handler bound to dynamic argument on a t-foreach", async () => {
expect.assertions(3);
class Parent extends Component {
static template = xml`
<div>
<t t-foreach="items" t-as="item" t-key="item">
<div class="item" t-on-click="onClick(item)"/>
</t>
</div>`;
items = [1, 2, 3, 4];
onClick(n: number, ev: MouseEvent) {
expect(n).toBe(1);
expect(ev).toBeInstanceOf(MouseEvent);
}
}
await mount(Parent, fixture);
(<HTMLElement>fixture.querySelector('.item')).click();
});
});
+303
View File
@@ -0,0 +1,303 @@
import { App, Component, mount, useRef, useState, useComponent, onMounted, onPatched, onWillStart, onWillUpdateProps, onWillPatch, xml, onWillUnmount } from "../../src";
import {
makeTestFixture,
nextTick,
snapshotEverything,
} from "../helpers";
let fixture: HTMLElement;
snapshotEverything();
beforeEach(() => {
fixture = makeTestFixture();
});
describe("hooks", () => {
test("useRef hook: basic use", async () => {
let counter: Counter;
class Counter extends Component {
static template = xml`<div><button t-ref="button"><t t-esc="value"/></button></div>`;
button = useRef("button");
value = 0;
setup() {
counter = this;
}
increment() {
this.value++;
(this.button.el as HTMLButtonElement).innerHTML = String(this.value);
}
}
const mounted = mount(Counter, fixture);
expect(counter!.button.el).toBe(null);
await mounted;
expect(fixture.innerHTML).toBe("<div><button>0</button></div>");
expect(counter!.button.el).not.toBe(null);
expect(counter!.button.el).toBe(fixture.querySelector("button"));
counter!.increment();
expect(fixture.innerHTML).toBe("<div><button>1</button></div>");
});
// TODO: rename like next test (ensures willPatch/patched calls are symmetrical)
test("two different call to willPatch/patched should work", async () => {
const steps: string[] = [];
function useMyHook(i: number) {
onPatched(() => {
steps.push("hook:patched" + i);
});
onWillPatch(() => {
steps.push("hook:willPatch" + i);
});
}
class Test extends Component {
static template = xml`<div>hey<t t-esc="state.value"/></div>`;
state = useState({ value: 1 });
setup() {
useMyHook(1);
useMyHook(2);
}
}
const component = await mount(Test, fixture);
expect(fixture.innerHTML).toBe("<div>hey1</div>");
component.state.value++;
await nextTick();
expect(fixture.innerHTML).toBe("<div>hey2</div>");
expect(steps).toEqual(["hook:willPatch2", "hook:willPatch1", "hook:patched1", "hook:patched2"]);
});
test("mounted callbacks should be called in reverse order from willUnmount callbacks", async () => {
const steps: string[] = [];
function useMyHook(i: number) {
onMounted(() => {
steps.push("hook:mounted" + i);
});
onWillUnmount(() => {
steps.push("hook:willUnmount" + i);
});
}
class Test extends Component {
static template = xml`<div>hey<t t-esc="state.value"/></div>`;
state = useState({ value: 1 });
setup() {
useMyHook(1);
useMyHook(2);
}
}
const app = new App(Test);
await app.mount(fixture);
app.destroy();
expect(steps).toEqual(["hook:mounted2", "hook:mounted1", "hook:willUnmount1", "hook:willUnmount2"]);
});
describe("autofocus hook", () => {
function useAutofocus(name: string) {
let ref = useRef(name);
let isInDom = false;
function updateFocus() {
if (!isInDom && ref.el) {
isInDom = true;
ref.el.focus();
} else if (isInDom && !ref.el) {
isInDom = false;
}
}
onPatched(updateFocus);
onMounted(updateFocus);
}
test("simple input", async () => {
class Test extends Component {
static template = xml`
<div>
<input t-ref="input1"/>
<input t-ref="input2"/>
</div>`;
setup() {
useAutofocus("input2");
}
}
await mount(Test, fixture);
expect(fixture.innerHTML).toBe("<div><input><input></div>");
const input2 = fixture.querySelectorAll("input")[1];
expect(input2).toBe(document.activeElement);
});
test("input in a t-if", async () => {
class Test extends Component {
static template = xml`
<div>
<input t-ref="input1"/>
<t t-if="state.flag"><input t-ref="input2"/></t>
</div>`;
state = useState({ flag: false });
setup() {
useAutofocus("input2");
}
}
const component = await mount(Test, fixture);
expect(fixture.innerHTML).toBe("<div><input></div>");
expect(document.activeElement).toBe(document.body);
component.state.flag = true;
await nextTick();
const input2 = fixture.querySelectorAll("input")[1];
expect(input2).toBe(document.activeElement);
});
});
test.skip("can use useEnv", async () => {
expect.assertions(2);
class Test extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`;
setup() {
//expect(useEnv()).toBe(this.env);
}
}
await mount(Test, fixture);
});
test.skip("can use sub env", async () => {
class Test extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`;
setup() {
//useSubEnv({ val: 3 });
}
}
const component = await mount(Test, fixture);
expect(fixture.innerHTML).toBe("<div>3</div>");
expect(component.env).not.toHaveProperty("val");
expect(component.env).toHaveProperty("val");
});
test("can use useComponent", async () => {
expect.assertions(2);
class Test extends Component {
static template = xml`<div></div>`;
setup() {
expect(useComponent()).toBe(this);
}
}
await mount(Test, fixture);
});
test.skip("parent and child env", async () => {
class Child extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`;
super() {
//useSubEnv({ val: 5 });
}
}
class Parent extends Component {
static template = xml`<t t-esc="env.val"/><Child/>`;
static components = { Child };
setup() {
//useSubEnv({ val: 3 });
}
}
mount(Parent, fixture);
expect(fixture.innerHTML).toBe("3<div>5</div>");
});
test("can use onWillStart, onWillUpdateProps", async () => {
const steps: string[] = [];
async function slow(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve("slow");
}, 0);
});
}
function useMyHook() {
onWillStart(async () => {
steps.push(await slow());
steps.push("onWillStart");
});
onWillUpdateProps(async (nextProps) => {
expect(nextProps).toEqual({ value: 2 });
steps.push(await slow());
steps.push("onWillUpdateProps");
});
}
function use2ndHook() {
onWillStart(() => {
steps.push("on2ndStart");
});
onWillUpdateProps((nextProps) => {
expect(nextProps).toEqual({ value: 2 });
steps.push("on2ndUpdate");
});
}
class MyComponent extends Component {
static template = xml`<span><t t-esc="props.value"/></span>`;
setup() {
useMyHook();
use2ndHook();
}
}
class App extends Component {
static template = xml`<MyComponent value="state.value"/>`;
static components = { MyComponent };
state = useState({ value: 1 });
}
const app = await mount(App, fixture);
expect(fixture.innerHTML).toBe("<span>1</span>");
// NOTE: 'on2ndStart' appears first in the list even though
// the 'use2ndHook' is declared after 'useMyHook'. This is
// because Promise.all is used to call the callbacks specified
// in the hooks, which runs them simultaneously.
// Additionally, 'slow' should be listed before 'onWillStart'
// because call to `slow` is awaited.
expect(steps).toEqual(["on2ndStart", "slow", "onWillStart"]);
app.state.value = 2;
await nextTick();
expect(fixture.innerHTML).toBe("<span>2</span>");
expect(steps).toEqual([
"on2ndStart",
"slow",
"onWillStart",
"on2ndUpdate",
"slow",
"onWillUpdateProps",
]);
});
test.skip("useExternalListener", async () => {
let n = 0;
class MyComponent extends Component {
static template = xml`<span><t t-esc="props.value"/></span>`;
setup() {
//useExternalListener(window as any, "click", this.increment);
}
increment() {
n++;
}
}
class App extends Component {
static template = xml`<MyComponent t-if="state.flag"/>`;
static components = { MyComponent };
state = useState({ flag: false });
}
const app = await mount(App, fixture);
expect(n).toBe(0);
window.dispatchEvent(new Event("click"));
expect(n).toBe(0);
app.state.flag = true;
await nextTick();
window.dispatchEvent(new Event("click"));
expect(n).toBe(1);
app.state.flag = false;
await nextTick();
window.dispatchEvent(new Event("click"));
expect(n).toBe(1);
});
});
+95
View File
@@ -969,4 +969,99 @@ describe("lifecycle hooks", () => {
"hook:willunmount1",
]);*/
});
// TODO: rename (corresponds to https://github.com/odoo/owl/blob/master/doc/reference/concurrency_model.md#semantics)
test("component semantics", async () => {
let steps: string[] = [];
class TestWidget extends Component {
name: string = "test";
setup() {
useLogLifecycle(steps);
}
}
class B extends TestWidget {
static template = xml`<div>B</div>`;
name = "B";
}
class D extends TestWidget {
static template = xml`<div>D</div>`;
name = "D";
}
class E extends TestWidget {
static template = xml`<div>E</div>`;
name = "E";
}
class F extends TestWidget {
static template = xml`<div>F</div>`;
name = "F";
}
let c: C;
class C extends TestWidget {
static template = xml`
<div>C<D />
<E t-if="state.flag" />
<F t-else="!state.flag" />
</div>`;
static components = { D, E, F };
name = "C";
state = useState({ flag: true });
setup() {
c = this;
super.setup();
}
}
class A extends TestWidget {
static template = xml`<div>A<B /><C /></div>`;
static components = { B, C };
name = "A";
}
await mount(A, fixture);
expect(fixture.innerHTML).toBe(`<div>A<div>B</div><div>C<div>D</div><div>E</div></div></div>`);
expect(steps).toEqual([
"A:setup",
"A:willStart",
"A:render",
"B:setup",
"B:willStart",
"C:setup",
"C:willStart",
"B:render",
"C:render",
"D:setup",
"D:willStart",
"E:setup",
"E:willStart",
"D:render",
"E:render",
"E:mounted",
"D:mounted",
"C:mounted",
"B:mounted",
"A:mounted",
]);
// update
steps.splice(0);
c!.state.flag = false;
await nextTick();
expect(steps).toEqual([
"C:render",
"D:willUpdateProps",
"F:setup",
"F:willStart",
"D:render",
"F:render",
"C:willPatch",
"D:willPatch",
"E:willUnmount",
"E:destroyed",
"F:mounted",
"D:patched",
"C:patched",
]);
});
});
+100
View File
@@ -44,4 +44,104 @@ describe("refs", () => {
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
);
});
// TODO: rename
test.skip("t-refs on widget are components", async () => {
class Child extends Component {
static template = xml`<div>b</div>`;
}
let parent: Parent;
class Parent extends Component {
static template = xml`<div class="outer-div">Hello<Child t-ref="mywidgetb" /></div>`;
static components = { Child };
ref = useRef<Child>("mywidgetb");
setup() {
parent = this;
}
}
const mounted = mount(Parent, fixture);
expect(parent!.ref.comp).toBe(null);
expect(parent!.ref.el).toBe(null);
await mounted;
expect(parent!.ref.comp).toBeInstanceOf(Child);
expect(parent!.ref.el).toEqual(fixture.querySelector(".outer-div > div"));
});
test.skip("t-refs are bound at proper timing", async () => {
expect.assertions(4);
class Child extends Component {
static template = xml`<div>widget</div>`;
}
class Parent extends Component {
static template = xml`
<div>
<Child t-foreach="state.list" t-as="elem" t-ref="child" t-key="elem"/>
</div>
`;
static components = { Child };
state = useState({ list: <any>[] });
child = useRef("child");
willPatch() {
expect(this.child.comp).toBeNull();
}
patched() {
expect(this.child.comp).not.toBeNull();
}
}
const parent = await mount(Parent, fixture);
parent.state.list.push(1);
await nextTick();
});
test.skip("t-refs are bound at proper timing (2)", async () => {
expect.assertions(10);
class Child extends Component {
static template = xml`<div>widget</div>`;
}
class Parent extends Component {
static template = xml`
<div>
<Child t-if="state.child1" t-ref="child1"/>
<Child t-if="state.child2" t-ref="child2"/>
</div>`;
static components = { Child };
state = useState({ child1: true, child2: false });
child1 = useRef("child1");
child2 = useRef("child2");
count = 0;
mounted() {
expect(this.child1.comp).toBeDefined();
expect(this.child2.comp).toBeNull();
}
willPatch() {
if (this.count === 0) {
expect(this.child1.comp).toBeDefined();
expect(this.child2.comp).toBeNull();
}
if (this.count === 1) {
expect(this.child1.comp).toBeDefined();
expect(this.child2.comp).toBeDefined();
}
}
patched() {
if (this.count === 0) {
expect(this.child1.comp).toBeDefined();
expect(this.child2.comp).toBeDefined();
}
if (this.count === 1) {
expect(this.child1.comp).toBeNull();
expect(this.child2.comp).toBeDefined();
}
this.count++;
}
}
const parent = await mount(Parent, fixture);
parent.state.child2 = true;
await nextTick();
parent.state.child1 = false;
await nextTick();
});
});