diff --git a/tests/components/__snapshots__/basics.test.ts.snap b/tests/components/__snapshots__/basics.test.ts.snap
index 5158f02e..82c79418 100644
--- a/tests/components/__snapshots__/basics.test.ts.snap
+++ b/tests/components/__snapshots__/basics.test.ts.snap
@@ -328,6 +328,44 @@ exports[`basics do not remove previously rendered dom if not necessary, variatio
}"
`;
+exports[`basics don't fallback to component's registry if widget defined in the instance's context 1`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ let block1 = createBlock(\`ChildB \`);
+
+ return function template(ctx, node, key = \\"\\") {
+ return block1();
+ }
+}"
+`;
+
+exports[`basics don't fallback to component's registry if widget defined in the instance's context 2`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ return function template(ctx, node, key = \\"\\") {
+ return component(\`Child\`, {}, key + \`__1\`, node, ctx);
+ }
+}"
+`;
+
+exports[`basics don't fallback to component's registry if widget defined in the instance's context 3`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ return function template(ctx, node, key = \\"\\") {
+ return component(\`Child\`, {}, key + \`__1\`, node, ctx);
+ }
+}"
+`;
+
exports[`basics has no el after creation 1`] = `
"function anonymous(bdom, helpers
) {
@@ -400,6 +438,45 @@ exports[`basics higher order components parent and child 4`] = `
}"
`;
+exports[`basics list of two sub components inside other nodes 1`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ let block1 = createBlock(\`asdf \`);
+
+ return function template(ctx, node, key = \\"\\") {
+ return block1();
+ }
+}"
+`;
+
+exports[`basics list of two sub components inside other nodes 2`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ let block1 = createBlock(\`
\`);
+ let block3 = createBlock(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ ctx = Object.create(ctx);
+ const [k2, v2, l2, c2] = prepareList(ctx['state'].blips);
+ for (let i1 = 0; i1 < l2; i1++) {
+ ctx[\`blip\`] = v2[i1];
+ let key1 = ctx['blip'].id;
+ let b4 = component(\`SubWidget\`, {}, key + \`__1__\${key1}\`, node, ctx);
+ let b5 = component(\`SubWidget\`, {}, key + \`__2__\${key1}\`, node, ctx);
+ c2[i1] = withKey(block3([], [b4, b5]), key1);
+ }
+ let b2 = list(c2);
+ return block1([], [b2]);
+ }
+}"
+`;
+
exports[`basics parent, child and grandchild 1`] = `
"function anonymous(bdom, helpers
) {
@@ -482,6 +559,35 @@ exports[`basics reconciliation alg is not confused in some specific situation 2`
}"
`;
+exports[`basics rerendering a widget with a sub widget 1`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ let block1 = createBlock(\`Inc
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let d1 = ctx['state'].counter;
+ const v1 = ctx['state'];
+ let d2 = (e) => {const res = (() => { return v1.counter++ })(); if (typeof res === 'function') { res(e) }};
+ return block1([d1, d2]);
+ }
+}"
+`;
+
+exports[`basics rerendering a widget with a sub widget 2`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ return function template(ctx, node, key = \\"\\") {
+ return component(\`Counter\`, {}, key + \`__1\`, node, ctx);
+ }
+}"
+`;
+
exports[`basics same t-keys in two different places 1`] = `
"function anonymous(bdom, helpers
) {
@@ -557,6 +663,47 @@ exports[`basics some simple sanity checks (el/status) 1`] = `
}"
`;
+exports[`basics sub components between t-ifs 1`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ let block1 = createBlock(\`child \`);
+
+ return function template(ctx, node, key = \\"\\") {
+ return block1();
+ }
+}"
+`;
+
+exports[`basics sub components between t-ifs 2`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ let block1 = createBlock(\`
\`);
+ let block2 = createBlock(\`hey \`);
+ let block3 = createBlock(\`noo \`);
+ let block5 = createBlock(\`test \`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let b2,b3,b4,b5;
+ if (ctx['state'].flag) {
+ b2 = block2();
+ } else {
+ b3 = block3();
+ }
+ b4 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
+ if (ctx['state'].flag) {
+ b5 = block5();
+ }
+ return block1([], [b2, b3, b4, b5]);
+ }
+}"
+`;
+
exports[`basics t-elif works with t-component 1`] = `
"function anonymous(bdom, helpers
) {
diff --git a/tests/components/__snapshots__/lifecycle.test.ts.snap b/tests/components/__snapshots__/lifecycle.test.ts.snap
index 582b8181..18adf0c3 100644
--- a/tests/components/__snapshots__/lifecycle.test.ts.snap
+++ b/tests/components/__snapshots__/lifecycle.test.ts.snap
@@ -479,6 +479,36 @@ exports[`lifecycle hooks patched hook is called after updating State 1`] = `
}"
`;
+exports[`lifecycle hooks sub widget (inside sub node): hooks are correctly called 1`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ let block1 = createBlock(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ return block1();
+ }
+}"
+`;
+
+exports[`lifecycle hooks sub widget (inside sub node): hooks are correctly called 2`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ return function template(ctx, node, key = \\"\\") {
+ let b2;
+ if (ctx['state'].flag) {
+ b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
+ }
+ return multi([b2]);
+ }
+}"
+`;
+
exports[`lifecycle hooks willPatch, patched hook are called on subsubcomponents, in proper order 1`] = `
"function anonymous(bdom, helpers
) {
diff --git a/tests/components/__snapshots__/t_component.test.ts.snap b/tests/components/__snapshots__/t_component.test.ts.snap
index 4cd15b9f..f2ee0124 100644
--- a/tests/components/__snapshots__/t_component.test.ts.snap
+++ b/tests/components/__snapshots__/t_component.test.ts.snap
@@ -198,6 +198,36 @@ exports[`t-component switching dynamic component 3`] = `
}"
`;
+exports[`t-component t-component not on a node 1`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ let block1 = createBlock(\`1 \`);
+
+ return function template(ctx, node, key = \\"\\") {
+ return block1();
+ }
+}"
+`;
+
+exports[`t-component t-component not on a node 2`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ let block1 = createBlock(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ let Comp2 = ctx['Child'];
+ let b2 = toggler(Comp2, component(Comp2, {}, key + \`__1\`, node, ctx));
+ return block1([], [b2]);
+ }
+}"
+`;
+
exports[`t-component t-component works in simple case 1`] = `
"function anonymous(bdom, helpers
) {
diff --git a/tests/components/__snapshots__/t_foreach.test.ts.snap b/tests/components/__snapshots__/t_foreach.test.ts.snap
index c8072b57..edff6eba 100644
--- a/tests/components/__snapshots__/t_foreach.test.ts.snap
+++ b/tests/components/__snapshots__/t_foreach.test.ts.snap
@@ -39,6 +39,44 @@ exports[`list of components components in a node in a t-foreach 2`] = `
}"
`;
+exports[`list of components list of sub components inside other nodes 1`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ let block1 = createBlock(\`asdf \`);
+
+ return function template(ctx, node, key = \\"\\") {
+ return block1();
+ }
+}"
+`;
+
+exports[`list of components list of sub components inside other nodes 2`] = `
+"function anonymous(bdom, helpers
+) {
+ let { text, createBlock, list, multi, html, toggler, component } = bdom;
+ let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, shallowEqual } = helpers;
+
+ let block1 = createBlock(\`
\`);
+ let block3 = createBlock(\`
\`);
+
+ return function template(ctx, node, key = \\"\\") {
+ ctx = Object.create(ctx);
+ const [k2, v2, l2, c2] = prepareList(ctx['state'].blips);
+ for (let i1 = 0; i1 < l2; i1++) {
+ ctx[\`blip\`] = v2[i1];
+ let key1 = ctx['blip'].id;
+ let b4 = component(\`SubComponent\`, {}, key + \`__1__\${key1}\`, node, ctx);
+ c2[i1] = withKey(block3([], [b4]), key1);
+ }
+ let b2 = list(c2);
+ return block1([], [b2]);
+ }
+}"
+`;
+
exports[`list of components reconciliation alg works for t-foreach in t-foreach 1`] = `
"function anonymous(bdom, helpers
) {
diff --git a/tests/components/basics.test.ts b/tests/components/basics.test.ts
index 2b8ebd99..4a86ad4e 100644
--- a/tests/components/basics.test.ts
+++ b/tests/components/basics.test.ts
@@ -383,6 +383,27 @@ describe("basics", () => {
await nextTick();
expect(fixture.innerHTML).toBe("1Inc
");
});
+ // TODO: rename
+ test("rerendering a widget with a sub widget", async () => {
+ class Counter extends Component {
+ static template = xml`
+ Inc
`;
+ state = useState({
+ counter: 0,
+ });
+ }
+ class Parent extends Component {
+ static template = xml` `;
+ static components = { Counter };
+ }
+ const parent = await mount(Parent, fixture);
+ const button = fixture.getElementsByTagName("button")[0];
+ button.click();
+ await nextTick();
+ expect(fixture.innerHTML).toBe("1Inc
");
+ await parent.render();
+ expect(fixture.innerHTML).toBe("1Inc
");
+ });
test("can handle empty props", async () => {
class Child extends Component {
@@ -654,4 +675,68 @@ describe("basics", () => {
await nextTick();
expect(fixture.innerHTML).toBe("hey
");
});
+
+ // TODO: implement scope lookup for components
+ test.skip("don't fallback to component's registry if widget defined in the instance's context", async () => {
+ class ChildA extends Component {
+ static template = xml`ChildA `;
+ }
+ class ChildB extends Component {
+ static template = xml`ChildB `;
+ }
+ class Parent extends Component {
+ static template = xml` `;
+ static components = { Child: ChildA };
+ Child = ChildB;
+ }
+ await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe("ChildB ");
+ });
+
+ test("sub components between t-ifs", async () => {
+ // this confuses the patching algorithm...
+ class Child extends Component {
+ static template = xml`child `;
+ }
+
+ class Parent extends Component {
+ static template = xml`
+
+
hey
+ noo
+
+ test
+ `;
+ static components = { Child };
+ state = useState({ flag: false });
+ }
+ const parent = await mount(Parent, fixture);
+ const child = Object.values(parent.__owl__.children)[0].component;
+ expect(fixture.innerHTML).toBe(`
noo child `);
+
+ parent.state.flag = true;
+ await nextTick();
+ expect(Object.values(parent.__owl__.children)[0].component).toBe(child);
+ expect(status(child)).toBe("mounted");
+ expect(fixture.innerHTML).toBe(`
hey child test `)
+ });
+
+ test("list of two sub components inside other nodes", async () => {
+ class SubWidget extends Component {
+ static template = xml`asdf `;
+ }
+ class Parent extends Component {
+ static template = xml`
+ `;
+ static components = { SubWidget };
+ state = useState({ blips: [{ a: "a", id: 1 }] });
+ }
+ await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe("");
+ });
});
diff --git a/tests/components/error_handling.test.ts b/tests/components/error_handling.test.ts
index 02320f42..327af00a 100644
--- a/tests/components/error_handling.test.ts
+++ b/tests/components/error_handling.test.ts
@@ -39,4 +39,25 @@ describe("basics", () => {
/Cannot read properties of undefined \(reading 'this'\)|Cannot read property 'this' of undefined/g;
expect(error.message).toMatch(regexp);
});
+
+ test.skip("display a nice error if it cannot find component", async () => {
+ const consoleError = console.error;
+ console.error = jest.fn();
+
+ class SomeComponent extends Component {}
+ class Parent extends Component {
+ static template = xml` `;
+ static components = { SomeComponent };
+ }
+ let error;
+ try {
+ await mount(Parent, fixture);
+ } catch (e) {
+ error = e;
+ }
+ expect(error).toBeDefined();
+ expect(error.message).toBe('Cannot find the definition of component "SomeMispelledWidget"');
+ expect(console.error).toBeCalledTimes(0);
+ console.error = consoleError;
+ });
});
diff --git a/tests/components/lifecycle.test.ts b/tests/components/lifecycle.test.ts
index c91bca52..796a82ca 100644
--- a/tests/components/lifecycle.test.ts
+++ b/tests/components/lifecycle.test.ts
@@ -909,4 +909,31 @@ describe("lifecycle hooks", () => {
]);
Object.freeze(steps);
});
+
+ // TODO: rename (remove? seems covered by lifecycle semantics)
+ test("sub widget (inside sub node): hooks are correctly called", async () => {
+ let created = false;
+ let mounted = false;
+
+ class Child extends Component {
+ static template = xml`
`;
+ setup() {
+ created = true;
+ onMounted(() => { mounted = true; })
+ }
+ }
+ class Parent extends Component {
+ static template = xml` `;
+ static components = { Child };
+ state = useState({ flag: false });
+ }
+ const parent = await mount(Parent, fixture);
+ expect(created).toBe(false);
+ expect(mounted).toBe(false);
+
+ parent.state.flag = true;
+ await nextTick();
+ expect(mounted).toBe(true);
+ expect(created).toBe(true);
+ });
});
diff --git a/tests/components/t_component.test.ts b/tests/components/t_component.test.ts
index b959a855..e6e364e4 100644
--- a/tests/components/t_component.test.ts
+++ b/tests/components/t_component.test.ts
@@ -190,4 +190,24 @@ describe("t-component", () => {
await nextTick();
expect(fixture.innerHTML).toBe("");
});
+
+ test.skip("t-component not on a node", async () => {
+ class Child extends Component {
+ static template = xml`1 `;
+ }
+ class Parent extends Component {
+ static components = { Child };
+ static template = xml``;
+ }
+ let error;
+ try {
+ await mount(Parent, fixture);
+ } catch (e) {
+ error = e;
+ }
+ expect(error).toBeDefined();
+ expect(error.message).toBe(
+ `Directive 't-component' can only be used on nodes (used on a )`
+ );
+ });
});
diff --git a/tests/components/t_foreach.test.ts b/tests/components/t_foreach.test.ts
index 7445168b..44166c5a 100644
--- a/tests/components/t_foreach.test.ts
+++ b/tests/components/t_foreach.test.ts
@@ -197,4 +197,36 @@ describe("list of components", () => {
await nextTick();
expect(fixture.innerHTML).toBe(`
`);
});
+
+ test("list of sub components inside other nodes", async () => {
+ // this confuses the patching algorithm...
+ class SubComponent extends Component {
+ static template = xml`
asdf `;
+ }
+ class Parent extends Component {
+ static template = xml`
+
`;
+ static components = { SubComponent };
+ state = useState({
+ blips: [
+ { a: "a", id: 1 },
+ { b: "b", id: 2 },
+ { c: "c", id: 4 },
+ ],
+ });
+ }
+ const parent = await mount(Parent, fixture);
+ expect(fixture.innerHTML).toBe(
+ "
"
+ );
+ parent.state.blips.splice(0, 1);
+ await nextTick();
+ expect(fixture.innerHTML).toBe(
+ "
"
+ );
+ });
});