Compare commits

..

22 Commits

Author SHA1 Message Date
Géry Debongnie 3ae81e9bee wip 2023-02-21 15:12:19 +01:00
Géry Debongnie 56cfc6403d wip 2023-02-20 17:04:49 +01:00
Géry Debongnie 276c8a0295 [REL] v2.0.7
# v2.0.7

 - [FIX] compiler: t-key and t-ref together
2023-02-20 09:44:36 +01:00
Lucas Perais 0717fe241d [FIX] compiler: t-key and t-ref together
Have a t-ref on a DOM node with a t-key.
Change the t-key.

Before this commit, the old block removed its element from the component's refs *after*
the new block had been mounted, meaning that in effect, the resulting
ref at the end of the whole patch was null.

After this commit, we only remove an element from the component's ref if that very same
element was indeed the ref. (otherwise it means someone else has changed the ref.)
2023-02-20 09:42:16 +01:00
Géry Debongnie 1291f1f175 [REL] v2.0.6
# v2.0.6

 - [IMP] devtools: provide access to Fiber and RootFiber
 - Bump shelljs and git-rev-sync
 - [FIX] props validation: do not subscribe to props keys
 - [FIX] reactivity: only show key in subscription if observed by callback
 - [FIX] components: stop rendering stale t-component when delayed
