mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6373840aa1 |
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20.x, 22.x]
|
||||
node-version: [12.x, 14.x, 16.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
- [API](#api)
|
||||
- [Configuration](#configuration)
|
||||
- [`mount` helper](#mount-helper)
|
||||
- [Roots](#roots)
|
||||
- [Loading templates](#loading-templates)
|
||||
|
||||
## Overview
|
||||
@@ -93,33 +92,6 @@ Most of the time, the `mount` helper is more convenient, but whenever one needs
|
||||
a reference to the actual Owl App, then using the `App` class directly is
|
||||
possible.
|
||||
|
||||
## Roots
|
||||
|
||||
An application can have multiple roots. It is sometimes useful to instantiate
|
||||
sub components in places that are not managed by Owl, such as an html editor
|
||||
with dynamic content (the Knowledge application in Odoo).
|
||||
|
||||
To create a root, one can use the `createRoot` method, which takes two arguments:
|
||||
|
||||
- **`Component`**: a component class (Root component of the app)
|
||||
- **`config (optional)`**: a config object that may contain a `props` object or a
|
||||
`env` object.
|
||||
|
||||
The `createRoot` method returns an object with a `mount` method (same API as
|
||||
the `App.mount` method), and a `destroy` method.
|
||||
|
||||
```js
|
||||
const root = app.createRoot(MyComponent, { props: { someProps: true } });
|
||||
await root.mount(targetElement);
|
||||
|
||||
// later
|
||||
root.destroy();
|
||||
```
|
||||
|
||||
Note that, like with owl `App`, it is the responsibility of the code that created
|
||||
the root to properly destroy it (before it has been removed from the DOM!). Owl
|
||||
has no way of doing it itself.
|
||||
|
||||
## Loading templates
|
||||
|
||||
Most applications will need to load templates whenever they start. Here is
|
||||
|
||||
@@ -140,28 +140,6 @@ class SomeComponent extends Component {
|
||||
The `.bind` suffix also implies `.alike`, so these props will not cause additional
|
||||
renderings.
|
||||
|
||||
## Translatable props
|
||||
|
||||
When you need to pass a user-facing string to a subcomponent, you likely want it
|
||||
to be translated. Unfortunately, because props are arbitrary expressions, it wouldn't
|
||||
be practical for Owl to find out which parts of the expression are strings and translate
|
||||
them, and it also makes it difficult for tooling to extract these strings to generate
|
||||
terms to translate. While you can work around this issue by doing the translation in
|
||||
JavaScript, or by using `t-set` with a body (the body of `t-set` is translated),
|
||||
and passing the variable as a prop, this is a sufficiently common use case that Owl
|
||||
provides a suffix for this purpose: `.translate`.
|
||||
|
||||
```xml
|
||||
<t t-name="ParentComponent">
|
||||
<Child someProp.translate="some message"/>
|
||||
</t>
|
||||
```
|
||||
|
||||
Note that the content of this attribute is _NOT_ treated as a JavaScript expression:
|
||||
it is treated as a string, as if it was an attribute on an HTML element, and translated
|
||||
before being passed to the component. If you need to interpolate some data into the
|
||||
string, you will still have to do this in JavaScript.
|
||||
|
||||
## Dynamic Props
|
||||
|
||||
The `t-props` directive can be used to specify totally dynamic props:
|
||||
|
||||
@@ -201,17 +201,16 @@ use this `Notebook` component:
|
||||
|
||||
```xml
|
||||
<Notebook>
|
||||
<t t-set-slot="page1" title.translate="Page 1">
|
||||
<t t-set-slot="page1" title="'Page 1'">
|
||||
<div>this is in the page 1</div>
|
||||
</t>
|
||||
<t t-set-slot="page2" title.translate="Page 2" hidden="somevalue">
|
||||
<t t-set-slot="page2" title="'Page 2'" hidden="somevalue">
|
||||
<div>this is in the page 2</div>
|
||||
</t>
|
||||
</Notebook>
|
||||
```
|
||||
|
||||
Slot params works like normal props, so one can use suffixes like `.translate`
|
||||
when a prop is a user facing string and should be translated, or `.bind` to
|
||||
Slot params works like normal props, so one can use the `.bind` suffix to
|
||||
bind a function if needed.
|
||||
|
||||
## Slot scopes
|
||||
|
||||
+35
-78
@@ -2598,47 +2598,42 @@ class ComponentNode {
|
||||
}
|
||||
|
||||
const TIMEOUT = Symbol("timeout");
|
||||
const HOOK_TIMEOUT = {
|
||||
onWillStart: 3000,
|
||||
onWillUpdateProps: 3000,
|
||||
};
|
||||
function wrapError(fn, hookName) {
|
||||
const error = new OwlError();
|
||||
const timeoutError = new OwlError();
|
||||
const error = new OwlError(`The following error occurred in ${hookName}: `);
|
||||
const timeoutError = new OwlError(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
||||
const node = getCurrent();
|
||||
return (...args) => {
|
||||
const onError = (cause) => {
|
||||
error.cause = cause;
|
||||
error.message =
|
||||
cause instanceof Error
|
||||
? `The following error occurred in ${hookName}: "${cause.message}"`
|
||||
: `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
|
||||
if (cause instanceof Error) {
|
||||
error.message += `"${cause.message}"`;
|
||||
}
|
||||
else {
|
||||
error.message = `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
|
||||
}
|
||||
throw error;
|
||||
};
|
||||
let result;
|
||||
try {
|
||||
result = fn(...args);
|
||||
const result = fn(...args);
|
||||
if (result instanceof Promise) {
|
||||
if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
|
||||
const fiber = node.fiber;
|
||||
Promise.race([
|
||||
result.catch(() => { }),
|
||||
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
|
||||
]).then((res) => {
|
||||
if (res === TIMEOUT && node.fiber === fiber && node.status <= 2) {
|
||||
console.warn(timeoutError);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result.catch(onError);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (cause) {
|
||||
onError(cause);
|
||||
}
|
||||
if (!(result instanceof Promise)) {
|
||||
return result;
|
||||
}
|
||||
const timeout = HOOK_TIMEOUT[hookName];
|
||||
if (timeout) {
|
||||
const fiber = node.fiber;
|
||||
Promise.race([
|
||||
result.catch(() => { }),
|
||||
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), timeout)),
|
||||
]).then((res) => {
|
||||
if (res === TIMEOUT && node.fiber === fiber && node.status <= 2) {
|
||||
timeoutError.message = `${hookName}'s promise hasn't resolved after ${timeout / 1000} seconds`;
|
||||
console.log(timeoutError);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result.catch(onError);
|
||||
};
|
||||
}
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -4599,12 +4594,7 @@ class CodeGenerator {
|
||||
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
|
||||
*/
|
||||
formatProp(name, value) {
|
||||
if (name.endsWith(".translate")) {
|
||||
value = toStringExpression(this.translateFn(value));
|
||||
}
|
||||
else {
|
||||
value = this.captureExpression(value);
|
||||
}
|
||||
value = this.captureExpression(value);
|
||||
if (name.includes(".")) {
|
||||
let [_name, suffix] = name.split(".");
|
||||
name = _name;
|
||||
@@ -4613,7 +4603,6 @@ class CodeGenerator {
|
||||
value = `(${value}).bind(this)`;
|
||||
break;
|
||||
case "alike":
|
||||
case "translate":
|
||||
break;
|
||||
default:
|
||||
throw new OwlError("Invalid prop suffix");
|
||||
@@ -5557,7 +5546,7 @@ function compile(template, options = {}) {
|
||||
}
|
||||
|
||||
// do not modify manually. This file is generated by the release script.
|
||||
const version = "2.4.0";
|
||||
const version = "2.2.11";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Scheduler
|
||||
@@ -5652,7 +5641,6 @@ class App extends TemplateSet {
|
||||
constructor(Root, config = {}) {
|
||||
super(config);
|
||||
this.scheduler = new Scheduler();
|
||||
this.subRoots = new Set();
|
||||
this.root = null;
|
||||
this.name = config.name || "";
|
||||
this.Root = Root;
|
||||
@@ -5671,42 +5659,14 @@ class App extends TemplateSet {
|
||||
this.props = config.props || {};
|
||||
}
|
||||
mount(target, options) {
|
||||
const root = this.createRoot(this.Root, { props: this.props });
|
||||
this.root = root.node;
|
||||
this.subRoots.delete(root.node);
|
||||
return root.mount(target, options);
|
||||
}
|
||||
createRoot(Root, config = {}) {
|
||||
const props = config.props || {};
|
||||
// hack to make sure the sub root get the sub env if necessary. for owl 3,
|
||||
// would be nice to rethink the initialization process to make sure that
|
||||
// we can create a ComponentNode and give it explicitely the env, instead
|
||||
// of looking it up in the app
|
||||
const env = this.env;
|
||||
if (config.env) {
|
||||
this.env = config.env;
|
||||
App.validateTarget(target);
|
||||
if (this.dev) {
|
||||
validateProps(this.Root, this.props, { __owl__: { app: this } });
|
||||
}
|
||||
const node = this.makeNode(Root, props);
|
||||
if (config.env) {
|
||||
this.env = env;
|
||||
}
|
||||
this.subRoots.add(node);
|
||||
return {
|
||||
node,
|
||||
mount: (target, options) => {
|
||||
App.validateTarget(target);
|
||||
if (this.dev) {
|
||||
validateProps(Root, props, { __owl__: { app: this } });
|
||||
}
|
||||
const prom = this.mountNode(node, target, options);
|
||||
return prom;
|
||||
},
|
||||
destroy: () => {
|
||||
this.subRoots.delete(node);
|
||||
node.destroy();
|
||||
this.scheduler.processTasks();
|
||||
},
|
||||
};
|
||||
const node = this.makeNode(this.Root, this.props);
|
||||
const prom = this.mountNode(node, target, options);
|
||||
this.root = node;
|
||||
return prom;
|
||||
}
|
||||
makeNode(Component, props) {
|
||||
return new ComponentNode(Component, props, this, null, null);
|
||||
@@ -5738,9 +5698,6 @@ class App extends TemplateSet {
|
||||
}
|
||||
destroy() {
|
||||
if (this.root) {
|
||||
for (let subroot of this.subRoots) {
|
||||
subroot.destroy();
|
||||
}
|
||||
this.root.destroy();
|
||||
this.scheduler.processTasks();
|
||||
}
|
||||
@@ -6018,6 +5975,6 @@ TemplateSet.prototype._compileTemplate = function _compileTemplate(name, templat
|
||||
export { App, Component, EventBus, OwlError, __info__, batched, 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 = '2024-09-30T08:49:29.420Z';
|
||||
__info__.hash = 'eb2b32a';
|
||||
__info__.date = '2024-06-17T13:31:12.099Z';
|
||||
__info__.hash = 'e7f405c';
|
||||
__info__.url = 'https://github.com/odoo/owl';
|
||||
|
||||
Generated
+207
-32
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "2.4.0",
|
||||
"version": "2.2.11",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -127,25 +127,6 @@
|
||||
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz",
|
||||
"integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/types": "^7.21.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-hoist-variables": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
|
||||
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.18.6"
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-imports": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
|
||||
@@ -428,28 +409,222 @@
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz",
|
||||
"integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz",
|
||||
"integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.21.3",
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-function-name": "^7.21.0",
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/parser": "^7.21.3",
|
||||
"@babel/types": "^7.21.3",
|
||||
"debug": "^4.1.0",
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/generator": "^7.24.7",
|
||||
"@babel/helper-environment-visitor": "^7.24.7",
|
||||
"@babel/helper-function-name": "^7.24.7",
|
||||
"@babel/helper-hoist-variables": "^7.24.7",
|
||||
"@babel/helper-split-export-declaration": "^7.24.7",
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/types": "^7.24.7",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
|
||||
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.24.7",
|
||||
"picocolors": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz",
|
||||
"integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.24.7",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^2.5.1"
|
||||
}
|
||||
},
|
||||
"@babel/helper-environment-visitor": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
|
||||
"integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.24.7"
|
||||
}
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
|
||||
"integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/template": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
}
|
||||
},
|
||||
"@babel/helper-hoist-variables": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
|
||||
"integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.24.7"
|
||||
}
|
||||
},
|
||||
"@babel/helper-split-export-declaration": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
|
||||
"integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.24.7"
|
||||
}
|
||||
},
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz",
|
||||
"integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
|
||||
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz",
|
||||
"integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
|
||||
"integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz",
|
||||
"integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.24.7",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@jridgewell/gen-mapping": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"@jridgewell/set-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true
|
||||
},
|
||||
"globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "2.4.0",
|
||||
"version": "2.2.11",
|
||||
"description": "Odoo Web Library (OWL)",
|
||||
"main": "dist/owl.cjs.js",
|
||||
"module": "dist/owl.es.js",
|
||||
@@ -9,7 +9,7 @@
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
"node": ">=12.18.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build:bundle": "rollup -c --failAfterWarnings",
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
interpolate,
|
||||
INTERP_REGEXP,
|
||||
replaceDynamicParts,
|
||||
} from "../common/inline_expressions";
|
||||
} from "./inline_expressions";
|
||||
import {
|
||||
AST,
|
||||
ASTComment,
|
||||
@@ -1136,11 +1136,7 @@ export class CodeGenerator {
|
||||
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
|
||||
*/
|
||||
formatProp(name: string, value: string): string {
|
||||
if (name.endsWith(".translate")) {
|
||||
value = toStringExpression(this.translateFn(value));
|
||||
} else {
|
||||
value = this.captureExpression(value);
|
||||
}
|
||||
value = this.captureExpression(value);
|
||||
if (name.includes(".")) {
|
||||
let [_name, suffix] = name.split(".");
|
||||
name = _name;
|
||||
@@ -1149,7 +1145,6 @@ export class CodeGenerator {
|
||||
value = `(${value}).bind(this)`;
|
||||
break;
|
||||
case "alike":
|
||||
case "translate":
|
||||
break;
|
||||
default:
|
||||
throw new OwlError("Invalid prop suffix");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OwlError } from "./owl_error";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
|
||||
/**
|
||||
* Owl QWeb Expression Parser
|
||||
+19
-87
@@ -1,14 +1,14 @@
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { version } from "../version";
|
||||
import { Component, ComponentConstructor, Props } from "./component";
|
||||
import { ComponentNode, saveCurrent } from "./component_node";
|
||||
import { handleError, nodeErrorHandlers } from "./error_handling";
|
||||
import { Fiber, MountOptions, RootFiber } from "./fibers";
|
||||
import { reactive, toRaw } from "./reactivity";
|
||||
import { ComponentNode } from "./component_node";
|
||||
import { nodeErrorHandlers, handleError } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
import { Fiber, RootFiber, MountOptions } from "./fibers";
|
||||
import { Scheduler } from "./scheduler";
|
||||
import { validateProps } from "./template_helpers";
|
||||
import { TemplateSet, TemplateSetConfig } from "./template_set";
|
||||
import { validateTarget } from "./utils";
|
||||
import { toRaw, reactive } from "./reactivity";
|
||||
|
||||
// reimplement dev mode stuff see last change in 0f7a8289a6fb8387c3c1af41c6664b2a8448758f
|
||||
|
||||
@@ -16,13 +16,10 @@ export interface Env {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface RootConfig<P, E> {
|
||||
export interface AppConfig<P, E> extends TemplateSetConfig {
|
||||
name?: string;
|
||||
props?: P;
|
||||
env?: E;
|
||||
}
|
||||
|
||||
export interface AppConfig<P, E> extends TemplateSetConfig, RootConfig<P, E> {
|
||||
name?: string;
|
||||
test?: boolean;
|
||||
warnIfNoStaticProps?: boolean;
|
||||
}
|
||||
@@ -52,12 +49,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
interface Root<P extends Props, E> {
|
||||
node: ComponentNode<P, E> | null;
|
||||
mount(target: HTMLElement | ShadowRoot, options?: MountOptions): Promise<Component<P, E>>;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
window.__OWL_DEVTOOLS__ ||= { apps, Fiber, RootFiber, toRaw, reactive };
|
||||
|
||||
export class App<
|
||||
@@ -74,10 +65,8 @@ export class App<
|
||||
props: P;
|
||||
env: E;
|
||||
scheduler = new Scheduler();
|
||||
subRoots: Set<Root<any, any>> = new Set();
|
||||
root: Root<P, E> | null = null;
|
||||
root: ComponentNode<P, E> | null = null;
|
||||
warnIfNoStaticProps: boolean;
|
||||
_lastRootEl: HTMLElement | ShadowRoot | null = null; // temporary ref to propagate to roots
|
||||
|
||||
constructor(Root: ComponentConstructor<P, E>, config: AppConfig<P, E> = {}) {
|
||||
super(config);
|
||||
@@ -102,64 +91,14 @@ export class App<
|
||||
target: HTMLElement | ShadowRoot,
|
||||
options?: MountOptions
|
||||
): Promise<Component<P, E> & InstanceType<T>> {
|
||||
this.root = this.createRoot(this.Root, { props: this.props });
|
||||
return this.root.mount(target, options) as any;
|
||||
}
|
||||
|
||||
createRoot<Props extends object, SubEnv = any>(
|
||||
Root: ComponentConstructor<Props, E>,
|
||||
config: RootConfig<Props, SubEnv> = {}
|
||||
): Root<Props, SubEnv> {
|
||||
const props = config.props || ({} as Props);
|
||||
const env = this.env;
|
||||
const root: Root<Props, SubEnv> = {
|
||||
node: null,
|
||||
mount: (target: HTMLElement | ShadowRoot, options?: MountOptions) => {
|
||||
App.validateTarget(target);
|
||||
|
||||
// hack to make sure the sub root get the sub env if necessary. for owl 3,
|
||||
// would be nice to rethink the initialization process to make sure that
|
||||
// we can create a ComponentNode and give it explicitely the env, instead
|
||||
// of looking it up in the app
|
||||
if (config.env) {
|
||||
this.env = config.env as any;
|
||||
}
|
||||
const restore = saveCurrent();
|
||||
if (options?.position === "attach") {
|
||||
if (Root.template) {
|
||||
throw new Error("Cannot attach a component with a template");
|
||||
}
|
||||
this._lastRootEl = target;
|
||||
} else {
|
||||
if (!Root.template) {
|
||||
// no template => trigger an error
|
||||
this.getTemplate("");
|
||||
}
|
||||
}
|
||||
const node = this.makeNode(Root, props);
|
||||
root.node = node;
|
||||
this._lastRootEl = null;
|
||||
restore();
|
||||
if (config.env) {
|
||||
this.env = env;
|
||||
}
|
||||
if (this.dev) {
|
||||
validateProps(Root, props, { __owl__: { app: this } });
|
||||
}
|
||||
const prom = this.mountNode(node, target, options);
|
||||
return prom;
|
||||
},
|
||||
destroy: () => {
|
||||
this.subRoots.delete(root);
|
||||
if (root.node) {
|
||||
root.node?.destroy();
|
||||
this.scheduler.processTasks();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
this.subRoots.add(root);
|
||||
return root;
|
||||
App.validateTarget(target);
|
||||
if (this.dev) {
|
||||
validateProps(this.Root, this.props, { __owl__: { app: this } });
|
||||
}
|
||||
const node = this.makeNode(this.Root, this.props);
|
||||
const prom = this.mountNode(node, target, options);
|
||||
this.root = node;
|
||||
return prom;
|
||||
}
|
||||
|
||||
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
|
||||
@@ -194,17 +133,10 @@ export class App<
|
||||
}
|
||||
|
||||
destroy() {
|
||||
const roots = [...this.subRoots].reverse();
|
||||
for (let root of roots) {
|
||||
root.destroy();
|
||||
if (this.root) {
|
||||
this.root.destroy();
|
||||
this.scheduler.processTasks();
|
||||
}
|
||||
// if (this.root) {
|
||||
// for (let subroot of this.subRoots) {
|
||||
// subroot.destroy();
|
||||
// }
|
||||
// this.root.destroy();
|
||||
this.scheduler.processTasks();
|
||||
// }
|
||||
apps.delete(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ export type Props = { [key: string]: any };
|
||||
|
||||
interface StaticComponentProperties {
|
||||
template: string;
|
||||
dynamicContent?: { [spec: string]: string };
|
||||
defaultProps?: any;
|
||||
props?: Schema;
|
||||
components?: { [componentName: string]: ComponentConstructor };
|
||||
|
||||
@@ -6,19 +6,10 @@ import { OwlError } from "../common/owl_error";
|
||||
import { Fiber, makeChildFiber, makeRootFiber, MountFiber, MountOptions } from "./fibers";
|
||||
import { clearReactivesForCallback, getSubscriptions, reactive, targets } from "./reactivity";
|
||||
import { STATUS } from "./status";
|
||||
import { batched, Callback, Markup } from "./utils";
|
||||
import { xml } from "./template_set";
|
||||
import { compileExpr } from "../common/inline_expressions";
|
||||
import { batched, Callback } from "./utils";
|
||||
|
||||
let currentNode: ComponentNode | null = null;
|
||||
|
||||
export function saveCurrent() {
|
||||
let n = currentNode;
|
||||
return () => {
|
||||
currentNode = n;
|
||||
};
|
||||
}
|
||||
|
||||
export function getCurrent(): ComponentNode {
|
||||
if (!currentNode) {
|
||||
throw new OwlError("No active component (a hook function should only be called in 'setup')");
|
||||
@@ -126,102 +117,11 @@ 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 });
|
||||
if (C.template) {
|
||||
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
|
||||
} else {
|
||||
// component will be attached
|
||||
this.renderFn = app.getTemplate(xml``).bind(this.component, ctx, this);
|
||||
if (C.dynamicContent) {
|
||||
this.prepareAttach(app._lastRootEl!, C.dynamicContent, ctx);
|
||||
}
|
||||
}
|
||||
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
|
||||
this.component.setup();
|
||||
currentNode = null;
|
||||
}
|
||||
|
||||
prepareAttach(
|
||||
el: HTMLElement | ShadowRoot,
|
||||
dynamicContent: { [spec: string]: string },
|
||||
ctx: any
|
||||
) {
|
||||
const attrs: { selector: string; attr: string; fn: Function }[] = [];
|
||||
const handlers: { selector: string; event: string; fn: any }[] = [];
|
||||
const tOuts: { selector: string; fn: Function }[] = [];
|
||||
for (let key in dynamicContent) {
|
||||
const value = dynamicContent[key];
|
||||
const parts = key.split(":");
|
||||
if (parts[1].startsWith("t-att-")) {
|
||||
const attr = parts[1].slice(6);
|
||||
const fn = new Function("ctx", `return ${compileExpr(value)};`);
|
||||
attrs.push({
|
||||
selector: parts[0],
|
||||
attr,
|
||||
fn,
|
||||
});
|
||||
}
|
||||
if (parts[1].startsWith("t-on-")) {
|
||||
const event = parts[1].slice(5);
|
||||
// const fn = new Function("ctx", "ev", `${compileExpr(value)}(ev);`);
|
||||
const fn = (ev: any) => (this as any).component[value](ev);
|
||||
handlers.push({
|
||||
selector: parts[0],
|
||||
event,
|
||||
fn,
|
||||
});
|
||||
}
|
||||
if (parts[1] === "t-out") {
|
||||
const fn = new Function("ctx", `return ${compileExpr(value)};`);
|
||||
tOuts.push({ selector: parts[0], fn });
|
||||
}
|
||||
}
|
||||
const handleAttrs = () => {
|
||||
for (let attr of attrs) {
|
||||
const val = attr.fn.call(this.component, ctx);
|
||||
// todo: cache the queryselector result?
|
||||
const target = attr.selector === "root" ? el : (el.querySelector(attr.selector) as any);
|
||||
if (target) {
|
||||
target.setAttribute(attr.attr, val);
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleEvents = () => {
|
||||
for (let handler of handlers) {
|
||||
// const val = attr.fn.call(this.component, ctx);
|
||||
// todo: cache the queryselector result?
|
||||
const target =
|
||||
handler.selector === "root" ? el : (el.querySelector(handler.selector) as any);
|
||||
if (target) {
|
||||
target.addEventListener(handler.event, handler.fn);
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleTOuts = () => {
|
||||
for (let tOut of tOuts) {
|
||||
const val = tOut.fn.call(this.component, ctx);
|
||||
// todo: cache the queryselector result?
|
||||
const target = tOut.selector === "root" ? el : (el.querySelector(tOut.selector) as any);
|
||||
if (target) {
|
||||
if (val instanceof Markup) {
|
||||
target.innerHTML = val as any;
|
||||
} else {
|
||||
target.textContent = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (attrs.length) {
|
||||
this.mounted.push(handleAttrs);
|
||||
this.patched.push(handleAttrs);
|
||||
}
|
||||
if (handlers.length) {
|
||||
this.mounted.push(handleEvents);
|
||||
}
|
||||
if (tOuts.length) {
|
||||
this.mounted.push(handleTOuts);
|
||||
this.patched.push(handleTOuts);
|
||||
}
|
||||
}
|
||||
|
||||
mountComponent(target: any, options?: MountOptions) {
|
||||
const fiber = new MountFiber(this, target, options);
|
||||
this.app.scheduler.addFiber(fiber);
|
||||
|
||||
@@ -207,7 +207,7 @@ export class RootFiber extends Fiber {
|
||||
}
|
||||
}
|
||||
|
||||
type Position = "first-child" | "last-child" | "attach";
|
||||
type Position = "first-child" | "last-child";
|
||||
|
||||
export interface MountOptions {
|
||||
position?: Position;
|
||||
|
||||
@@ -136,20 +136,3 @@ export function useExternalListener(
|
||||
onMounted(() => target.addEventListener(eventName, boundHandler, eventParams));
|
||||
onWillUnmount(() => target.removeEventListener(eventName, boundHandler, eventParams));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// useAttachedEl
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The purpose of this hook is to allow attached components to get a reference to
|
||||
* the element they have been attached on.
|
||||
*/
|
||||
export function useAttachedEl(): HTMLElement {
|
||||
const node = getCurrent();
|
||||
const el = node.app._lastRootEl as HTMLElement;
|
||||
if (!el) {
|
||||
throw new Error("useAttachedEl can only be called with component that are attached");
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
@@ -3,50 +3,42 @@ import { nodeErrorHandlers } from "./error_handling";
|
||||
import { OwlError } from "../common/owl_error";
|
||||
|
||||
const TIMEOUT = Symbol("timeout");
|
||||
const HOOK_TIMEOUT: { [key: string]: number } = {
|
||||
onWillStart: 3000,
|
||||
onWillUpdateProps: 3000,
|
||||
};
|
||||
function wrapError(fn: (...args: any[]) => any, hookName: string) {
|
||||
const error = new OwlError() as Error & {
|
||||
const error = new OwlError(`The following error occurred in ${hookName}: `) as Error & {
|
||||
cause: any;
|
||||
};
|
||||
const timeoutError = new OwlError();
|
||||
const timeoutError = new OwlError(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
||||
const node = getCurrent();
|
||||
return (...args: any[]) => {
|
||||
const onError = (cause: any) => {
|
||||
error.cause = cause;
|
||||
error.message =
|
||||
cause instanceof Error
|
||||
? `The following error occurred in ${hookName}: "${cause.message}"`
|
||||
: `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
|
||||
if (cause instanceof Error) {
|
||||
error.message += `"${cause.message}"`;
|
||||
} else {
|
||||
error.message = `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
|
||||
}
|
||||
throw error;
|
||||
};
|
||||
let result;
|
||||
try {
|
||||
result = fn(...args);
|
||||
const result = fn(...args);
|
||||
if (result instanceof Promise) {
|
||||
if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
|
||||
const fiber = node.fiber;
|
||||
Promise.race([
|
||||
result.catch(() => {}),
|
||||
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
|
||||
]).then((res) => {
|
||||
if (res === TIMEOUT && node.fiber === fiber && node.status <= 2) {
|
||||
console.warn(timeoutError);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result.catch(onError);
|
||||
}
|
||||
return result;
|
||||
} catch (cause) {
|
||||
onError(cause);
|
||||
}
|
||||
if (!(result instanceof Promise)) {
|
||||
return result;
|
||||
}
|
||||
const timeout = HOOK_TIMEOUT[hookName];
|
||||
if (timeout) {
|
||||
const fiber = node.fiber;
|
||||
Promise.race([
|
||||
result.catch(() => {}),
|
||||
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), timeout)),
|
||||
]).then((res) => {
|
||||
if (res === TIMEOUT && node.fiber === fiber && node.status <= 2) {
|
||||
timeoutError.message = `${hookName}'s promise hasn't resolved after ${
|
||||
timeout / 1000
|
||||
} seconds`;
|
||||
console.log(timeoutError);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result.catch(onError);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ export class Scheduler {
|
||||
frame: number = 0;
|
||||
delayedRenders: Fiber[] = [];
|
||||
cancelledNodes: Set<ComponentNode> = new Set();
|
||||
processing = false;
|
||||
|
||||
constructor() {
|
||||
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
||||
@@ -54,10 +53,6 @@ export class Scheduler {
|
||||
}
|
||||
|
||||
processTasks() {
|
||||
if (this.processing) {
|
||||
return;
|
||||
}
|
||||
this.processing = true;
|
||||
this.frame = 0;
|
||||
for (let node of this.cancelledNodes) {
|
||||
node._destroy();
|
||||
@@ -71,7 +66,6 @@ export class Scheduler {
|
||||
this.tasks.delete(task);
|
||||
}
|
||||
}
|
||||
this.processing = false;
|
||||
}
|
||||
|
||||
processFiber(fiber: RootFiber) {
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
// do not modify manually. This file is generated by the release script.
|
||||
export const version = "2.4.0";
|
||||
export const version = "2.2.11";
|
||||
|
||||
@@ -43,33 +43,6 @@ exports[`app app: clear scheduler tasks and destroy cancelled nodes immediately
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app can call processTask twice in a row without crashing 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = text(\`parent\`);
|
||||
const b3 = comp1({}, key + \`__1\`, node, this, null);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app can call processTask twice in a row without crashing 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`app can configure an app with props 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`destroy a subroot while another component is mounted in main app 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`ChildB\`, true, false, false, []);
|
||||
const comp2 = app.createComponent(\`ChildA\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2, b3;
|
||||
if (ctx['state'].flag) {
|
||||
b2 = comp1({}, key + \`__1\`, node, this, null);
|
||||
} else {
|
||||
b3 = comp2({}, key + \`__2\`, node, this, null);
|
||||
}
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`destroy a subroot while another component is mounted in main app 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block3 = createBlock(\`<div block-ref=\\"0\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = text(\`a\`);
|
||||
let ref1 = (el) => this.__owl__.setRef((\`elem\`), el);
|
||||
const b3 = block3([ref1]);
|
||||
return multi([b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`destroy a subroot while another component is mounted in main app 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[`destroy a subroot while another component is mounted in main app 4`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`b\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot by default, env is the same in sub root 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>main app</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot by default, env is the same in sub root 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>sub root</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot can create a root in a setup function, then use a hook 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`a\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot can mount subroot 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>main app</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot can mount subroot 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>sub root</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot can mount subroot inside own dom 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>main app</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot can mount subroot inside own dom 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>sub root</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot env can be specified for sub roots 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>main app</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot env can be specified for sub roots 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>sub root</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot subcomponents can be destroyed, and it properly cleanup the subroots 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>main app</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`subroot subcomponents can be destroyed, and it properly cleanup the subroots 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>sub root</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
+1
-18
@@ -1,4 +1,4 @@
|
||||
import { App, Component, mount, onWillPatch, onWillStart, useState, xml } from "../../src";
|
||||
import { App, Component, mount, onWillStart, useState, xml } from "../../src";
|
||||
import { status } from "../../src/runtime/status";
|
||||
import {
|
||||
makeTestFixture,
|
||||
@@ -184,21 +184,4 @@ describe("app", () => {
|
||||
expect(Object.keys(app.templates)).toEqual(["hello"]);
|
||||
expect(Object.keys(app.rawTemplates)).toEqual(["hello", "world"]);
|
||||
});
|
||||
|
||||
test("can call processTask twice in a row without crashing", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<div/>`;
|
||||
setup() {
|
||||
onWillPatch(() => app.scheduler.processTasks());
|
||||
}
|
||||
}
|
||||
class SomeComponent extends Component {
|
||||
static template = xml`parent<Child/>`;
|
||||
static components = { Child };
|
||||
}
|
||||
|
||||
const app = new App(SomeComponent);
|
||||
await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("parent<div></div>");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
import { App, Component, onMounted, onWillDestroy, useRef, useState, xml } from "../../src";
|
||||
import { status } from "../../src/runtime/status";
|
||||
import { makeTestFixture, nextTick, snapshotEverything } from "../helpers";
|
||||
|
||||
let fixture: HTMLElement;
|
||||
|
||||
snapshotEverything();
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
});
|
||||
|
||||
class SomeComponent extends Component {
|
||||
static template = xml`<div>main app</div>`;
|
||||
}
|
||||
|
||||
class SubComponent extends Component {
|
||||
static template = xml`<div>sub root</div>`;
|
||||
}
|
||||
|
||||
describe("subroot", () => {
|
||||
test("can mount subroot", async () => {
|
||||
const app = new App(SomeComponent);
|
||||
const comp = await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>main app</div>");
|
||||
const subRoot = app.createRoot(SubComponent);
|
||||
const subcomp = await subRoot.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>main app</div><div>sub root</div>");
|
||||
|
||||
app.destroy();
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
expect(status(comp)).toBe("destroyed");
|
||||
expect(status(subcomp)).toBe("destroyed");
|
||||
});
|
||||
|
||||
test("can mount subroot inside own dom", async () => {
|
||||
const app = new App(SomeComponent);
|
||||
const comp = await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>main app</div>");
|
||||
const subRoot = app.createRoot(SubComponent);
|
||||
const subcomp = await subRoot.mount(fixture.querySelector("div")!);
|
||||
expect(fixture.innerHTML).toBe("<div>main app<div>sub root</div></div>");
|
||||
|
||||
app.destroy();
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
expect(status(comp)).toBe("destroyed");
|
||||
expect(status(subcomp)).toBe("destroyed");
|
||||
});
|
||||
|
||||
test("by default, env is the same in sub root", async () => {
|
||||
let env, subenv;
|
||||
class SC extends SomeComponent {
|
||||
setup() {
|
||||
env = this.env;
|
||||
}
|
||||
}
|
||||
class Sub extends SubComponent {
|
||||
setup() {
|
||||
subenv = this.env;
|
||||
}
|
||||
}
|
||||
|
||||
const app = new App(SC);
|
||||
await app.mount(fixture);
|
||||
const subRoot = app.createRoot(Sub);
|
||||
await subRoot.mount(fixture);
|
||||
|
||||
expect(env).toBeDefined();
|
||||
expect(subenv).toBeDefined();
|
||||
expect(env).toBe(subenv);
|
||||
});
|
||||
|
||||
test("env can be specified for sub roots", async () => {
|
||||
const env1 = { env1: true };
|
||||
const env2 = {};
|
||||
let someComponentEnv: any, subComponentEnv: any;
|
||||
class SC extends SomeComponent {
|
||||
setup() {
|
||||
someComponentEnv = this.env;
|
||||
}
|
||||
}
|
||||
class Sub extends SubComponent {
|
||||
setup() {
|
||||
subComponentEnv = this.env;
|
||||
}
|
||||
}
|
||||
|
||||
const app = new App(SC, { env: env1 });
|
||||
await app.mount(fixture);
|
||||
const subRoot = app.createRoot(Sub, { env: env2 });
|
||||
await subRoot.mount(fixture);
|
||||
|
||||
// because env is different in app => it is given a sub object, frozen and all
|
||||
// not sure it is a good idea, but it's the way owl 2 works. maybe we should
|
||||
// avoid doing anything with the main env and let user code do it if they
|
||||
// want. in that case, we can change the test here to assert that they are equal
|
||||
expect(someComponentEnv).not.toBe(env1);
|
||||
expect(someComponentEnv!.env1).toBe(true);
|
||||
expect(subComponentEnv).toBe(env2);
|
||||
});
|
||||
|
||||
test("subcomponents can be destroyed, and it properly cleanup the subroots", async () => {
|
||||
const app = new App(SomeComponent);
|
||||
const comp = await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>main app</div>");
|
||||
const root = app.createRoot(SubComponent);
|
||||
const subcomp = await root.mount(fixture.querySelector("div")!);
|
||||
expect(fixture.innerHTML).toBe("<div>main app<div>sub root</div></div>");
|
||||
|
||||
root.destroy();
|
||||
expect(fixture.innerHTML).toBe("<div>main app</div>");
|
||||
expect(status(comp)).not.toBe("destroyed");
|
||||
expect(status(subcomp)).toBe("destroyed");
|
||||
});
|
||||
|
||||
test("can create a root in a setup function, then use a hook", async () => {
|
||||
class C extends Component {
|
||||
static template = xml`c`;
|
||||
}
|
||||
|
||||
class A extends Component {
|
||||
static template = xml`a`;
|
||||
state: any;
|
||||
setup() {
|
||||
app.createRoot(C);
|
||||
this.state = useState({ value: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
const app = new App(A);
|
||||
await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("a");
|
||||
});
|
||||
});
|
||||
|
||||
test("destroy a subroot while another component is mounted in main app", async () => {
|
||||
class C extends Component {
|
||||
static template = xml`c`;
|
||||
}
|
||||
|
||||
class ChildA extends Component {
|
||||
static template = xml`a<div t-ref="elem"></div>`;
|
||||
ref: any;
|
||||
setup() {
|
||||
this.ref = useRef("elem");
|
||||
let root = app.createRoot(C);
|
||||
onMounted(() => {
|
||||
root.mount(this.ref.el);
|
||||
});
|
||||
onWillDestroy(() => {
|
||||
root.destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
class ChildB extends Component {
|
||||
static template = xml`b`;
|
||||
}
|
||||
|
||||
class SomeComponent extends Component {
|
||||
static template = xml`
|
||||
<t t-if="state.flag"><ChildB/></t>
|
||||
<t t-else=""><ChildA/></t>
|
||||
`;
|
||||
static components = { ChildA, ChildB };
|
||||
state = useState({ flag: false });
|
||||
}
|
||||
|
||||
const app = new App(SomeComponent);
|
||||
const comp = await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("a<div></div>");
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("a<div>c</div>");
|
||||
comp.state.flag = true;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("b");
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { compileExpr, tokenize } from "../../src/common/inline_expressions";
|
||||
import { compileExpr, tokenize } from "../../src/compiler/inline_expressions";
|
||||
|
||||
describe("tokenizer", () => {
|
||||
test("simple tokens", () => {
|
||||
|
||||
@@ -48,7 +48,7 @@ describe("basic validation", () => {
|
||||
test("compilation error", () => {
|
||||
const template = `<div t-att-class="a b">test</div>`;
|
||||
expect(() => renderToString(template))
|
||||
.toThrow(`Failed to compile anonymous template: Unexpected identifier 'ctx'
|
||||
.toThrow(`Failed to compile anonymous template: Unexpected identifier
|
||||
|
||||
generated code:
|
||||
function(app, bdom, helpers) {
|
||||
|
||||
@@ -97,19 +97,6 @@ exports[`basics a component cannot be mounted in a detached node (even if node i
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics a component cannot be mounted in a detached node 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics a component inside a component 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -274,19 +261,6 @@ exports[`basics can mount a simple component with props 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics cannot mount on a documentFragment 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div>content</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics child can be updated 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -1028,19 +1002,6 @@ exports[`basics three level of components with collapsing root nodes 3`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics throws if mounting on target=null 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<span>simple vnode</span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics two child components 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -332,31 +332,6 @@ exports[`lifecycle hooks lifecycle semantics, part 3 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks lifecycle semantics, part 3 3`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`GrandChild\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return comp1({}, key + \`__1\`, node, this, null);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks lifecycle semantics, part 3 4`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks lifecycle semantics, part 4 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -708,7 +683,7 @@ exports[`lifecycle hooks sub widget (inside sub node): hooks are correctly calle
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks timeout in onWillStart doesn't emit a console log if app is destroyed 1`] = `
|
||||
exports[`lifecycle hooks timeout in onWillStart doesn't emit a warning if app is destroyed 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
@@ -721,7 +696,7 @@ exports[`lifecycle hooks timeout in onWillStart doesn't emit a console log if ap
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks timeout in onWillStart emits a console log 1`] = `
|
||||
exports[`lifecycle hooks timeout in onWillStart emits a warning 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
@@ -734,7 +709,7 @@ exports[`lifecycle hooks timeout in onWillStart emits a console log 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks timeout in onWillUpdateProps emits a console log 1`] = `
|
||||
exports[`lifecycle hooks timeout in onWillUpdateProps emits a warning 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
@@ -748,7 +723,7 @@ exports[`lifecycle hooks timeout in onWillUpdateProps emits a console log 1`] =
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks timeout in onWillUpdateProps emits a console log 2`] = `
|
||||
exports[`lifecycle hooks timeout in onWillUpdateProps emits a warning 2`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
@@ -66,29 +66,6 @@ exports[`.alike suffix in a simple case 2`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`.translate props are translated 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return comp1({message: \`translated message\`}, key + \`__1\`, node, this, null);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`.translate props are translated 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'].message);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics accept ES6-like syntax for props (with getters) 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -435,29 +412,6 @@ exports[`can bind function prop with bind suffix 2`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can use .translate suffix 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
const comp1 = app.createComponent(\`Child\`, true, false, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return comp1({message: \`some message\`}, key + \`__1\`, node, this, null);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`can use .translate suffix 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'].message);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`do not crash when binding anonymous function prop with bind suffix 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -924,20 +924,6 @@ exports[`props validation props: list of strings 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props validation validate props for root component 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
|
||||
let block1 = createBlock(\`<div><block-text-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let txt1 = ctx['message'];
|
||||
return block1([txt1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`props validation validate simple types 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -1,30 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`slots .translate slot props are translated 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { capture, markRaw } = helpers;
|
||||
const comp1 = app.createComponent(\`Child\`, true, true, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx1 = capture(ctx);
|
||||
return comp1({slots: markRaw({'default': {message: \`translated message\`}})}, key + \`__1\`, node, this, null);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots .translate slot props are translated 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'].slots.default.message);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots can define a default content 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
@@ -226,31 +201,6 @@ exports[`slots can render only empty slot 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots can use .translate suffix on slot props 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||
let { capture, markRaw } = helpers;
|
||||
const comp1 = app.createComponent(\`Child\`, true, true, false, []);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx1 = capture(ctx);
|
||||
return comp1({slots: markRaw({'default': {message: \`some message\`}})}, key + \`__1\`, node, this, null);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots can use .translate suffix on slot props 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'].slots.default.message);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots can use component in default-content of t-slot 1`] = `
|
||||
"function anonymous(app, bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
import { Component, markup, mount, useState, xml } from "../../src";
|
||||
import { useAttachedEl } from "../../src/runtime/hooks";
|
||||
import { makeTestFixture, nextTick, steps, useLogLifecycle } from "../helpers";
|
||||
|
||||
let fixture: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
});
|
||||
|
||||
describe("basics", () => {
|
||||
test("can attach an empty component", async () => {
|
||||
fixture.innerHTML = "<div>hello</div>";
|
||||
|
||||
class Test extends Component {
|
||||
setup() {
|
||||
useLogLifecycle();
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Test, fixture, { position: "attach" });
|
||||
expect(steps.splice(0)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"Test:setup",
|
||||
"Test:willStart",
|
||||
"Test:willRender",
|
||||
"Test:rendered",
|
||||
"Test:mounted",
|
||||
]
|
||||
`);
|
||||
expect(fixture.innerHTML).toBe("<div>hello</div>");
|
||||
});
|
||||
|
||||
test("attaching a component with a template throws", async () => {
|
||||
fixture.innerHTML = "<div>hello</div>";
|
||||
|
||||
class Test extends Component {
|
||||
static template = xml`hello`;
|
||||
}
|
||||
|
||||
let error: Error | null = null;
|
||||
try {
|
||||
await mount(Test, fixture, { position: "attach" });
|
||||
} catch (e: any) {
|
||||
error = e;
|
||||
}
|
||||
expect(error!.message).toBe("Cannot attach a component with a template");
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div>hello</div>");
|
||||
});
|
||||
|
||||
test("can attach a component with simple dynamic content", async () => {
|
||||
fixture.innerHTML = "<div><p>hello</p></div>";
|
||||
|
||||
class Test extends Component {
|
||||
static dynamicContent = {
|
||||
"p:t-att-a": "value",
|
||||
};
|
||||
value: string = "";
|
||||
setup() {
|
||||
this.value = "b";
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Test, fixture, { position: "attach" });
|
||||
expect(fixture.innerHTML).toBe('<div><p a="b">hello</p></div>');
|
||||
});
|
||||
|
||||
test("useAttachedEl returns the attached element", async () => {
|
||||
fixture.innerHTML = "<div><p>hello</p></div>";
|
||||
let el: any = null;
|
||||
|
||||
class Test extends Component {
|
||||
setup() {
|
||||
el = useAttachedEl();
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Test, fixture, { position: "attach" });
|
||||
expect(el).toBe(fixture);
|
||||
});
|
||||
|
||||
test("useAttachedEl throws if component is not attached", async () => {
|
||||
class Test extends Component {
|
||||
static template = xml`hello`;
|
||||
setup() {
|
||||
useAttachedEl();
|
||||
}
|
||||
}
|
||||
|
||||
let error: any = null;
|
||||
try {
|
||||
await mount(Test, fixture);
|
||||
} catch (_e: any) {
|
||||
error = _e;
|
||||
}
|
||||
|
||||
expect(error.message).toBe("useAttachedEl can only be called with component that are attached");
|
||||
});
|
||||
|
||||
test("multiple dynamic attribute", async () => {
|
||||
fixture.innerHTML = "<div><p>hello</p></div>";
|
||||
|
||||
class Test extends Component {
|
||||
static dynamicContent = {
|
||||
"p:t-att-a": "value",
|
||||
"p:t-att-b": "value + 'coucou'",
|
||||
};
|
||||
value: string = "";
|
||||
setup() {
|
||||
this.value = "b";
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Test, fixture, { position: "attach" });
|
||||
expect(fixture.innerHTML).toBe('<div><p a="b" b="bcoucou">hello</p></div>');
|
||||
});
|
||||
|
||||
test("attrs can target root", async () => {
|
||||
fixture.innerHTML = "<p>hello</p>";
|
||||
|
||||
class Test extends Component {
|
||||
static dynamicContent = {
|
||||
"root:t-att-a": "value",
|
||||
};
|
||||
value: string = "";
|
||||
setup() {
|
||||
this.value = "b";
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Test, fixture, { position: "attach" });
|
||||
expect(fixture.outerHTML).toBe('<div a="b"><p>hello</p></div>');
|
||||
});
|
||||
|
||||
test("dynamic attribute is updated on rerender", async () => {
|
||||
fixture.innerHTML = "<div><p>hello</p></div>";
|
||||
|
||||
class Test extends Component {
|
||||
static dynamicContent = {
|
||||
"p:t-att-a": "state.value",
|
||||
};
|
||||
state: any;
|
||||
setup() {
|
||||
this.state = useState({ value: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
const test = await mount(Test, fixture, { position: "attach" });
|
||||
expect(fixture.innerHTML).toBe('<div><p a="1">hello</p></div>');
|
||||
test.state.value = 2;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe('<div><p a="2">hello</p></div>');
|
||||
});
|
||||
|
||||
test("t-on-click, basic", async () => {
|
||||
fixture.innerHTML = "<div><p>hello</p></div>";
|
||||
|
||||
let ev: Event | null = null;
|
||||
|
||||
class Test extends Component {
|
||||
static dynamicContent = {
|
||||
"p:t-att-a": "state.value",
|
||||
"p:t-on-click": "onClick",
|
||||
};
|
||||
state: any;
|
||||
setup() {
|
||||
this.state = useState({ value: 1 });
|
||||
}
|
||||
onClick(_ev: any) {
|
||||
ev = _ev;
|
||||
this.state.value = 2;
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Test, fixture, { position: "attach" });
|
||||
expect(fixture.innerHTML).toBe('<div><p a="1">hello</p></div>');
|
||||
fixture.querySelector("p")!.click();
|
||||
expect(ev).toBeInstanceOf(Event);
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe('<div><p a="2">hello</p></div>');
|
||||
});
|
||||
|
||||
test("t-on-click, target root element", async () => {
|
||||
fixture.innerHTML = "hello";
|
||||
let click = false;
|
||||
class Test extends Component {
|
||||
static dynamicContent = {
|
||||
"root:t-on-click": "onClick",
|
||||
};
|
||||
onClick() {
|
||||
click = true;
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Test, fixture, { position: "attach" });
|
||||
fixture.click();
|
||||
expect(click).toBe(true);
|
||||
});
|
||||
|
||||
test("t-out, basic", async () => {
|
||||
fixture.innerHTML = "<div><p>hello</p></div>";
|
||||
|
||||
class Test extends Component {
|
||||
static dynamicContent = {
|
||||
"p:t-out": "state.value",
|
||||
};
|
||||
state: any;
|
||||
setup() {
|
||||
this.state = useState({ value: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Test, fixture, { position: "attach" });
|
||||
expect(fixture.innerHTML).toBe("<div><p>1</p></div>");
|
||||
});
|
||||
|
||||
test("t-out, on root", async () => {
|
||||
fixture.innerHTML = "hello";
|
||||
|
||||
class Test extends Component {
|
||||
static dynamicContent = {
|
||||
"root:t-out": "state.value",
|
||||
};
|
||||
state: any;
|
||||
setup() {
|
||||
this.state = useState({ value: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
expect(fixture.outerHTML).toBe("<div>hello</div>");
|
||||
await mount(Test, fixture, { position: "attach" });
|
||||
expect(fixture.outerHTML).toBe("<div>1</div>");
|
||||
});
|
||||
|
||||
test("t-out, with markup", async () => {
|
||||
fixture.innerHTML = `<p class="p1">hello</p><p class="p2">hello</p>`;
|
||||
|
||||
class Test extends Component {
|
||||
static dynamicContent = {
|
||||
"p.p1:t-out": "value1",
|
||||
"p.p2:t-out": "value2",
|
||||
};
|
||||
|
||||
value1: any;
|
||||
value2: any;
|
||||
setup() {
|
||||
this.value1 = "<div>value1</div>";
|
||||
this.value2 = markup("<div>value2</div>");
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Test, fixture, { position: "attach" });
|
||||
expect(fixture.innerHTML).toBe(
|
||||
`<p class="p1"><div>value1</div></p><p class="p2"><div>value2</div></p>`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -157,7 +157,7 @@ describe("basics", () => {
|
||||
} catch (e) {
|
||||
error = e as Error;
|
||||
}
|
||||
const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier 'ctx'
|
||||
const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier
|
||||
|
||||
generated code:
|
||||
function(app, bdom, helpers) {
|
||||
@@ -182,7 +182,7 @@ function(app, bdom, helpers) {
|
||||
static components = { Child };
|
||||
static template = xml`<Child/>`;
|
||||
}
|
||||
const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier 'ctx'
|
||||
const expectedErrorMessage = `Failed to compile anonymous template: Unexpected identifier
|
||||
|
||||
generated code:
|
||||
function(app, bdom, helpers) {
|
||||
|
||||
@@ -112,10 +112,10 @@ describe("lifecycle hooks", () => {
|
||||
await mount(Test, fixture);
|
||||
});
|
||||
|
||||
test("timeout in onWillStart emits a console log", async () => {
|
||||
const { log } = console;
|
||||
let logArgs: any[];
|
||||
console.log = jest.fn((...args) => (logArgs = args));
|
||||
test("timeout in onWillStart emits a warning", async () => {
|
||||
const { warn } = console;
|
||||
let warnArgs: any[];
|
||||
console.warn = jest.fn((...args) => (warnArgs = args));
|
||||
const { setTimeout } = window;
|
||||
let timeoutCbs: any = {};
|
||||
let timeoutId = 0;
|
||||
@@ -138,17 +138,17 @@ describe("lifecycle hooks", () => {
|
||||
}
|
||||
await nextMicroTick();
|
||||
await nextMicroTick();
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
expect(logArgs![0]!.message).toBe("onWillStart's promise hasn't resolved after 3 seconds");
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
expect(warnArgs![0]!.message).toBe("onWillStart's promise hasn't resolved after 3 seconds");
|
||||
} finally {
|
||||
console.log = log;
|
||||
console.warn = warn;
|
||||
window.setTimeout = setTimeout;
|
||||
}
|
||||
});
|
||||
|
||||
test("timeout in onWillStart doesn't emit a console log if app is destroyed", async () => {
|
||||
const { log } = console;
|
||||
console.log = jest.fn();
|
||||
test("timeout in onWillStart doesn't emit a warning if app is destroyed", async () => {
|
||||
const { warn } = console;
|
||||
console.warn = jest.fn();
|
||||
const { setTimeout } = window;
|
||||
let timeoutCbs: any = {};
|
||||
let timeoutId = 0;
|
||||
@@ -172,14 +172,14 @@ describe("lifecycle hooks", () => {
|
||||
}
|
||||
await nextMicroTick();
|
||||
await nextMicroTick();
|
||||
expect(console.log).toHaveBeenCalledTimes(0);
|
||||
expect(console.warn).toHaveBeenCalledTimes(0);
|
||||
} finally {
|
||||
console.log = log;
|
||||
console.warn = warn;
|
||||
window.setTimeout = setTimeout;
|
||||
}
|
||||
});
|
||||
|
||||
test("timeout in onWillUpdateProps emits a console log", async () => {
|
||||
test("timeout in onWillUpdateProps emits a warning", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml``;
|
||||
setup() {
|
||||
@@ -193,9 +193,9 @@ describe("lifecycle hooks", () => {
|
||||
}
|
||||
const parent = await mount(Parent, fixture, { test: true });
|
||||
|
||||
const { log } = console;
|
||||
let logArgs: any[];
|
||||
console.log = jest.fn((...args) => (logArgs = args));
|
||||
const { warn } = console;
|
||||
let warnArgs: any[];
|
||||
console.warn = jest.fn((...args) => (warnArgs = args));
|
||||
const { setTimeout } = window;
|
||||
let timeoutCbs: any = {};
|
||||
let timeoutId = 0;
|
||||
@@ -218,12 +218,12 @@ describe("lifecycle hooks", () => {
|
||||
delete timeoutCbs[id];
|
||||
}
|
||||
await tick;
|
||||
expect(console.log).toHaveBeenCalledTimes(1);
|
||||
expect(logArgs![0]!.message).toBe(
|
||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
||||
expect(warnArgs![0]!.message).toBe(
|
||||
"onWillUpdateProps's promise hasn't resolved after 3 seconds"
|
||||
);
|
||||
} finally {
|
||||
console.log = log;
|
||||
console.warn = warn;
|
||||
window.setTimeout = setTimeout;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -299,34 +299,6 @@ test("bound functions are considered 'alike'", async () => {
|
||||
expect(fixture.innerHTML).toBe("3child");
|
||||
});
|
||||
|
||||
test("can use .translate suffix", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<t t-esc="props.message"/>`;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`<Child message.translate="some message"/>`;
|
||||
static components = { Child };
|
||||
}
|
||||
|
||||
await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("some message");
|
||||
});
|
||||
|
||||
test(".translate props are translated", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<t t-esc="props.message"/>`;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`<Child message.translate="some message"/>`;
|
||||
static components = { Child };
|
||||
}
|
||||
|
||||
await mount(Parent, fixture, { translateFn: () => "translated message" });
|
||||
expect(fixture.innerHTML).toBe("translated message");
|
||||
});
|
||||
|
||||
test("throw if prop uses an unknown suffix", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<t t-esc="props.val"/>`;
|
||||
|
||||
@@ -702,7 +702,7 @@ describe("props validation", () => {
|
||||
const app = new App(Parent, { test: true });
|
||||
await app.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("12");
|
||||
expect(app.root!.node!.subscriptions).toEqual([{ keys: ["otherValue"], target: obj }]);
|
||||
expect(app.root!.subscriptions).toEqual([{ keys: ["otherValue"], target: obj }]);
|
||||
});
|
||||
|
||||
test("props are validated whenever component is updated", async () => {
|
||||
|
||||
@@ -179,34 +179,6 @@ describe("slots", () => {
|
||||
expect(fixture.innerHTML).toBe("<span>default empty</span>");
|
||||
});
|
||||
|
||||
test("can use .translate suffix on slot props", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<t t-esc="props.slots.default.message"/>`;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`<Child><t t-set-slot="default" message.translate="some message"/></Child>`;
|
||||
static components = { Child };
|
||||
}
|
||||
|
||||
await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("some message");
|
||||
});
|
||||
|
||||
test(".translate slot props are translated", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<t t-esc="props.slots.default.message"/>`;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`<Child><t t-set-slot="default" message.translate="some message"/></Child>`;
|
||||
static components = { Child };
|
||||
}
|
||||
|
||||
await mount(Parent, fixture, { translateFn: () => "translated message" });
|
||||
expect(fixture.innerHTML).toBe("translated message");
|
||||
});
|
||||
|
||||
test("default slot with slot scope: shorthand syntax", async () => {
|
||||
let child: any;
|
||||
class Child extends Component {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"default_popup": "popup_app/popup.html"
|
||||
},
|
||||
"permissions": ["scripting", "storage"],
|
||||
"host_permissions": ["http://*/*", "https://*/*", "file://*"],
|
||||
"host_permissions": ["http://*/*", "https://*/*"],
|
||||
"content_security_policy": {
|
||||
"script-src": "self",
|
||||
"object-src": "self"
|
||||
|
||||
@@ -109,34 +109,23 @@
|
||||
object(obj) {
|
||||
const result = [];
|
||||
let length = 0;
|
||||
if (obj instanceof String) {
|
||||
result[0] = `'${obj.toString()}'`;
|
||||
} else if (obj instanceof Array) {
|
||||
return `${obj.constructor.name} ${this.array([...obj])}`;
|
||||
} else if (obj instanceof Number) {
|
||||
result[0] = obj.toString();
|
||||
} else {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (length > 25) {
|
||||
result.push("...");
|
||||
break;
|
||||
}
|
||||
const element = key + ": " + this.serializeItem(value);
|
||||
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);
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (length > 25) {
|
||||
result.push("...");
|
||||
break;
|
||||
}
|
||||
const element = key + ": " + this.serializeItem(value);
|
||||
length += element.length;
|
||||
result.push(element);
|
||||
}
|
||||
if (obj.constructor.name !== "Object") {
|
||||
return obj.constructor.name + " {" + result.join(", ") + "}";
|
||||
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(", ") + "}";
|
||||
},
|
||||
@@ -834,7 +823,7 @@
|
||||
child.contentType = "set";
|
||||
child.hasChildren = true;
|
||||
break;
|
||||
case obj.constructor.name === "Array":
|
||||
case obj instanceof Array:
|
||||
child.contentType = "array";
|
||||
child.hasChildren = obj.length > 0;
|
||||
break;
|
||||
@@ -845,9 +834,7 @@
|
||||
case obj instanceof Object:
|
||||
child.contentType = "object";
|
||||
child.hasChildren =
|
||||
Object.keys(obj).length ||
|
||||
Object.getOwnPropertySymbols(obj).length ||
|
||||
obj.constructor.name !== "Object";
|
||||
Object.keys(obj).length || Object.getOwnPropertySymbols(obj).length;
|
||||
break;
|
||||
default:
|
||||
child.contentType = typeof obj;
|
||||
|
||||
Reference in New Issue
Block a user