Compare commits

...

11 Commits

Author SHA1 Message Date
Lucas Perais (lpe) b3fe8d9966 wip 2019-12-12 17:30:22 +01:00
Géry Debongnie f6634df72c wip 2019-12-12 16:40:31 +01:00
Géry Debongnie 0931a4dc5b [FIX] qweb: bind handlers to component
Before this commit, the handlers were bound to the current context,
which is often the component but not necessarily.  In some cases, a sub
scope can be created (with t-foreach, or slots, or t-call), and the
context is actually an object with the actual component instance it its
prototype chain, but not the component.
2019-12-12 14:32:43 +01:00
Géry Debongnie 4e22dbcad6 [FIX] qweb: accept assignations in qweb expressions
closes #560
closes #569
2019-12-12 14:32:43 +01:00
Géry Debongnie b283e65ad4 [REF] qweb: improve t-on generated code
and deduplicate logic between directive t-on applied on a node and on a
component
2019-12-12 14:32:43 +01:00
Géry Debongnie f517d44e32 [IMP] qweb: slightly simplify generated templates
- In some cases, we can simply inline a template key instead of assigning
  it to a variable and then using that variable
- Add component name in generated code for components
2019-12-12 09:39:26 +01:00
Lucas Perais (lpe) 08022b49df [FIX] qweb: conditional t-call with body
When a t-call has a condition and a body
the t-if's and its flavors should be ignored when compiling the body of a t-call
2019-12-11 14:09:45 +01:00
Géry Debongnie b96ea79f2b [FIX] qweb: renderToString now properly escape
closes #565
2019-12-11 12:25:05 +01:00
Géry Debongnie 97d8b3ed8c [REF] tests: remove Widget common class
Bad name, and bad practice to use it. It makes tests less standalone.
2019-12-11 12:25:05 +01:00
Géry Debongnie 9921ae07b1 [REF] tests: move slots tests out of main component file 2019-12-11 12:25:05 +01:00
Géry Debongnie 41cb5f1abd [FIX] *: remove useless values to t-else and t-debug
The actual value does not matter, only the fact that it is present.