2023-02-17 14:31:18 +01:00
Géry Debongnie 6c0a3525c8 [IMP] devtools: provide access to Fiber and RootFiber
The devtools extension needs to hook itself into some internal functions of
owl, and to do that, it needs a reference to Fiber and RootFiber
classes.
2023-02-17 14:20:03 +01:00
dependabot[bot] 13241422e9 Bump shelljs and git-rev-sync
Bumps [shelljs](https://github.com/shelljs/shelljs) to 0.8.5 and updates ancestor dependency [git-rev-sync](https://github.com/kurttheviking/git-rev-sync-js). These dependencies need to be updated together.


Updates `shelljs` from 0.7.7 to 0.8.5
- [Release notes](https://github.com/shelljs/shelljs/releases)
- [Changelog](https://github.com/shelljs/shelljs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/shelljs/shelljs/compare/v0.7.7...v0.8.5)

Updates `git-rev-sync` from 1.12.0 to 3.0.2
- [Release notes](https://github.com/kurttheviking/git-rev-sync-js/releases)
- [Commits](https://github.com/kurttheviking/git-rev-sync-js/compare/v1.12.0...v3.0.2)

---
updated-dependencies:
- dependency-name: shelljs
  dependency-type: indirect
- dependency-name: git-rev-sync
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-17 11:42:38 +01:00
Géry Debongnie 8f2c7f24d7 [FIX] props validation: do not subscribe to props keys
In dev mode, owl inserts an additional props validation step. Before
this commit, the validation would iterate on all key/value pairs of each
props. If the props is reactive, this has the unfortunate side effect of
subscribing the component to each of the keys, even though it should not
be.

This commit avoids the issue by only validating the raw object.
2023-02-09 13:34:07 +01:00
Samuel Degueldre fed9cc467f [FIX] reactivity: only show key in subscription if observed by callback
Previously, the getSubscriptions debugging utility returned all observed
keys for a given target, instead of only the keys observed by the
callback it received as argument. This commit fixes that issues.
2023-02-09 10:13:41 +01:00
Samuel Degueldre 33174b301b [FIX] components: stop rendering stale t-component when delayed
Previously, if the value of a t-component directive changed, but there
was already a scheduled render for the component before that change, the
rendering of the existing component would get delayed (as it should),
but after the parent's rendering was complete, the old component which
will be destroyed during the patch would still get rendered, despite
being stale. This can cause crashes if that component relies on state
that no longer exists.

This was caused by the fact that when rendering, we check the parent
chain for an active render, and also check that the component still
exists in the parent's fiber childrenMap (ie, we know that the upcoming
patch is not about to destroy the component). To do this, we use the
component's parentKey, but in the case of t-component, the parentKey for
both possible components is the same, causing the stale component to
incorrectly believe that it's not about to be destroyed, by finding the
next component in the childrenMap under the same key.

This commit fixes that by prepending the component's name to the
parentKey, meaning that if the value of the t-component changes, so will
the key, and the render will be further delayed until the new state is
patched, at which point the stale component will be destroyed and the
following attempt to render will be cancelled as expected.

The choice to use the component's name is to simplify the
implementation, this means that if using t-component and switching
between components that have the same class name, this protection will
not work. The alternative would be to generate a unique id for the
component class, eg with a WeakMap, but this both complicates the
implementation and adds runtime overhead, for a case that is likely
extremely rare. We are open to refine the implementation should this
problem occur in practice.
2023-02-06 11:11:42 +01:00
Samuel Degueldre ea5d2be502 [REL] v2.0.5
# v2.0.5

 - [FIX] reactivity: improve performance for long-lived reactives
2023-01-27 15:29:07 +01:00
Samuel Degueldre 4a761a7403 [FIX] reactivity: improve performance for long-lived reactives
Previously, when having a long lived reactive object and writing a lot
of keys to it, clearing the reactive subscriptions on that object would
slow down as time went on. This is because when clearing a target's
subsctiptions for a callback, we look at all the keys that are observed, and for all
the observed keys, we remove the callback from the set of callbacks
observing that key. The problem arises from the fact that after doing
that, even if the set of callbacks observing that key is now empty, we
don't remove the key from the observed keys, meaning that any further
clearing of subscriptions will have to iterate over that key to attempt
to clear the callbacks even if there are none.

This commit fixes this problem by simply removing the key from the
observedKeys if there are no longer any callbacks observing it.

This commit also improves performance for bare reactives (reactives
created with no callback). The initial implementation simply creates a
default empty callback when creating a reactive, and treats it like any
other, meaning it can observe keys and be notified of changes even
though we know in advance that it does nothing. This can compound with
the previous issue when you're doing a lot of manipulations on a bare
reactive internally for the sole purpose of notifying outside observers,
as this creates a lot of useless work in the reactivity system.
2023-01-27 09:34:55 +01:00
Lucas Lefèvre 8702db03fb [FIX] tools: allow to pre-compile templates with - in their name
Compiling a template with a dash ("-") in its name generates an
invalid function name in the compiled code.

A template named `"my-component"` leads to the function
`function my-component(app, bdom, helpers) { ... }` which has an
invalid name.

closes #1333
2023-01-25 07:56:30 +01:00
Samuel Degueldre b2685b6709 [IMP] tools: pull playground before trying to publish
Previously, if you were not the last person to publish to the
playground, your playground branch would be behind the remote and trying
to push to it would fail.

This commit attempts to pull the changes before updating owl so that
even if you were not the last person to push to the playground, as long
as the pull is a fast-forward, publishing to the playground won't fail.

This commit also adapts some of the status number manipulation to use
bitwise or instead of addition, since return status code can be both
positive or negative and may cancel one another. Using bitwise or
ensures than any non-zero code will make the status non-zero and stay
that way.
2023-01-23 12:39:50 +01:00
Samuel Degueldre c30678f3ea [REL] v2.0.4
# v2.0.4

 - [IMP] app: expose live apps for the devtools
 - [FIX] compiler: support t-model radio group in t-foreach
 - [IMP] runtime: improve useExternalListener typing
2023-01-23 12:21:13 +01:00
Samuel Degueldre 6ca6717965 [IMP] tools: release scripts creates a template for release notes
This template contains the release version as a markdown title followed
by a markdown list with all the commit titles since the previous
release.
2023-01-23 12:19:14 +01:00
Julien Carion ad4adb930e [IMP] app: expose live apps for the devtools
This commit exposes owl apps in a global variable so that the owl
devtools can hook themselves on these apps. While the devtools are not
yet ready to be merged, exposing the apps will allow us to test the
devtools in production scenarios while polishing the development of
them.
2023-01-23 11:40:02 +01:00
Lucas Perais cea82e945d [FIX] compiler: t-model supports radio group in t-foreach
Have a radio group defined inside a t-foreach:
```xml
  <t t-foreach="values" t-as="val" t-key="val">
    <input name="radiogroup" t-att-value="val" t-model="state.radioGroup" />
  </t>
```

Before this commit the algorithm that set the "checked" attribute on the current active
radio button according to the state did not support having a dynamic value (`t-att-value`)

After this commit, this use case works as we go look in the dynamic attributes too.
2023-01-20 09:26:54 +01:00
Samuel Degueldre a69f8a39e7 [IMP] runtime: improve useExternalListener typing
Previously the type of the target for useExternalListener was
HTMLElement or Window, this makes document an invalid target. Here there
is no reason to use the EventTarget interface instead, as
useExternalListener only uses methods from that interface and should
work with any event target.

Closes odoo/owl#1323
2023-01-18 13:03:23 +01:00
Géry Debongnie 316eb06279 [REL] v2.0.3
# v2.0.2

Some small bug fixes

- fix: compiler: correctly escape backslashes when emitting block string
- fix: reactivity: don't subscribe to keys when making reactive
- fix: t-call-context: fix capture making component available in ctx
- fix: t-call-context: make `this` unavailable in rendering context
2023-01-12 16:29:11 +01:00
Samuel Degueldre df59ec49ae [FIX] t-call-context: make this unavailable in rendering context
t-call-context is a feature that's supposed to mask the rendering
context completely, but currently the component remains available
through `this`.

This commit stops treating `this` as a reserved word, so that it's
compiled to a lookup in the rendering context, and adds `this` to the
rendering context when binding the component's rendering function. With
these changes, `this` behaves the same as before when outside a
t-call-context, but when the rendering context is overriden, the
template can no longer access `this`. `this` still represents the
instance of the component inside of the rendering function since it's
needed by owl internally.

A side-effect of this change is that now the rendering context is no
longer the instance of the component by default, but is always an object
with the component in its prototype chain. This was already the case
before in some contexts (eg inside t-foreach, or inside components with
a t-set/t-call anywhere in its template). This can cause issues in rare
cases when a component method was called directly on the rendering
context, as before this change, the method's bound this would be the
component instance (except in a t-foreach, component with a
t-set/t-call, etc), while after this change it is now never the
component instance. When the method only reads on `this` there is no
issue as all the components properties are available on the rendering
contexts, but setting a value on `this` will write on the rendering
context and not the component which is likely a mistake.

While this is a breaking change, simply adding a t-set/t-call to any
template would break components that would be broken by this change,
with this in mind we decided to make this change anyway so that
developers get the error as early as possible in the development cycle
rather than having a seemingly inocuous change break code under them.
2023-01-12 09:38:22 +01:00
Samuel Degueldre 2a008a8679 [IMP] tooling: add eslint ci step to avoid stray .only and debugger 2023-01-11 15:47:09 +01:00
43 changed files with 6668 additions and 348 deletions
+47
View File
@@ -0,0 +1,47 @@
{
"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,
},
}
+2 -1
View File
@@ -22,7 +22,8 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm ci
- run: npm run test
- run: npm run check-formatting
- run: npm run lint
- run: npm run build
-3
View File
@@ -14,9 +14,6 @@ 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
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`,
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`,
that we will use in the next section.
Finally, notice the use of the `t-att-checked` attribute:
+3 -4
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 (typically, the component):
2. it can use anything in the rendering context (which typically contains the properties of the component):
```xml
<p t-if="user.birthday === today()">Happy bithday!</p>
@@ -541,9 +541,8 @@ 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 (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:
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:
```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.2",
"version": "2.0.7",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"module": "dist/owl.es.js",
@@ -25,6 +25,7 @@
"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"
@@ -42,26 +43,25 @@
"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",
"git-rev-sync": "^1.12.0",
"eslint": "8.31.0",
"git-rev-sync": "^3.0.2",
"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",
"uglify-es": "^3.3.9"
"typescript": "4.5.2"
},
"jest": {
"testEnvironment": "jsdom",
+82 -37
View File
@@ -15,6 +15,7 @@ import {
ASTLog,
ASTMulti,
ASTSlot,
ASTSlotDefinition,
ASTTCall,
ASTTCallBlock,
ASTTEsc,
@@ -137,12 +138,14 @@ 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 {
@@ -155,6 +158,7 @@ function createContext(parentCtx: Context, params?: Partial<Context>): Context {
tKeyExpr: null,
nameSpace: parentCtx.nameSpace,
tModelSelectedExpr: parentCtx.tModelSelectedExpr,
slotVar: parentCtx.slotVar
},
params
);
@@ -358,7 +362,7 @@ export class CodeGenerator {
): BlockDescription {
const hasRoot = this.target.hasRoot;
const block = new BlockDescription(this.target, type);
if (!hasRoot && !ctx.preventRoot) {
if (!hasRoot && !ctx.preventRoot && !ctx.ignoreRoot) {
this.target.hasRoot = true;
block.isRoot = true;
}
@@ -384,6 +388,9 @@ 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);
@@ -465,6 +472,8 @@ 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:
@@ -632,7 +641,15 @@ export class CodeGenerator {
let idx: number;
if (specialInitTargetAttr) {
idx = block!.insertData(`${fullExpression} === '${attrs[targetAttr]}'`, "attr");
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");
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
} else if (hasDynamicChildren) {
const bValueId = generateId("bValue");
@@ -664,8 +681,9 @@ 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(`(el) => refs[${str}] = el`, "ref");
const idx = block!.insertData(`singleRefSetter(refs, ${str})`, "ref");
attrs["block-ref"] = String(idx);
} else {
let name = ast.ref;
@@ -678,7 +696,8 @@ export class CodeGenerator {
info[1] = `multiRefSetter(refs, \`${name}\`)`;
} else {
let id = generateId("ref");
this.target.refInfo[name] = [id, `(el) => refs[\`${name}\`] = el`];
this.helpers.add("singleRefSetter");
this.target.refInfo[name] = [id, `singleRefSetter(refs, \`${name}\`)`];
const index = block!.data.push(id) - 1;
attrs["block-ref"] = String(index);
}
@@ -1134,52 +1153,38 @@ export class CodeGenerator {
const props: string[] = ast.props ? this.formatPropObject(ast.props) : [];
// slots
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 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 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 (slotDef && !(ast.dynamicProps || hasSlotsProp)) {
this.helpers.add("markRaw");
props.push(`slots: markRaw(${slotDef})`);
if (slotVar && !(ast.dynamicProps || hasSlotsProp)) {
props.push(`slots: ${slotVar}`);
}
let propString = this.getPropString(props, ast.dynamicProps);
let propVar: string;
if ((slotDef && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
if ((slotVar && (ast.dynamicProps || hasSlotsProp)) || this.dev) {
propVar = generateId("props");
this.define(propVar!, propString);
propString = propVar!;
}
if (slotDef && (ast.dynamicProps || hasSlotsProp)) {
if (slotVar && (ast.dynamicProps || hasSlotsProp)) {
this.helpers.add("markRaw");
this.addLine(`${propVar!}.slots = markRaw(Object.assign(${slotDef}, ${propVar!}.slots))`);
this.addLine(`${propVar!}.slots = markRaw(Object.assign(${slotVar}, ${propVar!}.slots))`);
}
// cmap key
@@ -1210,11 +1215,18 @@ export class CodeGenerator {
id,
expr: `app.createComponent(${
ast.isDynamic ? null : expr
}, ${!ast.isDynamic}, ${!!ast.slots}, ${!!ast.dynamicProps}, ${
}, ${!ast.isDynamic}, ${!!ast.body}, ${!!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})`;
@@ -1246,6 +1258,39 @@ 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,this,eval,void,Math,RegExp,Array,Object,Date".split(
"true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date".split(
","
);
+88 -67
View File
@@ -23,6 +23,7 @@ export const enum ASTType {
TDebug,
TLog,
TSlot,
TSetSlot,
TCallBlock,
TTranslation,
TPortal,
@@ -120,7 +121,9 @@ export interface ASTTCall {
context: string | null;
}
interface SlotDefinition {
export interface ASTSlotDefinition {
type: ASTType.TSetSlot;
name: string;
content: AST | null;
scope: string | null;
on: EventHandlers | null;
@@ -134,7 +137,8 @@ export interface ASTComponent {
dynamicProps: string | null;
on: EventHandlers | null;
props: { [name: string]: string } | null;
slots: { [name: string]: SlotDefinition } | null;
body: AST | null;
// slots: { [name: string]: ASTSlotDefinition } | null;
}
export interface ASTSlot {
@@ -186,6 +190,7 @@ export type AST =
| ASTTKey
| ASTComponent
| ASTSlot
| ASTSlotDefinition
| ASTTCallBlock
| ASTLog
| ASTDebug
@@ -238,6 +243,7 @@ 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) ||
@@ -573,12 +579,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);
@@ -706,8 +712,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;
@@ -727,67 +733,17 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
}
}
let slots: ASTComponent["slots"] | null = null;
let body: ASTComponent["body"] = null;
// let slots: ASTComponent["slots"] | null = null;
if (node.hasChildNodes()) {
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 };
body = parseChildNodes(node, ctx);
if (!node.querySelector('[t-set-slot]')) {
body = {type: ASTType.TSetSlot, name: "default", content: body, on: null, attrs: null, scope: null}
}
// 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, slots, on };
return { type: ASTType.TComponent, name, isDynamic, dynamicProps, props, body, on };
}
// -----------------------------------------------------------------------------
@@ -821,6 +777,71 @@ 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;
+20 -3
View File
@@ -1,12 +1,11 @@
import { Component, ComponentConstructor, Props } from "./component";
import { ComponentNode } from "./component_node";
import { nodeErrorHandlers, OwlError } from "./error_handling";
import { Fiber, MountOptions } from "./fibers";
import { nodeErrorHandlers, OwlError, handleError } from "./error_handling";
import { Fiber, RootFiber, 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
@@ -32,6 +31,22 @@ 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,
@@ -49,6 +64,7 @@ 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;
}
@@ -110,6 +126,7 @@ export class App<
this.scheduler.flush();
this.root.destroy();
}
window.__OWL_DEVTOOLS__.apps.delete(this);
}
createComponent<P extends Props>(
+3 -1
View File
@@ -114,7 +114,8 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
}
}
this.component = new C(props, env, this);
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
const ctx = Object.assign(Object.create(this.component), { this: this.component });
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
this.component.setup();
currentNode = null;
}
@@ -317,6 +318,7 @@ 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: HTMLElement | typeof window,
target: EventTarget,
eventName: string,
handler: EventListener,
eventParams?: AddEventListenerOptions
+23 -7
View File
@@ -1,8 +1,13 @@
import { Callback } from "./utils";
import type { 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"
@@ -86,6 +91,9 @@ 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());
}
@@ -140,8 +148,11 @@ export function clearReactivesForCallback(callback: Callback): void {
if (!observedKeys) {
continue;
}
for (const callbacks of observedKeys.values()) {
for (const [key, callbacks] of observedKeys.entries()) {
callbacks.delete(callback);
if (!callbacks.size) {
observedKeys.delete(key);
}
}
}
targetsToClear.clear();
@@ -151,10 +162,15 @@ export function getSubscriptions(callback: Callback) {
const targets = callbacksToTargets.get(callback) || [];
return [...targets].map((target) => {
const keysToCallbacks = targetToKeysToCallbacks.get(target);
return {
target,
keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
};
let keys = [];
if (keysToCallbacks) {
for (const [key, cbs] of keysToCallbacks) {
if (cbs.has(callback)) {
keys.push(key);
}
}
}
return { target, keys };
});
}
// Maps reactive objects to the underlying target
@@ -187,7 +203,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 = () => {}): T {
export function reactive<T extends Target>(target: T, callback: Callback = NO_CALLBACK): T {
if (!canBeMadeReactive(target)) {
throw new OwlError(`Cannot make the given value reactive`);
}
+11 -7
View File
@@ -29,13 +29,6 @@ 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;
}
@@ -209,6 +202,16 @@ 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
@@ -267,6 +270,7 @@ export const helpers = {
prepareList,
setContextValue,
multiRefSetter,
singleRefSetter,
shallowEqual,
toNumber,
validateProps,
+2
View File
@@ -1,4 +1,5 @@
import { OwlError } from "./error_handling";
import { toRaw } from "./reactivity";
type BaseType =
| typeof String
@@ -84,6 +85,7 @@ 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,8 +519,9 @@ 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['value'];
let hdlr1 = [()=>this.update(v1), ctx];
const v1 = ctx['this'];
const v2 = ctx['value'];
let hdlr1 = [()=>v1.update(v2), 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 } = helpers;
let { prepareList, withKey, singleRefSetter } = 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 = (el) => refs[\`search_input\`] = el;
const ref2 = (el) => refs[\`settings_menu\`] = el;
const ref1 = singleRefSetter(refs, \`search_input\`);
const ref2 = singleRefSetter(refs, \`settings_menu\`);
let b2,b4,b14,b17,b22,b23,b24,b25;
let attr1 = \`/runbot/\${ctx['project'].slug}\`;
let txt1 = ctx['project'].name;
@@ -4,13 +4,14 @@ 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 = (el) => refs[\`myspan\${v1}\`] = el;
let ref1 = singleRefSetter(refs, \`myspan\${v1}\`);
return block1([ref1]);
}
}"
@@ -20,13 +21,14 @@ 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 = (el) => refs[\`myspan\${v1}\`] = el;
let ref1 = singleRefSetter(refs, \`myspan\${v1}\`);
return block1([ref1]);
}
}"
@@ -36,12 +38,13 @@ 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 = (el) => refs[\`myspan\`] = el;
const ref1 = singleRefSetter(refs, \`myspan\`);
return block1([ref1]);
}
}"
@@ -66,12 +69,13 @@ 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 = (el) => refs[\`name\`] = el;
const ref1 = singleRefSetter(refs, \`name\`);
return block1([ref1]);
}
}"
@@ -81,13 +85,14 @@ 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 = (el) => refs[\`name\`] = el;
const ref1 = singleRefSetter(refs, \`name\`);
let b2;
if (ctx['condition']) {
b2 = block2([ref1]);
@@ -101,7 +106,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, withKey } = helpers;
let { prepareList, singleRefSetter, withKey } = helpers;
let block1 = createBlock(\`<div><block-child-0/></div>\`);
let block3 = createBlock(\`<div block-ref=\\"0\\"><block-text-1/></div>\`);
@@ -115,7 +120,7 @@ exports[`t-ref refs in a loop 1`] = `
const key1 = ctx['item'];
const tKey_1 = ctx['item'];
const v1 = ctx['item'];
let ref1 = (el) => refs[(v1)] = el;
let ref1 = singleRefSetter(refs, (v1));
let txt1 = ctx['item'];
c_block2[i1] = withKey(block3([ref1, txt1]), tKey_1 + key1);
}
@@ -129,14 +134,15 @@ 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 = (el) => refs[\`name\`] = el;
const ref2 = (el) => refs[\`p\`] = el;
const ref1 = singleRefSetter(refs, \`name\`);
const ref2 = singleRefSetter(refs, \`p\`);
let b2;
if (ctx['condition']) {
b2 = block2([ref1]);
+3
View File
@@ -284,6 +284,9 @@ 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']}, key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({displayGrandChild: ctx['displayGrandChild']}, (Comp1).name + key + \`__1\`, node, this, Comp1));
}
}"
`;
@@ -1213,6 +1213,45 @@ 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
) {
@@ -1776,7 +1815,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]}, key + \`__1__\${key1}\`, node, this, Comp1));
b3 = toggler(Comp1, comp1({key: ctx['arr'][0]}, (Comp1).name + key + \`__1__\${key1}\`, node, this, Comp1));
}
c_block1[i1] = withKey(multi([b3]), key1);
}
@@ -1810,7 +1849,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']}, tKey_1 + key + \`__1\`, node, this, Comp1)));
const b2 = toggler(tKey_1, toggler(Comp1, comp1({key: ctx['key']}, (Comp1).name + tKey_1 + key + \`__1\`, node, this, Comp1)));
return toggler(tKey_1, block1([], [b2]));
}
}"
@@ -1836,7 +1875,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']}, tKey_1 + key + \`__1\`, node, this, Comp1)));
return toggler(tKey_1, toggler(Comp1, comp1({key: ctx['key']}, (Comp1).name + 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({}, key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
}
return function template(ctx, node, key = \\"\\") {
@@ -940,9 +940,10 @@ 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['cp'];
const v1 = ctx['this'];
const v2 = ctx['cp'];
const ctx1 = capture(ctx);
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);
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);
}
return list(c_block1);
}
@@ -1009,18 +1010,19 @@ 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]}, key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({id: ctx['elem'][0]}, (Comp1).name + 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(this.elements));;
const [k_block1, v_block1, l_block1, c_block1] = prepareList(Object.entries(ctx['this'].elements));;
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`elem\`] = v_block1[i1];
const key1 = ctx['elem'][0];
const v1 = ctx['elem'];
const v1 = ctx['this'];
const v2 = ctx['elem'];
const ctx1 = capture(ctx);
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);
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);
}
return list(c_block1);
}
@@ -1155,7 +1157,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(this.will.crash);
b2 = text(ctx['this'].will.crash);
} else {
b3 = text(ctx['state'].error);
}
@@ -1186,7 +1188,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(this.will.crash);
b2 = text(ctx['this'].will.crash);
} else {
b3 = text(ctx['state'].error);
}
@@ -1218,7 +1220,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 = this.will.crash;
let txt1 = ctx['this'].will.crash;
return block1([txt1]);
}
}"
@@ -1232,7 +1234,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 = this.will.crash;
let txt1 = ctx['this'].will.crash;
return block1([txt1]);
}
}"
@@ -1277,7 +1279,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(this.will.crash);
b2 = text(ctx['this'].will.crash);
}
return block1([], [b2]);
}
@@ -4,14 +4,15 @@ 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 = (el) => refs[\`input1\`] = el;
const ref2 = (el) => refs[\`input2\`] = el;
const ref1 = singleRefSetter(refs, \`input1\`);
const ref2 = singleRefSetter(refs, \`input2\`);
let b2;
if (ctx['state'].flag) {
b2 = block2([ref2]);
@@ -25,13 +26,14 @@ 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 = (el) => refs[\`input1\`] = el;
const ref2 = (el) => refs[\`input2\`] = el;
const ref1 = singleRefSetter(refs, \`input1\`);
const ref2 = singleRefSetter(refs, \`input2\`);
return block1([ref1, ref2]);
}
}"
@@ -264,12 +266,13 @@ 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 = (el) => refs[\`div\`] = el;
const ref1 = singleRefSetter(refs, \`div\`);
let b2;
if (ctx['state'].value) {
b2 = block2([ref1]);
@@ -353,12 +356,13 @@ 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 = (el) => refs[\`button\`] = el;
const ref1 = singleRefSetter(refs, \`button\`);
let txt1 = ctx['value'];
return block1([ref1, txt1]);
}
@@ -841,6 +841,33 @@ 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,3 +140,39 @@ 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,12 +4,13 @@ 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 = (el) => refs[\`div\`] = el;
const ref1 = singleRefSetter(refs, \`div\`);
return block1([ref1]);
}
}"
@@ -19,7 +20,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 { multiRefSetter } = helpers;
let { singleRefSetter, multiRefSetter } = helpers;
let block2 = createBlock(\`<div block-ref=\\"0\\"/>\`);
let block3 = createBlock(\`<span block-ref=\\"0\\"/>\`);
@@ -42,13 +43,14 @@ 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 = (el) => refs[\`root\`] = el;
const ref1 = singleRefSetter(refs, \`root\`);
let b2;
let txt1 = ctx['props'].tree.value;
if (ctx['props'].tree.child) {
@@ -59,11 +61,33 @@ 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, markRaw } = helpers;
let { capture, singleRefSetter, 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>\`);
@@ -71,7 +95,7 @@ exports[`refs refs are properly bound in slots 1`] = `
function slot1(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = (el) => refs[\`myButton\`] = el;
const ref1 = singleRefSetter(refs, \`myButton\`);
let hdlr1 = [ctx['doSomething'], ctx];
return block2([hdlr1, ref1]);
}
@@ -104,7 +128,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 { multiRefSetter } = helpers;
let { singleRefSetter, 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 { markRaw } = helpers;
let { singleRefSetter, 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 = (el) => refs[\`div\`] = el;
const ref1 = singleRefSetter(refs, \`div\`);
const b2 = block2([ref1]);
const b3 = comp1({}, key + \`__1\`, node, this, null);
return multi([b2, b3]);
@@ -282,6 +282,47 @@ 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
) {
@@ -349,42 +390,6 @@ 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
) {
@@ -1471,7 +1476,8 @@ exports[`slots simple default slot 1`] = `
}
return function template(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null);
let slots1 = markRaw({'default': {__render: slot1.bind(this), __ctx: ctx}});
return comp1({slots: slots1}, key + \`__1\`, node, this, null);
}
}"
`;
@@ -1570,7 +1576,8 @@ exports[`slots simple default slot, variation 1`] = `
}
return function template(ctx, node, key = \\"\\") {
return comp1({slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx}})}, key + \`__1\`, node, this, null);
let slots1 = markRaw({'default': {__render: slot1.bind(this), __ctx: ctx}});
return comp1({slots: slots1}, key + \`__1\`, node, this, null);
}
}"
`;
@@ -2028,7 +2035,8 @@ exports[`slots slot content is bound to caller (variation) 1`] = `
ctx = Object.create(ctx);
ctx[isBoundary] = 1
setContextValue(ctx, \\"var\\", 1);
let hdlr1 = [()=>this.inc(), ctx];
const v1 = ctx['this'];
let hdlr1 = [()=>v1.inc(), ctx];
return block1([hdlr1]);
}
@@ -2395,7 +2403,8 @@ exports[`slots slots are properly bound to correct component 2`] = `
ctx = Object.create(ctx);
ctx[isBoundary] = 1
setContextValue(ctx, \\"var\\", 1);
let hdlr1 = [()=>this.increment(), ctx];
const v1 = ctx['this'];
let hdlr1 = [()=>v1.increment(), ctx];
let txt1 = ctx['state'].value;
return block1([hdlr1, txt1]);
}
@@ -2875,16 +2884,23 @@ 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 { capture, markRaw } = helpers;
let { markRaw, capture } = 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);
return comp1({slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
let slots1 = markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}});
return comp1({slots: slots1}, 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 = this.will.crash;
let txt1 = ctx['this'].will.crash;
return block1([attr1, txt1]);
}
}"
@@ -109,7 +109,8 @@ 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 = \\"\\") {
let hdlr1 = [()=>this.update(), ctx];
const v1 = ctx['this'];
let hdlr1 = [()=>v1.update(), ctx];
return block1([hdlr1]);
}
}"
@@ -168,8 +169,9 @@ 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['a'];
let hdlr1 = [()=>this.update(v1), ctx];
const v1 = ctx['this'];
const v2 = ctx['a'];
let hdlr1 = [()=>v1.update(v2), ctx];
return block1([hdlr1]);
}
}"
@@ -287,7 +289,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(this), ctx];
let hdlr1 = [\\"stop\\", ctx['onClicked'].bind(ctx['this']), ctx];
let txt1 = ctx['level'];
const b3 = block3([hdlr1, txt1]);
ctx = Object.create(ctx);
@@ -532,35 +534,32 @@ 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 { bind, capture, isBoundary, withDefault, setContextValue, markRaw } = helpers;
let { singleRefSetter, 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 = (el) => refs[\`myRef2\`] = el;
const ref2 = singleRefSetter(refs, \`myRef2\`);
ctx = Object.create(ctx);
ctx[isBoundary] = 1
const b4 = block4([ref2]);
setContextValue(ctx, \\"test\\", 3);
let txt1 = this.__owl__.name;
let txt1 = ctx['test'];
const b5 = block5([txt1]);
let txt2 = ctx['test'];
const b6 = block6([txt2]);
return multi([b4, b5, b6]);
return multi([b4, b5]);
}
return function template(ctx, node, key = \\"\\") {
const refs = this.__owl__.refs;
const ref1 = (el) => refs[\`myRef\`] = el;
const ref1 = singleRefSetter(refs, \`myRef\`);
const b2 = block2([ref1]);
const ctx1 = capture(ctx);
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]);
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]);
}
}"
`;
@@ -624,3 +623,27 @@ 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({}, key + \`__1\`, node, this, Comp1));
const b2 = toggler(Comp1, comp1({}, (Comp1).name + 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({}, tKey_1 + key + \`__1\`, node, this, Comp1)));
return toggler(tKey_1, toggler(Comp1, comp1({}, (Comp1).name + 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({}, tKey_1 + key + \`__1\`, node, this, Comp1)));
return toggler(tKey_1, toggler(Comp1, comp1({}, (Comp1).name + 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({}, key + \`__1\`, node, this, Comp1));
const b2 = toggler(Comp1, comp1({}, (Comp1).name + 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({}, key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({}, (Comp1).name + 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({}, key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
}
}"
`;
@@ -569,6 +569,36 @@ 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,8 +111,9 @@ 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['val'];
let hdlr1 = [()=>this.addVal(v1), ctx];
const v1 = ctx['this'];
const v2 = ctx['val'];
let hdlr1 = [()=>v1.addVal(v2), ctx];
c_block2[i1] = withKey(block3([txt1, txt2, hdlr1]), key1);
}
const b2 = list(c_block2);
@@ -197,8 +198,9 @@ 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['name'];
const hdlr1 = [()=>this.log(v1), ctx];
const v1 = ctx['this'];
const v2 = ctx['name'];
const hdlr1 = [()=>v1.log(v2), ctx];
c_block1[i1] = withKey(catcher1(comp1({value: ctx['name']}, key + \`__1__\${key1}\`, node, this, null), [hdlr1]), key1);
}
return list(c_block1);
@@ -293,8 +295,9 @@ 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['name'];
const hdlr1 = [()=>this.log(v1), ctx];
const v1 = ctx['this'];
const v2 = ctx['name'];
const hdlr1 = [()=>v1.log(v2), ctx];
return catcher1(comp1({value: ctx['name']}, key + \`__1\`, node, this, null), [hdlr1]);
}
}"
@@ -393,7 +396,8 @@ exports[`t-on t-on on t-set-slots 1`] = `
function slot1(ctx, node, key = \\"\\") {
const b6 = block6();
const b7 = block7();
const hdlr1 = [()=>this.state.count++, ctx];
const v1 = ctx['this'];
const hdlr1 = [()=>v1.state.count++, ctx];
return catcher1(multi([b6, b7]), [hdlr1]);
}
@@ -450,7 +454,8 @@ exports[`t-on t-on on t-slots 2`] = `
const b2 = text(\` [\`);
const b3 = text(ctx['state'].count);
const b4 = text(\`] \`);
const hdlr1 = [()=>this.state.count++, ctx];
const v1 = ctx['this'];
const hdlr1 = [()=>v1.state.count++, ctx];
const b5 = catcher1(callSlot(ctx, node, key, 'default', false, {}), [hdlr1]);
return multi([b2, b3, b4, b5]);
}
+59
View File
@@ -1,6 +1,7 @@
import {
App,
Component,
ComponentConstructor,
mount,
onMounted,
onRendered,
@@ -4067,6 +4068,64 @@ 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 };
+24 -1
View File
@@ -1,5 +1,5 @@
import { makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
import { Component, onError, xml, mount, OwlError } from "../../src";
import { Component, onError, xml, mount, OwlError, useState } from "../../src";
import { App, DEV_MSG } from "../../src/runtime/app";
import { validateProps } from "../../src/runtime/template_helpers";
import { Schema } from "../../src/runtime/validation";
@@ -682,6 +682,29 @@ 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,6 +7,7 @@ import {
onWillUnmount,
useState,
xml,
toRaw,
} from "../../src";
import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
@@ -232,3 +233,34 @@ 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) }]);
});
});
+39 -2
View File
@@ -1,6 +1,14 @@
import { App, Component, mount, onMounted, useRef, useState } from "../../src/index";
import {
App,
Component,
mount,
onMounted,
onPatched,
useRef,
useState,
xml,
} from "../../src/index";
import { logStep, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
import { xml } from "../../src/index";
snapshotEverything();
let fixture: HTMLElement;
@@ -128,4 +136,33 @@ 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"));
});
});
+94 -78
View File
@@ -62,7 +62,7 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("some other text");
});
test("simple slot with slot scope", async () => {
test.skip("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("slot with slot scope and t-props", async () => {
test.skip("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("simple dynamic slot with slot scope", async () => {
test.skip("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("simple named and empty slot", async () => {
test.skip("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("simple named and empty slot -- 2", async () => {
test.skip("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("default slot with slot scope: shorthand syntax", async () => {
test.skip("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("simple default slot with params", async () => {
test.skip("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("simple default slot with params and bound function", async () => {
test.skip("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("default slot with params with - in it", async () => {
test.skip("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("fun: two calls to the same slot", async () => {
test.skip("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("slot content is bound to caller", async () => {
test.skip("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("slot content is bound to caller (variation)", async () => {
test.skip("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("can define and call slots", async () => {
test.skip("can define and call slots", async () => {
class Dialog extends Component {
static template = xml`
<div>
@@ -352,7 +352,7 @@ describe("slots", () => {
);
});
test("can define and call slots with params", async () => {
test.skip("can define and call slots with params", async () => {
class Dialog extends Component {
static template = xml`
<div>
@@ -381,7 +381,7 @@ describe("slots", () => {
);
});
test("can define and call slots with bound params", async () => {
test.skip("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("no named slot content => just no children", async () => {
test.skip("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("named slots can define a default content", async () => {
test.skip("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("can define a default content", async () => {
test.skip("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("default content is not rendered if slot is provided", async () => {
test.skip("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("default content is not rendered if named slot is provided", async () => {
test.skip("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("slots are properly bound to correct component", async () => {
test.skip("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("slots are rendered with proper context", async () => {
test.skip("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("slots are rendered with proper context, part 2", async () => {
test.skip("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("slots are rendered with proper context, part 3", async () => {
test.skip("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("slots are rendered with proper context, part 4", async () => {
test.skip("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("content is the default slot", async () => {
test.skip("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("content is the default slot (variation)", async () => {
test.skip("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("default slot work with text nodes", async () => {
test.skip("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("default slot work with text nodes (variation)", async () => {
test.skip("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("multiple roots are allowed in a named slot", async () => {
test.skip("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("multiple roots are allowed in a default slot", async () => {
test.skip("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("missing slots are ignored", async () => {
test.skip("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("t-debug on a t-set-slot (defining a slot)", async () => {
test.skip("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("slot preserves properly parented relationship", async () => {
test.skip("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("slot preserves properly parented relationship, even through t-call", async () => {
test.skip("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("t-slot scope context", async () => {
test.skip("t-slot scope context", async () => {
expect.assertions(4);
class Wrapper extends Component {
@@ -916,7 +916,7 @@ describe("slots", () => {
await nextTick();
});
test("t-slot in recursive templates", async () => {
test.skip("t-slot in recursive templates", async () => {
class Wrapper extends Component {
static template = xml`
<wrapper>
@@ -979,7 +979,7 @@ describe("slots", () => {
);
});
test("t-slot within dynamic t-call", async () => {
test.skip("t-slot within dynamic t-call", async () => {
class Child extends Component {
static template = xml`<div class="child"/>`;
}
@@ -1021,7 +1021,7 @@ describe("slots", () => {
);
});
test("slots in t-foreach in t-foreach", async () => {
test.skip("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("default slot next to named slot, with default content", async () => {
test.skip("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("dynamic t-slot call", async () => {
test.skip("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("dynamic t-slot call with default", async () => {
test.skip("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("slot are properly rendered if inner props are changed", async () => {
test.skip("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("slots and wrapper components", async () => {
test.skip("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("template can just return a slot", async () => {
test.skip("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("multiple slots containing components", async () => {
test.skip("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("slots in t-foreach and re-rendering", async () => {
test.skip("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("slots in t-foreach with t-set and re-rendering", async () => {
test.skip("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("nested slots in same template", async () => {
test.skip("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("t-slot nested within another slot", async () => {
test.skip("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("slots in slots, with vars", async () => {
test.skip("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("t-set t-value in a slot", async () => {
test.skip("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("slot and t-esc", async () => {
test.skip("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("slot and t-call", async () => {
test.skip("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("slot and (inline) t-call", async () => {
test.skip("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("nested slots: evaluation context and parented relationship", async () => {
test.skip("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("named slot inside slot", async () => {
test.skip("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("named slots inside slot, again", async () => {
test.skip("named slots inside slot, again", async () => {
class Child extends Component {
static template = xml`
<child>
@@ -1641,7 +1641,7 @@ describe("slots", () => {
);
});
test("named slot inside slot, part 3", async () => {
test.skip("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("can render only empty slot", async () => {
test.skip("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("can render node with t-ref and Component in same slot", async () => {
test.skip("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("can use t-call in default-content of t-slot", async () => {
test.skip("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("can use component in default-content of t-slot", async () => {
test.skip("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("slot content has different key from other content -- static slot", async () => {
test.skip("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("slot content has different key from other content -- dynamic slot", async () => {
test.skip("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("mix of slots, t-call, t-call with body, and giving own props child", async () => {
test.skip("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("slot in multiple locations", async () => {
test.skip("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("dynamic slot in multiple locations", async () => {
test.skip("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("slot in t-foreach locations", async () => {
test.skip("slot in t-foreach locations", async () => {
class Child extends Component {
static template = xml`<div>child</div>`;
}
@@ -1934,16 +1934,32 @@ describe("slots", () => {
);
});
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`<Dialog>Before - <t t-out="0"/> - After</Dialog>`;
static components = { Dialog };
test.skip("conditional slot", async () => {
class Child extends Component {
static template = xml`<t t-slot="abc"/>`;
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("Before - default content - After");
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});
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("blue");
parent.state.flag = false;
await nextTick();
expect(fixture.innerHTML).toBe("red");
});
});
+17 -2
View File
@@ -345,14 +345,13 @@ 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>Root</div><div>3</div>"
"<div>outside slot</div><div>I'm the default slot</div><div>3</div>"
);
expect(Object.keys(child.__owl__.refs)).toEqual([]);
expect(Object.keys(root.__owl__.refs)).toEqual(["myRef", "myRef2"]);
@@ -383,4 +382,20 @@ 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,4 +644,35 @@ 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&&this.will.crash;
let txt1 = ctx['props'].error&&ctx['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) {
+35 -6
View File
@@ -47,6 +47,18 @@ 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) {
@@ -104,7 +116,7 @@ async function startRelease() {
// ---------------------------------------------------------------------------
log(`Step 7/${STEPS}: Creating the release...`);
const relaseResult = await execCommand(`gh release create v${next} dist/*.js ${draft} -F ${REL_NOTES_FILE}`);
const relaseResult = await execCommand(`gh release create v${next} dist/*.js ${draft} -F ${file}`);
if (relaseResult !== 0) {
logError("github release failed. Aborting.");
return;
@@ -120,7 +132,7 @@ async function startRelease() {
if (shouldUploadPlayground) {
log(`Bonus step: publishing new release on playground...`);
let owl_code = null;
status = 0
let status = 0
try {
owl_code = await readFile("dist/owl.iife.js");
@@ -130,7 +142,8 @@ async function startRelease() {
return;
}
status += await execCommand("git checkout gh-pages");
status |= await execCommand("git checkout gh-pages");
status |= await execCommand("git pull --rebase");
if (status !== 0) {
logError("Couldn't switch to gh-pages branch")
@@ -144,9 +157,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.")
}
@@ -231,3 +244,19 @@ 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);
}
});
});
}