mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] *: re-add a bunch of tests
This commit is contained in:
committed by
Géry Debongnie
parent
ba2fe3ff55
commit
e01fe301c0
@@ -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
|
||||
) {
|
||||
|
||||
@@ -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><b>one</b><b>one</b><b>two</b><b>two</b><b>tree</b><b>tree</b></div>"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user