mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b15327309c |
@@ -1,5 +1,7 @@
|
||||
/node_modules
|
||||
/dist
|
||||
# compiled devtools files
|
||||
/tools/devtools/assets/*.js
|
||||
|
||||
npm-debug.log
|
||||
|
||||
|
||||
@@ -451,6 +451,5 @@ console.log(status(component));
|
||||
// logs either:
|
||||
// - 'new', if the component is new and has not been mounted yet
|
||||
// - 'mounted', if the component is currently mounted
|
||||
// - 'cancelled', if the component has not been mounted yet but will be destroyed soon
|
||||
// - 'destroyed' if the component is currently destroyed
|
||||
```
|
||||
|
||||
@@ -376,16 +376,15 @@ An important difference should be made with the usual `QWeb` behaviour: Owl
|
||||
requires the presence of a `t-key` directive, to be able to properly reconcile
|
||||
renderings.
|
||||
|
||||
`t-foreach` can iterate on any iterable, and also has special support for objects
|
||||
and maps, it will expose the key of the current iteration as the contents of the
|
||||
`t-as`, and the corresponding value with the same name and the suffix `_value`.
|
||||
`t-foreach` can iterate on an array (the current item will be the current value)
|
||||
or an object (the current item will be the current key).
|
||||
|
||||
In addition to the name passed via t-as, `t-foreach` provides a few other useful
|
||||
variables (note: `$as` will be replaced with the name passed to `t-as`):
|
||||
In addition to the name passed via t-as, `t-foreach` provides a few other
|
||||
variables for various data points (note: `$as` will be replaced with the name
|
||||
passed to `t-as`):
|
||||
|
||||
- `$as_value`: the current iteration value, identical to `$as` for arrays and
|
||||
other iterables, but for objects and maps, it provides the value (where `$as`
|
||||
provides the key)
|
||||
- `$as_value`: the current iteration value, identical to `$as` for lists and
|
||||
integers, but for objects, it provides the value (where `$as` provides the key)
|
||||
- `$as_index`: the current iteration index (the first item of the iteration has index 0)
|
||||
- `$as_first`: whether the current item is the first of the iteration
|
||||
(equivalent to `$as_index == 0`)
|
||||
|
||||
+64
-129
@@ -122,16 +122,14 @@ function handleError(params) {
|
||||
}
|
||||
const node = "node" in params ? params.node : params.fiber.node;
|
||||
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
||||
if (fiber) {
|
||||
// resets the fibers on components if possible. This is important so that
|
||||
// new renderings can be properly included in the initial one, if any.
|
||||
let current = fiber;
|
||||
do {
|
||||
current.node.fiber = current;
|
||||
current = current.parent;
|
||||
} while (current);
|
||||
fibersInError.set(fiber.root, error);
|
||||
}
|
||||
// resets the fibers on components if possible. This is important so that
|
||||
// new renderings can be properly included in the initial one, if any.
|
||||
let current = fiber;
|
||||
do {
|
||||
current.node.fiber = current;
|
||||
current = current.parent;
|
||||
} while (current);
|
||||
fibersInError.set(fiber.root, error);
|
||||
const handled = _handleError(node, error);
|
||||
if (!handled) {
|
||||
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
||||
@@ -177,21 +175,11 @@ function createAttrUpdater(attr) {
|
||||
}
|
||||
function attrsSetter(attrs) {
|
||||
if (isArray(attrs)) {
|
||||
if (attrs[0] === "class") {
|
||||
setClass.call(this, attrs[1]);
|
||||
}
|
||||
else {
|
||||
setAttribute.call(this, attrs[0], attrs[1]);
|
||||
}
|
||||
setAttribute.call(this, attrs[0], attrs[1]);
|
||||
}
|
||||
else {
|
||||
for (let k in attrs) {
|
||||
if (k === "class") {
|
||||
setClass.call(this, attrs[k]);
|
||||
}
|
||||
else {
|
||||
setAttribute.call(this, k, attrs[k]);
|
||||
}
|
||||
setAttribute.call(this, k, attrs[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,12 +191,7 @@ function attrsUpdater(attrs, oldAttrs) {
|
||||
if (val === oldAttrs[1]) {
|
||||
return;
|
||||
}
|
||||
if (name === "class") {
|
||||
updateClass.call(this, val, oldAttrs[1]);
|
||||
}
|
||||
else {
|
||||
setAttribute.call(this, name, val);
|
||||
}
|
||||
setAttribute.call(this, name, val);
|
||||
}
|
||||
else {
|
||||
removeAttribute.call(this, oldAttrs[0]);
|
||||
@@ -218,23 +201,13 @@ function attrsUpdater(attrs, oldAttrs) {
|
||||
else {
|
||||
for (let k in oldAttrs) {
|
||||
if (!(k in attrs)) {
|
||||
if (k === "class") {
|
||||
updateClass.call(this, "", oldAttrs[k]);
|
||||
}
|
||||
else {
|
||||
removeAttribute.call(this, k);
|
||||
}
|
||||
removeAttribute.call(this, k);
|
||||
}
|
||||
}
|
||||
for (let k in attrs) {
|
||||
const val = attrs[k];
|
||||
if (val !== oldAttrs[k]) {
|
||||
if (k === "class") {
|
||||
updateClass.call(this, val, oldAttrs[k]);
|
||||
}
|
||||
else {
|
||||
setAttribute.call(this, k, val);
|
||||
}
|
||||
setAttribute.call(this, k, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,13 +286,20 @@ function updateClass(val, oldVal) {
|
||||
* @returns a batched version of the original callback
|
||||
*/
|
||||
function batched(callback) {
|
||||
let scheduled = false;
|
||||
return async (...args) => {
|
||||
if (!scheduled) {
|
||||
scheduled = true;
|
||||
await Promise.resolve();
|
||||
scheduled = false;
|
||||
callback(...args);
|
||||
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();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1652,7 +1632,8 @@ function cancelFibers(fibers) {
|
||||
let node = fiber.node;
|
||||
fiber.render = throwOnRender;
|
||||
if (node.status === 0 /* NEW */) {
|
||||
node.cancel();
|
||||
node.destroy();
|
||||
delete node.parent.children[node.parentKey];
|
||||
}
|
||||
node.fiber = null;
|
||||
if (fiber.bdom) {
|
||||
@@ -2379,9 +2360,6 @@ class ComponentNode {
|
||||
}
|
||||
}
|
||||
async render(deep) {
|
||||
if (this.status >= 2 /* CANCELLED */) {
|
||||
return;
|
||||
}
|
||||
let current = this.fiber;
|
||||
if (current && (current.root.locked || current.bdom === true)) {
|
||||
await Promise.resolve();
|
||||
@@ -2407,7 +2385,7 @@ class ComponentNode {
|
||||
this.fiber = fiber;
|
||||
this.app.scheduler.addFiber(fiber);
|
||||
await Promise.resolve();
|
||||
if (this.status >= 2 /* CANCELLED */) {
|
||||
if (this.status === 2 /* DESTROYED */) {
|
||||
return;
|
||||
}
|
||||
// We only want to actually render the component if the following two
|
||||
@@ -2425,18 +2403,6 @@ class ComponentNode {
|
||||
fiber.render();
|
||||
}
|
||||
}
|
||||
cancel() {
|
||||
this._cancel();
|
||||
delete this.parent.children[this.parentKey];
|
||||
this.app.scheduler.scheduleDestroy(this);
|
||||
}
|
||||
_cancel() {
|
||||
this.status = 2 /* CANCELLED */;
|
||||
const children = this.children;
|
||||
for (let childKey in children) {
|
||||
children[childKey]._cancel();
|
||||
}
|
||||
}
|
||||
destroy() {
|
||||
let shouldRemove = this.status === 1 /* MOUNTED */;
|
||||
this._destroy();
|
||||
@@ -2464,7 +2430,7 @@ class ComponentNode {
|
||||
this.app.handleError({ error: e, node: this });
|
||||
}
|
||||
}
|
||||
this.status = 3 /* DESTROYED */;
|
||||
this.status = 2 /* DESTROYED */;
|
||||
}
|
||||
async updateAndRender(props, parentFiber) {
|
||||
this.nextProps = props;
|
||||
@@ -2997,22 +2963,12 @@ function prepareList(collection) {
|
||||
keys = collection;
|
||||
values = collection;
|
||||
}
|
||||
else if (collection instanceof Map) {
|
||||
keys = [...collection.keys()];
|
||||
values = [...collection.values()];
|
||||
}
|
||||
else if (collection && typeof collection === "object") {
|
||||
if (Symbol.iterator in collection) {
|
||||
keys = [...collection];
|
||||
values = keys;
|
||||
}
|
||||
else {
|
||||
values = Object.keys(collection);
|
||||
keys = Object.values(collection);
|
||||
}
|
||||
else if (collection) {
|
||||
values = Object.keys(collection);
|
||||
keys = Object.values(collection);
|
||||
}
|
||||
else {
|
||||
throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
|
||||
throw new OwlError("Invalid loop expression");
|
||||
}
|
||||
const n = values.length;
|
||||
return [keys, values, n, new Array(n)];
|
||||
@@ -3919,10 +3875,6 @@ class CodeGenerator {
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
translate(str) {
|
||||
const match = translationRE.exec(str);
|
||||
return match[1] + this.translateFn(match[2]) + match[3];
|
||||
}
|
||||
/**
|
||||
* @returns the newly created block name, if any
|
||||
*/
|
||||
@@ -4000,7 +3952,8 @@ class CodeGenerator {
|
||||
let { block, forceNewBlock } = ctx;
|
||||
let value = ast.value;
|
||||
if (value && ctx.translate !== false) {
|
||||
value = this.translate(value);
|
||||
const match = translationRE.exec(value);
|
||||
value = match[1] + this.translateFn(match[2]) + match[3];
|
||||
}
|
||||
if (!ctx.inPreTag) {
|
||||
value = value.replace(whitespaceRE, " ");
|
||||
@@ -4541,12 +4494,11 @@ class CodeGenerator {
|
||||
else {
|
||||
let value;
|
||||
if (ast.defaultValue) {
|
||||
const defaultValue = ctx.translate ? this.translate(ast.defaultValue) : ast.defaultValue;
|
||||
if (ast.value) {
|
||||
value = `withDefault(${expr}, \`${defaultValue}\`)`;
|
||||
value = `withDefault(${expr}, \`${ast.defaultValue}\`)`;
|
||||
}
|
||||
else {
|
||||
value = `\`${defaultValue}\``;
|
||||
value = `\`${ast.defaultValue}\``;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -4927,10 +4879,10 @@ function parseDOMNode(node, ctx) {
|
||||
let model = null;
|
||||
for (let attr of nodeAttrsNames) {
|
||||
const value = node.getAttribute(attr);
|
||||
if (attr === "t-on" || attr === "t-on-") {
|
||||
throw new OwlError("Missing event name with t-on directive");
|
||||
}
|
||||
if (attr.startsWith("t-on-")) {
|
||||
if (attr.startsWith("t-on")) {
|
||||
if (attr === "t-on") {
|
||||
throw new OwlError("Missing event name with t-on directive");
|
||||
}
|
||||
on = on || {};
|
||||
on[attr.slice(5)] = value;
|
||||
}
|
||||
@@ -4955,8 +4907,10 @@ function parseDOMNode(node, ctx) {
|
||||
const typeAttr = node.getAttribute("type");
|
||||
const isInput = tagName === "input";
|
||||
const isSelect = tagName === "select";
|
||||
const isTextarea = tagName === "textarea";
|
||||
const isCheckboxInput = isInput && typeAttr === "checkbox";
|
||||
const isRadioInput = isInput && typeAttr === "radio";
|
||||
const isOtherInput = isInput && !isCheckboxInput && !isRadioInput;
|
||||
const hasLazyMod = attr.includes(".lazy");
|
||||
const hasNumberMod = attr.includes(".number");
|
||||
const hasTrimMod = attr.includes(".trim");
|
||||
@@ -4968,8 +4922,8 @@ function parseDOMNode(node, ctx) {
|
||||
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
||||
eventType,
|
||||
hasDynamicChildren: false,
|
||||
shouldTrim: hasTrimMod,
|
||||
shouldNumberize: hasNumberMod,
|
||||
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
||||
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
||||
};
|
||||
if (isSelect) {
|
||||
// don't pollute the original ctx
|
||||
@@ -5552,7 +5506,7 @@ function compile(template, options = {}) {
|
||||
}
|
||||
|
||||
// do not modify manually. This file is generated by the release script.
|
||||
const version = "2.1.4";
|
||||
const version = "2.1.2";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Scheduler
|
||||
@@ -5562,18 +5516,11 @@ class Scheduler {
|
||||
this.tasks = new Set();
|
||||
this.frame = 0;
|
||||
this.delayedRenders = [];
|
||||
this.cancelledNodes = new Set();
|
||||
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
||||
}
|
||||
addFiber(fiber) {
|
||||
this.tasks.add(fiber.root);
|
||||
}
|
||||
scheduleDestroy(node) {
|
||||
this.cancelledNodes.add(node);
|
||||
if (this.frame === 0) {
|
||||
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Process all current tasks. This only applies to the fibers that are ready.
|
||||
* Other tasks are left unchanged.
|
||||
@@ -5583,28 +5530,21 @@ class Scheduler {
|
||||
let renders = this.delayedRenders;
|
||||
this.delayedRenders = [];
|
||||
for (let f of renders) {
|
||||
if (f.root && f.node.status !== 3 /* DESTROYED */ && f.node.fiber === f) {
|
||||
if (f.root && f.node.status !== 2 /* DESTROYED */ && f.node.fiber === f) {
|
||||
f.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.frame === 0) {
|
||||
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
||||
}
|
||||
}
|
||||
processTasks() {
|
||||
this.frame = 0;
|
||||
for (let node of this.cancelledNodes) {
|
||||
node._destroy();
|
||||
}
|
||||
this.cancelledNodes.clear();
|
||||
for (let task of this.tasks) {
|
||||
this.processFiber(task);
|
||||
}
|
||||
for (let task of this.tasks) {
|
||||
if (task.node.status === 3 /* DESTROYED */) {
|
||||
this.tasks.delete(task);
|
||||
}
|
||||
this.frame = this.requestAnimationFrame(() => {
|
||||
this.frame = 0;
|
||||
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
||||
for (let task of this.tasks) {
|
||||
if (task.node.status === 2 /* DESTROYED */) {
|
||||
this.tasks.delete(task);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
processFiber(fiber) {
|
||||
@@ -5617,7 +5557,7 @@ class Scheduler {
|
||||
this.tasks.delete(fiber);
|
||||
return;
|
||||
}
|
||||
if (fiber.node.status === 3 /* DESTROYED */) {
|
||||
if (fiber.node.status === 2 /* DESTROYED */) {
|
||||
this.tasks.delete(fiber);
|
||||
return;
|
||||
}
|
||||
@@ -5645,8 +5585,6 @@ window.__OWL_DEVTOOLS__ || (window.__OWL_DEVTOOLS__ = {
|
||||
apps: new Set(),
|
||||
Fiber: Fiber,
|
||||
RootFiber: RootFiber,
|
||||
toRaw: toRaw,
|
||||
reactive: reactive,
|
||||
});
|
||||
class App extends TemplateSet {
|
||||
constructor(Root, config = {}) {
|
||||
@@ -5841,11 +5779,9 @@ function status(component) {
|
||||
switch (component.__owl__.status) {
|
||||
case 0 /* NEW */:
|
||||
return "new";
|
||||
case 2 /* CANCELLED */:
|
||||
return "cancelled";
|
||||
case 1 /* MOUNTED */:
|
||||
return "mounted";
|
||||
case 3 /* DESTROYED */:
|
||||
case 2 /* DESTROYED */:
|
||||
return "destroyed";
|
||||
}
|
||||
}
|
||||
@@ -5901,9 +5837,8 @@ function useChildSubEnv(envExtension) {
|
||||
* will run a cleanup function before patching and before unmounting the
|
||||
* the component.
|
||||
*
|
||||
* @template T
|
||||
* @param {Effect<T>} effect the effect to run on component mount and/or patch
|
||||
* @param {()=>T} [computeDependencies=()=>[NaN]] a callback to compute
|
||||
* @param {Effect} effect the effect to run on component mount and/or patch
|
||||
* @param {()=>any[]} [computeDependencies=()=>[NaN]] a callback to compute
|
||||
* dependencies that will decide if the effect needs to be cleaned up and
|
||||
* run again. If the dependencies did not change, the effect will not run
|
||||
* again. The default value returns an array containing only NaN because
|
||||
@@ -5985,6 +5920,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
|
||||
export { App, Component, EventBus, OwlError, __info__, blockDom, loadFile, markRaw, markup, mount, onError, onMounted, onPatched, onRendered, onWillDestroy, onWillPatch, onWillRender, onWillStart, onWillUnmount, onWillUpdateProps, reactive, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, validate, validateType, whenReady, xml };
|
||||
|
||||
|
||||
__info__.date = '2023-07-18T14:07:26.565Z';
|
||||
__info__.hash = '836e12b';
|
||||
__info__.date = '2023-04-29T07:45:54.333Z';
|
||||
__info__.hash = 'aabb755';
|
||||
__info__.url = 'https://github.com/odoo/owl';
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"testEnvironment": "jsdom",
|
||||
"roots": [
|
||||
"<rootDir>/tests"
|
||||
],
|
||||
"setupFiles": [
|
||||
"./tests/mocks/mockEventTarget.js"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.ts?$": "ts-jest"
|
||||
},
|
||||
"verbose": false,
|
||||
"testRegex": "(/tests/.*(test|spec))\\.ts?$",
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
Generated
+41
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "2.2",
|
||||
"version": "2.1.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -1048,6 +1048,31 @@
|
||||
"@babel/types": "^7.3.0"
|
||||
}
|
||||
},
|
||||
"@types/chrome": {
|
||||
"version": "0.0.114",
|
||||
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.114.tgz",
|
||||
"integrity": "sha512-i7qRr74IrxHtbnrZSKUuP5Uvd5EOKwlwJq/yp7+yTPihOXnPhNQO4Z5bqb1XTnrjdbUKEJicaVVbhcgtRijmLA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/filesystem": "*",
|
||||
"@types/har-format": "*"
|
||||
}
|
||||
},
|
||||
"@types/filesystem": {
|
||||
"version": "0.0.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz",
|
||||
"integrity": "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/filewriter": "*"
|
||||
}
|
||||
},
|
||||
"@types/filewriter": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz",
|
||||
"integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/fs-extra": {
|
||||
"version": "8.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.2.tgz",
|
||||
@@ -1076,6 +1101,12 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/har-format": {
|
||||
"version": "1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.11.tgz",
|
||||
"integrity": "sha512-T232/TneofqK30AD1LRrrf8KnjLvzrjWDp7eWST5KoiSzrBfRsLrWDPk4STQPW4NZG6v2MltnduBVmakbZOBIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
|
||||
@@ -3338,6 +3369,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"jest-chrome": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-chrome/-/jest-chrome-0.8.0.tgz",
|
||||
"integrity": "sha512-39RR1GT9nI4e4jsuH1vIf4l5ApxxkcstjGJr+GsOURL8f4Db0UlbRnsZaM+ZRniaGtokqklUH5VFKGZZ6YztUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/chrome": "^0.0.114"
|
||||
}
|
||||
},
|
||||
"jest-circus": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz",
|
||||
|
||||
+10
-29
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "2.2",
|
||||
"version": "2.1.3",
|
||||
"description": "Odoo Web Library (OWL)",
|
||||
"main": "dist/owl.cjs.js",
|
||||
"module": "dist/owl.es.js",
|
||||
@@ -21,15 +21,19 @@
|
||||
"dev:devtools-firefox": "npm run build:devtools -- --config-browser=firefox",
|
||||
"build:devtools-chrome": "npm run dev:devtools-chrome -- --config-env=production",
|
||||
"build:devtools-firefox": "npm run dev:devtools-firefox -- --config-env=production",
|
||||
"test": "jest",
|
||||
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch --testTimeout=5000000",
|
||||
"test:watch": "jest --watch",
|
||||
"test-owl": "jest",
|
||||
"test-owl:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --watch --testTimeout=5000000",
|
||||
"test-owl:watch": "jest --watch",
|
||||
"test-devtools": "npm run build:devtools-chrome && jest --config=tools/devtools/tests/jest.config.json",
|
||||
"test-devtools:debug": "npm run build:devtools-chrome && node --inspect-brk node_modules/.bin/jest --config=tools/devtools/tests/jest.config.json --runInBand --watch --testTimeout=5000000",
|
||||
"test-devtools:watch": "jest --config=tools/devtools/tests/jest.config.json --watch",
|
||||
"test": "npm run test-owl && npm run test-devtools",
|
||||
"playground:serve": "python3 tools/playground_server.py || python tools/playground_server.py",
|
||||
"playground": "npm run build && npm run playground:serve",
|
||||
"preplayground:watch": "npm run build",
|
||||
"playground:watch": "npm-run-all --parallel playground:serve \"build:* -- --watch\"",
|
||||
"prettier": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md,tools/devtools/**/*.js} --write",
|
||||
"check-formatting": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md,tools/devtools/**/*.js} --check",
|
||||
"check-formatting": "npm run prettier -- --check",
|
||||
"lint": "eslint src/**/*.ts tests/**/*.ts",
|
||||
"release": "node tools/release.js",
|
||||
"compile_templates": "node tools/compile_xml.js"
|
||||
@@ -55,13 +59,13 @@
|
||||
"git-rev-sync": "^3.0.2",
|
||||
"github-api": "^3.3.0",
|
||||
"jest": "^27.1.0",
|
||||
"jest-chrome": "^0.8.0",
|
||||
"jest-diff": "^27.3.1",
|
||||
"jest-environment-jsdom": "^27.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "2.4.1",
|
||||
"rollup": "^2.56.3",
|
||||
"rollup-plugin-copy": "^3.3.0",
|
||||
"rollup-plugin-delete": "^2.0.0",
|
||||
"rollup-plugin-dts": "^4.2.2",
|
||||
"rollup-plugin-execute": "^1.1.1",
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
@@ -71,29 +75,6 @@
|
||||
"ts-jest": "^27.0.5",
|
||||
"typescript": "4.5.2"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom",
|
||||
"roots": [
|
||||
"<rootDir>/src",
|
||||
"<rootDir>/tests"
|
||||
],
|
||||
"setupFiles": [
|
||||
"./tests/mocks/mockEventTarget.js"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.ts?$": "ts-jest"
|
||||
},
|
||||
"verbose": false,
|
||||
"testRegex": "(/tests/.*(test|spec))\\.ts?$",
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 100,
|
||||
"endOfLine": "auto"
|
||||
|
||||
@@ -365,8 +365,10 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
const typeAttr = node.getAttribute("type");
|
||||
const isInput = tagName === "input";
|
||||
const isSelect = tagName === "select";
|
||||
const isTextarea = tagName === "textarea";
|
||||
const isCheckboxInput = isInput && typeAttr === "checkbox";
|
||||
const isRadioInput = isInput && typeAttr === "radio";
|
||||
const isOtherInput = isInput && !isCheckboxInput && !isRadioInput;
|
||||
const hasLazyMod = attr.includes(".lazy");
|
||||
const hasNumberMod = attr.includes(".number");
|
||||
const hasTrimMod = attr.includes(".trim");
|
||||
@@ -379,8 +381,8 @@ function parseDOMNode(node: Element, ctx: ParsingContext): AST | null {
|
||||
specialInitTargetAttr: isRadioInput ? "checked" : null,
|
||||
eventType,
|
||||
hasDynamicChildren: false,
|
||||
shouldTrim: hasTrimMod,
|
||||
shouldNumberize: hasNumberMod,
|
||||
shouldTrim: hasTrimMod && (isOtherInput || isTextarea),
|
||||
shouldNumberize: hasNumberMod && (isOtherInput || isTextarea),
|
||||
};
|
||||
if (isSelect) {
|
||||
// don't pollute the original ctx
|
||||
|
||||
@@ -145,9 +145,6 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
}
|
||||
|
||||
async render(deep: boolean) {
|
||||
if (this.status >= STATUS.CANCELLED) {
|
||||
return;
|
||||
}
|
||||
let current = this.fiber;
|
||||
if (current && (current.root!.locked || (current as any).bdom === true)) {
|
||||
await Promise.resolve();
|
||||
@@ -174,7 +171,7 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
|
||||
this.app.scheduler.addFiber(fiber);
|
||||
await Promise.resolve();
|
||||
if (this.status >= STATUS.CANCELLED) {
|
||||
if (this.status === STATUS.DESTROYED) {
|
||||
return;
|
||||
}
|
||||
// We only want to actually render the component if the following two
|
||||
@@ -193,20 +190,6 @@ export class ComponentNode<P extends Props = any, E = any> implements VNode<Comp
|
||||
}
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this._cancel();
|
||||
delete this.parent!.children[this.parentKey!];
|
||||
this.app.scheduler.scheduleDestroy(this);
|
||||
}
|
||||
|
||||
_cancel() {
|
||||
this.status = STATUS.CANCELLED;
|
||||
const children = this.children;
|
||||
for (let childKey in children) {
|
||||
children[childKey]._cancel();
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
let shouldRemove = this.status === STATUS.MOUNTED;
|
||||
this._destroy();
|
||||
|
||||
@@ -51,19 +51,17 @@ export function handleError(params: ErrorParams) {
|
||||
);
|
||||
}
|
||||
const node = "node" in params ? params.node : params.fiber.node;
|
||||
const fiber = "fiber" in params ? params.fiber : node.fiber;
|
||||
const fiber = "fiber" in params ? params.fiber : node.fiber!;
|
||||
|
||||
if (fiber) {
|
||||
// resets the fibers on components if possible. This is important so that
|
||||
// new renderings can be properly included in the initial one, if any.
|
||||
let current: Fiber | null = fiber;
|
||||
do {
|
||||
current.node.fiber = current;
|
||||
current = current.parent;
|
||||
} while (current);
|
||||
// resets the fibers on components if possible. This is important so that
|
||||
// new renderings can be properly included in the initial one, if any.
|
||||
let current: Fiber | null = fiber;
|
||||
do {
|
||||
current.node.fiber = current;
|
||||
current = current.parent;
|
||||
} while (current);
|
||||
|
||||
fibersInError.set(fiber.root!, error);
|
||||
}
|
||||
fibersInError.set(fiber.root!, error);
|
||||
|
||||
const handled = _handleError(node, error);
|
||||
if (!handled) {
|
||||
|
||||
@@ -55,7 +55,8 @@ function cancelFibers(fibers: Fiber[]): number {
|
||||
let node = fiber.node;
|
||||
fiber.render = throwOnRender;
|
||||
if (node.status === STATUS.NEW) {
|
||||
node.cancel();
|
||||
node.destroy();
|
||||
delete node.parent!.children[node.parentKey!];
|
||||
}
|
||||
node.fiber = null;
|
||||
if (fiber.bdom) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { ComponentNode } from "./component_node";
|
||||
import { fibersInError } from "./error_handling";
|
||||
import { Fiber, RootFiber } from "./fibers";
|
||||
import { STATUS } from "./status";
|
||||
@@ -15,7 +14,6 @@ export class Scheduler {
|
||||
requestAnimationFrame: Window["requestAnimationFrame"];
|
||||
frame: number = 0;
|
||||
delayedRenders: Fiber[] = [];
|
||||
cancelledNodes: Set<ComponentNode> = new Set();
|
||||
|
||||
constructor() {
|
||||
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
||||
@@ -25,13 +23,6 @@ export class Scheduler {
|
||||
this.tasks.add(fiber.root!);
|
||||
}
|
||||
|
||||
scheduleDestroy(node: ComponentNode) {
|
||||
this.cancelledNodes.add(node);
|
||||
if (this.frame === 0) {
|
||||
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all current tasks. This only applies to the fibers that are ready.
|
||||
* Other tasks are left unchanged.
|
||||
@@ -48,23 +39,15 @@ export class Scheduler {
|
||||
}
|
||||
|
||||
if (this.frame === 0) {
|
||||
this.frame = this.requestAnimationFrame(() => this.processTasks());
|
||||
}
|
||||
}
|
||||
|
||||
processTasks() {
|
||||
this.frame = 0;
|
||||
for (let node of this.cancelledNodes) {
|
||||
node._destroy();
|
||||
}
|
||||
this.cancelledNodes.clear();
|
||||
for (let task of this.tasks) {
|
||||
this.processFiber(task);
|
||||
}
|
||||
for (let task of this.tasks) {
|
||||
if (task.node.status === STATUS.DESTROYED) {
|
||||
this.tasks.delete(task);
|
||||
}
|
||||
this.frame = this.requestAnimationFrame(() => {
|
||||
this.frame = 0;
|
||||
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
||||
for (let task of this.tasks) {
|
||||
if (task.node.status === STATUS.DESTROYED) {
|
||||
this.tasks.delete(task);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,20 +7,15 @@ import type { Component } from "./component";
|
||||
export const enum STATUS {
|
||||
NEW,
|
||||
MOUNTED, // is ready, and in DOM. It has a valid el
|
||||
// component has been created, but has been replaced by a newer component before being mounted
|
||||
// it is cancelled until the next animation frame where it will be destroyed
|
||||
CANCELLED,
|
||||
DESTROYED,
|
||||
}
|
||||
|
||||
type STATUS_DESCR = "new" | "mounted" | "cancelled" | "destroyed";
|
||||
type STATUS_DESCR = "new" | "mounted" | "destroyed";
|
||||
|
||||
export function status(component: Component): STATUS_DESCR {
|
||||
switch (component.__owl__.status) {
|
||||
case STATUS.NEW:
|
||||
return "new";
|
||||
case STATUS.CANCELLED:
|
||||
return "cancelled";
|
||||
case STATUS.MOUNTED:
|
||||
return "mounted";
|
||||
case STATUS.DESTROYED:
|
||||
|
||||
@@ -60,26 +60,18 @@ function withKey(elem: any, k: string) {
|
||||
return elem;
|
||||
}
|
||||
|
||||
function prepareList(collection: unknown): [unknown[], unknown[], number, undefined[]] {
|
||||
let keys: unknown[];
|
||||
let values: unknown[];
|
||||
function prepareList(collection: any): [any[], any[], number, any[]] {
|
||||
let keys: any[];
|
||||
let values: any[];
|
||||
|
||||
if (Array.isArray(collection)) {
|
||||
keys = collection;
|
||||
values = collection;
|
||||
} else if (collection instanceof Map) {
|
||||
keys = [...collection.keys()];
|
||||
values = [...collection.values()];
|
||||
} else if (collection && typeof collection === "object") {
|
||||
if (Symbol.iterator in collection) {
|
||||
keys = [...(<Iterable<unknown>>collection)];
|
||||
values = keys;
|
||||
} else {
|
||||
values = Object.keys(collection);
|
||||
keys = Object.values(collection);
|
||||
}
|
||||
} else if (collection) {
|
||||
values = Object.keys(collection);
|
||||
keys = Object.values(collection);
|
||||
} else {
|
||||
throw new OwlError(`Invalid loop expression: "${collection}" is not iterable`);
|
||||
throw new OwlError("Invalid loop expression");
|
||||
}
|
||||
const n = values.length;
|
||||
return [keys, values, n, new Array(n)];
|
||||
|
||||
+14
-7
@@ -9,13 +9,20 @@ export type Callback = () => void;
|
||||
* @returns a batched version of the original callback
|
||||
*/
|
||||
export function batched(callback: Callback): Callback {
|
||||
let scheduled = false;
|
||||
return async (...args) => {
|
||||
if (!scheduled) {
|
||||
scheduled = true;
|
||||
await Promise.resolve();
|
||||
scheduled = false;
|
||||
callback(...args);
|
||||
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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
// do not modify manually. This file is generated by the release script.
|
||||
export const version = "2.2";
|
||||
export const version = "2.1.3";
|
||||
|
||||
@@ -77,62 +77,6 @@ exports[`t-foreach iterate on items 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, Map param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { prepareList, withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['value']);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = k_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, Set param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { prepareList, withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['value']);;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = k_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, dict param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -164,62 +108,6 @@ exports[`t-foreach iterate, dict param 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, generator param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { prepareList, withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['gen']());;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = k_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, iterable param 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { prepareList, withKey } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block1, v_block1, l_block1, c_block1] = prepareList(ctx['map'].values());;
|
||||
for (let i1 = 0; i1 < l_block1; i1++) {
|
||||
ctx[\`item\`] = v_block1[i1];
|
||||
ctx[\`item_index\`] = i1;
|
||||
ctx[\`item_value\`] = k_block1[i1];
|
||||
const key1 = ctx['item_index'];
|
||||
const b3 = text(\` [\`);
|
||||
const b4 = text(ctx['item_index']);
|
||||
const b5 = text(\`: \`);
|
||||
const b6 = text(ctx['item']);
|
||||
const b7 = text(\` \`);
|
||||
const b8 = text(ctx['item_value']);
|
||||
const b9 = text(\`] \`);
|
||||
c_block1[i1] = withKey(multi([b3, b4, b5, b6, b7, b8, b9]), key1);
|
||||
}
|
||||
return list(c_block1);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-foreach iterate, position 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -1991,8 +1991,8 @@ describe("qweb parser", () => {
|
||||
baseExpr: "state",
|
||||
expr: "'stuff'",
|
||||
eventType: "click",
|
||||
shouldNumberize: true,
|
||||
shouldTrim: true,
|
||||
shouldNumberize: false,
|
||||
shouldTrim: false,
|
||||
targetAttr: "value",
|
||||
hasDynamicChildren: false,
|
||||
specialInitTargetAttr: "checked",
|
||||
|
||||
@@ -105,64 +105,6 @@ describe("t-foreach", () => {
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("iterate, Map param", () => {
|
||||
const template = `
|
||||
<t t-foreach="value" t-as="item" t-key="item_index">
|
||||
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||
</t>`;
|
||||
const expected = ` [0: 1 a] [1: 2 b] [2: 3 c] `;
|
||||
const context = {
|
||||
value: new Map([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
["c", 3],
|
||||
]),
|
||||
};
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("iterate, Set param", () => {
|
||||
const template = `
|
||||
<t t-foreach="value" t-as="item" t-key="item_index">
|
||||
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||
</t>`;
|
||||
const expected = ` [0: 1 1] [1: 2 2] [2: 3 3] `;
|
||||
const context = { value: new Set([1, 2, 3]) };
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("iterate, iterable param", () => {
|
||||
const template = `
|
||||
<t t-foreach="map.values()" t-as="item" t-key="item_index">
|
||||
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||
</t>`;
|
||||
const expected = ` [0: 1 1] [1: 2 2] [2: 3 3] `;
|
||||
const context = {
|
||||
map: new Map([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
["c", 3],
|
||||
]),
|
||||
};
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("iterate, generator param", () => {
|
||||
const template = `
|
||||
<t t-foreach="gen()" t-as="item" t-key="item_index">
|
||||
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]
|
||||
</t>`;
|
||||
const expected = ` [0: 1 1] [1: 2 2] [2: 3 3] `;
|
||||
const context = {
|
||||
*gen() {
|
||||
yield 1;
|
||||
yield 2;
|
||||
yield 3;
|
||||
},
|
||||
};
|
||||
expect(renderToString(template, context)).toBe(expected);
|
||||
});
|
||||
|
||||
test("does not pollute the rendering context", () => {
|
||||
const template = `
|
||||
<div>
|
||||
@@ -251,9 +193,7 @@ describe("t-foreach", () => {
|
||||
|
||||
test("throws error if invalid loop expression", () => {
|
||||
const test = `<div><t t-foreach="abc" t-as="item" t-key="item"><span t-key="item_index"/></t></div>`;
|
||||
expect(() => renderToString(test)).toThrow(
|
||||
'Invalid loop expression: "undefined" is not iterable'
|
||||
);
|
||||
expect(() => renderToString(test)).toThrow("Invalid loop expression");
|
||||
});
|
||||
|
||||
test("t-foreach with t-if inside", () => {
|
||||
|
||||
@@ -212,73 +212,6 @@ exports[`changing state before first render does not trigger a render 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`component destroyed just after render 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`B\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return comp1({}, key + \`__1\`, node, this, null);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`component destroyed just after render 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'].value);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`components are not destroyed between animation frame 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`B\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2,b3;
|
||||
b2 = text(\`A\`);
|
||||
if (ctx['state'].flag) {
|
||||
b3 = comp1({}, key + \`__1\`, node, this, null);
|
||||
}
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`components are not destroyed between animation frame 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`C\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = text(\`B\`);
|
||||
const b3 = comp1({}, key + \`__1\`, node, this, null);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`components are not destroyed between animation frame 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[`concurrent renderings scenario 1 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -468,36 +468,6 @@ exports[`t-model directive t-model on select with static options 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-model directive t-model with dynamic number values on select options in foreach 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { toNumber, prepareList, withKey } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<select block-handler-0=\\"change\\"><block-child-0/></select>\`);
|
||||
let block3 = createBlock(\`<option block-attribute-0=\\"value\\" block-attribute-1=\\"selected\\"><block-text-2/></option>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const bExpr1 = ctx['state'];
|
||||
const expr1 = 'value';
|
||||
const bValue1 = bExpr1[expr1];
|
||||
let hdlr1 = [(ev) => { bExpr1[expr1] = toNumber(ev.target.value); }];
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['state'].options);;
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`o\`] = v_block2[i1];
|
||||
const key1 = ctx['o'].value;
|
||||
let attr1 = ctx['o'].value;
|
||||
let attr2 = bValue1 === ctx['o'].value;
|
||||
let txt1 = ctx['o'].value;
|
||||
c_block2[i1] = withKey(block3([attr1, attr2, txt1]), key1);
|
||||
}
|
||||
const b2 = list(c_block2);
|
||||
return block1([hdlr1], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-model directive t-model with dynamic values on select options -- 2 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -115,7 +115,13 @@ test("destroying/recreating a subwidget with different props (if start is not ov
|
||||
await nextMicroTick();
|
||||
expect(n).toBe(2);
|
||||
|
||||
expect(["W:willRender", "Child:setup", "Child:willStart", "W:rendered"]).toBeLogged();
|
||||
expect([
|
||||
"Child:willDestroy",
|
||||
"W:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"W:rendered",
|
||||
]).toBeLogged();
|
||||
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
@@ -124,7 +130,6 @@ test("destroying/recreating a subwidget with different props (if start is not ov
|
||||
expect([
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:willDestroy",
|
||||
"W:willPatch",
|
||||
"Child:mounted",
|
||||
"W:patched",
|
||||
@@ -173,13 +178,13 @@ test("destroying/recreating a subcomponent, other scenario", async () => {
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Parent:rendered",
|
||||
"Child:willDestroy",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:willDestroy",
|
||||
"Parent:willPatch",
|
||||
"Child:mounted",
|
||||
"Parent:patched",
|
||||
@@ -246,13 +251,13 @@ test("creating two async components, scenario 1", async () => {
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
expect([
|
||||
"ChildA:willDestroy",
|
||||
"Parent:willRender",
|
||||
"ChildA:setup",
|
||||
"ChildA:willStart",
|
||||
"ChildB:setup",
|
||||
"ChildB:willStart",
|
||||
"Parent:rendered",
|
||||
"ChildA:willDestroy",
|
||||
]).toBeLogged();
|
||||
|
||||
defB.resolve();
|
||||
@@ -698,13 +703,13 @@ test("rendering component again in next microtick", async () => {
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Parent:rendered",
|
||||
"Child:willDestroy",
|
||||
"Parent:willRender",
|
||||
"Child:setup",
|
||||
"Child:willStart",
|
||||
"Parent:rendered",
|
||||
"Child:willRender",
|
||||
"Child:rendered",
|
||||
"Child:willDestroy",
|
||||
"Parent:willPatch",
|
||||
"Child:mounted",
|
||||
"Parent:patched",
|
||||
@@ -1727,9 +1732,9 @@ test("concurrent renderings scenario 10", async () => {
|
||||
expect(fixture.innerHTML).toBe("<div><p></p></div>");
|
||||
expect([
|
||||
"ComponentA:willRender",
|
||||
"ComponentC:willDestroy",
|
||||
"ComponentB:willUpdateProps",
|
||||
"ComponentA:rendered",
|
||||
"ComponentC:willDestroy",
|
||||
]).toBeLogged();
|
||||
|
||||
defB.resolve();
|
||||
@@ -2277,6 +2282,7 @@ test("concurrent renderings scenario 16", async () => {
|
||||
"D:setup",
|
||||
"D:willStart",
|
||||
"C:rendered",
|
||||
"D:willDestroy",
|
||||
"B:willRender",
|
||||
"C:willUpdateProps",
|
||||
"B:rendered",
|
||||
@@ -2284,7 +2290,6 @@ test("concurrent renderings scenario 16", async () => {
|
||||
"D:setup",
|
||||
"D:willStart",
|
||||
"C:rendered",
|
||||
"D:willDestroy",
|
||||
]).toBeLogged();
|
||||
|
||||
// at this point, C rendering is still pending, and nothing should have been
|
||||
@@ -2992,11 +2997,11 @@ test("t-key on dom node having a component", async () => {
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div>3</div>");
|
||||
expect([
|
||||
"Child (2):willDestroy",
|
||||
"Child (3):setup",
|
||||
"Child (3):willStart",
|
||||
"Child (3):willRender",
|
||||
"Child (3):rendered",
|
||||
"Child (2):willDestroy",
|
||||
"Child (1):willUnmount",
|
||||
"Child (1):willDestroy",
|
||||
"Child (3):mounted",
|
||||
@@ -3050,11 +3055,11 @@ test("t-key on dynamic async component (toggler is never patched)", async () =>
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div>3</div>");
|
||||
expect([
|
||||
"Child (2):willDestroy",
|
||||
"Child (3):setup",
|
||||
"Child (3):willStart",
|
||||
"Child (3):willRender",
|
||||
"Child (3):rendered",
|
||||
"Child (2):willDestroy",
|
||||
"Child (1):willUnmount",
|
||||
"Child (1):willDestroy",
|
||||
"Child (3):mounted",
|
||||
@@ -3109,11 +3114,11 @@ test("t-foreach with dynamic async component", async () => {
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div>3</div>");
|
||||
expect([
|
||||
"Child (2):willDestroy",
|
||||
"Child (3):setup",
|
||||
"Child (3):willStart",
|
||||
"Child (3):willRender",
|
||||
"Child (3):rendered",
|
||||
"Child (2):willDestroy",
|
||||
"Child (1):willUnmount",
|
||||
"Child (1):willDestroy",
|
||||
"Child (3):mounted",
|
||||
@@ -3796,7 +3801,7 @@ test("destroyed component causes other soon to be destroyed component to rerende
|
||||
static template = xml`<t t-esc="state.val + props.value"/>`;
|
||||
state = useState({ val: 0 });
|
||||
setup() {
|
||||
c = c || this;
|
||||
c = this;
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
@@ -3841,6 +3846,8 @@ test("destroyed component causes other soon to be destroyed component to rerende
|
||||
parent.state.valueB = 2;
|
||||
await nextTick();
|
||||
expect([
|
||||
"B:willDestroy",
|
||||
"C:willDestroy",
|
||||
"A:willRender",
|
||||
"B:setup",
|
||||
"B:willStart",
|
||||
@@ -3851,8 +3858,6 @@ test("destroyed component causes other soon to be destroyed component to rerende
|
||||
"B:rendered",
|
||||
"C:willRender",
|
||||
"C:rendered",
|
||||
"B:willDestroy",
|
||||
"C:willDestroy",
|
||||
"A:willPatch",
|
||||
"C:mounted",
|
||||
"B:mounted",
|
||||
@@ -4195,116 +4200,6 @@ test("delayed render is not cancelled by upcoming render", async () => {
|
||||
]).toBeLogged();
|
||||
});
|
||||
|
||||
test("components are not destroyed between animation frame", async () => {
|
||||
const def = makeDeferred();
|
||||
class C extends Component {
|
||||
static template = xml`C`;
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
class B extends Component {
|
||||
static template = xml`B<C/>`;
|
||||
static components = { C };
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
onWillStart(() => {
|
||||
return def;
|
||||
});
|
||||
}
|
||||
}
|
||||
class A extends Component {
|
||||
static template = xml`A<B t-if="state.flag"/>`;
|
||||
static components = { B };
|
||||
|
||||
state = useState({ flag: false });
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
const a = await mount(A, fixture);
|
||||
expect(fixture.innerHTML).toBe("A");
|
||||
expect(["A:setup", "A:willStart", "A:willRender", "A:rendered", "A:mounted"]).toBeLogged();
|
||||
|
||||
// turn the flag on, this will render A and stops at B because of def
|
||||
a.state.flag = true;
|
||||
await nextTick();
|
||||
expect(["A:willRender", "B:setup", "B:willStart", "A:rendered"]).toBeLogged();
|
||||
|
||||
// force a render of A
|
||||
// => owl will need to create a new B component
|
||||
// => initial B component will be cancelled
|
||||
a.render();
|
||||
await nextMicroTick();
|
||||
expect([
|
||||
// note that B is not destroyed here. It is cancelled instead
|
||||
"A:willRender",
|
||||
"B:setup",
|
||||
"B:willStart",
|
||||
"A:rendered",
|
||||
]).toBeLogged();
|
||||
|
||||
// resolve def, so B render is unblocked
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
expect([
|
||||
"B:willRender",
|
||||
"C:setup",
|
||||
"C:willStart",
|
||||
"B:rendered",
|
||||
"C:willRender",
|
||||
"C:rendered",
|
||||
// animation frame callback starts here
|
||||
"B:willDestroy", // B is destroyed here
|
||||
"A:willPatch",
|
||||
"C:mounted",
|
||||
"B:mounted",
|
||||
"A:patched",
|
||||
]).toBeLogged();
|
||||
});
|
||||
|
||||
test("component destroyed just after render", async () => {
|
||||
let stateB: any;
|
||||
|
||||
class B extends Component {
|
||||
static template = xml`B<t t-esc="state.value"/>`;
|
||||
state = useState({ value: 1 });
|
||||
setup() {
|
||||
stateB = this.state;
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
class A extends Component {
|
||||
static template = xml`<B/>`;
|
||||
static components = { B };
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
const a = await mount(A, fixture);
|
||||
expect(fixture.innerHTML).toBe("B1");
|
||||
expect([
|
||||
"A:setup",
|
||||
"A:willStart",
|
||||
"A:willRender",
|
||||
"B:setup",
|
||||
"B:willStart",
|
||||
"A:rendered",
|
||||
"B:willRender",
|
||||
"B:rendered",
|
||||
"B:mounted",
|
||||
"A:mounted",
|
||||
]).toBeLogged();
|
||||
|
||||
stateB!.value++; // force a render of B
|
||||
await nextMicroTick(); // wait for B render to actually start
|
||||
a.__owl__.app.destroy();
|
||||
expect(["A:willUnmount", "B:willUnmount", "B:willDestroy", "A:willDestroy"]).toBeLogged();
|
||||
await nextTick();
|
||||
// check that B was not rendered after being destroyed
|
||||
expect([]).toBeLogged();
|
||||
});
|
||||
|
||||
// test.skip("components with shouldUpdate=false", async () => {
|
||||
// const state = { p: 1, cc: 10 };
|
||||
|
||||
|
||||
@@ -1444,7 +1444,6 @@ describe("can catch errors", () => {
|
||||
"Parent:willPatch",
|
||||
"Child:willUnmount",
|
||||
"Child:willDestroy",
|
||||
"Parent:patched",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:willPatch",
|
||||
@@ -1507,15 +1506,12 @@ describe("can catch errors", () => {
|
||||
parent.state.hasChild = false;
|
||||
await nextTick();
|
||||
expect([
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Child:willDestroy",
|
||||
"Parent:willRender",
|
||||
"Parent:rendered",
|
||||
"Parent:willPatch",
|
||||
"Parent:patched",
|
||||
]).toBeLogged();
|
||||
expect(fixture.innerHTML).toBe("1");
|
||||
await nextTick();
|
||||
expect(["Parent:willPatch", "Parent:patched"]).toBeLogged();
|
||||
expect(fixture.innerHTML).toBe("2");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -626,39 +626,6 @@ describe("t-model directive", () => {
|
||||
expect(fixture.querySelector("select")!.value).toEqual("b");
|
||||
});
|
||||
|
||||
test("t-model with dynamic number values on select options in foreach", async () => {
|
||||
class Test extends Component {
|
||||
static template = xml`
|
||||
<select t-model.number="state.value">
|
||||
<t t-foreach="state.options" t-as="o" t-key="o.value">
|
||||
<option t-att-value="o.value" t-esc="o.value"/>
|
||||
</t>
|
||||
</select>
|
||||
`;
|
||||
state: any;
|
||||
setup() {
|
||||
this.state = useState({
|
||||
value: 2,
|
||||
options: [{ value: 1 }, { value: 2 }, { value: 3 }],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const comp = await mount(Test, fixture);
|
||||
// check that we have a value of 2 selected
|
||||
expect(fixture.querySelector("select")!.value).toEqual("2");
|
||||
expect(comp.state.value).toBe(2);
|
||||
|
||||
// emulate a click on the option=3 element
|
||||
fixture.querySelectorAll("option")[2].selected = true;
|
||||
fixture.querySelector("select")!.dispatchEvent(new Event("change"));
|
||||
|
||||
await nextTick();
|
||||
// check that we have now selected the number 3 (and not the string)
|
||||
expect(fixture.querySelector("select")!.value).toEqual("3");
|
||||
expect(comp.state.value).toBe(3);
|
||||
});
|
||||
|
||||
test("t-model is applied before t-on-input", async () => {
|
||||
expect.assertions(3);
|
||||
class SomeComponent extends Component {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import terser from "rollup-plugin-terser";
|
||||
import copy from "rollup-plugin-copy";
|
||||
import execute from "rollup-plugin-execute";
|
||||
import del from "rollup-plugin-delete";
|
||||
import { string } from "rollup-plugin-string";
|
||||
|
||||
const isWindows = process.platform === "win32";
|
||||
@@ -37,12 +36,12 @@ export default ({ "config-browser": browser, "config-env": env }) => {
|
||||
},
|
||||
];
|
||||
|
||||
function generateRule(input, format = "esm") {
|
||||
function generateRule(input, plugins = []) {
|
||||
return {
|
||||
input: input,
|
||||
input,
|
||||
output: [
|
||||
{
|
||||
format: format,
|
||||
format: "esm",
|
||||
file: input.replace("tools/devtools/src", "dist/devtools"),
|
||||
},
|
||||
],
|
||||
@@ -51,6 +50,7 @@ export default ({ "config-browser": browser, "config-env": env }) => {
|
||||
include: "**/page_scripts/owl_devtools_global_hook.js",
|
||||
}),
|
||||
isProduction && terser.terser(),
|
||||
...plugins,
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -58,7 +58,6 @@ export default ({ "config-browser": browser, "config-env": env }) => {
|
||||
commands[1] = isWindows
|
||||
? "npm run compile_templates -- tools\\devtools\\src && move templates.js tools\\devtools\\assets\\templates.js"
|
||||
: "npm run compile_templates -- tools/devtools/src && mv templates.js tools/devtools/assets/templates.js";
|
||||
const firstRule = generateRule("tools/devtools/src/page_scripts/owl_devtools_global_hook.js");
|
||||
if (isProduction) {
|
||||
commands[0] = isWindows
|
||||
? "npm run build && copy dist\\owl.iife.js tools\\devtools\\assets\\owl.js && npm run build:compiler"
|
||||
@@ -68,19 +67,16 @@ export default ({ "config-browser": browser, "config-env": env }) => {
|
||||
? "copy dist\\owl.iife.js tools\\devtools\\assets\\owl.js"
|
||||
: "cp dist/owl.iife.js tools/devtools/assets/owl.js";
|
||||
}
|
||||
firstRule.plugins.push(execute(commands, true));
|
||||
const secondRule = generateRule("tools/devtools/src/content.js");
|
||||
secondRule.plugins.push(copy({ targets: filesToMove }));
|
||||
const lastRule = generateRule("tools/devtools/src/background.js");
|
||||
lastRule.plugins.push(del({ targets: "tools/devtools/assets/*.js" }));
|
||||
|
||||
return [
|
||||
firstRule,
|
||||
secondRule,
|
||||
generateRule("tools/devtools/src/page_scripts/owl_devtools_global_hook.js", [
|
||||
execute(commands, true),
|
||||
]),
|
||||
generateRule("tools/devtools/src/content.js", [copy({ targets: filesToMove })]),
|
||||
generateRule("tools/devtools/src/devtools_app/devtools.js"),
|
||||
generateRule("tools/devtools/src/utils.js"),
|
||||
generateRule("tools/devtools/src/devtools_app/devtools_panel.js"),
|
||||
generateRule("tools/devtools/src/popup_app/popup.js"),
|
||||
lastRule,
|
||||
generateRule("tools/devtools/src/background.js"),
|
||||
];
|
||||
};
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
import { DevtoolsWindow } from "./devtools_window/devtools_window";
|
||||
const { mount } = owl;
|
||||
import { templates } from "../../assets/templates.js";
|
||||
import { createStore } from "./store/store";
|
||||
|
||||
for (const template in templates) {
|
||||
owl.App.registerTemplate(template, templates[template]);
|
||||
}
|
||||
createStore();
|
||||
mount(DevtoolsWindow, document.body, { dev: true });
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ export class ObjectTreeElement extends Component {
|
||||
|
||||
classFor(object) {
|
||||
// Prototype items will be dyed down to appear less important
|
||||
if (object.path.some((item) => item?.type === "prototype") && !object.keepLit) {
|
||||
if (object.path.some((item) => item?.type === "prototype")) {
|
||||
return "attenuate";
|
||||
}
|
||||
// Same for subscription items which are not present in the keys while the keys will be bold
|
||||
|
||||
@@ -102,7 +102,7 @@ export const store = reactive({
|
||||
if (IS_FIREFOX) {
|
||||
await evalInWindow("window.$0 = $0;", this.activeFrame);
|
||||
}
|
||||
const [apps, details] = await evalFunctionInWindow(
|
||||
const [apps, component] = await evalFunctionInWindow(
|
||||
"getComponentsTree",
|
||||
fromOld && this.activeComponent
|
||||
? [this.activeComponent.path, this.apps, this.activeComponent]
|
||||
@@ -113,8 +113,7 @@ export const store = reactive({
|
||||
if (!fromOld && this.settings.expandByDefault) {
|
||||
this.apps.forEach((tree) => expandNodes(tree, true));
|
||||
}
|
||||
keepEnvLit(details);
|
||||
this.activeComponent = details;
|
||||
this.activeComponent = component;
|
||||
},
|
||||
|
||||
// Select a component by retrieving its details from the page based on its path
|
||||
@@ -151,11 +150,9 @@ export const store = reactive({
|
||||
[component.path],
|
||||
this.activeFrame
|
||||
);
|
||||
if (!details) {
|
||||
this.activeComponent = details;
|
||||
if (!this.activeComponent) {
|
||||
await this.loadComponentsTree(false);
|
||||
} else {
|
||||
keepEnvLit(details);
|
||||
this.activeComponent = details;
|
||||
}
|
||||
if (this.page !== "ComponentsTab") {
|
||||
this.switchTab("ComponentsTab");
|
||||
@@ -628,9 +625,7 @@ export function useStore() {
|
||||
return useState(store);
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
async function init() {
|
||||
export async function createStore() {
|
||||
store.devtoolsId = await getTabURL();
|
||||
|
||||
evalFunctionInWindow("initDevtools", []);
|
||||
@@ -890,31 +885,6 @@ function expandNodes(node, blacklist = false) {
|
||||
}
|
||||
}
|
||||
|
||||
// This function transforms the env part of the details such that all env keys are not
|
||||
// greyed out in the UI at their first occurence
|
||||
function keepEnvLit(details) {
|
||||
let alreadyMet = new Set();
|
||||
for (let i = 0; i < details.env.children.length; i++) {
|
||||
if (i < details.env.children.length - 1) {
|
||||
alreadyMet.add(details.env.children[i].name);
|
||||
} else {
|
||||
let lastElement = details.env.children[i];
|
||||
while (lastElement.children.at(-1).name === "[[Prototype]]") {
|
||||
for (const [index, child] of lastElement.children.entries()) {
|
||||
if (index < lastElement.children.length - 1) {
|
||||
if (!alreadyMet.has(child.name)) {
|
||||
child.keepLit = true;
|
||||
alreadyMet.add(child.name);
|
||||
}
|
||||
} else {
|
||||
lastElement = child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fold the node given in entry and all of its children
|
||||
function foldNodes(node) {
|
||||
node.toggled = false;
|
||||
|
||||
@@ -33,11 +33,14 @@
|
||||
* This process may seem long and indirect but is necessary. This applies to all window.top.postMessage methods in this file.
|
||||
* More information in the docs: https://developer.chrome.com/docs/extensions/mv3/devtools/#evaluated-scripts-to-devtools
|
||||
*/
|
||||
window.top.postMessage({
|
||||
source: "owl-devtools",
|
||||
type: "NewIFrame",
|
||||
data: addedNode.contentDocument.location.href,
|
||||
});
|
||||
window.top.postMessage(
|
||||
{
|
||||
source: "owl-devtools",
|
||||
type: "NewIFrame",
|
||||
data: addedNode.contentDocument.location.href,
|
||||
},
|
||||
"/"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -117,15 +120,6 @@
|
||||
length += element.length;
|
||||
result.push(element);
|
||||
}
|
||||
for (const key of Object.getOwnPropertySymbols(obj)) {
|
||||
if (length > 25) {
|
||||
result.push("...");
|
||||
break;
|
||||
}
|
||||
const element = key.toString() + ": " + this.serializeItem(obj[key]);
|
||||
length += element.length;
|
||||
result.push(element);
|
||||
}
|
||||
return "{" + result.join(", ") + "}";
|
||||
},
|
||||
map(obj) {
|
||||
@@ -183,18 +177,24 @@
|
||||
original_Complete.call(this, ...arguments);
|
||||
const path = self.getComponentPath(this.node);
|
||||
//Add a functionnality to the complete function which sends a message to the window every time it is triggered.
|
||||
window.top.postMessage({
|
||||
source: "owl-devtools",
|
||||
type: "Complete",
|
||||
data: path,
|
||||
origin: { frame: self.frame },
|
||||
});
|
||||
if (self.recordEvents) {
|
||||
window.top.postMessage({
|
||||
window.top.postMessage(
|
||||
{
|
||||
source: "owl-devtools",
|
||||
type: "Event",
|
||||
data: self.eventsBatch,
|
||||
});
|
||||
type: "Complete",
|
||||
data: path,
|
||||
origin: { frame: self.frame },
|
||||
},
|
||||
"/"
|
||||
);
|
||||
if (self.recordEvents) {
|
||||
window.top.postMessage(
|
||||
{
|
||||
source: "owl-devtools",
|
||||
type: "Event",
|
||||
data: self.eventsBatch,
|
||||
},
|
||||
"/"
|
||||
);
|
||||
self.eventsBatch = [];
|
||||
}
|
||||
};
|
||||
@@ -207,17 +207,23 @@
|
||||
const originalDelete = this.apps.delete;
|
||||
this.apps.add = function () {
|
||||
originalAdd.call(this, ...arguments);
|
||||
window.top.postMessage({
|
||||
source: "owl-devtools",
|
||||
type: "RefreshApps",
|
||||
});
|
||||
window.top.postMessage(
|
||||
{
|
||||
source: "owl-devtools",
|
||||
type: "RefreshApps",
|
||||
},
|
||||
"/"
|
||||
);
|
||||
};
|
||||
this.apps.delete = function () {
|
||||
originalDelete.call(this, ...arguments);
|
||||
window.top.postMessage({
|
||||
source: "owl-devtools",
|
||||
type: "RefreshApps",
|
||||
});
|
||||
window.top.postMessage(
|
||||
{
|
||||
source: "owl-devtools",
|
||||
type: "RefreshApps",
|
||||
},
|
||||
"/"
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -611,11 +617,14 @@
|
||||
const path = this.getElementPath(target);
|
||||
this.highlightComponent(path);
|
||||
this.currentSelectedElement = target;
|
||||
window.top.postMessage({
|
||||
source: "owl-devtools",
|
||||
type: "SelectElement",
|
||||
data: path,
|
||||
});
|
||||
window.top.postMessage(
|
||||
{
|
||||
source: "owl-devtools",
|
||||
type: "SelectElement",
|
||||
data: path,
|
||||
},
|
||||
"/"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -639,10 +648,13 @@
|
||||
document.removeEventListener("mouseover", this.HTMLSelector, { capture: true });
|
||||
document.removeEventListener("click", this.disableHTMLSelector, { capture: true });
|
||||
document.removeEventListener("mouseout", this.removeHighlights, { capture: true });
|
||||
window.top.postMessage({
|
||||
source: "owl-devtools",
|
||||
type: "StopSelector",
|
||||
});
|
||||
window.top.postMessage(
|
||||
{
|
||||
source: "owl-devtools",
|
||||
type: "StopSelector",
|
||||
},
|
||||
"/"
|
||||
);
|
||||
};
|
||||
|
||||
// Returns the object specified by the path starting from the topParent object
|
||||
@@ -748,9 +760,6 @@
|
||||
child.contentType = "object";
|
||||
child.content = this.serializer.serializeItem(Object.getPrototypeOf(parentObj), true);
|
||||
child.hasChildren = true;
|
||||
if (!oldTree && type === "env") {
|
||||
child.toggled = true;
|
||||
}
|
||||
break;
|
||||
case "set entries":
|
||||
case "map entries":
|
||||
@@ -827,8 +836,7 @@
|
||||
break;
|
||||
case obj instanceof Object:
|
||||
child.contentType = "object";
|
||||
child.hasChildren =
|
||||
Object.keys(obj).length || Object.getOwnPropertySymbols(obj).length;
|
||||
child.hasChildren = Object.keys(obj).length > 0;
|
||||
break;
|
||||
default:
|
||||
child.contentType = typeof obj;
|
||||
@@ -893,7 +901,7 @@
|
||||
const children = [];
|
||||
depth = depth + 1;
|
||||
let obj = this.getObjectProperty(path);
|
||||
let oldBranch = oldTree && this.getObjectInOldTree(oldTree, path, objType);
|
||||
let oldBranch = this.getObjectInOldTree(oldTree, path, objType);
|
||||
if (!obj) {
|
||||
return [];
|
||||
}
|
||||
@@ -908,7 +916,7 @@
|
||||
depth,
|
||||
objType,
|
||||
path,
|
||||
oldBranch?.children[0],
|
||||
oldBranch.children[0],
|
||||
oldTree
|
||||
);
|
||||
children.push(mapKey);
|
||||
@@ -918,7 +926,7 @@
|
||||
depth,
|
||||
objType,
|
||||
path,
|
||||
oldBranch?.children[1],
|
||||
oldBranch.children[1],
|
||||
oldTree
|
||||
);
|
||||
children.push(mapValue);
|
||||
@@ -929,7 +937,7 @@
|
||||
depth,
|
||||
objType,
|
||||
path,
|
||||
oldBranch?.children[0],
|
||||
oldBranch.children[0],
|
||||
oldTree
|
||||
);
|
||||
children.push(setValue);
|
||||
@@ -950,7 +958,7 @@
|
||||
depth,
|
||||
objType,
|
||||
path,
|
||||
oldBranch?.children[index],
|
||||
oldBranch.children[index],
|
||||
oldTree
|
||||
);
|
||||
if (child) {
|
||||
@@ -965,7 +973,7 @@
|
||||
depth,
|
||||
objType,
|
||||
path,
|
||||
oldBranch?.children[index],
|
||||
oldBranch.children[index],
|
||||
oldTree
|
||||
);
|
||||
if (child) {
|
||||
@@ -982,7 +990,7 @@
|
||||
depth,
|
||||
objType,
|
||||
path,
|
||||
oldBranch?.children[index],
|
||||
oldBranch.children[index],
|
||||
oldTree
|
||||
);
|
||||
if (entries) {
|
||||
@@ -996,7 +1004,7 @@
|
||||
depth,
|
||||
objType,
|
||||
path,
|
||||
oldBranch?.children[index],
|
||||
oldBranch.children[index],
|
||||
oldTree
|
||||
);
|
||||
if (child) {
|
||||
@@ -1031,7 +1039,7 @@
|
||||
depth,
|
||||
objType,
|
||||
path,
|
||||
oldBranch?.children[index],
|
||||
oldBranch.children[index],
|
||||
oldTree
|
||||
);
|
||||
if (child) children.push(child);
|
||||
@@ -1064,14 +1072,14 @@
|
||||
});
|
||||
proto = Object.getPrototypeOf(proto);
|
||||
}
|
||||
if (obj.__proto__) {
|
||||
if (!(obj.constructor.name === "Object")) {
|
||||
prototype = this.serializeObjectChild(
|
||||
obj,
|
||||
{ type: "prototype", childIndex: children.length },
|
||||
depth,
|
||||
objType,
|
||||
path,
|
||||
oldBranch?.children.at(-1),
|
||||
oldBranch.children.at(-1),
|
||||
oldTree
|
||||
);
|
||||
children.push(prototype);
|
||||
@@ -1446,11 +1454,8 @@
|
||||
return;
|
||||
}
|
||||
}
|
||||
const item = path.pop();
|
||||
const key = path.pop().value;
|
||||
const obj = this.getObjectProperty(path);
|
||||
const key = item.hasOwnProperty("symbolIndex")
|
||||
? Object.getOwnPropertySymbols(obj)[item.symbolIndex]
|
||||
: item.value;
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
@@ -1519,9 +1524,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
// If nothing was found, return the path of the first root component found in the apps
|
||||
const appIndex = [...this.apps].findIndex((app) => app.root);
|
||||
return [appIndex.toString(), "root"];
|
||||
// If nothing was found, return the first app's root component path
|
||||
return ["0", "root"];
|
||||
}
|
||||
// Returns the tree of components of the inspected page in a parsed format
|
||||
// Use inspectedPath to specify the path of the selected component
|
||||
@@ -1735,7 +1739,7 @@
|
||||
owlStatus = -1;
|
||||
}
|
||||
}
|
||||
window.postMessage({ source: "owl-devtools", type: "owlStatus", data: owlStatus });
|
||||
window.postMessage({ source: "owl-devtools", type: "owlStatus", data: owlStatus }, "/");
|
||||
}
|
||||
|
||||
if (!window.__OWL_DEVTOOLS__) {
|
||||
@@ -1757,19 +1761,19 @@
|
||||
if (value?.Fiber !== undefined) {
|
||||
window.__OWL__DEVTOOLS_GLOBAL_HOOK__ = new OwlDevtoolsGlobalHook();
|
||||
}
|
||||
window.top.postMessage({ source: "owl-devtools", type: "FrameReady" });
|
||||
window.top.postMessage({ source: "owl-devtools", type: "FrameReady" }, "/");
|
||||
checkOwlStatus();
|
||||
},
|
||||
});
|
||||
// Do note that the reload message is not sent on the top window so that it is not intercepted when originating
|
||||
// from an iframe
|
||||
window.postMessage({ source: "owl-devtools", type: "Reload" });
|
||||
window.postMessage({ source: "owl-devtools", type: "Reload" }, "/");
|
||||
} else if (
|
||||
window.__OWL_DEVTOOLS__?.Fiber !== undefined &&
|
||||
!window.__OWL__DEVTOOLS_GLOBAL_HOOK__
|
||||
) {
|
||||
window.__OWL__DEVTOOLS_GLOBAL_HOOK__ = new OwlDevtoolsGlobalHook();
|
||||
window.postMessage({ source: "owl-devtools", type: "Reload" });
|
||||
window.postMessage({ source: "owl-devtools", type: "Reload" }, "/");
|
||||
}
|
||||
// Listener that checks whether owl is available on the page and if it has the right version
|
||||
window.addEventListener(
|
||||
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
import type * as owl from "../../../src/runtime";
|
||||
import type { JestChrome } from "jest-chrome/types/jest-chrome";
|
||||
declare global {
|
||||
interface Window {
|
||||
owl: typeof owl;
|
||||
browser: any;
|
||||
chrome: JestChrome;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { mount } from "../../../src";
|
||||
import { makeTestFixture } from "../../../tests/helpers";
|
||||
import { DevtoolsWindow } from "../src/devtools_app/devtools_window/devtools_window";
|
||||
|
||||
let fixture = makeTestFixture();
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
});
|
||||
|
||||
describe("devtools", () => {
|
||||
test("mounting the devtools", async () => {
|
||||
await mount(DevtoolsWindow as any, fixture);
|
||||
expect(fixture.querySelector(".status-message")!.textContent).toContain("There are no apps currently running.")
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"testEnvironment": "jsdom",
|
||||
"roots": [
|
||||
"<rootDir>"
|
||||
],
|
||||
"setupFiles": [
|
||||
"<rootDir>/../../../tests/mocks/mockEventTarget.js",
|
||||
"<rootDir>/setup.ts"
|
||||
],
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"tsconfig": "<rootDir>/tsconfig.json"
|
||||
}
|
||||
},
|
||||
"transform": {
|
||||
"^.+\\.[jt]s?$": "ts-jest"
|
||||
},
|
||||
"verbose": false,
|
||||
"testRegex": "(/tests/.*(test|spec))\\.ts?$",
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import * as owl from "../../../src/runtime";
|
||||
window.owl = owl;
|
||||
import { chrome } from "jest-chrome";
|
||||
window.chrome = chrome;
|
||||
|
||||
import { templates } from "../assets/templates";
|
||||
for (const template in templates) {
|
||||
owl.App.registerTemplate(template, templates[template as keyof typeof templates]);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
{
|
||||
/**
|
||||
** Commented-out options have their default values.
|
||||
**/
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
// "exclude": [],
|
||||
// "files": [], // A list of relative or absolute file paths to include.
|
||||
// "extends": "", // A string containing a path to another configuration file to inherit from.
|
||||
// "references": [], // An array of objects `{"path": "./to/dirOrConfig"}` that specifies projects to reference.
|
||||
// "compileOnSave": false, // Signals to the IDE to generate all files for a given tsconfig.json upon saving.
|
||||
"compilerOptions": {
|
||||
// Main options
|
||||
"target": "es2019", // Specify ECMAScript target version: 'es3' (default), 'es5', 'es2015', 'es2016', 'es2017','es2018' or 'esnext'.
|
||||
"module": "es6", // Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.
|
||||
// "lib": ["esnext", "dom"], // Specify library files to be included in the compilation.
|
||||
"allowJs": true, // Allow javascript files to be compiled.
|
||||
// "checkJs": false, // Report errors in .js files.
|
||||
// "outFile": "./", // Concatenate and emit output to single file.
|
||||
"outDir": "dist", // Redirect output structure to the directory.
|
||||
// "rootDir": "./", // Specify the root directory of input files. Use to control the output directory structure with `--outDir`.
|
||||
// "project": "", // Compile a project given a valid configuration file.
|
||||
// Compilation options
|
||||
// "composite": true, // Enable project compilation
|
||||
// "diagnostics": false, // Show diagnostic information.
|
||||
// "incremental": true, // Enable incremental compilation by reading/writing information from prior compilations to a file on disk.
|
||||
// "isolatedModules": false, // Transpile each file as a separate module (similar to 'ts.transpileModule').
|
||||
// "listEmittedFiles": false, // Print names of generated files part of the compilation.
|
||||
// "listFiles": true, // Print names of files part of the compilation.
|
||||
// "noErrorTruncation": false, // Do not truncate error messages.
|
||||
// "preserveWatchOutput": false, // Keep outdated console output in watch mode instead of clearing the screen.
|
||||
// "traceResolution": false, // Enable tracing of the name resolution process.
|
||||
// "tsBuildInfoFile": ".tsbuildinfo", // Specify file to store incremental compilation information.
|
||||
// Strict typechecking options
|
||||
"strict": true, // Enable all strict type-checking options.
|
||||
// "noImplicitAny": true, // Raise error on expressions and declarations with an implied 'any' type.
|
||||
// "noImplicitThis": true, // Raise error on 'this' expressions with an implied 'any' type.
|
||||
// "strictBindCallApply": true, // Enable stricter checking of of the `bind`, `call`, and `apply` methods on functions.
|
||||
// "strictFunctionTypes": true, // Disable bivariant parameter checking for function types.
|
||||
// "strictNullChecks": true, // In strict null checking mode, the null and undefined values are not in the domain of every type and are only assignable to themselves and any.
|
||||
// "strictPropertyInitialization": true, // Ensure non-undefined class properties are initialized in the constructor. This option requires `--strictNullChecks` be enabled in order to take effect.
|
||||
// "alwaysStrict": true, // Parse in strict mode and emit "use strict" for each source file.
|
||||
// Additional checks
|
||||
// "allowUnreachableCode": false, // Do not report errors on unreachable code.
|
||||
// "allowUnusedLabels": false, // Do not report errors on unused labels.
|
||||
"forceConsistentCasingInFileNames": true, // Disallow inconsistently-cased references to the same file.
|
||||
// "noStrictGenericChecks": false, // Disable strict checking of generic signatures in function types.
|
||||
"noUnusedLocals": true, // Report errors on unused locals.
|
||||
"noUnusedParameters": false, // Report errors on unused parameters.
|
||||
"noImplicitReturns": true, // Report error when not all code paths in function return a value.
|
||||
"noFallthroughCasesInSwitch": true, // Report errors for fallthrough cases in switch statement.
|
||||
// "skipLibCheck": false, // Skip type checking of all declaration files (*.d.ts).
|
||||
// "suppressExcessPropertyErrors": false, // Suppress excess property checks for object literals.
|
||||
// "suppressImplicitAnyIndexErrors": false, // Suppress noImplicitAny errors for indexing objects lacking index signatures.
|
||||
// Module resolution options
|
||||
"moduleResolution": "node", // Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6).
|
||||
// "baseUrl": "./", // Base directory to resolve non-absolute module names.
|
||||
// "paths": {}, // A series of entries which re-map imports to lookup locations relative to the 'baseUrl'.
|
||||
// "rootDirs": [], // List of root folders whose combined content represents the structure of the project at runtime.
|
||||
// "typeRoots": [
|
||||
// "./node_modules/@types",
|
||||
// "./tools/devtools/tests"
|
||||
// ], // List of folders to include type definitions from.
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
], // Type declaration files to be included in compilation.
|
||||
// "allowSyntheticDefaultImports": false // Allow default imports from modules with no default export. This does not affect code emit, just typechecking.
|
||||
"esModuleInterop": true, // Emit '__importStar' and '__importDefault' helpers for runtime babel ecosystem compatibility and enable '--allowSyntheticDefaultImports' for typesystem compatibility.
|
||||
// "maxNodeModuleJsDepth": 0, // The maximum dependency depth to search under node_modules and load JavaScript files. Only applicable with --allowJs.
|
||||
// "preserveSymlinks": false, // Do not resolve the real path of symlinks.
|
||||
"resolveJsonModule": true, // Include modules imported with '.json' extension.
|
||||
// Emit options
|
||||
"declaration": true, // Generates corresponding '.d.ts' file.
|
||||
"declarationDir": "dist/types", // Output directory for generated declaration files.
|
||||
// "declarationMap": false, // Generates a sourcemap for each corresponding '.d.ts' file.
|
||||
// "emitBOM": false, // Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files.
|
||||
// "emitDeclarationOnly": false, // Only emit ‘.d.ts’ declaration files.
|
||||
// "importHelpers": false, // Import emit helpers from 'tslib'.
|
||||
// "newLine": "LF", // Use the specified end of line sequence to be used when emitting files: "crlf" (windows) or "lf" (unix).”
|
||||
// "noEmit": true, // Do not emit outputs.
|
||||
// "noEmitHelpers": false, // Do not generate custom helper functions like __extends in compiled output.
|
||||
// "noEmitOnError": false, // Do not emit outputs if any errors were reported.
|
||||
// "noImplicitUseStrict": false, // Do not emit "use strict" directives in module output.
|
||||
// "noResolve": false, // Do not add triple-slash references or module import targets to the list of compiled files.
|
||||
"preserveConstEnums": false, // Do not erase const enum declarations in generated code.
|
||||
// "removeComments": false, // Remove all comments except copy-right header comments beginning with
|
||||
// "experimentalDecorators": true, // Enables experimental support for ES7 decorators.
|
||||
// "emitDecoratorMetadata": true, // Enables experimental support for emitting type metadata for decorators.
|
||||
// Source map options
|
||||
// "sourceMap": false, // Generates corresponding '.map' file.
|
||||
// "sourceRoot": "", // Specify the location where debugger should locate TypeScript files instead of source locations.
|
||||
// "mapRoot": "", // Specify the location where debugger should locate map files instead of generated locations.
|
||||
// "inlineSourceMap": true, // Emit a single file with source maps instead of having a separate file.
|
||||
// "inlineSources": true, // Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set.
|
||||
// JSX options
|
||||
// "jsx": "preserve", // Specify JSX code generation: 'preserve', 'react-native', or 'react'.
|
||||
// "jsxFactory": "React.createElement", // Specify the JSX factory function to use when targeting react JSX emit, e.g. 'React.createElement' or 'h'.
|
||||
// Other options
|
||||
// "allowUmdGlobalAccess": true, // Allow accessing UMD globals from modules.
|
||||
// "charset": "utf8", // The character set of the input files.
|
||||
// "downlevelIteration": false, // Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'.
|
||||
// "disableSizeLimit": false, // Disable size limitation on JavaScript project.
|
||||
// "keyofStringsOnly": false, // Resolve 'keyof' to string valued property names only (no numbers or symbols).
|
||||
// "noLib": false, // Do not include the default library file (lib.d.ts).
|
||||
// "pretty": true, // Stylize errors and messages using color and context.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user