Compare commits

..

1 Commits

Author SHA1 Message Date
Géry Debongnie 7ad08a6825 [TEST] experiment qith queuemicrotask 2023-01-11 14:52:35 +01:00
37 changed files with 104 additions and 6268 deletions
-47
View File
@@ -1,47 +0,0 @@
{
"env": {
"browser": true,
"node": true,
"es2022": true
},
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"parserOptions": {
"sourceType": "module"
},
"root": true,
"rules": {
"no-restricted-globals": ["error", "event", "self"],
"no-const-assign": ["error"],
"no-debugger": ["error"],
"no-dupe-class-members": ["error"],
"no-dupe-keys": ["error"],
"no-dupe-args": ["error"],
"no-dupe-else-if": ["error"],
"no-unsafe-negation": ["error"],
"no-duplicate-imports": ["error"],
"valid-typeof": ["error"],
"@typescript-eslint/no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": false, "caughtErrors": "all" }],
"no-restricted-syntax": [
"error",
{
"selector": "MemberExpression[object.name='test'][property.name='only']",
"message": "test.only(...) is forbidden",
},
{
"selector": "MemberExpression[object.name='describe'][property.name='only']",
"message": "describe.only(...) is forbidden",
}
],
},
"globals": {
"describe": true,
"expect": true,
"test": true,
"beforeEach": true,
"beforeAll": true,
"afterEach": true,
"afterAll": true,
"jest": true,
},
}
+1 -2
View File
@@ -22,8 +22,7 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm install
- run: npm run test
- run: npm run check-formatting
- run: npm run lint
- run: npm run build
+3
View File
@@ -14,6 +14,9 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
yarn.lock
#ide's
.vscode
.idea
+3 -3
View File
@@ -174,9 +174,9 @@ class Root extends Component {
```
The template contains a [`t-foreach`](../reference/templates.md#loops) loop to iterate
through the tasks. It can find the `tasks` list from the component, since the rendering
context contains the properties of the component. Note that we use the `id` of each task
as a `t-key`, which is very common. There are two css classes: `task-list` and `task`,
through the tasks. It can find the `tasks` list from the component, since the
component is the rendering context. Note that we use the `id` of each task as a
`t-key`, which is very common. There are two css classes: `task-list` and `task`,
that we will use in the next section.
Finally, notice the use of the `t-att-checked` attribute:
+4 -3
View File
@@ -115,7 +115,7 @@ It is useful to explain the various rules that apply on these expressions:
<div><p t-if="console.log(1)">NOT valid</p></div>
```
2. it can use anything in the rendering context (which typically contains the properties of the component):
2. it can use anything in the rendering context (typically, the component):
```xml
<p t-if="user.birthday === today()">Happy bithday!</p>
@@ -541,8 +541,9 @@ This can be used to define variables scoped to a sub template:
```
Note: by default, the rendering context for a sub template is simply the current
rendering context. However, it may be useful to be able to specify a specific
object as context. This can be done by using the `t-call-context` directive:
rendering context (so, the current component). However, it may be useful to be
able to specify a specific object as context. This can be done by using the
`t-call-context` directive:
```xml
<t t-call="other-template" t-call-context="obj"/>
-5711
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@odoo/owl",
"version": "2.0.6",
"version": "2.0.2",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js",
"module": "dist/owl.es.js",
@@ -25,7 +25,6 @@
"playground:watch": "npm-run-all --parallel playground:serve \"build:* -- --watch\"",
"prettier": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --write",
"check-formatting": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --check",
"lint": "eslint src/**/*.ts tests/**/*.ts",
"publish": "npm run build && npm publish",
"release": "node tools/release.js",
"compile_templates": "node tools/compile_xml.js"
@@ -43,25 +42,26 @@
"devDependencies": {
"@types/jest": "^27.0.1",
"@types/node": "^14.11.8",
"@typescript-eslint/eslint-plugin": "5.48.1",
"@typescript-eslint/parser": "5.48.1",
"chalk": "^3.0.0",
"cpx": "^1.5.0",
"current-git-branch": "^1.1.0",
"eslint": "8.31.0",
"git-rev-sync": "^3.0.2",
"git-rev-sync": "^1.12.0",
"github-api": "^3.3.0",
"jest": "^27.1.0",
"jest-diff": "^27.3.1",
"jest-environment-jsdom": "^27.1.0",
"live-server": "^1.2.1",
"npm-run-all": "^4.1.5",
"prettier": "2.4.1",
"rollup": "^2.56.3",
"rollup-plugin-dts": "^4.2.2",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.31.1",
"sass": "^1.16.1",
"source-map-support": "^0.5.10",
"ts-jest": "^27.0.5",
"typescript": "4.5.2"
"typescript": "4.5.2",
"uglify-es": "^3.3.9"
},
"jest": {
"testEnvironment": "jsdom",
+1 -16
View File
@@ -632,15 +632,7 @@ export class CodeGenerator {
let idx: number;
if (specialInitTargetAttr) {
let targetExpr = targetAttr in attrs && `'${attrs[targetAttr]}'`;
if (!targetExpr && ast.attrs) {
// look at the dynamic attribute counterpart
const dynamicTgExpr = ast.attrs[`t-att-${targetAttr}`];
if (dynamicTgExpr) {
targetExpr = compileExpr(dynamicTgExpr);
}
}
idx = block!.insertData(`${fullExpression} === ${targetExpr}`, "attr");
idx = block!.insertData(`${fullExpression} === '${attrs[targetAttr]}'`, "attr");
attrs[`block-attribute-${idx}`] = specialInitTargetAttr;
} else if (hasDynamicChildren) {
const bValueId = generateId("bValue");
@@ -1223,13 +1215,6 @@ export class CodeGenerator {
})`,
});
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})`;
+1 -1
View File
@@ -28,7 +28,7 @@ import { OwlError } from "../runtime/error_handling";
//------------------------------------------------------------------------------
const RESERVED_WORDS =
"true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date".split(
"true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,eval,void,Math,RegExp,Array,Object,Date".split(
","
);
+3 -20
View File
@@ -1,11 +1,12 @@
import { Component, ComponentConstructor, Props } from "./component";
import { ComponentNode } from "./component_node";
import { nodeErrorHandlers, OwlError, handleError } from "./error_handling";
import { Fiber, RootFiber, MountOptions } from "./fibers";
import { nodeErrorHandlers, OwlError } from "./error_handling";
import { Fiber, MountOptions } from "./fibers";
import { Scheduler } from "./scheduler";
import { validateProps } from "./template_helpers";
import { TemplateSet, TemplateSetConfig } from "./template_set";
import { validateTarget } from "./utils";
import { handleError } from "./error_handling";
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
@@ -31,22 +32,6 @@ This is not suitable for production use.
See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`;
};
declare global {
interface Window {
__OWL_DEVTOOLS__: {
apps: Set<App>;
Fiber: typeof Fiber;
RootFiber: typeof RootFiber;
};
}
}
window.__OWL_DEVTOOLS__ ||= {
apps: new Set<App>(),
Fiber: Fiber,
RootFiber: RootFiber,
};
export class App<
T extends abstract new (...args: any) => any = any,
P extends object = any,
@@ -64,7 +49,6 @@ export class App<
constructor(Root: ComponentConstructor<P, E>, config: AppConfig<P, E> = {}) {
super(config);
this.Root = Root;
window.__OWL_DEVTOOLS__.apps.add(this);
if (config.test) {
this.dev = true;
}
@@ -126,7 +110,6 @@ export class App<
this.scheduler.flush();
this.root.destroy();
}
window.__OWL_DEVTOOLS__.apps.delete(this);
}
createComponent<P extends Props>(
+1 -3
View File
@@ -114,8 +114,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
}
}
this.component = new C(props, env, this);
const ctx = Object.assign(Object.create(this.component), { this: this.component });
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
this.component.setup();
currentNode = null;
}
@@ -318,7 +317,6 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
}
_patch() {
let hasChildren = false;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (let _k in this.children) {
hasChildren = true;
break;
+1 -1
View File
@@ -117,7 +117,7 @@ export function useEffect(effect: Effect, computeDependencies: () => any[] = ()
* `useExternalListener(window, 'click', this._doSomething);`
* */
export function useExternalListener(
target: EventTarget,
target: HTMLElement | typeof window,
eventName: string,
handler: EventListener,
eventParams?: AddEventListenerOptions
+7 -23
View File
@@ -1,13 +1,8 @@
import type { Callback } from "./utils";
import { Callback } from "./utils";
import { OwlError } from "./error_handling";
// Special key to subscribe to, to be notified of key creation/deletion
const KEYCHANGES = Symbol("Key changes");
// Used to specify the absence of a callback, can be used as WeakMap key but
// should only be used as a sentinel value and never called.
const NO_CALLBACK = () => {
throw new Error("Called NO_CALLBACK. Owl is broken, please report this to the maintainers.");
};
// The following types only exist to signify places where objects are expected
// to be reactive or not, they provide no type checking benefit over "object"
@@ -91,9 +86,6 @@ const targetToKeysToCallbacks = new WeakMap<Target, Map<PropertyKey, Set<Callbac
* @param callback the function to call when the key changes
*/
function observeTargetKey(target: Target, key: PropertyKey, callback: Callback): void {
if (callback === NO_CALLBACK) {
return;
}
if (!targetToKeysToCallbacks.get(target)) {
targetToKeysToCallbacks.set(target, new Map());
}
@@ -148,11 +140,8 @@ export function clearReactivesForCallback(callback: Callback): void {
if (!observedKeys) {
continue;
}
for (const [key, callbacks] of observedKeys.entries()) {
for (const callbacks of observedKeys.values()) {
callbacks.delete(callback);
if (!callbacks.size) {
observedKeys.delete(key);
}
}
}
targetsToClear.clear();
@@ -162,15 +151,10 @@ export function getSubscriptions(callback: Callback) {
const targets = callbacksToTargets.get(callback) || [];
return [...targets].map((target) => {
const keysToCallbacks = targetToKeysToCallbacks.get(target);
let keys = [];
if (keysToCallbacks) {
for (const [key, cbs] of keysToCallbacks) {
if (cbs.has(callback)) {
keys.push(key);
}
}
}
return { target, keys };
return {
target,
keys: keysToCallbacks ? [...keysToCallbacks.keys()] : [],
};
});
}
// Maps reactive objects to the underlying target
@@ -203,7 +187,7 @@ const reactiveCache = new WeakMap<Target, WeakMap<Callback, Reactive<Target>>>()
* reactive has changed
* @returns a proxy that tracks changes to it
*/
export function reactive<T extends Target>(target: T, callback: Callback = NO_CALLBACK): T {
export function reactive<T extends Target>(target: T, callback: Callback = () => {}): T {
if (!canBeMadeReactive(target)) {
throw new OwlError(`Cannot make the given value reactive`);
}
+13 -14
View File
@@ -10,20 +10,19 @@ export type Callback = () => void;
*/
export function batched(callback: Callback): Callback {
let called = false;
return async () => {
// This await blocks all calls to the callback here, then releases them sequentially
// in the next microtick. This line decides the granularity of the batch.
await Promise.resolve();
if (!called) {
called = true;
// wait for all calls in this microtick to fall through before resetting "called"
// so that only the first call to the batched function calls the original callback.
// Schedule this before calling the callback so that calls to the batched function
// within the callback will proceed only after resetting called to false, and have
// a chance to execute the callback again
Promise.resolve().then(() => (called = false));
callback();
}
return () => {
queueMicrotask(() => {
if (!called) {
called = true;
// wait for all calls in this microtick to fall through before resetting "called"
// so that only the first call to the batched function calls the original callback.
// Schedule this before calling the callback so that calls to the batched function
// within the callback will proceed only after resetting called to false, and have
// a chance to execute the callback again
queueMicrotask(() => (called = false));
callback();
}
});
};
}
-2
View File
@@ -1,5 +1,4 @@
import { OwlError } from "./error_handling";
import { toRaw } from "./reactivity";
type BaseType =
| typeof String
@@ -85,7 +84,6 @@ export function validateSchema(obj: { [key: string]: any }, schema: Schema): str
if (Array.isArray(schema)) {
schema = toSchema(schema);
}
obj = toRaw(obj);
let errors = [];
// check if each value in obj has correct shape
for (let key in obj) {
@@ -519,9 +519,8 @@ exports[`t-on t-on, with arguments and t-call 2`] = `
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['this'];
const v2 = ctx['value'];
let hdlr1 = [()=>v1.update(v2), ctx];
const v1 = ctx['value'];
let hdlr1 = [()=>this.update(v1), ctx];
return block1([hdlr1]);
}
}"
-3
View File
@@ -284,9 +284,6 @@ describe("t-on", () => {
expect(this).toBe(owner);
expect(val).toBe(444);
},
get this() {
return owner;
},
value: 444,
};
@@ -8,7 +8,7 @@ exports[`basics GrandChild display is controlled by its GrandParent 1`] = `
return function template(ctx, node, key = \\"\\") {
const Comp1 = ctx['myComp'];
return toggler(Comp1, comp1({displayGrandChild: ctx['displayGrandChild']}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({displayGrandChild: ctx['displayGrandChild']}, key + \`__1\`, node, this, Comp1));
}
}"
`;
@@ -1213,45 +1213,6 @@ exports[`delayed fiber does not get rendered if it was cancelled 4`] = `
}"
`;
exports[`delayed render does not go through when t-component value changed 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(null, false, false, false, true);
return function template(ctx, node, key = \\"\\") {
const b2 = text(\`A\`);
const Comp1 = ctx['state'].component;
const b3 = toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return multi([b2, b3]);
}
}"
`;
exports[`delayed render does not go through when t-component value changed 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
const b2 = text(\`B\`);
const b3 = text(ctx['state'].val);
return multi([b2, b3]);
}
}"
`;
exports[`delayed render does not go through when t-component value changed 3`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(\`C\`);
}
}"
`;
exports[`delayed rendering, but then initial rendering is cancelled by yet another render 1`] = `
"function anonymous(app, bdom, helpers
) {
@@ -1815,7 +1776,7 @@ exports[`t-foreach with dynamic async component 1`] = `
let b3;
if (ctx['arr']) {
const Comp1 = ctx['myComp'];
b3 = toggler(Comp1, comp1({key: ctx['arr'][0]}, (Comp1).name + key + \`__1__\${key1}\`, node, this, Comp1));
b3 = toggler(Comp1, comp1({key: ctx['arr'][0]}, key + \`__1__\${key1}\`, node, this, Comp1));
}
c_block1[i1] = withKey(multi([b3]), key1);
}
@@ -1849,7 +1810,7 @@ exports[`t-key on dom node having a component 1`] = `
return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['key'];
const Comp1 = ctx['myComp'];
const b2 = toggler(tKey_1, toggler(Comp1, comp1({key: ctx['key']}, (Comp1).name + tKey_1 + key + \`__1\`, node, this, Comp1)));
const b2 = toggler(tKey_1, toggler(Comp1, comp1({key: ctx['key']}, tKey_1 + key + \`__1\`, node, this, Comp1)));
return toggler(tKey_1, block1([], [b2]));
}
}"
@@ -1875,7 +1836,7 @@ exports[`t-key on dynamic async component (toggler is never patched) 1`] = `
return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['key'];
const Comp1 = ctx['myComp'];
return toggler(tKey_1, toggler(Comp1, comp1({key: ctx['key']}, (Comp1).name + tKey_1 + key + \`__1\`, node, this, Comp1)));
return toggler(tKey_1, toggler(Comp1, comp1({key: ctx['key']}, tKey_1 + key + \`__1\`, node, this, Comp1)));
}
}"
`;
@@ -931,7 +931,7 @@ exports[`can catch errors catching error, rethrow, render parent -- a main comp
function slot1(ctx, node, key = \\"\\") {
const Comp1 = ctx['cp'].Comp;
return toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({}, key + \`__1\`, node, this, Comp1));
}
return function template(ctx, node, key = \\"\\") {
@@ -940,10 +940,9 @@ exports[`can catch errors catching error, rethrow, render parent -- a main comp
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`cp\`] = v_block1[i1];
const key1 = ctx['cp'].id;
const v1 = ctx['this'];
const v2 = ctx['cp'];
const v1 = ctx['cp'];
const ctx1 = capture(ctx);
c_block1[i1] = withKey(comp2({onError: ()=>v1.cleanUp(v2.id),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__2__\${key1}\`, node, this, null), key1);
c_block1[i1] = withKey(comp2({onError: ()=>this.cleanUp(v1.id),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__2__\${key1}\`, node, this, null), key1);
}
return list(c_block1);
}
@@ -1010,19 +1009,18 @@ exports[`can catch errors catching in child makes parent render 1`] = `
function slot1(ctx, node, key = \\"\\") {
const Comp1 = ctx['elem'][1];
return toggler(Comp1, comp1({id: ctx['elem'][0]}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({id: ctx['elem'][0]}, key + \`__1\`, node, this, Comp1));
}
return function template(ctx, node, key = \\"\\") {
ctx = Object.create(ctx);
const [k_block1, v_block1, l_block1, c_block1] = prepareList(Object.entries(ctx['this'].elements));;
const [k_block1, v_block1, l_block1, c_block1] = prepareList(Object.entries(this.elements));;
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`elem\`] = v_block1[i1];
const key1 = ctx['elem'][0];
const v1 = ctx['this'];
const v2 = ctx['elem'];
const v1 = ctx['elem'];
const ctx1 = capture(ctx);
c_block1[i1] = withKey(comp2({onError: (_error)=>v1.onError(v2[0],_error),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__2__\${key1}\`, node, this, null), key1);
c_block1[i1] = withKey(comp2({onError: (_error)=>this.onError(v1[0],_error),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__2__\${key1}\`, node, this, null), key1);
}
return list(c_block1);
}
@@ -1157,7 +1155,7 @@ exports[`can catch errors onError in class inheritance is called if rethrown 2`]
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (!ctx['state'].error) {
b2 = text(ctx['this'].will.crash);
b2 = text(this.will.crash);
} else {
b3 = text(ctx['state'].error);
}
@@ -1188,7 +1186,7 @@ exports[`can catch errors onError in class inheritance is not called if no rethr
return function template(ctx, node, key = \\"\\") {
let b2,b3;
if (!ctx['state'].error) {
b2 = text(ctx['this'].will.crash);
b2 = text(this.will.crash);
} else {
b3 = text(ctx['state'].error);
}
@@ -1220,7 +1218,7 @@ exports[`errors and promises a rendering error in a sub component will reject th
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['this'].will.crash;
let txt1 = this.will.crash;
return block1([txt1]);
}
}"
@@ -1234,7 +1232,7 @@ exports[`errors and promises a rendering error will reject the mount promise 1`]
let block1 = createBlock(\`<div><block-text-0/></div>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['this'].will.crash;
let txt1 = this.will.crash;
return block1([txt1]);
}
}"
@@ -1279,7 +1277,7 @@ exports[`errors and promises a rendering error will reject the render promise 1`
return function template(ctx, node, key = \\"\\") {
let b2;
if (ctx['flag']) {
b2 = text(ctx['this'].will.crash);
b2 = text(this.will.crash);
}
return block1([], [b2]);
}
@@ -841,33 +841,6 @@ exports[`props validation props are validated whenever component is updated 2`]
}"
`;
exports[`props validation props validation does not cause additional subscription 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, false);
return function template(ctx, node, key = \\"\\") {
const props1 = {obj: ctx['obj']};
helpers.validateProps(\`Child\`, props1, this);
const b2 = comp1(props1, key + \`__1\`, node, this, null);
const b3 = text(ctx['obj'].otherValue);
return multi([b2, b3]);
}
}"
`;
exports[`props validation props validation does not cause additional subscription 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['props'].obj.value);
}
}"
`;
exports[`props validation props: list of strings 1`] = `
"function anonymous(app, bdom, helpers
) {
@@ -140,39 +140,3 @@ exports[`reactivity in lifecycle state changes in willUnmount do not trigger rer
}
}"
`;
exports[`subscriptions subscriptions returns the keys and targets observed by the component 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['state'].a);
}
}"
`;
exports[`subscriptions subscriptions returns the keys observed by the component 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const comp1 = app.createComponent(\`Child\`, true, false, false, false);
return function template(ctx, node, key = \\"\\") {
const b2 = text(ctx['state'].a);
const b3 = comp1({state: ctx['state']}, key + \`__1\`, node, this, null);
return multi([b2, b3]);
}
}"
`;
exports[`subscriptions subscriptions returns the keys observed by the component 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['props'].state.b);
}
}"
`;
@@ -1992,8 +1992,7 @@ exports[`slots slot content is bound to caller (variation) 1`] = `
ctx = Object.create(ctx);
ctx[isBoundary] = 1
setContextValue(ctx, \\"var\\", 1);
const v1 = ctx['this'];
let hdlr1 = [()=>v1.inc(), ctx];
let hdlr1 = [()=>this.inc(), ctx];
return block1([hdlr1]);
}
@@ -2360,8 +2359,7 @@ exports[`slots slots are properly bound to correct component 2`] = `
ctx = Object.create(ctx);
ctx[isBoundary] = 1
setContextValue(ctx, \\"var\\", 1);
const v1 = ctx['this'];
let hdlr1 = [()=>v1.increment(), ctx];
let hdlr1 = [()=>this.increment(), ctx];
let txt1 = ctx['state'].value;
return block1([hdlr1, txt1]);
}
@@ -363,7 +363,7 @@ exports[`style and class handling error in subcomponent with class 2`] = `
return function template(ctx, node, key = \\"\\") {
let attr1 = ctx['props'].class;
let txt1 = ctx['this'].will.crash;
let txt1 = this.will.crash;
return block1([attr1, txt1]);
}
}"
@@ -109,8 +109,7 @@ exports[`t-call handlers are properly bound through a dynamic t-call 2`] = `
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['this'];
let hdlr1 = [()=>v1.update(), ctx];
let hdlr1 = [()=>this.update(), ctx];
return block1([hdlr1]);
}
}"
@@ -169,9 +168,8 @@ exports[`t-call handlers with arguments are properly bound through a t-call 2`]
let block1 = createBlock(\`<p block-handler-0=\\"click\\">lucas</p>\`);
return function template(ctx, node, key = \\"\\") {
const v1 = ctx['this'];
const v2 = ctx['a'];
let hdlr1 = [()=>v1.update(v2), ctx];
const v1 = ctx['a'];
let hdlr1 = [()=>this.update(v1), ctx];
return block1([hdlr1]);
}
}"
@@ -289,7 +287,7 @@ exports[`t-call recursive t-call binding this -- static t-call 2`] = `
ctx[isBoundary] = 1
let b2;
if (ctx['level']<2) {
let hdlr1 = [\\"stop\\", ctx['onClicked'].bind(ctx['this']), ctx];
let hdlr1 = [\\"stop\\", ctx['onClicked'].bind(this), ctx];
let txt1 = ctx['level'];
const b3 = block3([hdlr1, txt1]);
ctx = Object.create(ctx);
@@ -540,6 +538,7 @@ exports[`t-call t-call-context: ComponentNode is not looked up in the context 2`
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;
@@ -548,9 +547,11 @@ exports[`t-call t-call-context: ComponentNode is not looked up in the context 2`
ctx[isBoundary] = 1
const b4 = block4([ref2]);
setContextValue(ctx, \\"test\\", 3);
let txt1 = ctx['test'];
let txt1 = this.__owl__.name;
const b5 = block5([txt1]);
return multi([b4, b5]);
let txt2 = ctx['test'];
const b6 = block6([txt2]);
return multi([b4, b5, b6]);
}
return function template(ctx, node, key = \\"\\") {
@@ -558,8 +559,8 @@ exports[`t-call t-call-context: ComponentNode is not looked up in the context 2`
const ref1 = (el) => refs[\`myRef\`] = el;
const b2 = block2([ref1]);
const ctx1 = capture(ctx);
const b6 = comp1({prop: bind(this, ctx['method']),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return multi([b2, b6]);
const b7 = comp1({prop: bind(this, ctx['method']),slots: markRaw({'default': {__render: slot1.bind(this), __ctx: ctx1}})}, key + \`__1\`, node, this, null);
return multi([b2, b7]);
}
}"
`;
@@ -623,27 +624,3 @@ exports[`t-call t-call-context: slots don't make component available again when
}
}"
`;
exports[`t-call t-call-context: this is not available inside t-call-context 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
const callTemplate_1 = app.getTemplate(\`someTemplate\`);
return function template(ctx, node, key = \\"\\") {
let ctx1 = {};
return callTemplate_1.call(this, ctx1, node, key + \`__1\`);
}
}"
`;
exports[`t-call t-call-context: this is not available inside t-call-context 2`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
return text(ctx['this']);
}
}"
`;
@@ -10,7 +10,7 @@ exports[`t-component can switch between dynamic components without the need for
return function template(ctx, node, key = \\"\\") {
const Comp1 = ctx['constructor'].components[ctx['state'].child];
const b2 = toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
const b2 = toggler(Comp1, comp1({}, key + \`__1\`, node, this, Comp1));
return block1([], [b2]);
}
}"
@@ -51,7 +51,7 @@ exports[`t-component can use dynamic components (the class) if given (with diffe
return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['state'].child;
const Comp1 = ctx['myComponent'];
return toggler(tKey_1, toggler(Comp1, comp1({}, (Comp1).name + tKey_1 + key + \`__1\`, node, this, Comp1)));
return toggler(tKey_1, toggler(Comp1, comp1({}, tKey_1 + key + \`__1\`, node, this, Comp1)));
}
}"
`;
@@ -91,7 +91,7 @@ exports[`t-component can use dynamic components (the class) if given 1`] = `
return function template(ctx, node, key = \\"\\") {
const tKey_1 = ctx['state'].child;
const Comp1 = ctx['myComponent'];
return toggler(tKey_1, toggler(Comp1, comp1({}, (Comp1).name + tKey_1 + key + \`__1\`, node, this, Comp1)));
return toggler(tKey_1, toggler(Comp1, comp1({}, tKey_1 + key + \`__1\`, node, this, Comp1)));
}
}"
`;
@@ -132,7 +132,7 @@ exports[`t-component modifying a sub widget 1`] = `
return function template(ctx, node, key = \\"\\") {
const Comp1 = ctx['Counter'];
const b2 = toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
const b2 = toggler(Comp1, comp1({}, key + \`__1\`, node, this, Comp1));
return block1([], [b2]);
}
}"
@@ -162,7 +162,7 @@ exports[`t-component switching dynamic component 1`] = `
return function template(ctx, node, key = \\"\\") {
const Comp1 = ctx['Child'];
return toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({}, key + \`__1\`, node, this, Comp1));
}
}"
`;
@@ -199,7 +199,7 @@ exports[`t-component t-component works in simple case 1`] = `
return function template(ctx, node, key = \\"\\") {
const Comp1 = ctx['Child'];
return toggler(Comp1, comp1({}, (Comp1).name + key + \`__1\`, node, this, Comp1));
return toggler(Comp1, comp1({}, key + \`__1\`, node, this, Comp1));
}
}"
`;
@@ -569,36 +569,6 @@ exports[`t-model directive t-model with dynamic values on select options in fore
}"
`;
exports[`t-model directive t-model with radio button group in t-foreach 1`] = `
"function anonymous(app, bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
let { prepareList, toNumber, withKey } = helpers;
let block1 = createBlock(\`<div id=\\"get_data\\" block-handler-0=\\"click\\"><block-child-0/></div>\`);
let block3 = createBlock(\`<input type=\\"radio\\" name=\\"radio_group\\" block-attribute-0=\\"value\\" block-attribute-1=\\"id\\" block-attribute-2=\\"checked\\" block-handler-3=\\"click\\"/>\`);
return function template(ctx, node, key = \\"\\") {
let hdlr1 = [ctx['getData'], ctx];
ctx = Object.create(ctx);
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['options']);;
for (let i1 = 0; i1 < l_block2; i1++) {
ctx[\`opt\`] = v_block2[i1];
const key1 = ctx['opt'];
let attr1 = new String((ctx['opt']) || \\"\\");
let attr2 = ctx['opt'];
const bExpr1 = ctx['state'];
const expr1 = 'group';
let attr3 = bExpr1[expr1] === ctx['opt'];
let hdlr2 = [(ev) => { bExpr1[expr1] = ev.target.value; }];
c_block2[i1] = withKey(block3([attr1, attr2, attr3, hdlr2]), key1);
}
const b2 = list(c_block2);
return block1([hdlr1], [b2]);
}
}"
`;
exports[`t-model directive two inputs in a div alternating with a t-if 1`] = `
"function anonymous(app, bdom, helpers
) {
@@ -111,9 +111,8 @@ exports[`t-on t-on method call in t-foreach 1`] = `
const key1 = ctx['val'];
let txt1 = ctx['val_index'];
let txt2 = ctx['val']+'';
const v1 = ctx['this'];
const v2 = ctx['val'];
let hdlr1 = [()=>v1.addVal(v2), ctx];
const v1 = ctx['val'];
let hdlr1 = [()=>this.addVal(v1), ctx];
c_block2[i1] = withKey(block3([txt1, txt2, hdlr1]), key1);
}
const b2 = list(c_block2);
@@ -198,9 +197,8 @@ exports[`t-on t-on on components and t-foreach 1`] = `
for (let i1 = 0; i1 < l_block1; i1++) {
ctx[\`name\`] = v_block1[i1];
const key1 = ctx['name'];
const v1 = ctx['this'];
const v2 = ctx['name'];
const hdlr1 = [()=>v1.log(v2), ctx];
const v1 = ctx['name'];
const hdlr1 = [()=>this.log(v1), ctx];
c_block1[i1] = withKey(catcher1(comp1({value: ctx['name']}, key + \`__1__\${key1}\`, node, this, null), [hdlr1]), key1);
}
return list(c_block1);
@@ -295,9 +293,8 @@ exports[`t-on t-on on components, with a handler update 1`] = `
ctx = Object.create(ctx);
ctx[isBoundary] = 1
setContextValue(ctx, \\"name\\", ctx['state'].name);
const v1 = ctx['this'];
const v2 = ctx['name'];
const hdlr1 = [()=>v1.log(v2), ctx];
const v1 = ctx['name'];
const hdlr1 = [()=>this.log(v1), ctx];
return catcher1(comp1({value: ctx['name']}, key + \`__1\`, node, this, null), [hdlr1]);
}
}"
@@ -396,8 +393,7 @@ exports[`t-on t-on on t-set-slots 1`] = `
function slot1(ctx, node, key = \\"\\") {
const b6 = block6();
const b7 = block7();
const v1 = ctx['this'];
const hdlr1 = [()=>v1.state.count++, ctx];
const hdlr1 = [()=>this.state.count++, ctx];
return catcher1(multi([b6, b7]), [hdlr1]);
}
@@ -454,8 +450,7 @@ exports[`t-on t-on on t-slots 2`] = `
const b2 = text(\` [\`);
const b3 = text(ctx['state'].count);
const b4 = text(\`] \`);
const v1 = ctx['this'];
const hdlr1 = [()=>v1.state.count++, ctx];
const hdlr1 = [()=>this.state.count++, ctx];
const b5 = catcher1(callSlot(ctx, node, key, 'default', false, {}), [hdlr1]);
return multi([b2, b3, b4, b5]);
}
-59
View File
@@ -1,7 +1,6 @@
import {
App,
Component,
ComponentConstructor,
mount,
onMounted,
onRendered,
@@ -4068,64 +4067,6 @@ test("renderings, destruction, patch, stuff, ... yet another variation", async (
expect(fixture.innerHTML).toBe("ABD<p>2</p>");
});
test("delayed render does not go through when t-component value changed", async () => {
class C extends Component {
static template = xml`C`;
setup() {
useLogLifecycle("", true);
}
}
class B extends Component {
static template = xml`B<t t-esc="state.val"/>`;
state = useState({ val: 1 });
setup() {
useLogLifecycle("", true);
b = this;
}
}
let b: B;
class A extends Component {
static template = xml`A<t t-component="state.component"/>`;
state: { component: ComponentConstructor } = useState({ component: B });
setup() {
useLogLifecycle("", true);
}
}
const a = await mount(A, fixture);
expect(fixture.innerHTML).toBe("AB1");
expect([
"A:setup",
"A:willRender",
"B:setup",
"A:rendered",
"B:willRender",
"B:rendered",
"B:mounted",
"A:mounted",
]).toBeLogged();
// start a render in B
b!.state.val = 2;
// start a render in A, invalidating the scheduled render of B, which could crash if executed.
a.state.component = C;
await nextTick();
expect(fixture.innerHTML).toBe("AC");
expect([
"A:willRender",
"C:setup",
"A:rendered",
"C:willRender",
"C:rendered",
"A:willPatch",
"B:willUnmount",
"B:willDestroy",
"C:mounted",
"A:patched",
]).toBeLogged();
});
// test.skip("components with shouldUpdate=false", async () => {
// const state = { p: 1, cc: 10 };
+1 -24
View File
@@ -1,5 +1,5 @@
import { makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
import { Component, onError, xml, mount, OwlError, useState } from "../../src";
import { Component, onError, xml, mount, OwlError } from "../../src";
import { App, DEV_MSG } from "../../src/runtime/app";
import { validateProps } from "../../src/runtime/template_helpers";
import { Schema } from "../../src/runtime/validation";
@@ -682,29 +682,6 @@ describe("props validation", () => {
expect(error!.message).toBe("Invalid props for component 'SubComp': 'p' is missing");
});
test("props validation does not cause additional subscription", async () => {
let obj = {
value: 1,
otherValue: 2,
};
class Child extends Component {
static props = {
obj: { type: Object, shape: { value: Number, otherValue: Number } },
};
static template = xml`<t t-esc="props.obj.value"/>`;
}
class Parent extends Component {
static template = xml`<Child obj="obj"/><t t-esc="obj.otherValue"/>`;
static components = { Child };
obj = useState(obj);
}
const app = new App(Parent, { test: true });
await app.mount(fixture);
expect(fixture.innerHTML).toBe("12");
expect(app.root!.subscriptions).toEqual([{ keys: ["otherValue"], target: obj }]);
});
test("props are validated whenever component is updated", async () => {
let error: Error;
class SubComp extends Component {
-32
View File
@@ -7,7 +7,6 @@ import {
onWillUnmount,
useState,
xml,
toRaw,
} from "../../src";
import { makeTestFixture, nextTick, snapshotEverything, useLogLifecycle } from "../helpers";
@@ -233,34 +232,3 @@ describe("reactivity in lifecycle", () => {
expect(fixture.innerHTML).toBe("34");
});
});
describe("subscriptions", () => {
test("subscriptions returns the keys and targets observed by the component", async () => {
class Comp extends Component {
static template = xml`<t t-esc="state.a"/>`;
state = useState({ a: 1, b: 2 });
}
const comp = await mount(Comp, fixture);
expect(fixture.innerHTML).toBe("1");
expect(comp.__owl__.subscriptions).toEqual([{ keys: ["a"], target: toRaw(comp.state) }]);
});
test("subscriptions returns the keys observed by the component", async () => {
class Child extends Component {
static template = xml`<t t-esc="props.state.b"/>`;
setup() {
child = this;
}
}
let child: Child;
class Parent extends Component {
static template = xml`<t t-esc="state.a"/><Child state="state"/>`;
static components = { Child };
state = useState({ a: 1, b: 2 });
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("12");
expect(parent.__owl__.subscriptions).toEqual([{ keys: ["a"], target: toRaw(parent.state) }]);
expect(child!.__owl__.subscriptions).toEqual([{ keys: ["b"], target: toRaw(parent.state) }]);
});
});
+2 -1
View File
@@ -1,5 +1,6 @@
import { App, Component, mount, onMounted, useRef, useState, xml } from "../../src/index";
import { App, Component, mount, onMounted, useRef, useState } from "../../src/index";
import { logStep, makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
import { xml } from "../../src/index";
snapshotEverything();
let fixture: HTMLElement;
+2 -17
View File
@@ -345,13 +345,14 @@ describe("t-call", () => {
<Child prop.bind="method">
<div t-ref="myRef2">I'm the default slot</div>
<t t-set="test" t-value="3"/>
<div t-esc="this.__owl__.name"/>
<div t-esc="test"/>
</Child>
</t>
</templates>`,
});
expect(fixture.innerHTML).toBe(
"<div>outside slot</div><div>I'm the default slot</div><div>3</div>"
"<div>outside slot</div><div>I'm the default slot</div><div>Root</div><div>3</div>"
);
expect(Object.keys(child.__owl__.refs)).toEqual([]);
expect(Object.keys(root.__owl__.refs)).toEqual(["myRef", "myRef2"]);
@@ -382,20 +383,4 @@ describe("t-call", () => {
});
expect(fixture.innerHTML).toBe("");
});
test("t-call-context: this is not available inside t-call-context", async () => {
class Root extends Component {
static template = xml`<t t-call="someTemplate" t-call-context="{}"/>`;
}
await mount(Root, fixture, {
templates: `
<templates>
<t t-name="someTemplate">
<t t-esc="this"/>
</t>
</templates>`,
});
expect(fixture.innerHTML).toBe("");
});
});
-31
View File
@@ -644,35 +644,4 @@ describe("t-model directive", () => {
const input = fixture.querySelector("input")!;
await editInput(input, "Beam me up, Scotty");
});
test("t-model with radio button group in t-foreach", async () => {
expect.assertions(6);
const steps: string[] = [];
class SomeComponent extends Component {
static template = xml`
<div t-on-click="getData" id="get_data">
<t t-foreach="options" t-as="opt" t-key="opt">
<input type="radio" name="radio_group" t-model="state.group" t-att-value="opt" t-att-id="opt"/>
</t>
</div>
`;
state = useState({ group: "scotty" });
options = ["beam", "scotty"];
getData() {
steps.push(`group: ${this.state.group}`);
}
}
await mount(SomeComponent, fixture);
const divEl = fixture.querySelector("#get_data") as HTMLElement;
expect(fixture.querySelector("input:checked")!.getAttribute("id")).toBe("scotty");
divEl.click();
expect(steps).toEqual(["group: scotty"]);
fixture.querySelector("input")!.click();
expect(steps).toEqual(["group: scotty", "group: beam"]);
await nextTick();
expect(fixture.querySelector("input:checked")!.getAttribute("id")).toBe("beam");
divEl.click();
expect(steps).toEqual(["group: scotty", "group: beam", "group: beam"]);
});
});
+1 -1
View File
@@ -646,7 +646,7 @@ exports[`Portal portal destroys on crash 2`] = `
let block1 = createBlock(\`<span><block-text-0/></span>\`);
return function template(ctx, node, key = \\"\\") {
let txt1 = ctx['props'].error&&ctx['this'].will.crash;
let txt1 = ctx['props'].error&&this.will.crash;
return block1([txt1]);
}
}"
+1 -1
View File
@@ -48,7 +48,7 @@ function writeToFile(filepath, data) {
}
// adapted from https://medium.com/@mhagemann/the-ultimate-way-to-slugify-a-url-string-in-javascript-b8e4a0d849e1
const a = "·-_,:;";
const a = "·_,:;";
const p = new RegExp(a.split("").join("|"), "g");
function slugify(str) {
+6 -35
View File
@@ -47,18 +47,6 @@ async function startRelease() {
let file = await ask(`Release notes (${REL_NOTES_FILE}): `);
file = file || REL_NOTES_FILE;
let content;
if (!fs.existsSync(`./${file}`)) {
let lastRelease = await getOutput("git log --grep='\\[REL\\]' -n 1 --pretty=%H");
const commitsSinceLastRelease = await getOutput(`git log ${lastRelease.trim()}..HEAD --pretty=%s`);
const commitsAsMdList = commitsSinceLastRelease.trim().split("\n").map(l => " - " + l).join("\n");
log(`${file} did not exist, created a template containing all commits since last release.`)
fs.writeFileSync(file, `# v${next}\n\n${commitsAsMdList}`);
const shouldContinue = await ask(`Check that the contents of ${file} is correct, then press y to continue: `);
if (shouldContinue.toLowerCase() !== "y") {
log("aborted");
return;
}
}
try {
content = await readFile("./" + file);
} catch (e) {
@@ -116,7 +104,7 @@ async function startRelease() {
// ---------------------------------------------------------------------------
log(`Step 7/${STEPS}: Creating the release...`);
const relaseResult = await execCommand(`gh release create v${next} dist/*.js ${draft} -F ${file}`);
const relaseResult = await execCommand(`gh release create v${next} dist/*.js ${draft} -F ${REL_NOTES_FILE}`);
if (relaseResult !== 0) {
logError("github release failed. Aborting.");
return;
@@ -132,7 +120,7 @@ async function startRelease() {
if (shouldUploadPlayground) {
log(`Bonus step: publishing new release on playground...`);
let owl_code = null;
let status = 0
status = 0
try {
owl_code = await readFile("dist/owl.iife.js");
@@ -142,8 +130,7 @@ async function startRelease() {
return;
}
status |= await execCommand("git checkout gh-pages");
status |= await execCommand("git pull --rebase");
status += await execCommand("git checkout gh-pages");
if (status !== 0) {
logError("Couldn't switch to gh-pages branch")
@@ -157,9 +144,9 @@ async function startRelease() {
return;
}
status |= await execCommand(`git commit -am "[IMP] update owl to v${next}"`);
status |= await execCommand(`git push origin gh-pages`);
status |= await execCommand("git checkout -");
status += await execCommand(`git commit -am "[IMP] update owl to v${next}"`);
status += await execCommand(`git push origin gh-pages`);
status += await execCommand("git checkout -");
if (status !== 0) {
logError("Something went wrong for the playground update.")
}
@@ -244,19 +231,3 @@ async function replaceInFile(file, from, to) {
});
});
}
async function getOutput(command) {
return new Promise((resolve, reject) => {
const childProcess = exec(command, (err, stdout, stderr) => {
if (err) {
reject(err);
}
resolve(stdout);
});
childProcess.on("exit", code => {
if (code !== 0) {
reject(code);
}
});
});
}