Compare commits

..

1 Commits

Author SHA1 Message Date
Simon Genin (ges) 9e2202c02a [IMP] devtools: add __owl__ access.
To be able to make devtools, we need access some inner state of a
component. We create a __owl_devtools__ variable on components.

Just like in Vue js, we attach it to the HTMLElement of a component.
It's how we give it to the "outside world", through the DOM.

For its update, we use the onMounted and onPatched hook.
2020-11-10 17:22:37 +01:00
54 changed files with 466 additions and 1433 deletions
+1 -1
View File
@@ -120,7 +120,7 @@ npm install @odoo/owl
If you want to use a simple `<script>` tag, the last release can be downloaded here: If you want to use a simple `<script>` tag, the last release can be downloaded here:
- [owl-1.3.0](https://github.com/odoo/owl/releases/tag/v1.3.0) - [owl-1.0.13](https://github.com/odoo/owl/releases/tag/v1.0.13)
## License ## License
+4 -4
View File
@@ -545,7 +545,7 @@ application), since it involves extracting all task related code out of the
components. Here is the new content of the `app.js` file: components. Here is the new content of the `app.js` file:
```js ```js
const { Component, Store, mount } = owl; const { Component, Store } = owl;
const { xml } = owl.tags; const { xml } = owl.tags;
const { whenReady } = owl.utils; const { whenReady } = owl.utils;
const { useRef, useDispatch, useStore } = owl.hooks; const { useRef, useDispatch, useStore } = owl.hooks;
@@ -663,7 +663,7 @@ function makeStore() {
function setup() { function setup() {
owl.config.mode = "dev"; owl.config.mode = "dev";
const env = { store: makeStore() }; const env = {store = makeStore()};
mount(App, { target: document.body, env }); mount(App, { target: document.body, env });
} }
``` ```
@@ -808,7 +808,7 @@ For reference, here is the final code:
```js ```js
(function () { (function () {
const { Component, Store, mount } = owl; const { Component, Store } = owl;
const { xml } = owl.tags; const { xml } = owl.tags;
const { whenReady } = owl.utils; const { whenReady } = owl.utils;
const { useRef, useDispatch, useState, useStore } = owl.hooks; const { useRef, useDispatch, useState, useStore } = owl.hooks;
@@ -939,7 +939,7 @@ For reference, here is the final code:
function setup() { function setup() {
owl.config.mode = "dev"; owl.config.mode = "dev";
const env = { store: makeStore() }; const env = {store = makeStore()};
mount(App, { target: document.body, env }); mount(App, { target: document.body, env });
} }
-1
View File
@@ -17,7 +17,6 @@ You will find here a complete reference of every feature, class or object
provided by Owl. provided by Owl.
- [Animations](reference/animations.md) - [Animations](reference/animations.md)
- [Browser](reference/browser.md)
- [Component](reference/component.md) - [Component](reference/component.md)
- [Content](reference/content.md) - [Content](reference/content.md)
- [Concurrency Model](reference/concurrency_model.md) - [Concurrency Model](reference/concurrency_model.md)
+9 -41
View File
@@ -52,20 +52,21 @@ sequence of events will happen:
At node insertion: At node insertion:
- the css classes `name-enter` and `name-enter-active` will be added directly - the css classes `name-enter` and `name-enter-active` will be added directly
when the node is inserted into the DOM. when the node is inserted into the DOM,
- on the next animation frame: the css class `name-enter` will be removed and the - on the next animation frame: the css class `name-enter` will be removed and the
class `name-enter-to` will be added (so they can be used to trigger css class `name-enter-to` will be added (so they can be used to trigger css
transition effects). transition effects),
- at the end of the transition, `name-enter-to` and `name-enter-active` will be removed. - the css class `name-enter-active` will be removed whenever a css transition
ends.
At node destruction: At node destruction:
- the css classes `name-leave` and `name-leave-active` will be added before the - the css classes `name-leave` and `name-leave-active` will be added before the
node is removed to the DOM. node is removed to the DOM,
- on the next animation frame: the css class `name-leave` will be removed and the - the css class `name-leave` will be removed on the next animation frame (so it
class `name-leave-to` will be added (so they can be used to trigger css can be used to trigger css transition effects),
transition effects). - the css class `name-leave-active` will be removed whenever a css transition
- at the end of the transition, `name-leave-to` and `name-leave-active` will be removed. ends. Only then will the element be removed from the DOM.
For example, a simple fade in/out effect can be done with this: For example, a simple fade in/out effect can be done with this:
@@ -92,36 +93,3 @@ Notes:
Owl does not support more than one transition on a single node, so the Owl does not support more than one transition on a single node, so the
`t-transition` expression must be a single value (i.e. no space allowed). `t-transition` expression must be a single value (i.e. no space allowed).
## SCSS Mixins
If you use SCSS, you can use mixins to make generic animations. Here is an exemple with a fade in / fade out animation:
```scss
@mixin animation-fade($time, $name) {
.#{$name}_fade-enter-active,
.#{$name}_fade-active {
transition: all $time;
}
.#{$name}_fade-enter {
opacity: 0;
}
.#{$name}_fade-leave-to {
opacity: 0;
}
}
```
Usage:
```scss
@include animation-fade(0.5s, "o_notification");
```
You can now have in your template:
```xml
<SomeTag t-transition="o_notification_fade"/>
```
-33
View File
@@ -1,33 +0,0 @@
# 🦉 Browser 🦉
## Content
- [Overview](#overview)
- [Browser Content](#browser-content)
## Overview
The browser object contains some browser native APIs, such as `setTimeout`, that
are used by Owl and its utility functions. They are exposed with the intent of
making them mockable if necessary.
```js
owl.browser.setTimeout === window.setTimeout; // return true
```
For now, this object contains some functions that are not used by Owl. They
will eventually be removed in Owl 2.0.
## Browser Content
More specifically, the `browser` object contains the following methods and objects:
- `setTimeout`
- `clearTimeout`
- `setInterval`
- `clearInterval`
- `requestAnimationFrame`
- `random`
- `Date`
- `fetch`
- `localStorage`
+5 -36
View File
@@ -10,22 +10,13 @@
- [Static Properties](#static-properties) - [Static Properties](#static-properties)
- [Methods](#methods) - [Methods](#methods)
- [Lifecycle](#lifecycle) - [Lifecycle](#lifecycle)
- [`constructor(parent, props)`](#constructorparent-props)
- [`setup()`](#setup)
- [`willStart()`](#willstart)
- [`mounted()`](#mounted)
- [`willUpdateProps(nextProps)`](#willupdatepropsnextprops)
- [`willPatch()`](#willpatch)
- [`patched(snapshot)`](#patchedsnapshot)
- [`willUnmount()`](#willunmount)
- [`catchError(error)`](#catcherrorerror)
- [Root Component](#root-component) - [Root Component](#root-component)
- [Composition](#composition) - [Composition](#composition)
- [Form Input Bindings](#form-input-bindings) - [Form Input Bindings](#form-input-bindings)
- [References](#references) - [References](#references)
- [Dynamic sub components](#dynamic-sub-components) - [Dynamic sub components](#dynamic-sub-components)
- [Functional Components](#functional-components) - [Functional Components](#functional-components)
- [SVG Components](#svg-components) - [SVG components](#svg-components)
## Overview ## Overview
@@ -298,13 +289,8 @@ We explain here all the public methods of the `Component` class.
are updated. It returns a boolean, which indicates if the component should are updated. It returns a boolean, which indicates if the component should
ignore a props update. If it returns false, then `willUpdateProps` will not ignore a props update. If it returns false, then `willUpdateProps` will not
be called, and no rendering will occur. Its default implementation is to be called, and no rendering will occur. Its default implementation is to
always return true. Note that this is an optimization, similar to React's `shouldComponentUpdate`. Most of the time, this should not be used, but it always return true. This is an optimization, similar to React's `shouldComponentUpdate`. Most of the time, this should not be used, but it
can be useful if we are handling large number of components. Since this is an can be useful if we are handling large number of components.
optimization, Owl has the freedom to ignore the result of `shouldUpdate` in
some cases (for example, if a component is remounted, or if we want to force
a full rerender of the UI). However, if `shouldUpdate` returns true, then Owl
provides the guarantee that the component will be rendered at some point in
the future (except if the component is destroyed or if some part of the UI crashes).
* **`destroy()`**. As its name suggests, this method will remove the component, * **`destroy()`**. As its name suggests, this method will remove the component,
and perform all necessary cleanup, such as unmounting the component, its children, and perform all necessary cleanup, such as unmounting the component, its children,
@@ -325,7 +311,7 @@ a owl component:
| Method | Description | | Method | Description |
| ------------------------------------------------ | ----------------------------------------------------------- | | ------------------------------------------------ | ----------------------------------------------------------- |
| **[setup](#setup)** | setup | | **[constructor](#constructorparent-props)** | constructor |
| **[willStart](#willstart)** | async, before first rendering | | **[willStart](#willstart)** | async, before first rendering |
| **[mounted](#mounted)** | just after component is rendered and added to the DOM | | **[mounted](#mounted)** | just after component is rendered and added to the DOM |
| **[willUpdateProps](#willupdatepropsnextprops)** | async, before props update | | **[willUpdateProps](#willupdatepropsnextprops)** | async, before props update |
@@ -370,23 +356,6 @@ class ClickCounter extends owl.Component {
} }
``` ```
Hook functions can be called in the constructor.
#### `setup()`
_setup_ is run just after the component is constructed. It is a lifecycle method,
very similar to the _constructor_, except that it does not receive any argument.
It is a valid method to call hook functions. Note that one of the main reason to
have the `setup` hook in the component lifecycle is to make it possible to
monkey patch it. It is a common need in the Odoo ecosystem.
```javascript
setup() {
useSetupAutofocus();
}
```
#### `willStart()` #### `willStart()`
willStart is an asynchronous hook that can be implemented to willStart is an asynchronous hook that can be implemented to
@@ -789,7 +758,7 @@ template rendered with `props`. In Owl, this can be done by
simply defining a template, that will access the `props` object: simply defining a template, that will access the `props` object:
```js ```js
const Welcome = xml`<h1>Hello, <t t-esc="props.name"/></h1>`; const Welcome = xml`<h1>Hello, {props.name}</h1>`;
class MyComponent extends Component { class MyComponent extends Component {
static template = xml` static template = xml`
-3
View File
@@ -7,7 +7,6 @@ For example, `Component` is available at `owl.Component` and `EventBus` is
exported as `owl.core.EventBus`. exported as `owl.core.EventBus`.
``` ```
browser
Component misc Component misc
Context AsyncRoot Context AsyncRoot
QWeb Portal QWeb Portal
@@ -29,8 +28,6 @@ hooks utils
useContext useContext
useState useState
useRef useRef
useComponent
useEnv
useSubEnv useSubEnv
useStore useStore
useDispatch useDispatch
+14 -1
View File
@@ -133,4 +133,17 @@ the `QWeb` instance and a `browser` object:
- `qweb` will be set to an empty `QWeb` instance. This is absolutely necessary - `qweb` will be set to an empty `QWeb` instance. This is absolutely necessary
for Owl to be able to render anything for Owl to be able to render anything
- `browser`: this is an object that contains some common access points to the - `browser`: this is an object that contains some common access points to the
browser methods with a side effect. See [browser](browser.md) for more information. Note that the browser object will be removed from the environment in Owl 2.0. browser methods with a side effect. This is particularly useful when one want
to test more advanced components, and be able to mock those methods.
More specifically, the `browser` object contains the following methods and objects:
- `setTimeout`
- `clearTimeout`
- `setInterval`
- `clearInterval`
- `requestAnimationFrame`
- `random`
- `Date`
- `fetch`
- `localStorage`
+5 -22
View File
@@ -21,8 +21,6 @@
- [`useStore`](#usestore) - [`useStore`](#usestore)
- [`useDispatch`](#usedispatch) - [`useDispatch`](#usedispatch)
- [`useGetters`](#usegetters) - [`useGetters`](#usegetters)
- [`useComponent`](#usecomponent)
- [`useEnv`](#useenv)
- [Making customized hooks](#making-customized-hooks) - [Making customized hooks](#making-customized-hooks)
## Overview ## Overview
@@ -131,7 +129,7 @@ class SomeComponent extends Component {
### One rule ### One rule
There is only one rule: every hook for a component has to be called in the There is only one rule: every hook for a component has to be called in the
constructor, in the _setup_ method, or in class fields: constructor (or in class fields):
```js ```js
// ok // ok
@@ -147,13 +145,6 @@ class SomeComponent extends Component {
} }
} }
// also ok
class SomeComponent extends Component {
setup() {
this.state = useState({ value: 0 });
}
}
// not ok: this is executed after the constructor is called // not ok: this is executed after the constructor is called
class SomeComponent extends Component { class SomeComponent extends Component {
async willStart() { async willStart() {
@@ -390,16 +381,6 @@ The `useDispatch` hook is the way for components to get a reference to the store
The `useGetters` hook is the way for components to get a reference to the store The `useGetters` hook is the way for components to get a reference to the store
getters. See the [store documentation](store.md) for more information. getters. See the [store documentation](store.md) for more information.
### `useComponent`
The `useComponent` hook is useful as a building block for some customized hooks,
that may need a reference to the component calling them.
### `useEnv`
The `useEnv` hook is useful as a building block for some customized hooks,
that may need a reference to the env of the component calling them.
### Making customized hooks ### Making customized hooks
Hooks are a wonderful way to organize the code of a complex component by feature Hooks are a wonderful way to organize the code of a complex component by feature
@@ -454,11 +435,13 @@ not the solution to every problem.
```js ```js
function useRouter() { function useRouter() {
const env = useEnv(); return Component.current.env.router;
return env.router;
} }
``` ```
This means that we give control to the application developer to create the This means that we give control to the application developer to create the
router, which is good, so they can set it up, subclass it, ... And then, to router, which is good, so they can set it up, subclass it, ... And then, to
test our components, we can just add a mock router in the environment. test our components, we can just add a mock router in the environment.
Note: the code above makes use of the `Component.current` property. This is the
way hooks are able to get a reference to the component currently being created.
+5 -5
View File
@@ -15,7 +15,7 @@ use cases, there is no need to directly instantiate an observer.
For example, this code will display `update` in the console: For example, this code will display `update` in the console:
```javascript ```javascript
const observer = new owl.core.Observer(); const observer = new owl.Observer();
observer.notifyCB = () => console.log("update"); observer.notifyCB = () => console.log("update");
const obj = observer.observe({ a: { b: 1 } }); const obj = observer.observe({ a: { b: 1 } });
@@ -39,14 +39,14 @@ is incremented every time the value is observed. Sometimes, it can be useful
to obtain that number: to obtain that number:
```js ```js
const observer = new owl.core.Observer(); const observer = new owl.Observer();
const obj = observer.observe({ a: { b: 1 } }); const obj = observer.observe({ a: { b: 1 } });
observer.revNumber(obj.a); // 1 observer.deepRevNumber(obj.a); // 1
obj.a.b = 2; obj.a.b = 2;
observer.revNumber(obj.a); // 2 observer.deepRevNumber(obj.a); // 2
``` ```
The `revNumber` can also return 0, which indicates that the value is not The `deepRevNumber` can also return 0, which indicates that the value is not
observed. observed.
-18
View File
@@ -13,7 +13,6 @@
- [Setting Variables](#setting-variables) - [Setting Variables](#setting-variables)
- [Conditionals](#conditionals) - [Conditionals](#conditionals)
- [Dynamic Attributes](#dynamic-attributes) - [Dynamic Attributes](#dynamic-attributes)
- [Dynamic Tag Names](#dynamic-tag-names)
- [Loops](#loops) - [Loops](#loops)
- [Rendering Sub Templates](#rendering-sub-templates) - [Rendering Sub Templates](#rendering-sub-templates)
- [Dynamic Sub Templates](#dynamic-sub-templates) - [Dynamic Sub Templates](#dynamic-sub-templates)
@@ -77,7 +76,6 @@ needs. Here is a list of all Owl specific directives:
| `t-transition` | [Defining an animation](animations.md#css-transitions) | | `t-transition` | [Defining an animation](animations.md#css-transitions) |
| `t-slot` | [Rendering a slot](slots.md) | | `t-slot` | [Rendering a slot](slots.md) |
| `t-model` | [Form input bindings](component.md#form-input-bindings) | | `t-model` | [Form input bindings](component.md#form-input-bindings) |
| `t-tag` | [Rendering nodes with dynamic tag name](#dynamic-tag-names) |
## Reference ## Reference
@@ -326,22 +324,6 @@ values) or a pair `[key, value]`. For example:
<div t-att="['a', 'b']"/> <!-- <div a="b"></div> --> <div t-att="['a', 'b']"/> <!-- <div a="b"></div> -->
``` ```
### Dynamic tag names
When writing generic components or templates, the specific concrete tag for an
HTML element is not known yet. In those situations, the `t-tag` directive is
useful. It simply evaluates dynamically an expression to use as a tag name. The
template:
```xml
<t t-tag="tag">
<span>content</span>
</t>
```
will be rendered as `<div><span>content</span></div>` if the `tag` context key
is set to `div`.
### Loops ### Loops
QWeb has an iteration directive `t-foreach` which take an expression returning the QWeb has an iteration directive `t-foreach` which take an expression returning the
+1 -1
View File
@@ -44,7 +44,7 @@ Slots are defined by the caller, with the `t-set-slot` directive:
```xml ```xml
<div t-name="SomeComponent"> <div t-name="SomeComponent">
<div>some component</div> <div>some component</div>
<Dialog title="'Some Dialog'"> <Dialog title="Some Dialog">
<t t-set-slot="content"> <t t-set-slot="content">
<div>hey</div> <div>hey</div>
</t> </t>
+3 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@odoo/owl", "name": "@odoo/owl",
"version": "1.3.0", "version": "1.0.13",
"description": "Odoo Web Library (OWL)", "description": "Odoo Web Library (OWL)",
"main": "dist/owl.cjs.js", "main": "dist/owl.cjs.js",
"browser": "dist/owl.iife.js", "browser": "dist/owl.iife.js",
@@ -13,8 +13,8 @@
"node": ">=10.15.3" "node": ">=10.15.3"
}, },
"scripts": { "scripts": {
"build:bundle": "rollup -c", "dev": "rollup -c",
"build": "npm run build:bundle", "build": "NODE_ENV=production rollup -c",
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"tools:serve": "python3 tools/server.py || python tools/server.py", "tools:serve": "python3 tools/server.py || python tools/server.py",
@@ -22,7 +22,6 @@
"pretools:watch": "npm run build", "pretools:watch": "npm run build",
"tools:watch": "npm-run-all --parallel tools:serve \"build:* -- --watch\"", "tools:watch": "npm-run-all --parallel tools:serve \"build:* -- --watch\"",
"prettier": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --write", "prettier": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --write",
"check-formatting": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --check",
"publish": "npm run build && npm publish", "publish": "npm run build && npm publish",
"release": "node tools/release.js" "release": "node tools/release.js"
}, },
@@ -41,7 +40,6 @@
"@types/node": "^14.11.8", "@types/node": "^14.11.8",
"chalk": "^3.0.0", "chalk": "^3.0.0",
"cpx": "^1.5.0", "cpx": "^1.5.0",
"current-git-branch": "^1.1.0",
"git-rev-sync": "^1.12.0", "git-rev-sync": "^1.12.0",
"github-api": "^3.3.0", "github-api": "^3.3.0",
"jest": "^23.6.0", "jest": "^23.6.0",
+1 -1
View File
@@ -1,6 +1,6 @@
# 🦉 OWL Roadmap 🦉 # 🦉 OWL Roadmap 🦉
- Current version: 1.3.0 - Current version: 1.0.13
- Status: stable - Status: stable
This roadmap is only an attempt at predicting Owl's future. Everything may This roadmap is only an attempt at predicting Owl's future. Everything may
+1 -8
View File
@@ -10,8 +10,6 @@ export interface Browser {
localStorage: Window["localStorage"]; localStorage: Window["localStorage"];
} }
let localStorage: Window["localStorage"] | null = null;
export const browser: Browser = { export const browser: Browser = {
setTimeout: window.setTimeout.bind(window), setTimeout: window.setTimeout.bind(window),
clearTimeout: window.clearTimeout.bind(window), clearTimeout: window.clearTimeout.bind(window),
@@ -21,10 +19,5 @@ export const browser: Browser = {
random: Math.random, random: Math.random,
Date: window.Date, Date: window.Date,
fetch: (window.fetch || (() => {})).bind(window), fetch: (window.fetch || (() => {})).bind(window),
get localStorage() { localStorage: window.localStorage,
return localStorage || window.localStorage;
},
set localStorage(newLocalStorage: Window["localStorage"]) {
localStorage = newLocalStorage;
},
}; };
+116 -98
View File
@@ -8,6 +8,7 @@ import "./props_validation";
import { Scheduler, scheduler } from "./scheduler"; import { Scheduler, scheduler } from "./scheduler";
import { activateSheet } from "./styles"; import { activateSheet } from "./styles";
import { Browser, browser } from "../browser"; import { Browser, browser } from "../browser";
import { onMounted, onPatched } from "../hooks";
/** /**
* Owl Component System * Owl Component System
@@ -44,15 +45,6 @@ interface MountOptions {
position?: MountPosition; position?: MountPosition;
} }
export const enum STATUS {
CREATED,
WILLSTARTED, // willstart has been called
RENDERED, // first render is completed (so, vnode is now defined)
MOUNTED, // is ready, and in DOM. It has a valid el
UNMOUNTED, // has a valid el, but is not in DOM
DESTROYED,
}
/** /**
* This is mostly an internal detail of implementation. The Meta interface is * This is mostly an internal detail of implementation. The Meta interface is
* useful to typecheck and describe the internal keys used by Owl to manage the * useful to typecheck and describe the internal keys used by Owl to manage the
@@ -65,7 +57,8 @@ interface Internal<T extends Env> {
depth: number; depth: number;
vnode: VNode | null; vnode: VNode | null;
pvnode: VNode | null; pvnode: VNode | null;
status: STATUS; isMounted: boolean;
isDestroyed: boolean;
// parent and children keys are obviously useful to setup the parent-children // parent and children keys are obviously useful to setup the parent-children
// relationship. // relationship.
@@ -99,8 +92,24 @@ interface Internal<T extends Env> {
refs: { [key: string]: Component<any, T> | HTMLElement | undefined } | null; refs: { [key: string]: Component<any, T> | HTMLElement | undefined } | null;
} }
interface DevToolsAccess {
props?: any;
defaultProps?: any;
template?: string | null;
state: Observer;
tag: String;
depth: number,
}
export const portalSymbol = Symbol("portal"); // FIXME export const portalSymbol = Symbol("portal"); // FIXME
/**
* It is required for the dev tools to have access to the __owl__ element.
*/
interface HTMLElementWithDevToolsAccess extends HTMLElement {
__owl_devtools__: DevToolsAccess
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Component // Component
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -118,11 +127,13 @@ export class Component<Props extends {} = any, T extends Env = Env> {
// expose scheduler s.t. it can be mocked for testing purposes // expose scheduler s.t. it can be mocked for testing purposes
static scheduler: Scheduler = scheduler; static scheduler: Scheduler = scheduler;
__devtools__: DevToolsAccess;
/** /**
* The `el` is the root element of the component. Note that it could be null: * The `el` is the root element of the component. Note that it could be null:
* this is the case if the component is not mounted yet, or is destroyed. * this is the case if the component is not mounted yet, or is destroyed.
*/ */
get el(): HTMLElement | null { get el(): HTMLElementWithDevToolsAccess | null {
return this.__owl__.vnode ? (<any>this).__owl__.vnode.elm : null; return this.__owl__.vnode ? (<any>this).__owl__.vnode.elm : null;
} }
@@ -167,23 +178,20 @@ export class Component<Props extends {} = any, T extends Env = Env> {
if (!this.env.qweb) { if (!this.env.qweb) {
this.env.qweb = new QWeb(); this.env.qweb = new QWeb();
} }
// TODO: remove this in owl 2.0
if (!this.env.browser) { if (!this.env.browser) {
this.env.browser = browser; this.env.browser = browser;
} }
this.env.qweb.on("update", this, () => { this.env.qweb.on("update", this, () => {
switch (this.__owl__.status) { if (this.__owl__.isMounted) {
case STATUS.MOUNTED: this.render(true);
this.render(true); }
break; if (this.__owl__.isDestroyed) {
case STATUS.DESTROYED: // this is unlikely to happen, but if a root widget is destroyed,
// this is unlikely to happen, but if a root widget is destroyed, // we want to remove our subscription. The usual way to do that
// we want to remove our subscription. The usual way to do that // would be to perform some check in the destroy method, but since
// would be to perform some check in the destroy method, but since // it is very performance sensitive, and since this is a rare event,
// it is very performance sensitive, and since this is a rare event, // we simply do it lazily
// we simply do it lazily this.env.qweb.off("update", this);
this.env.qweb.off("update", this);
break;
} }
}); });
depth = 0; depth = 0;
@@ -196,7 +204,8 @@ export class Component<Props extends {} = any, T extends Env = Env> {
depth: depth, depth: depth,
vnode: null, vnode: null,
pvnode: null, pvnode: null,
status: STATUS.CREATED, isMounted: false,
isDestroyed: false,
parent: parent || null, parent: parent || null,
children: {}, children: {},
cmap: {}, cmap: {},
@@ -218,19 +227,25 @@ export class Component<Props extends {} = any, T extends Env = Env> {
if (constr.style) { if (constr.style) {
this.__applyStyles(constr); this.__applyStyles(constr);
} }
this.setup();
}
/** // DevTools hooks
* setup is run just after the component is constructed. This is the standard onMounted(() => {
* location where the component can setup its hooks. It has some advantages this.__devtools__ = {
* over the constructor: depth: this.__owl__.depth,
* - it can be patched (useful in odoo ecosystem) state: this.__owl__.observer,
* - it does not need to propagate the arguments to the super call tag: this.constructor.name
* };
* Note: this method should not be called manually. this.__devtools__.defaultProps = defaultProps;
*/ this.__devtools__.props = this.props;
setup() {} this.__devtools__.template = template;
this.el.__owl_devtools__ = this.__devtools__;
})
onPatched(() => {
this.__devtools__.depth = this.__owl__.depth,
this.el.__owl_devtools__ = this.__devtools__;
})
}
/** /**
* willStart is an asynchronous hook that can be implemented to perform some * willStart is an asynchronous hook that can be implemented to perform some
@@ -326,49 +341,42 @@ export class Component<Props extends {} = any, T extends Env = Env> {
* Note that a component can be mounted an unmounted several times * Note that a component can be mounted an unmounted several times
*/ */
async mount(target: HTMLElement | DocumentFragment, options: MountOptions = {}): Promise<void> { async mount(target: HTMLElement | DocumentFragment, options: MountOptions = {}): Promise<void> {
const position = options.position || "last-child";
const __owl__ = this.__owl__;
if (__owl__.isMounted) {
if (position !== "self" && this.el!.parentNode !== target) {
// in this situation, we are trying to mount a component on a different
// target. In this case, we need to unmount first, otherwise it will
// not work.
this.unmount();
} else {
return Promise.resolve();
}
}
if (__owl__.isDestroyed) {
throw new Error("Cannot mount a destroyed component");
}
if (__owl__.currentFiber) {
const currentFiber = __owl__.currentFiber;
if (currentFiber.target === target && currentFiber.position === position) {
return scheduler.addFiber(currentFiber);
} else {
scheduler.rejectFiber(currentFiber, "Mounting operation cancelled");
}
}
if (!(target instanceof HTMLElement || target instanceof DocumentFragment)) { if (!(target instanceof HTMLElement || target instanceof DocumentFragment)) {
let message = `Component '${this.constructor.name}' cannot be mounted: the target is not a valid DOM node.`; let message = `Component '${this.constructor.name}' cannot be mounted: the target is not a valid DOM node.`;
message += `\nMaybe the DOM is not ready yet? (in that case, you can use owl.utils.whenReady)`; message += `\nMaybe the DOM is not ready yet? (in that case, you can use owl.utils.whenReady)`;
throw new Error(message); throw new Error(message);
} }
const position = options.position || "last-child"; const fiber = new Fiber(null, this, false, target, position);
const __owl__ = this.__owl__; fiber.shouldPatch = false;
const currentFiber = __owl__.currentFiber; if (!__owl__.vnode) {
this.__prepareAndRender(fiber, () => {});
switch (__owl__.status) { } else {
case STATUS.CREATED: { this.__render(fiber);
const fiber = new Fiber(null, this, true, target, position);
fiber.shouldPatch = false;
this.__prepareAndRender(fiber, () => {});
return scheduler.addFiber(fiber);
}
case STATUS.WILLSTARTED:
case STATUS.RENDERED:
currentFiber.target = target;
currentFiber.position = position;
return scheduler.addFiber(currentFiber);
case STATUS.UNMOUNTED: {
const fiber = new Fiber(null, this, true, target, position);
fiber.shouldPatch = false;
this.__render(fiber);
return scheduler.addFiber(fiber);
}
case STATUS.MOUNTED: {
if (position !== "self" && this.el!.parentNode !== target) {
const fiber = new Fiber(null, this, true, target, position);
fiber.shouldPatch = false;
this.__render(fiber);
return scheduler.addFiber(fiber);
} else {
return Promise.resolve();
}
}
case STATUS.DESTROYED:
throw new Error("Cannot mount a destroyed component");
} }
return scheduler.addFiber(fiber);
} }
/** /**
@@ -376,7 +384,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
* to call willUnmount calls and remove the component from the DOM. * to call willUnmount calls and remove the component from the DOM.
*/ */
unmount() { unmount() {
if (this.__owl__.status === STATUS.MOUNTED) { if (this.__owl__.isMounted) {
this.__callWillUnmount(); this.__callWillUnmount();
this.el!.remove(); this.el!.remove();
} }
@@ -394,7 +402,10 @@ export class Component<Props extends {} = any, T extends Env = Env> {
async render(force: boolean = false): Promise<void> { async render(force: boolean = false): Promise<void> {
const __owl__ = this.__owl__; const __owl__ = this.__owl__;
const currentFiber = __owl__.currentFiber; const currentFiber = __owl__.currentFiber;
if (!__owl__.vnode && !currentFiber) { if (!__owl__.isMounted && !currentFiber) {
// if we get here, this means that the component was either never mounted,
// or was unmounted and some state change triggered a render. Either way,
// we do not want to actually render anything in this case.
return; return;
} }
if (currentFiber && !currentFiber.isRendered && !currentFiber.isCompleted) { if (currentFiber && !currentFiber.isRendered && !currentFiber.isCompleted) {
@@ -403,13 +414,15 @@ export class Component<Props extends {} = any, T extends Env = Env> {
// if we aren't mounted at this point, it implies that there is a // if we aren't mounted at this point, it implies that there is a
// currentFiber that is already rendered (isRendered is true), so we are // currentFiber that is already rendered (isRendered is true), so we are
// about to be mounted // about to be mounted
const status = __owl__.status; const isMounted = __owl__.isMounted;
const fiber = new Fiber(null, this, force, null, null); const fiber = new Fiber(null, this, force, null, null);
Promise.resolve().then(() => { Promise.resolve().then(() => {
if (__owl__.status === STATUS.MOUNTED || status !== STATUS.MOUNTED) { if (__owl__.isMounted || !isMounted) {
if (fiber.isCompleted || fiber.isRendered) { if (fiber.isCompleted) {
return; return;
} }
// we are mounted (__owl__.isMounted), or if we are currently being
// mounted (!isMounted), so we call __render
this.__render(fiber); this.__render(fiber);
} else { } else {
// we were mounted when render was called, but we aren't anymore, so we // we were mounted when render was called, but we aren't anymore, so we
@@ -433,7 +446,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
*/ */
destroy() { destroy() {
const __owl__ = this.__owl__; const __owl__ = this.__owl__;
if (__owl__.status !== STATUS.DESTROYED) { if (!__owl__.isDestroyed) {
const el = this.el; const el = this.el;
this.__destroy(__owl__.parent); this.__destroy(__owl__.parent);
if (el) { if (el) {
@@ -478,12 +491,13 @@ export class Component<Props extends {} = any, T extends Env = Env> {
*/ */
__destroy(parent: Component | null) { __destroy(parent: Component | null) {
const __owl__ = this.__owl__; const __owl__ = this.__owl__;
if (__owl__.status === STATUS.MOUNTED) { const isMounted = __owl__.isMounted;
if (isMounted) {
if (__owl__.willUnmountCB) { if (__owl__.willUnmountCB) {
__owl__.willUnmountCB(); __owl__.willUnmountCB();
} }
this.willUnmount(); this.willUnmount();
__owl__.status = STATUS.UNMOUNTED; __owl__.isMounted = false;
} }
const children = __owl__.children; const children = __owl__.children;
for (let key in children) { for (let key in children) {
@@ -494,7 +508,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
delete parent.__owl__.children[id]; delete parent.__owl__.children[id];
__owl__.parent = null; __owl__.parent = null;
} }
__owl__.status = STATUS.DESTROYED; __owl__.isDestroyed = true;
delete __owl__.vnode; delete __owl__.vnode;
if (__owl__.currentFiber) { if (__owl__.currentFiber) {
__owl__.currentFiber.isCompleted = true; __owl__.currentFiber.isCompleted = true;
@@ -504,7 +518,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
__callMounted() { __callMounted() {
const __owl__ = this.__owl__; const __owl__ = this.__owl__;
__owl__.status = STATUS.MOUNTED; __owl__.isMounted = true;
__owl__.currentFiber = null; __owl__.currentFiber = null;
this.mounted(); this.mounted();
if (__owl__.mountedCB) { if (__owl__.mountedCB) {
@@ -518,7 +532,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
__owl__.willUnmountCB(); __owl__.willUnmountCB();
} }
this.willUnmount(); this.willUnmount();
__owl__.status = STATUS.UNMOUNTED; __owl__.isMounted = false;
if (__owl__.currentFiber) { if (__owl__.currentFiber) {
__owl__.currentFiber.isCompleted = true; __owl__.currentFiber.isCompleted = true;
__owl__.currentFiber.root.counter = 0; __owl__.currentFiber.root.counter = 0;
@@ -526,7 +540,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
const children = __owl__.children; const children = __owl__.children;
for (let id in children) { for (let id in children) {
const comp = children[id]; const comp = children[id];
if (comp.__owl__.status === STATUS.MOUNTED) { if (comp.__owl__.isMounted) {
comp.__callWillUnmount(); comp.__callWillUnmount();
} }
} }
@@ -647,25 +661,18 @@ export class Component<Props extends {} = any, T extends Env = Env> {
} }
return p._template; return p._template;
} }
async __prepareAndRender(fiber: Fiber, cb: CallableFunction) { async __prepareAndRender(fiber: Fiber, cb: CallableFunction) {
try { try {
const proms = Promise.all([ await Promise.all([this.willStart(), this.__owl__.willStartCB && this.__owl__.willStartCB()]);
this.willStart(),
this.__owl__.willStartCB && this.__owl__.willStartCB(),
]);
this.__owl__.status = STATUS.WILLSTARTED;
await proms;
if (this.__owl__.status === <any>STATUS.DESTROYED) {
return Promise.resolve();
}
} catch (e) { } catch (e) {
fiber.handleError(e); fiber.handleError(e);
return Promise.resolve(); return Promise.resolve();
} }
if (this.__owl__.isDestroyed) {
return Promise.resolve();
}
if (!fiber.isCompleted) { if (!fiber.isCompleted) {
this.__render(fiber); this.__render(fiber);
this.__owl__.status = STATUS.RENDERED;
cb(); cb();
} }
} }
@@ -688,7 +695,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
for (let childKey in __owl__.children) { for (let childKey in __owl__.children) {
const child = __owl__.children[childKey]; const child = __owl__.children[childKey];
const childOwl = child.__owl__; const childOwl = child.__owl__;
if (childOwl.status !== STATUS.MOUNTED && childOwl.parentLastFiberId < fiber.id) { if (!childOwl.isMounted && childOwl.parentLastFiberId < fiber.id) {
// we only do here a "soft" destroy, meaning that we leave the child // we only do here a "soft" destroy, meaning that we leave the child
// dom node alone, without removing it. Most of the time, it does not // dom node alone, without removing it. Most of the time, it does not
// matter, because the child component is already unmounted. However, // matter, because the child component is already unmounted. However,
@@ -735,6 +742,17 @@ export class Component<Props extends {} = any, T extends Env = Env> {
} }
} }
/**
* Only called by qweb t-component directive (when t-keepalive is set)
*/
__remount() {
const __owl__ = this.__owl__;
if (!__owl__.isMounted) {
__owl__.isMounted = true;
this.mounted();
}
}
/** /**
* Apply default props (only top level). * Apply default props (only top level).
* *
+6 -4
View File
@@ -1,7 +1,6 @@
import { QWeb } from "../qweb/index"; import { QWeb } from "../qweb/index";
import { INTERP_REGEXP } from "../qweb/compilation_context"; import { INTERP_REGEXP } from "../qweb/compilation_context";
import { makeHandlerCode, MODS_CODE } from "../qweb/extensions"; import { makeHandlerCode, MODS_CODE } from "../qweb/extensions";
import { STATUS } from "./component";
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// t-component // t-component
@@ -362,7 +361,7 @@ QWeb.addDirective({
// need to update component // need to update component
let styleCode = ""; let styleCode = "";
if (tattStyle) { if (tattStyle) {
styleCode = `.then(()=>{if (w${componentID}.__owl__.status === ${STATUS.DESTROYED}) {return};w${componentID}.el.style=${tattStyle};});`; styleCode = `.then(()=>{if (w${componentID}.__owl__.isDestroyed) {return};w${componentID}.el.style=${tattStyle};});`;
} }
ctx.addLine( ctx.addLine(
`w${componentID}.__updateProps(props${componentID}, extra.fiber, ${scope})${styleCode};` `w${componentID}.__updateProps(props${componentID}, extra.fiber, ${scope})${styleCode};`
@@ -378,11 +377,14 @@ QWeb.addDirective({
ctx.addElse(); ctx.addElse();
// new component // new component
const contextualValue = value.match(INTERP_REGEXP) ? "false" : ctx.formatExpression(value); let dynamicFallback = "";
if (!value.match(INTERP_REGEXP)) {
dynamicFallback = `|| ${ctx.formatExpression(value)}`;
}
const interpValue = ctx.interpolate(value); const interpValue = ctx.interpolate(value);
ctx.addLine(`let componentKey${componentID} = ${interpValue};`); ctx.addLine(`let componentKey${componentID} = ${interpValue};`);
ctx.addLine( ctx.addLine(
`let W${componentID} = ${contextualValue} || context.constructor.components[componentKey${componentID}] || QWeb.components[componentKey${componentID}];` `let W${componentID} = context.constructor.components[componentKey${componentID}] || QWeb.components[componentKey${componentID}]${dynamicFallback};`
); );
// maybe only do this in dev mode... // maybe only do this in dev mode...
+20 -35
View File
@@ -1,5 +1,5 @@
import { h, VNode } from "../vdom/index"; import { h, VNode } from "../vdom/index";
import { Component, MountPosition, STATUS } from "./component"; import { Component, MountPosition } from "./component";
import { scheduler } from "./scheduler"; import { scheduler } from "./scheduler";
/** /**
@@ -82,7 +82,6 @@ export class Fiber {
let oldFiber = __owl__.currentFiber; let oldFiber = __owl__.currentFiber;
if (oldFiber && !oldFiber.isCompleted) { if (oldFiber && !oldFiber.isCompleted) {
this.force = true;
if (oldFiber.root === oldFiber && !parent) { if (oldFiber.root === oldFiber && !parent) {
// both oldFiber and this fiber are root fibers // both oldFiber and this fiber are root fibers
this._reuseFiber(oldFiber); this._reuseFiber(oldFiber);
@@ -107,8 +106,6 @@ export class Fiber {
*/ */
_reuseFiber(oldFiber: Fiber) { _reuseFiber(oldFiber: Fiber) {
oldFiber.cancel(); // cancel children fibers oldFiber.cancel(); // cancel children fibers
oldFiber.target = this.target || oldFiber.target;
oldFiber.position = this.position || oldFiber.position;
oldFiber.isCompleted = false; // keep the root fiber alive oldFiber.isCompleted = false; // keep the root fiber alive
oldFiber.isRendered = false; // the fiber has to be re-rendered oldFiber.isRendered = false; // the fiber has to be re-rendered
if (oldFiber.child) { if (oldFiber.child) {
@@ -190,8 +187,7 @@ export class Fiber {
complete() { complete() {
let component = this.component; let component = this.component;
this.isCompleted = true; this.isCompleted = true;
const status = component.__owl__.status; if (!this.target && !component.__owl__.isMounted) {
if (status === STATUS.DESTROYED) {
return; return;
} }
@@ -205,16 +201,14 @@ export class Fiber {
const patchLen = patchQueue.length; const patchLen = patchQueue.length;
// call willPatch hook on each fiber of patchQueue // call willPatch hook on each fiber of patchQueue
if (status === STATUS.MOUNTED) { for (let i = 0; i < patchLen; i++) {
for (let i = 0; i < patchLen; i++) { const fiber = patchQueue[i];
const fiber = patchQueue[i]; if (fiber.shouldPatch) {
if (fiber.shouldPatch) { component = fiber.component;
component = fiber.component; if (component.__owl__.willPatchCB) {
if (component.__owl__.willPatchCB) { component.__owl__.willPatchCB();
component.__owl__.willPatchCB();
}
component.willPatch();
} }
component.willPatch();
} }
} }
@@ -255,9 +249,8 @@ export class Fiber {
component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm; component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm;
} }
} }
const compOwl = component.__owl__; if (fiber === component.__owl__.currentFiber) {
if (fiber === compOwl.currentFiber) { component.__owl__.currentFiber = null;
compOwl.currentFiber = null;
} }
} }
@@ -277,24 +270,16 @@ export class Fiber {
} }
// call patched/mounted hook on each fiber of (reversed) patchQueue // call patched/mounted hook on each fiber of (reversed) patchQueue
if (status === STATUS.MOUNTED || inDOM) { for (let i = patchLen - 1; i >= 0; i--) {
for (let i = patchLen - 1; i >= 0; i--) { const fiber = patchQueue[i];
const fiber = patchQueue[i]; component = fiber.component;
component = fiber.component; if (fiber.shouldPatch && !this.target) {
if (fiber.shouldPatch && !this.target) { component.patched();
component.patched(); if (component.__owl__.patchedCB) {
if (component.__owl__.patchedCB) { component.__owl__.patchedCB();
component.__owl__.patchedCB();
}
} else {
component.__callMounted();
} }
} } else if (this.target ? inDOM : true) {
} else { component.__callMounted();
for (let i = patchLen - 1; i >= 0; i--) {
const fiber = patchQueue[i];
component = fiber.component;
component.__owl__.status = STATUS.UNMOUNTED;
} }
} }
} }
+10
View File
@@ -115,6 +115,16 @@ export function useContextWithCB(ctx: Context, component: Component, method): an
__owl__.observer = new Observer(); __owl__.observer = new Observer();
__owl__.observer.notifyCB = component.render.bind(component); __owl__.observer.notifyCB = component.render.bind(component);
} }
const currentCB = __owl__.observer.notifyCB;
__owl__.observer.notifyCB = function () {
if (ctx.rev > mapping[id]) {
// in this case, the context has been updated since we were rendering
// last, and we do not need to render here with the observer. A
// rendering is coming anyway, with the correct props.
return;
}
currentCB();
};
mapping[id] = 0; mapping[id] = 0;
const renderFn = __owl__.renderFn; const renderFn = __owl__.renderFn;
-1
View File
@@ -21,7 +21,6 @@ export class Observer {
rev: number = 1; rev: number = 1;
allowMutations: boolean = true; allowMutations: boolean = true;
weakMap: WeakMap<any, any> = new WeakMap(); weakMap: WeakMap<any, any> = new WeakMap();
notifyCB() {} notifyCB() {}
observe<T>(value: T, parent?: any): T { observe<T>(value: T, parent?: any): T {
+1 -21
View File
@@ -1,4 +1,4 @@
import { Component, Env } from "./component/component"; import { Component } from "./component/component";
import { Observer } from "./core/observer"; import { Observer } from "./core/observer";
/** /**
@@ -118,26 +118,6 @@ export function useRef<C extends Component = Component>(name: string): Ref<C> {
}; };
} }
// -----------------------------------------------------------------------------
// "Builder" hooks
// -----------------------------------------------------------------------------
/**
* This hook is useful as a building block for some customized hooks, that may
* need a reference to the component calling them.
*/
export function useComponent<P, E extends Env>(): Component<P, E> {
return Component.current as any;
}
/**
* This hook is useful as a building block for some customized hooks, that may
* need a reference to the env of the component calling them.
*/
export function useEnv<E extends Env>(): E {
return Component.current.env as any;
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// useSubEnv // useSubEnv
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
-1
View File
@@ -22,7 +22,6 @@ import { Router } from "./router/router";
export { Component, mount } from "./component/component"; export { Component, mount } from "./component/component";
export { QWeb }; export { QWeb };
export { config }; export { config };
export { browser } from "./browser";
export const Context = _context.Context; export const Context = _context.Context;
export const useState = _hooks.useState; export const useState = _hooks.useState;
+4 -4
View File
@@ -29,14 +29,14 @@ const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in
"," ","
); );
const WORD_REPLACEMENT = Object.assign(Object.create(null), { const WORD_REPLACEMENT = {
and: "&&", and: "&&",
or: "||", or: "||",
gt: ">", gt: ">",
gte: ">=", gte: ">=",
lt: "<", lt: "<",
lte: "<=", lte: "<=",
}); };
export interface QWebVar { export interface QWebVar {
id: string; // foo id: string; // foo
@@ -69,7 +69,7 @@ interface Token {
varName?: string; varName?: string;
} }
const STATIC_TOKEN_MAP: { [key: string]: TKind } = Object.assign(Object.create(null), { const STATIC_TOKEN_MAP: { [key: string]: TKind } = {
"{": "LEFT_BRACE", "{": "LEFT_BRACE",
"}": "RIGHT_BRACE", "}": "RIGHT_BRACE",
"[": "LEFT_BRACKET", "[": "LEFT_BRACKET",
@@ -78,7 +78,7 @@ const STATIC_TOKEN_MAP: { [key: string]: TKind } = Object.assign(Object.create(n
",": "COMMA", ",": "COMMA",
"(": "LEFT_PAREN", "(": "LEFT_PAREN",
")": "RIGHT_PAREN", ")": "RIGHT_PAREN",
}); };
// note that the space after typeof is relevant. It makes sure that the formatted // note that the space after typeof is relevant. It makes sure that the formatted
// expression has a space after typeof // expression has a space after typeof
+1 -2
View File
@@ -1,4 +1,3 @@
import { STATUS } from "../component/component";
import { VNode } from "../vdom/index"; import { VNode } from "../vdom/index";
import { INTERP_REGEXP } from "./compilation_context"; import { INTERP_REGEXP } from "./compilation_context";
import { QWeb } from "./qweb"; import { QWeb } from "./qweb";
@@ -77,7 +76,7 @@ export function makeHandlerCode(
code = ctx.captureExpression(value); code = ctx.captureExpression(value);
} }
const modCode = mods.map((mod) => modcodes[mod]).join(""); const modCode = mods.map((mod) => modcodes[mod]).join("");
let handler = `function (e) {if (context.__owl__.status === ${STATUS.DESTROYED}){return}${modCode}${code}}`; let handler = `function (e) {if (!context.__owl__.isMounted){return}${modCode}${code}}`;
if (putInCache) { if (putInCache) {
const key = ctx.generateTemplateKey(event); const key = ctx.generateTemplateKey(event);
ctx.addLine(`extra.handlers[${key}] = extra.handlers[${key}] || ${handler};`); ctx.addLine(`extra.handlers[${key}] = extra.handlers[${key}] || ${handler};`);
+3 -13
View File
@@ -70,7 +70,6 @@ const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
const lineBreakRE = /[\r\n]/; const lineBreakRE = /[\r\n]/;
const whitespaceRE = /\s+/g; const whitespaceRE = /\s+/g;
const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
const NODE_HOOKS_PARAMS = { const NODE_HOOKS_PARAMS = {
create: "(_, n)", create: "(_, n)",
@@ -203,7 +202,6 @@ export class QWeb extends EventBus {
att: 1, att: 1,
attf: 1, attf: 1,
translation: 1, translation: 1,
tag: 1,
}; };
static DIRECTIVES: Directive[] = []; static DIRECTIVES: Directive[] = [];
@@ -498,8 +496,7 @@ export class QWeb extends EventBus {
} }
if (this.translateFn) { if (this.translateFn) {
if ((node.parentNode as any).getAttribute("t-translation") !== "off") { if ((node.parentNode as any).getAttribute("t-translation") !== "off") {
const match = translationRE.exec(text); text = this.translateFn(text);
text = match[1] + this.translateFn(match[2]) + match[3];
} }
} }
if (ctx.parentNode) { if (ctx.parentNode) {
@@ -615,7 +612,7 @@ export class QWeb extends EventBus {
} }
} }
if (node.nodeName !== "t" || node.hasAttribute("t-tag")) { if (node.nodeName !== "t") {
let nodeID = this._compileGenericNode(node, ctx, withHandlers); let nodeID = this._compileGenericNode(node, ctx, withHandlers);
ctx = ctx.withParent(nodeID); ctx = ctx.withParent(nodeID);
let nodeHooks = {}; let nodeHooks = {};
@@ -842,14 +839,7 @@ export class QWeb extends EventBus {
ctx.addLine(`}`); ctx.addLine(`}`);
ctx.closeIf(); ctx.closeIf();
} }
let nodeName = `'${node.nodeName}'`; ctx.addLine(`let vn${nodeID} = h('${node.nodeName}', p${nodeID}, c${nodeID});`);
if ((<Element>node).hasAttribute("t-tag")) {
const tagExpr = (<Element>node).getAttribute("t-tag");
(<Element>node).removeAttribute("t-tag");
nodeName = `tag${ctx.generateID()}`;
ctx.addLine(`let ${nodeName} = ${ctx.formatExpression(tagExpr)};`);
}
ctx.addLine(`let vn${nodeID} = h(${nodeName}, p${nodeID}, c${nodeID});`);
if (ctx.parentNode) { if (ctx.parentNode) {
ctx.addLine(`c${ctx.parentNode}.push(vn${nodeID});`); ctx.addLine(`c${ctx.parentNode}.push(vn${nodeID});`);
} else if (ctx.loopNumber || ctx.hasKey0) { } else if (ctx.loopNumber || ctx.hasKey0) {
+31 -36
View File
@@ -11,7 +11,6 @@ type NavigationGuard = (info: {
export interface Route { export interface Route {
name: string; name: string;
path: string; path: string;
extractionRegExp: RegExp;
component?: any; component?: any;
redirect?: Destination; redirect?: Destination;
params: string[]; params: string[];
@@ -55,7 +54,6 @@ export interface EnvWithRouter extends Env {
} }
const paramRegexp = /\{\{(.*?)\}\}/; const paramRegexp = /\{\{(.*?)\}\}/;
const globalParamRegexp = new RegExp(paramRegexp.source, "g");
export class Router { export class Router {
currentRoute: Route | null = null; currentRoute: Route | null = null;
@@ -89,7 +87,6 @@ export class Router {
this.validateDestination(partialRoute.redirect); this.validateDestination(partialRoute.redirect);
} }
partialRoute.params = partialRoute.path ? findParams(partialRoute.path) : []; partialRoute.params = partialRoute.path ? findParams(partialRoute.path) : [];
partialRoute.extractionRegExp = makeExtractionRegExp(partialRoute.path);
this.routes[partialRoute.name] = partialRoute as Route; this.routes[partialRoute.name] = partialRoute as Route;
this.routeIds.push(partialRoute.name); this.routeIds.push(partialRoute.name);
} }
@@ -173,14 +170,19 @@ export class Router {
} }
private routeToPath(route: Route, params: RouteParams): string { private routeToPath(route: Route, params: RouteParams): string {
const path = route.path;
const parts = path.split("/");
const l = parts.length;
for (let i = 0; i < l; i++) {
const part = parts[i];
const match = part.match(paramRegexp);
if (match) {
const key = match[1].split(".")[0];
parts[i] = <string>params[key];
}
}
const prefix = this.mode === "hash" ? "#" : ""; const prefix = this.mode === "hash" ? "#" : "";
return ( return prefix + parts.join("/");
prefix +
route.path.replace(globalParamRegexp, (match, param) => {
const [key] = param.split(".");
return <string>params[key];
})
);
} }
private currentPath(): string { private currentPath(): string {
@@ -242,47 +244,40 @@ export class Router {
if (path.startsWith("#")) { if (path.startsWith("#")) {
path = path.slice(1); path = path.slice(1);
} }
const paramsMatch = path.match(route.extractionRegExp); const descrParts = route.path.split("/");
if (!paramsMatch) { const targetParts = path.split("/");
const l = descrParts.length;
if (l !== targetParts.length) {
return false; return false;
} }
const result = {}; const result = {};
route.params.forEach((param, index) => { for (let i = 0; i < l; i++) {
const [key, suffix] = param.split("."); const descr = descrParts[i];
const paramValue = paramsMatch[index + 1]; let target: string | number = targetParts[i];
if (suffix === "number") { const match = descr.match(paramRegexp);
return (result[key] = parseInt(paramValue, 10)); if (match) {
const [key, suffix] = match[1].split(".");
if (suffix === "number") {
target = parseInt(target, 10);
}
result[key] = target;
} else if (descr !== target) {
return false;
} }
return (result[key] = paramValue); }
});
return result; return result;
} }
} }
function findParams(str: string): string[] { function findParams(str: string): string[] {
const globalParamRegexp = /\{\{(.*?)\}\}/g;
const result: string[] = []; const result: string[] = [];
let m; let m;
do { do {
m = globalParamRegexp.exec(str); m = globalParamRegexp.exec(str);
if (m) { if (m) {
result.push(m[1]); result.push(m[1].split(".")[0]);
} }
} while (m); } while (m);
return result; return result;
} }
function escapeRegExp(str: string) {
return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
function makeExtractionRegExp(path: string) {
// replace param strings with capture groups so that we can build a regex to match over the path
const extractionString = path
.split(paramRegexp)
.map((part, index) => {
return index % 2 ? "(.*)" : escapeRegExp(part);
})
.join("");
// Example: /home/{{param1}}/{{param2}} => ^\/home\/(.*)\/(.*)$
return new RegExp(`^${extractionString}$`);
}
+5 -15
View File
@@ -1,4 +1,5 @@
import { Component, Env } from "./component/component"; import { Component } from "./component/component";
import { Env } from "./component/component";
import { Context, useContextWithCB } from "./context"; import { Context, useContextWithCB } from "./context";
import { onWillUpdateProps } from "./hooks"; import { onWillUpdateProps } from "./hooks";
@@ -75,11 +76,6 @@ export class Store extends Context {
); );
return result; return result;
} }
__notifyComponents(): Promise<void> {
this.trigger("before-update");
return super.__notifyComponents();
}
} }
interface SelectorOptions { interface SelectorOptions {
@@ -110,16 +106,13 @@ export function useStore(selector, options: SelectorOptions = {}): any {
const newRevNumber = hashFn(result); const newRevNumber = hashFn(result);
if ((newRevNumber > 0 && revNumber !== newRevNumber) || !isEqual(oldResult, result)) { if ((newRevNumber > 0 && revNumber !== newRevNumber) || !isEqual(oldResult, result)) {
revNumber = newRevNumber; revNumber = newRevNumber;
if (options.onUpdate) {
options.onUpdate(result);
}
return true; return true;
} }
return false; return false;
} }
if (options.onUpdate) {
store.on("before-update", component, () => {
const newValue = selector(store!.state, component.props!);
options.onUpdate(newValue);
});
}
store.updateFunctions[componentId].push(function (): boolean { store.updateFunctions[componentId].push(function (): boolean {
return selectCompareUpdate(store!.state, component.props); return selectCompareUpdate(store!.state, component.props);
}); });
@@ -140,9 +133,6 @@ export function useStore(selector, options: SelectorOptions = {}): any {
const __destroy = component.__destroy; const __destroy = component.__destroy;
component.__destroy = (parent) => { component.__destroy = (parent) => {
delete store.updateFunctions[componentId]; delete store.updateFunctions[componentId];
if (options.onUpdate) {
store.off("before-update", component);
}
__destroy.call(component, parent); __destroy.call(component, parent);
}; };
+3 -3
View File
@@ -24,7 +24,7 @@ exports[`animations t-transition combined with component 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Child\`; let componentKey2 = \`Child\`;
let W2 = scope['Child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
const __patch2 = w2.__patch; const __patch2 = w2.__patch;
@@ -69,7 +69,7 @@ exports[`animations t-transition combined with t-component and t-if 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Child\`; let componentKey2 = \`Child\`;
let W2 = scope['Child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
const __patch2 = w2.__patch; const __patch2 = w2.__patch;
@@ -115,7 +115,7 @@ exports[`animations t-transition combined with t-component, remove and re-add be
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Child\`; let componentKey2 = \`Child\`;
let W2 = scope['Child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
const __patch2 = w2.__patch; const __patch2 = w2.__patch;
@@ -20,12 +20,12 @@ exports[`class and style attributes with t-component dynamic t-att-style is prop
w2 = false; w2 = false;
} }
if (w2) { if (w2) {
w2.__updateProps(props2, extra.fiber, undefined).then(()=>{if (w2.__owl__.status === 5) {return};w2.el.style=_4;});; w2.__updateProps(props2, extra.fiber, undefined).then(()=>{if (w2.__owl__.isDestroyed) {return};w2.el.style=_4;});;
let pvnode = w2.__owl__.pvnode; let pvnode = w2.__owl__.pvnode;
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`child\`; let componentKey2 = \`child\`;
let W2 = scope['child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
@@ -67,7 +67,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Child\`; let componentKey2 = \`Child\`;
let W2 = scope['Child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
@@ -125,7 +125,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Child\`; let componentKey2 = \`Child\`;
let W2 = scope['Child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
@@ -24,7 +24,7 @@ exports[`basic widget properties can handle empty props 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Child\`; let componentKey2 = \`Child\`;
let W2 = scope['Child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
@@ -97,7 +97,7 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey10 = \`Child\`; let componentKey10 = \`Child\`;
let W10 = scope['Child'] || context.constructor.components[componentKey10] || QWeb.components[componentKey10]; let W10 = context.constructor.components[componentKey10] || QWeb.components[componentKey10]|| scope['Child'];
if (!W10) {throw new Error('Cannot find the definition of component \\"' + componentKey10 + '\\"')} if (!W10) {throw new Error('Cannot find the definition of component \\"' + componentKey10 + '\\"')}
w10 = new W10(parent, props10); w10 = new W10(parent, props10);
parent.__owl__.cmap[k11] = w10.__owl__.id; parent.__owl__.cmap[k11] = w10.__owl__.id;
@@ -145,7 +145,7 @@ exports[`basic widget properties same t-keys in two different places 1`] = `
c2.push(pvnode); c2.push(pvnode);
} else { } else {
let componentKey3 = \`Child\`; let componentKey3 = \`Child\`;
let W3 = scope['Child'] || context.constructor.components[componentKey3] || QWeb.components[componentKey3]; let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['Child'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')} if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3); w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id; parent.__owl__.cmap[k4] = w3.__owl__.id;
@@ -175,7 +175,7 @@ exports[`basic widget properties same t-keys in two different places 1`] = `
c5.push(pvnode); c5.push(pvnode);
} else { } else {
let componentKey6 = \`Child\`; let componentKey6 = \`Child\`;
let W6 = scope['Child'] || context.constructor.components[componentKey6] || QWeb.components[componentKey6]; let W6 = context.constructor.components[componentKey6] || QWeb.components[componentKey6]|| scope['Child'];
if (!W6) {throw new Error('Cannot find the definition of component \\"' + componentKey6 + '\\"')} if (!W6) {throw new Error('Cannot find the definition of component \\"' + componentKey6 + '\\"')}
w6 = new W6(parent, props6); w6 = new W6(parent, props6);
parent.__owl__.cmap[k7] = w6.__owl__.id; parent.__owl__.cmap[k7] = w6.__owl__.id;
@@ -218,7 +218,7 @@ exports[`basic widget properties t-key on a component with t-if, and a sibling c
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Child\`; let componentKey2 = \`Child\`;
let W2 = scope['Child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap[k3] = w2.__owl__.id; parent.__owl__.cmap[k3] = w2.__owl__.id;
@@ -243,7 +243,7 @@ exports[`basic widget properties t-key on a component with t-if, and a sibling c
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey4 = \`Child\`; let componentKey4 = \`Child\`;
let W4 = scope['Child'] || context.constructor.components[componentKey4] || QWeb.components[componentKey4]; let W4 = context.constructor.components[componentKey4] || QWeb.components[componentKey4]|| scope['Child'];
if (!W4) {throw new Error('Cannot find the definition of component \\"' + componentKey4 + '\\"')} if (!W4) {throw new Error('Cannot find the definition of component \\"' + componentKey4 + '\\"')}
w4 = new W4(parent, props4); w4 = new W4(parent, props4);
parent.__owl__.cmap['__5__'] = w4.__owl__.id; parent.__owl__.cmap['__5__'] = w4.__owl__.id;
@@ -301,7 +301,7 @@ exports[`composition sub components with some state rendered in a loop 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey6 = \`ChildWidget\`; let componentKey6 = \`ChildWidget\`;
let W6 = scope['ChildWidget'] || context.constructor.components[componentKey6] || QWeb.components[componentKey6]; let W6 = context.constructor.components[componentKey6] || QWeb.components[componentKey6]|| scope['ChildWidget'];
if (!W6) {throw new Error('Cannot find the definition of component \\"' + componentKey6 + '\\"')} if (!W6) {throw new Error('Cannot find the definition of component \\"' + componentKey6 + '\\"')}
w6 = new W6(parent, props6); w6 = new W6(parent, props6);
parent.__owl__.cmap[k7] = w6.__owl__.id; parent.__owl__.cmap[k7] = w6.__owl__.id;
@@ -342,7 +342,7 @@ exports[`composition t-component with dynamic value 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = (scope['state'].widget); let componentKey2 = (scope['state'].widget);
let W2 = false || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
@@ -380,7 +380,7 @@ exports[`composition t-component with dynamic value 2 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Widget\${scope['state'].widget}\`; let componentKey2 = \`Widget\${scope['state'].widget}\`;
let W2 = false || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
@@ -440,11 +440,11 @@ exports[`composition t-ref on a node, and t-on-click 2`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Child\`; let componentKey2 = \`Child\`;
let W2 = scope['Child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('click', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](e);});}});}); let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('click', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w2.__owl__.pvnode = pvnode; w2.__owl__.pvnode = pvnode;
@@ -478,7 +478,7 @@ exports[`dynamic t-props basic use 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Child\`; let componentKey2 = \`Child\`;
let W2 = scope['Child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
@@ -523,7 +523,7 @@ exports[`other directives with t-component slot setted value (with t-set) not ac
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey3 = \`ChildWidget\`; let componentKey3 = \`ChildWidget\`;
let W3 = scope['ChildWidget'] || context.constructor.components[componentKey3] || QWeb.components[componentKey3]; let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['ChildWidget'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')} if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3); w3 = new W3(parent, props3);
parent.__owl__.cmap['__4__'] = w3.__owl__.id; parent.__owl__.cmap['__4__'] = w3.__owl__.id;
@@ -579,7 +579,7 @@ exports[`other directives with t-component t-on expression captured in t-foreach
c6.push(vn7); c6.push(vn7);
const otherState_8 = scope['otherState']; const otherState_8 = scope['otherState'];
const iter_8 = scope.iter; const iter_8 = scope.iter;
p7.on['click'] = function (e) {if (context.__owl__.status === 5){return}otherState_8.vals.push(iter_8+'_'+iter_8)}; p7.on['click'] = function (e) {if (!context.__owl__.isMounted){return}otherState_8.vals.push(iter_8+'_'+iter_8)};
c7.push({text: \`expr\`}); c7.push({text: \`expr\`});
utils.getScope(scope, 'iter').iter = scope.iter+1; utils.getScope(scope, 'iter').iter = scope.iter+1;
} }
@@ -630,7 +630,7 @@ exports[`other directives with t-component t-on expression in t-foreach 1`] = `
c6.push(vn9); c6.push(vn9);
const otherState_10 = scope['otherState']; const otherState_10 = scope['otherState'];
const val_10 = scope['val']; const val_10 = scope['val'];
p9.on['click'] = function (e) {if (context.__owl__.status === 5){return}otherState_10.vals.push(val_10)}; p9.on['click'] = function (e) {if (!context.__owl__.isMounted){return}otherState_10.vals.push(val_10)};
c9.push({text: \`Expr\`}); c9.push({text: \`Expr\`});
} }
scope = _origScope5; scope = _origScope5;
@@ -684,7 +684,7 @@ exports[`other directives with t-component t-on expression in t-foreach with t-s
const otherState_10 = scope['otherState']; const otherState_10 = scope['otherState'];
const val_10 = scope['val']; const val_10 = scope['val'];
const bossa_10 = scope.bossa; const bossa_10 = scope.bossa;
p9.on['click'] = function (e) {if (context.__owl__.status === 5){return}otherState_10.vals.push(val_10+'_'+bossa_10)}; p9.on['click'] = function (e) {if (!context.__owl__.isMounted){return}otherState_10.vals.push(val_10+'_'+bossa_10)};
c9.push({text: \`Expr\`}); c9.push({text: \`Expr\`});
} }
scope = _origScope5; scope = _origScope5;
@@ -734,7 +734,7 @@ exports[`other directives with t-component t-on method call in t-foreach 1`] = `
let vn9 = h('button', p9, c9); let vn9 = h('button', p9, c9);
c6.push(vn9); c6.push(vn9);
let args10 = [scope['val']]; let args10 = [scope['val']];
p9.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['addVal'](...args10, e);}; p9.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['addVal'](...args10, e);};
c9.push({text: \`meth call\`}); c9.push({text: \`meth call\`});
} }
scope = _origScope5; scope = _origScope5;
@@ -766,11 +766,11 @@ exports[`other directives with t-component t-on with .capture modifier 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Child\`; let componentKey2 = \`Child\`;
let W2 = scope['Child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('click', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['capture'](e);}, true);}});}); let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('click', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['capture'](e);}, true);}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w2.__owl__.pvnode = pvnode; w2.__owl__.pvnode = pvnode;
@@ -808,11 +808,11 @@ exports[`other directives with t-component t-on with getter as handler 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey3 = \`Child\`; let componentKey3 = \`Child\`;
let W3 = scope['Child'] || context.constructor.components[componentKey3] || QWeb.components[componentKey3]; let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['Child'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')} if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3); w3 = new W3(parent, props3);
parent.__owl__.cmap['__4__'] = w3.__owl__.id; parent.__owl__.cmap['__4__'] = w3.__owl__.id;
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['handler'](e);});}});}); let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['handler'](e);});}});});
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
@@ -847,11 +847,11 @@ exports[`other directives with t-component t-on with handler bound to argument 1
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`child\`; let componentKey2 = \`child\`;
let W2 = scope['child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args4, e);});}});}); let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv'](...args4, e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w2.__owl__.pvnode = pvnode; w2.__owl__.pvnode = pvnode;
@@ -886,11 +886,11 @@ exports[`other directives with t-component t-on with handler bound to empty obje
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`child\`; let componentKey2 = \`child\`;
let W2 = scope['child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args4, e);});}});}); let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv'](...args4, e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w2.__owl__.pvnode = pvnode; w2.__owl__.pvnode = pvnode;
@@ -925,11 +925,11 @@ exports[`other directives with t-component t-on with handler bound to empty obje
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`child\`; let componentKey2 = \`child\`;
let W2 = scope['child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args4, e);});}});}); let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv'](...args4, e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w2.__owl__.pvnode = pvnode; w2.__owl__.pvnode = pvnode;
@@ -964,11 +964,11 @@ exports[`other directives with t-component t-on with handler bound to object 1`]
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`child\`; let componentKey2 = \`child\`;
let W2 = scope['child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args4, e);});}});}); let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv'](...args4, e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w2.__owl__.pvnode = pvnode; w2.__owl__.pvnode = pvnode;
@@ -1007,11 +1007,11 @@ exports[`other directives with t-component t-on with inline statement 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey3 = \`Child\`; let componentKey3 = \`Child\`;
let W3 = scope['Child'] || context.constructor.components[componentKey3] || QWeb.components[componentKey3]; let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['Child'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')} if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3); w3 = new W3(parent, props3);
parent.__owl__.cmap['__4__'] = w3.__owl__.id; parent.__owl__.cmap['__4__'] = w3.__owl__.id;
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}state_5.counter++});}});}); let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}state_5.counter++});}});});
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
@@ -1045,11 +1045,11 @@ exports[`other directives with t-component t-on with no handler (only modifiers)
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`ComponentA\`; let componentKey2 = \`ComponentA\`;
let W2 = scope['ComponentA'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['ComponentA'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](e);});}});}); let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv'](e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w2.__owl__.pvnode = pvnode; w2.__owl__.pvnode = pvnode;
@@ -1083,11 +1083,11 @@ exports[`other directives with t-component t-on with prevent and self modifiers
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Child\`; let componentKey2 = \`Child\`;
let W2 = scope['Child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}e.preventDefault();if (e.target !== vn.elm) {return}utils.getComponent(context)['onEv'](e);});}});}); let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();if (e.target !== vn.elm) {return}utils.getComponent(context)['onEv'](e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w2.__owl__.pvnode = pvnode; w2.__owl__.pvnode = pvnode;
@@ -1121,11 +1121,11 @@ exports[`other directives with t-component t-on with self and prevent modifiers
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`child\`; let componentKey2 = \`child\`;
let W2 = scope['child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}if (e.target !== vn.elm) {return}e.preventDefault();utils.getComponent(context)['onEv'](e);});}});}); let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}if (e.target !== vn.elm) {return}e.preventDefault();utils.getComponent(context)['onEv'](e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w2.__owl__.pvnode = pvnode; w2.__owl__.pvnode = pvnode;
@@ -1159,11 +1159,11 @@ exports[`other directives with t-component t-on with self modifier 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`child\`; let componentKey2 = \`child\`;
let W2 = scope['child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv1'](e);});vn.elm.addEventListener('ev-2', function (e) {if (context.__owl__.status === 5){return}if (e.target !== vn.elm) {return}utils.getComponent(context)['onEv2'](e);});}});}); let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv1'](e);});vn.elm.addEventListener('ev-2', function (e) {if (!context.__owl__.isMounted){return}if (e.target !== vn.elm) {return}utils.getComponent(context)['onEv2'](e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w2.__owl__.pvnode = pvnode; w2.__owl__.pvnode = pvnode;
@@ -1197,11 +1197,11 @@ exports[`other directives with t-component t-on with stop and/or prevent modifie
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`child\`; let componentKey2 = \`child\`;
let W2 = scope['child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {if (context.__owl__.status === 5){return}e.stopPropagation();utils.getComponent(context)['onEv1'](e);});vn.elm.addEventListener('ev-2', function (e) {if (context.__owl__.status === 5){return}e.preventDefault();utils.getComponent(context)['onEv2'](e);});vn.elm.addEventListener('ev-3', function (e) {if (context.__owl__.status === 5){return}e.stopPropagation();e.preventDefault();utils.getComponent(context)['onEv3'](e);});}});}); let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {if (!context.__owl__.isMounted){return}e.stopPropagation();utils.getComponent(context)['onEv1'](e);});vn.elm.addEventListener('ev-2', function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();utils.getComponent(context)['onEv2'](e);});vn.elm.addEventListener('ev-3', function (e) {if (!context.__owl__.isMounted){return}e.stopPropagation();e.preventDefault();utils.getComponent(context)['onEv3'](e);});}});});
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w2.__owl__.pvnode = pvnode; w2.__owl__.pvnode = pvnode;
@@ -1337,7 +1337,7 @@ exports[`other directives with t-component t-set not altered by child widget 1`]
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey3 = \`ChildWidget\`; let componentKey3 = \`ChildWidget\`;
let W3 = scope['ChildWidget'] || context.constructor.components[componentKey3] || QWeb.components[componentKey3]; let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['ChildWidget'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')} if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3); w3 = new W3(parent, props3);
parent.__owl__.cmap['__4__'] = w3.__owl__.id; parent.__owl__.cmap['__4__'] = w3.__owl__.id;
@@ -1432,7 +1432,7 @@ exports[`props evaluation t-set with a body expression can be used as textual p
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey3 = \`Child\`; let componentKey3 = \`Child\`;
let W3 = scope['Child'] || context.constructor.components[componentKey3] || QWeb.components[componentKey3]; let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['Child'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')} if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3); w3 = new W3(parent, props3);
parent.__owl__.cmap['__4__'] = w3.__owl__.id; parent.__owl__.cmap['__4__'] = w3.__owl__.id;
@@ -1491,7 +1491,7 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`child\`; let componentKey2 = \`child\`;
let W2 = scope['child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap[k3] = w2.__owl__.id; parent.__owl__.cmap[k3] = w2.__owl__.id;
@@ -1551,11 +1551,11 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey6 = \`Child\`; let componentKey6 = \`Child\`;
let W6 = scope['Child'] || context.constructor.components[componentKey6] || QWeb.components[componentKey6]; let W6 = context.constructor.components[componentKey6] || QWeb.components[componentKey6]|| scope['Child'];
if (!W6) {throw new Error('Cannot find the definition of component \\"' + componentKey6 + '\\"')} if (!W6) {throw new Error('Cannot find the definition of component \\"' + componentKey6 + '\\"')}
w6 = new W6(parent, props6); w6 = new W6(parent, props6);
parent.__owl__.cmap[k7] = w6.__owl__.id; parent.__owl__.cmap[k7] = w6.__owl__.id;
let fiber = w6.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args8, e);});}});}); let fiber = w6.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onEv'](...args8, e);});}});});
let pvnode = h('dummy', {key: k7, hook: {remove() {},destroy(vn) {w6.destroy();}}}); let pvnode = h('dummy', {key: k7, hook: {remove() {},destroy(vn) {w6.destroy();}}});
c1.push(pvnode); c1.push(pvnode);
w6.__owl__.pvnode = pvnode; w6.__owl__.pvnode = pvnode;
@@ -1580,7 +1580,7 @@ exports[`t-call handlers are properly bound through a t-call 1`] = `
let vn3 = h('p', p3, c3); let vn3 = h('p', p3, c3);
c2.push(vn3); c2.push(vn3);
let k4 = \`click__4__\${key0}__\`; let k4 = \`click__4__\${key0}__\`;
extra.handlers[k4] = extra.handlers[k4] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['update'](e);}; extra.handlers[k4] = extra.handlers[k4] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['update'](e);};
p3.on['click'] = extra.handlers[k4]; p3.on['click'] = extra.handlers[k4];
c3.push({text: \`lucas\`}); c3.push({text: \`lucas\`});
}" }"
@@ -1599,7 +1599,7 @@ exports[`t-call handlers with arguments are properly bound through a t-call 1`]
let vn3 = h('p', p3, c3); let vn3 = h('p', p3, c3);
c2.push(vn3); c2.push(vn3);
let args4 = [scope['a']]; let args4 = [scope['a']];
p3.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['update'](...args4, e);}; p3.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['update'](...args4, e);};
c3.push({text: \`lucas\`}); c3.push({text: \`lucas\`});
}" }"
`; `;
@@ -1629,7 +1629,7 @@ exports[`t-call parent is set within t-call 1`] = `
c2.push(pvnode); c2.push(pvnode);
} else { } else {
let componentKey3 = \`Child\`; let componentKey3 = \`Child\`;
let W3 = scope['Child'] || context.constructor.components[componentKey3] || QWeb.components[componentKey3]; let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['Child'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')} if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3); w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id; parent.__owl__.cmap[k4] = w3.__owl__.id;
@@ -1667,7 +1667,7 @@ exports[`t-call parent is set within t-call with no parentNode 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Child\`; let componentKey2 = \`Child\`;
let W2 = scope['Child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap[k3] = w2.__owl__.id; parent.__owl__.cmap[k3] = w2.__owl__.id;
@@ -2062,7 +2062,7 @@ exports[`top level sub widgets basic use 1`] = `
utils.defineProxy(vn3, pvnode); utils.defineProxy(vn3, pvnode);
} else { } else {
let componentKey1 = \`Child\`; let componentKey1 = \`Child\`;
let W1 = scope['Child'] || context.constructor.components[componentKey1] || QWeb.components[componentKey1]; let W1 = context.constructor.components[componentKey1] || QWeb.components[componentKey1]|| scope['Child'];
if (!W1) {throw new Error('Cannot find the definition of component \\"' + componentKey1 + '\\"')} if (!W1) {throw new Error('Cannot find the definition of component \\"' + componentKey1 + '\\"')}
w1 = new W1(parent, props1); w1 = new W1(parent, props1);
parent.__owl__.cmap['__2__'] = w1.__owl__.id; parent.__owl__.cmap['__2__'] = w1.__owl__.id;
@@ -2102,7 +2102,7 @@ exports[`top level sub widgets can select a sub widget 1`] = `
utils.defineProxy(vn3, pvnode); utils.defineProxy(vn3, pvnode);
} else { } else {
let componentKey1 = \`Child\`; let componentKey1 = \`Child\`;
let W1 = scope['Child'] || context.constructor.components[componentKey1] || QWeb.components[componentKey1]; let W1 = context.constructor.components[componentKey1] || QWeb.components[componentKey1]|| scope['Child'];
if (!W1) {throw new Error('Cannot find the definition of component \\"' + componentKey1 + '\\"')} if (!W1) {throw new Error('Cannot find the definition of component \\"' + componentKey1 + '\\"')}
w1 = new W1(parent, props1); w1 = new W1(parent, props1);
parent.__owl__.cmap['__2__'] = w1.__owl__.id; parent.__owl__.cmap['__2__'] = w1.__owl__.id;
@@ -2129,7 +2129,7 @@ exports[`top level sub widgets can select a sub widget 1`] = `
utils.defineProxy(vn6, pvnode); utils.defineProxy(vn6, pvnode);
} else { } else {
let componentKey4 = \`OtherChild\`; let componentKey4 = \`OtherChild\`;
let W4 = scope['OtherChild'] || context.constructor.components[componentKey4] || QWeb.components[componentKey4]; let W4 = context.constructor.components[componentKey4] || QWeb.components[componentKey4]|| scope['OtherChild'];
if (!W4) {throw new Error('Cannot find the definition of component \\"' + componentKey4 + '\\"')} if (!W4) {throw new Error('Cannot find the definition of component \\"' + componentKey4 + '\\"')}
w4 = new W4(parent, props4); w4 = new W4(parent, props4);
parent.__owl__.cmap['__5__'] = w4.__owl__.id; parent.__owl__.cmap['__5__'] = w4.__owl__.id;
@@ -24,7 +24,7 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Child\`; let componentKey2 = \`Child\`;
let W2 = scope['Child'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
@@ -24,7 +24,7 @@ exports[`t-slot directive can define and call slots 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Dialog\`; let componentKey2 = \`Dialog\`;
let W2 = scope['Dialog'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Dialog'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
@@ -114,7 +114,7 @@ exports[`t-slot directive can define and call slots using old t-set keyword 1`]
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Dialog\`; let componentKey2 = \`Dialog\`;
let W2 = scope['Dialog'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Dialog'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
@@ -229,7 +229,7 @@ exports[`t-slot directive dynamic t-slot call 1`] = `
let h = this.h; let h = this.h;
let c10 = [], p10 = {key:10,on:{}}; let c10 = [], p10 = {key:10,on:{}};
let vn10 = h('button', p10, c10); let vn10 = h('button', p10, c10);
extra.handlers['click__11__'] = extra.handlers['click__11__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['toggle'](e);}; extra.handlers['click__11__'] = extra.handlers['click__11__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['toggle'](e);};
p10.on['click'] = extra.handlers['click__11__']; p10.on['click'] = extra.handlers['click__11__'];
const slot12 = this.constructor.slots[context.__owl__.slotId + '_' + (scope['current'].slot)]; const slot12 = this.constructor.slots[context.__owl__.slotId + '_' + (scope['current'].slot)];
if (slot12) { if (slot12) {
@@ -301,7 +301,7 @@ exports[`t-slot directive refs are properly bound in slots 1`] = `
let c9 = [], p9 = {key:9,on:{}}; let c9 = [], p9 = {key:9,on:{}};
let vn9 = h('button', p9, c9); let vn9 = h('button', p9, c9);
c8.push(vn9); c8.push(vn9);
extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](e);}; extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);};
p9.on['click'] = extra.handlers['click__10__']; p9.on['click'] = extra.handlers['click__10__'];
const ref11 = \`myButton\`; const ref11 = \`myButton\`;
p9.hook = { p9.hook = {
@@ -326,7 +326,7 @@ exports[`t-slot directive slots are rendered with proper context 1`] = `
let c9 = [], p9 = {key:9,on:{}}; let c9 = [], p9 = {key:9,on:{}};
let vn9 = h('button', p9, c9); let vn9 = h('button', p9, c9);
c8.push(vn9); c8.push(vn9);
extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](e);}; extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);};
p9.on['click'] = extra.handlers['click__10__']; p9.on['click'] = extra.handlers['click__10__'];
c9.push({text: \`do something\`}); c9.push({text: \`do something\`});
}" }"
@@ -397,7 +397,7 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
c7.push(pvnode); c7.push(pvnode);
} else { } else {
let componentKey8 = \`Link\`; let componentKey8 = \`Link\`;
let W8 = scope['Link'] || context.constructor.components[componentKey8] || QWeb.components[componentKey8]; let W8 = context.constructor.components[componentKey8] || QWeb.components[componentKey8]|| scope['Link'];
if (!W8) {throw new Error('Cannot find the definition of component \\"' + componentKey8 + '\\"')} if (!W8) {throw new Error('Cannot find the definition of component \\"' + componentKey8 + '\\"')}
w8 = new W8(parent, props8); w8 = new W8(parent, props8);
parent.__owl__.cmap[k9] = w8.__owl__.id; parent.__owl__.cmap[k9] = w8.__owl__.id;
@@ -495,7 +495,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
c7.push(pvnode); c7.push(pvnode);
} else { } else {
let componentKey8 = \`Link\`; let componentKey8 = \`Link\`;
let W8 = scope['Link'] || context.constructor.components[componentKey8] || QWeb.components[componentKey8]; let W8 = context.constructor.components[componentKey8] || QWeb.components[componentKey8]|| scope['Link'];
if (!W8) {throw new Error('Cannot find the definition of component \\"' + componentKey8 + '\\"')} if (!W8) {throw new Error('Cannot find the definition of component \\"' + componentKey8 + '\\"')}
w8 = new W8(parent, props8); w8 = new W8(parent, props8);
parent.__owl__.cmap[k9] = w8.__owl__.id; parent.__owl__.cmap[k9] = w8.__owl__.id;
@@ -551,7 +551,7 @@ exports[`t-slot directive slots are rendered with proper context, part 4 1`] = `
c1.push(pvnode); c1.push(pvnode);
} else { } else {
let componentKey2 = \`Link\`; let componentKey2 = \`Link\`;
let W2 = scope['Link'] || context.constructor.components[componentKey2] || QWeb.components[componentKey2]; let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Link'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2); w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id; parent.__owl__.cmap['__3__'] = w2.__owl__.id;
+5 -161
View File
@@ -1,4 +1,4 @@
import { Component, Env, STATUS } from "../../src/component/component"; import { Component, Env } from "../../src/component/component";
import { useState } from "../../src/hooks"; import { useState } from "../../src/hooks";
import { xml } from "../../src/tags"; import { xml } from "../../src/tags";
import { makeDeferred, makeTestEnv, makeTestFixture, nextMicroTick, nextTick } from "../helpers"; import { makeDeferred, makeTestEnv, makeTestFixture, nextMicroTick, nextTick } from "../helpers";
@@ -40,14 +40,14 @@ describe("async rendering", () => {
} }
} }
const w = new W(); const w = new W();
expect(w.__owl__.status).toBe(STATUS.CREATED);
w.mount(fixture); w.mount(fixture);
expect(w.__owl__.status).toBe(STATUS.WILLSTARTED); expect(w.__owl__.isDestroyed).toBe(false);
expect(w.__owl__.isMounted).toBe(false);
w.destroy(); w.destroy();
expect(w.__owl__.status).toBe(STATUS.DESTROYED);
def.resolve(); def.resolve();
await nextTick(); await nextTick();
expect(w.__owl__.status).toBe(STATUS.DESTROYED); expect(w.__owl__.isDestroyed).toBe(true);
expect(w.__owl__.isMounted).toBe(false);
}); });
test("destroying/recreating a subwidget with different props (if start is not over)", async () => { test("destroying/recreating a subwidget with different props (if start is not over)", async () => {
@@ -1405,160 +1405,4 @@ describe("async rendering", () => {
expect(fixture.innerHTML).toBe("<div>2</div>"); expect(fixture.innerHTML).toBe("<div>2</div>");
expect(Widget.prototype.__render).toHaveBeenCalledTimes(2); expect(Widget.prototype.__render).toHaveBeenCalledTimes(2);
}); });
test("components with shouldUpdate=false", async () => {
const state = { p: 1, cc: 10 };
class ChildChild extends Component {
static template = xml`
<div>
child child: <t t-esc="state.cc"/>
</div>`;
state = state;
shouldUpdate() {
return false;
}
}
class Child extends Component {
static components = { ChildChild };
static template = xml`
<div>
child
<ChildChild/>
</div>`;
shouldUpdate() {
return false;
}
}
let parent: any;
class Parent extends Component {
static components = { Child };
static template = xml`
<div>
parent: <t t-esc="state.p"/>
<Child/>
</div>`;
state = state;
constructor(a, b) {
super(a, b);
parent = this;
}
shouldUpdate() {
return false;
}
}
class App extends Component {
static components = { Parent };
static template = xml`
<div>
<Parent/>
</div>`;
}
var div = document.createElement("div");
fixture.appendChild(div);
const app = new App();
await app.mount(fixture);
expect(fixture.innerHTML).toBe(
"<div></div><div><div> parent: 1<div> child <div> child child: 10</div></div></div></div>"
);
app.mount(div);
// wait for rendering from second mount to go through parent
await Promise.resolve();
await Promise.resolve();
state.cc++;
state.p++;
parent.render();
await nextTick();
expect(fixture.innerHTML).toBe(
"<div><div><div> parent: 2<div> child <div> child child: 11</div></div></div></div></div>"
);
});
test("components with shouldUpdate=false, part 2", async () => {
const state = { p: 1, cc: 10 };
let shouldUpdate = true;
class ChildChild extends Component {
static template = xml`
<div>
child child: <t t-esc="state.cc"/>
</div>`;
state = state;
shouldUpdate() {
return shouldUpdate;
}
}
class Child extends Component {
static components = { ChildChild };
static template = xml`
<div>
child
<ChildChild/>
</div>`;
shouldUpdate() {
return shouldUpdate;
}
}
let parent: any;
class Parent extends Component {
static components = { Child };
static template = xml`
<div>
parent: <t t-esc="state.p"/>
<Child/>
</div>`;
state = state;
constructor(a, b) {
super(a, b);
parent = this;
}
shouldUpdate() {
return shouldUpdate;
}
}
class App extends Component {
static components = { Parent };
static template = xml`
<div>
<Parent/>
</div>`;
}
const app = new App();
await app.mount(fixture);
expect(fixture.innerHTML).toBe(
"<div><div> parent: 1<div> child <div> child child: 10</div></div></div></div>"
);
state.cc++;
state.p++;
app.render();
// wait for rendering to go through child
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
shouldUpdate = false;
parent.render();
await nextTick();
expect(fixture.innerHTML).toBe(
"<div><div> parent: 2<div> child <div> child child: 11</div></div></div></div>"
);
});
}); });
+28 -122
View File
@@ -1,4 +1,4 @@
import { Component, Env, mount, STATUS } from "../../src/component/component"; import { Component, Env, mount } from "../../src/component/component";
import { EventBus } from "../../src/core/event_bus"; import { EventBus } from "../../src/core/event_bus";
import { useRef, useState } from "../../src/hooks"; import { useRef, useState } from "../../src/hooks";
import { QWeb } from "../../src/qweb/qweb"; import { QWeb } from "../../src/qweb/qweb";
@@ -163,7 +163,7 @@ describe("basic widget properties", () => {
expect(fixture.innerHTML).toBe("<div><span></span></div>"); expect(fixture.innerHTML).toBe("<div><span></span></div>");
}); });
test("can be clicked on and updated if not in DOM", async () => { test("cannot be clicked on and updated if not in DOM", async () => {
class Counter extends Component { class Counter extends Component {
static template = xml` static template = xml`
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`; <div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
@@ -178,8 +178,8 @@ describe("basic widget properties", () => {
const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0]; const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0];
button.click(); button.click();
await nextTick(); await nextTick();
expect(target.innerHTML).toBe("<div>1<button>Inc</button></div>"); expect(target.innerHTML).toBe("<div>0<button>Inc</button></div>");
expect(counter.state.counter).toBe(1); expect(counter.state.counter).toBe(0);
}); });
test("widget style and classname", async () => { test("widget style and classname", async () => {
@@ -661,8 +661,9 @@ describe("lifecycle hooks", () => {
class ChildWidget extends Component { class ChildWidget extends Component {
static template = xml`<div/>`; static template = xml`<div/>`;
setup() { constructor(parent) {
steps.push("setup"); super(parent);
steps.push("init");
} }
async willStart() { async willStart() {
steps.push("willstart"); steps.push("willstart");
@@ -686,10 +687,10 @@ describe("lifecycle hooks", () => {
const widget = new ParentWidget(); const widget = new ParentWidget();
await widget.mount(fixture); await widget.mount(fixture);
expect(steps).toEqual(["setup", "willstart", "mounted"]); expect(steps).toEqual(["init", "willstart", "mounted"]);
widget.state.ok = false; widget.state.ok = false;
await nextTick(); await nextTick();
expect(steps).toEqual(["setup", "willstart", "mounted", "willunmount"]); expect(steps).toEqual(["init", "willstart", "mounted", "willunmount"]);
}); });
test("components are unmounted and destroyed if no longer in DOM, even after updateprops", async () => { test("components are unmounted and destroyed if no longer in DOM, even after updateprops", async () => {
@@ -737,7 +738,8 @@ describe("lifecycle hooks", () => {
class ChildWidget extends Component { class ChildWidget extends Component {
static template = xml`<div/>`; static template = xml`<div/>`;
setup() { constructor(parent) {
super(parent);
steps.push("c init"); steps.push("c init");
} }
async willStart() { async willStart() {
@@ -753,7 +755,8 @@ describe("lifecycle hooks", () => {
class ParentWidget extends Component { class ParentWidget extends Component {
static template = xml`<div><t t-component="child"/></div>`; static template = xml`<div><t t-component="child"/></div>`;
static components = { child: ChildWidget }; static components = { child: ChildWidget };
setup() { constructor(parent?) {
super(parent);
steps.push("p init"); steps.push("p init");
} }
async willStart() { async willStart() {
@@ -959,69 +962,6 @@ describe("lifecycle hooks", () => {
"parent:patched", "parent:patched",
]); ]);
}); });
test("willPatch/patched hook is not called if not mounted in DOM", async () => {
const steps: string[] = [];
class ChildWidget extends Component {
static template = xml`<div/>`;
constructor(parent, props) {
super(parent, props);
steps.push("child:constructor");
}
mounted() {
steps.push("child:mounted");
}
willPatch() {
steps.push("child:willPatch");
}
patched() {
steps.push("child:patched");
}
}
class ParentWidget extends Component {
static template = xml`
<div>
<t t-component="child" v="state.n"/>
</div>
`;
static components = { child: ChildWidget };
state = useState({ n: 1 });
constructor() {
super();
steps.push("parent:constructor");
}
mounted() {
steps.push("parent:mounted");
}
willPatch() {
steps.push("parent:willPatch");
}
patched() {
steps.push("parent:patched");
}
}
const div = document.createElement("div");
const widget = new ParentWidget();
await widget.mount(div);
expect(steps).toEqual(["parent:constructor", "child:constructor"]);
widget.state.n = 2;
await nextTick();
expect(steps).toEqual(["parent:constructor", "child:constructor"]);
// then we remount the component in the dom
await widget.mount(fixture);
expect(steps).toEqual([
"parent:constructor",
"child:constructor",
"child:mounted",
"parent:mounted",
]);
});
}); });
describe("destroy method", () => { describe("destroy method", () => {
@@ -1035,7 +975,8 @@ describe("destroy method", () => {
expect(document.contains(widget.el)).toBe(true); expect(document.contains(widget.el)).toBe(true);
widget.destroy(); widget.destroy();
expect(document.contains(widget.el)).toBe(false); expect(document.contains(widget.el)).toBe(false);
expect(widget.__owl__.status).toBe(STATUS.DESTROYED); expect(widget.__owl__.isMounted).toBe(false);
expect(widget.__owl__.isDestroyed).toBe(true);
}); });
test("destroying a parent also destroys its children", async () => { test("destroying a parent also destroys its children", async () => {
@@ -1044,9 +985,9 @@ describe("destroy method", () => {
const child = children(parent)[0]; const child = children(parent)[0];
expect(child.__owl__.status).toBe(STATUS.MOUNTED); expect(child.__owl__.isDestroyed).toBe(false);
parent.destroy(); parent.destroy();
expect(child.__owl__.status).toBe(STATUS.DESTROYED); expect(child.__owl__.isDestroyed).toBe(true);
}); });
test("destroy remove the parent/children link", async () => { test("destroy remove the parent/children link", async () => {
@@ -1072,15 +1013,17 @@ describe("destroy method", () => {
} }
expect(fixture.innerHTML).toBe(""); expect(fixture.innerHTML).toBe("");
const widget = new DelayedWidget(); const widget = new DelayedWidget();
expect(widget.__owl__.status).toBe(STATUS.CREATED);
widget.mount(fixture); widget.mount(fixture);
expect(widget.__owl__.status).toBe(STATUS.WILLSTARTED); expect(widget.__owl__.isMounted).toBe(false);
expect(widget.__owl__.isDestroyed).toBe(false);
widget.destroy(); widget.destroy();
expect(widget.__owl__.status).toBe(STATUS.DESTROYED); expect(widget.__owl__.isMounted).toBe(false);
expect(widget.__owl__.isDestroyed).toBe(true);
def.resolve(); def.resolve();
await nextTick(); await nextTick();
expect(widget.__owl__.status).toBe(STATUS.DESTROYED); expect(widget.__owl__.isMounted).toBe(false);
expect(widget.__owl__.isDestroyed).toBe(true);
expect(widget.__owl__.vnode).toBe(undefined); expect(widget.__owl__.vnode).toBe(undefined);
expect(fixture.innerHTML).toBe(""); expect(fixture.innerHTML).toBe("");
expect(isRendered).toBe(false); expect(isRendered).toBe(false);
@@ -1242,23 +1185,6 @@ describe("composition", () => {
delete QWeb.components["WidgetB"]; delete QWeb.components["WidgetB"];
}); });
test("don't fallback to global/component's registry if widget defined in the instance's context", async () => {
QWeb.registerComponent("WidgetB", WidgetB); // should not use this widget
env.qweb.addTemplate("ParentWidget", `<div><t t-component="WidgetB"/></div>`);
env.qweb.addTemplate("ComponentWidgetB", `<span>Belgium</span>`); // should not use this widget either
env.qweb.addTemplate("InstanceWidgetB", `<span>Chocolate</span>`); // should use this
class ComponentWidgetB extends Component {}
class InstanceWidgetB extends Component {}
class ParentWidget extends Component {
static components = { WidgetB: ComponentWidgetB };
WidgetB = InstanceWidgetB;
}
const widget = new ParentWidget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>Chocolate</span></div>");
delete QWeb.components["WidgetB"];
});
test("can define components in template without t-component", async () => { test("can define components in template without t-component", async () => {
env.qweb.addTemplates(` env.qweb.addTemplates(`
<templates> <templates>
@@ -1553,7 +1479,7 @@ describe("composition", () => {
parent.state.flag = true; parent.state.flag = true;
await nextTick(); await nextTick();
expect(children(parent)[0]).toBe(child); expect(children(parent)[0]).toBe(child);
expect(child.__owl__.status).toBe(STATUS.MOUNTED); expect(child.__owl__.isDestroyed).toBe(false);
expect(normalize(fixture.innerHTML)).toBe( expect(normalize(fixture.innerHTML)).toBe(
normalize(` normalize(`
<div> <div>
@@ -2301,29 +2227,9 @@ describe("other directives with t-component", () => {
el.click(); el.click();
expect(steps).toEqual(["click"]); expect(steps).toEqual(["click"]);
parent.unmount(); parent.unmount();
expect(child.__owl__.status).toBe(STATUS.UNMOUNTED); expect(child.__owl__.isMounted).toBe(false);
el.click(); el.click();
expect(steps).toEqual(["click", "click"]); expect(steps).toEqual(["click"]);
});
test("triggering custom event on mounted components", async () => {
let value = false;
class Child extends Component {
static template = xml`<div/>`;
mounted() {
this.trigger("coucou");
}
}
class Parent extends Component {
static template = xml`<Child t-on-coucou="doSomething"/>`;
static components = { Child };
doSomething() {
value = true;
}
}
const parent = new Parent();
await parent.mount(fixture);
expect(value).toBe(true);
}); });
test("t-on with .capture modifier", async () => { test("t-on with .capture modifier", async () => {
@@ -2375,7 +2281,7 @@ describe("other directives with t-component", () => {
expect(steps).toEqual(["click"]); expect(steps).toEqual(["click"]);
parent.state.flag = false; parent.state.flag = false;
await nextTick(); await nextTick();
expect(child.__owl__.status).toBe(STATUS.DESTROYED); expect(child.__owl__.isDestroyed).toBe(true);
el.click(); el.click();
expect(steps).toEqual(["click"]); expect(steps).toEqual(["click"]);
}); });
@@ -2410,7 +2316,7 @@ describe("other directives with t-component", () => {
expect(steps).toEqual(["click"]); expect(steps).toEqual(["click"]);
parent.state.flag = false; parent.state.flag = false;
await nextTick(); await nextTick();
expect(child.__owl__.status).toBe(STATUS.DESTROYED); expect(child.__owl__.isDestroyed).toBe(true);
el.click(); el.click();
expect(steps).toEqual(["click"]); expect(steps).toEqual(["click"]);
}); });
+2 -2
View File
@@ -1,4 +1,4 @@
import { Component, Env, STATUS } from "../../src/component/component"; import { Component, Env } from "../../src/component/component";
import { useState } from "../../src/hooks"; import { useState } from "../../src/hooks";
import { xml } from "../../src/tags"; import { xml } from "../../src/tags";
import { makeTestEnv, makeTestFixture, nextTick } from "../helpers"; import { makeTestEnv, makeTestFixture, nextTick } from "../helpers";
@@ -108,7 +108,7 @@ describe("component error handling (catchError)", () => {
expect(console.error).toBeCalledTimes(0); expect(console.error).toBeCalledTimes(0);
console.error = consoleError; console.error = consoleError;
expect(app.__owl__.status).toBe(STATUS.DESTROYED); expect(app.__owl__.isDestroyed).toBe(true);
expect(handler).toBeCalledTimes(1); expect(handler).toBeCalledTimes(1);
}); });
+4 -133
View File
@@ -323,54 +323,6 @@ describe("unmounting and remounting", () => {
expect(steps).toEqual([2, 2, 3]); expect(steps).toEqual([2, 2, 3]);
}); });
test("change state and render while mounted in detached dom", async () => {
class App extends Component {
static template = xml`<div><t t-esc="state.val"/></div>`;
state = useState({ val: 1 });
}
const detachedDiv = document.createElement("div");
const app = await mount(App, { target: detachedDiv });
expect(detachedDiv.innerHTML).toBe("<div>1</div>");
app.state.val = 2;
await nextTick();
expect(detachedDiv.innerHTML).toBe("<div>2</div>");
});
test("change state and render while not mounted ", async () => {
class App extends Component {
static template = xml`<div><t t-esc="state.val"/></div>`;
state = useState({ val: 1 });
}
const app = new App(null);
app.state.val = 2; // will call the render method (before being mounted)
await nextTick();
await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div>2</div>");
});
test("destroy and change state after mounted in detached dom", async () => {
class App extends Component {
static template = xml`<div><t t-esc="state.val"/></div>`;
state = useState({ val: 1 });
}
const detachedDiv = document.createElement("div");
const app = await mount(App, { target: detachedDiv });
expect(detachedDiv.innerHTML).toBe("<div>1</div>");
app.destroy();
app.state.val = 2;
await nextTick();
expect(detachedDiv.innerHTML).toBe("");
});
test("change state while component is unmounted", async () => { test("change state while component is unmounted", async () => {
let child; let child;
class Child extends Component { class Child extends Component {
@@ -404,40 +356,6 @@ describe("unmounting and remounting", () => {
expect(fixture.innerHTML).toBe("<div>P2<span>C2</span></div>"); expect(fixture.innerHTML).toBe("<div>P2<span>C2</span></div>");
}); });
test("change state while component is mounted in a fragment", async () => {
class Child1 extends Component {
static template = xml`<span>C1</span>`;
}
class Child2 extends Component {
static template = xml`<span>C2</span>`;
}
class Parent extends Component {
static components = { Child1, Child2 };
static template = xml`
<div>
<Child1 t-if="child == 'c1'"/>
<Child2 t-if="child == 'c2'"/>
</div>`;
child: string | false = false;
}
const fragment = document.createDocumentFragment();
const parent = new Parent();
await parent.mount(fragment);
expect(parent.el.outerHTML).toBe("<div></div>");
parent.child = "c1";
parent.render();
await Promise.resolve();
parent.child = "c2";
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>C2</span></div>");
});
test("unmount component during a re-rendering", async () => { test("unmount component during a re-rendering", async () => {
const def = makeDeferred(); const def = makeDeferred();
class Child extends Component { class Child extends Component {
@@ -524,17 +442,17 @@ describe("unmounting and remounting", () => {
// one full tick. // one full tick.
await nextMicroTick(); await nextMicroTick();
await nextMicroTick(); await nextMicroTick();
expect(steps).toEqual([]); expect(steps).toEqual(["1 catch"]);
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div></div><span></span>"); expect(fixture.innerHTML).toBe("<div></div><span></span>");
def.resolve(); def.resolve();
await nextTick(); await nextTick();
expect(steps).toEqual(["2 resolved"]); expect(steps).toEqual(["1 catch", "2 resolved"]);
expect(fixture.innerHTML).toBe("<div></div><span><div>Hey</div></span>"); expect(fixture.innerHTML).toBe("<div></div><span><div>Hey</div></span>");
}); });
test("component can be mounted on same target, another situation", async () => { test("widget can be mounted on same target, another situation", async () => {
const def = makeDeferred(); const def = makeDeferred();
const steps: string[] = []; const steps: string[] = [];
@@ -562,8 +480,8 @@ describe("unmounting and remounting", () => {
def.resolve(); def.resolve();
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div>Hey</div>");
expect(steps).toEqual(["1 resolved", "2 resolved"]); expect(steps).toEqual(["1 resolved", "2 resolved"]);
expect(fixture.innerHTML).toBe("<div>Hey</div>");
}); });
test("mounting a destroyed widget", async () => { test("mounting a destroyed widget", async () => {
@@ -682,51 +600,4 @@ describe("unmounting and remounting", () => {
await parent.render(); await parent.render();
expect(fixture.textContent).toBe("fixedsome text"); expect(fixture.textContent).toBe("fixedsome text");
}); });
test("remounting component tree where a component implement shouldupdate", async () => {
let state: any;
const steps = [];
class Child extends Component {
static template = xml`<div><t t-esc="state.word"/><t t-esc="props.name"/></div>`;
state = useState({ word: "hello" });
constructor(parent, props) {
super(parent, props);
state = this.state;
}
patched() {
steps.push("patched");
}
mounted() {
steps.push("mounted");
}
willUnmount() {
steps.push("willUnmount");
}
shouldUpdate() {
return false;
}
}
class Parent extends Component {
static template = xml`<div><Child name="state.name"/></div>`;
static components = { Child };
state = useState({ name: "World" });
}
const parent = await mount(Parent, { target: fixture });
expect(fixture.innerHTML).toBe("<div><div>helloWorld</div></div>");
parent.unmount();
expect(fixture.innerHTML).toBe("");
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div>helloWorld</div></div>");
state.word = "test";
await nextTick();
expect(fixture.innerHTML).toBe("<div><div>testWorld</div></div>");
expect(steps).toEqual(["mounted", "willUnmount", "mounted", "patched"]);
});
}); });
+1 -20
View File
@@ -289,26 +289,7 @@ describe("Context", () => {
expect(testContext.subscriptions.update.length).toBe(0); expect(testContext.subscriptions.update.length).toBe(0);
}); });
test.skip("concurrent renderings", async () => { test("concurrent renderings", async () => {
/**
* Note: this test is interesting, but sadly just an incomplete attempt at
* protecting users against themselves. With the context API, it is not
* possible for the framework to protect completely against crashes. Maybe
* like in this case, when a component is in a simple hierarchy where all
* renderings come from the context changes, but in a real case, where some
* code can trigger a rendering independently, it is insufficient.
*
* The main problem is that the sub component depends on some external state,
* which may be modified, and then incompatible with the component actual
* state (for example, if the sub component has an id key related to some
* object that has been removed from the context).
*
* For now, sadly, the only solution is that components that depends on external
* state should guarantee their own integrity themselves. Then maybe this
* could be solved at the level of a state management solution that has a
* more advanced API, to let components determine if they should be updated
* or not (so, something slightly more advanced that the useStore hook).
*/
const testContext = new Context({ x: { n: 1 }, key: "x" }); const testContext = new Context({ x: { n: 1 }, key: "x" });
const def = makeDeferred(); const def = makeDeferred();
let stateC; let stateC;
+2 -2
View File
@@ -1,4 +1,4 @@
import { Env, Component, STATUS } from "../src/component/component"; import { Env, Component } from "../src/component/component";
import { scheduler } from "../src/component/scheduler"; import { scheduler } from "../src/component/scheduler";
import { EvalContext, QWeb } from "../src/qweb/qweb"; import { EvalContext, QWeb } from "../src/qweb/qweb";
import { CompilationContext } from "../src/qweb/compilation_context"; import { CompilationContext } from "../src/qweb/compilation_context";
@@ -92,7 +92,7 @@ export function renderToDOM(
if (!context.__owl__) { if (!context.__owl__) {
// we add `__owl__` to better simulate a component as context. This is // we add `__owl__` to better simulate a component as context. This is
// particularly important for event handlers added with the `t-on` directive. // particularly important for event handlers added with the `t-on` directive.
context.__owl__ = { status: STATUS.MOUNTED }; context.__owl__ = { isMounted: true };
} }
const vnode = qweb.render(template, context, extra); const vnode = qweb.render(template, context, extra);
-28
View File
@@ -9,10 +9,8 @@ import {
onWillPatch, onWillPatch,
onWillStart, onWillStart,
onWillUpdateProps, onWillUpdateProps,
useEnv,
useSubEnv, useSubEnv,
useExternalListener, useExternalListener,
useComponent,
} from "../src/hooks"; } from "../src/hooks";
import { xml } from "../src/tags"; import { xml } from "../src/tags";
@@ -522,19 +520,6 @@ describe("hooks", () => {
}); });
}); });
test("can use useEnv", async () => {
expect.assertions(1);
class TestComponent extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`;
constructor() {
super();
expect(useEnv()).toBe(env);
}
}
const component = new TestComponent();
await component.mount(fixture);
});
test("can use sub env", async () => { test("can use sub env", async () => {
class TestComponent extends Component { class TestComponent extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`; static template = xml`<div><t t-esc="env.val"/></div>`;
@@ -550,19 +535,6 @@ describe("hooks", () => {
expect(component.env).toHaveProperty("val"); expect(component.env).toHaveProperty("val");
}); });
test("can use useComponent", async () => {
expect.assertions(1);
class TestComponent extends Component {
static template = xml`<div></div>`;
constructor() {
super();
expect(useComponent()).toBe(this);
}
}
const component = new TestComponent();
await component.mount(fixture);
});
test("parent and child env", async () => { test("parent and child env", async () => {
class Child extends Component { class Child extends Component {
static template = xml`<div><t t-esc="env.val"/></div>`; static template = xml`<div><t t-esc="env.val"/></div>`;
+26 -38
View File
@@ -2727,7 +2727,7 @@ exports[`t-on can bind event handler 1`] = `
let h = this.h; let h = this.h;
let c1 = [], p1 = {key:1,on:{}}; let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1); let vn1 = h('button', p1, c1);
extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](e);}; extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['add'](e);};
p1.on['click'] = extra.handlers['click__2__']; p1.on['click'] = extra.handlers['click__2__'];
c1.push({text: \`Click\`}); c1.push({text: \`Click\`});
return vn1; return vn1;
@@ -2744,7 +2744,7 @@ exports[`t-on can bind handlers with arguments 1`] = `
let c1 = [], p1 = {key:1,on:{}}; let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1); let vn1 = h('button', p1, c1);
let args2 = [5]; let args2 = [5];
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](...args2, e);}; p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['add'](...args2, e);};
c1.push({text: \`Click\`}); c1.push({text: \`Click\`});
return vn1; return vn1;
}" }"
@@ -2760,7 +2760,7 @@ exports[`t-on can bind handlers with empty object (with non empty inner string)
let c1 = [], p1 = {key:1,on:{}}; let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1); let vn1 = h('button', p1, c1);
let args2 = [{}]; let args2 = [{}];
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](...args2, e);}; p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](...args2, e);};
c1.push({text: \`Click\`}); c1.push({text: \`Click\`});
return vn1; return vn1;
}" }"
@@ -2776,7 +2776,7 @@ exports[`t-on can bind handlers with empty object 1`] = `
let c1 = [], p1 = {key:1,on:{}}; let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1); let vn1 = h('button', p1, c1);
let args2 = [{}]; let args2 = [{}];
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](...args2, e);}; p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](...args2, e);};
c1.push({text: \`Click\`}); c1.push({text: \`Click\`});
return vn1; return vn1;
}" }"
@@ -2815,7 +2815,7 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = `
let vn7 = h('a', p7, c7); let vn7 = h('a', p7, c7);
c6.push(vn7); c6.push(vn7);
let args8 = [scope['action']]; let args8 = [scope['action']];
p7.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['activate'](...args8, e);}; p7.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['activate'](...args8, e);};
c7.push({text: \`link\`}); c7.push({text: \`link\`});
} }
scope = _origScope5; scope = _origScope5;
@@ -2833,7 +2833,7 @@ exports[`t-on can bind handlers with object arguments 1`] = `
let c1 = [], p1 = {key:1,on:{}}; let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1); let vn1 = h('button', p1, c1);
let args2 = [{val:5}]; let args2 = [{val:5}];
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](...args2, e);}; p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['add'](...args2, e);};
c1.push({text: \`Click\`}); c1.push({text: \`Click\`});
return vn1; return vn1;
}" }"
@@ -2847,9 +2847,9 @@ exports[`t-on can bind two event handlers 1`] = `
let h = this.h; let h = this.h;
let c1 = [], p1 = {key:1,on:{}}; let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1); let vn1 = h('button', p1, c1);
extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['handleClick'](e);}; extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['handleClick'](e);};
p1.on['click'] = extra.handlers['click__2__']; p1.on['click'] = extra.handlers['click__2__'];
extra.handlers['dblclick__3__'] = extra.handlers['dblclick__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['handleDblClick'](e);}; extra.handlers['dblclick__3__'] = extra.handlers['dblclick__3__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['handleDblClick'](e);};
p1.on['dblclick'] = extra.handlers['dblclick__3__']; p1.on['dblclick'] = extra.handlers['dblclick__3__'];
c1.push({text: \`Click\`}); c1.push({text: \`Click\`});
return vn1; return vn1;
@@ -2864,7 +2864,7 @@ exports[`t-on handler is bound to proper owner 1`] = `
let h = this.h; let h = this.h;
let c1 = [], p1 = {key:1,on:{}}; let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1); let vn1 = h('button', p1, c1);
extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](e);}; extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['add'](e);};
p1.on['click'] = extra.handlers['click__2__']; p1.on['click'] = extra.handlers['click__2__'];
c1.push({text: \`Click\`}); c1.push({text: \`Click\`});
return vn1; return vn1;
@@ -2883,7 +2883,7 @@ exports[`t-on t-on combined with t-esc 1`] = `
let c2 = [], p2 = {key:2,on:{}}; let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2); let vn2 = h('button', p2, c2);
c1.push(vn2); c1.push(vn2);
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onClick'](e);}; extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__']; p2.on['click'] = extra.handlers['click__3__'];
let _4 = scope['text']; let _4 = scope['text'];
if (_4 != null) { if (_4 != null) {
@@ -2905,7 +2905,7 @@ exports[`t-on t-on combined with t-raw 1`] = `
let c2 = [], p2 = {key:2,on:{}}; let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2); let vn2 = h('button', p2, c2);
c1.push(vn2); c1.push(vn2);
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onClick'](e);}; extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__']; p2.on['click'] = extra.handlers['click__3__'];
let _4 = scope['html']; let _4 = scope['html'];
if (_4 != null) { if (_4 != null) {
@@ -2923,12 +2923,12 @@ exports[`t-on t-on with .capture modifier 1`] = `
let h = this.h; let h = this.h;
let c1 = [], p1 = {key:1,on:{}}; let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('div', p1, c1); let vn1 = h('div', p1, c1);
extra.handlers['!click__2__'] = extra.handlers['!click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onCapture'](e);}; extra.handlers['!click__2__'] = extra.handlers['!click__2__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onCapture'](e);};
p1.on['!click'] = extra.handlers['!click__2__']; p1.on['!click'] = extra.handlers['!click__2__'];
let c3 = [], p3 = {key:3,on:{}}; let c3 = [], p3 = {key:3,on:{}};
let vn3 = h('button', p3, c3); let vn3 = h('button', p3, c3);
c1.push(vn3); c1.push(vn3);
extra.handlers['click__4__'] = extra.handlers['click__4__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](e);}; extra.handlers['click__4__'] = extra.handlers['click__4__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);};
p3.on['click'] = extra.handlers['click__4__']; p3.on['click'] = extra.handlers['click__4__'];
c3.push({text: \`Button\`}); c3.push({text: \`Button\`});
return vn1; return vn1;
@@ -2946,7 +2946,7 @@ exports[`t-on t-on with empty handler (only modifiers) 1`] = `
let c2 = [], p2 = {key:2,on:{}}; let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2); let vn2 = h('button', p2, c2);
c1.push(vn2); c1.push(vn2);
p2.on['click'] = function (e) {if (context.__owl__.status === 5){return}e.preventDefault();}; p2.on['click'] = function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();};
c2.push({text: \`Button\`}); c2.push({text: \`Button\`});
return vn1; return vn1;
}" }"
@@ -2961,7 +2961,7 @@ exports[`t-on t-on with inline statement (function call) 1`] = `
let c1 = [], p1 = {key:1,on:{}}; let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1); let vn1 = h('button', p1, c1);
const state_2 = scope['state']; const state_2 = scope['state'];
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.incrementCounter(2)}; p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}state_2.incrementCounter(2)};
c1.push({text: \`Click\`}); c1.push({text: \`Click\`});
return vn1; return vn1;
}" }"
@@ -2976,7 +2976,7 @@ exports[`t-on t-on with inline statement 1`] = `
let c1 = [], p1 = {key:1,on:{}}; let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1); let vn1 = h('button', p1, c1);
const state_2 = scope['state']; const state_2 = scope['state'];
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.counter++}; p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}state_2.counter++};
c1.push({text: \`Click\`}); c1.push({text: \`Click\`});
return vn1; return vn1;
}" }"
@@ -2991,7 +2991,7 @@ exports[`t-on t-on with inline statement, part 2 1`] = `
let c1 = [], p1 = {key:1,on:{}}; let c1 = [], p1 = {key:1,on:{}};
let vn1 = h('button', p1, c1); let vn1 = h('button', p1, c1);
const state_2 = scope['state']; const state_2 = scope['state'];
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.flag=!state_2.flag}; p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}state_2.flag=!state_2.flag};
c1.push({text: \`Toggle\`}); c1.push({text: \`Toggle\`});
return vn1; return vn1;
}" }"
@@ -3007,7 +3007,7 @@ exports[`t-on t-on with inline statement, part 3 1`] = `
let vn1 = h('button', p1, c1); let vn1 = h('button', p1, c1);
const state_2 = scope['state']; const state_2 = scope['state'];
const someFunction_2 = scope['someFunction']; const someFunction_2 = scope['someFunction'];
p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.n=someFunction_2(3)}; p1.on['click'] = function (e) {if (!context.__owl__.isMounted){return}state_2.n=someFunction_2(3)};
c1.push({text: \`Toggle\`}); c1.push({text: \`Toggle\`});
return vn1; return vn1;
}" }"
@@ -3024,7 +3024,7 @@ exports[`t-on t-on with prevent and self modifiers (order matters) 1`] = `
let c2 = [], p2 = {key:2,on:{}}; let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2); let vn2 = h('button', p2, c2);
c1.push(vn2); c1.push(vn2);
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}e.preventDefault();if (e.target !== this.elm) {return}utils.getComponent(context)['onClick'](e);}; extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();if (e.target !== this.elm) {return}utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__']; p2.on['click'] = extra.handlers['click__3__'];
let c4 = [], p4 = {key:4}; let c4 = [], p4 = {key:4};
let vn4 = h('span', p4, c4); let vn4 = h('span', p4, c4);
@@ -3045,19 +3045,19 @@ exports[`t-on t-on with prevent and/or stop modifiers 1`] = `
let c2 = [], p2 = {key:2,on:{}}; let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2); let vn2 = h('button', p2, c2);
c1.push(vn2); c1.push(vn2);
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}e.preventDefault();utils.getComponent(context)['onClickPrevented'](e);}; extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();utils.getComponent(context)['onClickPrevented'](e);};
p2.on['click'] = extra.handlers['click__3__']; p2.on['click'] = extra.handlers['click__3__'];
c2.push({text: \`Button 1\`}); c2.push({text: \`Button 1\`});
let c4 = [], p4 = {key:4,on:{}}; let c4 = [], p4 = {key:4,on:{}};
let vn4 = h('button', p4, c4); let vn4 = h('button', p4, c4);
c1.push(vn4); c1.push(vn4);
extra.handlers['click__5__'] = extra.handlers['click__5__'] || function (e) {if (context.__owl__.status === 5){return}e.stopPropagation();utils.getComponent(context)['onClickStopped'](e);}; extra.handlers['click__5__'] = extra.handlers['click__5__'] || function (e) {if (!context.__owl__.isMounted){return}e.stopPropagation();utils.getComponent(context)['onClickStopped'](e);};
p4.on['click'] = extra.handlers['click__5__']; p4.on['click'] = extra.handlers['click__5__'];
c4.push({text: \`Button 2\`}); c4.push({text: \`Button 2\`});
let c6 = [], p6 = {key:6,on:{}}; let c6 = [], p6 = {key:6,on:{}};
let vn6 = h('button', p6, c6); let vn6 = h('button', p6, c6);
c1.push(vn6); c1.push(vn6);
extra.handlers['click__7__'] = extra.handlers['click__7__'] || function (e) {if (context.__owl__.status === 5){return}e.preventDefault();e.stopPropagation();utils.getComponent(context)['onClickPreventedAndStopped'](e);}; extra.handlers['click__7__'] = extra.handlers['click__7__'] || function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();e.stopPropagation();utils.getComponent(context)['onClickPreventedAndStopped'](e);};
p6.on['click'] = extra.handlers['click__7__']; p6.on['click'] = extra.handlers['click__7__'];
c6.push({text: \`Button 3\`}); c6.push({text: \`Button 3\`});
return vn1; return vn1;
@@ -3097,7 +3097,7 @@ exports[`t-on t-on with prevent modifier in t-foreach 1`] = `
let vn7 = h('a', p7, c7); let vn7 = h('a', p7, c7);
c1.push(vn7); c1.push(vn7);
let args8 = [scope['project'].id]; let args8 = [scope['project'].id];
p7.on['click'] = function (e) {if (context.__owl__.status === 5){return}e.preventDefault();utils.getComponent(context)['onEdit'](...args8, e);}; p7.on['click'] = function (e) {if (!context.__owl__.isMounted){return}e.preventDefault();utils.getComponent(context)['onEdit'](...args8, e);};
c7.push({text: \` Edit \`}); c7.push({text: \` Edit \`});
let _9 = scope['project'].name; let _9 = scope['project'].name;
if (_9 != null) { if (_9 != null) {
@@ -3121,7 +3121,7 @@ exports[`t-on t-on with self and prevent modifiers (order matters) 1`] = `
let c2 = [], p2 = {key:2,on:{}}; let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2); let vn2 = h('button', p2, c2);
c1.push(vn2); c1.push(vn2);
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}if (e.target !== this.elm) {return}e.preventDefault();utils.getComponent(context)['onClick'](e);}; extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}if (e.target !== this.elm) {return}e.preventDefault();utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__']; p2.on['click'] = extra.handlers['click__3__'];
let c4 = [], p4 = {key:4}; let c4 = [], p4 = {key:4};
let vn4 = h('span', p4, c4); let vn4 = h('span', p4, c4);
@@ -3142,7 +3142,7 @@ exports[`t-on t-on with self modifier 1`] = `
let c2 = [], p2 = {key:2,on:{}}; let c2 = [], p2 = {key:2,on:{}};
let vn2 = h('button', p2, c2); let vn2 = h('button', p2, c2);
c1.push(vn2); c1.push(vn2);
extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onClick'](e);}; extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['onClick'](e);};
p2.on['click'] = extra.handlers['click__3__']; p2.on['click'] = extra.handlers['click__3__'];
let c4 = [], p4 = {key:4}; let c4 = [], p4 = {key:4};
let vn4 = h('span', p4, c4); let vn4 = h('span', p4, c4);
@@ -3151,7 +3151,7 @@ exports[`t-on t-on with self modifier 1`] = `
let c5 = [], p5 = {key:5,on:{}}; let c5 = [], p5 = {key:5,on:{}};
let vn5 = h('button', p5, c5); let vn5 = h('button', p5, c5);
c1.push(vn5); c1.push(vn5);
extra.handlers['click__6__'] = extra.handlers['click__6__'] || function (e) {if (context.__owl__.status === 5){return}if (e.target !== this.elm) {return}utils.getComponent(context)['onClickSelf'](e);}; extra.handlers['click__6__'] = extra.handlers['click__6__'] || function (e) {if (!context.__owl__.isMounted){return}if (e.target !== this.elm) {return}utils.getComponent(context)['onClickSelf'](e);};
p5.on['click'] = extra.handlers['click__6__']; p5.on['click'] = extra.handlers['click__6__'];
let c7 = [], p7 = {key:7}; let c7 = [], p7 = {key:7};
let vn7 = h('span', p7, c7); let vn7 = h('span', p7, c7);
@@ -3924,18 +3924,6 @@ exports[`translation support some attributes are translated 1`] = `
}" }"
`; `;
exports[`translation support translation is done on the trimmed text, with extra spaces readded after 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
c1.push({text: \` mot \`});
return vn1;
}"
`;
exports[`whitespace handling consecutives whitespaces are condensed into a single space 1`] = ` exports[`whitespace handling consecutives whitespaces are condensed into a single space 1`] = `
"function anonymous(context, extra "function anonymous(context, extra
) { ) {
@@ -1,71 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`qweb t-tag simple usecases 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let scope = Object.create(context);
let result;
let h = this.h;
let c1 = [], p1 = {key:1};
let tag2 = 'div';
let vn1 = h(tag2, p1, c1);
result = vn1;
return result;
}"
`;
exports[`qweb t-tag simple usecases 2`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let scope = Object.create(context);
let result;
let h = this.h;
let c3 = [], p3 = {key:3};
let tag4 = scope['tag'];
let vn3 = h(tag4, p3, c3);
result = vn3;
c3.push({text: \`text\`});
return result;
}"
`;
exports[`qweb t-tag with multiple attributes 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let scope = Object.create(context);
let result;
let h = this.h;
let _2 = {'blueberry':true};
let _3 = 'raspberry';
let c4 = [], p4 = {key:4,attrs:{taste: _3},class:_2};
let tag5 = scope['tag'];
let vn4 = h(tag5, p4, c4);
result = vn4;
c4.push({text: \`gooseberry\`});
return result;
}"
`;
exports[`qweb t-tag with multiple child nodes 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let scope = Object.create(context);
let result;
let h = this.h;
let c1 = [], p1 = {key:1};
let tag2 = scope['tag'];
let vn1 = h(tag2, p1, c1);
result = vn1;
c1.push({text: \` pear \`});
let c3 = [], p3 = {key:3};
let vn3 = h('span', p3, c3);
c1.push(vn3);
c3.push({text: \`apple\`});
c1.push({text: \` strawberry \`});
return result;
}"
`;
-11
View File
@@ -2154,17 +2154,6 @@ describe("translation support", () => {
'<div><p label="mot">mot</p><p title="mot">mot</p><p placeholder="mot">mot</p><p alt="mot">mot</p><p something="word">mot</p></div>' '<div><p label="mot">mot</p><p title="mot">mot</p><p placeholder="mot">mot</p><p alt="mot">mot</p><p something="word">mot</p></div>'
); );
}); });
test("translation is done on the trimmed text, with extra spaces readded after", () => {
const translations = {
word: "mot",
};
const translateFn = jest.fn((expr) => translations[expr] || expr);
const qweb = new QWeb({ translateFn });
qweb.addTemplate("test", "<div> word </div>");
expect(renderToString(qweb, "test")).toBe("<div> mot </div>");
expect(translateFn).toHaveBeenCalledWith("word");
});
}); });
describe("t-key tests", () => { describe("t-key tests", () => {
-4
View File
@@ -196,8 +196,4 @@ describe("expression evaluation", () => {
expect(compileExpr("f(...state.list)", {})).toBe("scope['f'](...scope['state'].list)"); expect(compileExpr("f(...state.list)", {})).toBe("scope['f'](...scope['state'].list)");
expect(compileExpr("f([...list])", {})).toBe("scope['f']([...scope['list']])"); expect(compileExpr("f([...list])", {})).toBe("scope['f']([...scope['list']])");
}); });
test("works with builtin properties", () => {
expect(compileExpr("state.constructor.name", {})).toBe("scope['state'].constructor.name");
});
}); });
-42
View File
@@ -1,42 +0,0 @@
import { QWeb } from "../../src/qweb/index";
import { renderToString } from "../helpers";
//------------------------------------------------------------------------------
// Setup and helpers
//------------------------------------------------------------------------------
function render(template, context = {}) {
const qweb = new QWeb();
qweb.addTemplate("test", template);
return renderToString(qweb, "test", context);
}
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
describe("qweb t-tag", () => {
test("simple usecases", () => {
expect(render(`<t t-tag="'div'"></t>`)).toBe("<div></div>");
expect(render(`<t t-tag="tag">text</t>`, { tag: "span" })).toBe("<span>text</span>");
});
test("with multiple child nodes", () => {
const template = `
<t t-tag="tag">
pear
<span>apple</span>
strawberry
</t>`;
expect(render(template, { tag: "div" })).toBe(
"<div> pear <span>apple</span> strawberry </div>"
);
});
test("with multiple attributes", () => {
const template = `
<t t-tag="tag" class="blueberry" taste="raspberry">gooseberry</t>`;
const expected = `<div taste=\"raspberry\" class=\"blueberry\">gooseberry</div>`;
expect(render(template, { tag: "div" })).toBe(expected);
});
});
+1 -1
View File
@@ -11,7 +11,7 @@ exports[`Link component can render simple cases 1`] = `
let _6 = scope['href']; let _6 = scope['href'];
let c7 = [], p7 = {key:7,attrs:{href: _6},class:_5,on:{}}; let c7 = [], p7 = {key:7,attrs:{href: _6},class:_5,on:{}};
let vn7 = h('a', p7, c7); let vn7 = h('a', p7, c7);
extra.handlers['click__8__'] = extra.handlers['click__8__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['navigate'](e);}; extra.handlers['click__8__'] = extra.handlers['click__8__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['navigate'](e);};
p7.on['click'] = extra.handlers['click__8__']; p7.on['click'] = extra.handlers['click__8__'];
const slot9 = this.constructor.slots[context.__owl__.slotId + '_' + 'default']; const slot9 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
if (slot9) { if (slot9) {
@@ -29,7 +29,7 @@ exports[`RouteComponent can render simple cases 1`] = `
utils.defineProxy(vn6, pvnode); utils.defineProxy(vn6, pvnode);
} else { } else {
let componentKey4 = \`routeComponent\`; let componentKey4 = \`routeComponent\`;
let W4 = scope['routeComponent'] || context.constructor.components[componentKey4] || QWeb.components[componentKey4]; let W4 = context.constructor.components[componentKey4] || QWeb.components[componentKey4]|| scope['routeComponent'];
if (!W4) {throw new Error('Cannot find the definition of component \\"' + componentKey4 + '\\"')} if (!W4) {throw new Error('Cannot find the definition of component \\"' + componentKey4 + '\\"')}
w4 = new W4(parent, props4); w4 = new W4(parent, props4);
parent.__owl__.cmap[k5] = w4.__owl__.id; parent.__owl__.cmap[k5] = w4.__owl__.id;
-24
View File
@@ -103,28 +103,4 @@ describe("RouteComponent", () => {
await app.mount(fixture); await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>Book 1984|124</span></div>"); expect(fixture.innerHTML).toBe("<div><span>Book 1984|124</span></div>");
}); });
test("can render parameterized route where params are not separated by slashes", async () => {
env.qweb.addTemplates(`
<templates>
<div t-name="App">
<RouteComponent />
</div>
<span t-name="Book">Book <t t-esc="props.title"/>|<t t-esc="props.val"/></span>
</templates>
`);
class Book extends Component {}
class App extends Component {
static components = { RouteComponent };
}
const routes = [
{ name: "book", path: "/#title={{title}}&val={{val.number}}", component: Book },
];
router = new TestRouter(env, routes, { mode: "hash" });
await router.navigate({ to: "book", params: { title: "1984", val: "123" } });
const app = new App();
await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>Book 1984|123</span></div>");
});
}); });
+23 -27
View File
@@ -1,6 +1,6 @@
import { Destination, RouterEnv, Route } from "../../src/router/router"; import { Destination, RouterEnv, Route } from "../../src/router/router";
import { makeTestEnv, nextTick } from "../helpers"; import { makeTestEnv, nextTick } from "../helpers";
import { TestRouter, getRouteParams } from "./test_router"; import { TestRouter } from "./test_router";
let env: RouterEnv; let env: RouterEnv;
let router: TestRouter | null = null; let router: TestRouter | null = null;
@@ -107,64 +107,60 @@ describe("destToPath", () => {
describe("getRouteParams", () => { describe("getRouteParams", () => {
test("properly match simple routes", () => { test("properly match simple routes", () => {
router = new TestRouter(env, []);
// simple route // simple route
expect(getRouteParams({ path: "/home" }, "/home")).toEqual({}); expect(router["getRouteParams"]({ path: "/home" } as Route, "/home")).toEqual({});
// no match // no match
expect(getRouteParams({ path: "/home" }, "/otherpath")).toEqual(false); expect(router["getRouteParams"]({ path: "/home" } as Route, "/otherpath")).toEqual(false);
// fallback route // fallback route
expect(getRouteParams({ path: "*" }, "somepath")).toEqual({}); expect(router["getRouteParams"]({ path: "*" } as Route, "somepath")).toEqual({});
}); });
test("properly match simple routes, mode hash", () => { test("properly match simple routes, mode hash", () => {
router = new TestRouter(env, [], { mode: "hash" });
// simple route // simple route
expect(getRouteParams({ path: "/home" }, "#/home")).toEqual({}); expect(router["getRouteParams"]({ path: "/home" } as Route, "#/home")).toEqual({});
// no match // no match
expect(getRouteParams({ path: "/home" }, "#/otherpath")).toEqual(false); expect(router["getRouteParams"]({ path: "/home" } as Route, "#/otherpath")).toEqual(false);
// fallback route // fallback route
expect(getRouteParams({ path: "*" }, "#/somepath")).toEqual({}); expect(router["getRouteParams"]({ path: "*" } as Route, "#/somepath")).toEqual({});
}); });
test("match some parameterized routes", () => { test("match some parameterized routes", () => {
expect(getRouteParams({ path: "/invoices/{{id}}" }, "/invoices/3")).toEqual({ router = new TestRouter(env, []);
expect(router["getRouteParams"]({ path: "/invoices/{{id}}" } as Route, "/invoices/3")).toEqual({
id: "3", id: "3",
}); });
}); });
test("match some parameterized routes, mode hash", () => { test("match some parameterized routes, mode hash", () => {
expect(getRouteParams({ path: "/invoices/{{id}}" }, "#/invoices/3")).toEqual({ router = new TestRouter(env, [], { mode: "hash" });
id: "3", expect(router["getRouteParams"]({ path: "/invoices/{{id}}" } as Route, "#/invoices/3")).toEqual(
}); {
id: "3",
}
);
}); });
test("can convert to number if needed", () => { test("can convert to number if needed", () => {
expect(getRouteParams({ path: "/invoices/{{id.number}}" }, "/invoices/3")).toEqual({ router = new TestRouter(env, []);
expect(
router["getRouteParams"]({ path: "/invoices/{{id.number}}" } as Route, "/invoices/3")
).toEqual({
id: 3, id: 3,
}); });
}); });
test("can convert to number if needed, mode: hash", () => { test("can convert to number if needed, mode: hash", () => {
expect(getRouteParams({ path: "/invoices/{{id.number}}" }, "#/invoices/3")).toEqual({ router = new TestRouter(env, [], { mode: "hash" });
id: 3,
});
});
test("can extract params not separated by slashes", () => {
expect(getRouteParams({ path: "/books/{{id.number}}-{{name}}" }, "/books/3-1984")).toEqual({
id: 3,
name: "1984",
});
});
test("can extract params not separated by slashes, mode: hash", () => {
expect( expect(
getRouteParams({ path: "books&id={{id.number}}&name={{name}}" }, "#books&id=3&name=1984") router["getRouteParams"]({ path: "/invoices/{{id.number}}" } as Route, "#/invoices/3")
).toEqual({ ).toEqual({
id: 3, id: 3,
name: "1984",
}); });
}); });
}); });
+1 -12
View File
@@ -1,5 +1,4 @@
import { Router, Route, RouterEnv } from "../../src/router/router"; import { Router } from "../../src/router/router";
import { makeTestEnv } from "../helpers";
import { QWeb } from "../../src/qweb/index"; import { QWeb } from "../../src/qweb/index";
export class TestRouter extends Router { export class TestRouter extends Router {
@@ -14,13 +13,3 @@ export class TestRouter extends Router {
} }
} }
} }
export function getRouteParams(route: Partial<Route>, path: string) {
const env = <RouterEnv>makeTestEnv();
const router = new TestRouter(env, [route]);
const {
routeIds: [routeId],
routes,
} = router;
return router["getRouteParams"](routes[routeId], path);
}
+3 -161
View File
@@ -1,4 +1,4 @@
import { Component, Env, mount } from "../src/component/component"; import { Component, Env } from "../src/component/component";
import { Store, useStore, useDispatch, useGetters, EnvWithStore } from "../src/store"; import { Store, useStore, useDispatch, useGetters, EnvWithStore } from "../src/store";
import { useState } from "../src/hooks"; import { useState } from "../src/hooks";
import { xml } from "../src/tags"; import { xml } from "../src/tags";
@@ -571,12 +571,12 @@ describe("connecting a component to store", () => {
app.state.beerId = 2; app.state.beerId = 2;
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div><span>kwak</span></div>"); expect(fixture.innerHTML).toBe("<div><span>kwak</span></div>");
expect(counter).toBe(0); expect(counter).toBe(1);
store.dispatch("renameBeer", { id: 2, name: "orval" }); store.dispatch("renameBeer", { id: 2, name: "orval" });
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div><span>orval</span></div>"); expect(fixture.innerHTML).toBe("<div><span>orval</span></div>");
expect(counter).toBe(1); expect(counter).toBe(2);
}); });
test("connected component is properly cleaned up on destroy", async () => { test("connected component is properly cleaned up on destroy", async () => {
@@ -1241,162 +1241,4 @@ describe("various scenarios", () => {
await nextTick(); await nextTick();
expect(fixture.innerHTML).toMatchSnapshot(); expect(fixture.innerHTML).toMatchSnapshot();
}); });
test("component with store, useState and shouldUpdate=false", async () => {
let state: any;
const store = new Store({ state: { rev: 0 } });
class Child extends Component {
static template = xml`<div><t t-esc="state.word"/><t t-esc="props.name"/></div>`;
state = useState({ word: "hello" });
constructor(parent, props) {
super(parent, props);
state = this.state;
useStore((props) => {
return 1;
});
}
shouldUpdate() {
return false;
}
}
class Parent extends Component {
static template = xml`<div><Child name="state.name"/></div>`;
static components = { Child };
state = useState({ name: "World" });
constructor(parent, props) {
super(parent, props);
useStore((props) => store.state.rev);
}
}
(env as any).store = store;
await mount(Parent, { target: fixture, env });
expect(fixture.innerHTML).toBe("<div><div>helloWorld</div></div>");
store.state.rev++;
// this is the key to the bug, it makes Parent be in "render" state but not
// yet rendered while the change of state happens
await Promise.resolve();
state.word = "test";
await nextTick();
expect(fixture.innerHTML).toBe("<div><div>testWorld</div></div>");
});
test("component with store, useState, shouldUpdate=false and child with shouldupdate false", async () => {
let state: any;
const store = new Store({ state: { rev: 0 } });
class ChildChild extends Component {
static template = xml`<div><t t-esc="props.value"/></div>`;
shouldUpdate() {
return false;
}
}
class Child extends Component {
static template = xml`<div><t t-esc="state.word"/><t t-esc="props.name"/><ChildChild value="state.value"/></div>`;
static components = { ChildChild };
state = useState({ word: "hello", value: 3 });
constructor(parent, props) {
super(parent, props);
state = this.state;
useStore((props) => {
return 1;
});
}
shouldUpdate() {
return false;
}
}
class Parent extends Component {
static template = xml`<div><Child name="state.name"/></div>`;
static components = { Child };
state = useState({ name: "World" });
constructor(parent, props) {
super(parent, props);
useStore((props) => store.state.rev);
}
}
(env as any).store = store;
await mount(Parent, { target: fixture, env });
expect(fixture.innerHTML).toBe("<div><div>helloWorld<div>3</div></div></div>");
store.state.rev++;
// this is the key to the bug, it makes Parent be in "render" state but not
// yet rendered while the change of state happens
await Promise.resolve();
state.word = "test";
state.value = 44;
await nextTick();
expect(fixture.innerHTML).toBe("<div><div>testWorld<div>3</div></div></div>");
});
test("parent/children with store, parent is remounted", async () => {
const store = new Store({ state: { a: 1, b: 1 } });
class Child extends Component {
static template = xml`<div><t t-esc="a"/></div>`;
a: any;
constructor(parent, props) {
super(parent, props);
this.a = useStore(
(state, props) => {
return state.a;
},
{
onUpdate: (a) => {
this.a = a;
},
}
);
}
}
class Parent extends Component {
static template = xml`
<div>
parent: <t t-esc="b"/>
<Child/>
</div>`;
static components = { Child };
b: any;
constructor(parent, props) {
super(parent, props);
this.b = useStore((state, props) => {
return state.b;
});
}
}
(env as any).store = store;
const div = document.createElement("div");
fixture.appendChild(div);
// initial mounting
const parent = await mount(Parent, { target: fixture, env });
expect(fixture.innerHTML).toBe("<div></div><div> parent: 1<div>1</div></div>");
// remounting component, then immediately update store.state
parent.mount(div);
store.state.a++;
await nextTick();
expect(fixture.innerHTML).toBe("<div><div> parent: 1<div>2</div></div></div>");
});
}); });
+2 -6
View File
@@ -8,7 +8,7 @@ import * as owl from "../../src/index";
import { Component, Env } from "../../src/component/component"; import { Component, Env } from "../../src/component/component";
import { xml } from "../../src/tags"; import { xml } from "../../src/tags";
import { makeTestFixture, makeTestEnv, nextTick } from "../helpers"; import { makeTestFixture, makeTestEnv } from "../helpers";
let fixture: HTMLElement = makeTestFixture(); let fixture: HTMLElement = makeTestFixture();
let env: Env = makeTestEnv(); let env: Env = makeTestEnv();
@@ -31,7 +31,6 @@ test("log a specific message for render method calls if component is not mounted
parent.unmount(); parent.unmount();
parent.state.value = 2; parent.state.value = 2;
await nextTick();
expect(steps).toEqual([ expect(steps).toEqual([
"[OWL_DEBUG] Parent<id=1> constructor, props={}", "[OWL_DEBUG] Parent<id=1> constructor, props={}",
"[OWL_DEBUG] Parent<id=1> mount", "[OWL_DEBUG] Parent<id=1> mount",
@@ -41,10 +40,7 @@ test("log a specific message for render method calls if component is not mounted
"[OWL_DEBUG] Parent<id=1> mounted", "[OWL_DEBUG] Parent<id=1> mounted",
"[OWL_DEBUG] scheduler: stop running tasks queue", "[OWL_DEBUG] scheduler: stop running tasks queue",
"[OWL_DEBUG] Parent<id=1> willUnmount", "[OWL_DEBUG] Parent<id=1> willUnmount",
"[OWL_DEBUG] Parent<id=1> render (warning: component is not mounted)", "[OWL_DEBUG] Parent<id=1> render (warning: component is not mounted, this render has no effect)",
"[OWL_DEBUG] scheduler: start running tasks queue",
"[OWL_DEBUG] Parent<id=1> rendering template",
"[OWL_DEBUG] scheduler: stop running tasks queue",
]); ]);
console.log = log; console.log = log;
}); });
+2 -2
View File
@@ -101,8 +101,8 @@
component.render = function(...args) { component.render = function(...args) {
const __owl__ = component.__owl__; const __owl__ = component.__owl__;
let msg = `render`; let msg = `render`;
if (__owl__.status !== 3 /* mounted */ && !__owl__.currentFiber) { if (!__owl__.isMounted && !__owl__.currentFiber) {
msg += ` (warning: component is not mounted)`; msg += ` (warning: component is not mounted, this render has no effect)`;
} }
log(msg); log(msg);
return render(...args); return render(...args);
+41 -22
View File
@@ -2,7 +2,8 @@ const COMPONENTS = `// In this example, we show how components can be defined an
const { Component, useState, mount } = owl; const { Component, useState, mount } = owl;
class Greeter extends Component { class Greeter extends Component {
setup() { constructor() {
super(...arguments);
this.state = useState({ word: 'Hello' }); this.state = useState({ word: 'Hello' });
} }
@@ -13,9 +14,10 @@ class Greeter extends Component {
// Main root component // Main root component
class App extends Component { class App extends Component {
setup() { constructor() {
this.state = useState({ name: 'World'}); super(...arguments);
} this.state = useState({ name: 'World'});
}
} }
App.components = { Greeter }; App.components = { Greeter };
@@ -50,7 +52,8 @@ const ANIMATION = `// The goal of this component is to see how the t-transition
const { Component, useState, mount } = owl; const { Component, useState, mount } = owl;
class Counter extends Component { class Counter extends Component {
setup() { constructor() {
super(...arguments);
this.state = useState({ value: 0 }); this.state = useState({ value: 0 });
} }
@@ -60,7 +63,8 @@ class Counter extends Component {
} }
class App extends Component { class App extends Component {
setup() { constructor() {
super(...arguments);
this.state = useState({ flag: false, componentFlag: false, numbers: [] }); this.state = useState({ flag: false, componentFlag: false, numbers: [] });
} }
@@ -190,9 +194,10 @@ const LIFECYCLE_DEMO = `// This example shows all the possible lifecycle hooks
const { Component, useState, mount } = owl; const { Component, useState, mount } = owl;
class DemoComponent extends Component { class DemoComponent extends Component {
setup() { constructor() {
super(...arguments);
this.state = useState({ n: 0 }); this.state = useState({ n: 0 });
console.log("setup"); console.log("constructor");
} }
async willStart() { async willStart() {
console.log("willstart"); console.log("willstart");
@@ -218,7 +223,8 @@ class DemoComponent extends Component {
} }
class App extends Component { class App extends Component {
setup() { constructor() {
super(...arguments);
this.state = useState({ n: 0, flag: true }); this.state = useState({ n: 0, flag: true });
} }
@@ -289,7 +295,8 @@ function useMouse() {
// Main root component // Main root component
class App extends owl.Component { class App extends owl.Component {
setup() { constructor() {
super(...arguments);
// simple state hook (reactive object) // simple state hook (reactive object)
this.counter = useState({ value: 0 }); this.counter = useState({ value: 0 });
@@ -326,7 +333,8 @@ const { Component, Context, mount } = owl;
const { useContext } = owl.hooks; const { useContext } = owl.hooks;
class ToolbarButton extends Component { class ToolbarButton extends Component {
setup() { constructor() {
super(...arguments);
this.theme = useContext(this.env.themeContext); this.theme = useContext(this.env.themeContext);
} }
@@ -460,7 +468,8 @@ const actions = {
// TodoItem // TodoItem
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class TodoItem extends Component { class TodoItem extends Component {
setup() { constructor() {
super(...arguments);
useAutofocus("input"); useAutofocus("input");
this.state = useState({ isEditing: false }); this.state = useState({ isEditing: false });
this.dispatch = useDispatch(); this.dispatch = useDispatch();
@@ -490,7 +499,8 @@ class TodoItem extends Component {
// TodoApp // TodoApp
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class TodoApp extends Component { class TodoApp extends Component {
setup() { constructor() {
super(...arguments);
this.state = useState({ filter: "all" }); this.state = useState({ filter: "all" });
this.todos = useStore(state => state.todos); this.todos = useStore(state => state.todos);
this.dispatch = useDispatch(); this.dispatch = useDispatch();
@@ -1016,7 +1026,8 @@ class FormView extends owl.Component {}
FormView.components = { AdvancedComponent }; FormView.components = { AdvancedComponent };
class Chatter extends owl.Component { class Chatter extends owl.Component {
setup() { constructor() {
super(...arguments);
this.messages = Array.from(Array(100).keys()); this.messages = Array.from(Array(100).keys());
} }
} }
@@ -1159,7 +1170,8 @@ const SLOTS = `// We show here how slots can be used to create generic component
const { Component, useState, mount } = owl; const { Component, useState, mount } = owl;
class Card extends Component { class Card extends Component {
setup() { constructor() {
super(...arguments);
this.state = useState({ showContent: true }); this.state = useState({ showContent: true });
} }
@@ -1169,7 +1181,8 @@ class Card extends Component {
} }
class Counter extends Component { class Counter extends Component {
setup() { constructor() {
super(...arguments);
this.state = useState({val: 1}); this.state = useState({val: 1});
} }
@@ -1180,7 +1193,8 @@ class Counter extends Component {
// Main root component // Main root component
class App extends Component { class App extends Component {
setup() { constructor() {
super(...arguments);
this.state = useState({a: 1, b: 3}); this.state = useState({a: 1, b: 3});
} }
@@ -1292,7 +1306,8 @@ class SlowComponent extends Component {
class NotificationList extends Component {} class NotificationList extends Component {}
class App extends Component { class App extends Component {
setup() { constructor() {
super(...arguments);
this.state = useState({ value: 0, notifs: [] }); this.state = useState({ value: 0, notifs: [] });
} }
@@ -1366,7 +1381,8 @@ const FORM = `// This example illustrate how the t-model directive can be used t
const { Component, useState, mount } = owl; const { Component, useState, mount } = owl;
class Form extends Component { class Form extends Component {
setup() { constructor() {
super(...arguments);
this.state = useState({ this.state = useState({
text: "", text: "",
othertext: "", othertext: "",
@@ -1540,7 +1556,8 @@ const { useRef } = owl.hooks;
class HelloWorld extends Component {} class HelloWorld extends Component {}
class Counter extends Component { class Counter extends Component {
setup() { constructor() {
super(...arguments);
this.state = useState({ value: 0 }); this.state = useState({ value: 0 });
} }
@@ -1593,7 +1610,8 @@ class Window extends Component {
} }
class WindowManager extends Component { class WindowManager extends Component {
setup() { constructor() {
super(...arguments);
this.windows = []; this.windows = [];
this.nextId = 1; this.nextId = 1;
this.currentZindex = 1; this.currentZindex = 1;
@@ -1643,7 +1661,8 @@ class WindowManager extends Component {
WindowManager.components = { Window }; WindowManager.components = { Window };
class App extends Component { class App extends Component {
setup() { constructor() {
super(...arguments);
this.wmRef = useRef("wm"); this.wmRef = useRef("wm");
} }
+9 -64
View File
@@ -3,11 +3,9 @@ const readline = require("readline");
const fs = require("fs"); const fs = require("fs");
const exec = require("child_process").exec; const exec = require("child_process").exec;
const chalk = require("chalk"); const chalk = require("chalk");
const branchName = require('current-git-branch');
const REL_NOTES_FILE = `release-notes.md`; const REL_NOTES_FILE = `release-notes.md`;
const STEPS = 8; const STEPS = 8;
const branch = "master";
const rl = readline.createInterface({ const rl = readline.createInterface({
input: process.stdin, input: process.stdin,
@@ -23,19 +21,6 @@ startRelease().then(() => {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
async function startRelease() { async function startRelease() {
// First check we are on master
if (branchName() !== branch) {
logError(`You shall not pass! You are not on the ${branch} branch!`)
return;
}
log("Check if code formatting is right...")
const checkFormatting = await execCommand("npm run check-formatting");
if (checkFormatting !== 0) {
logError("Prettier format validation failed. Aborting.");
return;
}
log(`*** Owl release script ***`); log(`*** Owl release script ***`);
log(`Current Version: ${package.version}`); log(`Current Version: ${package.version}`);
@@ -51,7 +36,7 @@ async function startRelease() {
content = await readFile("./" + file); content = await readFile("./" + file);
} catch (e) { } catch (e) {
logSubContent(e.message); logSubContent(e.message);
logError("Cannot find release notes... Aborting"); log("Cannot find release notes... Aborting");
return; return;
} }
let shouldBeDraft = await ask(`Should be a draft [y/n] ? (n)`); let shouldBeDraft = await ask(`Should be a draft [y/n] ? (n)`);
@@ -60,14 +45,12 @@ async function startRelease() {
{ {
draft = "--draft"; draft = "--draft";
} }
let shouldUploadPlayground = await ask(`Should this release be uploaded on the playground [y/n] ? (y)`);
shouldUploadPlayground = shouldUploadPlayground.toLowerCase() !== 'n';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
log(`Step 2/${STEPS}: running tests...`); log(`Step 2/${STEPS}: running tests...`);
const testsResult = await execCommand("npm run test"); const testsResult = await execCommand("npm run test");
if (testsResult !== 0) { if (testsResult !== 0) {
logError("Test suite does not pass. Aborting."); log("Test suite does not pass. Aborting.");
return; return;
} }
@@ -81,24 +64,24 @@ async function startRelease() {
log(`Step 4/${STEPS}: creating git commit...`); log(`Step 4/${STEPS}: creating git commit...`);
const gitResult = await execCommand(`git commit -am "[REL] v${next}\n\n${content}"`); const gitResult = await execCommand(`git commit -am "[REL] v${next}\n\n${content}"`);
if (gitResult !== 0) { if (gitResult !== 0) {
logError("Git commit failed. Aborting."); log("Git commit failed. Aborting.");
return; return;
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
log(`Step 5/${STEPS}: building owl...`); log(`Step 5/${STEPS}: building owl...`);
await execCommand("rm -rf dist/"); await execCommand("npm run prettier");
const buildResult = await execCommand("npm run build"); const buildResult = await execCommand("npm run build");
if (buildResult !== 0) { if (buildResult !== 0) {
logError("Build failed. Aborting."); log("Build failed. Aborting.");
return; return;
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
log(`Step 6/${STEPS}: pushing on github...`); log(`Step 6/${STEPS}: pushing on github...`);
const pushResult = await execCommand("git push origin " + branch); const pushResult = await execCommand("git push");
if (pushResult !== 0) { if (pushResult !== 0) {
logError("git push failed. Aborting."); log("git push failed. Aborting.");
return; return;
} }
@@ -107,51 +90,17 @@ async function startRelease() {
log(`Step 7/${STEPS}: Creating the release...`); log(`Step 7/${STEPS}: Creating the release...`);
const relaseResult = await execCommand(`gh release create v${next} dist/*.js ${draft} -F release-notes.md`); const relaseResult = await execCommand(`gh release create v${next} dist/*.js ${draft} -F release-notes.md`);
if (relaseResult !== 0) { if (relaseResult !== 0) {
logError("github release failed. Aborting."); log("github release failed. Aborting.");
return; return;
} }
log(`Step 8/${STEPS}: publishing module on npm...`); log(`Step 8/${STEPS}: publishing module on npm...`);
await execCommand("npm run publish"); await execCommand("npm run publish");
log("Owl Release process completed! Thank you for your patience"); log("Owl Release process completed! Thank you for your patience");
await execCommand(`gh release view`); await execCommand(`gh release view`);
await execCommand(`gh release view -w`); await execCommand(`gh release view -w`);
if (shouldUploadPlayground) {
log(`Bonus step: publishing new release on playground...`);
let owl_code = null;
status = 0
try {
owl_code = await readFile("dist/owl.iife.js");
} catch (e) {
logSubContent(e.message);
logError("Cannot read owl.iife.js... Aborting");
return;
}
status += await execCommand("git checkout gh-pages");
if (status !== 0) {
logError("Couldn't switch to gh-pages branch")
return;
}
try {
fs.writeFileSync('owl.js', owl_code)
} catch (err) {
logError(err)
return;
}
status += await execCommand(`git commit -am "[IMP] update owl to v${next}"`);
status += await execCommand(`git push origin gh-pages`);
status += await execCommand("git checkout -");
if (status !== 0) {
logError("Something went wrong for the playground update.")
}
}
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -162,10 +111,6 @@ function log(text) {
console.log(chalk.yellow(formatLog(text))); console.log(chalk.yellow(formatLog(text)));
} }
function logError(text) {
console.log(chalk.red(formatLog(text)));
}
function formatLog(text) { function formatLog(text) {
return `[REL] ${text}`; return `[REL] ${text}`;
} }