From 9921ae07b1749602f106105fb9ce98b9812ca3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Wed, 11 Dec 2019 11:06:09 +0100 Subject: [PATCH] [REF] tests: move slots tests out of main component file --- .../__snapshots__/component.test.ts.snap | 487 ---------- .../__snapshots__/slots.test.ts.snap | 488 ++++++++++ tests/component/component.test.ts | 839 ----------------- tests/component/slots.test.ts | 887 ++++++++++++++++++ 4 files changed, 1375 insertions(+), 1326 deletions(-) create mode 100644 tests/component/__snapshots__/slots.test.ts.snap create mode 100644 tests/component/slots.test.ts diff --git a/tests/component/__snapshots__/component.test.ts.snap b/tests/component/__snapshots__/component.test.ts.snap index 859fb0e5..1060aa71 100644 --- a/tests/component/__snapshots__/component.test.ts.snap +++ b/tests/component/__snapshots__/component.test.ts.snap @@ -1291,493 +1291,6 @@ exports[`t-model directive on an input, type=checkbox 1`] = ` }" `; -exports[`t-slot directive can define and call slots 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"Parent\\" - let utils = this.constructor.utils; - let QWeb = this.constructor; - let parent = context; - let scope = Object.create(context); - var h = this.h; - let c1 = [], p1 = {key:1}; - var vn1 = h('div', p1, c1); - //COMPONENT - let k3 = \`__4__\`; - let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : false; - let props2 = {}; - if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) { - w2.destroy(); - w2 = false; - } - if (w2) { - w2.__updateProps(props2, extra.fiber, Object.assign(Object.create(context), scope)); - let pvnode = w2.__owl__.pvnode; - c1.push(pvnode); - } else { - let componentKey2 = \`Dialog\`; - let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Dialog']; - if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} - w2 = new W2(parent, props2); - parent.__owl__.cmap[k3] = w2.__owl__.id; - w2.__owl__.slotId = 1; - let fiber = w2.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); - let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}}); - c1.push(pvnode); - w2.__owl__.pvnode = pvnode; - } - w2.__owl__.parentLastFiberId = extra.fiber.id; - return vn1; -}" -`; - -exports[`t-slot directive can define and call slots 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"Dialog\\" - var h = this.h; - let c7 = [], p7 = {key:7}; - var vn7 = h('div', p7, c7); - let c8 = [], p8 = {key:8}; - var vn8 = h('div', p8, c8); - c7.push(vn8); - const slot9 = this.constructor.slots[context.__owl__.slotId + '_' + 'header']; - if (slot9) { - slot9.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c8, parent: extra.parent || context})); - } - let c10 = [], p10 = {key:10}; - var vn10 = h('div', p10, c10); - c7.push(vn10); - const slot11 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer']; - if (slot11) { - slot11.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c10, parent: extra.parent || context})); - } - return vn7; -}" -`; - -exports[`t-slot directive can define and call slots 3`] = ` -"function anonymous(context, extra -) { - // Template name: \\"slot_header_template\\" - var h = this.h; - let c1 = extra.parentNode; - let c5 = [], p5 = {key:5}; - var vn5 = h('span', p5, c5); - c1.push(vn5); - c5.push({text: \`header\`}); -}" -`; - -exports[`t-slot directive can define and call slots 4`] = ` -"function anonymous(context, extra -) { - // Template name: \\"slot_footer_template\\" - var h = this.h; - let c1 = extra.parentNode; - let c6 = [], p6 = {key:6}; - var vn6 = h('span', p6, c6); - c1.push(vn6); - c6.push({text: \`footer\`}); -}" -`; - -exports[`t-slot directive content is the default slot 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"slot_default_template\\" - var h = this.h; - let c1 = extra.parentNode; - let c5 = [], p5 = {key:5}; - var vn5 = h('span', p5, c5); - c1.push(vn5); - c5.push({text: \`sts rocks\`}); -}" -`; - -exports[`t-slot directive dafault slots can define a default content 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"__template__1\\" - var h = this.h; - let c5 = [], p5 = {key:5}; - var vn5 = h('span', p5, c5); - const slot6 = this.constructor.slots[context.__owl__.slotId + '_' + 'default']; - if (slot6) { - slot6.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c5, parent: extra.parent || context})); - } else { - c5.push({text: \`default content\`}); - } - return vn5; -}" -`; - -exports[`t-slot directive default slot work with text nodes 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"slot_default_template\\" - var h = this.h; - let c1 = extra.parentNode; - c1.push({text: \`sts rocks\`}); -}" -`; - -exports[`t-slot directive multiple roots are allowed in a default slot 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"slot_default_template\\" - var h = this.h; - let c1 = extra.parentNode; - let c5 = [], p5 = {key:5}; - var vn5 = h('span', p5, c5); - c1.push(vn5); - c5.push({text: \`sts\`}); - let c6 = [], p6 = {key:6}; - var vn6 = h('span', p6, c6); - c1.push(vn6); - c6.push({text: \`rocks\`}); -}" -`; - -exports[`t-slot directive multiple roots are allowed in a named slot 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"slot_content_template\\" - var h = this.h; - let c1 = extra.parentNode; - let c5 = [], p5 = {key:5}; - var vn5 = h('span', p5, c5); - c1.push(vn5); - c5.push({text: \`sts\`}); - let c6 = [], p6 = {key:6}; - var vn6 = h('span', p6, c6); - c1.push(vn6); - c6.push({text: \`rocks\`}); -}" -`; - -exports[`t-slot directive named slots can define a default content 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"__template__1\\" - var h = this.h; - let c5 = [], p5 = {key:5}; - var vn5 = h('span', p5, c5); - const slot6 = this.constructor.slots[context.__owl__.slotId + '_' + 'header']; - if (slot6) { - slot6.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c5, parent: extra.parent || context})); - } else { - c5.push({text: \`default content\`}); - } - return vn5; -}" -`; - -exports[`t-slot directive refs are properly bound in slots 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"slot_footer_template\\" - context.__owl__.refs = context.__owl__.refs || {}; - var h = this.h; - let c1 = extra.parentNode; - let c9 = [], p9 = {key:9,on:{}}; - var vn9 = h('button', p9, c9); - c1.push(vn9); - extra.handlers['click' + 9] = extra.handlers['click' + 9] || function (e) {if (!context.__owl__.isMounted){return}const fn = context['doSomething'];if (fn) { fn.call(context, e); } else { context.doSomething; }}; - p9.on['click'] = extra.handlers['click' + 9]; - const ref10 = \`myButton\`; - p9.hook = { - create: (_, n) => { - context.__owl__.refs[ref10] = n.elm; - }, - destroy: () => { - delete context.__owl__.refs[ref10]; - }, - }; - c9.push({text: \`do something\`}); -}" -`; - -exports[`t-slot directive slots are rendered with proper context 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"slot_footer_template\\" - var h = this.h; - let c1 = extra.parentNode; - let c9 = [], p9 = {key:9,on:{}}; - var vn9 = h('button', p9, c9); - c1.push(vn9); - extra.handlers['click' + 9] = extra.handlers['click' + 9] || function (e) {if (!context.__owl__.isMounted){return}const fn = context['doSomething'];if (fn) { fn.call(context, e); } else { context.doSomething; }}; - p9.on['click'] = extra.handlers['click' + 9]; - c9.push({text: \`do something\`}); -}" -`; - -exports[`t-slot directive slots are rendered with proper context, part 2 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"Link\\" - let scope = Object.create(context); - var h = this.h; - var _13 = scope['props'].to; - let c14 = [], p14 = {key:14,attrs:{href: _13}}; - var vn14 = h('a', p14, c14); - const slot15 = this.constructor.slots[context.__owl__.slotId + '_' + 'default']; - if (slot15) { - slot15.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c14, parent: extra.parent || context})); - } - return vn14; -}" -`; - -exports[`t-slot directive slots are rendered with proper context, part 2 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"App\\" - let utils = this.constructor.utils; - let QWeb = this.constructor; - let parent = context; - let scope = Object.create(context); - var h = this.h; - let c1 = [], p1 = {key:1}; - var vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2}; - var vn2 = h('u', p2, c2); - c1.push(vn2); - var _3 = scope['state'].users; - if (!_3) { throw new Error('QWeb error: Invalid loop expression')} - var _4 = _5 = _3; - if (!(_3 instanceof Array)) { - _4 = Object.keys(_3); - _5 = Object.values(_3); - } - var _length4 = _4.length; - const _origScope6 = scope; - scope = Object.assign(Object.create(context), scope); - for (let i1 = 0; i1 < _length4; i1++) { - scope.user_first = i1 === 0 - scope.user_last = i1 === _length4 - 1 - scope.user_index = i1 - scope.user = _4[i1] - scope.user_value = _5[i1] - const nodeKey7 = scope['user'].id; - let c8 = [], p8 = {key:nodeKey7}; - var vn8 = h('li', p8, c8); - c2.push(vn8); - //COMPONENT - let k10 = \`__11__\` + nodeKey7; - let w9 = k10 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k10]] : false; - let props9 = {to:'/user/'+scope['user'].id}; - if (w9 && w9.__owl__.currentFiber && !w9.__owl__.vnode) { - w9.destroy(); - w9 = false; - } - if (w9) { - w9.__updateProps(props9, extra.fiber, Object.assign(Object.create(context), scope)); - let pvnode = w9.__owl__.pvnode; - c8.push(pvnode); - } else { - let componentKey9 = \`Link\`; - let W9 = context.constructor.components[componentKey9] || QWeb.components[componentKey9]|| scope['Link']; - if (!W9) {throw new Error('Cannot find the definition of component \\"' + componentKey9 + '\\"')} - w9 = new W9(parent, props9); - parent.__owl__.cmap[k10] = w9.__owl__.id; - w9.__owl__.slotId = 1; - let fiber = w9.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); - let pvnode = h('dummy', {key: k10, hook: {remove() {},destroy(vn) {w9.destroy();}}}); - c8.push(pvnode); - w9.__owl__.pvnode = pvnode; - } - w9.__owl__.parentLastFiberId = extra.fiber.id; - } - scope = _origScope6; - return vn1; -}" -`; - -exports[`t-slot directive slots are rendered with proper context, part 2 3`] = ` -"function anonymous(context, extra -) { - // Template name: \\"slot_default_template\\" - let scope = Object.create(context); - var h = this.h; - let c8 = extra.parentNode; - c8.push({text: \`User \`}); - var _12 = scope['user'].name; - if (_12 || _12 === 0) { - c8.push({text: _12}); - } -}" -`; - -exports[`t-slot directive slots are rendered with proper context, part 3 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"Link\\" - let scope = Object.create(context); - var h = this.h; - var _12 = scope['props'].to; - let c13 = [], p13 = {key:13,attrs:{href: _12}}; - var vn13 = h('a', p13, c13); - const slot14 = this.constructor.slots[context.__owl__.slotId + '_' + 'default']; - if (slot14) { - slot14.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c13, parent: extra.parent || context})); - } - return vn13; -}" -`; - -exports[`t-slot directive slots are rendered with proper context, part 3 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"App\\" - let utils = this.constructor.utils; - let QWeb = this.constructor; - let parent = context; - let scope = Object.create(context); - var h = this.h; - let c1 = [], p1 = {key:1}; - var vn1 = h('div', p1, c1); - let c2 = [], p2 = {key:2}; - var vn2 = h('u', p2, c2); - c1.push(vn2); - var _3 = scope['state'].users; - if (!_3) { throw new Error('QWeb error: Invalid loop expression')} - var _4 = _5 = _3; - if (!(_3 instanceof Array)) { - _4 = Object.keys(_3); - _5 = Object.values(_3); - } - var _length4 = _4.length; - const _origScope6 = scope; - scope = Object.assign(Object.create(context), scope); - for (let i1 = 0; i1 < _length4; i1++) { - scope.user_first = i1 === 0 - scope.user_last = i1 === _length4 - 1 - scope.user_index = i1 - scope.user = _4[i1] - scope.user_value = _5[i1] - const nodeKey7 = scope['user'].id; - let c8 = [], p8 = {key:nodeKey7}; - var vn8 = h('li', p8, c8); - c2.push(vn8); - scope.userdescr = 'User '+scope['user'].name; - //COMPONENT - let k10 = \`__11__\` + nodeKey7; - let w9 = k10 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k10]] : false; - let props9 = {to:'/user/'+scope['user'].id}; - if (w9 && w9.__owl__.currentFiber && !w9.__owl__.vnode) { - w9.destroy(); - w9 = false; - } - if (w9) { - w9.__updateProps(props9, extra.fiber, Object.assign(Object.create(context), scope)); - let pvnode = w9.__owl__.pvnode; - c8.push(pvnode); - } else { - let componentKey9 = \`Link\`; - let W9 = context.constructor.components[componentKey9] || QWeb.components[componentKey9]|| scope['Link']; - if (!W9) {throw new Error('Cannot find the definition of component \\"' + componentKey9 + '\\"')} - w9 = new W9(parent, props9); - parent.__owl__.cmap[k10] = w9.__owl__.id; - w9.__owl__.slotId = 1; - let fiber = w9.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); - let pvnode = h('dummy', {key: k10, hook: {remove() {},destroy(vn) {w9.destroy();}}}); - c8.push(pvnode); - w9.__owl__.pvnode = pvnode; - } - w9.__owl__.parentLastFiberId = extra.fiber.id; - } - scope = _origScope6; - return vn1; -}" -`; - -exports[`t-slot directive slots are rendered with proper context, part 3 3`] = ` -"function anonymous(context, extra -) { - // Template name: \\"slot_default_template\\" - let scope = Object.create(context); - var h = this.h; - let c8 = extra.parentNode; - if (scope.userdescr || scope.userdescr === 0) { - c8.push({text: scope.userdescr}); - } -}" -`; - -exports[`t-slot directive slots are rendered with proper context, part 4 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"App\\" - let utils = this.constructor.utils; - let QWeb = this.constructor; - let parent = context; - let scope = Object.create(context); - var h = this.h; - let c1 = [], p1 = {key:1}; - var vn1 = h('div', p1, c1); - scope.userdescr = 'User '+scope['state'].user.name; - //COMPONENT - let k3 = \`__4__\`; - let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : false; - let props2 = {to:'/user/'+scope['state'].user.id}; - if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) { - w2.destroy(); - w2 = false; - } - if (w2) { - w2.__updateProps(props2, extra.fiber, Object.assign(Object.create(context), scope)); - let pvnode = w2.__owl__.pvnode; - c1.push(pvnode); - } else { - let componentKey2 = \`Link\`; - let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Link']; - if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} - w2 = new W2(parent, props2); - parent.__owl__.cmap[k3] = w2.__owl__.id; - w2.__owl__.slotId = 1; - let fiber = w2.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); - let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}}); - c1.push(pvnode); - w2.__owl__.pvnode = pvnode; - } - w2.__owl__.parentLastFiberId = extra.fiber.id; - return vn1; -}" -`; - -exports[`t-slot directive slots are rendered with proper context, part 4 2`] = ` -"function anonymous(context, extra -) { - // Template name: \\"slot_default_template\\" - let scope = Object.create(context); - var h = this.h; - let c1 = extra.parentNode; - if (scope.userdescr || scope.userdescr === 0) { - c1.push({text: scope.userdescr}); - } -}" -`; - -exports[`t-slot directive template can just return a slot 1`] = ` -"function anonymous(context, extra -) { - // Template name: \\"__template__2\\" - let utils = this.constructor.utils; - let result; - var h = this.h; - const slot8 = this.constructor.slots[context.__owl__.slotId + '_' + 'default']; - if (slot8) { - let children9= [] - result = {} - slot8.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: children9, parent: extra.parent || context})); - utils.defineProxy(result, children9[0]); - } - return result; -}" -`; - exports[`top level sub widgets basic use 1`] = ` "function anonymous(context, extra ) { diff --git a/tests/component/__snapshots__/slots.test.ts.snap b/tests/component/__snapshots__/slots.test.ts.snap new file mode 100644 index 00000000..8a14014d --- /dev/null +++ b/tests/component/__snapshots__/slots.test.ts.snap @@ -0,0 +1,488 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`t-slot directive can define and call slots 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"Parent\\" + let utils = this.constructor.utils; + let QWeb = this.constructor; + let parent = context; + let scope = Object.create(context); + var h = this.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + //COMPONENT + let k3 = \`__4__\`; + let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : false; + let props2 = {}; + if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) { + w2.destroy(); + w2 = false; + } + if (w2) { + w2.__updateProps(props2, extra.fiber, Object.assign(Object.create(context), scope)); + let pvnode = w2.__owl__.pvnode; + c1.push(pvnode); + } else { + let componentKey2 = \`Dialog\`; + let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Dialog']; + if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} + w2 = new W2(parent, props2); + parent.__owl__.cmap[k3] = w2.__owl__.id; + w2.__owl__.slotId = 1; + let fiber = w2.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}}); + c1.push(pvnode); + w2.__owl__.pvnode = pvnode; + } + w2.__owl__.parentLastFiberId = extra.fiber.id; + return vn1; +}" +`; + +exports[`t-slot directive can define and call slots 2`] = ` +"function anonymous(context, extra +) { + // Template name: \\"Dialog\\" + var h = this.h; + let c7 = [], p7 = {key:7}; + var vn7 = h('div', p7, c7); + let c8 = [], p8 = {key:8}; + var vn8 = h('div', p8, c8); + c7.push(vn8); + const slot9 = this.constructor.slots[context.__owl__.slotId + '_' + 'header']; + if (slot9) { + slot9.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c8, parent: extra.parent || context})); + } + let c10 = [], p10 = {key:10}; + var vn10 = h('div', p10, c10); + c7.push(vn10); + const slot11 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer']; + if (slot11) { + slot11.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c10, parent: extra.parent || context})); + } + return vn7; +}" +`; + +exports[`t-slot directive can define and call slots 3`] = ` +"function anonymous(context, extra +) { + // Template name: \\"slot_header_template\\" + var h = this.h; + let c1 = extra.parentNode; + let c5 = [], p5 = {key:5}; + var vn5 = h('span', p5, c5); + c1.push(vn5); + c5.push({text: \`header\`}); +}" +`; + +exports[`t-slot directive can define and call slots 4`] = ` +"function anonymous(context, extra +) { + // Template name: \\"slot_footer_template\\" + var h = this.h; + let c1 = extra.parentNode; + let c6 = [], p6 = {key:6}; + var vn6 = h('span', p6, c6); + c1.push(vn6); + c6.push({text: \`footer\`}); +}" +`; + +exports[`t-slot directive content is the default slot 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"slot_default_template\\" + var h = this.h; + let c1 = extra.parentNode; + let c5 = [], p5 = {key:5}; + var vn5 = h('span', p5, c5); + c1.push(vn5); + c5.push({text: \`sts rocks\`}); +}" +`; + +exports[`t-slot directive dafault slots can define a default content 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"__template__1\\" + var h = this.h; + let c5 = [], p5 = {key:5}; + var vn5 = h('span', p5, c5); + const slot6 = this.constructor.slots[context.__owl__.slotId + '_' + 'default']; + if (slot6) { + slot6.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c5, parent: extra.parent || context})); + } else { + c5.push({text: \`default content\`}); + } + return vn5; +}" +`; + +exports[`t-slot directive default slot work with text nodes 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"slot_default_template\\" + var h = this.h; + let c1 = extra.parentNode; + c1.push({text: \`sts rocks\`}); +}" +`; + +exports[`t-slot directive multiple roots are allowed in a default slot 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"slot_default_template\\" + var h = this.h; + let c1 = extra.parentNode; + let c5 = [], p5 = {key:5}; + var vn5 = h('span', p5, c5); + c1.push(vn5); + c5.push({text: \`sts\`}); + let c6 = [], p6 = {key:6}; + var vn6 = h('span', p6, c6); + c1.push(vn6); + c6.push({text: \`rocks\`}); +}" +`; + +exports[`t-slot directive multiple roots are allowed in a named slot 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"slot_content_template\\" + var h = this.h; + let c1 = extra.parentNode; + let c5 = [], p5 = {key:5}; + var vn5 = h('span', p5, c5); + c1.push(vn5); + c5.push({text: \`sts\`}); + let c6 = [], p6 = {key:6}; + var vn6 = h('span', p6, c6); + c1.push(vn6); + c6.push({text: \`rocks\`}); +}" +`; + +exports[`t-slot directive named slots can define a default content 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"__template__1\\" + var h = this.h; + let c5 = [], p5 = {key:5}; + var vn5 = h('span', p5, c5); + const slot6 = this.constructor.slots[context.__owl__.slotId + '_' + 'header']; + if (slot6) { + slot6.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c5, parent: extra.parent || context})); + } else { + c5.push({text: \`default content\`}); + } + return vn5; +}" +`; + +exports[`t-slot directive refs are properly bound in slots 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"slot_footer_template\\" + context.__owl__.refs = context.__owl__.refs || {}; + var h = this.h; + let c1 = extra.parentNode; + let c9 = [], p9 = {key:9,on:{}}; + var vn9 = h('button', p9, c9); + c1.push(vn9); + extra.handlers['click' + 9] = extra.handlers['click' + 9] || function (e) {if (!context.__owl__.isMounted){return}const fn = context['doSomething'];if (fn) { fn.call(context, e); } else { context.doSomething; }}; + p9.on['click'] = extra.handlers['click' + 9]; + const ref10 = \`myButton\`; + p9.hook = { + create: (_, n) => { + context.__owl__.refs[ref10] = n.elm; + }, + destroy: () => { + delete context.__owl__.refs[ref10]; + }, + }; + c9.push({text: \`do something\`}); +}" +`; + +exports[`t-slot directive slots are rendered with proper context 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"slot_footer_template\\" + var h = this.h; + let c1 = extra.parentNode; + let c9 = [], p9 = {key:9,on:{}}; + var vn9 = h('button', p9, c9); + c1.push(vn9); + extra.handlers['click' + 9] = extra.handlers['click' + 9] || function (e) {if (!context.__owl__.isMounted){return}const fn = context['doSomething'];if (fn) { fn.call(context, e); } else { context.doSomething; }}; + p9.on['click'] = extra.handlers['click' + 9]; + c9.push({text: \`do something\`}); +}" +`; + +exports[`t-slot directive slots are rendered with proper context, part 2 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"Link\\" + let scope = Object.create(context); + var h = this.h; + var _13 = scope['props'].to; + let c14 = [], p14 = {key:14,attrs:{href: _13}}; + var vn14 = h('a', p14, c14); + const slot15 = this.constructor.slots[context.__owl__.slotId + '_' + 'default']; + if (slot15) { + slot15.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c14, parent: extra.parent || context})); + } + return vn14; +}" +`; + +exports[`t-slot directive slots are rendered with proper context, part 2 2`] = ` +"function anonymous(context, extra +) { + // Template name: \\"App\\" + let utils = this.constructor.utils; + let QWeb = this.constructor; + let parent = context; + let scope = Object.create(context); + var h = this.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + let c2 = [], p2 = {key:2}; + var vn2 = h('u', p2, c2); + c1.push(vn2); + var _3 = scope['state'].users; + if (!_3) { throw new Error('QWeb error: Invalid loop expression')} + var _4 = _5 = _3; + if (!(_3 instanceof Array)) { + _4 = Object.keys(_3); + _5 = Object.values(_3); + } + var _length4 = _4.length; + const _origScope6 = scope; + scope = Object.assign(Object.create(context), scope); + for (let i1 = 0; i1 < _length4; i1++) { + scope.user_first = i1 === 0 + scope.user_last = i1 === _length4 - 1 + scope.user_index = i1 + scope.user = _4[i1] + scope.user_value = _5[i1] + const nodeKey7 = scope['user'].id; + let c8 = [], p8 = {key:nodeKey7}; + var vn8 = h('li', p8, c8); + c2.push(vn8); + //COMPONENT + let k10 = \`__11__\` + nodeKey7; + let w9 = k10 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k10]] : false; + let props9 = {to:'/user/'+scope['user'].id}; + if (w9 && w9.__owl__.currentFiber && !w9.__owl__.vnode) { + w9.destroy(); + w9 = false; + } + if (w9) { + w9.__updateProps(props9, extra.fiber, Object.assign(Object.create(context), scope)); + let pvnode = w9.__owl__.pvnode; + c8.push(pvnode); + } else { + let componentKey9 = \`Link\`; + let W9 = context.constructor.components[componentKey9] || QWeb.components[componentKey9]|| scope['Link']; + if (!W9) {throw new Error('Cannot find the definition of component \\"' + componentKey9 + '\\"')} + w9 = new W9(parent, props9); + parent.__owl__.cmap[k10] = w9.__owl__.id; + w9.__owl__.slotId = 1; + let fiber = w9.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + let pvnode = h('dummy', {key: k10, hook: {remove() {},destroy(vn) {w9.destroy();}}}); + c8.push(pvnode); + w9.__owl__.pvnode = pvnode; + } + w9.__owl__.parentLastFiberId = extra.fiber.id; + } + scope = _origScope6; + return vn1; +}" +`; + +exports[`t-slot directive slots are rendered with proper context, part 2 3`] = ` +"function anonymous(context, extra +) { + // Template name: \\"slot_default_template\\" + let scope = Object.create(context); + var h = this.h; + let c8 = extra.parentNode; + c8.push({text: \`User \`}); + var _12 = scope['user'].name; + if (_12 || _12 === 0) { + c8.push({text: _12}); + } +}" +`; + +exports[`t-slot directive slots are rendered with proper context, part 3 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"Link\\" + let scope = Object.create(context); + var h = this.h; + var _12 = scope['props'].to; + let c13 = [], p13 = {key:13,attrs:{href: _12}}; + var vn13 = h('a', p13, c13); + const slot14 = this.constructor.slots[context.__owl__.slotId + '_' + 'default']; + if (slot14) { + slot14.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c13, parent: extra.parent || context})); + } + return vn13; +}" +`; + +exports[`t-slot directive slots are rendered with proper context, part 3 2`] = ` +"function anonymous(context, extra +) { + // Template name: \\"App\\" + let utils = this.constructor.utils; + let QWeb = this.constructor; + let parent = context; + let scope = Object.create(context); + var h = this.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + let c2 = [], p2 = {key:2}; + var vn2 = h('u', p2, c2); + c1.push(vn2); + var _3 = scope['state'].users; + if (!_3) { throw new Error('QWeb error: Invalid loop expression')} + var _4 = _5 = _3; + if (!(_3 instanceof Array)) { + _4 = Object.keys(_3); + _5 = Object.values(_3); + } + var _length4 = _4.length; + const _origScope6 = scope; + scope = Object.assign(Object.create(context), scope); + for (let i1 = 0; i1 < _length4; i1++) { + scope.user_first = i1 === 0 + scope.user_last = i1 === _length4 - 1 + scope.user_index = i1 + scope.user = _4[i1] + scope.user_value = _5[i1] + const nodeKey7 = scope['user'].id; + let c8 = [], p8 = {key:nodeKey7}; + var vn8 = h('li', p8, c8); + c2.push(vn8); + scope.userdescr = 'User '+scope['user'].name; + //COMPONENT + let k10 = \`__11__\` + nodeKey7; + let w9 = k10 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k10]] : false; + let props9 = {to:'/user/'+scope['user'].id}; + if (w9 && w9.__owl__.currentFiber && !w9.__owl__.vnode) { + w9.destroy(); + w9 = false; + } + if (w9) { + w9.__updateProps(props9, extra.fiber, Object.assign(Object.create(context), scope)); + let pvnode = w9.__owl__.pvnode; + c8.push(pvnode); + } else { + let componentKey9 = \`Link\`; + let W9 = context.constructor.components[componentKey9] || QWeb.components[componentKey9]|| scope['Link']; + if (!W9) {throw new Error('Cannot find the definition of component \\"' + componentKey9 + '\\"')} + w9 = new W9(parent, props9); + parent.__owl__.cmap[k10] = w9.__owl__.id; + w9.__owl__.slotId = 1; + let fiber = w9.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + let pvnode = h('dummy', {key: k10, hook: {remove() {},destroy(vn) {w9.destroy();}}}); + c8.push(pvnode); + w9.__owl__.pvnode = pvnode; + } + w9.__owl__.parentLastFiberId = extra.fiber.id; + } + scope = _origScope6; + return vn1; +}" +`; + +exports[`t-slot directive slots are rendered with proper context, part 3 3`] = ` +"function anonymous(context, extra +) { + // Template name: \\"slot_default_template\\" + let scope = Object.create(context); + var h = this.h; + let c8 = extra.parentNode; + if (scope.userdescr || scope.userdescr === 0) { + c8.push({text: scope.userdescr}); + } +}" +`; + +exports[`t-slot directive slots are rendered with proper context, part 4 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"App\\" + let utils = this.constructor.utils; + let QWeb = this.constructor; + let parent = context; + let scope = Object.create(context); + var h = this.h; + let c1 = [], p1 = {key:1}; + var vn1 = h('div', p1, c1); + scope.userdescr = 'User '+scope['state'].user.name; + //COMPONENT + let k3 = \`__4__\`; + let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : false; + let props2 = {to:'/user/'+scope['state'].user.id}; + if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) { + w2.destroy(); + w2 = false; + } + if (w2) { + w2.__updateProps(props2, extra.fiber, Object.assign(Object.create(context), scope)); + let pvnode = w2.__owl__.pvnode; + c1.push(pvnode); + } else { + let componentKey2 = \`Link\`; + let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Link']; + if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} + w2 = new W2(parent, props2); + parent.__owl__.cmap[k3] = w2.__owl__.id; + w2.__owl__.slotId = 1; + let fiber = w2.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}}); + c1.push(pvnode); + w2.__owl__.pvnode = pvnode; + } + w2.__owl__.parentLastFiberId = extra.fiber.id; + return vn1; +}" +`; + +exports[`t-slot directive slots are rendered with proper context, part 4 2`] = ` +"function anonymous(context, extra +) { + // Template name: \\"slot_default_template\\" + let scope = Object.create(context); + var h = this.h; + let c1 = extra.parentNode; + if (scope.userdescr || scope.userdescr === 0) { + c1.push({text: scope.userdescr}); + } +}" +`; + +exports[`t-slot directive template can just return a slot 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"__template__2\\" + let utils = this.constructor.utils; + let result; + var h = this.h; + const slot8 = this.constructor.slots[context.__owl__.slotId + '_' + 'default']; + if (slot8) { + let children9= [] + result = {} + slot8.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: children9, parent: extra.parent || context})); + utils.defineProxy(result, children9[0]); + } + return result; +}" +`; diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts index 66c8423f..f6df45ce 100644 --- a/tests/component/component.test.ts +++ b/tests/component/component.test.ts @@ -4000,845 +4000,6 @@ describe("can deduce template from name", () => { }); }); -describe("t-slot directive", () => { - test("can define and call slots", async () => { - env.qweb.addTemplates(` - -
- - header - footer - -
-
-
-
-
-
- `); - class Dialog extends Widget {} - class Parent extends Widget { - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe( - "
header
footer
" - ); - expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot(); - expect(env.qweb.templates.Dialog.fn.toString()).toMatchSnapshot(); - expect(QWeb.slots["1_header"].toString()).toMatchSnapshot(); - expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot(); - }); - - test("named slots can define a default content", async () => { - class Dialog extends Component { - static template = xml` - - default content - `; - } - class Parent extends Component { - static template = xml`
`; - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
default content
"); - expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot(); - }); - - test("dafault slots can define a default content", async () => { - class Dialog extends Component { - static template = xml` - - default content - `; - } - class Parent extends Component { - static template = xml`
`; - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
default content
"); - expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot(); - }); - - test("default content is not rendered if slot is provided", async () => { - class Dialog extends Component { - static template = xml` - - default content - `; - } - class Parent extends Component { - static template = xml`
hey
`; - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
hey
"); - }); - - test("default content is not rendered if named slot is provided", async () => { - class Dialog extends Component { - static template = xml` - - default content - `; - } - class Parent extends Component { - static template = xml`
hey
`; - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
hey
"); - }); - - test("slots are rendered with proper context", async () => { - env.qweb.addTemplates(` - -
- - - - -
- -
- `); - class Dialog extends Widget {} - class Parent extends Widget { - static components = { Dialog }; - state = useState({ val: 0 }); - doSomething() { - this.state.val++; - } - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe( - '
0
' - ); - - fixture.querySelector("button")!.click(); - await nextTick(); - - expect(fixture.innerHTML).toBe( - '
1
' - ); - expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot(); - }); - - test("slots are rendered with proper context, part 2", async () => { - env.qweb.addTemplates(` - - - - -
-
  • - User -
  • -
    -
    - `); - class Link extends Widget {} - - class App extends Widget { - state = useState({ - users: [ - { id: 1, name: "Aaron" }, - { id: 2, name: "David" } - ] - }); - static components = { Link }; - } - - const app = new App(); - await app.mount(fixture); - - expect(fixture.innerHTML).toBe( - '' - ); - expect(env.qweb.templates.Link.fn.toString()).toMatchSnapshot(); - expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot(); - - // test updateprops here - app.state.users[1].name = "Mathieu"; - await nextTick(); - expect(fixture.innerHTML).toBe( - '' - ); - expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); - }); - - test("slots are rendered with proper context, part 3", async () => { - env.qweb.addTemplates(` - - - - -
    -
  • - - -
  • -
    -
    - `); - class Link extends Widget {} - - class App extends Widget { - state = useState({ - users: [ - { id: 1, name: "Aaron" }, - { id: 2, name: "David" } - ] - }); - static components = { Link }; - } - - const app = new App(); - await app.mount(fixture); - - expect(fixture.innerHTML).toBe( - '' - ); - expect(env.qweb.templates.Link.fn.toString()).toMatchSnapshot(); - expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot(); - - // test updateprops here - app.state.users[1].name = "Mathieu"; - await nextTick(); - expect(fixture.innerHTML).toBe( - '' - ); - expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); - }); - - test("slots are rendered with proper context, part 4", async () => { - env.qweb.addTemplates(` - - - - -
    - - -
    -
    - `); - class Link extends Widget {} - - class App extends Widget { - state = useState({ user: { id: 1, name: "Aaron" } }); - static components = { Link }; - } - - const app = new App(); - await app.mount(fixture); - - expect(fixture.innerHTML).toBe(''); - - expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot(); - - // test updateprops here - app.state.user.name = "David"; - await nextTick(); - expect(fixture.innerHTML).toBe(''); - expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); - }); - - test("refs are properly bound in slots", async () => { - class Dialog extends Widget { - static template = xml``; - } - class Parent extends Widget { - static template = xml` -
    - - - - -
    - `; - static components = { Dialog }; - state = useState({ val: 0 }); - button = useRef("myButton"); - doSomething() { - this.state.val++; - } - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe( - '
    0
    ' - ); - - parent.button.el!.click(); - await nextTick(); - - expect(fixture.innerHTML).toBe( - '
    1
    ' - ); - expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot(); - }); - - test("content is the default slot", async () => { - env.qweb.addTemplates(` - -
    - - sts rocks - -
    -
    -
    - `); - class Dialog extends Widget {} - class Parent extends Widget { - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
    sts rocks
    "); - expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); - }); - - test("default slot work with text nodes", async () => { - env.qweb.addTemplates(` - -
    - sts rocks -
    -
    -
    - `); - class Dialog extends Widget {} - class Parent extends Widget { - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
    sts rocks
    "); - expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); - }); - - test("multiple roots are allowed in a named slot", async () => { - env.qweb.addTemplates(` - -
    - - - sts - rocks - - -
    -
    -
    - `); - class Dialog extends Widget {} - class Parent extends Widget { - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
    stsrocks
    "); - expect(QWeb.slots["1_content"].toString()).toMatchSnapshot(); - }); - - test("multiple roots are allowed in a default slot", async () => { - env.qweb.addTemplates(` - -
    - - sts - rocks - -
    -
    -
    - `); - class Dialog extends Widget {} - class Parent extends Widget { - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
    stsrocks
    "); - expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); - }); - - test("missing slots are ignored", async () => { - env.qweb.addTemplates(` - -
    - -
    - - - some content - - -
    - `); - class Dialog extends Widget {} - class Parent extends Widget { - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
    some content
    "); - }); - - test("t-debug on a t-set (defining a slot)", async () => { - const consoleLog = console.log; - console.log = jest.fn(); - - env.qweb.addTemplates(` - -
    - abc -
    - - - -
    - `); - class Dialog extends Widget {} - class Parent extends Widget { - static components = { Dialog }; - } - const parent = new Parent(); - await parent.mount(fixture); - expect(console.log).toHaveBeenCalledTimes(0); - console.log = consoleLog; - }); - - test("slot preserves properly parented relationship", async () => { - env.qweb.addTemplates(` - -
    - - - -
    -
    -
    Grand Child
    -
    - `); - class Child extends Widget {} - class GrandChild extends Widget {} - class Parent extends Widget { - static components = { Child, GrandChild }; - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
    Grand Child
    "); - - const parentChildren = children(parent); - expect(parentChildren.length).toBe(1); - expect(parentChildren[0]).toBeInstanceOf(Child); - - const childrenChildren = children(parentChildren[0]); - expect(childrenChildren.length).toBe(1); - expect(childrenChildren[0]).toBeInstanceOf(GrandChild); - }); - - test("nested slots: evaluation context and parented relationship", async () => { - let slot; - class Slot extends Component { - static template = xml``; - constructor(parent, props) { - super(parent, props); - slot = this; - } - } - class GrandChild extends Component { - static template = xml`
    `; - } - class Child extends Component { - static components = { GrandChild }; - static template = xml` - - - `; - } - class Parent extends Component { - static components = { Child, Slot }; - static template = xml``; - state = useState({ val: 3 }); - } - const parent = new Parent(); - await parent.mount(fixture); - - expect(fixture.innerHTML).toBe("
    3
    "); - expect(slot.__owl__.parent).toBeInstanceOf(GrandChild); - }); - - test("slot are properly rendered if inner props are changed", async () => { - env.qweb.addTemplates(` - -
    - SC: -
    - - -
    - -
    - -
    - - - - -
    -
    - `); - class SomeComponent extends Widget {} - class GenericComponent extends Widget {} - class App extends Widget { - static components = { GenericComponent, SomeComponent }; - state = useState({ val: 4 }); - - inc() { - this.state.val++; - } - } - const app = new App(); - await app.mount(fixture); - - expect(fixture.innerHTML).toBe("
    SC:4
    "); - (fixture.querySelector("button")).click(); - await nextTick(); - expect(fixture.innerHTML).toBe("
    SC:5
    "); - }); - - test("slots and wrapper components", async () => { - class Link extends Component { - static template = xml` - - - `; - } - - class A extends Component { - static template = xml`hey`; - static components = { Link: Link }; - } - - const a = new A(); - await a.mount(fixture); - - expect(fixture.innerHTML).toBe(`hey`); - }); - - test("template can just return a slot", async () => { - class Child extends Widget { - static template = xml``; - } - class SlotComponent extends Widget { - static template = xml``; - } - - class Parent extends Widget { - static template = xml` -
    - -
    `; - static components = { SlotComponent, Child }; - state = useState({ value: 3 }); - } - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
    3
    "); - - expect(QWeb.TEMPLATES[SlotComponent.template].fn.toString()).toMatchSnapshot(); - - parent.state.value = 5; - await nextTick(); - expect(fixture.innerHTML).toBe("
    5
    "); - }); - - test("multiple slots containing components", async () => { - class C extends Component { - static template = xml``; - } - class B extends Component { - static template = xml`
    `; - } - class A extends Component { - static template = xml` - - - - `; - static components = { B, C }; - } - - const a = new A(); - await a.mount(fixture); - - expect(fixture.innerHTML).toBe(`
    12
    `); - }); - - test("slots in t-foreach and re-rendering", async () => { - class Child extends Widget { - static template = xml``; - state = useState({ val: "A" }); - mounted() { - this.state.val = "B"; - } - } - class Parent extends Widget { - static components = { Child }; - static template = xml` -
    - - - -
    `; - } - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("
    A0A1
    "); - - await nextTick(); // wait for the changes triggered in mounted to be applied - expect(fixture.innerHTML).toBe("
    B0B1
    "); - }); - - test("slots in t-foreach with t-set and re-rendering", async () => { - class Child extends Widget { - static template = xml` - - - - `; - state = useState({ val: "A" }); - mounted() { - this.state.val = "B"; - } - } - class ParentWidget extends Widget { - static components = { Child }; - static template = xml` -
    - - - - -
    `; - } - - const widget = new ParentWidget(); - await widget.mount(fixture); - expect(fixture.innerHTML).toBe("
    A0A1
    "); - - await nextTick(); // wait for changes triggered in mounted to be applied - expect(fixture.innerHTML).toBe("
    B0B1
    "); - }); - - test("nested slots in same template", async () => { - let child, child2, child3; - class Child extends Widget { - static template = xml` - -
    - -
    -
    `; - constructor(parent, props) { - super(parent, props); - child = this; - } - } - class Child2 extends Widget { - static template = xml` - - - `; - constructor(parent, props) { - super(parent, props); - child2 = this; - } - } - class Child3 extends Widget { - static template = xml` - Child 3`; - constructor(parent, props) { - super(parent, props); - child3 = this; - } - } - class Parent extends Widget { - static components = { Child, Child2, Child3 }; - static template = xml` - - - - - - - `; - } - - const widget = new Parent(); - await widget.mount(fixture); - expect(fixture.innerHTML).toBe( - '
    Child 3
    ' - ); - - expect(child3.__owl__.parent).toStrictEqual(child2); - expect(child2.__owl__.parent).toStrictEqual(child); - expect(child.__owl__.parent).toStrictEqual(widget); - }); - - test("t-slot nested within another slot", async () => { - let portal, modal, child3; - class Child3 extends Widget { - static template = xml` - Child 3`; - constructor(parent, props) { - super(parent, props); - child3 = this; - } - } - class Modal extends Widget { - static template = xml` - - - `; - constructor(parent, props) { - super(parent, props); - modal = this; - } - } - class Portal extends Widget { - static template = xml` - - - `; - constructor(parent, props) { - super(parent, props); - portal = this; - } - } - class Dialog extends Widget { - static components = { Modal, Portal }; - static template = xml` - - - - - - - `; - } - class Parent extends Widget { - static components = { Child3, Dialog }; - static template = xml` - - - - - `; - } - - const widget = new Parent(); - await widget.mount(fixture); - expect(fixture.innerHTML).toBe( - 'Child 3' - ); - - expect(child3.__owl__.parent).toStrictEqual(portal); - expect(portal.__owl__.parent).toStrictEqual(modal); - }); - - test("t-slot supports many instances", async () => { - let child3; - class Child3 extends Widget { - static template = xml` - Child 3`; - constructor(parent, props) { - super(parent, props); - child3 = this; - } - } - class Dialog extends Widget { - static template = xml` - - - `; - } - class Parent extends Widget { - static components = { Child3, Dialog }; - static template = xml` - - - - - `; - state = { lol: "k" }; - } - - const widget = new Parent(); - await widget.mount(fixture); - expect(child3.props.val).toBe("k"); - - const widget_1 = new Parent(); - widget_1.state.lol = "m"; - await widget_1.mount(fixture); - expect(child3.props.val).toBe("m"); - }); - - test("slots in slots, with vars", async () => { - class B extends Component { - static template = xml``; - } - class A extends Component { - static template = xml` -
    - - - -
    `; - static components = { B }; - } - class Parent extends Component { - static template = xml` -
    - - -

    hey

    -
    -
    `; - static components = { A }; - state = { name: "aaron" }; - } - - const parent = new Parent(); - await parent.mount(fixture); - expect(fixture.innerHTML).toBe("

    heyaaron

    "); - }); -}); describe("t-model directive", () => { test("basic use, on an input", async () => { diff --git a/tests/component/slots.test.ts b/tests/component/slots.test.ts new file mode 100644 index 00000000..b01f9c95 --- /dev/null +++ b/tests/component/slots.test.ts @@ -0,0 +1,887 @@ +import { Component, Env } from "../../src/component/component"; +import { QWeb } from "../../src/qweb/qweb"; +import { xml } from "../../src/tags"; +import { useState, useRef } from "../../src/hooks"; +import { + makeTestFixture, + makeTestEnv, + nextTick, +} from "../helpers"; + +//------------------------------------------------------------------------------ +// Setup and helpers +//------------------------------------------------------------------------------ + +// We create before each test: +// - fixture: a div, appended to the DOM, intended to be the target of dom +// manipulations. Note that it is removed after each test. +// - env: a WEnv, necessary to create new components + +let fixture: HTMLElement; +let env: Env; + +beforeEach(() => { + fixture = makeTestFixture(); + env = makeTestEnv(); + env.qweb.addTemplate("Component", "
    "); + env.qweb.addTemplate( + "Counter", + `
    ` + ); + env.qweb.addTemplate("WidgetA", `
    Hello
    `); + env.qweb.addTemplate("WidgetB", `
    world
    `); + Component.env = env; +}); + +afterEach(() => { + fixture.remove(); +}); + +function children(w: Component): Component[] { + const childrenMap = w.__owl__.children; + return Object.keys(childrenMap).map(id => childrenMap[id]); + } + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("t-slot directive", () => { + test("can define and call slots", async () => { + env.qweb.addTemplates(` + +
    + + header + footer + +
    +
    +
    +
    +
    +
    + `); + class Dialog extends Component {} + class Parent extends Component { + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe( + "
    header
    footer
    " + ); + expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot(); + expect(env.qweb.templates.Dialog.fn.toString()).toMatchSnapshot(); + expect(QWeb.slots["1_header"].toString()).toMatchSnapshot(); + expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot(); + }); + + test("named slots can define a default content", async () => { + class Dialog extends Component { + static template = xml` + + default content + `; + } + class Parent extends Component { + static template = xml`
    `; + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
    default content
    "); + expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot(); + }); + + test("dafault slots can define a default content", async () => { + class Dialog extends Component { + static template = xml` + + default content + `; + } + class Parent extends Component { + static template = xml`
    `; + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
    default content
    "); + expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot(); + }); + + test("default content is not rendered if slot is provided", async () => { + class Dialog extends Component { + static template = xml` + + default content + `; + } + class Parent extends Component { + static template = xml`
    hey
    `; + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
    hey
    "); + }); + + test("default content is not rendered if named slot is provided", async () => { + class Dialog extends Component { + static template = xml` + + default content + `; + } + class Parent extends Component { + static template = xml`
    hey
    `; + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
    hey
    "); + }); + + test("slots are rendered with proper context", async () => { + env.qweb.addTemplates(` + +
    + + + + +
    + +
    + `); + class Dialog extends Component {} + class Parent extends Component { + static components = { Dialog }; + state = useState({ val: 0 }); + doSomething() { + this.state.val++; + } + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe( + '
    0
    ' + ); + + fixture.querySelector("button")!.click(); + await nextTick(); + + expect(fixture.innerHTML).toBe( + '
    1
    ' + ); + expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot(); + }); + + test("slots are rendered with proper context, part 2", async () => { + env.qweb.addTemplates(` + + + + +
    +
  • + User +
  • +
    +
    + `); + class Link extends Component {} + + class App extends Component { + state = useState({ + users: [ + { id: 1, name: "Aaron" }, + { id: 2, name: "David" } + ] + }); + static components = { Link }; + } + + const app = new App(); + await app.mount(fixture); + + expect(fixture.innerHTML).toBe( + '' + ); + expect(env.qweb.templates.Link.fn.toString()).toMatchSnapshot(); + expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot(); + + // test updateprops here + app.state.users[1].name = "Mathieu"; + await nextTick(); + expect(fixture.innerHTML).toBe( + '' + ); + expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); + }); + + test("slots are rendered with proper context, part 3", async () => { + env.qweb.addTemplates(` + + + + +
    +
  • + + +
  • +
    +
    + `); + class Link extends Component {} + + class App extends Component { + state = useState({ + users: [ + { id: 1, name: "Aaron" }, + { id: 2, name: "David" } + ] + }); + static components = { Link }; + } + + const app = new App(); + await app.mount(fixture); + + expect(fixture.innerHTML).toBe( + '' + ); + expect(env.qweb.templates.Link.fn.toString()).toMatchSnapshot(); + expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot(); + + // test updateprops here + app.state.users[1].name = "Mathieu"; + await nextTick(); + expect(fixture.innerHTML).toBe( + '' + ); + expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); + }); + + test("slots are rendered with proper context, part 4", async () => { + env.qweb.addTemplates(` + + + + +
    + + +
    +
    + `); + class Link extends Component {} + + class App extends Component { + state = useState({ user: { id: 1, name: "Aaron" } }); + static components = { Link }; + } + + const app = new App(); + await app.mount(fixture); + + expect(fixture.innerHTML).toBe(''); + + expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot(); + + // test updateprops here + app.state.user.name = "David"; + await nextTick(); + expect(fixture.innerHTML).toBe(''); + expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); + }); + + test("refs are properly bound in slots", async () => { + class Dialog extends Component { + static template = xml``; + } + class Parent extends Component { + static template = xml` +
    + + + + +
    + `; + static components = { Dialog }; + state = useState({ val: 0 }); + button = useRef("myButton"); + doSomething() { + this.state.val++; + } + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe( + '
    0
    ' + ); + + parent.button.el!.click(); + await nextTick(); + + expect(fixture.innerHTML).toBe( + '
    1
    ' + ); + expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot(); + }); + + test("content is the default slot", async () => { + env.qweb.addTemplates(` + +
    + + sts rocks + +
    +
    +
    + `); + class Dialog extends Component {} + class Parent extends Component { + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
    sts rocks
    "); + expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); + }); + + test("default slot work with text nodes", async () => { + env.qweb.addTemplates(` + +
    + sts rocks +
    +
    +
    + `); + class Dialog extends Component {} + class Parent extends Component { + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
    sts rocks
    "); + expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); + }); + + test("multiple roots are allowed in a named slot", async () => { + env.qweb.addTemplates(` + +
    + + + sts + rocks + + +
    +
    +
    + `); + class Dialog extends Component {} + class Parent extends Component { + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
    stsrocks
    "); + expect(QWeb.slots["1_content"].toString()).toMatchSnapshot(); + }); + + test("multiple roots are allowed in a default slot", async () => { + env.qweb.addTemplates(` + +
    + + sts + rocks + +
    +
    +
    + `); + class Dialog extends Component {} + class Parent extends Component { + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
    stsrocks
    "); + expect(QWeb.slots["1_default"].toString()).toMatchSnapshot(); + }); + + test("missing slots are ignored", async () => { + env.qweb.addTemplates(` + +
    + +
    + + + some content + + +
    + `); + class Dialog extends Component {} + class Parent extends Component { + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
    some content
    "); + }); + + test("t-debug on a t-set (defining a slot)", async () => { + const consoleLog = console.log; + console.log = jest.fn(); + + env.qweb.addTemplates(` + +
    + abc +
    + + + +
    + `); + class Dialog extends Component {} + class Parent extends Component { + static components = { Dialog }; + } + const parent = new Parent(); + await parent.mount(fixture); + expect(console.log).toHaveBeenCalledTimes(0); + console.log = consoleLog; + }); + + test("slot preserves properly parented relationship", async () => { + env.qweb.addTemplates(` + +
    + + + +
    +
    +
    Grand Child
    +
    + `); + class Child extends Component {} + class GrandChild extends Component {} + class Parent extends Component { + static components = { Child, GrandChild }; + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
    Grand Child
    "); + + const parentChildren = children(parent); + expect(parentChildren.length).toBe(1); + expect(parentChildren[0]).toBeInstanceOf(Child); + + const childrenChildren = children(parentChildren[0]); + expect(childrenChildren.length).toBe(1); + expect(childrenChildren[0]).toBeInstanceOf(GrandChild); + }); + + test("nested slots: evaluation context and parented relationship", async () => { + let slot; + class Slot extends Component { + static template = xml``; + constructor(parent, props) { + super(parent, props); + slot = this; + } + } + class GrandChild extends Component { + static template = xml`
    `; + } + class Child extends Component { + static components = { GrandChild }; + static template = xml` + + + `; + } + class Parent extends Component { + static components = { Child, Slot }; + static template = xml``; + state = useState({ val: 3 }); + } + const parent = new Parent(); + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
    3
    "); + expect(slot.__owl__.parent).toBeInstanceOf(GrandChild); + }); + + test("slot are properly rendered if inner props are changed", async () => { + env.qweb.addTemplates(` + +
    + SC: +
    + + +
    + +
    + +
    + + + + +
    +
    + `); + class SomeComponent extends Component {} + class GenericComponent extends Component {} + class App extends Component { + static components = { GenericComponent, SomeComponent }; + state = useState({ val: 4 }); + + inc() { + this.state.val++; + } + } + const app = new App(); + await app.mount(fixture); + + expect(fixture.innerHTML).toBe("
    SC:4
    "); + (fixture.querySelector("button")).click(); + await nextTick(); + expect(fixture.innerHTML).toBe("
    SC:5
    "); + }); + + test("slots and wrapper components", async () => { + class Link extends Component { + static template = xml` + + + `; + } + + class A extends Component { + static template = xml`hey`; + static components = { Link: Link }; + } + + const a = new A(); + await a.mount(fixture); + + expect(fixture.innerHTML).toBe(`hey`); + }); + + test("template can just return a slot", async () => { + class Child extends Component { + static template = xml``; + } + class SlotComponent extends Component { + static template = xml``; + } + + class Parent extends Component { + static template = xml` +
    + +
    `; + static components = { SlotComponent, Child }; + state = useState({ value: 3 }); + } + const parent = new Parent(); + await parent.mount(fixture); + expect(fixture.innerHTML).toBe("
    3
    "); + + expect(QWeb.TEMPLATES[SlotComponent.template].fn.toString()).toMatchSnapshot(); + + parent.state.value = 5; + await nextTick(); + expect(fixture.innerHTML).toBe("
    5
    "); + }); + + test("multiple slots containing components", async () => { + class C extends Component { + static template = xml``; + } + class B extends Component { + static template = xml`
    `; + } + class A extends Component { + static template = xml` + + + + `; + static components = { B, C }; + } + + const a = new A(); + await a.mount(fixture); + + expect(fixture.innerHTML).toBe(`
    12
    `); + }); + + test("slots in t-foreach and re-rendering", async () => { + class Child extends Component { + static template = xml``; + state = useState({ val: "A" }); + mounted() { + this.state.val = "B"; + } + } + class Parent extends Component { + static components = { Child }; + static template = xml` +
    + + + +
    `; + } + const parent = new Parent(); + await parent.mount(fixture); + expect(fixture.innerHTML).toBe("
    A0A1
    "); + + await nextTick(); // wait for the changes triggered in mounted to be applied + expect(fixture.innerHTML).toBe("
    B0B1
    "); + }); + + test("slots in t-foreach with t-set and re-rendering", async () => { + class Child extends Component { + static template = xml` + + + + `; + state = useState({ val: "A" }); + mounted() { + this.state.val = "B"; + } + } + class ParentWidget extends Component { + static components = { Child }; + static template = xml` +
    + + + + +
    `; + } + + const widget = new ParentWidget(); + await widget.mount(fixture); + expect(fixture.innerHTML).toBe("
    A0A1
    "); + + await nextTick(); // wait for changes triggered in mounted to be applied + expect(fixture.innerHTML).toBe("
    B0B1
    "); + }); + + test("nested slots in same template", async () => { + let child, child2, child3; + class Child extends Component { + static template = xml` + +
    + +
    +
    `; + constructor(parent, props) { + super(parent, props); + child = this; + } + } + class Child2 extends Component { + static template = xml` + + + `; + constructor(parent, props) { + super(parent, props); + child2 = this; + } + } + class Child3 extends Component { + static template = xml` + Child 3`; + constructor(parent, props) { + super(parent, props); + child3 = this; + } + } + class Parent extends Component { + static components = { Child, Child2, Child3 }; + static template = xml` + + + + + + + `; + } + + const widget = new Parent(); + await widget.mount(fixture); + expect(fixture.innerHTML).toBe( + '
    Child 3
    ' + ); + + expect(child3.__owl__.parent).toStrictEqual(child2); + expect(child2.__owl__.parent).toStrictEqual(child); + expect(child.__owl__.parent).toStrictEqual(widget); + }); + + test("t-slot nested within another slot", async () => { + let portal, modal, child3; + class Child3 extends Component { + static template = xml` + Child 3`; + constructor(parent, props) { + super(parent, props); + child3 = this; + } + } + class Modal extends Component { + static template = xml` + + + `; + constructor(parent, props) { + super(parent, props); + modal = this; + } + } + class Portal extends Component { + static template = xml` + + + `; + constructor(parent, props) { + super(parent, props); + portal = this; + } + } + class Dialog extends Component { + static components = { Modal, Portal }; + static template = xml` + + + + + + + `; + } + class Parent extends Component { + static components = { Child3, Dialog }; + static template = xml` + + + + + `; + } + + const widget = new Parent(); + await widget.mount(fixture); + expect(fixture.innerHTML).toBe( + 'Child 3' + ); + + expect(child3.__owl__.parent).toStrictEqual(portal); + expect(portal.__owl__.parent).toStrictEqual(modal); + }); + + test("t-slot supports many instances", async () => { + let child3; + class Child3 extends Component { + static template = xml` + Child 3`; + constructor(parent, props) { + super(parent, props); + child3 = this; + } + } + class Dialog extends Component { + static template = xml` + + + `; + } + class Parent extends Component { + static components = { Child3, Dialog }; + static template = xml` + + + + + `; + state = { lol: "k" }; + } + + const widget = new Parent(); + await widget.mount(fixture); + expect(child3.props.val).toBe("k"); + + const widget_1 = new Parent(); + widget_1.state.lol = "m"; + await widget_1.mount(fixture); + expect(child3.props.val).toBe("m"); + }); + + test("slots in slots, with vars", async () => { + class B extends Component { + static template = xml``; + } + class A extends Component { + static template = xml` +
    + + + +
    `; + static components = { B }; + } + class Parent extends Component { + static template = xml` +
    + + +

    hey

    +
    +
    `; + static components = { A }; + state = { name: "aaron" }; + } + + const parent = new Parent(); + await parent.mount(fixture); + expect(fixture.innerHTML).toBe("

    heyaaron

    "); + }); + }); \ No newline at end of file