closes #564
2019-12-11 12:25:05 +01:00
26 changed files with 2582 additions and 2092 deletions
+12 -2
View File
@@ -628,10 +628,20 @@ as it will be translated to:
```js
button.addEventListener("click", () => {
component.state.counter++;
context.state.counter++;
});
```
Warning: inline expressions are evaluated in the context of the template. This
means that they can access the component methods and properties. But if they set
a key, the inline statement will actually not modify the component, but a key in
a sub scope.
```xml
<button t-on-click="value = 1">Set value to 1 (does not work!!!)</button>
<button t-on-click="state.value = 1">Set state.value to 1 (work as expected)</button>
```
In order to remove the DOM event details from the event handlers (like calls to
`event.preventDefault`) and let them focus on data logic, _modifiers_ can be
specified as additional suffixes of the `t-on` directive.
@@ -946,7 +956,7 @@ For example, here is how we could implement an `ErrorBoundary` component:
<t t-if="state.error">
Error handled
</t>
<t t-else="1">
<t t-else="">
<t t-slot="default" />
</t>
</div>
+1 -1
View File
@@ -57,7 +57,7 @@ class SomeComponent extends Component {
<t t-if=device.isMobile>
some simplified user interface
</t>
<t t-else="1">
<t t-else="">
a more advanced user interface
</t>
</div>`;
+2 -2
View File
@@ -28,7 +28,7 @@ generate a virtual dom representation of the HTML.
```xml
<div>
<span t-if="somecondition">Some string</span>
<ul t-else="1">
<ul t-else="">
<li t-foreach="messages" t-as="message">
<t t-esc="message"/>
</li>
@@ -101,7 +101,7 @@ precisely, the result of a template rendering should have a single root node:
<!–– ok: result has one single root node ––>
<t>
<div t-if="someCondition">foo</div>
<span t-else="1">bar</span>
<span t-else="">bar</span>
</t>
```
+13 -36
View File
@@ -1,6 +1,6 @@
import { QWeb } from "../qweb/index";
import { INTERP_REGEXP } from "../qweb/compilation_context";
import { MODS_CODE } from "../qweb/extensions";
import { makeHandlerCode, MODS_CODE } from "../qweb/extensions";
//------------------------------------------------------------------------------
// t-component
@@ -189,7 +189,7 @@ QWeb.addDirective({
extraNames: ["props"],
priority: 100,
atNodeEncounter({ ctx, value, node, qweb }): boolean {
ctx.addLine("//COMPONENT");
ctx.addLine(`// Component '${value}'`);
ctx.rootContext.shouldDefineQWeb = true;
ctx.rootContext.shouldDefineParent = true;
ctx.rootContext.shouldDefineUtils = true;
@@ -197,7 +197,7 @@ QWeb.addDirective({
let hasDynamicProps = node.getAttribute("t-props") ? true : false;
// t-on- events and t-transition
const events: [string, string[], string, string][] = [];
const events: [string, string][] = [];
let transition: string = "";
const attributes = (<Element>node).attributes;
const props: { [key: string]: string } = {};
@@ -205,13 +205,7 @@ QWeb.addDirective({
const name = attributes[i].name;
const value = attributes[i].textContent!;
if (name.startsWith("t-on-")) {
const [eventName, ...mods] = name.slice(5).split(".");
let extraArgs;
let handlerValue = value.replace(/\(.*\)/, function(args) {
extraArgs = args.slice(1, -1);
return "";
});
events.push([eventName, mods, handlerValue, extraArgs]);
events.push([name, value]);
} else if (name === "t-transition") {
transition = value;
} else if (!name.startsWith("t-")) {
@@ -284,32 +278,15 @@ QWeb.addDirective({
}
}
let eventsCode = events
.map(function([eventName, mods, handlerValue, extraArgs]) {
let params = "context";
if (extraArgs) {
if (ctx.loopNumber) {
let argId = ctx.generateID();
// we need to evaluate the arguments now, because the handler will
// be set asynchronously later when the widget is ready, and the
// context might be different.
ctx.addLine(`let arg${argId} = ${ctx.formatExpression(extraArgs)};`);
params = `context, arg${argId}`;
} else {
params = `context, ${ctx.formatExpression(extraArgs)}`;
}
}
let handler = `function (e) {if(!context.__owl__.isMounted){return}`;
handler += mods
.map(function(mod) {
return T_COMPONENT_MODS_CODE[mod];
})
.join("");
if (handlerValue) {
handler += `const fn = context['${handlerValue}'];`;
handler += `if (fn) { fn.call(${params}, e); } else { context.${handlerValue}; }`;
}
handler += `}`;
return `vn.elm.addEventListener('${eventName}', ${handler});`;
.map(function([name, value]) {
const { event, handler } = makeHandlerCode(
ctx,
name,
value,
false,
T_COMPONENT_MODS_CODE
);
return `vn.elm.addEventListener('${event}', ${handler});`;
})
.join("");
const styleExpr = tattStyle || (styleAttr ? `'${styleAttr}'` : false);
+8
View File
@@ -29,6 +29,14 @@ export class Observer {
// fun fact: typeof null === 'object'
return value;
}
if (
value instanceof Map ||
value instanceof WeakMap ||
value instanceof Set ||
value instanceof WeakSet
) {
return value;
}
let metadata = this.weakMap.get(value) || this._observe(value, parent);
return metadata.proxy;
}
+3 -1
View File
@@ -242,7 +242,9 @@ QWeb.addDirective({
ctx.indent();
protectID = ctx.startProtectScope();
const nodeCopy = node.cloneNode(true) as Element;
nodeCopy.removeAttribute("t-call");
for (let attr of ["t-if", "t-else", "t-elif", "t-call"]) {
nodeCopy.removeAttribute(attr);
}
const parentNode = ctx.parentNode;
ctx.parentNode = "__0";
// this local scope is intended to trap c__0
+5 -2
View File
@@ -46,9 +46,12 @@ export class CompilationContext {
* Such a key is necessary when we need to associate an id to some element
* generated by a template (for example, a component)
*/
generateTemplateKey(): string {
generateTemplateKey(prefix: string = ""): string {
const id = this.generateID();
let locationExpr = `\`__${this.generateID()}__`;
if (this.loopNumber === 0 && !this.currentKey) {
return `'${prefix}__${id}__'`;
}
let locationExpr = `\`${prefix}__${id}__`;
for (let i = 0; i < this.loopNumber - 1; i++) {
locationExpr += `\${i${i + 1}}__`;
}
+1 -1
View File
@@ -81,7 +81,7 @@ const STATIC_TOKEN_MAP: { [key: string]: TKind } = {
// note that the space after typeof is relevant. It makes sure that the formatted
// expression has a space after typeof
const OPERATORS = ".,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>".split(",");
const OPERATORS = ".,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,=,;".split(",");
type Tokenizer = (expr: string) => Token | false;
+56 -38
View File
@@ -26,48 +26,66 @@ export const MODS_CODE = {
stop: "e.stopPropagation();"
};
interface HandlerInfo {
event: string;
handler: string;
}
const FNAMEREGEXP = /^[$A-Z_][0-9A-Z_$]*$/i;
export function makeHandlerCode(
ctx,
fullName,
value,
putInCache: boolean,
modcodes = MODS_CODE
): HandlerInfo {
const [event, ...mods] = fullName.slice(5).split(".");
if (!event) {
throw new Error("Missing event name with t-on directive");
}
let code: string;
// check if it is a method with no args, a method with args or an expression
let args: string = "";
const name: string = value.replace(/\(.*\)/, function(_args) {
args = _args.slice(1, -1);
return "";
});
const isMethodCall = name.match(FNAMEREGEXP);
// then generate code
if (isMethodCall) {
ctx.rootContext.shouldDefineUtils = true;
const comp = `utils.getComponent(context)`;
if (args) {
let argId = ctx.generateID();
ctx.addLine(`let args${argId} = [${ctx.formatExpression(args)}];`);
code = `${comp}['${name}'](...args${argId}, e);`;
putInCache = false;
} else {
code = `${comp}['${name}'](e);`;
}
} else {
// if we get here, then it is an expression
putInCache = false;
code = ctx.formatExpression(value);
}
const modCode = mods.map(mod => modcodes[mod]).join("");
let handler = `function (e) {if (!context.__owl__.isMounted){return}${modCode}${code}}`;
if (putInCache) {
const key = ctx.generateTemplateKey(event);
ctx.addLine(`extra.handlers[${key}] = extra.handlers[${key}] || ${handler};`);
handler = `extra.handlers[${key}]`;
}
return { event, handler };
}
QWeb.addDirective({
name: "on",
priority: 90,
atNodeCreation({ ctx, fullName, value, nodeID }) {
const [eventName, ...mods] = fullName.slice(5).split(".");
if (!eventName) {
throw new Error("Missing event name with t-on directive");
}
let extraArgs;
let handlerName = value.replace(/\(.*\)/, function(args) {
extraArgs = args.slice(1, -1);
return "";
});
let params = extraArgs ? `context, ${ctx.formatExpression(extraArgs)}` : "context";
let handler = `function (e) {if (!context.__owl__.isMounted){return}`;
handler += mods
.map(function(mod) {
return MODS_CODE[mod];
})
.join("");
if (handlerName) {
if (!extraArgs) {
handler += `const fn = context['${handlerName}'];`;
handler += `if (fn) { fn.call(${params}, e); } else { context.${handlerName}; }`;
handler += `}`;
ctx.addLine(
`extra.handlers['${eventName}' + ${nodeID}] = extra.handlers['${eventName}' + ${nodeID}] || ${handler};`
);
ctx.addLine(`p${nodeID}.on['${eventName}'] = extra.handlers['${eventName}' + ${nodeID}];`);
} else {
const handlerKey = `handler${ctx.generateID()}`;
ctx.addLine(
`const ${handlerKey} = context['${handlerName}'] && context['${handlerName}'].bind(${params});`
);
handler += `if (${handlerKey}) { ${handlerKey}(e); } else { context.${value}; }`;
handler += `}`;
ctx.addLine(`p${nodeID}.on['${eventName}'] = ${handler};`);
}
} else {
handler += "}";
ctx.addLine(`p${nodeID}.on['${eventName}'] = ${handler};`);
}
const { event, handler } = makeHandlerCode(ctx, fullName, value, true);
ctx.addLine(`p${nodeID}.on['${event}'] = ${handler};`);
}
});
+18 -3
View File
@@ -1,7 +1,7 @@
import { EventBus } from "../core/event_bus";
import { h, patch, VNode } from "../vdom/index";
import { CompilationContext } from "./compilation_context";
import { shallowEqual } from "../utils";
import { shallowEqual, escape } from "../utils";
import { addNS } from "../vdom/vdom";
/**
@@ -120,6 +120,12 @@ const UTILS: Utils = {
}
})
.join();
},
getComponent(obj) {
while (obj && !obj.hasOwnProperty("__owl__")) {
obj = obj.__proto__;
}
return obj;
}
};
@@ -348,8 +354,17 @@ export class QWeb extends EventBus {
return vnode.text!;
}
const node = document.createElement(vnode.sel);
const result = patch(node, vnode);
return (<HTMLElement>result.elm).outerHTML;
const elem = patch(node, vnode).elm as HTMLElement;
function escapeTextNodes(node) {
if (node.nodeType === 3) {
node.textContent = escape(node.textContent);
}
for (let n of node.childNodes) {
escapeTextNodes(n);
}
}
escapeTextNodes(elem);
return elem.outerHTML;
}
/**
+3 -6
View File
@@ -58,12 +58,9 @@ export function escape(str: string | number | undefined): string {
if (typeof str === "number") {
return String(str);
}
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&#x27;")
.replace(/`/g, "&#x60;");
const p = document.createElement("p");
p.textContent = str;
return p.innerHTML;
}
/**
+12 -15
View File
@@ -11,9 +11,8 @@ exports[`animations t-transition combined with component 1`] = `
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;
// Component 'Child'
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
let props2 = {};
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
w2.destroy();
@@ -30,9 +29,9 @@ exports[`animations t-transition combined with component 1`] = `
w2 = new W2(parent, props2);
const __patch2 = w2.__patch;
w2.__patch = fiber => {__patch2.call(w2, fiber); if(!w2.__owl__.transitionInserted){w2.__owl__.transitionInserted = true;utils.transitionInsert(w2.__owl__.vnode, 'chimay');}};
parent.__owl__.cmap[k3] = w2.__owl__.id;
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {let finalize = () => {
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {let finalize = () => {
w2.destroy();
};
delete w2.__owl__.transitionInserted;
@@ -57,9 +56,8 @@ exports[`animations t-transition combined with t-component and t-if 1`] = `
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
if (scope['state'].display) {
//COMPONENT
let k3 = \`__4__\`;
let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : false;
// Component 'Child'
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
let props2 = {};
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
w2.destroy();
@@ -76,9 +74,9 @@ exports[`animations t-transition combined with t-component and t-if 1`] = `
w2 = new W2(parent, props2);
const __patch2 = w2.__patch;
w2.__patch = fiber => {__patch2.call(w2, fiber); if(!w2.__owl__.transitionInserted){w2.__owl__.transitionInserted = true;utils.transitionInsert(w2.__owl__.vnode, 'chimay');}};
parent.__owl__.cmap[k3] = w2.__owl__.id;
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {let finalize = () => {
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {let finalize = () => {
w2.destroy();
};
delete w2.__owl__.transitionInserted;
@@ -104,9 +102,8 @@ exports[`animations t-transition combined with t-component, remove and re-add be
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
if (scope['state'].flag) {
//COMPONENT
let k3 = \`__4__\`;
let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : false;
// Component 'Child'
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
let props2 = {};
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
w2.destroy();
@@ -123,9 +120,9 @@ exports[`animations t-transition combined with t-component, remove and re-add be
w2 = new W2(parent, props2);
const __patch2 = w2.__patch;
w2.__patch = fiber => {__patch2.call(w2, fiber); if(!w2.__owl__.transitionInserted){w2.__owl__.transitionInserted = true;utils.transitionInsert(w2.__owl__.vnode, 'chimay');}};
parent.__owl__.cmap[k3] = w2.__owl__.id;
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {let finalize = () => {
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {let finalize = () => {
w2.destroy();
};
delete w2.__owl__.transitionInserted;
+6 -6
View File
@@ -254,11 +254,11 @@ describe("animations", () => {
widget.state.display = false;
patchNextFrame(cb => {
expect(fixture.innerHTML).toBe(
'<div><span class="chimay-leave chimay-leave-active" data-owl-key="__4__">blue</span></div>'
'<div><span class="chimay-leave chimay-leave-active" data-owl-key="__3__">blue</span></div>'
);
cb();
expect(fixture.innerHTML).toBe(
'<div><span class="chimay-leave-active chimay-leave-to" data-owl-key="__4__">blue</span></div>'
'<div><span class="chimay-leave-active chimay-leave-to" data-owl-key="__3__">blue</span></div>'
);
def.resolve();
});
@@ -371,7 +371,7 @@ describe("animations", () => {
await def; // wait for the mocked repaint to be done
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend")); // mock end of css transition
expect(fixture.innerHTML).toBe('<div><span class="" data-owl-key="__4__">blue</span></div>');
expect(fixture.innerHTML).toBe('<div><span class="" data-owl-key="__3__">blue</span></div>');
});
test("transitionInsert is called the correct amount of times", async () => {
@@ -405,14 +405,14 @@ describe("animations", () => {
widget.state.flag = false;
await nextTick();
expect(fixture.innerHTML).toBe(
'<div><span class="chimay-leave-active chimay-leave-to" data-owl-key="__4__">blue</span></div>'
'<div><span class="chimay-leave-active chimay-leave-to" data-owl-key="__3__">blue</span></div>'
);
expect(QWeb.utils.transitionInsert).toBeCalledTimes(1);
widget.state.flag = true;
await nextTick();
expect(fixture.innerHTML).toBe(
'<div><span class="chimay-enter-active chimay-enter-to" data-owl-key="__4__">blue</span></div>'
'<div><span class="chimay-enter-active chimay-enter-to" data-owl-key="__3__">blue</span></div>'
);
expect(QWeb.utils.transitionInsert).toBeCalledTimes(2);
@@ -423,7 +423,7 @@ describe("animations", () => {
expect(QWeb.utils.transitionInsert).toBeCalledTimes(3);
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend"));
expect(fixture.innerHTML).toBe('<div><span class="" data-owl-key="__4__">blue</span></div>');
expect(fixture.innerHTML).toBe('<div><span class="" data-owl-key="__3__">blue</span></div>');
QWeb.utils.transitionInsert = oldTransitionInsert;
});
});
File diff suppressed because it is too large Load Diff
@@ -11,9 +11,8 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
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;
// Component 'Child'
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
let props2 = {message:1};
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
w2.destroy();
@@ -28,9 +27,9 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap[k3] = w2.__owl__.id;
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
}
@@ -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 'Dialog'
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : 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['__3__'] = 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: '__3__', 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 c6 = [], p6 = {key:6};
var vn6 = h('div', p6, c6);
let c7 = [], p7 = {key:7};
var vn7 = h('div', p7, c7);
c6.push(vn7);
const slot8 = this.constructor.slots[context.__owl__.slotId + '_' + 'header'];
if (slot8) {
slot8.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c7, parent: extra.parent || context}));
}
let c9 = [], p9 = {key:9};
var vn9 = h('div', p9, c9);
c6.push(vn9);
const slot10 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer'];
if (slot10) {
slot10.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c9, parent: extra.parent || context}));
}
return vn6;
}"
`;
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 c4 = [], p4 = {key:4};
var vn4 = h('span', p4, c4);
c1.push(vn4);
c4.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 c5 = [], p5 = {key:5};
var vn5 = h('span', p5, c5);
c1.push(vn5);
c5.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 c4 = [], p4 = {key:4};
var vn4 = h('span', p4, c4);
c1.push(vn4);
c4.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 c4 = [], p4 = {key:4};
var vn4 = h('span', p4, c4);
const slot5 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
if (slot5) {
slot5.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c4, parent: extra.parent || context}));
} else {
c4.push({text: \`default content\`});
}
return vn4;
}"
`;
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 c4 = [], p4 = {key:4};
var vn4 = h('span', p4, c4);
c1.push(vn4);
c4.push({text: \`sts\`});
let c5 = [], p5 = {key:5};
var vn5 = h('span', p5, c5);
c1.push(vn5);
c5.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 c4 = [], p4 = {key:4};
var vn4 = h('span', p4, c4);
c1.push(vn4);
c4.push({text: \`sts\`});
let c5 = [], p5 = {key:5};
var vn5 = h('span', p5, c5);
c1.push(vn5);
c5.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 c4 = [], p4 = {key:4};
var vn4 = h('span', p4, c4);
const slot5 = this.constructor.slots[context.__owl__.slotId + '_' + 'header'];
if (slot5) {
slot5.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c4, parent: extra.parent || context}));
} else {
c4.push({text: \`default content\`});
}
return vn4;
}"
`;
exports[`t-slot directive refs are properly bound in slots 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"slot_footer_template\\"
let utils = this.constructor.utils;
context.__owl__.refs = context.__owl__.refs || {};
var h = this.h;
let c1 = extra.parentNode;
let c8 = [], p8 = {key:8,on:{}};
var vn8 = h('button', p8, c8);
c1.push(vn8);
extra.handlers['click__9__'] = extra.handlers['click__9__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);};
p8.on['click'] = extra.handlers['click__9__'];
const ref10 = \`myButton\`;
p8.hook = {
create: (_, n) => {
context.__owl__.refs[ref10] = n.elm;
},
destroy: () => {
delete context.__owl__.refs[ref10];
},
};
c8.push({text: \`do something\`});
}"
`;
exports[`t-slot directive slots are rendered with proper context 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"slot_footer_template\\"
let utils = this.constructor.utils;
var h = this.h;
let c1 = extra.parentNode;
let c8 = [], p8 = {key:8,on:{}};
var vn8 = h('button', p8, c8);
c1.push(vn8);
extra.handlers['click__9__'] = extra.handlers['click__9__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);};
p8.on['click'] = extra.handlers['click__9__'];
c8.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 _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 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 'Link'
let k10 = \`__10__\` + 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 _11 = scope['user'].name;
if (_11 || _11 === 0) {
c8.push({text: _11});
}
}"
`;
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 _11 = scope['props'].to;
let c12 = [], p12 = {key:12,attrs:{href: _11}};
var vn12 = h('a', p12, c12);
const slot13 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
if (slot13) {
slot13.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c12, parent: extra.parent || context}));
}
return vn12;
}"
`;
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 'Link'
let k10 = \`__10__\` + 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 'Link'
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : 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['__3__'] = 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: '__3__', 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 slot6 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
if (slot6) {
let children7= []
result = {}
slot6.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: children7, parent: extra.parent || context}));
utils.defineProxy(result, children7[0]);
}
return result;
}"
`;
File diff suppressed because it is too large Load Diff
+883
View File
@@ -0,0 +1,883 @@
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<any,any>", "<div></div>");
env.qweb.addTemplate(
"Counter",
`<div><t t-esc="state.counter"/><button t-on-click="inc">Inc</button></div>`
);
env.qweb.addTemplate("WidgetA", `<div>Hello<t t-component="b"/></div>`);
env.qweb.addTemplate("WidgetB", `<div>world</div>`);
Component.env = env;
});
afterEach(() => {
fixture.remove();
});
function children(w: Component<any, any>): Component<any, any>[] {
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(`
<templates>
<div t-name="Parent">
<Dialog>
<t t-set="header"><span>header</span></t>
<t t-set="footer"><span>footer</span></t>
</Dialog>
</div>
<div t-name="Dialog">
<div><t t-slot="header"/></div>
<div><t t-slot="footer"/></div>
</div>
</templates>
`);
class Dialog extends Component<any, any> {}
class Parent extends Component<any, any> {
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe(
"<div><div><div><span>header</span></div><div><span>footer</span></div></div></div>"
);
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<any, any> {
static template = xml`
<span>
<t t-slot="header">default content</t>
</span>`;
}
class Parent extends Component<any, any> {
static template = xml`<div><Dialog/></div>`;
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>default content</span></div>");
expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot();
});
test("dafault slots can define a default content", async () => {
class Dialog extends Component<any, any> {
static template = xml`
<span>
<t t-slot="default">default content</t>
</span>`;
}
class Parent extends Component<any, any> {
static template = xml`<div><Dialog/></div>`;
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>default content</span></div>");
expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot();
});
test("default content is not rendered if slot is provided", async () => {
class Dialog extends Component<any, any> {
static template = xml`
<span>
<t t-slot="default">default content</t>
</span>`;
}
class Parent extends Component<any, any> {
static template = xml`<div><Dialog>hey</Dialog></div>`;
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>hey</span></div>");
});
test("default content is not rendered if named slot is provided", async () => {
class Dialog extends Component<any, any> {
static template = xml`
<span>
<t t-slot="header">default content</t>
</span>`;
}
class Parent extends Component<any, any> {
static template = xml`<div><Dialog><t t-set="header">hey</t></Dialog></div>`;
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>hey</span></div>");
});
test("slots are rendered with proper context", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="Parent">
<span class="counter"><t t-esc="state.val"/></span>
<Dialog>
<t t-set="footer"><button t-on-click="doSomething">do something</button></t>
</Dialog>
</div>
<span t-name="Dialog"><t t-slot="footer"/></span>
</templates>
`);
class Dialog extends Component<any, any> {}
class Parent extends Component<any, any> {
static components = { Dialog };
state = useState({ val: 0 });
doSomething() {
this.state.val++;
}
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe(
'<div><span class="counter">0</span><span><button>do something</button></span></div>'
);
fixture.querySelector("button")!.click();
await nextTick();
expect(fixture.innerHTML).toBe(
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
);
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
});
test("slots are rendered with proper context, part 2", async () => {
env.qweb.addTemplates(`
<templates>
<a t-name="Link" t-att-href="props.to">
<t t-slot="default"/>
</a>
<div t-name="App">
<u><li t-foreach="state.users" t-as="user" t-key="user.id">
<Link to="'/user/' + user.id">User <t t-esc="user.name"/></Link>
</li></u>
</div>
</templates>
`);
class Link extends Component<any, any> {}
class App extends Component<any, any> {
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(
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User David</a></li></u></div>'
);
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(
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User Mathieu</a></li></u></div>'
);
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
});
test("slots are rendered with proper context, part 3", async () => {
env.qweb.addTemplates(`
<templates>
<a t-name="Link" t-att-href="props.to">
<t t-slot="default"/>
</a>
<div t-name="App">
<u><li t-foreach="state.users" t-as="user" t-key="user.id" >
<t t-set="userdescr" t-value="'User ' + user.name"/>
<Link to="'/user/' + user.id"><t t-esc="userdescr"/></Link>
</li></u>
</div>
</templates>
`);
class Link extends Component<any, any> {}
class App extends Component<any, any> {
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(
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User David</a></li></u></div>'
);
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(
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User Mathieu</a></li></u></div>'
);
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
});
test("slots are rendered with proper context, part 4", async () => {
env.qweb.addTemplates(`
<templates>
<a t-name="Link" t-att-href="props.to">
<t t-slot="default"/>
</a>
<div t-name="App">
<t t-set="userdescr" t-value="'User ' + state.user.name"/>
<Link to="'/user/' + state.user.id"><t t-esc="userdescr"/></Link>
</div>
</templates>
`);
class Link extends Component<any, any> {}
class App extends Component<any, any> {
state = useState({ user: { id: 1, name: "Aaron" } });
static components = { Link };
}
const app = new App();
await app.mount(fixture);
expect(fixture.innerHTML).toBe('<div><a href="/user/1">User Aaron</a></div>');
expect(env.qweb.templates.App.fn.toString()).toMatchSnapshot();
// test updateprops here
app.state.user.name = "David";
await nextTick();
expect(fixture.innerHTML).toBe('<div><a href="/user/1">User David</a></div>');
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
});
test("refs are properly bound in slots", async () => {
class Dialog extends Component<any, any> {
static template = xml`<span><t t-slot="footer"/></span>`;
}
class Parent extends Component<any, any> {
static template = xml`
<div>
<span class="counter"><t t-esc="state.val"/></span>
<Dialog>
<t t-set="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
</Dialog>
</div>
`;
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(
'<div><span class="counter">0</span><span><button>do something</button></span></div>'
);
parent.button.el!.click();
await nextTick();
expect(fixture.innerHTML).toBe(
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
);
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
});
test("content is the default slot", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="Parent">
<Dialog>
<span>sts rocks</span>
</Dialog>
</div>
<div t-name="Dialog"><t t-slot="default"/></div>
</templates>
`);
class Dialog extends Component<any, any> {}
class Parent extends Component<any, any> {
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div><span>sts rocks</span></div></div>");
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
});
test("default slot work with text nodes", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="Parent">
<Dialog>sts rocks</Dialog>
</div>
<div t-name="Dialog"><t t-slot="default"/></div>
</templates>
`);
class Dialog extends Component<any, any> {}
class Parent extends Component<any, any> {
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div>sts rocks</div></div>");
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
});
test("multiple roots are allowed in a named slot", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="Parent">
<Dialog>
<t t-set="content">
<span>sts</span>
<span>rocks</span>
</t>
</Dialog>
</div>
<div t-name="Dialog"><t t-slot="content"/></div>
</templates>
`);
class Dialog extends Component<any, any> {}
class Parent extends Component<any, any> {
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div><span>sts</span><span>rocks</span></div></div>");
expect(QWeb.slots["1_content"].toString()).toMatchSnapshot();
});
test("multiple roots are allowed in a default slot", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="Parent">
<Dialog>
<span>sts</span>
<span>rocks</span>
</Dialog>
</div>
<div t-name="Dialog"><t t-slot="default"/></div>
</templates>
`);
class Dialog extends Component<any, any> {}
class Parent extends Component<any, any> {
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div><span>sts</span><span>rocks</span></div></div>");
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
});
test("missing slots are ignored", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="Parent">
<Dialog/>
</div>
<span t-name="Dialog">
<t t-slot="default"/>
<span>some content</span>
<t t-slot="footer"/>
</span>
</templates>
`);
class Dialog extends Component<any, any> {}
class Parent extends Component<any, any> {
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span><span>some content</span></span></div>");
});
test("t-debug on a t-set (defining a slot)", async () => {
const consoleLog = console.log;
console.log = jest.fn();
env.qweb.addTemplates(`
<templates>
<div t-name="Parent">
<Dialog><t t-set="content" t-debug="">abc</t></Dialog>
</div>
<span t-name="Dialog">
<t t-slot="content"/>
</span>
</templates>
`);
class Dialog extends Component<any, any> {}
class Parent extends Component<any, any> {
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(`
<templates>
<div t-name="Parent">
<Child>
<GrandChild/>
</Child>
</div>
<div t-name="Child"><t t-slot="default"/></div>
<div t-name="GrandChild">Grand Child</div>
</templates>
`);
class Child extends Component<any, any> {}
class GrandChild extends Component<any, any> {}
class Parent extends Component<any, any> {
static components = { Child, GrandChild };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div><div>Grand Child</div></div></div>");
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<any, any> {
static template = xml`<span t-esc="props.val"/>`;
constructor(parent, props) {
super(parent, props);
slot = this;
}
}
class GrandChild extends Component<any, any> {
static template = xml`<div><t t-slot="default"/></div>`;
}
class Child extends Component<any, any> {
static components = { GrandChild };
static template = xml`
<GrandChild>
<t t-slot="default"/>
</GrandChild>`;
}
class Parent extends Component<any, any> {
static components = { Child, Slot };
static template = xml`<Child><Slot val="state.val"/></Child>`;
state = useState({ val: 3 });
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>3</span></div>");
expect(slot.__owl__.parent).toBeInstanceOf(GrandChild);
});
test("slot are properly rendered if inner props are changed", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="SomeComponent">
SC:<t t-esc="props.val"/>
</div>
<div t-name="GenericComponent">
<t t-slot="default" />
</div>
<div t-name="App">
<button t-on-click="inc">Inc[<t t-esc="state.val"/>]</button>
<GenericComponent>
<SomeComponent val="state.val"/>
</GenericComponent>
</div>
</templates>
`);
class SomeComponent extends Component<any, any> {}
class GenericComponent extends Component<any, any> {}
class App extends Component<any, any> {
static components = { GenericComponent, SomeComponent };
state = useState({ val: 4 });
inc() {
this.state.val++;
}
}
const app = new App();
await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div><button>Inc[4]</button><div><div> SC:4</div></div></div>");
(<any>fixture.querySelector("button")).click();
await nextTick();
expect(fixture.innerHTML).toBe("<div><button>Inc[5]</button><div><div> SC:5</div></div></div>");
});
test("slots and wrapper components", async () => {
class Link extends Component<any, any> {
static template = xml`
<a href="abc">
<t t-slot="default"/>
</a>`;
}
class A extends Component<any, any> {
static template = xml`<Link>hey</Link>`;
static components = { Link: Link };
}
const a = new A();
await a.mount(fixture);
expect(fixture.innerHTML).toBe(`<a href="abc">hey</a>`);
});
test("template can just return a slot", async () => {
class Child extends Component<any, any> {
static template = xml`<span><t t-esc="props.value"/></span>`;
}
class SlotComponent extends Component<any, any> {
static template = xml`<t t-slot="default"/>`;
}
class Parent extends Component<any, any> {
static template = xml`
<div>
<SlotComponent><Child value="state.value"/></SlotComponent>
</div>`;
static components = { SlotComponent, Child };
state = useState({ value: 3 });
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>3</span></div>");
expect(QWeb.TEMPLATES[SlotComponent.template].fn.toString()).toMatchSnapshot();
parent.state.value = 5;
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>5</span></div>");
});
test("multiple slots containing components", async () => {
class C extends Component<any, any> {
static template = xml`<span><t t-esc="props.val"/></span>`;
}
class B extends Component<any, any> {
static template = xml`<div><t t-slot="s1"/><t t-slot="s2"/></div>`;
}
class A extends Component<any, any> {
static template = xml`
<B>
<t t-set="s1"><C val="1"/></t>
<t t-set="s2"><C val="2"/></t>
</B>`;
static components = { B, C };
}
const a = new A();
await a.mount(fixture);
expect(fixture.innerHTML).toBe(`<div><span>1</span><span>2</span></div>`);
});
test("slots in t-foreach and re-rendering", async () => {
class Child extends Component<any, any> {
static template = xml`<span><t t-esc="state.val"/><t t-slot="default"/></span>`;
state = useState({ val: "A" });
mounted() {
this.state.val = "B";
}
}
class Parent extends Component<any, any> {
static components = { Child };
static template = xml`
<div>
<t t-foreach="Array(2)" t-as="n" t-key="n_index">
<Child><t t-esc="n_index"/></Child>
</t>
</div>`;
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>A0</span><span>A1</span></div>");
await nextTick(); // wait for the changes triggered in mounted to be applied
expect(fixture.innerHTML).toBe("<div><span>B0</span><span>B1</span></div>");
});
test("slots in t-foreach with t-set and re-rendering", async () => {
class Child extends Component<any, any> {
static template = xml`
<span>
<t t-esc="state.val"/>
<t t-slot="default"/>
</span>`;
state = useState({ val: "A" });
mounted() {
this.state.val = "B";
}
}
class ParentWidget extends Component<any, any> {
static components = { Child };
static template = xml`
<div>
<t t-foreach="Array(2)" t-as="n" t-key="n_index">
<t t-set="dummy" t-value="n_index"/>
<Child><t t-esc="dummy"/></Child>
</t>
</div>`;
}
const widget = new ParentWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>A0</span><span>A1</span></div>");
await nextTick(); // wait for changes triggered in mounted to be applied
expect(fixture.innerHTML).toBe("<div><span>B0</span><span>B1</span></div>");
});
test("nested slots in same template", async () => {
let child, child2, child3;
class Child extends Component<any, any> {
static template = xml`
<span id="c1">
<div>
<t t-slot="default"/>
</div>
</span>`;
constructor(parent, props) {
super(parent, props);
child = this;
}
}
class Child2 extends Component<any, any> {
static template = xml`
<span id="c2">
<t t-slot="default"/>
</span>`;
constructor(parent, props) {
super(parent, props);
child2 = this;
}
}
class Child3 extends Component<any, any> {
static template = xml`
<span>Child 3</span>`;
constructor(parent, props) {
super(parent, props);
child3 = this;
}
}
class Parent extends Component<any, any> {
static components = { Child, Child2, Child3 };
static template = xml`
<span id="parent">
<Child>
<Child2>
<Child3/>
</Child2>
</Child>
</span>`;
}
const widget = new Parent();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe(
'<span id="parent"><span id="c1"><div><span id="c2"><span>Child 3</span></span></div></span></span>'
);
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<any, any> {
static template = xml`
<span>Child 3</span>`;
constructor(parent, props) {
super(parent, props);
child3 = this;
}
}
class Modal extends Component<any, any> {
static template = xml`
<span id="modal">
<t t-slot="default"/>
</span>`;
constructor(parent, props) {
super(parent, props);
modal = this;
}
}
class Portal extends Component<any, any> {
static template = xml`
<span id="portal">
<t t-slot="default"/>
</span>`;
constructor(parent, props) {
super(parent, props);
portal = this;
}
}
class Dialog extends Component<any, any> {
static components = { Modal, Portal };
static template = xml`
<span id="c2">
<Modal>
<Portal>
<t t-slot="default"/>
</Portal>
</Modal>
</span>`;
}
class Parent extends Component<any, any> {
static components = { Child3, Dialog };
static template = xml`
<span id="c1">
<Dialog>
<Child3/>
</Dialog>
</span>`;
}
const widget = new Parent();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe(
'<span id="c1"><span id="c2"><span id="modal"><span id="portal"><span>Child 3</span></span></span></span></span>'
);
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<any, any> {
static template = xml`
<span>Child 3</span>`;
constructor(parent, props) {
super(parent, props);
child3 = this;
}
}
class Dialog extends Component<any, any> {
static template = xml`
<span id="c2">
<t t-slot="default"/>
</span>`;
}
class Parent extends Component<any, any> {
static components = { Child3, Dialog };
static template = xml`
<span id="c1">
<Dialog>
<Child3 val="state.lol"/>
</Dialog>
</span>`;
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<any, any> {
static template = xml`<span><t t-slot="default"/></span>`;
}
class A extends Component<any, any> {
static template = xml`
<div>
<B>
<t t-slot="default"/>
</B>
</div>`;
static components = { B };
}
class Parent extends Component<any, any> {
static template = xml`
<div>
<t t-set="test" t-value="state.name"/>
<A>
<p>hey<t t-esc="test"/></p>
</A>
</div>`;
static components = { A };
state = { name: "aaron" };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div><span><p>heyaaron</p></span></div></div>");
});
});
+40
View File
@@ -429,4 +429,44 @@ describe("observer", () => {
obj.a.push(2);
}).toThrow('Observed state cannot be changed here! (key: "1", val: "2")');
});
test("functions are not observed, but are properly bound", () => {
const observer = new Observer();
const obj: any = observer.observe({ m: new Map() });
expect(observer.rev).toBe(1);
obj.m.set("a", 1);
expect(obj.m.get("a")).toBe(1);
// should be 2, but we do not support reactivity on maps/weakmaps/set/weakset
expect(observer.rev).toBe(1);
});
test("functions are correctly bound", async () => {
const observer = new Observer();
const obj: any = observer.observe({ set: new Set() });
expect(observer.revNumber(obj)).toBe(1);
obj.set.add('mojo');
expect(observer.revNumber(obj)).toBe(1);
const steps: string[] = [];
obj.set.forEach((elm)=>{steps.push(elm)});
expect(steps).toStrictEqual(['mojo']);
obj.set.clear();
expect(observer.revNumber(obj)).toBe(1);
});
test("functions are correctly bound", async () => {
const observer = new Observer();
const obj: any = observer.observe({ set: [] });
expect(observer.revNumber(obj)).toBe(1);
obj.set.push('mojo');
expect(observer.revNumber(obj)).toBe(2);
const steps: string[] = [];
obj.set.forEach((elm)=>{steps.push(elm)});
expect(steps).toStrictEqual(['mojo']);
});
});
+5 -6
View File
@@ -1,4 +1,4 @@
import { Env } from "../src/component/component";
import { Env, Component } from "../src/component/component";
import { scheduler } from "../src/component/scheduler";
import { EvalContext, QWeb } from "../src/qweb/qweb";
import { CompilationContext } from "../src/qweb/compilation_context";
@@ -25,6 +25,7 @@ beforeEach(() => {
slots = Object.assign({}, QWeb.slots);
nextId = QWeb.nextId;
TEMPLATES = Object.assign({}, QWeb.TEMPLATES);
Component.scheduler.tasks = [];
});
afterEach(() => {
@@ -32,6 +33,7 @@ afterEach(() => {
QWeb.slots = slots;
QWeb.nextId = nextId;
QWeb.TEMPLATES = TEMPLATES;
Component.scheduler.tasks = [];
});
// helpers
@@ -121,11 +123,8 @@ export function renderToString(
context: EvalContext = {},
extra?: any
): string {
const node = renderToDOM(qweb, t, context, extra);
const result = node instanceof Text ? node.textContent! : node.outerHTML;
if (result !== qweb.renderToString(t, context, extra)) {
throw new Error("HTML string returned by renderToString helper does not match QWeb render");
}
const result = qweb.renderToString(t, context, extra);
expect(qweb.templates[t].fn.toString()).toMatchSnapshot();
return result;
}
+209 -72
View File
@@ -347,6 +347,29 @@ exports[`attributes tuple variable 1`] = `
}"
`;
exports[`attributes various escapes 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let scope = Object.create(context);
var h = this.h;
var _1 = '<foo';
var _2 = scope['bar'];
var _3 = \`<\${scope['baz']}>\`;
var _4 = scope['qux'];
let c5 = [], p5 = {key:5,attrs:{foo: _1,bar: _2,baz: _3}};
if (_4 instanceof Array) {
p5.attrs[_4[0]] = _4[1];
} else {
for (let key in _4) {
p5.attrs[key] = _4[key];
}
}
var vn5 = h('div', p5, c5);
return vn5;
}"
`;
exports[`debugging t-debug 1`] = `
"function anonymous(context, extra
) {
@@ -1441,6 +1464,36 @@ exports[`t-call (template calling t-call with t-set inside and outside. 2 1`] =
}"
`;
exports[`t-call (template calling t-call, conditional and t-set in t-call body 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"caller\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
scope.v1 = 'elif';
if (scope.v1==='if') {
this.subTemplates['callee1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1}));
}
else if (scope.v1==='elif') {
{
const _origScope5 = scope;
scope = Object.assign(Object.create(context), scope);
{
let c__0 = [];
scope.v = 'success';
scope[utils.zero] = c__0;
}
this.subTemplates['callee2'].call(this, scope, Object.assign({}, extra, {parentNode: c1}));
scope = _origScope5;
}
}
return vn1;
}"
`;
exports[`t-call (template calling t-call, global templates 1`] = `
"function anonymous(context, extra
) {
@@ -1553,6 +1606,22 @@ exports[`t-call (template calling with used set body 1`] = `
}"
`;
exports[`t-esc escaping 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('span', p1, c1);
var _2 = scope['var'];
if (_2 || _2 === 0) {
c1.push({text: _2});
}
return vn1;
}"
`;
exports[`t-esc escaping on a node 1`] = `
"function anonymous(context, extra
) {
@@ -2011,11 +2080,12 @@ exports[`t-on can bind event handler 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
var h = this.h;
let c1 = [], p1 = {key:1,on:{}};
var vn1 = h('button', p1, c1);
extra.handlers['click' + 1] = extra.handlers['click' + 1] || function (e) {if (!context.__owl__.isMounted){return}const fn = context['add'];if (fn) { fn.call(context, e); } else { context.add; }};
p1.on['click'] = extra.handlers['click' + 1];
extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['add'](e);};
p1.on['click'] = extra.handlers['click__2__'];
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2025,12 +2095,13 @@ exports[`t-on can bind handlers with arguments 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1,on:{}};
var vn1 = h('button', p1, c1);
const handler2 = context['add'] && context['add'].bind(context, 5);
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}if (handler2) { handler2(e); } else { context.add(5); }};
let args2 = [5];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['add'](...args2, e);};
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2040,12 +2111,13 @@ exports[`t-on can bind handlers with empty object (with non empty inner string)
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1,on:{}};
var vn1 = h('button', p1, c1);
const handler2 = context['doSomething'] && context['doSomething'].bind(context, {});
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}if (handler2) { handler2(e); } else { context.doSomething({ }); }};
let args2 = [{}];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](...args2, e);};
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2055,12 +2127,13 @@ exports[`t-on can bind handlers with empty object 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1,on:{}};
var vn1 = h('button', p1, c1);
const handler2 = context['doSomething'] && context['doSomething'].bind(context, {});
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}if (handler2) { handler2(e); } else { context.doSomething({}); }};
let args2 = [{}];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](...args2, e);};
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2070,6 +2143,7 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1};
@@ -2097,8 +2171,8 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = `
let c8 = [], p8 = {key:nodeKey6,on:{}};
var vn8 = h('a', p8, c8);
c7.push(vn8);
const handler9 = context['activate'] && context['activate'].bind(context, scope['action']);
p8.on['click'] = function (e) {if (!context.__owl__.isMounted){return}if (handler9) { handler9(e); } else { context.activate(action); }};
let args9 = [scope['action']];
p8.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['activate'](...args9, e);};
c8.push({text: \`link\`});
}
scope = _origScope5;
@@ -2110,12 +2184,13 @@ exports[`t-on can bind handlers with object arguments 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1,on:{}};
var vn1 = h('button', p1, c1);
const handler2 = context['add'] && context['add'].bind(context, {val:5});
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}if (handler2) { handler2(e); } else { context.add({val: 5}); }};
let args2 = [{val:5}];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['add'](...args2, e);};
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2125,13 +2200,14 @@ exports[`t-on can bind two event handlers 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
var h = this.h;
let c1 = [], p1 = {key:1,on:{}};
var vn1 = h('button', p1, c1);
extra.handlers['click' + 1] = extra.handlers['click' + 1] || function (e) {if (!context.__owl__.isMounted){return}const fn = context['handleClick'];if (fn) { fn.call(context, e); } else { context.handleClick; }};
p1.on['click'] = extra.handlers['click' + 1];
extra.handlers['dblclick' + 1] = extra.handlers['dblclick' + 1] || function (e) {if (!context.__owl__.isMounted){return}const fn = context['handleDblClick'];if (fn) { fn.call(context, e); } else { context.handleDblClick; }};
p1.on['dblclick'] = extra.handlers['dblclick' + 1];
extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['handleClick'](e);};
p1.on['click'] = extra.handlers['click__2__'];
extra.handlers['dblclick__3__'] = extra.handlers['dblclick__3__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['handleDblClick'](e);};
p1.on['dblclick'] = extra.handlers['dblclick__3__'];
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2141,11 +2217,12 @@ exports[`t-on handler is bound to proper owner 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
var h = this.h;
let c1 = [], p1 = {key:1,on:{}};
var vn1 = h('button', p1, c1);
extra.handlers['click' + 1] = extra.handlers['click' + 1] || function (e) {if (!context.__owl__.isMounted){return}const fn = context['add'];if (fn) { fn.call(context, e); } else { context.add; }};
p1.on['click'] = extra.handlers['click' + 1];
extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['add'](e);};
p1.on['click'] = extra.handlers['click__2__'];
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2155,6 +2232,7 @@ exports[`t-on t-on combined with t-esc 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1};
@@ -2162,11 +2240,11 @@ exports[`t-on t-on combined with t-esc 1`] = `
let c2 = [], p2 = {key:2,on:{}};
var vn2 = h('button', p2, c2);
c1.push(vn2);
extra.handlers['click' + 2] = extra.handlers['click' + 2] || function (e) {if (!context.__owl__.isMounted){return}const fn = context['onClick'];if (fn) { fn.call(context, e); } else { context.onClick; }};
p2.on['click'] = extra.handlers['click' + 2];
var _3 = scope['text'];
if (_3 || _3 === 0) {
c2.push({text: _3});
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__'];
var _4 = scope['text'];
if (_4 || _4 === 0) {
c2.push({text: _4});
}
return vn1;
}"
@@ -2184,11 +2262,11 @@ exports[`t-on t-on combined with t-raw 1`] = `
let c2 = [], p2 = {key:2,on:{}};
var vn2 = h('button', p2, c2);
c1.push(vn2);
extra.handlers['click' + 2] = extra.handlers['click' + 2] || function (e) {if (!context.__owl__.isMounted){return}const fn = context['onClick'];if (fn) { fn.call(context, e); } else { context.onClick; }};
p2.on['click'] = extra.handlers['click' + 2];
var _3 = scope['html'];
if (_3 || _3 === 0) {
c2.push(...utils.htmlToVDOM(_3));
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__'];
var _4 = scope['html'];
if (_4 || _4 === 0) {
c2.push(...utils.htmlToVDOM(_4));
}
return vn1;
}"
@@ -2198,6 +2276,7 @@ exports[`t-on t-on with empty handler (only modifiers) 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
@@ -2218,8 +2297,7 @@ exports[`t-on t-on with inline statement (function call) 1`] = `
var h = this.h;
let c1 = [], p1 = {key:1,on:{}};
var vn1 = h('button', p1, c1);
const handler2 = context['state.incrementCounter'] && context['state.incrementCounter'].bind(context, 2);
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}if (handler2) { handler2(e); } else { context.state.incrementCounter(2); }};
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}scope['state'].incrementCounter(2)};
c1.push({text: \`Click\`});
return vn1;
}"
@@ -2229,32 +2307,61 @@ exports[`t-on t-on with inline statement 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1,on:{}};
var vn1 = h('button', p1, c1);
extra.handlers['click' + 1] = extra.handlers['click' + 1] || function (e) {if (!context.__owl__.isMounted){return}const fn = context['state.counter++'];if (fn) { fn.call(context, e); } else { context.state.counter++; }};
p1.on['click'] = extra.handlers['click' + 1];
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}scope['state'].counter++};
c1.push({text: \`Click\`});
return vn1;
}"
`;
exports[`t-on t-on with inline statement, part 2 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1,on:{}};
var vn1 = h('button', p1, c1);
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}scope['state'].flag=!scope['state'].flag};
c1.push({text: \`Toggle\`});
return vn1;
}"
`;
exports[`t-on t-on with inline statement, part 3 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1,on:{}};
var vn1 = h('button', p1, c1);
p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}scope['state'].n=scope['someFunction'](3)};
c1.push({text: \`Toggle\`});
return vn1;
}"
`;
exports[`t-on t-on with prevent and self modifiers (order matters) 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
let c2 = [], p2 = {key:2,on:{}};
var vn2 = h('button', p2, c2);
c1.push(vn2);
extra.handlers['click' + 2] = extra.handlers['click' + 2] || function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();if (e.target !== this.elm) {return}const fn = context['onClick'];if (fn) { fn.call(context, e); } else { context.onClick; }};
p2.on['click'] = extra.handlers['click' + 2];
let c3 = [], p3 = {key:3};
var vn3 = h('span', p3, c3);
c2.push(vn3);
c3.push({text: \`Button\`});
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();if (e.target !== this.elm) {return}utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__'];
let c4 = [], p4 = {key:4};
var vn4 = h('span', p4, c4);
c2.push(vn4);
c4.push({text: \`Button\`});
return vn1;
}"
`;
@@ -2263,27 +2370,28 @@ exports[`t-on t-on with prevent and/or stop modifiers 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
let c2 = [], p2 = {key:2,on:{}};
var vn2 = h('button', p2, c2);
c1.push(vn2);
extra.handlers['click' + 2] = extra.handlers['click' + 2] || function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();const fn = context['onClickPrevented'];if (fn) { fn.call(context, e); } else { context.onClickPrevented; }};
p2.on['click'] = extra.handlers['click' + 2];
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();utils.getComponent(context)['onClickPrevented'](e);};
p2.on['click'] = extra.handlers['click__3__'];
c2.push({text: \`Button 1\`});
let c3 = [], p3 = {key:3,on:{}};
var vn3 = h('button', p3, c3);
c1.push(vn3);
extra.handlers['click' + 3] = extra.handlers['click' + 3] || function (e) {if (!context.__owl__.isMounted){return}e.stopPropagation();const fn = context['onClickStopped'];if (fn) { fn.call(context, e); } else { context.onClickStopped; }};
p3.on['click'] = extra.handlers['click' + 3];
c3.push({text: \`Button 2\`});
let c4 = [], p4 = {key:4,on:{}};
var vn4 = h('button', p4, c4);
c1.push(vn4);
extra.handlers['click' + 4] = extra.handlers['click' + 4] || function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();e.stopPropagation();const fn = context['onClickPreventedAndStopped'];if (fn) { fn.call(context, e); } else { context.onClickPreventedAndStopped; }};
p4.on['click'] = extra.handlers['click' + 4];
c4.push({text: \`Button 3\`});
extra.handlers['click__5__'] = extra.handlers['click__5__'] || function (e) {if (!context.__owl__.isMounted){return}e.stopPropagation();utils.getComponent(context)['onClickStopped'](e);};
p4.on['click'] = extra.handlers['click__5__'];
c4.push({text: \`Button 2\`});
let c6 = [], p6 = {key:6,on:{}};
var vn6 = h('button', p6, c6);
c1.push(vn6);
extra.handlers['click__7__'] = extra.handlers['click__7__'] || function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();e.stopPropagation();utils.getComponent(context)['onClickPreventedAndStopped'](e);};
p6.on['click'] = extra.handlers['click__7__'];
c6.push({text: \`Button 3\`});
return vn1;
}"
`;
@@ -2292,6 +2400,7 @@ exports[`t-on t-on with prevent modifier in t-foreach 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1};
@@ -2317,8 +2426,8 @@ exports[`t-on t-on with prevent modifier in t-foreach 1`] = `
let c8 = [], p8 = {key:nodeKey6,attrs:{href: _7},on:{}};
var vn8 = h('a', p8, c8);
c1.push(vn8);
const handler9 = context['onEdit'] && context['onEdit'].bind(context, scope['project'].id);
p8.on['click'] = function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();if (handler9) { handler9(e); } else { context.onEdit(project.id); }};
let args9 = [scope['project'].id];
p8.on['click'] = function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();utils.getComponent(context)['onEdit'](...args9, e);};
c8.push({text: \` Edit \`});
var _10 = scope['project'].name;
if (_10 || _10 === 0) {
@@ -2334,18 +2443,19 @@ exports[`t-on t-on with self and prevent modifiers (order matters) 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
let c2 = [], p2 = {key:2,on:{}};
var vn2 = h('button', p2, c2);
c1.push(vn2);
extra.handlers['click' + 2] = extra.handlers['click' + 2] || function (e) {if (!context.__owl__.isMounted){return}if (e.target !== this.elm) {return}e.preventDefault();const fn = context['onClick'];if (fn) { fn.call(context, e); } else { context.onClick; }};
p2.on['click'] = extra.handlers['click' + 2];
let c3 = [], p3 = {key:3};
var vn3 = h('span', p3, c3);
c2.push(vn3);
c3.push({text: \`Button\`});
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}if (e.target !== this.elm) {return}e.preventDefault();utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__'];
let c4 = [], p4 = {key:4};
var vn4 = h('span', p4, c4);
c2.push(vn4);
c4.push({text: \`Button\`});
return vn1;
}"
`;
@@ -2354,27 +2464,54 @@ exports[`t-on t-on with self modifier 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
let c2 = [], p2 = {key:2,on:{}};
var vn2 = h('button', p2, c2);
c1.push(vn2);
extra.handlers['click' + 2] = extra.handlers['click' + 2] || function (e) {if (!context.__owl__.isMounted){return}const fn = context['onClick'];if (fn) { fn.call(context, e); } else { context.onClick; }};
p2.on['click'] = extra.handlers['click' + 2];
let c3 = [], p3 = {key:3};
var vn3 = h('span', p3, c3);
c2.push(vn3);
c3.push({text: \`Button\`});
let c4 = [], p4 = {key:4,on:{}};
var vn4 = h('button', p4, c4);
c1.push(vn4);
extra.handlers['click' + 4] = extra.handlers['click' + 4] || function (e) {if (!context.__owl__.isMounted){return}if (e.target !== this.elm) {return}const fn = context['onClickSelf'];if (fn) { fn.call(context, e); } else { context.onClickSelf; }};
p4.on['click'] = extra.handlers['click' + 4];
let c5 = [], p5 = {key:5};
var vn5 = h('span', p5, c5);
c4.push(vn5);
c5.push({text: \`Button\`});
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__'];
let c4 = [], p4 = {key:4};
var vn4 = h('span', p4, c4);
c2.push(vn4);
c4.push({text: \`Button\`});
let c5 = [], p5 = {key:5,on:{}};
var vn5 = h('button', p5, c5);
c1.push(vn5);
extra.handlers['click__6__'] = extra.handlers['click__6__'] || function (e) {if (!context.__owl__.isMounted){return}if (e.target !== this.elm) {return}utils.getComponent(context)['onClickSelf'](e);};
p5.on['click'] = extra.handlers['click__6__'];
let c7 = [], p7 = {key:7};
var vn7 = h('span', p7, c7);
c5.push(vn7);
c7.push({text: \`Button\`});
return vn1;
}"
`;
exports[`t-on t-on with t-call 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"main\\"
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1}));
return vn1;
}"
`;
exports[`t-on t-on, with arguments and t-call 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"main\\"
let scope = Object.create(context);
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1}));
return vn1;
}"
`;
+98 -14
View File
@@ -120,9 +120,11 @@ describe("t-esc", () => {
expect(renderToString(qweb, "test", { var: "ok" })).toBe("<span>ok</span>");
});
test.skip("escaping", () => {
test("escaping", () => {
qweb.addTemplate("test", `<span><t t-esc="var"/></span>`);
expect(renderToString(qweb, "test", { var: "<ok>" })).toBe("<span>&lt;ok&gt;</span>");
expect(renderToString(qweb, "test", { var: "<ok>abc</ok>" })).toBe(
"<span>&amp;lt;ok&amp;gt;abc&amp;lt;/ok&amp;gt;</span>"
);
});
test("escaping on a node", () => {
@@ -318,12 +320,13 @@ describe("t-set", () => {
<t t-set="v1" t-value="'after'"/>
<t t-set="v3" t-value="true"/>
<t t-raw="v2"/>
</div>`);
</div>`
);
expect(renderToString(qweb, "test")).toBe("<div><span>before</span></div>");
});
expect(renderToString(qweb, "test")).toBe("<div><span>before</span></div>");
});
test("t-set with t-value (truthy) and body", () => {
test("t-set with t-value (truthy) and body", () => {
qweb.addTemplate(
"test",
`<div>
@@ -335,10 +338,11 @@ describe("t-set", () => {
<t t-set="v1" t-value="'after'"/>
<t t-set="v3" t-value="false"/>
<t t-raw="v2"/>
</div>`);
</div>`
);
expect(renderToString(qweb, "test")).toBe("<div>Truthy</div>");
});
expect(renderToString(qweb, "test")).toBe("<div>Truthy</div>");
});
});
describe("t-if", () => {
@@ -426,7 +430,7 @@ describe("t-if", () => {
});
test("t-esc with t-elif", () => {
qweb.addTemplate("test", `<div><t t-if="false">abc</t><t t-else="1" t-esc="'x'"/></div>`);
qweb.addTemplate("test", `<div><t t-if="false">abc</t><t t-else="" t-esc="'x'"/></div>`);
expect(renderToString(qweb, "test")).toBe("<div>x</div>");
});
@@ -618,7 +622,7 @@ describe("attributes", () => {
expect(result).toBe(`<div foo="a 0 is 1 of 2 ]"></div>`);
});
test.skip("various escapes", () => {
test("various escapes", () => {
// not needed??
qweb.addTemplate(
"test",
@@ -634,7 +638,7 @@ describe("attributes", () => {
baz: 1,
qux: { qux: "<>" }
});
const expected = `<div foo="&lt;foo" bar="&lt;bar&gt;" baz="&lt;&quot;&lt;baz&gt;&quot;&gt;" qux="&lt;&gt;"></div>`;
const expected = '<div foo="<foo" bar="0" baz="<1>" qux="<>"></div>';
expect(result).toBe(expected);
});
@@ -880,6 +884,23 @@ describe("t-call (template calling", () => {
expect(trim(renderToString(qweb, "abcd"))).toBe(expected);
});
test("t-call, conditional and t-set in t-call body", () => {
QWeb.registerTemplate("callee1", "<div>callee1</div>");
QWeb.registerTemplate("callee2", '<div>callee2 <t t-esc="v"/></div>');
QWeb.registerTemplate(
"caller",
`<div>
<t t-set="v1" t-value="'elif'"/>
<t t-if="v1 === 'if'" t-call="callee1" />
<t t-elif="v1 === 'elif'" t-call="callee2" >
<t t-set="v" t-value="'success'" />
</t>
</div>`
);
const rendered = renderToString(qweb, "caller");
expect(rendered).toBe(`<div><div>callee2 success</div></div>`);
});
test("t-call with t-set inside and outside", () => {
qweb.addTemplates(`
<templates>
@@ -1272,6 +1293,69 @@ describe("t-on", () => {
expect(owner.state.counter).toBe(2);
});
test("t-on with inline statement, part 2", () => {
qweb.addTemplate("test", `<button t-on-click="state.flag = !state.flag">Toggle</button>`);
let owner = {
state: {
flag: true
}
};
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
expect(owner.state.flag).toBe(true);
(<HTMLElement>node).click();
expect(owner.state.flag).toBe(false);
(<HTMLElement>node).click();
expect(owner.state.flag).toBe(true);
});
test("t-on with inline statement, part 3", () => {
qweb.addTemplate("test", `<button t-on-click="state.n = someFunction(3)">Toggle</button>`);
let owner = {
someFunction(n) {
return n + 1;
},
state: {
n: 11
}
};
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
expect(owner.state.n).toBe(11);
(<HTMLElement>node).click();
expect(owner.state.n).toBe(4);
});
test("t-on with t-call", async () => {
expect.assertions(2);
qweb.addTemplate("sub", `<p t-on-click="update">lucas</p>`);
qweb.addTemplate("main", `<div><t t-call="sub"/></div>`);
let owner = {
update() {
expect(this).toBe(owner);
}
};
const node = renderToDOM(qweb, "main", owner, { handlers: [] });
(<HTMLElement>node).querySelector("p")!.click();
});
test("t-on, with arguments and t-call", async () => {
expect.assertions(3);
qweb.addTemplate("sub", `<p t-on-click="update(value)">lucas</p>`);
qweb.addTemplate("main", `<div><t t-call="sub"/></div>`);
let owner = {
update(val) {
expect(this).toBe(owner);
expect(val).toBe(444);
},
value: 444
};
const node = renderToDOM(qweb, "main", owner, { handlers: [] });
(<HTMLElement>node).querySelector("p")!.click();
});
test("t-on with prevent and/or stop modifiers", async () => {
expect.assertions(7);
qweb.addTemplate(
@@ -1644,7 +1728,7 @@ describe("debugging", () => {
console.log = jest.fn();
qweb.addTemplate(
"test",
`<div t-debug="1"><t t-if="true"><span t-debug="1">hey</span></t></div>`
`<div t-debug=""><t t-if="true"><span t-debug="">hey</span></t></div>`
);
qweb.render("test");
expect(qweb.templates.test.fn.toString()).toMatchSnapshot();
@@ -1658,7 +1742,7 @@ describe("debugging", () => {
console.log = jest.fn();
qweb.addTemplates(`
<templates>
<p t-name="sub" t-debug="1">coucou</p>
<p t-name="sub" t-debug="">coucou</p>
<div t-name="test">
<t t-call="sub"/>
</div>
+7
View File
@@ -170,4 +170,11 @@ describe("expression evaluation", () => {
"scope['list'].map((elem,index)=>elem+index)"
);
});
test("assignation", () => {
expect(compileExpr("a = b", {})).toBe("scope['a']=scope['b']");
expect(compileExpr("a += b", {})).toBe("scope['a']+=scope['b']");
expect(compileExpr("a -= b", {})).toBe("scope['a']-=scope['b']");
expect(compileExpr("a.b = !a.b", {})).toBe("scope['a'].b=!scope['a'].b");
});
});
+8 -8
View File
@@ -7,16 +7,16 @@ exports[`Link component can render simple cases 1`] = `
let utils = this.constructor.utils;
let scope = Object.create(context);
var h = this.h;
let _5 = utils.toObj({'router-link-active':scope['isActive']});
var _6 = scope['href'];
let c7 = [], p7 = {key:7,attrs:{href: _6},class:_5,on:{}};
var vn7 = h('a', p7, c7);
extra.handlers['click' + 7] = extra.handlers['click' + 7] || function (e) {if (!context.__owl__.isMounted){return}const fn = context['navigate'];if (fn) { fn.call(context, e); } else { context.navigate; }};
p7.on['click'] = extra.handlers['click' + 7];
let _4 = utils.toObj({'router-link-active':scope['isActive']});
var _5 = scope['href'];
let c6 = [], p6 = {key:6,attrs:{href: _5},class:_4,on:{}};
var vn6 = h('a', p6, c6);
extra.handlers['click__7__'] = extra.handlers['click__7__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['navigate'](e);};
p6.on['click'] = extra.handlers['click__7__'];
const slot8 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
if (slot8) {
slot8.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c7, parent: extra.parent || context}));
slot8.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c6, parent: extra.parent || context}));
}
return vn7;
return vn6;
}"
`;
@@ -11,33 +11,33 @@ exports[`RouteComponent can render simple cases 1`] = `
let result;
var h = this.h;
if (scope['routeComponent']) {
const nodeKey5 = scope['env'].router.currentRouteName;
//COMPONENT
let k7 = \`__8__\` + nodeKey5;
let w6 = k7 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k7]] : false;
let vn9 = {};
result = vn9;
let props6 = Object.assign({}, scope['env'].router.currentParams);
if (w6 && w6.__owl__.currentFiber && !w6.__owl__.vnode) {
w6.destroy();
w6 = false;
const nodeKey4 = scope['env'].router.currentRouteName;
// Component 'routeComponent'
let k6 = \`__6__\` + nodeKey4;
let w5 = k6 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k6]] : false;
let vn7 = {};
result = vn7;
let props5 = Object.assign({}, scope['env'].router.currentParams);
if (w5 && w5.__owl__.currentFiber && !w5.__owl__.vnode) {
w5.destroy();
w5 = false;
}
if (w6) {
w6.__updateProps(props6, extra.fiber, undefined);
let pvnode = w6.__owl__.pvnode;
utils.defineProxy(vn9, pvnode);
if (w5) {
w5.__updateProps(props5, extra.fiber, undefined);
let pvnode = w5.__owl__.pvnode;
utils.defineProxy(vn7, pvnode);
} else {
let componentKey6 = \`routeComponent\`;
let W6 = context.constructor.components[componentKey6] || QWeb.components[componentKey6]|| scope['routeComponent'];
if (!W6) {throw new Error('Cannot find the definition of component \\"' + componentKey6 + '\\"')}
w6 = new W6(parent, props6);
parent.__owl__.cmap[k7] = w6.__owl__.id;
let fiber = w6.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: k7, hook: {remove() {},destroy(vn) {w6.destroy();}}});
utils.defineProxy(vn9, pvnode);
w6.__owl__.pvnode = pvnode;
let componentKey5 = \`routeComponent\`;
let W5 = context.constructor.components[componentKey5] || QWeb.components[componentKey5]|| scope['routeComponent'];
if (!W5) {throw new Error('Cannot find the definition of component \\"' + componentKey5 + '\\"')}
w5 = new W5(parent, props5);
parent.__owl__.cmap[k6] = w5.__owl__.id;
let fiber = w5.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: k6, hook: {remove() {},destroy(vn) {w5.destroy();}}});
utils.defineProxy(vn7, pvnode);
w5.__owl__.pvnode = pvnode;
}
w6.__owl__.parentLastFiberId = extra.fiber.id;
w5.__owl__.parentLastFiberId = extra.fiber.id;
}
return result;
}"
+2 -2
View File
@@ -1101,7 +1101,7 @@ const RESPONSIVE_XML = `<templates>
<t t-raw="maincontent"/>
</div>
</div>
<t t-else="1">
<t t-else="">
<t t-raw="maincontent"/>
</t>
</div>
@@ -1438,7 +1438,7 @@ const FORM_XML = `<templates>
<div>Text: <t t-esc="state.text"/></div>
<div>Other Text: <t t-esc="state.othertext"/></div>
<div>Number: <t t-esc="state.number"/></div>
<div>Boolean: <t t-if="state.bool">True</t><t t-else="1">False</t></div>
<div>Boolean: <t t-if="state.bool">True</t><t t-else="">False</t></div>
<div>Color: <t t-esc="state.color"/></div>
</div>
</templates>