mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20c6cacb4e | |||
| eb2b32ab60 | |||
| 2a223288d4 | |||
| 1272278225 | |||
| f502dd732e | |||
| 9c2d957525 | |||
| f8bb86820e | |||
| 0cde4b8737 |
@@ -6,6 +6,7 @@
|
|||||||
- [API](#api)
|
- [API](#api)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [`mount` helper](#mount-helper)
|
- [`mount` helper](#mount-helper)
|
||||||
|
- [Roots](#roots)
|
||||||
- [Loading templates](#loading-templates)
|
- [Loading templates](#loading-templates)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
@@ -92,6 +93,33 @@ 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
|
a reference to the actual Owl App, then using the `App` class directly is
|
||||||
possible.
|
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
|
## Loading templates
|
||||||
|
|
||||||
Most applications will need to load templates whenever they start. Here is
|
Most applications will need to load templates whenever they start. Here is
|
||||||
|
|||||||
@@ -140,6 +140,28 @@ class SomeComponent extends Component {
|
|||||||
The `.bind` suffix also implies `.alike`, so these props will not cause additional
|
The `.bind` suffix also implies `.alike`, so these props will not cause additional
|
||||||
renderings.
|
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
|
## Dynamic Props
|
||||||
|
|
||||||
The `t-props` directive can be used to specify totally dynamic props:
|
The `t-props` directive can be used to specify totally dynamic props:
|
||||||
|
|||||||
@@ -201,16 +201,17 @@ use this `Notebook` component:
|
|||||||
|
|
||||||
```xml
|
```xml
|
||||||
<Notebook>
|
<Notebook>
|
||||||
<t t-set-slot="page1" title="'Page 1'">
|
<t t-set-slot="page1" title.translate="Page 1">
|
||||||
<div>this is in the page 1</div>
|
<div>this is in the page 1</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-set-slot="page2" title="'Page 2'" hidden="somevalue">
|
<t t-set-slot="page2" title.translate="Page 2" hidden="somevalue">
|
||||||
<div>this is in the page 2</div>
|
<div>this is in the page 2</div>
|
||||||
</t>
|
</t>
|
||||||
</Notebook>
|
</Notebook>
|
||||||
```
|
```
|
||||||
|
|
||||||
Slot params works like normal props, so one can use the `.bind` suffix to
|
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
|
||||||
bind a function if needed.
|
bind a function if needed.
|
||||||
|
|
||||||
## Slot scopes
|
## Slot scopes
|
||||||
|
|||||||
+78
-35
@@ -2598,42 +2598,47 @@ class ComponentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TIMEOUT = Symbol("timeout");
|
const TIMEOUT = Symbol("timeout");
|
||||||
|
const HOOK_TIMEOUT = {
|
||||||
|
onWillStart: 3000,
|
||||||
|
onWillUpdateProps: 3000,
|
||||||
|
};
|
||||||
function wrapError(fn, hookName) {
|
function wrapError(fn, hookName) {
|
||||||
const error = new OwlError(`The following error occurred in ${hookName}: `);
|
const error = new OwlError();
|
||||||
const timeoutError = new OwlError(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
const timeoutError = new OwlError();
|
||||||
const node = getCurrent();
|
const node = getCurrent();
|
||||||
return (...args) => {
|
return (...args) => {
|
||||||
const onError = (cause) => {
|
const onError = (cause) => {
|
||||||
error.cause = cause;
|
error.cause = cause;
|
||||||
if (cause instanceof Error) {
|
error.message =
|
||||||
error.message += `"${cause.message}"`;
|
cause instanceof Error
|
||||||
}
|
? `The following error occurred in ${hookName}: "${cause.message}"`
|
||||||
else {
|
: `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
|
||||||
error.message = `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
|
|
||||||
}
|
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
|
let result;
|
||||||
try {
|
try {
|
||||||
const result = fn(...args);
|
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) {
|
catch (cause) {
|
||||||
onError(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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -4594,7 +4599,12 @@ class CodeGenerator {
|
|||||||
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
|
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
|
||||||
*/
|
*/
|
||||||
formatProp(name, value) {
|
formatProp(name, value) {
|
||||||
value = this.captureExpression(value);
|
if (name.endsWith(".translate")) {
|
||||||
|
value = toStringExpression(this.translateFn(value));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value = this.captureExpression(value);
|
||||||
|
}
|
||||||
if (name.includes(".")) {
|
if (name.includes(".")) {
|
||||||
let [_name, suffix] = name.split(".");
|
let [_name, suffix] = name.split(".");
|
||||||
name = _name;
|
name = _name;
|
||||||
@@ -4603,6 +4613,7 @@ class CodeGenerator {
|
|||||||
value = `(${value}).bind(this)`;
|
value = `(${value}).bind(this)`;
|
||||||
break;
|
break;
|
||||||
case "alike":
|
case "alike":
|
||||||
|
case "translate":
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new OwlError("Invalid prop suffix");
|
throw new OwlError("Invalid prop suffix");
|
||||||
@@ -5546,7 +5557,7 @@ function compile(template, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do not modify manually. This file is generated by the release script.
|
// do not modify manually. This file is generated by the release script.
|
||||||
const version = "2.2.11";
|
const version = "2.4.0";
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Scheduler
|
// Scheduler
|
||||||
@@ -5641,6 +5652,7 @@ class App extends TemplateSet {
|
|||||||
constructor(Root, config = {}) {
|
constructor(Root, config = {}) {
|
||||||
super(config);
|
super(config);
|
||||||
this.scheduler = new Scheduler();
|
this.scheduler = new Scheduler();
|
||||||
|
this.subRoots = new Set();
|
||||||
this.root = null;
|
this.root = null;
|
||||||
this.name = config.name || "";
|
this.name = config.name || "";
|
||||||
this.Root = Root;
|
this.Root = Root;
|
||||||
@@ -5659,14 +5671,42 @@ class App extends TemplateSet {
|
|||||||
this.props = config.props || {};
|
this.props = config.props || {};
|
||||||
}
|
}
|
||||||
mount(target, options) {
|
mount(target, options) {
|
||||||
App.validateTarget(target);
|
const root = this.createRoot(this.Root, { props: this.props });
|
||||||
if (this.dev) {
|
this.root = root.node;
|
||||||
validateProps(this.Root, this.props, { __owl__: { app: this } });
|
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;
|
||||||
}
|
}
|
||||||
const node = this.makeNode(this.Root, this.props);
|
const node = this.makeNode(Root, props);
|
||||||
const prom = this.mountNode(node, target, options);
|
if (config.env) {
|
||||||
this.root = node;
|
this.env = env;
|
||||||
return prom;
|
}
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
makeNode(Component, props) {
|
makeNode(Component, props) {
|
||||||
return new ComponentNode(Component, props, this, null, null);
|
return new ComponentNode(Component, props, this, null, null);
|
||||||
@@ -5698,6 +5738,9 @@ class App extends TemplateSet {
|
|||||||
}
|
}
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.root) {
|
if (this.root) {
|
||||||
|
for (let subroot of this.subRoots) {
|
||||||
|
subroot.destroy();
|
||||||
|
}
|
||||||
this.root.destroy();
|
this.root.destroy();
|
||||||
this.scheduler.processTasks();
|
this.scheduler.processTasks();
|
||||||
}
|
}
|
||||||
@@ -5975,6 +6018,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 };
|
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-06-17T13:31:12.099Z';
|
__info__.date = '2024-09-30T08:49:29.420Z';
|
||||||
__info__.hash = 'e7f405c';
|
__info__.hash = 'eb2b32a';
|
||||||
__info__.url = 'https://github.com/odoo/owl';
|
__info__.url = 'https://github.com/odoo/owl';
|
||||||
|
|||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.2.11",
|
"version": "2.4.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "2.2.11",
|
"version": "2.4.0",
|
||||||
"description": "Odoo Web Library (OWL)",
|
"description": "Odoo Web Library (OWL)",
|
||||||
"main": "dist/owl.cjs.js",
|
"main": "dist/owl.cjs.js",
|
||||||
"module": "dist/owl.es.js",
|
"module": "dist/owl.es.js",
|
||||||
|
|||||||
@@ -1136,7 +1136,11 @@ export class CodeGenerator {
|
|||||||
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
|
* "onClick.bind" "onClick" "onClick: bind(ctx, ctx['onClick'])"
|
||||||
*/
|
*/
|
||||||
formatProp(name: string, value: string): string {
|
formatProp(name: string, value: string): string {
|
||||||
value = this.captureExpression(value);
|
if (name.endsWith(".translate")) {
|
||||||
|
value = toStringExpression(this.translateFn(value));
|
||||||
|
} else {
|
||||||
|
value = this.captureExpression(value);
|
||||||
|
}
|
||||||
if (name.includes(".")) {
|
if (name.includes(".")) {
|
||||||
let [_name, suffix] = name.split(".");
|
let [_name, suffix] = name.split(".");
|
||||||
name = _name;
|
name = _name;
|
||||||
@@ -1145,6 +1149,7 @@ export class CodeGenerator {
|
|||||||
value = `(${value}).bind(this)`;
|
value = `(${value}).bind(this)`;
|
||||||
break;
|
break;
|
||||||
case "alike":
|
case "alike":
|
||||||
|
case "translate":
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new OwlError("Invalid prop suffix");
|
throw new OwlError("Invalid prop suffix");
|
||||||
|
|||||||
+54
-9
@@ -16,10 +16,13 @@ export interface Env {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppConfig<P, E> extends TemplateSetConfig {
|
export interface RootConfig<P, E> {
|
||||||
name?: string;
|
|
||||||
props?: P;
|
props?: P;
|
||||||
env?: E;
|
env?: E;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppConfig<P, E> extends TemplateSetConfig, RootConfig<P, E> {
|
||||||
|
name?: string;
|
||||||
test?: boolean;
|
test?: boolean;
|
||||||
warnIfNoStaticProps?: boolean;
|
warnIfNoStaticProps?: boolean;
|
||||||
}
|
}
|
||||||
@@ -49,6 +52,12 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Root<P, E> {
|
||||||
|
node: ComponentNode<P, E>;
|
||||||
|
mount(target: HTMLElement | ShadowRoot, options?: MountOptions): Promise<Component<P, E>>;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
|
||||||
window.__OWL_DEVTOOLS__ ||= { apps, Fiber, RootFiber, toRaw, reactive };
|
window.__OWL_DEVTOOLS__ ||= { apps, Fiber, RootFiber, toRaw, reactive };
|
||||||
|
|
||||||
export class App<
|
export class App<
|
||||||
@@ -65,6 +74,7 @@ export class App<
|
|||||||
props: P;
|
props: P;
|
||||||
env: E;
|
env: E;
|
||||||
scheduler = new Scheduler();
|
scheduler = new Scheduler();
|
||||||
|
subRoots: Set<ComponentNode> = new Set();
|
||||||
root: ComponentNode<P, E> | null = null;
|
root: ComponentNode<P, E> | null = null;
|
||||||
warnIfNoStaticProps: boolean;
|
warnIfNoStaticProps: boolean;
|
||||||
|
|
||||||
@@ -91,14 +101,46 @@ export class App<
|
|||||||
target: HTMLElement | ShadowRoot,
|
target: HTMLElement | ShadowRoot,
|
||||||
options?: MountOptions
|
options?: MountOptions
|
||||||
): Promise<Component<P, E> & InstanceType<T>> {
|
): Promise<Component<P, E> & InstanceType<T>> {
|
||||||
App.validateTarget(target);
|
const root = this.createRoot(this.Root, { props: this.props });
|
||||||
if (this.dev) {
|
this.root = root.node;
|
||||||
validateProps(this.Root, this.props, { __owl__: { app: this } });
|
this.subRoots.delete(root.node);
|
||||||
|
return 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);
|
||||||
|
// 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 as any;
|
||||||
}
|
}
|
||||||
const node = this.makeNode(this.Root, this.props);
|
const node = this.makeNode(Root, props);
|
||||||
const prom = this.mountNode(node, target, options);
|
if (config.env) {
|
||||||
this.root = node;
|
this.env = env;
|
||||||
return prom;
|
}
|
||||||
|
this.subRoots.add(node);
|
||||||
|
return {
|
||||||
|
node,
|
||||||
|
mount: (target: HTMLElement | ShadowRoot, options?: MountOptions) => {
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
|
makeNode(Component: ComponentConstructor, props: any): ComponentNode {
|
||||||
@@ -134,6 +176,9 @@ export class App<
|
|||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.root) {
|
if (this.root) {
|
||||||
|
for (let subroot of this.subRoots) {
|
||||||
|
subroot.destroy();
|
||||||
|
}
|
||||||
this.root.destroy();
|
this.root.destroy();
|
||||||
this.scheduler.processTasks();
|
this.scheduler.processTasks();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,42 +3,50 @@ import { nodeErrorHandlers } from "./error_handling";
|
|||||||
import { OwlError } from "../common/owl_error";
|
import { OwlError } from "../common/owl_error";
|
||||||
|
|
||||||
const TIMEOUT = Symbol("timeout");
|
const TIMEOUT = Symbol("timeout");
|
||||||
|
const HOOK_TIMEOUT: { [key: string]: number } = {
|
||||||
|
onWillStart: 3000,
|
||||||
|
onWillUpdateProps: 3000,
|
||||||
|
};
|
||||||
function wrapError(fn: (...args: any[]) => any, hookName: string) {
|
function wrapError(fn: (...args: any[]) => any, hookName: string) {
|
||||||
const error = new OwlError(`The following error occurred in ${hookName}: `) as Error & {
|
const error = new OwlError() as Error & {
|
||||||
cause: any;
|
cause: any;
|
||||||
};
|
};
|
||||||
const timeoutError = new OwlError(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
const timeoutError = new OwlError();
|
||||||
const node = getCurrent();
|
const node = getCurrent();
|
||||||
return (...args: any[]) => {
|
return (...args: any[]) => {
|
||||||
const onError = (cause: any) => {
|
const onError = (cause: any) => {
|
||||||
error.cause = cause;
|
error.cause = cause;
|
||||||
if (cause instanceof Error) {
|
error.message =
|
||||||
error.message += `"${cause.message}"`;
|
cause instanceof Error
|
||||||
} else {
|
? `The following error occurred in ${hookName}: "${cause.message}"`
|
||||||
error.message = `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
|
: `Something that is not an Error was thrown in ${hookName} (see this Error's "cause" property)`;
|
||||||
}
|
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
|
let result;
|
||||||
try {
|
try {
|
||||||
const result = fn(...args);
|
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) {
|
} catch (cause) {
|
||||||
onError(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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
// do not modify manually. This file is generated by the release script.
|
// do not modify manually. This file is generated by the release script.
|
||||||
export const version = "2.2.11";
|
export const version = "2.4.0";
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
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 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();
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
`;
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { App, Component, xml } from "../../src";
|
||||||
|
import { status } from "../../src/runtime/status";
|
||||||
|
import { makeTestFixture, 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");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -97,6 +97,19 @@ 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`] = `
|
exports[`basics a component inside a component 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -261,6 +274,19 @@ 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`] = `
|
exports[`basics child can be updated 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -1002,6 +1028,19 @@ 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`] = `
|
exports[`basics two child components 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -683,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 warning if app is destroyed 1`] = `
|
exports[`lifecycle hooks timeout in onWillStart doesn't emit a console log if app is destroyed 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
@@ -696,7 +696,7 @@ exports[`lifecycle hooks timeout in onWillStart doesn't emit a warning if app is
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`lifecycle hooks timeout in onWillStart emits a warning 1`] = `
|
exports[`lifecycle hooks timeout in onWillStart emits a console log 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
@@ -709,7 +709,7 @@ exports[`lifecycle hooks timeout in onWillStart emits a warning 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`lifecycle hooks timeout in onWillUpdateProps emits a warning 1`] = `
|
exports[`lifecycle hooks timeout in onWillUpdateProps emits a console log 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
@@ -723,7 +723,7 @@ exports[`lifecycle hooks timeout in onWillUpdateProps emits a warning 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`lifecycle hooks timeout in onWillUpdateProps emits a warning 2`] = `
|
exports[`lifecycle hooks timeout in onWillUpdateProps emits a console log 2`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
let { text, createBlock, list, multi, html, toggler, comment } = bdom;
|
||||||
|
|||||||
@@ -66,6 +66,29 @@ 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`] = `
|
exports[`basics accept ES6-like syntax for props (with getters) 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -412,6 +435,29 @@ 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`] = `
|
exports[`do not crash when binding anonymous function prop with bind suffix 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -924,6 +924,20 @@ 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`] = `
|
exports[`props validation validate simple types 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,5 +1,30 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// 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`] = `
|
exports[`slots can define a default content 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
@@ -201,6 +226,31 @@ 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`] = `
|
exports[`slots can use component in default-content of t-slot 1`] = `
|
||||||
"function anonymous(app, bdom, helpers
|
"function anonymous(app, bdom, helpers
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -112,10 +112,10 @@ describe("lifecycle hooks", () => {
|
|||||||
await mount(Test, fixture);
|
await mount(Test, fixture);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("timeout in onWillStart emits a warning", async () => {
|
test("timeout in onWillStart emits a console log", async () => {
|
||||||
const { warn } = console;
|
const { log } = console;
|
||||||
let warnArgs: any[];
|
let logArgs: any[];
|
||||||
console.warn = jest.fn((...args) => (warnArgs = args));
|
console.log = jest.fn((...args) => (logArgs = args));
|
||||||
const { setTimeout } = window;
|
const { setTimeout } = window;
|
||||||
let timeoutCbs: any = {};
|
let timeoutCbs: any = {};
|
||||||
let timeoutId = 0;
|
let timeoutId = 0;
|
||||||
@@ -138,17 +138,17 @@ describe("lifecycle hooks", () => {
|
|||||||
}
|
}
|
||||||
await nextMicroTick();
|
await nextMicroTick();
|
||||||
await nextMicroTick();
|
await nextMicroTick();
|
||||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
expect(console.log).toHaveBeenCalledTimes(1);
|
||||||
expect(warnArgs![0]!.message).toBe("onWillStart's promise hasn't resolved after 3 seconds");
|
expect(logArgs![0]!.message).toBe("onWillStart's promise hasn't resolved after 3 seconds");
|
||||||
} finally {
|
} finally {
|
||||||
console.warn = warn;
|
console.log = log;
|
||||||
window.setTimeout = setTimeout;
|
window.setTimeout = setTimeout;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("timeout in onWillStart doesn't emit a warning if app is destroyed", async () => {
|
test("timeout in onWillStart doesn't emit a console log if app is destroyed", async () => {
|
||||||
const { warn } = console;
|
const { log } = console;
|
||||||
console.warn = jest.fn();
|
console.log = jest.fn();
|
||||||
const { setTimeout } = window;
|
const { setTimeout } = window;
|
||||||
let timeoutCbs: any = {};
|
let timeoutCbs: any = {};
|
||||||
let timeoutId = 0;
|
let timeoutId = 0;
|
||||||
@@ -172,14 +172,14 @@ describe("lifecycle hooks", () => {
|
|||||||
}
|
}
|
||||||
await nextMicroTick();
|
await nextMicroTick();
|
||||||
await nextMicroTick();
|
await nextMicroTick();
|
||||||
expect(console.warn).toHaveBeenCalledTimes(0);
|
expect(console.log).toHaveBeenCalledTimes(0);
|
||||||
} finally {
|
} finally {
|
||||||
console.warn = warn;
|
console.log = log;
|
||||||
window.setTimeout = setTimeout;
|
window.setTimeout = setTimeout;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("timeout in onWillUpdateProps emits a warning", async () => {
|
test("timeout in onWillUpdateProps emits a console log", async () => {
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml``;
|
static template = xml``;
|
||||||
setup() {
|
setup() {
|
||||||
@@ -193,9 +193,9 @@ describe("lifecycle hooks", () => {
|
|||||||
}
|
}
|
||||||
const parent = await mount(Parent, fixture, { test: true });
|
const parent = await mount(Parent, fixture, { test: true });
|
||||||
|
|
||||||
const { warn } = console;
|
const { log } = console;
|
||||||
let warnArgs: any[];
|
let logArgs: any[];
|
||||||
console.warn = jest.fn((...args) => (warnArgs = args));
|
console.log = jest.fn((...args) => (logArgs = args));
|
||||||
const { setTimeout } = window;
|
const { setTimeout } = window;
|
||||||
let timeoutCbs: any = {};
|
let timeoutCbs: any = {};
|
||||||
let timeoutId = 0;
|
let timeoutId = 0;
|
||||||
@@ -218,12 +218,12 @@ describe("lifecycle hooks", () => {
|
|||||||
delete timeoutCbs[id];
|
delete timeoutCbs[id];
|
||||||
}
|
}
|
||||||
await tick;
|
await tick;
|
||||||
expect(console.warn).toHaveBeenCalledTimes(1);
|
expect(console.log).toHaveBeenCalledTimes(1);
|
||||||
expect(warnArgs![0]!.message).toBe(
|
expect(logArgs![0]!.message).toBe(
|
||||||
"onWillUpdateProps's promise hasn't resolved after 3 seconds"
|
"onWillUpdateProps's promise hasn't resolved after 3 seconds"
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
console.warn = warn;
|
console.log = log;
|
||||||
window.setTimeout = setTimeout;
|
window.setTimeout = setTimeout;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -299,6 +299,34 @@ test("bound functions are considered 'alike'", async () => {
|
|||||||
expect(fixture.innerHTML).toBe("3child");
|
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 () => {
|
test("throw if prop uses an unknown suffix", async () => {
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`<t t-esc="props.val"/>`;
|
static template = xml`<t t-esc="props.val"/>`;
|
||||||
|
|||||||
@@ -179,6 +179,34 @@ describe("slots", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<span>default empty</span>");
|
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 () => {
|
test("default slot with slot scope: shorthand syntax", async () => {
|
||||||
let child: any;
|
let child: any;
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"default_popup": "popup_app/popup.html"
|
"default_popup": "popup_app/popup.html"
|
||||||
},
|
},
|
||||||
"permissions": ["scripting", "storage"],
|
"permissions": ["scripting", "storage"],
|
||||||
"host_permissions": ["http://*/*", "https://*/*"],
|
"host_permissions": ["http://*/*", "https://*/*", "file://*"],
|
||||||
"content_security_policy": {
|
"content_security_policy": {
|
||||||
"script-src": "self",
|
"script-src": "self",
|
||||||
"object-src": "self"
|
"object-src": "self"
|
||||||
|
|||||||
Reference in New Issue
Block a user