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(\`
\`); + + 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("
1
"); }); + // TODO: rename + test("rerendering a widget with a sub widget", async () => { + class Counter extends Component { + static template = xml` +
`; + 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("
1
"); + await parent.render(); + expect(fixture.innerHTML).toBe("
1
"); + }); 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

childtest
`) + }); + + 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("
asdfasdf
"); + }); }); 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("
1
"); }); + + 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(`

1

3

`); }); + + 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( + "
asdf
asdf
asdf
" + ); + parent.state.blips.splice(0, 1); + await nextTick(); + expect(fixture.innerHTML).toBe( + "
asdf
asdf
" + ); + }); });