Compare commits

..

1 Commits

Author SHA1 Message Date
Samuel Degueldre abd2b42307 [IMP] slots: make defaultContent of slot available to slot as zero
In some cases, it is useful for a slot to be able to keep the default
content of the slot it is supposed to replace and simply add extra
content instead of simply replacing it entirely. This commit makes the
default content of a slot available in the context as `zero` similar to
how t-call can access the content that was inside the t-call.
2023-01-09 10:50:57 +01:00
43 changed files with 346 additions and 6666 deletions
-47
View File
@@ -1,47 +0,0 @@
{
"env": {
"browser": true,
"node": true,
"es2022": true
},
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"parserOptions": {
"sourceType": "module"
},
"root": true,
"rules": {
"no-restricted-globals": ["error", "event", "self"],
"no-const-assign": ["error"],
"no-debugger": ["error"],
"no-dupe-class-members": ["error"],
"no-dupe-keys": ["error"],
"no-dupe-args": ["error"],
"no-dupe-else-if": ["error"],
"no-unsafe-negation": ["error"],
"no-duplicate-imports": ["error"],
"valid-typeof": ["error"],
"@typescript-eslint/no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": false, "caughtErrors": "all" }],
"no-restricted-syntax": [
"error",
{
"selector": "MemberExpression[object.name='test'][property.name='only']",
"message": "test.only(...) is forbidden",
},
{
"selector": "MemberExpression[object.name='describe'][property.name='only']",
"message": "describe.only(...) is forbidden",
}
],
},
"globals": {
"describe": true,
"expect": true,
"test": true,
"beforeEach": true,
"beforeAll": true,
"afterEach": true,
"afterAll": true,
"jest": true,
},
}
+1 -2
View File
@@ -22,8 +22,7 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm install
- run: npm run test
- run: npm run check-formatting
- run: npm run lint
- run: npm run build
+3
View File
@@ -14,6 +14,9 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
yarn.lock
#ide's
.vscode
.idea
+3 -3
View File
@@ -174,9 +174,9 @@ class Root extends Component {
```
The template contains a [`t-foreach`](../reference/templates.md#loops) loop to iterate
through the tasks. It can find the `tasks` list from the component, since the rendering
context contains the properties of the component. Note that we use the `id` of each task
as a `t-key`, which is very common. There are two css classes: `task-list` and `task`,
through the tasks. It can find the `tasks` list from the component, since the
component is the rendering context. Note that we use the `id` of each task as a
`t-key`, which is very common. There are two css classes: `task-list` and `task`,
that we will use in the next section.
Finally, notice the use of the `t-att-checked` attribute:
+4 -3
View File
@@ -115,7 +115,7 @@ It is useful to explain the various rules that apply on these expressions:
<div><p t-if="console.log(1)">NOT valid</p></div>
```
2. it can use anything in the rendering context (which typically contains the properties of the component):
2. it can use anything in the rendering context (typically, the component):
```xml
<p t-if="user.birthday === today()">Happy bithday!</p>
@@ -541,8 +541,9 @@ This can be used to define variables scoped to a sub template:
```
Note: by default, the rendering context for a sub template is simply the current
rendering context. However, it may be useful to be able to specify a specific
object as context. This can be done by using the `t-call-context` directive:
rendering context (so, the current component). However, it may be useful to be
able to specify a specific object as context. This can be done by using the
`t-call-context` directive:
```xml
<t t-call="other-template" t-call-context="obj"/>
-5711
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "2.0.7",
"version": "2.0.2",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"module": "dist/owl.es.js",
@@ -25,7 +25,6 @@
"playground:watch": "npm-run-all --parallel playground:serve \"build:* -- --watch\"",
"prettier": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --write",
"check-formatting": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --check",
"lint": "eslint src/**/*.ts tests/**/*.ts",
"publish": "npm run build && npm publish",
"release": "node tools/release.js",
"compile_templates": "node tools/compile_xml.js"
@@ -43,25 +42,26 @@
"devDependencies": {
"@types/jest": "^27.0.1",
"@types/node": "^14.11.8",
"@typescript-eslint/eslint-plugin": "5.48.1",
"@typescript-eslint/parser": "5.48.1",
"chalk": "^3.0.0",
"cpx": "^1.5.0",
"current-git-branch": "^1.1.0",
"eslint": "8.31.0",
"git-rev-sync": "^3.0.2",
"git-rev-sync": "^1.12.0",
"github-api": "^3.3.0",
"jest": "^27.1.0",
"jest-diff": "^27.3.1",
"jest-environment-jsdom": "^27.1.0",
"live-server": "^1.2.1",
"npm-run-all": "^4.1.5",
"prettier": "2.4.1",
"rollup": "^2.56.3",
"rollup-plugin-dts": "^4.2.2",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.31.1",
"sass": "^1.16.1",
"source-map-support": "^0.5.10",
"ts-jest": "^27.0.5",
"typescript": "4.5.2"
"typescript": "4.5.2",
"uglify-es": "^3.3.9"
},
"jest": {
"testEnvironment": "jsdom",
+37 -82
View File
@@ -15,7 +15,6 @@ import {
ASTLog,
ASTMulti,
ASTSlot,
ASTSlotDefinition,
ASTTCall,
ASTTCallBlock,
ASTTEsc,
@@ -138,14 +137,12 @@ interface Context {
index: number | string;
forceNewBlock: boolean;
preventRoot?: boolean;
ignoreRoot?: boolean;
isLast?: boolean;
translate: boolean;
tKeyExpr: string | null;
nameSpace?: string;
tModelSelectedExpr?: string;
ctxVar?: string;
slotVar?: string;
}
function createContext(parentCtx: Context, params?: Partial<Context>): Context {
@@ -158,7 +155,6 @@ function createContext(parentCtx: Context, params?: Partial<Context>): Context {
tKeyExpr: null,
nameSpace: parentCtx.nameSpace,
tModelSelectedExpr: parentCtx.tModelSelectedExpr,
slotVar: parentCtx.slotVar
},
params
);
@@ -362,7 +358,7 @@ export class CodeGenerator {
): BlockDescription {
const hasRoot = this.target.hasRoot;
const block = new BlockDescription(this.target, type);
if (!hasRoot && !ctx.preventRoot && !ctx.ignoreRoot) {
if (!hasRoot && !ctx.preventRoot) {
this.target.hasRoot = true;
block.isRoot = true;
}
@@ -388,9 +384,6 @@ export class CodeGenerator {
blockExpr = `toggler(${ctx.tKeyExpr}, ${blockExpr})`;
}
if (ctx.ignoreRoot) {
return;
}
if (block.isRoot && !ctx.preventRoot) {
if (this.target.on) {
blockExpr = this.wrapWithEventCatcher(blockExpr, this.target.on);
@@ -472,8 +465,6 @@ export class CodeGenerator {
return this.compileLog(ast, ctx);
case ASTType.TSlot:
return this.compileTSlot(ast, ctx);
case ASTType.TSetSlot:
return this.compileTSetSlot(ast, ctx);
case ASTType.TTranslation:
return this.compileTTranslation(ast, ctx);
case ASTType.TPortal:
@@ -641,15 +632,7 @@ export class CodeGenerator {
let idx: number;
if (specialInitTargetAttr) {
let targetExpr = targetAttr in attrs && `'${attrs[targetAttr]}'`;
if (!targetExpr && ast.attrs) {
// look at the dynamic attribute counterpart
const dynamicTgExpr = ast.attrs[`t-att-${targetAttr}`];
if (dynamicTgExpr) {
targetExpr = compileExpr(dynamicTgExpr);
}
}
idx = block!.insertData(`${fullExpression} === ${targetExpr}`, "attr");
idx = block!.insertData(`${fullExpression} === '${attrs[targetAttr]}'`, "attr");
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
} else if (hasDynamicChildren) {
const bValueId = generateId("bValue");
@@ -681,9 +664,8 @@ export class CodeGenerator {
this.target.hasRef = true;
const isDynamic = INTERP_REGEXP.test(ast.ref);
if (isDynamic) {
this.helpers.add("singleRefSetter");
const str = replaceDynamicParts(ast.ref, (expr) => this.captureExpression(expr, true));
const idx = block!.insertData(`singleRefSetter(refs, ${str})`, "ref");
const idx = block!.insertData(`(el) => refs[${str}] = el`, "ref");
attrs["block-ref"] = String(idx);
} else {
let name = ast.ref;
@@ -696,8 +678,7 @@ export class CodeGenerator {
info[1] = `multiRefSetter(refs, \`${name}\`)`;
} else {
let id = generateId("ref");
this.helpers.add("singleRefSetter");
this.target.refInfo[name] = [id, `singleRefSetter(refs, \`${name}\`)`];
this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`];
const index = block!.data.push(id) - 1;
attrs["block-ref"] = String(index);
}
@@ -1153,38 +1134,52 @@ export class CodeGenerator {
const props: string[] = ast.props ? this.formatPropObject(ast.props) : [];
// slots
let slotVar: string = "";
if (ast.body) {
slotVar = generateId("slots");
this.helpers.add("markRaw");
if (ast.body.type === ASTType.TSetSlot) {
const subCtx = createContext(ctx, { slotVar: undefined, ignoreRoot: true });
const slotInfo = this.compileAST(ast.body, subCtx);
this.target.addLine(`let ${slotVar} = markRaw({'${ast.body.name}': ${slotInfo}});`);
} else {
this.target.addLine(`let ${slotVar} = markRaw({});`);
const subCtx = createContext(ctx, { slotVar, ignoreRoot: true });
this.compileAST(ast.body, subCtx);
let slotDef: string = "";
if (ast.slots) {
let ctxStr = "ctx";
if (this.target.loopLevel || !this.hasSafeContext) {
ctxStr = generateId("ctx");
this.helpers.add("capture");
this.define(ctxStr, `capture(ctx)`);
}
let slotStr: string[] = [];
for (let slotName in ast.slots) {
const slotAst = ast.slots[slotName];
const params = [];
if (slotAst.content) {
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
params.push(`__render: ${name}.bind(this), __ctx: ${ctxStr}`);
}
const scope = ast.slots[slotName].scope;
if (scope) {
params.push(`__scope: "${scope}"`);
}
if (ast.slots[slotName].attrs) {
params.push(...this.formatPropObject(ast.slots[slotName].attrs!));
}
const slotInfo = `{${params.join(", ")}}`;
slotStr.push(`'${slotName}': ${slotInfo}`);
}
slotDef = `{${slotStr.join(", ")}}`;
}
if (slotVar && !(ast.dynamicProps || hasSlotsProp)) {
props.push(`slots: ${slotVar}`);
if (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
this.helpers.add("markRaw");
props.push(`slots: markRaw(${slotDef})`);
}
let propString = this.getPropString(props, ast.dynamicProps);
let propVar: string;
if ((slotVar && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
propVar = generateId("props");
this.define(propVar!, propString);
propString = propVar!;
}
if (slotVar && (ast.dynamicProps || hasSlotsProp)) {
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
this.helpers.add("markRaw");
this.addLine(`${propVar!}.slots = markRaw(Object.assign(${slotVar}, ${propVar!}.slots))`);
this.addLine(`${propVar!}.slots = markRaw(Object.assign(${slotDef}, ${propVar!}.slots))`);
}
// cmap key
@@ -1215,18 +1210,11 @@ export class CodeGenerator {
id,
expr: `app.createComponent(${
ast.isDynamic ? null : expr
}, ${!ast.isDynamic}, ${!!ast.body}, ${!!ast.dynamicProps}, ${
}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, ${
!ast.props && !ast.dynamicProps
})`,
});
if (ast.isDynamic) {
// If the component class changes, this can cause delayed renders to go
// through if the key doesn't change. Use the component name for now.
// This means that two component classes with the same name isn't supported
// in t-component. We can generate a unique id per class later if needed.
keyArg = `(${expr}).name + ${keyArg}`;
}
let blockExpr = `${id}(${propString}, ${keyArg}, node, this, ${ast.isDynamic ? expr : null})`;
if (ast.isDynamic) {
blockExpr = `toggler(${expr}, ${blockExpr})`;
@@ -1258,39 +1246,6 @@ export class CodeGenerator {
return `${name}(${expr}, [${handlers.join(",")}])`;
}
compileTSetSlot(ast: ASTSlotDefinition, ctx: Context): string {
let ctxStr = "ctx";
if (this.target.loopLevel || !this.hasSafeContext) {
ctxStr = generateId("ctx");
this.helpers.add("capture");
this.define(ctxStr, `capture(ctx)`);
}
// let slotStr: string[] = [];
// for (let slotName in ast.slots) {
// const slotAst = ast.slots[slotName];
const params = [];
if (ast.content) {
const name = this.compileInNewTarget("slot", ast.content, ctx, ast.on);
params.push(`__render: ${name}.bind(this), __ctx: ${ctxStr}`);
}
// ast.scope
// const scope = ast.slots[slotName].scope;
if (ast.scope) {
params.push(`__scope: "${ast.scope}"`);
}
if (ast.attrs) {
params.push(...this.formatPropObject(ast.attrs!));
}
const slotInfo = `{${params.join(", ")}}`;
if (ctx.slotVar) {
this.target.addLine(`${ctx.slotVar}['${ast.name}'] = ${slotInfo};`)
}
// slotStr.push(`'${slotName}': ${slotInfo}`);
// }
// slotDef = `{${slotStr.join(", ")}}`;
return slotInfo;
}
compileTSlot(ast: ASTSlot, ctx: Context): string {
this.helpers.add("callSlot");
let { block } = ctx;
+1 -1
View File
@@ -28,7 +28,7 @@ import { OwlError } from "../runtime/error_handling";
//------------------------------------------------------------------------------
const RESERVED_WORDS =
"true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date".split(
"true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,eval,void,Math,RegExp,Array,Object,Date".split(
","
);
+67 -88
View File
@@ -23,7 +23,6 @@ export const enum ASTType {
TDebug,
TLog,
TSlot,
TSetSlot,
TCallBlock,
TTranslation,
TPortal,
@@ -121,9 +120,7 @@ export interface ASTTCall {
context: string | null;
}
export interface ASTSlotDefinition {
type: ASTType.TSetSlot;
name: string;
interface SlotDefinition {
content: AST | null;
scope: string | null;
on: EventHandlers | null;
@@ -137,8 +134,7 @@ export interface ASTComponent {
dynamicProps: string | null;
on: EventHandlers | null;
props: { [name: string]: string } | null;
body: AST | null;
// slots: { [name: string]: ASTSlotDefinition } | null;
slots: { [name: string]: SlotDefinition } | null;
}
export interface ASTSlot {
@@ -190,7 +186,6 @@ export type AST =
| ASTTKey
| ASTComponent
| ASTSlot
| ASTSlotDefinition
| ASTTCallBlock
| ASTLog
| ASTDebug
@@ -243,7 +238,6 @@ function parseNode(node: Node, ctx: ParsingContext): AST | null {
parseTKey(node, ctx) ||
parseTTranslation(node, ctx) ||
parseTSlot(node, ctx) ||
parseTSetSlot(node, ctx) ||
parseTOutNode(node, ctx) ||
parseComponent(node, ctx) ||
parseDOMNode(node, ctx) ||
@@ -579,12 +573,12 @@ function parseTCall(node: Element, ctx: ParsingContext): AST | null {
ast.content = [tcall];
return ast;
}
// if (ast && ast.type === ASTType.TComponent) {
// return {
// ...ast,
// slots: { default: { content: tcall, scope: null, on: null, attrs: null } },
// };
// }
if (ast && ast.type === ASTType.TComponent) {
return {
...ast,
slots: { default: { content: tcall, scope: null, on: null, attrs: null } },
};
}
}
const body = parseChildren(node, ctx);
@@ -712,8 +706,8 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
const dynamicProps = node.getAttribute("t-props");
node.removeAttribute("t-props");
// const defaultSlotScope = node.getAttribute("t-slot-scope");
// node.removeAttribute("t-slot-scope");
const defaultSlotScope = node.getAttribute("t-slot-scope");
node.removeAttribute("t-slot-scope");
let on: ASTComponent["on"] = null;
let props: ASTComponent["props"] = null;
@@ -733,17 +727,67 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
}
}
let body: ASTComponent["body"] = null;
// let slots: ASTComponent["slots"] | null = null;
let slots: ASTComponent["slots"] | null = null;
if (node.hasChildNodes()) {
body = parseChildNodes(node, ctx);
if (!node.querySelector('[t-set-slot]')) {
body = {type: ASTType.TSetSlot, name: "default", content: body, on: null, attrs: null, scope: null}
const clone = <Element>node.cloneNode(true);
// named slots
const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
for (let slotNode of slotNodes) {
if (slotNode.tagName !== "t") {
throw new OwlError(
`Directive 't-set-slot' can only be used on <t> nodes (used on a <${slotNode.tagName}>)`
);
}
const name = slotNode.getAttribute("t-set-slot")!;
// check if this is defined in a sub component (in which case it should
// be ignored)
let el = slotNode.parentElement!;
let isInSubComponent = false;
while (el !== clone) {
if (el!.hasAttribute("t-component") || el!.tagName[0] === el!.tagName[0].toUpperCase()) {
isInSubComponent = true;
break;
}
el = el.parentElement!;
}
if (isInSubComponent) {
continue;
}
slotNode.removeAttribute("t-set-slot");
slotNode.remove();
const slotAst = parseNode(slotNode, ctx);
let on: SlotDefinition["on"] = null;
let attrs: Attrs | null = null;
let scope: string | null = null;
for (let attributeName of slotNode.getAttributeNames()) {
const value = slotNode.getAttribute(attributeName)!;
if (attributeName === "t-slot-scope") {
scope = value;
continue;
} else if (attributeName.startsWith("t-on-")) {
on = on || {};
on[attributeName.slice(5)] = value;
} else {
attrs = attrs || {};
attrs[attributeName] = value;
}
}
slots = slots || {};
slots[name] = { content: slotAst, on, attrs, scope };
}
// default slot
const defaultContent = parseChildNodes(clone, ctx);
slots = slots || {};
// t-set-slot="default" has priority over content
if (defaultContent && !slots.default) {
slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
}
}
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, body, on };
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, slots, on };
}
// -----------------------------------------------------------------------------
@@ -777,71 +821,6 @@ function parseTSlot(node: Element, ctx: ParsingContext): AST | null {
};
}
function parseTSetSlot(node: Element, ctx: ParsingContext): AST | null {
if (!node.hasAttribute("t-set-slot")) {
return null;
}
// const t = el.ownerDocument.createElement("t");
// const clone = <Element>node.cloneNode(true);
// // named slots
// const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
// for (let slotNode of slotNodes) {
if (node.tagName !== "t") {
throw new OwlError(
`Directive 't-set-slot' can only be used on <t> nodes (used on a <${node.tagName}>)`
);
}
const name = node.getAttribute("t-set-slot")!;
// // check if this is defined in a sub component (in which case it should
// // be ignored)
// let el = slotNode.parentElement!;
// let isInSubComponent = false;
// while (el !== clone) {
// if (el!.hasAttribute("t-component") || el!.tagName[0] === el!.tagName[0].toUpperCase()) {
// isInSubComponent = true;
// break;
// }
// el = el.parentElement!;
// }
// if (isInSubComponent) {
// continue;
// }
node.removeAttribute("t-set-slot");
node.remove();
const slotAst = parseNode(node, ctx);
let on: ASTSlotDefinition["on"] = null;
let attrs: Attrs | null = null;
let scope: string | null = null;
for (let attributeName of node.getAttributeNames()) {
const value = node.getAttribute(attributeName)!;
if (attributeName === "t-slot-scope") {
scope = value;
continue;
} else if (attributeName.startsWith("t-on-")) {
on = on || {};
on[attributeName.slice(5)] = value;
} else {
attrs = attrs || {};
attrs[attributeName] = value;
}
}
// slots = slots || {};
return { type: ASTType.TSetSlot, name, content: slotAst, on, attrs, scope };
// }
// // default slot
// const defaultContent = parseChildNodes(clone, ctx);
// slots = slots || {};
// // t-set-slot="default" has priority over content
// if (defaultContent && !slots.default) {
// slots.default = { content: defaultContent, on, attrs: null, scope: defaultSlotScope };
// }
}
function parseTTranslation(node: Element, ctx: ParsingContext): AST | null {
if (node.getAttribute("t-translation") !== "off") {
return null;
+3 -20
View File
@@ -1,11 +1,12 @@
import { Component, ComponentConstructor, Props } from "./component";
import { ComponentNode } from "./component_node";
import { nodeErrorHandlers, OwlError, handleError } from "./error_handling";
import { Fiber, RootFiber, MountOptions } from "./fibers";
import { nodeErrorHandlers, OwlError } from "./error_handling";
import { Fiber, MountOptions } from "./fibers";
import { Scheduler } from "./scheduler";
import { validateProps } from "./template_helpers";
import { TemplateSet, TemplateSetConfig } from "./template_set";
import { validateTarget } from "./utils";
import { handleError } from "./error_handling";
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
@@ -31,22 +32,6 @@ This is not suitable for production use.
See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
};
declare global {
interface Window {
__OWL_DEVTOOLS__: {
apps: Set<App>;
Fiber: typeof Fiber;
RootFiber: typeof RootFiber;
};
}
}
window.__OWL_DEVTOOLS__ ||= {
apps: new Set<App>(),
Fiber: Fiber,
RootFiber: RootFiber,
};
export class App<
T extends abstract new (...args: any) => any = any,
P extends object = any,
@@ -64,7 +49,6 @@ export class App<
constructor(Root: ComponentConstructor<P, E>, config: AppConfig<P, E> = {}) {
super(config);
this.Root = Root;
window.__OWL_DEVTOOLS__.apps.add(this);
if (config.test) {
this.dev = true;
}
@@ -126,7 +110,6 @@ export class App<
this.scheduler.flush();
this.root.destroy();
}
window.__OWL_DEVTOOLS__.apps.delete(this);
}
createComponent<P extends Props>(
+1 -3
View File
@@ -114,8 +114,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
}
}
this.component = new C(props, env, this);
const ctx = Object.assign(Object.create(this.component), { this: this.component });
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
this.component.setup();
currentNode = null;
}
@@ -318,7 +317,6 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
}
_patch() {
let hasChildren = false;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (let _k in this.children) {
hasChildren = true;
break;
+1 -1
View File
@@ -117,7 +117,7 @@ export function useEffect(effect: Effect, computeDependencies: () => any[] = ()
* `useExternalListener(window, 'click', this._doSomething);`
* */
export function useExternalListener(
target: EventTarget,
target: HTMLElement | typeof window,
eventName: string,
handler: EventListener,
eventParams?: AddEventListenerOptions
+7 -23
View File
@@ -1,13 +1,8 @@
import type { Callback } from "./utils";
import { Callback } from "./utils";
import { OwlError } from "./error_handling";
// Special key to subscribe to, to be notified of key creation/deletion
const KEYCHANGES = Symbol("Key changes");
// Used to specify the absence of a callback, can be used as WeakMap key but
// should only be used as a sentinel value and never called.
const NO_CALLBACK = () => {
throw new Error("Called NO_CALLBACK. Owl is broken, please report this to the maintainers.");
};
// The following types only exist to signify places where objects are expected
// to be reactive or not, they provide no type checking benefit over "object"
@@ -91,9 +86,6 @@ const targetToKeysToCallbacks = new WeakMap<Target, Map<PropertyKey, Set<Callbac
* @param callback the function to call when the key changes
*/
function observeTargetKey(target: Target, key: PropertyKey, callback: Callback): void {
if (callback === NO_CALLBACK) {
return;
}
if (!targetToKeysToCallbacks.get(target)) {
targetToKeysToCallbacks.set(target, new Map());
}
@@ -148,11 +140,8 @@ export function clearReactivesForCallback(callback: Callback): void {
if (!observedKeys) {
continue;
}
for (const [key, callbacks] of observedKeys.entries()) {
for (const callbacks of observedKeys.values()) {
callbacks.delete(callback);
if (!callbacks.size) {
observedKeys.delete(key);
}
}
}
targetsToClear.clear();
@@ -162,15 +151,10 @@ export function getSubscriptions(callback: Callback) {
const targets = callbacksToTargets.get(callback) || [];
return [...targets].map((target) => {
const keysToCallbacks = targetToKeysToCallbacks.get(target);
let keys = [];
if (keysToCallbacks) {
for (const [key, cbs] of keysToCallbacks) {
if (cbs.has(callback)) {
keys.push(key);
}
}
}
return { target, keys };
return {
target,
keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
};
});
}
// Maps reactive objects to the underlying target
@@ -203,7 +187,7 @@ const reactiveCache = new WeakMap<Target, WeakMap<Callback, Reactive<Target>>>()
* reactive has changed
* @returns a proxy that tracks changes to it
*/
export function reactive<T extends Target>(target: T, callback: Callback = NO_CALLBACK): T {
export function reactive<T extends Target>(target: T, callback: Callback = () => {}): T {
if (!canBeMadeReactive(target)) {
throw new OwlError(`Cannot make the given value reactive`);
}
+7 -11
View File
@@ -29,6 +29,13 @@ function callSlot(
const slots = ctx.props.slots || {};
const { __render, __ctx, __scope } = slots[name] || {};
const slotScope = ObjectCreate(__ctx || {});
if (defaultContent) {
Object.defineProperty(slotScope, helpers.zero, {
get() {
return defaultContent(ctx, parent, key);
},
});
}
if (__scope) {
slotScope[__scope] = extra;
}
@@ -202,16 +209,6 @@ function multiRefSetter(refs: RefMap, name: string): RefSetter {
};
}
function singleRefSetter(refs: RefMap, name: string): RefSetter {
let _el: HTMLElement | null = null;
return (el) => {
if (el || refs[name] === _el) {
refs[name] = el;
_el = el;
}
};
}
/**
* Validate the component props (or next props) against the (static) props
* description. This is potentially an expensive operation: it may needs to
@@ -270,7 +267,6 @@ export const helpers = {
prepareList,
setContextValue,
multiRefSetter,
singleRefSetter,
shallowEqual,
toNumber,
validateProps,
-2
View File
@@ -1,5 +1,4 @@
import { OwlError } from "./error_handling";
import { toRaw } from "./reactivity";
type BaseType =
| typeof String
@@ -85,7 +84,6 @@ export function validateSchema(obj: { [key: string]: any }, schema: Schema): str
if (Array.isArray(schema)) {
schema = toSchema(schema);
}
obj = toRaw(obj);
let errors = [];
// check if each value in obj has correct shape
for (let key in obj) {
@@ -519,9 +519,8 @@ exports[`t-on t-on, with arguments and t-call 2`] = `
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['this'];
const v2 = ctx['value'];
let hdlr1 = [()=>v1.update(v2), ctx];
const v1 = ctx['value'];
let hdlr1 = [()=>this.update(v1), ctx];
return block1([hdlr1]);
}
}"
@@ -182,7 +182,7 @@ exports[`misc other complex template 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { prepareList, withKey, singleRefSetter } = helpers;
let { prepareList, withKey } = helpers;
const callTemplate_1 = app.getTemplate(\`LOAD_INFOS_TEMPLATE\`);
const comp1 = app.createComponent(\`BundlesList\`, true, false, false, false);
const comp2 = app.createComponent(\`BundlesList\`, true, false, false, false);
@@ -218,8 +218,8 @@ exports[`misc other complex template 1`] = `
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`search_input\`);
const ref2 = singleRefSetter(refs, \`settings_menu\`);
const ref1 = (el) => refs[\`search_input\`] = el;
const ref2 = (el) => refs[\`settings_menu\`] = el;
let b2,b4,b14,b17,b22,b23,b24,b25;
let attr1 = \`/runbot/\${ctx['project'].slug}\`;
let txt1 = ctx['project'].name;
@@ -4,14 +4,13 @@ exports[`t-ref can get a dynamic ref on a node 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter } = helpers;
let block1 = createBlock(\`<div><span block-ref=\\"0\\"/></div>\`);
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const v1 = ctx['id'];
let ref1 = singleRefSetter(refs, \`myspan\${v1}\`);
let ref1 = (el) => refs[\`myspan\${v1}\`] = el;
return block1([ref1]);
}
}"
@@ -21,14 +20,13 @@ exports[`t-ref can get a dynamic ref on a node, alternate syntax 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter } = helpers;
let block1 = createBlock(\`<div><span block-ref=\\"0\\"/></div>\`);
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const v1 = ctx['id'];
let ref1 = singleRefSetter(refs, \`myspan\${v1}\`);
let ref1 = (el) => refs[\`myspan\${v1}\`] = el;
return block1([ref1]);
}
}"
@@ -38,13 +36,12 @@ exports[`t-ref can get a ref on a node 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter } = helpers;
let block1 = createBlock(\`<div><span block-ref=\\"0\\"/></div>\`);
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`myspan\`);
const ref1 = (el) => refs[\`myspan\`] = el;
return block1([ref1]);
}
}"
@@ -69,13 +66,12 @@ exports[`t-ref ref in a t-call 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter } = helpers;
let block1 = createBlock(\`<div>1<span block-ref=\\"0\\"/>2</div>\`);
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`name\`);
const ref1 = (el) => refs[\`name\`] = el;
return block1([ref1]);
}
}"
@@ -85,14 +81,13 @@ exports[`t-ref ref in a t-if 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block2 = createBlock(\`<span block-ref=\\"0\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`name\`);
const ref1 = (el) => refs[\`name\`] = el;
let b2;
if (ctx['condition']) {
b2 = block2([ref1]);
@@ -106,7 +101,7 @@ exports[`t-ref refs in a loop 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { prepareList, singleRefSetter, withKey } = helpers;
let { prepareList, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<div block-ref=\\"0\\"><block-text-1/></div>\`);
@@ -120,7 +115,7 @@ exports[`t-ref refs in a loop 1`] = `
const key1 = ctx['item'];
const tKey_1 = ctx['item'];
const v1 = ctx['item'];
let ref1 = singleRefSetter(refs, (v1));
let ref1 = (el) => refs[(v1)] = el;
let txt1 = ctx['item'];
c_block2[i1] = withKey(block3([ref1, txt1]), tKey_1 + key1);
}
@@ -134,15 +129,14 @@ exports[`t-ref two refs, one in a t-if 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter } = helpers;
let block1 = createBlock(\`<div><block-child-0/><p block-ref=\\"0\\"/></div>\`);
let block2 = createBlock(\`<span block-ref=\\"0\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`name\`);
const ref2 = singleRefSetter(refs, \`p\`);
const ref1 = (el) => refs[\`name\`] = el;
const ref2 = (el) => refs[\`p\`] = el;
let b2;
if (ctx['condition']) {
b2 = block2([ref1]);
-3
View File
@@ -284,9 +284,6 @@ describe("t-on", () => {
expect(this).toBe(owner);
expect(val).toBe(444);
},
get this() {
return owner;
},
value: 444,
};
@@ -8,7 +8,7 @@ exports[`basics GrandChild display is controlled by its GrandParent 1`] = `
return function template(ctx, node, key = \\"\\") {
const Comp1 = ctx['myComp'];
return toggler(Comp1, comp1({displayGrandChild: ctx['displayGrandChild']}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({displayGrandChild: ctx['displayGrandChild']}, key + \`__1\`, node, this, Comp1));
}
}"
`;
@@ -1213,45 +1213,6 @@ exports[`delayed fiber does not get rendered if it was cancelled 4`] = `
}"
`;
exports[`delayed render does not go through when t-component value changed 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(null, false, false, false, true);
return function template(ctx, node, key = \\"\\") {
const b2 = text(\`A\`);
const Comp1 = ctx['state'].component;
const b3 = toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return multi([b2, b3]);
}
}"
`;
exports[`delayed render does not go through when t-component value changed 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
const b2 = text(\`B\`);
const b3 = text(ctx['state'].val);
return multi([b2, b3]);
}
}"
`;
exports[`delayed render does not go through when t-component value changed 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`C\`);
}
}"
`;
exports[`delayed rendering, but then initial rendering is cancelled by yet another render 1`] = `
"function anonymous(app, bdom, helpers
) {
@@ -1815,7 +1776,7 @@ exports[`t-foreach with dynamic async component 1`] = `
let b3;
if (ctx['arr']) {
const Comp1 = ctx['myComp'];
b3 = toggler(Comp1, comp1({key: ctx['arr'][0]}, (Comp1).name + key + \`__1__\${key1}\`, node, this, Comp1));
b3 = toggler(Comp1, comp1({key: ctx['arr'][0]}, key + \`__1__\${key1}\`, node, this, Comp1));
}
c_block1[i1] = withKey(multi([b3]), key1);
}
@@ -1849,7 +1810,7 @@ exports[`t-key on dom node having a component 1`] = `
return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['key'];
const Comp1 = ctx['myComp'];
const b2 = toggler(tKey_1, toggler(Comp1, comp1({key: ctx['key']}, (Comp1).name + tKey_1 + key + \`__1\`, node, this, Comp1)));
const b2 = toggler(tKey_1, toggler(Comp1, comp1({key: ctx['key']}, tKey_1 + key + \`__1\`, node, this, Comp1)));
return toggler(tKey_1, block1([], [b2]));
}
}"
@@ -1875,7 +1836,7 @@ exports[`t-key on dynamic async component (toggler is never patched) 1`] = `
return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['key'];
const Comp1 = ctx['myComp'];
return toggler(tKey_1, toggler(Comp1, comp1({key: ctx['key']}, (Comp1).name + tKey_1 + key + \`__1\`, node, this, Comp1)));
return toggler(tKey_1, toggler(Comp1, comp1({key: ctx['key']}, tKey_1 + key + \`__1\`, node, this, Comp1)));
}
}"
`;
@@ -931,7 +931,7 @@ exports[`can catch errors catching error, rethrow, render parent -- a main comp
function slot1(ctx, node, key = \\"\\") {
const Comp1 = ctx['cp'].Comp;
return toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({}, key + \`__1\`, node, this, Comp1));
}
return function template(ctx, node, key = \\"\\") {
@@ -940,10 +940,9 @@ exports[`can catch errors catching error, rethrow, render parent -- a main comp
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`cp\`] = v_block1[i1];
const key1 = ctx['cp'].id;
const v1 = ctx['this'];
const v2 = ctx['cp'];
const v1 = ctx['cp'];
const ctx1 = capture(ctx);
c_block1[i1] = withKey(comp2({onError: ()=>v1.cleanUp(v2.id),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__2__\${key1}\`, node, this, null), key1);
c_block1[i1] = withKey(comp2({onError: ()=>this.cleanUp(v1.id),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__2__\${key1}\`, node, this, null), key1);
}
return list(c_block1);
}
@@ -1010,19 +1009,18 @@ exports[`can catch errors catching in child makes parent render 1`] = `
function slot1(ctx, node, key = \\"\\") {
const Comp1 = ctx['elem'][1];
return toggler(Comp1, comp1({id: ctx['elem'][0]}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({id: ctx['elem'][0]}, key + \`__1\`, node, this, Comp1));
}
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block1, v_block1, l_block1, c_block1] = prepareList(Object.entries(ctx['this'].elements));;
const [k_block1, v_block1, l_block1, c_block1] = prepareList(Object.entries(this.elements));;
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`elem\`] = v_block1[i1];
const key1 = ctx['elem'][0];
const v1 = ctx['this'];
const v2 = ctx['elem'];
const v1 = ctx['elem'];
const ctx1 = capture(ctx);
c_block1[i1] = withKey(comp2({onError: (_error)=>v1.onError(v2[0],_error),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__2__\${key1}\`, node, this, null), key1);
c_block1[i1] = withKey(comp2({onError: (_error)=>this.onError(v1[0],_error),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__2__\${key1}\`, node, this, null), key1);
}
return list(c_block1);
}
@@ -1157,7 +1155,7 @@ exports[`can catch errors onError in class inheritance is called if rethrown 2`]
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (!ctx['state'].error) {
b2 = text(ctx['this'].will.crash);
b2 = text(this.will.crash);
} else {
b3 = text(ctx['state'].error);
}
@@ -1188,7 +1186,7 @@ exports[`can catch errors onError in class inheritance is not called if no rethr
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (!ctx['state'].error) {
b2 = text(ctx['this'].will.crash);
b2 = text(this.will.crash);
} else {
b3 = text(ctx['state'].error);
}
@@ -1220,7 +1218,7 @@ exports[`errors and promises a rendering error in a sub component will reject th
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['this'].will.crash;
let txt1 = this.will.crash;
return block1([txt1]);
}
}"
@@ -1234,7 +1232,7 @@ exports[`errors and promises a rendering error will reject the mount promise 1`]
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['this'].will.crash;
let txt1 = this.will.crash;
return block1([txt1]);
}
}"
@@ -1279,7 +1277,7 @@ exports[`errors and promises a rendering error will reject the render promise 1`
return function template(ctx, node, key = \\"\\") {
let b2;
if (ctx['flag']) {
b2 = text(ctx['this'].will.crash);
b2 = text(this.will.crash);
}
return block1([], [b2]);
}
@@ -4,15 +4,14 @@ exports[`hooks autofocus hook input in a t-if 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter } = helpers;
let block1 = createBlock(\`<div><input block-ref=\\"0\\"/><block-child-0/></div>\`);
let block2 = createBlock(\`<input block-ref=\\"0\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`input1\`);
const ref2 = singleRefSetter(refs, \`input2\`);
const ref1 = (el) => refs[\`input1\`] = el;
const ref2 = (el) => refs[\`input2\`] = el;
let b2;
if (ctx['state'].flag) {
b2 = block2([ref2]);
@@ -26,14 +25,13 @@ exports[`hooks autofocus hook simple input 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter } = helpers;
let block1 = createBlock(\`<div><input block-ref=\\"0\\"/><input block-ref=\\"1\\"/></div>\`);
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`input1\`);
const ref2 = singleRefSetter(refs, \`input2\`);
const ref1 = (el) => refs[\`input1\`] = el;
const ref2 = (el) => refs[\`input2\`] = el;
return block1([ref1, ref2]);
}
}"
@@ -266,13 +264,12 @@ exports[`hooks useEffect hook effect can depend on stuff in dom 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter } = helpers;
let block2 = createBlock(\`<div block-ref=\\"0\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`div\`);
const ref1 = (el) => refs[\`div\`] = el;
let b2;
if (ctx['state'].value) {
b2 = block2([ref1]);
@@ -356,13 +353,12 @@ exports[`hooks useRef hook: basic use 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter } = helpers;
let block1 = createBlock(\`<div><button block-ref=\\"0\\"><block-text-1/></button></div>\`);
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`button\`);
const ref1 = (el) => refs[\`button\`] = el;
let txt1 = ctx['value'];
return block1([ref1, txt1]);
}
@@ -841,33 +841,6 @@ exports[`props validation props are validated whenever component is updated 2`]
}"
`;
exports[`props validation props validation does not cause additional subscription 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, false);
return function template(ctx, node, key = \\"\\") {
const props1 = {obj: ctx['obj']};
helpers.validateProps(\`Child\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
const b3 = text(ctx['obj'].otherValue);
return multi([b2, b3]);
}
}"
`;
exports[`props validation props validation does not cause additional subscription 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['props'].obj.value);
}
}"
`;
exports[`props validation props: list of strings 1`] = `
"function anonymous(app, bdom, helpers
) {
@@ -140,39 +140,3 @@ exports[`reactivity in lifecycle state changes in willUnmount do not trigger rer
}
}"
`;
exports[`subscriptions subscriptions returns the keys and targets observed by the component 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['state'].a);
}
}"
`;
exports[`subscriptions subscriptions returns the keys observed by the component 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, false);
return function template(ctx, node, key = \\"\\") {
const b2 = text(ctx['state'].a);
const b3 = comp1({state: ctx['state']}, key + \`__1\`, node, this, null);
return multi([b2, b3]);
}
}"
`;
exports[`subscriptions subscriptions returns the keys observed by the component 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['props'].state.b);
}
}"
`;
@@ -4,13 +4,12 @@ exports[`refs basic use 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter } = helpers;
let block1 = createBlock(\`<div block-ref=\\"0\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`div\`);
const ref1 = (el) => refs[\`div\`] = el;
return block1([ref1]);
}
}"
@@ -20,7 +19,7 @@ exports[`refs can use 2 refs with same name in a t-if/t-else situation 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter, multiRefSetter } = helpers;
let { multiRefSetter } = helpers;
let block2 = createBlock(\`<div block-ref=\\"0\\"/>\`);
let block3 = createBlock(\`<span block-ref=\\"0\\"/>\`);
@@ -43,14 +42,13 @@ exports[`refs refs and recursive templates 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter } = helpers;
const comp1 = app.createComponent(\`Test\`, true, false, false, false);
let block1 = createBlock(\`<p block-ref=\\"0\\"><block-text-1/><block-child-0/></p>\`);
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`root\`);
const ref1 = (el) => refs[\`root\`] = el;
let b2;
let txt1 = ctx['props'].tree.value;
if (ctx['props'].tree.child) {
@@ -61,33 +59,11 @@ exports[`refs refs and recursive templates 1`] = `
}"
`;
exports[`refs refs and t-key 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter } = helpers;
let block2 = createBlock(\`<button block-handler-0=\\"click\\"/>\`);
let block3 = createBlock(\`<p block-ref=\\"0\\"/>\`);
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`root\`);
const v1 = ctx['state'];
let hdlr1 = [()=>v1.renderId++, ctx];
const b2 = block2([hdlr1]);
const tKey_1 = ctx['state'].renderId;
const b3 = toggler(tKey_1, block3([ref1]));
return multi([b2, b3]);
}
}"
`;
exports[`refs refs are properly bound in slots 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { capture, singleRefSetter, markRaw } = helpers;
let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Dialog\`, true, true, false, true);
let block1 = createBlock(\`<div><span class=\\"counter\\"><block-text-0/></span><block-child-0/></div>\`);
@@ -95,7 +71,7 @@ exports[`refs refs are properly bound in slots 1`] = `
function slot1(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`myButton\`);
const ref1 = (el) => refs[\`myButton\`] = el;
let hdlr1 = [ctx['doSomething'], ctx];
return block2([hdlr1, ref1]);
}
@@ -128,7 +104,7 @@ exports[`refs throws if there are 2 same refs at the same time 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter, multiRefSetter } = helpers;
let { multiRefSetter } = helpers;
let block2 = createBlock(\`<div block-ref=\\"0\\"/>\`);
let block3 = createBlock(\`<span block-ref=\\"0\\"/>\`);
@@ -158,7 +158,7 @@ exports[`slots can render node with t-ref and Component in same slot 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter, markRaw } = helpers;
let { markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, false, false, true);
const comp2 = app.createComponent(\`Child\`, true, true, false, true);
@@ -166,7 +166,7 @@ exports[`slots can render node with t-ref and Component in same slot 1`] = `
function slot1(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`div\`);
const ref1 = (el) => refs[\`div\`] = el;
const b2 = block2([ref1]);
const b3 = comp1({}, key + \`__1\`, node, this, null);
return multi([b2, b3]);
@@ -282,47 +282,6 @@ exports[`slots can use t-call in default-content of t-slot 3`] = `
}"
`;
exports[`slots conditional slot 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, true);
function slot1(ctx, node, key = \\"\\") {
return text(\`blue\`);
}
function slot2(ctx, node, key = \\"\\") {
return text(\`red\`);
}
return function template(ctx, node, key = \\"\\") {
let slots1 = {};
if (ctx['state'].flag) {
const ctx1 = capture(ctx);
slots1['abc'] = {__render: slot1.bind(this), __ctx: ctx1};
} else {
const ctx2 = capture(ctx);
slots1['abc'] = {__render: slot2.bind(this), __ctx: ctx2};
}
return comp1({slots: markRaw(slots1)}, key + \`__1\`, node, this, null);
}
}"
`;
exports[`slots conditional slot 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { callSlot } = helpers;
return function template(ctx, node, key = \\"\\") {
return callSlot(ctx, node, key, 'abc', false, {});
}
}"
`;
exports[`slots content is the default slot (variation) 1`] = `
"function anonymous(app, bdom, helpers
) {
@@ -390,6 +349,42 @@ exports[`slots content is the default slot 2`] = `
}"
`;
exports[`slots default content is available as 0 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { zero, markRaw } = helpers;
const comp1 = app.createComponent(\`Dialog\`, true, true, false, true);
function slot1(ctx, node, key = \\"\\") {
const b2 = text(\`Before - \`);
const b3 = ctx[zero];
const b4 = text(\` - After\`);
return multi([b2, b3, b4]);
}
return function template(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null);
}
}"
`;
exports[`slots default content is available as 0 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { callSlot } = helpers;
function defaultContent1(ctx, node, key = \\"\\") {
return text(\`default content\`);
}
return function template(ctx, node, key = \\"\\") {
return callSlot(ctx, node, key, 'default', false, {}, defaultContent1.bind(this));
}
}"
`;
exports[`slots default content is not rendered if named slot is provided 1`] = `
"function anonymous(app, bdom, helpers
) {
@@ -1476,8 +1471,7 @@ exports[`slots simple default slot 1`] = `
}
return function template(ctx, node, key = \\"\\") {
let slots1 = markRaw({'default': {__render: slot1.bind(this), __ctx: ctx}});
return comp1({slots: slots1}, key + \`__1\`, node, this, null);
return comp1({slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null);
}
}"
`;
@@ -1576,8 +1570,7 @@ exports[`slots simple default slot, variation 1`] = `
}
return function template(ctx, node, key = \\"\\") {
let slots1 = markRaw({'default': {__render: slot1.bind(this), __ctx: ctx}});
return comp1({slots: slots1}, key + \`__1\`, node, this, null);
return comp1({slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null);
}
}"
`;
@@ -2035,8 +2028,7 @@ exports[`slots slot content is bound to caller (variation) 1`] = `
ctx = Object.create(ctx);
ctx[isBoundary] = 1
setContextValue(ctx, \\"var\\", 1);
const v1 = ctx['this'];
let hdlr1 = [()=>v1.inc(), ctx];
let hdlr1 = [()=>this.inc(), ctx];
return block1([hdlr1]);
}
@@ -2403,8 +2395,7 @@ exports[`slots slots are properly bound to correct component 2`] = `
ctx = Object.create(ctx);
ctx[isBoundary] = 1
setContextValue(ctx, \\"var\\", 1);
const v1 = ctx['this'];
let hdlr1 = [()=>v1.increment(), ctx];
let hdlr1 = [()=>this.increment(), ctx];
let txt1 = ctx['state'].value;
return block1([hdlr1, txt1]);
}
@@ -2884,23 +2875,16 @@ exports[`slots t-set-slot=default has priority over rest of the content 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { markRaw, capture } = helpers;
let { capture, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, true);
function slot1(ctx, node, key = \\"\\") {
const b2 = text(\` some text \`);
const ctx2 = capture(ctx);
return multi([b2]);
}
function slot2(ctx, node, key = \\"\\") {
return text(\`some other text\`);
}
return function template(ctx, node, key = \\"\\") {
const ctx1 = capture(ctx);
let slots1 = markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}});
return comp1({slots: slots1}, key + \`__1\`, node, this, null);
return comp1({slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
}
}"
`;
@@ -363,7 +363,7 @@ exports[`style and class handling error in subcomponent with class 2`] = `
return function template(ctx, node, key = \\"\\") {
let attr1 = ctx['props'].class;
let txt1 = ctx['this'].will.crash;
let txt1 = this.will.crash;
return block1([attr1, txt1]);
}
}"
@@ -109,8 +109,7 @@ exports[`t-call handlers are properly bound through a dynamic t-call 2`] = `
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['this'];
let hdlr1 = [()=>v1.update(), ctx];
let hdlr1 = [()=>this.update(), ctx];
return block1([hdlr1]);
}
}"
@@ -169,9 +168,8 @@ exports[`t-call handlers with arguments are properly bound through a t-call 2`]
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['this'];
const v2 = ctx['a'];
let hdlr1 = [()=>v1.update(v2), ctx];
const v1 = ctx['a'];
let hdlr1 = [()=>this.update(v1), ctx];
return block1([hdlr1]);
}
}"
@@ -289,7 +287,7 @@ exports[`t-call recursive t-call binding this -- static t-call 2`] = `
ctx[isBoundary] = 1
let b2;
if (ctx['level']<2) {
let hdlr1 = [\\"stop\\", ctx['onClicked'].bind(ctx['this']), ctx];
let hdlr1 = [\\"stop\\", ctx['onClicked'].bind(this), ctx];
let txt1 = ctx['level'];
const b3 = block3([hdlr1, txt1]);
ctx = Object.create(ctx);
@@ -534,32 +532,35 @@ exports[`t-call t-call-context: ComponentNode is not looked up in the context 2`
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { singleRefSetter, bind, capture, isBoundary, withDefault, setContextValue, markRaw } = helpers;
let { bind, capture, isBoundary, withDefault, setContextValue, markRaw } = helpers;
const comp1 = app.createComponent(\`Child\`, true, true, false, false);
let block2 = createBlock(\`<div block-ref=\\"0\\">outside slot</div>\`);
let block4 = createBlock(\`<div block-ref=\\"0\\">I'm the default slot</div>\`);
let block5 = createBlock(\`<div><block-text-0/></div>\`);
let block6 = createBlock(\`<div><block-text-0/></div>\`);
function slot1(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref2 = singleRefSetter(refs, \`myRef2\`);
const ref2 = (el) => refs[\`myRef2\`] = el;
ctx = Object.create(ctx);
ctx[isBoundary] = 1
const b4 = block4([ref2]);
setContextValue(ctx, \\"test\\", 3);
let txt1 = ctx['test'];
let txt1 = this.__owl__.name;
const b5 = block5([txt1]);
return multi([b4, b5]);
let txt2 = ctx['test'];
const b6 = block6([txt2]);
return multi([b4, b5, b6]);
}
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = singleRefSetter(refs, \`myRef\`);
const ref1 = (el) => refs[\`myRef\`] = el;
const b2 = block2([ref1]);
const ctx1 = capture(ctx);
const b6 = comp1({prop: bind(this, ctx['method']),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return multi([b2, b6]);
const b7 = comp1({prop: bind(this, ctx['method']),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return multi([b2, b7]);
}
}"
`;
@@ -623,27 +624,3 @@ exports[`t-call t-call-context: slots don't make component available again when
}
}"
`;
exports[`t-call t-call-context: this is not available inside t-call-context 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const callTemplate_1 = app.getTemplate(\`someTemplate\`);
return function template(ctx, node, key = \\"\\") {
let ctx1 = {};
return callTemplate_1.call(this, ctx1, node, key + \`__1\`);
}
}"
`;
exports[`t-call t-call-context: this is not available inside t-call-context 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['this']);
}
}"
`;
@@ -10,7 +10,7 @@ exports[`t-component can switch between dynamic components without the need for
return function template(ctx, node, key = \\"\\") {
const Comp1 = ctx['constructor'].components[ctx['state'].child];
const b2 = toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
const b2 = toggler(Comp1, comp1({}, key + \`__1\`, node, this, Comp1));
return block1([], [b2]);
}
}"
@@ -51,7 +51,7 @@ exports[`t-component can use dynamic components (the class) if given (with diffe
return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['state'].child;
const Comp1 = ctx['myComponent'];
return toggler(tKey_1, toggler(Comp1, comp1({}, (Comp1).name + tKey_1 + key + \`__1\`, node, this, Comp1)));
return toggler(tKey_1, toggler(Comp1, comp1({}, tKey_1 + key + \`__1\`, node, this, Comp1)));
}
}"
`;
@@ -91,7 +91,7 @@ exports[`t-component can use dynamic components (the class) if given 1`] = `
return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['state'].child;
const Comp1 = ctx['myComponent'];
return toggler(tKey_1, toggler(Comp1, comp1({}, (Comp1).name + tKey_1 + key + \`__1\`, node, this, Comp1)));
return toggler(tKey_1, toggler(Comp1, comp1({}, tKey_1 + key + \`__1\`, node, this, Comp1)));
}
}"
`;
@@ -132,7 +132,7 @@ exports[`t-component modifying a sub widget 1`] = `
return function template(ctx, node, key = \\"\\") {
const Comp1 = ctx['Counter'];
const b2 = toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
const b2 = toggler(Comp1, comp1({}, key + \`__1\`, node, this, Comp1));
return block1([], [b2]);
}
}"
@@ -162,7 +162,7 @@ exports[`t-component switching dynamic component 1`] = `
return function template(ctx, node, key = \\"\\") {
const Comp1 = ctx['Child'];
return toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({}, key + \`__1\`, node, this, Comp1));
}
}"
`;
@@ -199,7 +199,7 @@ exports[`t-component t-component works in simple case 1`] = `
return function template(ctx, node, key = \\"\\") {
const Comp1 = ctx['Child'];
return toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({}, key + \`__1\`, node, this, Comp1));
}
}"
`;
@@ -569,36 +569,6 @@ exports[`t-model directive t-model with dynamic values on select options in fore
}"
`;
exports[`t-model directive t-model with radio button group in t-foreach 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { prepareList, toNumber, withKey } = helpers;
let block1 = createBlock(\`<div id=\\"get_data\\" block-handler-0=\\"click\\"><block-child-0/></div>\`);
let block3 = createBlock(\`<input type=\\"radio\\" name=\\"radio_group\\" block-attribute-0=\\"value\\" block-attribute-1=\\"id\\" block-attribute-2=\\"checked\\" block-handler-3=\\"click\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['getData'], ctx];
ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['options']);;
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`opt\`] = v_block2[i1];
const key1 = ctx['opt'];
let attr1 = new String((ctx['opt']) || \\"\\");
let attr2 = ctx['opt'];
const bExpr1 = ctx['state'];
const expr1 = 'group';
let attr3 = bExpr1[expr1] === ctx['opt'];
let hdlr2 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
c_block2[i1] = withKey(block3([attr1, attr2, attr3, hdlr2]), key1);
}
const b2 = list(c_block2);
return block1([hdlr1], [b2]);
}
}"
`;
exports[`t-model directive two inputs in a div alternating with a t-if 1`] = `
"function anonymous(app, bdom, helpers
) {
@@ -111,9 +111,8 @@ exports[`t-on t-on method call in t-foreach 1`] = `
const key1 = ctx['val'];
let txt1 = ctx['val_index'];
let txt2 = ctx['val']+'';
const v1 = ctx['this'];
const v2 = ctx['val'];
let hdlr1 = [()=>v1.addVal(v2), ctx];
const v1 = ctx['val'];
let hdlr1 = [()=>this.addVal(v1), ctx];
c_block2[i1] = withKey(block3([txt1, txt2, hdlr1]), key1);
}
const b2 = list(c_block2);
@@ -198,9 +197,8 @@ exports[`t-on t-on on components and t-foreach 1`] = `
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`name\`] = v_block1[i1];
const key1 = ctx['name'];
const v1 = ctx['this'];
const v2 = ctx['name'];
const hdlr1 = [()=>v1.log(v2), ctx];
const v1 = ctx['name'];
const hdlr1 = [()=>this.log(v1), ctx];
c_block1[i1] = withKey(catcher1(comp1({value: ctx['name']}, key + \`__1__\${key1}\`, node, this, null), [hdlr1]), key1);
}
return list(c_block1);
@@ -295,9 +293,8 @@ exports[`t-on t-on on components, with a handler update 1`] = `
ctx = Object.create(ctx);
ctx[isBoundary] = 1
setContextValue(ctx, \\"name\\", ctx['state'].name);
const v1 = ctx['this'];
const v2 = ctx['name'];
const hdlr1 = [()=>v1.log(v2), ctx];
const v1 = ctx['name'];
const hdlr1 = [()=>this.log(v1), ctx];
return catcher1(comp1({value: ctx['name']}, key + \`__1\`, node, this, null), [hdlr1]);
}
}"
@@ -396,8 +393,7 @@ exports[`t-on t-on on t-set-slots 1`] = `
function slot1(ctx, node, key = \\"\\") {
const b6 = block6();
const b7 = block7();
const v1 = ctx['this'];
const hdlr1 = [()=>v1.state.count++, ctx];
const hdlr1 = [()=>this.state.count++, ctx];
return catcher1(multi([b6, b7]), [hdlr1]);
}
@@ -454,8 +450,7 @@ exports[`t-on t-on on t-slots 2`] = `
const b2 = text(\` [\`);
const b3 = text(ctx['state'].count);
const b4 = text(\`] \`);
const v1 = ctx['this'];
const hdlr1 = [()=>v1.state.count++, ctx];
const hdlr1 = [()=>this.state.count++, ctx];
const b5 = catcher1(callSlot(ctx, node, key, 'default', false, {}), [hdlr1]);
return multi([b2, b3, b4, b5]);
}
-59
View File
@@ -1,7 +1,6 @@
import {
App,
Component,
ComponentConstructor,
mount,
onMounted,
onRendered,
@@ -4068,64 +4067,6 @@ test("renderings, destruction, patch, stuff, ... yet another variation", async (
expect(fixture.innerHTML).toBe("ABD<p>2</p>");
});
test("delayed render does not go through when t-component value changed", async () => {
class C extends Component {
static template = xml`C`;
setup() {
useLogLifecycle("", true);
}
}
class B extends Component {
static template = xml`B<t t-esc="state.val"/>`;
state = useState({ val: 1 });
setup() {
useLogLifecycle("", true);
b = this;
}
}
let b: B;
class A extends Component {
static template = xml`A<t t-component="state.component"/>`;
state: { component: ComponentConstructor } = useState({ component: B });
setup() {
useLogLifecycle("", true);
}
}
const a = await mount(A, fixture);
expect(fixture.innerHTML).toBe("AB1");
expect([
"A:setup",
"A:willRender",
"B:setup",
"A:rendered",
"B:willRender",
"B:rendered",
"B:mounted",
"A:mounted",
]).toBeLogged();
// start a render in B
b!.state.val = 2;
// start a render in A, invalidating the scheduled render of B, which could crash if executed.
a.state.component = C;
await nextTick();
expect(fixture.innerHTML).toBe("AC");
expect([
"A:willRender",
"C:setup",
"A:rendered",
"C:willRender",
"C:rendered",
"A:willPatch",
"B:willUnmount",
"B:willDestroy",
"C:mounted",
"A:patched",
]).toBeLogged();
});
// test.skip("components with shouldUpdate=false", async () => {
// const state = { p: 1, cc: 10 };
+1 -24
View File
@@ -1,5 +1,5 @@
import { makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
import { Component, onError, xml, mount, OwlError, useState } from "../../src";
import { Component, onError, xml, mount, OwlError } from "../../src";
import { App, DEV_MSG } from "../../src/runtime/app";
import { validateProps } from "../../src/runtime/template_helpers";
import { Schema } from "../../src/runtime/validation";
@@ -682,29 +682,6 @@ describe("props validation", () => {
expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is missing");
});
test("props validation does not cause additional subscription", async () => {
let obj = {
value: 1,
otherValue: 2,
};
class Child extends Component {
static props = {
obj: { type: Object, shape: { value: Number, otherValue: Number } },
};
static template = xml`<t t-esc="props.obj.value"/>`;
}
class Parent extends Component {
static template = xml`<Child obj="obj"/><t t-esc="obj.otherValue"/>`;
static components = { Child };
obj = useState(obj);
}
const app = new App(Parent, { test: true });
await app.mount(fixture);
expect(fixture.innerHTML).toBe("12");
expect(app.root!.subscriptions).toEqual([{ keys: ["otherValue"], target: obj }]);
});
test("props are validated whenever component is updated", async () => {
let error: Error;
class SubComp extends Component {
-32
View File
@@ -7,7 +7,6 @@ import {
onWillUnmount,
useState,
xml,
toRaw,
} from "../../src";
import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
@@ -233,34 +232,3 @@ describe("reactivity in lifecycle", () => {
expect(fixture.innerHTML).toBe("34");
});
});
describe("subscriptions", () => {
test("subscriptions returns the keys and targets observed by the component", async () => {
class Comp extends Component {
static template = xml`<t t-esc="state.a"/>`;
state = useState({ a: 1, b: 2 });
}
const comp = await mount(Comp, fixture);
expect(fixture.innerHTML).toBe("1");
expect(comp.__owl__.subscriptions).toEqual([{ keys: ["a"], target: toRaw(comp.state) }]);
});
test("subscriptions returns the keys observed by the component", async () => {
class Child extends Component {
static template = xml`<t t-esc="props.state.b"/>`;
setup() {
child = this;
}
}
let child: Child;
class Parent extends Component {
static template = xml`<t t-esc="state.a"/><Child state="state"/>`;
static components = { Child };
state = useState({ a: 1, b: 2 });
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("12");
expect(parent.__owl__.subscriptions).toEqual([{ keys: ["a"], target: toRaw(parent.state) }]);
expect(child!.__owl__.subscriptions).toEqual([{ keys: ["b"], target: toRaw(parent.state) }]);
});
});
+2 -39
View File
@@ -1,14 +1,6 @@
import {
App,
Component,
mount,
onMounted,
onPatched,
useRef,
useState,
xml,
} from "../../src/index";
import { App, Component, mount, onMounted, useRef, useState } from "../../src/index";
import { logStep, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
import { xml } from "../../src/index";
snapshotEverything();
let fixture: HTMLElement;
@@ -136,33 +128,4 @@ describe("refs", () => {
expect(fixture.innerHTML).toBe("<p>a<p>b</p></p>");
expect(["<p>b</p>", "<p>a<p>b</p></p>"]).toBeLogged();
});
test("refs and t-key", async () => {
let el;
class Test extends Component {
static components = {};
static template = xml`
<button t-on-click="() => state.renderId++" />
<p t-ref="root" t-key="state.renderId"/>`;
root = useRef("root");
state = useState({ renderId: 1 });
setup() {
onMounted(() => {
el = this.root.el;
});
onPatched(() => {
el = this.root.el;
});
}
}
await mount(Test, fixture);
expect(el).toBe(fixture.querySelector("p"));
const _el = el;
fixture.querySelector("button")!.click();
await nextTick();
expect(el).not.toBe(_el);
expect(el).toBe(fixture.querySelector("p"));
});
});
+76 -92
View File
@@ -62,7 +62,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("some other text");
});
test.skip("simple slot with slot scope", async () => {
test("simple slot with slot scope", async () => {
let child: any;
class Child extends Component {
static template = xml`<span><t t-slot="slotName" bool="state.bool"/></span>`;
@@ -91,7 +91,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<span>other text</span>");
});
test.skip("slot with slot scope and t-props", async () => {
test("slot with slot scope and t-props", async () => {
class Child extends Component {
static template = xml`
<t t-slot="slotName" t-props="info"/>`;
@@ -113,7 +113,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<p>1</p><p>2</p>");
});
test.skip("simple dynamic slot with slot scope", async () => {
test("simple dynamic slot with slot scope", async () => {
let child: any;
class Child extends Component {
static template = xml`<span><t t-slot="{{ 'slotName' }}" bool="state.bool"/></span>`;
@@ -142,7 +142,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<span>other text</span>");
});
test.skip("simple named and empty slot", async () => {
test("simple named and empty slot", async () => {
class Child extends Component {
static template = xml`<span><t t-slot="default" /><t t-slot="myEmptySlot"/></span>`;
@@ -160,7 +160,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<span>some text</span>");
});
test.skip("simple named and empty slot -- 2", async () => {
test("simple named and empty slot -- 2", async () => {
class Child extends Component {
static template = xml`<span><t t-slot="myEmptySlot">default empty</t></span>`;
@@ -179,7 +179,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<span>default empty</span>");
});
test.skip("default slot with slot scope: shorthand syntax", async () => {
test("default slot with slot scope: shorthand syntax", async () => {
let child: any;
class Child extends Component {
static template = xml`<span><t t-slot="default" bool="state.bool"/></span>`;
@@ -206,7 +206,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<span>other text</span>");
});
test.skip("simple default slot with params", async () => {
test("simple default slot with params", async () => {
class Child extends Component {
static template = xml`<span><t t-slot="default" bool="state.bool"/></span>`;
state = useState({ bool: true });
@@ -230,7 +230,7 @@ describe("slots", () => {
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test.skip("simple default slot with params and bound function", async () => {
test("simple default slot with params and bound function", async () => {
class Child extends Component {
static template = xml`<t t-slot="default" fn.bind="getValue"/>`;
state = useState({ value: 123 });
@@ -249,7 +249,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("123");
});
test.skip("default slot with params with - in it", async () => {
test("default slot with params with - in it", async () => {
class Child extends Component {
static template = xml`<t t-slot="default" some-value="state.value"/>`;
state = useState({ value: 123 });
@@ -265,7 +265,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("123");
});
test.skip("fun: two calls to the same slot", async () => {
test("fun: two calls to the same slot", async () => {
class Child extends Component {
static template = xml`<t t-slot="default"/><t t-slot="default"/>`;
}
@@ -279,7 +279,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("some textsome text");
});
test.skip("slot content is bound to caller", async () => {
test("slot content is bound to caller", async () => {
class Child extends Component {
static template = xml`<span><t t-slot="default"/></span>`;
}
@@ -299,7 +299,7 @@ describe("slots", () => {
expect(parent.state.value).toBe(1);
});
test.skip("slot content is bound to caller (variation)", async () => {
test("slot content is bound to caller (variation)", async () => {
class Child extends Component {
static template = xml`<span><t t-slot="default"/></span>`;
}
@@ -326,7 +326,7 @@ describe("slots", () => {
expect(parent.state.value).toBe(1);
});
test.skip("can define and call slots", async () => {
test("can define and call slots", async () => {
class Dialog extends Component {
static template = xml`
<div>
@@ -352,7 +352,7 @@ describe("slots", () => {
);
});
test.skip("can define and call slots with params", async () => {
test("can define and call slots with params", async () => {
class Dialog extends Component {
static template = xml`
<div>
@@ -381,7 +381,7 @@ describe("slots", () => {
);
});
test.skip("can define and call slots with bound params", async () => {
test("can define and call slots with bound params", async () => {
class Child extends Component {
static template = xml`
<t t-slot="abc"/>
@@ -405,7 +405,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("abc444");
});
test.skip("no named slot content => just no children", async () => {
test("no named slot content => just no children", async () => {
class Dialog extends Component {
static template = xml`<span><t t-slot="header"/></span>`;
}
@@ -418,7 +418,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<span></span>");
});
test.skip("named slots can define a default content", async () => {
test("named slots can define a default content", async () => {
class Dialog extends Component {
static template = xml`
<span>
@@ -434,7 +434,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span>default content</span></div>");
});
test.skip("can define a default content", async () => {
test("can define a default content", async () => {
class Dialog extends Component {
static template = xml`
<span>
@@ -450,7 +450,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span>default content</span></div>");
});
test.skip("default content is not rendered if slot is provided", async () => {
test("default content is not rendered if slot is provided", async () => {
class Dialog extends Component {
static template = xml`
<span>
@@ -466,7 +466,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span>hey</span></div>");
});
test.skip("default content is not rendered if named slot is provided", async () => {
test("default content is not rendered if named slot is provided", async () => {
class Dialog extends Component {
static template = xml`
<span>
@@ -482,7 +482,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span>hey</span></div>");
});
test.skip("slots are properly bound to correct component", async () => {
test("slots are properly bound to correct component", async () => {
let child: any = null;
class Child extends Component {
// t-set t-value in template is to force compiler to protect the scope
@@ -518,7 +518,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<button>2</button>");
});
test.skip("slots are rendered with proper context", async () => {
test("slots are rendered with proper context", async () => {
class Dialog extends Component {
static template = xml`<span><t t-slot="footer"/></span>`;
}
@@ -554,7 +554,7 @@ describe("slots", () => {
);
});
test.skip("slots are rendered with proper context, part 2", async () => {
test("slots are rendered with proper context, part 2", async () => {
class Link extends Component {
static template = xml`
<a t-att-href="props.to">
@@ -593,7 +593,7 @@ describe("slots", () => {
);
});
test.skip("slots are rendered with proper context, part 3", async () => {
test("slots are rendered with proper context, part 3", async () => {
class Link extends Component {
static template = xml`
<a t-att-href="props.to">
@@ -632,7 +632,7 @@ describe("slots", () => {
);
});
test.skip("slots are rendered with proper context, part 4", async () => {
test("slots are rendered with proper context, part 4", async () => {
class Link extends Component {
static template = xml`
<a t-att-href="props.to">
@@ -660,7 +660,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe('<div><a href="/user/1">User David</a></div>');
});
test.skip("content is the default slot", async () => {
test("content is the default slot", async () => {
class Dialog extends Component {
static template = xml`<div><t t-slot="default"/></div>`;
}
@@ -678,7 +678,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><div><span>sts rocks</span></div></div>");
});
test.skip("content is the default slot (variation)", async () => {
test("content is the default slot (variation)", async () => {
class Dialog extends Component {
static template = xml`<t t-slot="default"/>`;
}
@@ -694,7 +694,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<span>sts rocks</span>");
});
test.skip("default slot work with text nodes", async () => {
test("default slot work with text nodes", async () => {
class Dialog extends Component {
static template = xml`<div><t t-slot="default"/></div>`;
}
@@ -710,7 +710,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><div>sts rocks</div></div>");
});
test.skip("default slot work with text nodes (variation)", async () => {
test("default slot work with text nodes (variation)", async () => {
class Dialog extends Component {
static template = xml`<t t-slot="default"/>`;
}
@@ -723,7 +723,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("sts rocks");
});
test.skip("multiple roots are allowed in a named slot", async () => {
test("multiple roots are allowed in a named slot", async () => {
class Dialog extends Component {
static template = xml`<div><t t-slot="content"/></div>`;
}
@@ -744,7 +744,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><div><span>sts</span><span>rocks</span></div></div>");
});
test.skip("multiple roots are allowed in a default slot", async () => {
test("multiple roots are allowed in a default slot", async () => {
class Dialog extends Component {
static template = xml`<div><t t-slot="default"/></div>`;
}
@@ -763,7 +763,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><div><span>sts</span><span>rocks</span></div></div>");
});
test.skip("missing slots are ignored", async () => {
test("missing slots are ignored", async () => {
class Dialog extends Component {
static template = xml`
<span>
@@ -782,7 +782,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span><span>some content</span></span></div>");
});
test.skip("t-debug on a t-set-slot (defining a slot)", async () => {
test("t-debug on a t-set-slot (defining a slot)", async () => {
const consoleLog = console.log;
console.log = jest.fn();
@@ -802,7 +802,7 @@ describe("slots", () => {
console.log = consoleLog;
});
test.skip("slot preserves properly parented relationship", async () => {
test("slot preserves properly parented relationship", async () => {
class Child extends Component {
static template = xml`<t t-slot="default"/>`;
}
@@ -832,7 +832,7 @@ describe("slots", () => {
expect(childrenChildren[0]).toBeInstanceOf(GrandChild);
});
test.skip("slot preserves properly parented relationship, even through t-call", async () => {
test("slot preserves properly parented relationship, even through t-call", async () => {
class Child extends Component {
static template = xml`<t t-slot="default"/>`;
}
@@ -870,7 +870,7 @@ describe("slots", () => {
expect(childrenChildren[0]).toBeInstanceOf(GrandChild);
});
test.skip("t-slot scope context", async () => {
test("t-slot scope context", async () => {
expect.assertions(4);
class Wrapper extends Component {
@@ -916,7 +916,7 @@ describe("slots", () => {
await nextTick();
});
test.skip("t-slot in recursive templates", async () => {
test("t-slot in recursive templates", async () => {
class Wrapper extends Component {
static template = xml`
<wrapper>
@@ -979,7 +979,7 @@ describe("slots", () => {
);
});
test.skip("t-slot within dynamic t-call", async () => {
test("t-slot within dynamic t-call", async () => {
class Child extends Component {
static template = xml`<div class="child"/>`;
}
@@ -1021,7 +1021,7 @@ describe("slots", () => {
);
});
test.skip("slots in t-foreach in t-foreach", async () => {
test("slots in t-foreach in t-foreach", async () => {
class Child extends Component {
static template = xml`
<div><t t-slot="default" /></div>
@@ -1084,7 +1084,7 @@ describe("slots", () => {
);
});
test.skip("default slot next to named slot, with default content", async () => {
test("default slot next to named slot, with default content", async () => {
class Dialog extends Component {
// We're using 2 slots here: a "default" one and a "footer",
// both having default children nodes.
@@ -1125,7 +1125,7 @@ describe("slots", () => {
);
});
test.skip("dynamic t-slot call", async () => {
test("dynamic t-slot call", async () => {
class Toggler extends Component {
static template = xml`<button t-on-click="toggle"><t t-slot="{{current.slot}}"/></button>`;
current = useState({ slot: "slot1" });
@@ -1156,7 +1156,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><button><p>slot1</p><span>content</span></button></div>");
});
test.skip("dynamic t-slot call with default", async () => {
test("dynamic t-slot call with default", async () => {
class Toggler extends Component {
static template = xml`
<button t-on-click="toggle">
@@ -1188,7 +1188,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><button><h1>slot2</h1></button></div>");
});
test.skip("slot are properly rendered if inner props are changed", async () => {
test("slot are properly rendered if inner props are changed", async () => {
class SomeComponent extends Component {
static template = xml`
<div>
@@ -1222,7 +1222,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><button>Inc[5]</button><div><div> SC:5</div></div></div>");
});
test.skip("slots and wrapper components", async () => {
test("slots and wrapper components", async () => {
class Link extends Component {
static template = xml`
<a href="abc">
@@ -1240,7 +1240,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe(`<a href="abc">hey</a>`);
});
test.skip("template can just return a slot", async () => {
test("template can just return a slot", async () => {
class Child extends Component {
static template = xml`<span><t t-esc="props.value"/></span>`;
}
@@ -1265,7 +1265,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span>5</span></div>");
});
test.skip("multiple slots containing components", async () => {
test("multiple slots containing components", async () => {
class C extends Component {
static template = xml`<span><t t-esc="props.val"/></span>`;
}
@@ -1286,7 +1286,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe(`<div><span>1</span><span>2</span></div>`);
});
test.skip("slots in t-foreach and re-rendering", async () => {
test("slots in t-foreach and re-rendering", async () => {
class Child extends Component {
static template = xml`<span><t t-esc="state.val"/><t t-slot="default"/></span>`;
state = useState({ val: "A" });
@@ -1312,7 +1312,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span>B0</span><span>B1</span></div>");
});
test.skip("slots in t-foreach with t-set and re-rendering", async () => {
test("slots in t-foreach with t-set and re-rendering", async () => {
class Child extends Component {
static template = xml`
<span>
@@ -1344,7 +1344,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span>B0</span><span>B1</span></div>");
});
test.skip("nested slots in same template", async () => {
test("nested slots in same template", async () => {
let child: any = null;
let child2: any = null;
let child3: any = null;
@@ -1398,7 +1398,7 @@ describe("slots", () => {
expect(children(parent)).toEqual([child]);
});
test.skip("t-slot nested within another slot", async () => {
test("t-slot nested within another slot", async () => {
let portal: any = null;
let modal: any = null;
let child3: any = null;
@@ -1451,7 +1451,7 @@ describe("slots", () => {
expect(children(modal)[0]).toBe(portal);
});
test.skip("slots in slots, with vars", async () => {
test("slots in slots, with vars", async () => {
class B extends Component {
static template = xml`<span><t t-slot="default"/></span>`;
}
@@ -1480,7 +1480,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><div><span><p>heyaaron</p></span></div></div>");
});
test.skip("t-set t-value in a slot", async () => {
test("t-set t-value in a slot", async () => {
class Dialog extends Component {
static template = xml`
<span>
@@ -1502,7 +1502,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span>dash</span></div>");
});
test.skip("slot and t-esc", async () => {
test("slot and t-esc", async () => {
class Dialog extends Component {
static template = xml`<span><t t-slot="default"/></span>`;
}
@@ -1515,7 +1515,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span>toph</span></div>");
});
test.skip("slot and t-call", async () => {
test("slot and t-call", async () => {
let sokka = xml`<p>sokka</p>`;
class Dialog extends Component {
static template = xml`<span><t t-slot="default"/></span>`;
@@ -1529,7 +1529,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span><p>sokka</p></span></div>");
});
test.skip("slot and (inline) t-call", async () => {
test("slot and (inline) t-call", async () => {
let sokka = xml`<p>sokka</p>`;
class Dialog extends Component {
static template = xml`<span><t t-slot="default"/></span>`;
@@ -1543,7 +1543,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><span><p>sokka</p></span></div>");
});
test.skip("nested slots: evaluation context and parented relationship", async () => {
test("nested slots: evaluation context and parented relationship", async () => {
let slot: any = null;
let grandChild: any = null;
@@ -1578,7 +1578,7 @@ describe("slots", () => {
expect(children(grandChild)).toEqual([slot]);
});
test.skip("named slot inside slot", async () => {
test("named slot inside slot", async () => {
class Child extends Component {
static template = xml`
<div>
@@ -1608,7 +1608,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><div><p>Ablip</p><div><p>Bblip</p></div></div></div>");
});
test.skip("named slots inside slot, again", async () => {
test("named slots inside slot, again", async () => {
class Child extends Component {
static template = xml`
<child>
@@ -1641,7 +1641,7 @@ describe("slots", () => {
);
});
test.skip("named slot inside slot, part 3", async () => {
test("named slot inside slot, part 3", async () => {
class Child extends Component {
static template = xml`
<div>
@@ -1673,7 +1673,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div><div><p>Ablip</p><div><p>Bblip</p></div></div></div>");
});
test.skip("can render only empty slot", async () => {
test("can render only empty slot", async () => {
class Parent extends Component {
static template = xml`<t t-slot="default"/>`;
}
@@ -1688,7 +1688,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toEqual("");
});
test.skip("can render node with t-ref and Component in same slot", async () => {
test("can render node with t-ref and Component in same slot", async () => {
class Child extends Component {
static template = xml`<t t-slot="default"/>`;
}
@@ -1706,7 +1706,7 @@ describe("slots", () => {
expect(error).toBeNull();
});
test.skip("can use t-call in default-content of t-slot", async () => {
test("can use t-call in default-content of t-slot", async () => {
const template = xml``;
class Child extends Component {
static template = xml`<t t-slot="default"><t t-call="${template}"/></t>`;
@@ -1719,7 +1719,7 @@ describe("slots", () => {
await mount(Parent, fixture);
});
test.skip("can use component in default-content of t-slot", async () => {
test("can use component in default-content of t-slot", async () => {
class GrandChild extends Component {
static template = xml``;
}
@@ -1735,7 +1735,7 @@ describe("slots", () => {
await mount(Parent, fixture);
});
test.skip("slot content has different key from other content -- static slot", async () => {
test("slot content has different key from other content -- static slot", async () => {
class Child extends Component {
static template = xml`<div t-esc="props.parent" />`;
}
@@ -1757,7 +1757,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div>SlotDisplay</div><div>Parent</div>");
});
test.skip("slot content has different key from other content -- dynamic slot", async () => {
test("slot content has different key from other content -- dynamic slot", async () => {
class Child extends Component {
static template = xml`<div t-esc="props.parent" />`;
}
@@ -1780,7 +1780,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<div>SlotDisplay</div><div>Parent</div>");
});
test.skip("mix of slots, t-call, t-call with body, and giving own props child", async () => {
test("mix of slots, t-call, t-call with body, and giving own props child", async () => {
expect.assertions(11);
class C extends Component {
@@ -1836,7 +1836,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("<button>inc</button>[B][C][A][sub1] [sub2334]");
});
test.skip("slot in multiple locations", async () => {
test("slot in multiple locations", async () => {
class Child extends Component {
static template = xml`<div>child</div>`;
}
@@ -1869,7 +1869,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe(" hello <div>child</div>");
});
test.skip("dynamic slot in multiple locations", async () => {
test("dynamic slot in multiple locations", async () => {
class Child extends Component {
static template = xml`<div>child</div>`;
}
@@ -1902,7 +1902,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("hello <div>child</div>");
});
test.skip("slot in t-foreach locations", async () => {
test("slot in t-foreach locations", async () => {
class Child extends Component {
static template = xml`<div>child</div>`;
}
@@ -1934,32 +1934,16 @@ describe("slots", () => {
);
});
test.skip("conditional slot", async () => {
class Child extends Component {
static template = xml`<t t-slot="abc"/>`;
test("default content is available as 0", async () => {
class Dialog extends Component {
static template = xml`<t t-slot="default">default content</t>`;
}
class Parent extends Component {
static template = xml`
<Child>
<t t-if="state.flag">
<t t-set-slot="abc">blue</t>
</t>
<t t-else="">
<t t-set-slot="abc">red</t>
</t>
</Child>`;
static components = { Child };
state = useState({ flag: true});
static template = xml`<Dialog>Before - <t t-out="0"/> - After</Dialog>`;
static components = { Dialog };
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("blue");
parent.state.flag = false;
await nextTick();
expect(fixture.innerHTML).toBe("red");
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("Before - default content - After");
});
});
+2 -17
View File
@@ -345,13 +345,14 @@ describe("t-call", () => {
<Child prop.bind="method">
<div t-ref="myRef2">I'm the default slot</div>
<t t-set="test" t-value="3"/>
<div t-esc="this.__owl__.name"/>
<div t-esc="test"/>
</Child>
</t>
</templates>`,
});
expect(fixture.innerHTML).toBe(
"<div>outside slot</div><div>I'm the default slot</div><div>3</div>"
"<div>outside slot</div><div>I'm the default slot</div><div>Root</div><div>3</div>"
);
expect(Object.keys(child.__owl__.refs)).toEqual([]);
expect(Object.keys(root.__owl__.refs)).toEqual(["myRef", "myRef2"]);
@@ -382,20 +383,4 @@ describe("t-call", () => {
});
expect(fixture.innerHTML).toBe("");
});
test("t-call-context: this is not available inside t-call-context", async () => {
class Root extends Component {
static template = xml`<t t-call="someTemplate" t-call-context="{}"/>`;
}
await mount(Root, fixture, {
templates: `
<templates>
<t t-name="someTemplate">
<t t-esc="this"/>
</t>
</templates>`,
});
expect(fixture.innerHTML).toBe("");
});
});
-31
View File
@@ -644,35 +644,4 @@ describe("t-model directive", () => {
const input = fixture.querySelector("input")!;
await editInput(input, "Beam me up, Scotty");
});
test("t-model with radio button group in t-foreach", async () => {
expect.assertions(6);
const steps: string[] = [];
class SomeComponent extends Component {
static template = xml`
<div t-on-click="getData" id="get_data">
<t t-foreach="options" t-as="opt" t-key="opt">
<input type="radio" name="radio_group" t-model="state.group" t-att-value="opt" t-att-id="opt"/>
</t>
</div>
`;
state = useState({ group: "scotty" });
options = ["beam", "scotty"];
getData() {
steps.push(`group: ${this.state.group}`);
}
}
await mount(SomeComponent, fixture);
const divEl = fixture.querySelector("#get_data") as HTMLElement;
expect(fixture.querySelector("input:checked")!.getAttribute("id")).toBe("scotty");
divEl.click();
expect(steps).toEqual(["group: scotty"]);
fixture.querySelector("input")!.click();
expect(steps).toEqual(["group: scotty", "group: beam"]);
await nextTick();
expect(fixture.querySelector("input:checked")!.getAttribute("id")).toBe("beam");
divEl.click();
expect(steps).toEqual(["group: scotty", "group: beam", "group: beam"]);
});
});
+1 -1
View File
@@ -646,7 +646,7 @@ exports[`Portal portal destroys on crash 2`] = `
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['props'].error&&ctx['this'].will.crash;
let txt1 = ctx['props'].error&&this.will.crash;
return block1([txt1]);
}
}"
+1 -1
View File
@@ -48,7 +48,7 @@ function writeToFile(filepath, data) {
}
// adapted from https://medium.com/@mhagemann/the-ultimate-way-to-slugify-a-url-string-in-javascript-b8e4a0d849e1
const a = -_,:;";
const a = "·_,:;";
const p = new RegExp(a.split("").join("|"), "g");
function slugify(str) {
+6 -35
View File
@@ -47,18 +47,6 @@ async function startRelease() {
let file = await ask(`Release notes (${REL_NOTES_FILE}): `);
file = file || REL_NOTES_FILE;
let content;
if (!fs.existsSync(`./${file}`)) {
let lastRelease = await getOutput("git log --grep='\\[REL\\]' -n 1 --pretty=%H");
const commitsSinceLastRelease = await getOutput(`git log ${lastRelease.trim()}..HEAD --pretty=%s`);
const commitsAsMdList = commitsSinceLastRelease.trim().split("\n").map(l => " - " + l).join("\n");
log(`${file} did not exist, created a template containing all commits since last release.`)
fs.writeFileSync(file, `# v${next}\n\n${commitsAsMdList}`);
const shouldContinue = await ask(`Check that the contents of ${file} is correct, then press y to continue: `);
if (shouldContinue.toLowerCase() !== "y") {
log("aborted");
return;
}
}
try {
content = await readFile("./" + file);
} catch (e) {
@@ -116,7 +104,7 @@ async function startRelease() {
// ---------------------------------------------------------------------------
log(`Step 7/${STEPS}: Creating the release...`);
const relaseResult = await execCommand(`gh release create v${next} dist/*.js ${draft} -F ${file}`);
const relaseResult = await execCommand(`gh release create v${next} dist/*.js ${draft} -F ${REL_NOTES_FILE}`);
if (relaseResult !== 0) {
logError("github release failed. Aborting.");
return;
@@ -132,7 +120,7 @@ async function startRelease() {
if (shouldUploadPlayground) {
log(`Bonus step: publishing new release on playground...`);
let owl_code = null;
let status = 0
status = 0
try {
owl_code = await readFile("dist/owl.iife.js");
@@ -142,8 +130,7 @@ async function startRelease() {
return;
}
status |= await execCommand("git checkout gh-pages");
status |= await execCommand("git pull --rebase");
status += await execCommand("git checkout gh-pages");
if (status !== 0) {
logError("Couldn't switch to gh-pages branch")
@@ -157,9 +144,9 @@ async function startRelease() {
return;
}
status |= await execCommand(`git commit -am "[IMP] update owl to v${next}"`);
status |= await execCommand(`git push origin gh-pages`);
status |= await execCommand("git checkout -");
status += await execCommand(`git commit -am "[IMP] update owl to v${next}"`);
status += await execCommand(`git push origin gh-pages`);
status += await execCommand("git checkout -");
if (status !== 0) {
logError("Something went wrong for the playground update.")
}
@@ -244,19 +231,3 @@ async function replaceInFile(file, from, to) {
});
});
}
async function getOutput(command) {
return new Promise((resolve, reject) => {
const childProcess = exec(command, (err, stdout, stderr) => {
if (err) {
reject(err);
}
resolve(stdout);
});
childProcess.on("exit", code => {
if (code !== 0) {
reject(code);
}
});
});
}