mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a38c53419a | |||
| 0d0f64c0ed | |||
| 940ac64340 | |||
| c06049076a | |||
| bc04f727ac | |||
| 0bc9573a8a | |||
| 73f94fba3f | |||
| 7a16449724 | |||
| 718c765e3b | |||
| 150d620b8e | |||
| 307b936d01 | |||
| 6950f8e628 | |||
| 4e3b7c74da | |||
| 98bb2842d8 | |||
| 6e185f987b |
@@ -24,4 +24,4 @@ jobs:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install
|
||||
- run: npm run test
|
||||
- run: npm run prettier
|
||||
- run: npm run check-formatting
|
||||
|
||||
@@ -124,7 +124,7 @@ npm install @odoo/owl
|
||||
|
||||
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
||||
|
||||
- [owl-1.4.6](https://github.com/odoo/owl/releases/tag/v1.4.6)
|
||||
- [owl-1.4.11](https://github.com/odoo/owl/releases/tag/v1.4.11)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -176,9 +176,9 @@ const uuid = generateUUID();
|
||||
class MyComponent extends Component {
|
||||
static template = xml`<div data-o-${uuid}="">...</div>`;
|
||||
static style = css`
|
||||
[data-o-${uuid}] {
|
||||
color: red;
|
||||
}
|
||||
`;
|
||||
[data-o-${uuid}] {
|
||||
color: red;
|
||||
}
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "1.4.6",
|
||||
"version": "1.4.11",
|
||||
"description": "Odoo Web Library (OWL)",
|
||||
"main": "dist/owl.cjs.js",
|
||||
"browser": "dist/owl.iife.js",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# 🦉 OWL Roadmap 🦉
|
||||
|
||||
- Current version: 1.4.6
|
||||
- Current version: 1.4.11
|
||||
- Status: stable
|
||||
|
||||
This roadmap is only an attempt at predicting Owl's future. Everything may
|
||||
|
||||
@@ -767,7 +767,7 @@ export async function mount<T extends Type<Component>>(
|
||||
const { env, props, target } = params;
|
||||
let origEnv = C.hasOwnProperty("env") ? (C as any).env : null;
|
||||
if (env) {
|
||||
((C as any) as typeof Component).env = env;
|
||||
(C as any as typeof Component).env = env;
|
||||
}
|
||||
const component: Component = new C(null, props);
|
||||
if (origEnv) {
|
||||
|
||||
@@ -234,7 +234,11 @@ QWeb.addDirective({
|
||||
} else if (!name.startsWith("t-")) {
|
||||
if (name !== "class" && name !== "style") {
|
||||
// this is a prop!
|
||||
props[name] = ctx.formatExpression(value) || "undefined";
|
||||
if (value.includes("=>")) {
|
||||
props[name] = ctx.captureExpression(value);
|
||||
} else {
|
||||
props[name] = ctx.formatExpression(value) || "undefined";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,8 +243,9 @@ export class Fiber {
|
||||
}
|
||||
component.__patch(target!, fiber.vnode!);
|
||||
} else {
|
||||
if (fiber.shouldPatch) {
|
||||
component.__patch(component.__owl__.vnode!, fiber.vnode!);
|
||||
const vnode = component.__owl__.vnode;
|
||||
if (fiber.shouldPatch && vnode) {
|
||||
component.__patch(vnode, fiber.vnode!);
|
||||
// When updating a Component's props (in directive),
|
||||
// the component has a pvnode AND should be patched.
|
||||
// However, its pvnode.elm may have changed if it is a High Order Component
|
||||
|
||||
@@ -40,15 +40,16 @@ QWeb.utils.validateProps = function (Widget, props: Object) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let isValid;
|
||||
let whyInvalid;
|
||||
try {
|
||||
isValid = isValidProp(props[propName], propsDef[propName]);
|
||||
whyInvalid = whyInvalidProp(props[propName], propsDef[propName]);
|
||||
} catch (e) {
|
||||
e.message = `Invalid prop '${propName}' in component ${Widget.name} (${e.message})`;
|
||||
throw e;
|
||||
}
|
||||
if (!isValid) {
|
||||
throw new Error(`Invalid Prop '${propName}' in component '${Widget.name}'`);
|
||||
if (whyInvalid !== null) {
|
||||
whyInvalid = whyInvalid.replace(/\${propName}/g, propName);
|
||||
throw new Error(`Invalid Prop '${propName}' in component '${Widget.name}': ${whyInvalid}`);
|
||||
}
|
||||
}
|
||||
for (let propName in props) {
|
||||
@@ -60,11 +61,11 @@ QWeb.utils.validateProps = function (Widget, props: Object) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if an invidual prop value matches its (static) prop definition
|
||||
* Check why an invidual prop value doesn't match its (static) prop definition
|
||||
*/
|
||||
function isValidProp(prop, propDef): boolean {
|
||||
function whyInvalidProp(prop, propDef): string | null {
|
||||
if (propDef === true) {
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
if (typeof propDef === "function") {
|
||||
// Check if a value is constructed by some Constructor. Note that there is a
|
||||
@@ -73,43 +74,66 @@ function isValidProp(prop, propDef): boolean {
|
||||
// So, even though 1 is not an instance of Number, we want to consider that
|
||||
// it is valid.
|
||||
if (typeof prop === "object") {
|
||||
return prop instanceof propDef;
|
||||
if (prop instanceof propDef) {
|
||||
return null;
|
||||
}
|
||||
return `\${propName} is not an instance of ${propDef.name}`;
|
||||
}
|
||||
return typeof prop === propDef.name.toLowerCase();
|
||||
if (typeof prop === propDef.name.toLowerCase()) {
|
||||
return null;
|
||||
}
|
||||
return `type of \${propName} is not ${propDef.name}`;
|
||||
} else if (propDef instanceof Array) {
|
||||
// If this code is executed, this means that we want to check if a prop
|
||||
// matches at least one of its descriptor.
|
||||
let result = false;
|
||||
let reasons: string[] = [];
|
||||
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
||||
result = result || isValidProp(prop, propDef[i]);
|
||||
const why = whyInvalidProp(prop, propDef[i]);
|
||||
if (why === null) {
|
||||
return null;
|
||||
}
|
||||
reasons.push(why);
|
||||
}
|
||||
if (reasons.length > 1) {
|
||||
return reasons.slice(0, -1).join(", ") + " and " + reasons[reasons.length - 1];
|
||||
} else {
|
||||
return reasons[0];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// propsDef is an object
|
||||
if (propDef.optional && prop === undefined) {
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
let result = propDef.type ? isValidProp(prop, propDef.type) : true;
|
||||
if (propDef.validate) {
|
||||
result = result && propDef.validate(prop);
|
||||
if (propDef.type) {
|
||||
const why = whyInvalidProp(prop, propDef.type);
|
||||
if (why !== null) {
|
||||
return why;
|
||||
}
|
||||
}
|
||||
if (propDef.validate && !propDef.validate(prop)) {
|
||||
return "${propName} could not be validated by `validate` function";
|
||||
}
|
||||
if (propDef.type === Array && propDef.element) {
|
||||
for (let i = 0, iLen = prop.length; i < iLen; i++) {
|
||||
result = result && isValidProp(prop[i], propDef.element);
|
||||
const why = whyInvalidProp(prop[i], propDef.element);
|
||||
if (why !== null) {
|
||||
return why.replace(/\${propName}/g, `\${propName}[${i}]`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (propDef.type === Object && propDef.shape) {
|
||||
const shape = propDef.shape;
|
||||
for (let key in shape) {
|
||||
result = result && isValidProp(prop[key], shape[key]);
|
||||
const why = whyInvalidProp(prop[key], shape[key]);
|
||||
if (why !== null) {
|
||||
return why.replace(/\${propName}/g, `\${propName}['${key}']`);
|
||||
}
|
||||
}
|
||||
if (result) {
|
||||
for (let propName in prop) {
|
||||
if (!(propName in shape)) {
|
||||
throw new Error(`unknown prop '${propName}'`);
|
||||
}
|
||||
for (let propName in prop) {
|
||||
if (!(propName in shape)) {
|
||||
return `unknown prop \${propName}['${propName}']`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
+10
-2
@@ -104,10 +104,18 @@ export function useRef<C extends Component = Component>(name: string): Ref<C> {
|
||||
return {
|
||||
get el(): HTMLElement | null {
|
||||
const val = __owl__.refs && __owl__.refs[name];
|
||||
if (val instanceof Component) {
|
||||
return val.el;
|
||||
}
|
||||
if (val instanceof HTMLElement) {
|
||||
return val;
|
||||
} else if (val instanceof Component) {
|
||||
return val.el;
|
||||
}
|
||||
// Extra check in case the app was created outside an iframe but mounted into one
|
||||
// on Firefox 109+, the prototype of the element changes to use the iframe window's HTMLElement
|
||||
// see https://bugzilla.mozilla.org/show_bug.cgi?id=1813499
|
||||
const ownerWindow = (val as any)?.ownerDocument?.defaultView;
|
||||
if (ownerWindow && (val as any) instanceof ownerWindow.HTMLElement) {
|
||||
return val;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
+39
-86
@@ -44,88 +44,50 @@ function compileValueNode(value: any, node: Element, qweb: QWeb, ctx: Compilatio
|
||||
} else {
|
||||
exprID = `scope.${value.id}`;
|
||||
}
|
||||
ctx.addIf(`${exprID} != null`);
|
||||
|
||||
let protectID;
|
||||
if (value.hasBody) {
|
||||
if (ctx.escaping) {
|
||||
let protectID;
|
||||
if (value.hasBody) {
|
||||
ctx.rootContext.shouldDefineUtils = true;
|
||||
protectID = ctx.startProtectScope();
|
||||
ctx.addLine(
|
||||
`${exprID} = ${exprID} instanceof utils.VDomArray ? utils.vDomToString(${exprID}) : ${exprID};`
|
||||
);
|
||||
}
|
||||
if (ctx.parentTextNode) {
|
||||
ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
|
||||
} else if (ctx.parentNode) {
|
||||
ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`);
|
||||
} else {
|
||||
let nodeID = ctx.generateID();
|
||||
ctx.rootContext.rootNode = nodeID;
|
||||
ctx.rootContext.parentTextNode = nodeID;
|
||||
ctx.addLine(`let vn${nodeID} = {text: ${exprID}};`);
|
||||
if (ctx.rootContext.shouldDefineResult) {
|
||||
ctx.addLine(`result = vn${nodeID}`);
|
||||
}
|
||||
}
|
||||
if (value.hasBody) {
|
||||
ctx.stopProtectScope(protectID);
|
||||
}
|
||||
} else {
|
||||
ctx.rootContext.shouldDefineUtils = true;
|
||||
protectID = ctx.startProtectScope();
|
||||
ctx.addLine(
|
||||
`${exprID} = ${exprID} instanceof utils.VDomArray ? utils.vDomToString(${exprID}) : ${exprID};`
|
||||
);
|
||||
if (value.hasBody) {
|
||||
ctx.addLine(
|
||||
`const vnodeArray = ${exprID} instanceof utils.VDomArray ? ${exprID} : utils.htmlToVDOM(${exprID});`
|
||||
);
|
||||
ctx.addLine(`c${ctx.parentNode}.push(...vnodeArray);`);
|
||||
} else {
|
||||
ctx.addLine(`c${ctx.parentNode}.push(...utils.htmlToVDOM(${exprID}));`);
|
||||
}
|
||||
}
|
||||
if (node.childNodes.length) {
|
||||
// ctx.addLine(`c${ctx.parentNode}.push(...vnodeArray);`);
|
||||
ctx.addIf(`!${exprID}`);
|
||||
// if (node.childNodes.length) {
|
||||
// ctx.addElse();
|
||||
ctx.addElse();
|
||||
qweb._compileChildren(node, ctx);
|
||||
// }
|
||||
|
||||
ctx.closeIf();
|
||||
}
|
||||
if (ctx.parentTextNode) {
|
||||
ctx.addIf(`${exprID} != null`);
|
||||
ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
|
||||
ctx.closeIf();
|
||||
} else if (ctx.parentNode) {
|
||||
ctx.rootContext.shouldDefineUtils = true;
|
||||
ctx.addLine(`insertValue(c${ctx.parentNode}, ${exprID})`);
|
||||
} else {
|
||||
ctx.addIf(`${exprID} != null`);
|
||||
|
||||
let nodeID = ctx.generateID();
|
||||
ctx.rootContext.rootNode = nodeID;
|
||||
ctx.rootContext.parentTextNode = nodeID;
|
||||
ctx.addLine(`let vn${nodeID} = {text: ${exprID}};`);
|
||||
if (ctx.rootContext.shouldDefineResult) {
|
||||
ctx.addLine(`result = vn${nodeID}`);
|
||||
}
|
||||
ctx.closeIf();
|
||||
}
|
||||
if (value.hasBody) {
|
||||
ctx.stopProtectScope(protectID);
|
||||
}
|
||||
|
||||
// ctx.addIf(`${exprID} != null`);
|
||||
|
||||
// if (ctx.escaping) {
|
||||
// let protectID;
|
||||
// if (value.hasBody) {
|
||||
// ctx.rootContext.shouldDefineUtils = true;
|
||||
// protectID = ctx.startProtectScope();
|
||||
// ctx.addLine(
|
||||
// `${exprID} = ${exprID} instanceof utils.VDomArray ? utils.vDomToString(${exprID}) : ${exprID};`
|
||||
// );
|
||||
// }
|
||||
// if (ctx.parentTextNode) {
|
||||
// ctx.addLine(`vn${ctx.parentTextNode}.text += ${exprID};`);
|
||||
// } else if (ctx.parentNode) {
|
||||
// ctx.addLine(`c${ctx.parentNode}.push({text: ${exprID}});`);
|
||||
// } else {
|
||||
// let nodeID = ctx.generateID();
|
||||
// ctx.rootContext.rootNode = nodeID;
|
||||
// ctx.rootContext.parentTextNode = nodeID;
|
||||
// ctx.addLine(`let vn${nodeID} = {text: ${exprID}};`);
|
||||
// if (ctx.rootContext.shouldDefineResult) {
|
||||
// ctx.addLine(`result = vn${nodeID}`);
|
||||
// }
|
||||
// }
|
||||
// if (value.hasBody) {
|
||||
// ctx.stopProtectScope(protectID);
|
||||
// }
|
||||
// } else {
|
||||
// ctx.rootContext.shouldDefineUtils = true;
|
||||
// if (value.hasBody) {
|
||||
// ctx.addLine(
|
||||
// `const vnodeArray = ${exprID} instanceof utils.VDomArray ? ${exprID} : utils.htmlToVDOM(${exprID});`
|
||||
// );
|
||||
// ctx.addLine(`c${ctx.parentNode}.push(...vnodeArray);`);
|
||||
// } else {
|
||||
// ctx.addLine(`c${ctx.parentNode}.push(...utils.htmlToVDOM(${exprID}));`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// ctx.closeIf();
|
||||
ctx.closeIf();
|
||||
}
|
||||
|
||||
QWeb.addDirective({
|
||||
@@ -138,16 +100,6 @@ QWeb.addDirective({
|
||||
},
|
||||
});
|
||||
|
||||
QWeb.addDirective({
|
||||
name: "out",
|
||||
priority: 70,
|
||||
atNodeEncounter({ node, qweb, ctx }): boolean {
|
||||
let value = ctx.getValue(node.getAttribute("t-out")!);
|
||||
compileValueNode(value, node, qweb, ctx.subContext("escaping", true));
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
QWeb.addDirective({
|
||||
name: "raw",
|
||||
priority: 80,
|
||||
@@ -374,7 +326,8 @@ QWeb.addDirective({
|
||||
ctx.addLine(`if (!_${arrayID}) { throw new Error('QWeb error: Invalid loop expression')}`);
|
||||
let keysID = ctx.generateID();
|
||||
let valuesID = ctx.generateID();
|
||||
ctx.addLine(`let _${keysID} = _${valuesID} = _${arrayID};`);
|
||||
ctx.addLine(`let _${keysID} = _${arrayID};`);
|
||||
ctx.addLine(`let _${valuesID} = _${arrayID};`);
|
||||
ctx.addIf(`!(_${arrayID} instanceof Array)`);
|
||||
ctx.addLine(`_${keysID} = Object.keys(_${arrayID});`);
|
||||
ctx.addLine(`_${valuesID} = Object.values(_${arrayID});`);
|
||||
|
||||
@@ -84,7 +84,6 @@ export class CompilationContext {
|
||||
this.code.unshift(" let QWeb = this.constructor;");
|
||||
}
|
||||
if (this.shouldDefineUtils) {
|
||||
this.code.unshift(" let insertValue = utils.insertValue;");
|
||||
this.code.unshift(" let utils = this.constructor.utils;");
|
||||
}
|
||||
return this.code;
|
||||
@@ -163,8 +162,26 @@ export class CompilationContext {
|
||||
const tokens = compileExprToArray(expr, this.variables);
|
||||
const done = new Set();
|
||||
return tokens
|
||||
.map((tok) => {
|
||||
if (tok.varName) {
|
||||
.map((tok, i) => {
|
||||
// "this" in captured expressions should be the current component
|
||||
if (tok.value === "this") {
|
||||
if (!done.has("this")) {
|
||||
done.add("this");
|
||||
this.addLine(`const this_${argId} = utils.getComponent(context);`);
|
||||
}
|
||||
tok.value = `this_${argId}`;
|
||||
}
|
||||
// Variables that should be looked up in the scope. isLocal is for arrow
|
||||
// function arguments that should stay untouched (eg "ev => ev" should
|
||||
// not become "const ev_1 = scope['ev']; ev_1 => ev_1")
|
||||
if (
|
||||
tok.varName &&
|
||||
!tok.isLocal &&
|
||||
// HACK: for backwards compatibility, we don't capture bare methods
|
||||
// this allows them to be called with the rendering context/scope
|
||||
// as their this value.
|
||||
(!tokens[i + 1] || tokens[i + 1].type !== "LEFT_PAREN")
|
||||
) {
|
||||
if (!done.has(tok.varName)) {
|
||||
done.add(tok.varName);
|
||||
this.addLine(`const ${tok.varName}_${argId} = ${tok.value};`);
|
||||
|
||||
@@ -25,9 +25,10 @@
|
||||
// Misc types, constants and helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,eval,void,Math,RegExp,Array,Object,Date".split(
|
||||
","
|
||||
);
|
||||
const RESERVED_WORDS =
|
||||
"true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,eval,void,Math,RegExp,Array,Object,Date".split(
|
||||
","
|
||||
);
|
||||
|
||||
const WORD_REPLACEMENT = Object.assign(Object.create(null), {
|
||||
and: "&&",
|
||||
@@ -69,6 +70,7 @@ interface Token {
|
||||
size?: number;
|
||||
varName?: string;
|
||||
replace?: Function;
|
||||
isLocal?: boolean;
|
||||
}
|
||||
|
||||
const STATIC_TOKEN_MAP: { [key: string]: TKind } = Object.assign(Object.create(null), {
|
||||
@@ -253,6 +255,7 @@ const isRightSeparator = (token) =>
|
||||
* the list of variables so it does not get replaced by a lookup in the context
|
||||
*/
|
||||
export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar }): Token[] {
|
||||
const localVars = new Set<string>();
|
||||
scope = Object.create(scope);
|
||||
const tokens = tokenize(expr);
|
||||
|
||||
@@ -307,11 +310,13 @@ export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar
|
||||
if (tokens[j].type === "SYMBOL" && tokens[j].originalValue) {
|
||||
tokens[j].value = tokens[j].originalValue!;
|
||||
scope[tokens[j].value] = { id: tokens[j].value, expr: tokens[j].value };
|
||||
localVars.add(tokens[j].value);
|
||||
}
|
||||
j--;
|
||||
}
|
||||
} else {
|
||||
scope[token.value] = { id: token.value, expr: token.value };
|
||||
localVars.add(token.value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,6 +331,13 @@ export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// Mark all variables that have been used locally.
|
||||
// This assumes the expression has only one scope (incorrect but "good enough for now")
|
||||
for (const token of tokens) {
|
||||
if (token.type === "SYMBOL" && localVars.has(token.value)) {
|
||||
token.isLocal = true;
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
|
||||
+4
-28
@@ -1,9 +1,8 @@
|
||||
import { EventBus } from "../core/event_bus";
|
||||
import { h, patch, VNode } from "../vdom/index";
|
||||
import { CompilationContext } from "./compilation_context";
|
||||
import { shallowEqual, escape, _Markup } from "../utils";
|
||||
import { shallowEqual } from "../utils";
|
||||
import { addNS } from "../vdom/vdom";
|
||||
import { htmlToVDOM } from "../vdom/html_to_vdom";
|
||||
|
||||
/**
|
||||
* Owl QWeb Engine
|
||||
@@ -90,16 +89,6 @@ function isComponent(obj): boolean {
|
||||
return obj && obj.hasOwnProperty("__owl__");
|
||||
}
|
||||
|
||||
function insertValue(children: any[], value: any) {
|
||||
if (value != null) {
|
||||
if (value instanceof _Markup) {
|
||||
children.push(...htmlToVDOM(value as any));
|
||||
} else {
|
||||
children.push({ text: value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VDomArray extends Array {
|
||||
toString() {
|
||||
return vDomToString(this);
|
||||
@@ -122,7 +111,6 @@ function vDomToString(vdom: VNode[]): string {
|
||||
|
||||
const UTILS: Utils = {
|
||||
zero: Symbol("zero"),
|
||||
insertValue,
|
||||
toClassObj(expr) {
|
||||
const result = {};
|
||||
if (typeof expr === "string") {
|
||||
@@ -437,17 +425,8 @@ export class QWeb extends EventBus {
|
||||
return vnode.text!;
|
||||
}
|
||||
const node = document.createElement(vnode.sel);
|
||||
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;
|
||||
const result = patch(node, vnode);
|
||||
return (result.elm as HTMLElement).outerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -611,10 +590,7 @@ export class QWeb extends EventBus {
|
||||
if (!(dName in QWeb.DIRECTIVE_NAMES)) {
|
||||
throw new Error(`Unknown QWeb directive: '${attrName}'`);
|
||||
}
|
||||
if (
|
||||
node.tagName !== "t" &&
|
||||
(attrName === "t-esc" || attrName === "t-out" || attrName === "t-raw")
|
||||
) {
|
||||
if (node.tagName !== "t" && (attrName === "t-esc" || attrName === "t-raw")) {
|
||||
const tNode = document.implementation.createDocument(
|
||||
"http://www.w3.org/1999/xhtml",
|
||||
"t",
|
||||
|
||||
+1
-62
@@ -15,7 +15,7 @@ import { browser } from "./browser";
|
||||
export function whenReady(fn?: any) {
|
||||
return new Promise(function (resolve) {
|
||||
if (document.readyState !== "loading") {
|
||||
(resolve as any)();
|
||||
resolve();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", resolve, false);
|
||||
}
|
||||
@@ -101,64 +101,3 @@ export function shallowEqual(p1, p2): boolean {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// notable issues:
|
||||
// * objects can't be negative in JS, so !!"" -> false but
|
||||
// !!(new String) -> true, likewise markup
|
||||
// TODO (?)
|
||||
// * Markup.join / Markup#join => escapes items and returns a Markup
|
||||
// * Markup#replace => automatically escapes the replacements (difficult impl)
|
||||
export class _Markup extends String {}
|
||||
|
||||
/**
|
||||
* Returns a markup object, which acts like a String but is considered safe by
|
||||
* `_.escape`, and will therefore be injected as-is (without additional
|
||||
* escaping) in templates. Can be used to inject dynamic HTML in templates
|
||||
* (where the template itself can't), see first example.
|
||||
*
|
||||
* Can also be used as a *template tag*, in which case the literal content
|
||||
* won't be escaped but the substitutions which are not already markup objects
|
||||
* will be.
|
||||
*
|
||||
* ## WARNINGS:
|
||||
* * A markup object is a `String` (boxed) but not a `string` (primitive), they
|
||||
* typecheck differently which can be relevant.
|
||||
* * To strip out the "markupness", just call `String(markup)`.
|
||||
* * Most string operations (e.g. concatenation, `String#replace`, ...) will
|
||||
* also strip out markupness
|
||||
* * If the input is empty, returns a regular string (that way boolean tests
|
||||
* work as expected).
|
||||
*
|
||||
* @returns a markup object
|
||||
*
|
||||
* @example regular function
|
||||
* let h;
|
||||
* if (someTest) {
|
||||
* h = Markup(_t("This is a <strong>success</strong>"));
|
||||
* } else {
|
||||
* h = Markup(_t("Things did <strong>not</strong> work out"));
|
||||
* }
|
||||
* qweb.render("some_template", { message: h });
|
||||
*
|
||||
* @example template tag
|
||||
* const escaped = "<some> text";
|
||||
* const asis = Markup`some <b>text</b>`;
|
||||
* const h = Markup`Regular strings get ${escaped} but markup is injected ${asis}`;
|
||||
*/
|
||||
export function Markup(v, ...exprs) {
|
||||
if (!(v instanceof Array)) {
|
||||
return v ? new _Markup(v) : "";
|
||||
}
|
||||
const elements = [];
|
||||
let i = 0;
|
||||
for (; i < exprs.length; ++i) {
|
||||
elements.push(v[i], escape(exprs[i]));
|
||||
}
|
||||
elements.push(v[i]);
|
||||
|
||||
const s = elements.join("");
|
||||
if (!s) {
|
||||
return "";
|
||||
}
|
||||
return new _Markup(s);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['state'].s;
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
let _3 = _2;
|
||||
let _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
@@ -68,7 +69,8 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
|
||||
let key1 = i1;
|
||||
let _6 = scope['section'].blips;
|
||||
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _7 = _8 = _6;
|
||||
let _7 = _6;
|
||||
let _8 = _6;
|
||||
if (!(_6 instanceof Array)) {
|
||||
_7 = Object.keys(_6);
|
||||
_8 = Object.values(_6);
|
||||
@@ -309,7 +311,8 @@ exports[`composition sub components with some state rendered in a loop 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['state'].numbers;
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
let _3 = _2;
|
||||
let _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
@@ -597,7 +600,8 @@ exports[`other directives with t-component t-on expression captured in t-foreach
|
||||
scope.iter = 0;
|
||||
let _2 = scope['arr'];
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
let _3 = _2;
|
||||
let _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
@@ -639,7 +643,8 @@ exports[`other directives with t-component t-on expression in t-foreach 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['state'].values;
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
let _3 = _2;
|
||||
let _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
@@ -691,7 +696,8 @@ exports[`other directives with t-component t-on expression in t-foreach with t-s
|
||||
scope.bossa = 'nova';
|
||||
let _2 = scope['state'].values;
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
let _3 = _2;
|
||||
let _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
@@ -744,7 +750,8 @@ exports[`other directives with t-component t-on method call in t-foreach 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['state'].values;
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
let _3 = _2;
|
||||
let _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
@@ -1410,7 +1417,8 @@ exports[`other directives with t-component t-set outside modified in t-foreach 1
|
||||
scope.iter = 0;
|
||||
let _2 = scope['state'].values;
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
let _3 = _2;
|
||||
let _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
@@ -1446,6 +1454,405 @@ exports[`other directives with t-component t-set outside modified in t-foreach 1
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props evaluation arrow function prop captures component instance as 'this' 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);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
// Component 'Child'
|
||||
const this_2 = utils.getComponent(context);
|
||||
let w3 = '__4__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__4__']] : false;
|
||||
let props3 = {callback:value=>this_2.setValue(value),value:scope['state'].val};
|
||||
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
|
||||
w3.destroy();
|
||||
w3 = false;
|
||||
}
|
||||
if (w3) {
|
||||
w3.__updateProps(props3, extra.fiber, undefined);
|
||||
let pvnode = w3.__owl__.pvnode;
|
||||
c1.push(pvnode);
|
||||
} else {
|
||||
let componentKey3 = \`Child\`;
|
||||
let W3 = scope['Child'] || context.constructor.components[componentKey3] || QWeb.components[componentKey3];
|
||||
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
|
||||
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
c1.push(pvnode);
|
||||
w3.__owl__.pvnode = pvnode;
|
||||
}
|
||||
w3.__owl__.parentLastFiberId = extra.fiber.id;
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props evaluation arrow function prop captures component instance as 'this' 2`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"Child\\"
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c5 = [], p5 = {key:5};
|
||||
let vn5 = h('span', p5, c5);
|
||||
let _6 = scope['props'].value;
|
||||
if (_6 != null) {
|
||||
c5.push({text: _6});
|
||||
}
|
||||
return vn5;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props evaluation arrow function prop captures context component instance as 'this' inside slot 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);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
// Component 'Wrapper'
|
||||
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, utils.combine(context, scope));
|
||||
let pvnode = w2.__owl__.pvnode;
|
||||
c1.push(pvnode);
|
||||
} else {
|
||||
let componentKey2 = \`Wrapper\`;
|
||||
let W2 = scope['Wrapper'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2];
|
||||
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, utils.combine(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[`props evaluation arrow function prop captures context component instance as 'this' inside slot 2`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"Wrapper\\"
|
||||
let utils = this.constructor.utils;
|
||||
let result;
|
||||
let h = this.h;
|
||||
const slot8 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
||||
if (slot8) {
|
||||
let children9= []
|
||||
result = {}
|
||||
slot8.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: children9, parent: extra.parent || context}));
|
||||
utils.defineProxy(result, children9[0]);
|
||||
}
|
||||
return result;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props evaluation arrow function prop captures context component instance as 'this' inside slot 3`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"slot_default_template\\"
|
||||
let utils = this.constructor.utils;
|
||||
let QWeb = this.constructor;
|
||||
let parent = extra.parent;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c4 = extra.parentNode;
|
||||
// Component 'Child'
|
||||
const this_5 = utils.getComponent(context);
|
||||
let w6 = '__7__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__7__']] : false;
|
||||
let props6 = {callback:value=>this_5.setValue(value),value:scope['state'].val};
|
||||
if (w6 && w6.__owl__.currentFiber && !w6.__owl__.vnode) {
|
||||
w6.destroy();
|
||||
w6 = false;
|
||||
}
|
||||
if (w6) {
|
||||
w6.__updateProps(props6, extra.fiber, undefined);
|
||||
let pvnode = w6.__owl__.pvnode;
|
||||
c4.push(pvnode);
|
||||
} else {
|
||||
let componentKey6 = \`Child\`;
|
||||
let W6 = scope['Child'] || context.constructor.components[componentKey6] || QWeb.components[componentKey6];
|
||||
if (!W6) {throw new Error('Cannot find the definition of component \\"' + componentKey6 + '\\"')}
|
||||
w6 = new W6(parent, props6);
|
||||
parent.__owl__.cmap['__7__'] = w6.__owl__.id;
|
||||
let fiber = w6.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
let pvnode = h('dummy', {key: '__7__', hook: {remove() {},destroy(vn) {w6.destroy();}}});
|
||||
c4.push(pvnode);
|
||||
w6.__owl__.pvnode = pvnode;
|
||||
}
|
||||
w6.__owl__.parentLastFiberId = extra.fiber.id;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props evaluation arrow function prop captures context component instance as 'this' inside slot 4`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"Child\\"
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c10 = [], p10 = {key:10};
|
||||
let vn10 = h('span', p10, c10);
|
||||
let _11 = scope['props'].value;
|
||||
if (_11 != null) {
|
||||
c10.push({text: _11});
|
||||
}
|
||||
return vn10;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props evaluation arrow function prop captures context component instance as 'this' inside slot default content 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);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
// Component 'Wrapper'
|
||||
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, undefined);
|
||||
let pvnode = w2.__owl__.pvnode;
|
||||
c1.push(pvnode);
|
||||
} else {
|
||||
let componentKey2 = \`Wrapper\`;
|
||||
let W2 = scope['Wrapper'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2];
|
||||
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
|
||||
w2 = new W2(parent, props2);
|
||||
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: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
||||
c1.push(pvnode);
|
||||
w2.__owl__.pvnode = pvnode;
|
||||
}
|
||||
w2.__owl__.parentLastFiberId = extra.fiber.id;
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props evaluation arrow function prop captures context component instance as 'this' inside slot default content 2`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"Wrapper\\"
|
||||
let utils = this.constructor.utils;
|
||||
let QWeb = this.constructor;
|
||||
let parent = context;
|
||||
let scope = Object.create(context);
|
||||
let result;
|
||||
let h = this.h;
|
||||
const slot4 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
||||
if (slot4) {
|
||||
let children5= []
|
||||
result = {}
|
||||
slot4.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: children5, parent: extra.parent || context}));
|
||||
utils.defineProxy(result, children5[0]);
|
||||
} else {
|
||||
// Component 'Child'
|
||||
const this_6 = utils.getComponent(context);
|
||||
let w7 = '__8__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__8__']] : false;
|
||||
let vn9 = {};
|
||||
result = vn9;
|
||||
let props7 = {callback:value=>this_6.setValue(value),value:scope['state'].val};
|
||||
if (w7 && w7.__owl__.currentFiber && !w7.__owl__.vnode) {
|
||||
w7.destroy();
|
||||
w7 = false;
|
||||
}
|
||||
if (w7) {
|
||||
w7.__updateProps(props7, extra.fiber, undefined);
|
||||
let pvnode = w7.__owl__.pvnode;
|
||||
utils.defineProxy(vn9, pvnode);
|
||||
} else {
|
||||
let componentKey7 = \`Child\`;
|
||||
let W7 = scope['Child'] || context.constructor.components[componentKey7] || QWeb.components[componentKey7];
|
||||
if (!W7) {throw new Error('Cannot find the definition of component \\"' + componentKey7 + '\\"')}
|
||||
w7 = new W7(parent, props7);
|
||||
parent.__owl__.cmap['__8__'] = w7.__owl__.id;
|
||||
let fiber = w7.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
let pvnode = h('dummy', {key: '__8__', hook: {remove() {},destroy(vn) {w7.destroy();}}});
|
||||
utils.defineProxy(vn9, pvnode);
|
||||
w7.__owl__.pvnode = pvnode;
|
||||
}
|
||||
w7.__owl__.parentLastFiberId = extra.fiber.id;
|
||||
}
|
||||
return result;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props evaluation arrow function prop captures context component instance as 'this' inside slot default content 3`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"Child\\"
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c10 = [], p10 = {key:10};
|
||||
let vn10 = h('span', p10, c10);
|
||||
let _11 = scope['props'].value;
|
||||
if (_11 != null) {
|
||||
c10.push({text: _11});
|
||||
}
|
||||
return vn10;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props evaluation arrow function prop captures loop variables 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);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = [0,1];
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _2;
|
||||
let _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
scope = Object.create(scope);
|
||||
for (let i1 = 0; i1 < _length3; i1++) {
|
||||
scope.loopVar_first = i1 === 0
|
||||
scope.loopVar_last = i1 === _length3 - 1
|
||||
scope.loopVar_index = i1
|
||||
scope.loopVar = _3[i1]
|
||||
scope.loopVar_value = _4[i1]
|
||||
let key1 = scope['loopVar'];
|
||||
// Component 'Child'
|
||||
const this_6 = utils.getComponent(context);
|
||||
const loopVar_6 = scope['loopVar'];
|
||||
let k8 = \`__8__\${key1}__\`;
|
||||
let w7 = k8 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k8]] : false;
|
||||
let props7 = {callback:()=>this_6.setValue(loopVar_6),value:scope['state'].val};
|
||||
if (w7 && w7.__owl__.currentFiber && !w7.__owl__.vnode) {
|
||||
w7.destroy();
|
||||
w7 = false;
|
||||
}
|
||||
if (w7) {
|
||||
w7.__updateProps(props7, extra.fiber, undefined);
|
||||
let pvnode = w7.__owl__.pvnode;
|
||||
c1.push(pvnode);
|
||||
} else {
|
||||
let componentKey7 = \`Child\`;
|
||||
let W7 = scope['Child'] || context.constructor.components[componentKey7] || QWeb.components[componentKey7];
|
||||
if (!W7) {throw new Error('Cannot find the definition of component \\"' + componentKey7 + '\\"')}
|
||||
w7 = new W7(parent, props7);
|
||||
parent.__owl__.cmap[k8] = w7.__owl__.id;
|
||||
let fiber = w7.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
let pvnode = h('dummy', {key: k8, hook: {remove() {},destroy(vn) {w7.destroy();}}});
|
||||
c1.push(pvnode);
|
||||
w7.__owl__.pvnode = pvnode;
|
||||
}
|
||||
w7.__owl__.parentLastFiberId = extra.fiber.id;
|
||||
}
|
||||
scope = _origScope5;
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props evaluation arrow function prop captures loop variables 2`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"Child\\"
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c9 = [], p9 = {key:9};
|
||||
let vn9 = h('span', p9, c9);
|
||||
let _10 = scope['props'].value;
|
||||
if (_10 != null) {
|
||||
c9.push({text: _10});
|
||||
}
|
||||
return vn9;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props evaluation bare function calls in arrow function has rendering context as 'this' 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);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
scope.ctxVal = 2;
|
||||
// Component 'Child'
|
||||
let w3 = '__4__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__4__']] : false;
|
||||
let props3 = {callback:value=>scope['setValue'](value),value:scope['state'].val};
|
||||
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
|
||||
w3.destroy();
|
||||
w3 = false;
|
||||
}
|
||||
if (w3) {
|
||||
w3.__updateProps(props3, extra.fiber, undefined);
|
||||
let pvnode = w3.__owl__.pvnode;
|
||||
c1.push(pvnode);
|
||||
} else {
|
||||
let componentKey3 = \`Child\`;
|
||||
let W3 = scope['Child'] || context.constructor.components[componentKey3] || QWeb.components[componentKey3];
|
||||
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
|
||||
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
c1.push(pvnode);
|
||||
w3.__owl__.pvnode = pvnode;
|
||||
}
|
||||
w3.__owl__.parentLastFiberId = extra.fiber.id;
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props evaluation bare function calls in arrow function has rendering context as 'this' 2`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"Child\\"
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c5 = [], p5 = {key:5};
|
||||
let vn5 = h('span', p5, c5);
|
||||
let _6 = scope['props'].value;
|
||||
if (_6 != null) {
|
||||
c5.push({text: _6});
|
||||
}
|
||||
return vn5;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props evaluation t-set with a body expression can be used as textual prop 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
@@ -1560,7 +1967,8 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['items'];
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
let _3 = _2;
|
||||
let _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
@@ -1838,7 +2246,8 @@ exports[`t-model directive in a t-foreach 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['state'];
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
let _3 = _2;
|
||||
let _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
@@ -1878,7 +2287,8 @@ exports[`t-model directive in a t-foreach, part 2 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['state'];
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
let _3 = _2;
|
||||
let _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
|
||||
@@ -414,7 +414,8 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
|
||||
c1.push(vn2);
|
||||
let _3 = scope['state'].users;
|
||||
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _4 = _5 = _3;
|
||||
let _4 = _3;
|
||||
let _5 = _3;
|
||||
if (!(_3 instanceof Array)) {
|
||||
_4 = Object.keys(_3);
|
||||
_5 = Object.values(_3);
|
||||
@@ -512,7 +513,8 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
|
||||
c1.push(vn2);
|
||||
let _3 = scope['state'].users;
|
||||
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _4 = _5 = _3;
|
||||
let _4 = _3;
|
||||
let _5 = _3;
|
||||
if (!(_3 instanceof Array)) {
|
||||
_4 = Object.keys(_3);
|
||||
_5 = Object.values(_3);
|
||||
@@ -645,7 +647,8 @@ exports[`t-slot directive slots in t-foreach in t-foreach 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['tree'];
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _4 = _2;
|
||||
let _3 = _2;
|
||||
let _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
@@ -672,7 +675,8 @@ exports[`t-slot directive slots in t-foreach in t-foreach 1`] = `
|
||||
c1.push(vn8);
|
||||
let _9 = scope['node1'].nodes;
|
||||
if (!_9) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _10 = _11 = _9;
|
||||
let _10 = _9;
|
||||
let _11 = _9;
|
||||
if (!(_9 instanceof Array)) {
|
||||
_10 = Object.keys(_9);
|
||||
_11 = Object.values(_9);
|
||||
|
||||
@@ -265,7 +265,8 @@ describe("class and style attributes with t-component", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
});
|
||||
|
||||
@@ -1881,6 +1881,219 @@ describe("props evaluation ", () => {
|
||||
await widget.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><span><p>4343</p><p>43</p></span></div>");
|
||||
});
|
||||
|
||||
test("arrow function prop captures component instance as 'this'", async () => {
|
||||
expect.assertions(5);
|
||||
let child, parent;
|
||||
class Child extends Component {
|
||||
setup() {
|
||||
child = this;
|
||||
}
|
||||
}
|
||||
env.qweb.addTemplate("Child", `<span><t t-esc="props.value"/></span>`);
|
||||
|
||||
class Parent extends Component {
|
||||
static components = { Child };
|
||||
state = useState({ val: 42 });
|
||||
setup() {
|
||||
parent = this;
|
||||
}
|
||||
setValue(value) {
|
||||
expect(this).toBe(parent);
|
||||
this.state.val = value;
|
||||
}
|
||||
}
|
||||
env.qweb.addTemplate(
|
||||
"Parent",
|
||||
`<div>
|
||||
<Child callback="value => this.setValue(value)" value="state.val"/>
|
||||
</div>`
|
||||
);
|
||||
|
||||
const widget = new Parent();
|
||||
await widget.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
|
||||
child.props.callback(123);
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><span>123</span></div>");
|
||||
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates.Child.fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("bare function calls in arrow function has rendering context as 'this'", async () => {
|
||||
expect.assertions(7);
|
||||
let child, parent;
|
||||
class Child extends Component {
|
||||
setup() {
|
||||
child = this;
|
||||
}
|
||||
}
|
||||
env.qweb.addTemplate("Child", `<span><t t-esc="props.value"/></span>`);
|
||||
|
||||
class Parent extends Component {
|
||||
static components = { Child };
|
||||
state = useState({ val: 42 });
|
||||
setup() {
|
||||
parent = this;
|
||||
}
|
||||
setValue(value) {
|
||||
// 'this' is the rendering context, NOT the instance
|
||||
expect(this).not.toBe(parent);
|
||||
// the state in the rendering context should be the same as the instance's
|
||||
expect(this.state).toBe(parent.state);
|
||||
expect((this as any).ctxVal).toBe(2);
|
||||
this.state.val = value;
|
||||
}
|
||||
}
|
||||
env.qweb.addTemplate(
|
||||
"Parent",
|
||||
`<div>
|
||||
<t t-set="ctxVal" t-value="2"/>
|
||||
<Child callback="value => setValue(value)" value="state.val"/>
|
||||
</div>`
|
||||
);
|
||||
|
||||
const widget = new Parent();
|
||||
await widget.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
|
||||
child.props.callback(123);
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><span>123</span></div>");
|
||||
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates.Child.fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("arrow function prop captures context component instance as 'this' inside slot", async () => {
|
||||
expect.assertions(7);
|
||||
let child, parent;
|
||||
class Child extends Component {
|
||||
setup() {
|
||||
child = this;
|
||||
}
|
||||
}
|
||||
env.qweb.addTemplate("Child", `<span><t t-esc="props.value"/></span>`);
|
||||
|
||||
class Wrapper extends Component {}
|
||||
env.qweb.addTemplate("Wrapper", `<t t-slot="default"/>`);
|
||||
|
||||
class Parent extends Component {
|
||||
static components = { Child, Wrapper };
|
||||
state = useState({ val: 42 });
|
||||
setup() {
|
||||
parent = this;
|
||||
}
|
||||
setValue(value) {
|
||||
expect(this).toBe(parent);
|
||||
this.state.val = value;
|
||||
}
|
||||
}
|
||||
env.qweb.addTemplate(
|
||||
"Parent",
|
||||
`<div>
|
||||
<Wrapper>
|
||||
<Child callback="value => this.setValue(value)" value="state.val"/>
|
||||
</Wrapper>
|
||||
</div>`
|
||||
);
|
||||
|
||||
const widget = new Parent();
|
||||
await widget.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
|
||||
child.props.callback(123);
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><span>123</span></div>");
|
||||
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates.Wrapper.fn.toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates.Child.fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("arrow function prop captures context component instance as 'this' inside slot default content", async () => {
|
||||
expect.assertions(6);
|
||||
let child, wrapper;
|
||||
class Child extends Component {
|
||||
setup() {
|
||||
child = this;
|
||||
}
|
||||
}
|
||||
env.qweb.addTemplate("Child", `<span><t t-esc="props.value"/></span>`);
|
||||
|
||||
class Wrapper extends Component {
|
||||
static components = { Child };
|
||||
state = useState({ val: 42 });
|
||||
setup() {
|
||||
wrapper = this;
|
||||
}
|
||||
setValue(value) {
|
||||
expect(this).toBe(wrapper);
|
||||
this.state.val = value;
|
||||
}
|
||||
}
|
||||
env.qweb.addTemplate(
|
||||
"Wrapper",
|
||||
`<t t-slot="default">
|
||||
<Child callback="value => this.setValue(value)" value="state.val"/>
|
||||
</t>`
|
||||
);
|
||||
|
||||
class Parent extends Component {
|
||||
static components = { Wrapper };
|
||||
}
|
||||
env.qweb.addTemplate(
|
||||
"Parent",
|
||||
`<div>
|
||||
<Wrapper/>
|
||||
</div>`
|
||||
);
|
||||
|
||||
const widget = new Parent();
|
||||
await widget.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
|
||||
child.props.callback(123);
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><span>123</span></div>");
|
||||
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates.Wrapper.fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates.Child.fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("arrow function prop captures loop variables", async () => {
|
||||
let children = [];
|
||||
class Child extends Component {
|
||||
setup() {
|
||||
children.push(this);
|
||||
}
|
||||
}
|
||||
env.qweb.addTemplate("Child", `<span><t t-esc="props.value"/></span>`);
|
||||
|
||||
class Parent extends Component {
|
||||
static components = { Child };
|
||||
state = useState({ val: 42 });
|
||||
setValue(value) {
|
||||
this.state.val = value;
|
||||
}
|
||||
}
|
||||
env.qweb.addTemplate(
|
||||
"Parent",
|
||||
`<div>
|
||||
<t t-foreach="[0, 1]" t-as="loopVar" t-key="loopVar">
|
||||
<Child callback="() => this.setValue(loopVar)" value="state.val"/>
|
||||
</t>
|
||||
</div>`
|
||||
);
|
||||
|
||||
const widget = new Parent();
|
||||
await widget.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><span>42</span><span>42</span></div>");
|
||||
children[0].props.callback();
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><span>0</span><span>0</span></div>");
|
||||
children[1].props.callback();
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><span>1</span><span>1</span></div>");
|
||||
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates.Child.fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("other directives with t-component", () => {
|
||||
|
||||
@@ -361,7 +361,8 @@ describe("component error handling (catchError)", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
@@ -472,7 +473,8 @@ describe("component error handling (catchError)", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
@@ -499,7 +501,8 @@ describe("component error handling (catchError)", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
@@ -523,7 +526,8 @@ describe("component error handling (catchError)", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
const regexp = /Cannot read properties of undefined \(reading 'y'\)|Cannot read property 'y' of undefined/g;
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'y'\)|Cannot read property 'y' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
});
|
||||
|
||||
|
||||
@@ -138,7 +138,9 @@ describe("props validation", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component '_a'");
|
||||
expect(error.message).toBe(
|
||||
`Invalid Prop 'p' in component '_a': type of p is not ${test.type.name}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -195,7 +197,9 @@ describe("props validation", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component '_a'");
|
||||
expect(error.message).toBe(
|
||||
`Invalid Prop 'p' in component '_a': type of p is not ${test.type.name}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -240,7 +244,9 @@ describe("props validation", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'TestWidget'");
|
||||
expect(error.message).toBe(
|
||||
"Invalid Prop 'p' in component 'TestWidget': type of p is not String and type of p is not Boolean"
|
||||
);
|
||||
});
|
||||
|
||||
test("can validate an optional props", async () => {
|
||||
@@ -284,7 +290,9 @@ describe("props validation", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'TestWidget'");
|
||||
expect(error.message).toBe(
|
||||
"Invalid Prop 'p' in component 'TestWidget': type of p is not String"
|
||||
);
|
||||
});
|
||||
|
||||
test("can validate an array with given primitive type", async () => {
|
||||
@@ -389,7 +397,9 @@ describe("props validation", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'TestWidget'");
|
||||
expect(error.message).toBe(
|
||||
"Invalid Prop 'p' in component 'TestWidget': type of p[1] is not String and type of p[1] is not Boolean"
|
||||
);
|
||||
});
|
||||
|
||||
test("can validate an object with simple shape", async () => {
|
||||
@@ -426,7 +436,9 @@ describe("props validation", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid prop 'p' in component TestWidget (unknown prop 'extra')");
|
||||
expect(error.message).toBe(
|
||||
"Invalid Prop 'p' in component 'TestWidget': unknown prop p['extra']"
|
||||
);
|
||||
|
||||
try {
|
||||
props = { p: { id: "1", url: "url" } };
|
||||
@@ -436,7 +448,9 @@ describe("props validation", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'TestWidget'");
|
||||
expect(error.message).toBe(
|
||||
"Invalid Prop 'p' in component 'TestWidget': type of p['id'] is not Number"
|
||||
);
|
||||
|
||||
error = undefined;
|
||||
try {
|
||||
@@ -447,7 +461,9 @@ describe("props validation", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'TestWidget'");
|
||||
expect(error.message).toBe(
|
||||
"Invalid Prop 'p' in component 'TestWidget': type of p['url'] is not String"
|
||||
);
|
||||
});
|
||||
|
||||
test("can validate recursively complicated prop def", async () => {
|
||||
@@ -499,7 +515,9 @@ describe("props validation", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'p' in component 'TestWidget'");
|
||||
expect(error.message).toBe(
|
||||
"Invalid Prop 'p' in component 'TestWidget': p['url'] is not an instance of Boolean and type of p['url'][1] is not Number"
|
||||
);
|
||||
});
|
||||
|
||||
test("can validate optional attributes in nested sub props", () => {
|
||||
@@ -531,7 +549,7 @@ describe("props validation", () => {
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(
|
||||
"Invalid prop 'myprop' in component TestComponent (unknown prop 'a')"
|
||||
"Invalid Prop 'myprop' in component 'TestComponent': unknown prop myprop[0]['a']"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -557,7 +575,9 @@ describe("props validation", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'size' in component 'TestComponent'");
|
||||
expect(error.message).toBe(
|
||||
"Invalid Prop 'size' in component 'TestComponent': size could not be validated by `validate` function"
|
||||
);
|
||||
});
|
||||
|
||||
test("can validate with a custom validator, and a type", () => {
|
||||
@@ -585,7 +605,9 @@ describe("props validation", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'n' in component 'TestComponent'");
|
||||
expect(error.message).toBe(
|
||||
"Invalid Prop 'n' in component 'TestComponent': type of n is not Number"
|
||||
);
|
||||
expect(validator).toBeCalledTimes(1);
|
||||
|
||||
error = null;
|
||||
@@ -595,7 +617,9 @@ describe("props validation", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe("Invalid Prop 'n' in component 'TestComponent'");
|
||||
expect(error.message).toBe(
|
||||
"Invalid Prop 'n' in component 'TestComponent': n could not be validated by `validate` function"
|
||||
);
|
||||
expect(validator).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
|
||||
@@ -78,7 +78,9 @@ describe("Portal: Props validation", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Invalid Prop 'target' in component 'Portal'`);
|
||||
expect(error.message).toBe(
|
||||
"Invalid Prop 'target' in component 'Portal': target is not an instance of String"
|
||||
);
|
||||
|
||||
QWeb.dev = dev;
|
||||
});
|
||||
@@ -546,7 +548,8 @@ describe("Portal: Basic use and DOM placement", () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
const regexp =
|
||||
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
||||
expect(error.message).toMatch(regexp);
|
||||
});
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`memory t-foreach does not leak stuff in global scope 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('p', p1, c1);
|
||||
let _2 = [3,2,1];
|
||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _3 = _2;
|
||||
let _4 = _2;
|
||||
if (!(_2 instanceof Array)) {
|
||||
_3 = Object.keys(_2);
|
||||
_4 = Object.values(_2);
|
||||
}
|
||||
let _length3 = _3.length;
|
||||
let _origScope5 = scope;
|
||||
scope = Object.create(scope);
|
||||
for (let i1 = 0; i1 < _length3; i1++) {
|
||||
scope.item_first = i1 === 0
|
||||
scope.item_last = i1 === _length3 - 1
|
||||
scope.item_index = i1
|
||||
scope.item = _3[i1]
|
||||
scope.item_value = _4[i1]
|
||||
let key1 = i1;
|
||||
let _6 = scope['item'];
|
||||
if (_6 != null) {
|
||||
c1.push({text: _6});
|
||||
}
|
||||
}
|
||||
scope = _origScope5;
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
@@ -1,122 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`old t-esc directive escaping 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('span', p1, c1);
|
||||
let _2 = scope['var'];
|
||||
if (_2 != null) {
|
||||
c1.push({text: _2});
|
||||
}
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`old t-esc directive simple dynamic value 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let scope = Object.create(context);
|
||||
let result;
|
||||
let h = this.h;
|
||||
let _1 = scope['text'];
|
||||
if (_1 != null) {
|
||||
let vn2 = {text: _1};
|
||||
result = vn2
|
||||
}
|
||||
return result;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`old t-raw directive literal 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('span', p1, c1);
|
||||
let _2 = 'ok';
|
||||
if (_2 != null) {
|
||||
c1.push(...utils.htmlToVDOM(_2));
|
||||
}
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`old t-raw directive not escaping 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
let _2 = scope['var'];
|
||||
if (_2 != null) {
|
||||
c1.push(...utils.htmlToVDOM(_2));
|
||||
}
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`old t-raw directive t-raw and another sibling node 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('span', p1, c1);
|
||||
let c2 = [], p2 = {key:2};
|
||||
let vn2 = h('span', p2, c2);
|
||||
c1.push(vn2);
|
||||
c2.push({text: \`hello\`});
|
||||
let _3 = scope['var'];
|
||||
if (_3 != null) {
|
||||
c1.push(...utils.htmlToVDOM(_3));
|
||||
}
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`old t-raw directive t-raw with comment 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('span', p1, c1);
|
||||
let _2 = scope['var'];
|
||||
if (_2 != null) {
|
||||
c1.push(...utils.htmlToVDOM(_2));
|
||||
}
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`old t-raw directive variable 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"test\\"
|
||||
let utils = this.constructor.utils;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('span', p1, c1);
|
||||
let _2 = scope['var'];
|
||||
if (_2 != null) {
|
||||
c1.push(...utils.htmlToVDOM(_2));
|
||||
}
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
+100
-106
@@ -2,7 +2,6 @@ import { QWeb } from "../../src/qweb/index";
|
||||
import { config } from "../../src/index";
|
||||
import { nextTick, normalize, renderToDOM, renderToString, trim } from "../helpers";
|
||||
import { patch } from "../../src/vdom";
|
||||
import { Markup } from "../../src/utils";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Setup and helpers
|
||||
@@ -30,27 +29,27 @@ describe("static templates", () => {
|
||||
});
|
||||
|
||||
test("simple dynamic value", () => {
|
||||
qweb.addTemplate("test", '<t><t t-out="text"/></t>');
|
||||
qweb.addTemplate("test", '<t><t t-esc="text"/></t>');
|
||||
expect(renderToString(qweb, "test", { text: "hello vdom" })).toBe("hello vdom");
|
||||
});
|
||||
|
||||
test("inline template string in t-out", () => {
|
||||
qweb.addTemplate("test", '<t><t t-out="`text`"/></t>');
|
||||
test("inline template string in t-esc", () => {
|
||||
qweb.addTemplate("test", '<t><t t-esc="`text`"/></t>');
|
||||
expect(renderToString(qweb, "test")).toBe("text");
|
||||
});
|
||||
|
||||
test("inline template string with content in t-out", () => {
|
||||
qweb.addTemplate("test", '<t><t t-set="v" t-value="1"/><t t-out="`text${v}`"/></t>');
|
||||
test("inline template string with content in t-esc", () => {
|
||||
qweb.addTemplate("test", '<t><t t-set="v" t-value="1"/><t t-esc="`text${v}`"/></t>');
|
||||
expect(renderToString(qweb, "test")).toBe("text1");
|
||||
});
|
||||
|
||||
test("inline template string with variable in context", () => {
|
||||
qweb.addTemplate("test", '<t><t t-out="`text ${v}`"/></t>');
|
||||
qweb.addTemplate("test", '<t><t t-esc="`text ${v}`"/></t>');
|
||||
expect(renderToString(qweb, "test", { v: "from context" })).toBe("text from context");
|
||||
});
|
||||
|
||||
test("simple string, with some dynamic value", () => {
|
||||
qweb.addTemplate("test", '<t>hello <t t-out="text"/></t>');
|
||||
qweb.addTemplate("test", '<t>hello <t t-esc="text"/></t>');
|
||||
expect(renderToString(qweb, "test", { text: "vdom" })).toBe("hello vdom");
|
||||
});
|
||||
|
||||
@@ -150,47 +149,47 @@ describe("error handling", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("t-out", () => {
|
||||
describe("t-esc", () => {
|
||||
test("literal", () => {
|
||||
qweb.addTemplate("test", `<span><t t-out="'ok'"/></span>`);
|
||||
qweb.addTemplate("test", `<span><t t-esc="'ok'"/></span>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<span>ok</span>");
|
||||
});
|
||||
|
||||
test("variable", () => {
|
||||
qweb.addTemplate("test", `<span><t t-out="var"/></span>`);
|
||||
qweb.addTemplate("test", `<span><t t-esc="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: "ok" })).toBe("<span>ok</span>");
|
||||
});
|
||||
|
||||
test("escaping", () => {
|
||||
qweb.addTemplate("test", `<span><t t-out="var"/></span>`);
|
||||
qweb.addTemplate("test", `<span><t t-esc="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: "<ok>abc</ok>" })).toBe(
|
||||
"<span>&lt;ok&gt;abc&lt;/ok&gt;</span>"
|
||||
"<span><ok>abc</ok></span>"
|
||||
);
|
||||
});
|
||||
|
||||
test("escaping on a node", () => {
|
||||
qweb.addTemplate("test", `<span t-out="'ok'"/>`);
|
||||
qweb.addTemplate("test", `<span t-esc="'ok'"/>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<span>ok</span>");
|
||||
});
|
||||
|
||||
test("escaping on a node with a body", () => {
|
||||
qweb.addTemplate("test", `<span t-out="'ok'">nope</span>`);
|
||||
qweb.addTemplate("test", `<span t-esc="'ok'">nope</span>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<span>ok</span>");
|
||||
});
|
||||
|
||||
test("escaping on a node with a body, as a default", () => {
|
||||
qweb.addTemplate("test", `<span t-out="var">nope</span>`);
|
||||
qweb.addTemplate("test", `<span t-esc="var">nope</span>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<span>nope</span>");
|
||||
});
|
||||
|
||||
test("t-out is escaped", () => {
|
||||
qweb.addTemplate("test", `<div><t t-set="var"><p>escaped</p></t><t t-out="var"/></div>`);
|
||||
test("t-esc is escaped", () => {
|
||||
qweb.addTemplate("test", `<div><t t-set="var"><p>escaped</p></t><t t-esc="var"/></div>`);
|
||||
const domRendered = renderToDOM(qweb, "test");
|
||||
expect(domRendered.textContent).toBe("<p>escaped</p>");
|
||||
});
|
||||
|
||||
test("t-out=0 is escaped", () => {
|
||||
qweb.addTemplate("test", `<span><t t-out="0"/></span>`);
|
||||
test("t-esc=0 is escaped", () => {
|
||||
qweb.addTemplate("test", `<span><t t-esc="0"/></span>`);
|
||||
qweb.addTemplate("testCaller", `<div><t t-call="test"><p>escaped</p></t></div>`);
|
||||
const domRendered = renderToDOM(qweb, "testCaller") as HTMLElement;
|
||||
expect(domRendered.querySelector("span")!.textContent).toBe("<p>escaped</p>");
|
||||
@@ -201,11 +200,11 @@ describe("t-out", () => {
|
||||
"test",
|
||||
`
|
||||
<div>
|
||||
<p t-out="v1"/>
|
||||
<p t-out="v2"/>
|
||||
<p t-out="v3"/>
|
||||
<p t-out="v4"/>
|
||||
<p t-out="v5"/>
|
||||
<p t-esc="v1"/>
|
||||
<p t-esc="v2"/>
|
||||
<p t-esc="v3"/>
|
||||
<p t-esc="v4"/>
|
||||
<p t-esc="v5"/>
|
||||
</div>`
|
||||
);
|
||||
const vals = {
|
||||
@@ -220,51 +219,46 @@ describe("t-out", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("t-out work with spread operator", () => {
|
||||
qweb.addTemplate("test", `<span><t t-out="[...state.list]"/></span>`);
|
||||
test("t-esc work with spread operator", () => {
|
||||
qweb.addTemplate("test", `<span><t t-esc="[...state.list]"/></span>`);
|
||||
const result = renderToString(qweb, "test", { state: { list: [1, 2] } });
|
||||
expect(result).toBe("<span>1,2</span>");
|
||||
});
|
||||
|
||||
test("t-out inside t-call, with t-set outside", () => {
|
||||
test("t-esc inside t-call, with t-set outside", () => {
|
||||
qweb.addTemplate("main", `<div><t t-set="v">Hi</t><t t-call="sub"/></div>`);
|
||||
qweb.addTemplate("sub", `<span t-out="v"/>`);
|
||||
qweb.addTemplate("sub", `<span t-esc="v"/>`);
|
||||
const result = renderToString(qweb, "main");
|
||||
expect(result).toBe("<div><span>Hi</span></div>");
|
||||
});
|
||||
});
|
||||
|
||||
describe("t-out (formerly t-raw tests)", () => {
|
||||
describe("t-raw", () => {
|
||||
test("literal", () => {
|
||||
qweb.addTemplate("test", `<span><t t-out="'ok'"/></span>`);
|
||||
qweb.addTemplate("test", `<span><t t-raw="'ok'"/></span>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<span>ok</span>");
|
||||
});
|
||||
|
||||
test("variable", () => {
|
||||
qweb.addTemplate("test", `<span><t t-out="var"/></span>`);
|
||||
qweb.addTemplate("test", `<span><t t-raw="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: "ok" })).toBe("<span>ok</span>");
|
||||
});
|
||||
|
||||
test("not escaping (Markup as template function)", () => {
|
||||
qweb.addTemplate("test", `<div><t t-out="var"/></div>`);
|
||||
expect(renderToString(qweb, "test", { var: Markup`<ok></ok>` })).toBe("<div><ok></ok></div>");
|
||||
});
|
||||
|
||||
test("not escaping (Markup as function)", () => {
|
||||
qweb.addTemplate("test", `<div><t t-out="var"/></div>`);
|
||||
expect(renderToString(qweb, "test", { var: Markup(`<ok></ok>`) })).toBe("<div><ok></ok></div>");
|
||||
test("not escaping", () => {
|
||||
qweb.addTemplate("test", `<div><t t-raw="var"/></div>`);
|
||||
expect(renderToString(qweb, "test", { var: "<ok></ok>" })).toBe("<div><ok></ok></div>");
|
||||
});
|
||||
|
||||
test("t-raw and another sibling node", () => {
|
||||
qweb.addTemplate("test", `<span><span>hello</span><t t-raw="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: Markup`<ok>world</ok>` })).toBe(
|
||||
expect(renderToString(qweb, "test", { var: "<ok>world</ok>" })).toBe(
|
||||
"<span><span>hello</span><ok>world</ok></span>"
|
||||
);
|
||||
});
|
||||
|
||||
test("t-raw with comment", () => {
|
||||
qweb.addTemplate("test", `<span><t t-raw="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: Markup`<p>text<!-- top secret --></p>` })).toBe(
|
||||
expect(renderToString(qweb, "test", { var: "<p>text<!-- top secret --></p>" })).toBe(
|
||||
"<span><p>text<!-- top secret --></p></span>"
|
||||
);
|
||||
});
|
||||
@@ -272,7 +266,7 @@ describe("t-out (formerly t-raw tests)", () => {
|
||||
|
||||
describe("t-set", () => {
|
||||
test("set from attribute literal", () => {
|
||||
qweb.addTemplate("test", `<div><t t-set="value" t-value="'ok'"/><t t-out="value"/></div>`);
|
||||
qweb.addTemplate("test", `<div><t t-set="value" t-value="'ok'"/><t t-esc="value"/></div>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<div>ok</div>");
|
||||
});
|
||||
|
||||
@@ -288,12 +282,12 @@ describe("t-set", () => {
|
||||
});
|
||||
|
||||
test("set from body literal", () => {
|
||||
qweb.addTemplate("test", `<t><t t-set="value">ok</t><t t-out="value"/></t>`);
|
||||
qweb.addTemplate("test", `<t><t t-set="value">ok</t><t t-esc="value"/></t>`);
|
||||
expect(renderToString(qweb, "test")).toBe("ok");
|
||||
});
|
||||
|
||||
test("set from attribute lookup", () => {
|
||||
qweb.addTemplate("test", `<div><t t-set="stuff" t-value="value"/><t t-out="stuff"/></div>`);
|
||||
qweb.addTemplate("test", `<div><t t-set="stuff" t-value="value"/><t t-esc="stuff"/></div>`);
|
||||
expect(renderToString(qweb, "test", { value: "ok" })).toBe("<div>ok</div>");
|
||||
});
|
||||
|
||||
@@ -302,8 +296,8 @@ describe("t-set", () => {
|
||||
"test",
|
||||
`<div >
|
||||
<t t-set="v" t-value="value + ' artois'"/>
|
||||
<t t-out="v"/>
|
||||
<t t-out="v"/>
|
||||
<t t-esc="v"/>
|
||||
<t t-esc="v"/>
|
||||
</div>`
|
||||
);
|
||||
expect(renderToString(qweb, "test", { value: "stella" })).toBe(
|
||||
@@ -314,23 +308,23 @@ describe("t-set", () => {
|
||||
test("set from body lookup", () => {
|
||||
qweb.addTemplate(
|
||||
"test",
|
||||
`<div><t t-set="stuff"><t t-out="value"/></t><t t-out="stuff"/></div>`
|
||||
`<div><t t-set="stuff"><t t-esc="value"/></t><t t-esc="stuff"/></div>`
|
||||
);
|
||||
expect(renderToString(qweb, "test", { value: "ok" })).toBe("<div>ok</div>");
|
||||
});
|
||||
|
||||
test("set from empty body", () => {
|
||||
qweb.addTemplate("test", `<div><t t-set="stuff"/><t t-out="stuff"/></div>`);
|
||||
qweb.addTemplate("test", `<div><t t-set="stuff"/><t t-esc="stuff"/></div>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<div></div>");
|
||||
});
|
||||
|
||||
test("value priority", () => {
|
||||
qweb.addTemplate("test", `<div><t t-set="value" t-value="1">2</t><t t-out="value"/></div>`);
|
||||
qweb.addTemplate("test", `<div><t t-set="value" t-value="1">2</t><t t-esc="value"/></div>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<div>1</div>");
|
||||
});
|
||||
|
||||
test("evaluate value expression", () => {
|
||||
qweb.addTemplate("test", `<div><t t-set="value" t-value="1 + 2"/><t t-out="value"/></div>`);
|
||||
qweb.addTemplate("test", `<div><t t-set="value" t-value="1 + 2"/><t t-esc="value"/></div>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<div>3</div>");
|
||||
});
|
||||
|
||||
@@ -340,7 +334,7 @@ describe("t-set", () => {
|
||||
`<div>
|
||||
<t t-set="v" t-value="1"/>
|
||||
<div t-foreach="list" t-as="elem" t-key="elem_index">
|
||||
<span>v<t t-out="v"/></span>
|
||||
<span>v<t t-esc="v"/></span>
|
||||
<t t-set="v" t-value="elem"/>
|
||||
</div>
|
||||
</div>`
|
||||
@@ -350,12 +344,12 @@ describe("t-set", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("t-set with content and sub t-out", () => {
|
||||
test("t-set with content and sub t-esc", () => {
|
||||
qweb.addTemplate(
|
||||
"test",
|
||||
`<div>
|
||||
<t t-set="setvar"><t t-out="beep"/> boop</t>
|
||||
<t t-out="setvar"/>
|
||||
<t t-set="setvar"><t t-esc="beep"/> boop</t>
|
||||
<t t-esc="setvar"/>
|
||||
</div>`
|
||||
);
|
||||
|
||||
@@ -365,7 +359,7 @@ describe("t-set", () => {
|
||||
test("evaluate value expression, part 2", () => {
|
||||
qweb.addTemplate(
|
||||
"test",
|
||||
`<div><t t-set="value" t-value="somevariable + 2"/><t t-out="value"/></div>`
|
||||
`<div><t t-set="value" t-value="somevariable + 2"/><t t-esc="value"/></div>`
|
||||
);
|
||||
expect(renderToString(qweb, "test", { somevariable: 43 })).toBe("<div>45</div>");
|
||||
});
|
||||
@@ -376,7 +370,7 @@ describe("t-set", () => {
|
||||
`<div>
|
||||
<t t-if="flag" t-set="ourvar">1</t>
|
||||
<t t-else="" t-set="ourvar" t-value="0"></t>
|
||||
<t t-out="ourvar"/>
|
||||
<t t-esc="ourvar"/>
|
||||
</div>`
|
||||
);
|
||||
expect(renderToString(qweb, "test", { flag: true })).toBe("<div>1</div>");
|
||||
@@ -389,7 +383,7 @@ describe("t-set", () => {
|
||||
`<div>
|
||||
<t t-if="flag" t-set="ourvar" t-value="1"></t>
|
||||
<t t-else="" t-set="ourvar">0</t>
|
||||
<t t-out="ourvar"/>
|
||||
<t t-esc="ourvar"/>
|
||||
</div>`
|
||||
);
|
||||
expect(renderToString(qweb, "test", { flag: true })).toBe("<div>1</div>");
|
||||
@@ -402,10 +396,10 @@ describe("t-set", () => {
|
||||
`<div>
|
||||
<t t-set="v1" t-value="'before'"/>
|
||||
<t t-set="v2">
|
||||
<span><t t-out="v1"/></span>
|
||||
<span><t t-esc="v1"/></span>
|
||||
</t>
|
||||
<t t-set="v1" t-value="'after'"/>
|
||||
<t t-out="v2"/>
|
||||
<t t-raw="v2"/>
|
||||
</div>`
|
||||
);
|
||||
|
||||
@@ -419,7 +413,7 @@ describe("t-set", () => {
|
||||
<t t-set="v3" t-value="false"/>
|
||||
<t t-set="v1" t-value="'before'"/>
|
||||
<t t-set="v2" t-value="v3">
|
||||
<span><t t-out="v1"/></span>
|
||||
<span><t t-esc="v1"/></span>
|
||||
</t>
|
||||
<t t-set="v1" t-value="'after'"/>
|
||||
<t t-set="v3" t-value="true"/>
|
||||
@@ -437,7 +431,7 @@ describe("t-set", () => {
|
||||
<t t-set="v3" t-value="'Truthy'"/>
|
||||
<t t-set="v1" t-value="'before'"/>
|
||||
<t t-set="v2" t-value="v3">
|
||||
<span><t t-out="v1"/></span>
|
||||
<span><t t-esc="v1"/></span>
|
||||
</t>
|
||||
<t t-set="v1" t-value="'after'"/>
|
||||
<t t-set="v3" t-value="false"/>
|
||||
@@ -528,13 +522,13 @@ describe("t-if", () => {
|
||||
expect(normalize(renderToString(qweb, "test", context))).toBe("<div>andormgtnlt</div>");
|
||||
});
|
||||
|
||||
test("t-out with t-if", () => {
|
||||
qweb.addTemplate("test", `<div><t t-if="true" t-out="'x'"/></div>`);
|
||||
test("t-esc with t-if", () => {
|
||||
qweb.addTemplate("test", `<div><t t-if="true" t-esc="'x'"/></div>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<div>x</div>");
|
||||
});
|
||||
|
||||
test("t-out with t-elif", () => {
|
||||
qweb.addTemplate("test", `<div><t t-if="false">abc</t><t t-else="" t-out="'x'"/></div>`);
|
||||
test("t-esc with t-elif", () => {
|
||||
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>");
|
||||
});
|
||||
|
||||
@@ -544,7 +538,7 @@ describe("t-if", () => {
|
||||
`
|
||||
<div>
|
||||
<t t-set="title" t-value="'test'"/>
|
||||
<t t-if="title"><t t-out="title"/></t>
|
||||
<t t-if="title"><t t-esc="title"/></t>
|
||||
</div>`
|
||||
);
|
||||
const result = renderToString(qweb, "test");
|
||||
@@ -818,14 +812,14 @@ describe("t-call (template calling", () => {
|
||||
});
|
||||
|
||||
test("with used body", () => {
|
||||
qweb.addTemplate("_callee-printsbody", '<h1><t t-out="0"/></h1>');
|
||||
qweb.addTemplate("_callee-printsbody", '<h1><t t-esc="0"/></h1>');
|
||||
qweb.addTemplate("caller", '<t t-call="_callee-printsbody">ok</t>');
|
||||
const expected = "<h1>ok</h1>";
|
||||
expect(renderToString(qweb, "caller")).toBe(expected);
|
||||
});
|
||||
|
||||
test("with used set body", () => {
|
||||
qweb.addTemplate("_callee-uses-foo", '<t t-out="foo"/>');
|
||||
qweb.addTemplate("_callee-uses-foo", '<t t-esc="foo"/>');
|
||||
qweb.addTemplate(
|
||||
"caller",
|
||||
`
|
||||
@@ -836,7 +830,7 @@ describe("t-call (template calling", () => {
|
||||
});
|
||||
|
||||
test("inherit context", () => {
|
||||
qweb.addTemplate("_callee-uses-foo", '<t t-out="foo"/>');
|
||||
qweb.addTemplate("_callee-uses-foo", '<t t-esc="foo"/>');
|
||||
qweb.addTemplate(
|
||||
"caller",
|
||||
`
|
||||
@@ -855,7 +849,7 @@ describe("t-call (template calling", () => {
|
||||
<t t-call="_basic-callee">
|
||||
<t t-set="foo" t-value="42"/>
|
||||
</t>
|
||||
<t t-out="foo"/>
|
||||
<t t-esc="foo"/>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
@@ -943,7 +937,7 @@ describe("t-call (template calling", () => {
|
||||
</t>
|
||||
</div>
|
||||
<div t-name="nodeTemplate">
|
||||
<p><t t-out="node.val"/></p>
|
||||
<p><t t-esc="node.val"/></p>
|
||||
<t t-foreach="node.children or []" t-as="subtree">
|
||||
<t t-call="nodeTemplate">
|
||||
<t t-set="node" t-value="subtree"/>
|
||||
@@ -972,7 +966,7 @@ describe("t-call (template calling", () => {
|
||||
</t>
|
||||
</div>
|
||||
<div t-name="nodeTemplate">
|
||||
<p><t t-out="node.val"/></p>
|
||||
<p><t t-esc="node.val"/></p>
|
||||
<t t-foreach="node.children or []" t-as="subtree">
|
||||
<t t-call="nodeTemplate">
|
||||
<t t-set="node" t-value="subtree"/>
|
||||
@@ -1002,7 +996,7 @@ describe("t-call (template calling", () => {
|
||||
</div>
|
||||
<div t-name="nodeTemplate">
|
||||
<t t-set="recursive_idx" t-value="recursive_idx + 1"/>
|
||||
<p><t t-out="node.val"/> <t t-out="recursive_idx"/></p>
|
||||
<p><t t-esc="node.val"/> <t t-esc="recursive_idx"/></p>
|
||||
<t t-foreach="node.children or []" t-as="subtree">
|
||||
<t t-call="nodeTemplate">
|
||||
<t t-set="node" t-value="subtree"/>
|
||||
@@ -1033,7 +1027,7 @@ describe("t-call (template calling", () => {
|
||||
|
||||
test("t-call, conditional and t-set in t-call body", () => {
|
||||
QWeb.registerTemplate("callee1", "<div>callee1</div>");
|
||||
QWeb.registerTemplate("callee2", '<div>callee2 <t t-out="v"/></div>');
|
||||
QWeb.registerTemplate("callee2", '<div>callee2 <t t-esc="v"/></div>');
|
||||
QWeb.registerTemplate(
|
||||
"caller",
|
||||
`<div>
|
||||
@@ -1060,7 +1054,7 @@ describe("t-call (template calling", () => {
|
||||
</t>
|
||||
</div>
|
||||
<t t-name="sub">
|
||||
<span t-out="val3"/>
|
||||
<span t-esc="val3"/>
|
||||
</t>
|
||||
</templates>
|
||||
`);
|
||||
@@ -1081,8 +1075,8 @@ describe("t-call (template calling", () => {
|
||||
</t>
|
||||
</div>
|
||||
<t t-name="sub">
|
||||
<span t-out="val3"/>
|
||||
<t t-out="w"/>
|
||||
<span t-esc="val3"/>
|
||||
<t t-esc="w"/>
|
||||
</t>
|
||||
<p t-name="wrapper"><t t-set="w" t-value="'fromwrapper'"/><t t-call="main"/></p>
|
||||
</templates>
|
||||
@@ -1105,7 +1099,7 @@ describe("t-call (template calling", () => {
|
||||
});
|
||||
|
||||
test("t-call with t-set inside and body text content", () => {
|
||||
qweb.addTemplate("sub", `<p><t t-out="val"/></p>`);
|
||||
qweb.addTemplate("sub", `<p><t t-esc="val"/></p>`);
|
||||
qweb.addTemplate(
|
||||
"main",
|
||||
`
|
||||
@@ -1127,8 +1121,8 @@ describe("t-call (template calling", () => {
|
||||
});
|
||||
|
||||
test("dynamic t-call", () => {
|
||||
qweb.addTemplate("foo", `<foo><t t-out="val"/></foo>`);
|
||||
qweb.addTemplate("bar", `<bar><t t-out="val"/></bar>`);
|
||||
qweb.addTemplate("foo", `<foo><t t-esc="val"/></foo>`);
|
||||
qweb.addTemplate("bar", `<bar><t t-esc="val"/></bar>`);
|
||||
qweb.addTemplate("main", `<div><t t-call="{{template}}"/></div>`);
|
||||
const expected = "<div><foo>foo</foo></div>";
|
||||
expect(renderToString(qweb, "main", { template: "foo", val: "foo" })).toBe(expected);
|
||||
@@ -1147,7 +1141,7 @@ describe("foreach", () => {
|
||||
`
|
||||
<div>
|
||||
<t t-foreach="[3, 2, 1]" t-as="item">
|
||||
[<t t-out="item_index"/>: <t t-out="item"/> <t t-out="item_value"/>]
|
||||
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||
</t>
|
||||
</div>`
|
||||
);
|
||||
@@ -1161,7 +1155,7 @@ describe("foreach", () => {
|
||||
"test",
|
||||
`
|
||||
<div>
|
||||
<span t-foreach="[1, 2]" t-as="item" t-key="item"><t t-out="item"/></span>
|
||||
<span t-foreach="[1, 2]" t-as="item" t-key="item"><t t-esc="item"/></span>
|
||||
</div>`
|
||||
);
|
||||
const result = trim(renderToString(qweb, "test"));
|
||||
@@ -1175,7 +1169,7 @@ describe("foreach", () => {
|
||||
`
|
||||
<div>
|
||||
<t t-foreach="Array(5)" t-as="elem">
|
||||
-<t t-if="elem_first"> first</t><t t-if="elem_last"> last</t> (<t t-out="elem_index"/>)
|
||||
-<t t-if="elem_first"> first</t><t t-if="elem_last"> last</t> (<t t-esc="elem_index"/>)
|
||||
</t>
|
||||
</div>`
|
||||
);
|
||||
@@ -1190,7 +1184,7 @@ describe("foreach", () => {
|
||||
`
|
||||
<div>
|
||||
<t t-foreach="value" t-as="item">
|
||||
[<t t-out="item_index"/>: <t t-out="item"/> <t t-out="item_value"/>]
|
||||
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||
</t>
|
||||
</div>`
|
||||
);
|
||||
@@ -1203,7 +1197,7 @@ describe("foreach", () => {
|
||||
qweb.addTemplate(
|
||||
"test",
|
||||
`<div>
|
||||
<t t-foreach="[1]" t-as="item"><t t-out="item"/></t>
|
||||
<t t-foreach="[1]" t-as="item"><t t-esc="item"/></t>
|
||||
</div>`
|
||||
);
|
||||
const context = { __owl__: {} };
|
||||
@@ -1217,7 +1211,7 @@ describe("foreach", () => {
|
||||
`<div>
|
||||
<t t-foreach="numbers" t-as="number">
|
||||
<t t-foreach="letters" t-as="letter">
|
||||
[<t t-out="number"/><t t-out="letter"/>]
|
||||
[<t t-esc="number"/><t t-esc="letter"/>]
|
||||
</t>
|
||||
</t>
|
||||
</div>`
|
||||
@@ -1233,9 +1227,9 @@ describe("foreach", () => {
|
||||
"test_called",
|
||||
`<t>
|
||||
<t t-set="c" t-value="'x' + '_' + a + '_'+ b" />
|
||||
[<t t-out="a" />]
|
||||
[<t t-out="b" />]
|
||||
[<t t-out="c" />]
|
||||
[<t t-esc="a" />]
|
||||
[<t t-esc="b" />]
|
||||
[<t t-esc="c" />]
|
||||
</t>`
|
||||
);
|
||||
qweb.addTemplate(
|
||||
@@ -1245,9 +1239,9 @@ describe("foreach", () => {
|
||||
<t t-foreach="letters" t-as="b">
|
||||
<t t-call="test_called" />
|
||||
</t>
|
||||
<span t-out="c"/>
|
||||
<span t-esc="c"/>
|
||||
</t>
|
||||
<span>[<t t-out="a" />][<t t-out="b" />][<t t-out="c" />]</span>
|
||||
<span>[<t t-esc="a" />][<t t-esc="b" />][<t t-esc="c" />]</span>
|
||||
</div>`
|
||||
);
|
||||
const context = { numbers: [1, 2, 3], letters: ["a", "b"] };
|
||||
@@ -1260,9 +1254,9 @@ describe("foreach", () => {
|
||||
qweb.addTemplate(
|
||||
"test_called",
|
||||
`<t>
|
||||
[<t t-out="a" />]
|
||||
[<t t-out="b" />]
|
||||
[<t t-out="c" />]
|
||||
[<t t-esc="a" />]
|
||||
[<t t-esc="b" />]
|
||||
[<t t-esc="c" />]
|
||||
</t>`
|
||||
);
|
||||
qweb.addTemplate(
|
||||
@@ -1274,9 +1268,9 @@ describe("foreach", () => {
|
||||
<t t-set="c" t-value="'x' + '_' + a + '_'+ b" />
|
||||
</t>
|
||||
</t>
|
||||
<span t-out="c"/>
|
||||
<span t-esc="c"/>
|
||||
</t>
|
||||
<span>[<t t-out="a" />][<t t-out="b" />][<t t-out="c" />]</span>
|
||||
<span>[<t t-esc="a" />][<t t-esc="b" />][<t t-esc="c" />]</span>
|
||||
</div>`
|
||||
);
|
||||
const context = { numbers: [1, 2, 3], letters: ["a", "b"] };
|
||||
@@ -1302,7 +1296,7 @@ describe("foreach", () => {
|
||||
`
|
||||
<div>
|
||||
<t t-foreach="[1, 2]" t-as="item">
|
||||
<span><t t-out="item"/></span>
|
||||
<span><t t-esc="item"/></span>
|
||||
</t>
|
||||
</div>`
|
||||
);
|
||||
@@ -1318,14 +1312,14 @@ describe("foreach", () => {
|
||||
describe("misc", () => {
|
||||
test("global", () => {
|
||||
qweb.addTemplate("_callee-asc", `<año t-att-falló="'agüero'" t-raw="0"/>`);
|
||||
qweb.addTemplate("_callee-uses-foo", `<span t-out="foo">foo default</span>`);
|
||||
qweb.addTemplate("_callee-uses-foo", `<span t-esc="foo">foo default</span>`);
|
||||
qweb.addTemplate("_callee-asc-toto", `<div t-raw="toto">toto default</div>`);
|
||||
qweb.addTemplate(
|
||||
"caller",
|
||||
`
|
||||
<div>
|
||||
<t t-foreach="[4,5,6]" t-as="value">
|
||||
<span t-out="value"/>
|
||||
<span t-esc="value"/>
|
||||
<t t-call="_callee-asc">
|
||||
<t t-call="_callee-uses-foo">
|
||||
<t t-set="foo" t-value="'aaa'"/>
|
||||
@@ -1725,7 +1719,7 @@ describe("t-on", () => {
|
||||
`<div>
|
||||
<t t-foreach="projects" t-as="project">
|
||||
<a href="#" t-key="project" t-on-click.prevent="onEdit(project.id)">
|
||||
Edit <t t-out="project.name"/>
|
||||
Edit <t t-esc="project.name"/>
|
||||
</a>
|
||||
</t>
|
||||
</div>`
|
||||
@@ -1773,9 +1767,9 @@ describe("t-on", () => {
|
||||
button.click();
|
||||
});
|
||||
|
||||
test("t-on combined with t-out", async () => {
|
||||
test("t-on combined with t-esc", async () => {
|
||||
expect.assertions(3);
|
||||
qweb.addTemplate("test", `<div><button t-on-click="onClick" t-out="text"/></div>`);
|
||||
qweb.addTemplate("test", `<div><button t-on-click="onClick" t-esc="text"/></div>`);
|
||||
const steps: string[] = [];
|
||||
const owner = {
|
||||
text: "Click here",
|
||||
@@ -1858,7 +1852,7 @@ describe("t-ref", () => {
|
||||
`
|
||||
<div>
|
||||
<t t-foreach="items" t-as="item">
|
||||
<div t-ref="{{item}}" t-key="item"><t t-out="item"/></div>
|
||||
<div t-ref="{{item}}" t-key="item"><t t-esc="item"/></div>
|
||||
</t>
|
||||
</div>`
|
||||
);
|
||||
@@ -2044,7 +2038,7 @@ describe("whitespace handling", () => {
|
||||
|
||||
describe("t-key", () => {
|
||||
test("can use t-key directive on a node", () => {
|
||||
qweb.addTemplate("test", `<div t-key="beer.id"><t t-out="beer.name"/></div>`);
|
||||
qweb.addTemplate("test", `<div t-key="beer.id"><t t-esc="beer.name"/></div>`);
|
||||
expect(renderToString(qweb, "test", { beer: { id: 12, name: "Chimay Rouge" } })).toBe(
|
||||
"<div>Chimay Rouge</div>"
|
||||
);
|
||||
@@ -2054,7 +2048,7 @@ describe("t-key", () => {
|
||||
qweb.addTemplate(
|
||||
"test",
|
||||
`<ul>
|
||||
<li t-foreach="beers" t-as="beer" t-key="beer.id"><t t-out="beer.name"/></li>
|
||||
<li t-foreach="beers" t-as="beer" t-key="beer.id"><t t-esc="beer.name"/></li>
|
||||
</ul>`
|
||||
);
|
||||
expect(
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { QWeb } from "../../src/qweb/index";
|
||||
import { renderToString } from "../helpers";
|
||||
|
||||
describe("memory", () => {
|
||||
test("t-foreach does not leak stuff in global scope", () => {
|
||||
let qweb = new QWeb();
|
||||
const initialNumberOfGlobals = Object.keys(window).length;
|
||||
qweb.addTemplate("test", `<p><t t-foreach="[3, 2, 1]" t-as="item"><t t-esc="item"/></t></p>`);
|
||||
const result = renderToString(qweb, "test");
|
||||
const expected = `<p>321</p>`;
|
||||
expect(result).toBe(expected);
|
||||
expect(Object.keys(window).length).toBe(initialNumberOfGlobals);
|
||||
});
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
import { QWeb } from "../../src/qweb/index";
|
||||
import { renderToString } from "../helpers";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Setup and helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// We create before each test:
|
||||
// - qweb: a new QWeb instance
|
||||
|
||||
let qweb: QWeb;
|
||||
|
||||
beforeEach(() => {
|
||||
QWeb.TEMPLATES = {};
|
||||
QWeb.nextId = 1;
|
||||
qweb = new QWeb();
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Tests
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
describe("old t-esc directive", () => {
|
||||
test("simple dynamic value", () => {
|
||||
qweb.addTemplate("test", '<t><t t-esc="text"/></t>');
|
||||
expect(renderToString(qweb, "test", { text: "hello vdom" })).toBe("hello vdom");
|
||||
});
|
||||
|
||||
test("escaping", () => {
|
||||
qweb.addTemplate("test", `<span><t t-esc="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: "<ok>abc</ok>" })).toBe(
|
||||
"<span>&lt;ok&gt;abc&lt;/ok&gt;</span>"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("old t-raw directive", () => {
|
||||
test("literal", () => {
|
||||
qweb.addTemplate("test", `<span><t t-raw="'ok'"/></span>`);
|
||||
expect(renderToString(qweb, "test")).toBe("<span>ok</span>");
|
||||
});
|
||||
|
||||
test("variable", () => {
|
||||
qweb.addTemplate("test", `<span><t t-raw="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: "ok" })).toBe("<span>ok</span>");
|
||||
});
|
||||
|
||||
test("not escaping", () => {
|
||||
qweb.addTemplate("test", `<div><t t-raw="var"/></div>`);
|
||||
expect(renderToString(qweb, "test", { var: "<ok></ok>" })).toBe("<div><ok></ok></div>");
|
||||
});
|
||||
|
||||
test("t-raw and another sibling node", () => {
|
||||
qweb.addTemplate("test", `<span><span>hello</span><t t-raw="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: "<ok>world</ok>" })).toBe(
|
||||
"<span><span>hello</span><ok>world</ok></span>"
|
||||
);
|
||||
});
|
||||
|
||||
test("t-raw with comment", () => {
|
||||
qweb.addTemplate("test", `<span><t t-raw="var"/></span>`);
|
||||
expect(renderToString(qweb, "test", { var: "<p>text<!-- top secret --></p>" })).toBe(
|
||||
"<span><p>text<!-- top secret --></p></span>"
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user