[FIX] components: capture context in prop expressions

This commit is contained in:
Samuel Degueldre
2021-11-22 12:04:58 +01:00
committed by Aaron Bohy
parent c0cf2c9e3d
commit 7143dd3ff5
16 changed files with 202 additions and 134 deletions
+19 -4
View File
@@ -344,8 +344,23 @@ export class CodeGenerator {
}
this.addLine(`}`);
}
captureExpression(expr: string): string {
/**
* Captures variables that are used inside of an expression. This is useful
* because in compiled code, almost all variables are accessed through the ctx
* object. In the case of functions, that lookup in the context can be delayed
* which can cause issues if the value has changed since the function was
* defined.
*
* @param expr the expression to capture
* @param forceCapture whether the expression should capture its scope even if
* it doesn't contain a function. Useful when the expression will be used as
* a function body.
* @returns a new expression that uses the captured values
*/
captureExpression(expr: string, forceCapture: boolean = false): string {
if (!forceCapture && !expr.includes("=>")) {
return compileExpr(expr);
}
const tokens = compileExprToArray(expr);
const mapping = new Map<string, string>();
return tokens
@@ -535,7 +550,7 @@ export class CodeGenerator {
if (isDynamic) {
const str = ast.ref.replace(
INTERP_REGEXP,
(expr) => "${" + this.captureExpression(expr.slice(2, -2)) + "}"
(expr) => "${" + this.captureExpression(expr.slice(2, -2), true) + "}"
);
const idx = block!.insertData(`(el) => refs[\`${str}\`] = el`);
attrs["block-ref"] = String(idx);
@@ -952,7 +967,7 @@ export class CodeGenerator {
// props
const props: string[] = [];
for (let p in ast.props) {
props.push(`${p}: ${compileExpr(ast.props[p]) || undefined}`);
props.push(`${p}: ${this.captureExpression(ast.props[p]) || undefined}`);
}
const propStr = `{${props.join(",")}}`;
@@ -9,8 +9,7 @@ exports[`t-on can bind event handler 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['add'];
let d1 = [v1, ctx];
let d1 = [ctx['add'], ctx];
return block1([d1]);
}
}"
@@ -116,10 +115,8 @@ exports[`t-on can bind two event handlers 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\" block-handler-1=\\"dblclick\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['handleClick'];
let d1 = [v1, ctx];
const v2 = ctx['handleDblClick'];
let d2 = [v2, ctx];
let d1 = [ctx['handleClick'], ctx];
let d2 = [ctx['handleDblClick'], ctx];
return block1([d1, d2]);
}
}"
@@ -134,8 +131,7 @@ exports[`t-on handler is bound to proper owner 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['add'];
let d1 = [v1, ctx];
let d1 = [ctx['add'], ctx];
return block1([d1]);
}
}"
@@ -155,8 +151,7 @@ exports[`t-on handler is bound to proper owner, part 2 1`] = `
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`value\`] = v_block1[i1];
let key1 = ctx['value'];
const v1 = ctx['add'];
let d1 = [v1, ctx];
let d1 = [ctx['add'], ctx];
c_block1[i1] = withKey(block2([d1]), key1);
}
return list(c_block1);
@@ -173,8 +168,7 @@ exports[`t-on handler is bound to proper owner, part 3 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['add'];
let d1 = [v1, ctx];
let d1 = [ctx['add'], ctx];
return block1([d1]);
}
}"
@@ -202,8 +196,7 @@ exports[`t-on handler is bound to proper owner, part 4 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['add'];
let d1 = [v1, ctx];
let d1 = [ctx['add'], ctx];
return block1([d1]);
}
}"
@@ -242,8 +235,7 @@ exports[`t-on receive event in first argument 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">Click</button>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['add'];
let d1 = [v1, ctx];
let d1 = [ctx['add'], ctx];
return block1([d1]);
}
}"
@@ -258,10 +250,8 @@ exports[`t-on t-on modifiers (native listener) basic support for native listener
let block1 = createBlock(\`<div class=\\"myClass\\" block-handler-0=\\"click\\"><button block-handler-1=\\"click\\">Button</button></div>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['divClicked'];
let d1 = [v1, ctx];
const v2 = ctx['btnClicked'];
let d2 = [v2, ctx];
let d1 = [ctx['divClicked'], ctx];
let d2 = [ctx['btnClicked'], ctx];
return block1([d1, d2]);
}
}"
@@ -276,8 +266,7 @@ exports[`t-on t-on modifiers (native listener) t-on combined with t-esc 1`] = `
let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><block-text-1/></button></div>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick'];
let d1 = [v1, ctx];
let d1 = [ctx['onClick'], ctx];
let d2 = ctx['text'];
return block1([d1, d2]);
}
@@ -293,8 +282,7 @@ exports[`t-on t-on modifiers (native listener) t-on combined with t-out 1`] = `
let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><block-child-0/></button></div>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick'];
let d1 = [v1, ctx];
let d1 = [ctx['onClick'], ctx];
let b2 = safeOutput(ctx['html']);
return block1([d1], [b2]);
}
@@ -310,10 +298,8 @@ exports[`t-on t-on modifiers (native listener) t-on with .capture modifier 1`] =
let block1 = createBlock(\`<div block-handler-0=\\"click.capture\\"><button block-handler-1=\\"click\\">Button</button></div>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onCapture'];
let d1 = [\\"capture\\", v1, ctx];
const v2 = ctx['doSomething'];
let d2 = [v2, ctx];
let d1 = [\\"capture\\", ctx['onCapture'], ctx];
let d2 = [ctx['doSomething'], ctx];
return block1([d1, d2]);
}
}"
@@ -343,8 +329,7 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent and self modifi
let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent.self\\"><span>Button</span></button></div>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick'];
let d1 = [\\"prevent\\",\\"self\\", v1, ctx];
let d1 = [\\"prevent\\",\\"self\\", ctx['onClick'], ctx];
return block1([d1]);
}
}"
@@ -359,12 +344,9 @@ exports[`t-on t-on modifiers (native listener) t-on with prevent and/or stop mod
let block1 = createBlock(\`<div><button block-handler-0=\\"click.prevent\\">Button 1</button><button block-handler-1=\\"click.stop\\">Button 2</button><button block-handler-2=\\"click.prevent.stop\\">Button 3</button></div>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClickPrevented'];
let d1 = [\\"prevent\\", v1, ctx];
const v2 = ctx['onClickStopped'];
let d2 = [\\"stop\\", v2, ctx];
const v3 = ctx['onClickPreventedAndStopped'];
let d3 = [\\"prevent\\",\\"stop\\", v3, ctx];
let d1 = [\\"prevent\\", ctx['onClickPrevented'], ctx];
let d2 = [\\"stop\\", ctx['onClickStopped'], ctx];
let d3 = [\\"prevent\\",\\"stop\\", ctx['onClickPreventedAndStopped'], ctx];
return block1([d1, d2, d3]);
}
}"
@@ -406,8 +388,7 @@ exports[`t-on t-on modifiers (native listener) t-on with self and prevent modifi
let block1 = createBlock(\`<div><button block-handler-0=\\"click.self.prevent\\"><span>Button</span></button></div>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick'];
let d1 = [\\"self\\",\\"prevent\\", v1, ctx];
let d1 = [\\"self\\",\\"prevent\\", ctx['onClick'], ctx];
return block1([d1]);
}
}"
@@ -422,10 +403,8 @@ exports[`t-on t-on modifiers (native listener) t-on with self modifier 1`] = `
let block1 = createBlock(\`<div><button block-handler-0=\\"click\\"><span>Button</span></button><button block-handler-1=\\"click.self\\"><span>Button</span></button></div>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick'];
let d1 = [v1, ctx];
const v2 = ctx['onClickSelf'];
let d2 = [\\"self\\", v2, ctx];
let d1 = [ctx['onClick'], ctx];
let d2 = [\\"self\\", ctx['onClickSelf'], ctx];
return block1([d1, d2]);
}
}"
@@ -440,10 +419,8 @@ exports[`t-on t-on modifiers (synthetic listener) basic support for synthetic 1`
let block1 = createBlock(\`<div block-handler-0=\\"click.synthetic\\"><button block-handler-1=\\"click.synthetic\\">Button</button></div>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['divClicked'];
let d1 = [\\"synthetic\\", v1, ctx];
const v2 = ctx['btnClicked'];
let d2 = [\\"synthetic\\", v2, ctx];
let d1 = [\\"synthetic\\", ctx['divClicked'], ctx];
let d2 = [\\"synthetic\\", ctx['btnClicked'], ctx];
return block1([d1, d2]);
}
}"
@@ -523,8 +500,7 @@ exports[`t-on t-on with t-call 1`] = `
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['update'];
let d1 = [v1, ctx];
let d1 = [ctx['update'], ctx];
return block1([d1]);
}
}"
+15 -27
View File
@@ -182,7 +182,7 @@ exports[`misc other complex template 1`] = `
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
const callTemplate_14 = getTemplate(\`LOAD_INFOS_TEMPLATE\`);
const callTemplate_2 = getTemplate(\`LOAD_INFOS_TEMPLATE\`);
let block1 = createBlock(\`<div><header><nav class=\\"navbar navbar-expand-md navbar-light bg-light\\"><a block-attribute-0=\\"href\\"><b style=\\"color:#777;\\"><block-text-1/></b></a><button type=\\"button\\" class=\\"navbar-toggler\\" data-toggle=\\"collapse\\" data-target=\\"#top_menu_collapse\\"><span class=\\"navbar-toggler-icon\\"/></button><div class=\\"collapse navbar-collapse\\" id=\\"top_menu_collapse\\" aria-expanded=\\"false\\"><ul class=\\"nav navbar-nav ml-auto text-right\\" id=\\"top_menu\\"><block-child-0/><li class=\\"nav-item divider\\"/><block-child-1/></ul><div><div class=\\"input-group input-group-sm\\"><div class=\\"input-group-prepend input-group-sm\\"><button class=\\"btn btn-default fa fa-cog\\" title=\\"Settings\\" block-handler-2=\\"click\\"/><button class=\\"btn btn-default\\" block-handler-3=\\"click\\"> More </button><block-child-2/></div><input class=\\"form-control\\" type=\\"text\\" placeholder=\\"Search\\" aria-label=\\"Search\\" name=\\"search\\" block-attribute-4=\\"value\\" block-handler-5=\\"keyup\\" block-handler-6=\\"change\\" block-ref=\\"7\\"/><div class=\\"input-group-append\\"><button class=\\"btn btn-default fa fa-eraser\\" block-handler-8=\\"click\\"/></div></div></div></div></nav></header><div class=\\"container-fluid\\" block-ref=\\"9\\"><div class=\\"row\\"><!--div class=\\"form-group col-md-6\\">
<h5>Search options</h5>
@@ -223,9 +223,7 @@ exports[`misc other complex template 1`] = `
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`project\`] = v_block2[i1];
let key1 = ctx['project'].id;
const v1 = ctx['selectProject'];
const v2 = ctx['project'];
let d3 = [v1(v2), ctx];
let d3 = [ctx['selectProject'](ctx['project']), ctx];
let d4 = ctx['project'].name;
c_block2[i1] = withKey(block3([d3, d4]), key1);
}
@@ -257,10 +255,8 @@ exports[`misc other complex template 1`] = `
}
b4 = multi([b5, b6]);
}
const v3 = ctx['toggleSettingsMenu'];
let d11 = [v3, ctx];
const v4 = ctx['toggleMore'];
let d12 = [v4, ctx];
let d11 = [ctx['toggleSettingsMenu'], ctx];
let d12 = [ctx['toggleMore'], ctx];
if (ctx['categories']&&ctx['categories'].length>1) {
ctx = Object.create(ctx);
const [k_block15, v_block15, l_block15, c_block15] = prepareList(ctx['categories']);
@@ -277,13 +273,10 @@ exports[`misc other complex template 1`] = `
b14 = block14([], [b15]);
}
let d16 = ctx['search'].value;
const v5 = ctx['updateFilter'];
let d17 = [v5, ctx];
const v6 = ctx['updateFilter'];
let d18 = [v6, ctx];
let d17 = [ctx['updateFilter'], ctx];
let d18 = [ctx['updateFilter'], ctx];
let d19 = (el) => refs[\`search_input\`] = el;
const v7 = ctx['clearSearch'];
let d20 = [v7, ctx];
let d20 = [ctx['clearSearch'], ctx];
let d21 = (el) => refs[\`settings_menu\`] = el;
if (ctx['triggers']) {
ctx = Object.create(ctx);
@@ -297,8 +290,7 @@ exports[`misc other complex template 1`] = `
let d23 = \`trigger_\${ctx['trigger'].id}\`;
let d24 = ctx['options'].trigger_display[ctx['trigger'].id];
let d25 = ctx['trigger'].id;
const v8 = ctx['updateTriggerDisplay'];
let d26 = [v8, ctx];
let d26 = [ctx['updateTriggerDisplay'], ctx];
let d27 = \`trigger_\${ctx['trigger'].id}\`;
let d28 = ctx['trigger'].name;
b20 = block20([d22, d23, d24, d25, d26, d27, d28]);
@@ -307,19 +299,15 @@ exports[`misc other complex template 1`] = `
}
ctx = ctx.__proto__;
let b18 = list(c_block18);
const v9 = ctx['triggerAll'];
let d29 = [v9, ctx];
const v10 = ctx['triggerNone'];
let d30 = [v10, ctx];
const v11 = ctx['triggerDefault'];
let d31 = [v11, ctx];
const v12 = ctx['toggleSettingsMenu'];
let d32 = [v12, ctx];
let d29 = [ctx['triggerAll'], ctx];
let d30 = [ctx['triggerNone'], ctx];
let d31 = [ctx['triggerDefault'], ctx];
let d32 = [ctx['toggleSettingsMenu'], ctx];
let b21 = block21([d29, d30, d31, d32]);
b17 = multi([b18, b21]);
}
if (ctx['load_infos']) {
b22 = callTemplate_14.call(this, ctx, node, key + \`__13\`);
b22 = callTemplate_2.call(this, ctx, node, key + \`__1\`);
}
if (ctx['message']) {
let d33 = ctx['message'];
@@ -328,8 +316,8 @@ exports[`misc other complex template 1`] = `
if (!ctx['project']) {
b24 = block24();
} else {
let b26 = component(\`BundlesList\`, {bundles: ctx['bundles'].sticky,category_custom_views: ctx['category_custom_views'],search: ctx['search']}, key + \`__15\`, node, ctx);
let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev,search: ctx['search']}, key + \`__16\`, node, ctx);
let b26 = component(\`BundlesList\`, {bundles: ctx['bundles'].sticky,category_custom_views: ctx['category_custom_views'],search: ctx['search']}, key + \`__3\`, node, ctx);
let b27 = component(\`BundlesList\`, {bundles: ctx['bundles'].dev,search: ctx['search']}, key + \`__4\`, node, ctx);
b25 = block25([], [b26, b27]);
}
return block1([d1, d2, d11, d12, d16, d17, d18, d19, d20, d21], [b2, b4, b14, b17, b22, b23, b24, b25]);
@@ -1130,10 +1130,9 @@ exports[`rendering component again in next microtick 2`] = `
return function template(ctx, node, key = \\"\\") {
let b2;
const v1 = ctx['onClick'];
let d1 = [v1, ctx];
let d1 = [ctx['onClick'], ctx];
if (ctx['env'].config.flag) {
b2 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
}
return block1([d1], [b2]);
}
@@ -23,15 +23,40 @@ exports[`event handling handler receive the event as argument 2`] = `
let block1 = createBlock(\`<span block-handler-0=\\"click\\"><block-child-0/><block-text-1/></span>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['inc'];
let d1 = [v1, ctx];
let b2 = component(\`Child\`, {}, key + \`__2\`, node, ctx);
let d1 = [ctx['inc'], ctx];
let b2 = component(\`Child\`, {}, key + \`__1\`, node, ctx);
let d2 = ctx['state'].value;
return block1([d1, d2], [b2]);
}
}"
`;
exports[`event handling objects from scope are properly captured by t-on 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<div class=\\"item\\" block-handler-0=\\"click\\"/>\`);
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['items']);
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`item\`] = v_block2[i1];
let key1 = ctx['item'];
const v1 = ctx['onClick'];
const v2 = ctx['item'];
let d1 = [ev=>v1(v2.val,ev), ctx];
c_block2[i1] = withKey(block3([d1]), key1);
}
let b2 = list(c_block2);
return block1([], [b2]);
}
}"
`;
exports[`event handling support for callable expression in event handler 1`] = `
"function anonymous(bdom, helpers
) {
@@ -42,8 +67,7 @@ exports[`event handling support for callable expression in event handler 1`] = `
return function template(ctx, node, key = \\"\\") {
let d1 = ctx['state'].value;
const v1 = ctx['obj'];
let d2 = [v1.onInput, ctx];
let d2 = [ctx['obj'].onInput, ctx];
return block1([d1, d2]);
}
}"
@@ -130,8 +130,7 @@ exports[`basics sub widget is interactive 1`] = `
let block1 = createBlock(\`<span><button block-handler-0=\\"click\\">click</button>child<block-text-1/></span>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['inc'];
let d1 = [v1, ctx];
let d1 = [ctx['inc'], ctx];
let d2 = ctx['state'].val;
return block1([d1, d2]);
}
@@ -540,8 +540,7 @@ exports[`lifecycle hooks onWillRender 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\"><block-text-1/></button>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['increment'];
let d1 = [v1, ctx];
let d1 = [ctx['increment'], ctx];
let d2 = ctx['state'].value;
return block1([d1, d2]);
}
@@ -30,6 +30,42 @@ exports[`basics accept ES6-like syntax for props (with getters) 2`] = `
}"
`;
exports[`basics arrow functions as prop correctly capture their scope 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
let block1 = createBlock(\`<button block-handler-0=\\"click\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let d1 = [ctx['props'].onClick, ctx];
return block1([d1]);
}
}"
`;
exports[`basics arrow functions as prop correctly capture their scope 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component } = bdom;
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue, toNumber, safeOutput } = helpers;
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['items']);
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`item\`] = v_block1[i1];
let key1 = ctx['item'].val;
const v1 = ctx['onClick'];
const v2 = ctx['item'];
c_block1[i1] = withKey(component(\`Child\`, {onClick: ev=>v1(v2.val,ev)}, key + \`__3__\${key1}\`, node, ctx), key1);
}
return list(c_block1);
}
}"
`;
exports[`basics explicit object prop 1`] = `
"function anonymous(bdom, helpers
) {
@@ -43,8 +43,7 @@ exports[`refs refs are properly bound in slots 2`] = `
const slot3 = ctx => (node, key) => {
const refs = ctx.__owl__.refs
const v4 = ctx['doSomething'];
let d2 = [v4, ctx];
let d2 = [ctx['doSomething'], ctx];
let d3 = (el) => refs[\`myButton\`] = el;
return block2([d2, d3]);
}
@@ -389,10 +389,9 @@ exports[`slots dynamic t-slot call 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\"><block-child-0/></button>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['toggle'];
let d1 = [v1, ctx];
const slot2 = (ctx['current'].slot);
let b2 = toggler(slot2, callSlot(ctx, node, key, slot2));
let d1 = [ctx['toggle'], ctx];
const slot1 = (ctx['current'].slot);
let b2 = toggler(slot1, callSlot(ctx, node, key, slot1));
return block1([d1], [b2]);
}
}"
@@ -436,14 +435,13 @@ exports[`slots dynamic t-slot call with default 1`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\"><block-child-0/></button>\`);
const defaultSlot2 = ctx => {
const defaultSlot1 = ctx => {
return text(\` owl \`);
}
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['toggle'];
let d1 = [v1, ctx];
let b3 = callSlot(ctx, node, key, (ctx['current'].slot), defaultSlot2, true);
let d1 = [ctx['toggle'], ctx];
let b3 = callSlot(ctx, node, key, (ctx['current'].slot), defaultSlot1, true);
return block1([d1], [b3]);
}
}"
@@ -1262,15 +1260,14 @@ exports[`slots slot are properly rendered if inner props are changed 3`] = `
let block1 = createBlock(\`<div><button block-handler-0=\\"click\\">Inc[<block-text-1/>]</button><block-child-0/></div>\`);
const slot3 = ctx => (node, key) => {
return component(\`SomeComponent\`, {val: ctx['state'].val}, key + \`__4\`, node, ctx);
const slot2 = ctx => (node, key) => {
return component(\`SomeComponent\`, {val: ctx['state'].val}, key + \`__3\`, node, ctx);
}
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['inc'];
let d1 = [v1, ctx];
let d1 = [ctx['inc'], ctx];
let d2 = ctx['state'].val;
let b3 = assign(component(\`GenericComponent\`, {}, key + \`__2\`, node, ctx), {slots: {'default': slot3(ctx)}});
let b3 = assign(component(\`GenericComponent\`, {}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
return block1([d1, d2], [b3]);
}
}"
@@ -1301,8 +1298,7 @@ exports[`slots slot content is bound to caller 2`] = `
let block1 = createBlock(\`<button block-handler-0=\\"click\\">some text</button>\`);
const slot2 = ctx => (node, key) => {
const v3 = ctx['inc'];
let d1 = [v3, ctx];
let d1 = [ctx['inc'], ctx];
return block1([d1]);
}
@@ -1472,8 +1468,7 @@ exports[`slots slots are rendered with proper context 2`] = `
let block2 = createBlock(\`<button block-handler-0=\\"click\\">do something</button>\`);
const slot3 = ctx => (node, key) => {
const v4 = ctx['doSomething'];
let d2 = [v4, ctx];
let d2 = [ctx['doSomething'], ctx];
return block2([d2]);
}
@@ -2083,8 +2078,7 @@ exports[`slots t-slot scope context 2`] = `
let block1 = createBlock(\`<div block-handler-0=\\"click\\"><block-child-0/></div>\`);
const slot2 = ctx => (node, key) => {
const v3 = ctx['onClick'];
let d1 = [v3, ctx];
let d1 = [ctx['onClick'], ctx];
let b2 = callSlot(ctx, node, key, 'default');
return block1([d1], [b2]);
}
@@ -52,8 +52,7 @@ exports[`t-call handlers are properly bound through a t-call 1`] = `
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['update'];
let d1 = [v1, ctx];
let d1 = [ctx['update'], ctx];
return block1([d1]);
}
}"
@@ -117,11 +117,10 @@ exports[`t-model directive can also define t-on directive on same event, part 1
let block1 = createBlock(\`<div><input block-handler-0=\\"input\\" block-attribute-1=\\"value\\" block-handler-2=\\"input\\"/></div>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onInput'];
let d1 = [v1, ctx];
const bExpr2 = ctx['state'];
let d1 = [ctx['onInput'], ctx];
const bExpr1 = ctx['state'];
let d2 = ctx['state']['text'];
let d3 = [(ev) => { bExpr2['text'] = ev.target.value; }];
let d3 = [(ev) => { bExpr1['text'] = ev.target.value; }];
return block1([d1, d2, d3]);
}
}"
@@ -136,21 +135,18 @@ exports[`t-model directive can also define t-on directive on same event, part 2
let block1 = createBlock(\`<div><input type=\\"radio\\" id=\\"one\\" value=\\"One\\" block-handler-0=\\"click\\" block-attribute-1=\\"checked\\" block-handler-2=\\"click\\"/><input type=\\"radio\\" id=\\"two\\" value=\\"Two\\" block-handler-3=\\"click\\" block-attribute-4=\\"checked\\" block-handler-5=\\"click\\"/><input type=\\"radio\\" id=\\"three\\" value=\\"Three\\" block-handler-6=\\"click\\" block-attribute-7=\\"checked\\" block-handler-8=\\"click\\"/></div>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick'];
let d1 = [v1, ctx];
const bExpr2 = ctx['state'];
let d1 = [ctx['onClick'], ctx];
const bExpr1 = ctx['state'];
let d2 = ctx['state']['choice'] === 'One';
let d3 = [(ev) => { bExpr2['choice'] = ev.target.value; }];
const v3 = ctx['onClick'];
let d4 = [v3, ctx];
const bExpr4 = ctx['state'];
let d3 = [(ev) => { bExpr1['choice'] = ev.target.value; }];
let d4 = [ctx['onClick'], ctx];
const bExpr2 = ctx['state'];
let d5 = ctx['state']['choice'] === 'Two';
let d6 = [(ev) => { bExpr4['choice'] = ev.target.value; }];
const v5 = ctx['onClick'];
let d7 = [v5, ctx];
const bExpr6 = ctx['state'];
let d6 = [(ev) => { bExpr2['choice'] = ev.target.value; }];
let d7 = [ctx['onClick'], ctx];
const bExpr3 = ctx['state'];
let d8 = ctx['state']['choice'] === 'Three';
let d9 = [(ev) => { bExpr6['choice'] = ev.target.value; }];
let d9 = [(ev) => { bExpr3['choice'] = ev.target.value; }];
return block1([d1, d2, d3, d4, d5, d6, d7, d8, d9]);
}
}"
@@ -130,8 +130,7 @@ exports[`t-on t-on on destroyed components 1`] = `
let block1 = createBlock(\`<div block-handler-0=\\"click\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onClick'];
let d1 = [v1, ctx];
let d1 = [ctx['onClick'], ctx];
return block1([d1]);
}
}"
+21
View File
@@ -72,4 +72,25 @@ describe("event handling", () => {
expect(onClickArgs![0]).toBe(1);
expect(onClickArgs![1]).toBeInstanceOf(MouseEvent);
});
test("objects from scope are properly captured by t-on", async () => {
let onClickArgs: [number, MouseEvent] | null = null;
class Parent extends Component {
static template = xml`
<div>
<t t-foreach="items" t-as="item" t-key="item">
<div class="item" t-on-click="ev => onClick(item.val, ev)"/>
</t>
</div>`;
items = [{ val: 1 }, { val: 2 }, { val: 3 }, { val: 4 }];
onClick(n: number, ev: MouseEvent) {
onClickArgs = [n, ev];
}
}
await mount(Parent, fixture);
expect(onClickArgs).toBeNull();
(<HTMLElement>fixture.querySelector(".item")).click();
expect(onClickArgs![0]).toBe(1);
expect(onClickArgs![1]).toBeInstanceOf(MouseEvent);
});
});
+25
View File
@@ -102,4 +102,29 @@ describe("basics", () => {
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("<div><span>&lt;p&gt;43&lt;/p&gt;<p>43</p></span></div>");
});
test("arrow functions as prop correctly capture their scope", async () => {
class Child extends Component {
static template = xml`<button t-on-click="props.onClick"/>`;
}
let onClickArgs: [number, MouseEvent] | null = null;
class Parent extends Component {
static template = xml`
<t t-foreach="items" t-as="item" t-key="item.val">
<Child onClick="ev => onClick(item.val, ev)"/>
</t>
`;
static components = { Child };
items = [{ val: 1 }, { val: 2 }, { val: 3 }, { val: 4 }];
onClick(n: number, ev: MouseEvent) {
onClickArgs = [n, ev];
}
}
await mount(Parent, fixture);
expect(onClickArgs).toBeNull();
(<HTMLElement>fixture.querySelector("button")).click();
expect(onClickArgs![0]).toBe(1);
expect(onClickArgs![1]).toBeInstanceOf(MouseEvent);
});
});
+1 -2
View File
@@ -9,8 +9,7 @@ exports[`Portal Portal composed with t-slot 1`] = `
let block1 = createBlock(\`<div block-handler-0=\\"custom\\"><span id=\\"childSpan\\">child2</span></div>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['onCustom'];
let d1 = [v1, ctx];
let d1 = [ctx['onCustom'], ctx];
return block1([d1]);
}
}"