mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e2202c02a |
@@ -120,7 +120,7 @@ npm install @odoo/owl
|
||||
|
||||
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
||||
|
||||
- [owl-1.2.0](https://github.com/odoo/owl/releases/tag/v1.2.0)
|
||||
- [owl-1.0.13](https://github.com/odoo/owl/releases/tag/v1.0.13)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ You will find here a complete reference of every feature, class or object
|
||||
provided by Owl.
|
||||
|
||||
- [Animations](reference/animations.md)
|
||||
- [Browser](reference/browser.md)
|
||||
- [Component](reference/component.md)
|
||||
- [Content](reference/content.md)
|
||||
- [Concurrency Model](reference/concurrency_model.md)
|
||||
|
||||
@@ -52,20 +52,21 @@ sequence of events will happen:
|
||||
At node insertion:
|
||||
|
||||
- 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
|
||||
class `name-enter-to` will be added (so they can be used to trigger css
|
||||
transition effects).
|
||||
- at the end of the transition, `name-enter-to` and `name-enter-active` will be removed.
|
||||
transition effects),
|
||||
- the css class `name-enter-active` will be removed whenever a css transition
|
||||
ends.
|
||||
|
||||
At node destruction:
|
||||
|
||||
- the css classes `name-leave` and `name-leave-active` will be added before the
|
||||
node is removed to the DOM.
|
||||
- on the next animation frame: the css class `name-leave` will be removed and the
|
||||
class `name-leave-to` will be added (so they can be used to trigger css
|
||||
transition effects).
|
||||
- at the end of the transition, `name-leave-to` and `name-leave-active` will be removed.
|
||||
node is removed to the DOM,
|
||||
- the css class `name-leave` will be removed on the next animation frame (so it
|
||||
can be used to trigger css transition effects),
|
||||
- the css class `name-leave-active` will be removed whenever a css transition
|
||||
ends. Only then will the element be removed from the DOM.
|
||||
|
||||
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
|
||||
`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"/>
|
||||
```
|
||||
|
||||
@@ -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`
|
||||
@@ -7,7 +7,6 @@ For example, `Component` is available at `owl.Component` and `EventBus` is
|
||||
exported as `owl.core.EventBus`.
|
||||
|
||||
```
|
||||
browser
|
||||
Component misc
|
||||
Context AsyncRoot
|
||||
QWeb Portal
|
||||
@@ -29,8 +28,6 @@ hooks utils
|
||||
useContext
|
||||
useState
|
||||
useRef
|
||||
useComponent
|
||||
useEnv
|
||||
useSubEnv
|
||||
useStore
|
||||
useDispatch
|
||||
|
||||
@@ -133,4 +133,17 @@ the `QWeb` instance and a `browser` object:
|
||||
- `qweb` will be set to an empty `QWeb` instance. This is absolutely necessary
|
||||
for Owl to be able to render anything
|
||||
- `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`
|
||||
|
||||
+4
-14
@@ -21,8 +21,6 @@
|
||||
- [`useStore`](#usestore)
|
||||
- [`useDispatch`](#usedispatch)
|
||||
- [`useGetters`](#usegetters)
|
||||
- [`useComponent`](#usecomponent)
|
||||
- [`useEnv`](#useenv)
|
||||
- [Making customized hooks](#making-customized-hooks)
|
||||
|
||||
## Overview
|
||||
@@ -383,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
|
||||
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
|
||||
|
||||
Hooks are a wonderful way to organize the code of a complex component by feature
|
||||
@@ -447,11 +435,13 @@ not the solution to every problem.
|
||||
|
||||
```js
|
||||
function useRouter() {
|
||||
const env = useEnv();
|
||||
return env.router;
|
||||
return Component.current.env.router;
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "1.2.0",
|
||||
"version": "1.0.13",
|
||||
"description": "Odoo Web Library (OWL)",
|
||||
"main": "dist/owl.cjs.js",
|
||||
"browser": "dist/owl.iife.js",
|
||||
@@ -13,8 +13,8 @@
|
||||
"node": ">=10.15.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build:bundle": "rollup -c",
|
||||
"build": "npm run build:bundle",
|
||||
"dev": "rollup -c",
|
||||
"build": "NODE_ENV=production rollup -c",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"tools:serve": "python3 tools/server.py || python tools/server.py",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# 🦉 OWL Roadmap 🦉
|
||||
|
||||
- Current version: 1.2.0
|
||||
- Current version: 1.0.13
|
||||
- Status: stable
|
||||
|
||||
This roadmap is only an attempt at predicting Owl's future. Everything may
|
||||
|
||||
+1
-8
@@ -10,8 +10,6 @@ export interface Browser {
|
||||
localStorage: Window["localStorage"];
|
||||
}
|
||||
|
||||
let localStorage: Window["localStorage"] | null = null;
|
||||
|
||||
export const browser: Browser = {
|
||||
setTimeout: window.setTimeout.bind(window),
|
||||
clearTimeout: window.clearTimeout.bind(window),
|
||||
@@ -21,10 +19,5 @@ export const browser: Browser = {
|
||||
random: Math.random,
|
||||
Date: window.Date,
|
||||
fetch: (window.fetch || (() => {})).bind(window),
|
||||
get localStorage() {
|
||||
return localStorage || window.localStorage;
|
||||
},
|
||||
set localStorage(newLocalStorage: Window["localStorage"]) {
|
||||
localStorage = newLocalStorage;
|
||||
},
|
||||
localStorage: window.localStorage,
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import "./props_validation";
|
||||
import { Scheduler, scheduler } from "./scheduler";
|
||||
import { activateSheet } from "./styles";
|
||||
import { Browser, browser } from "../browser";
|
||||
import { onMounted, onPatched } from "../hooks";
|
||||
|
||||
/**
|
||||
* Owl Component System
|
||||
@@ -91,8 +92,24 @@ interface Internal<T extends Env> {
|
||||
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
|
||||
|
||||
/**
|
||||
* It is required for the dev tools to have access to the __owl__ element.
|
||||
*/
|
||||
interface HTMLElementWithDevToolsAccess extends HTMLElement {
|
||||
__owl_devtools__: DevToolsAccess
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Component
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -110,11 +127,13 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
||||
// expose scheduler s.t. it can be mocked for testing purposes
|
||||
static scheduler: Scheduler = scheduler;
|
||||
|
||||
__devtools__: DevToolsAccess;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
get el(): HTMLElement | null {
|
||||
get el(): HTMLElementWithDevToolsAccess | null {
|
||||
return this.__owl__.vnode ? (<any>this).__owl__.vnode.elm : null;
|
||||
}
|
||||
|
||||
@@ -159,7 +178,6 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
||||
if (!this.env.qweb) {
|
||||
this.env.qweb = new QWeb();
|
||||
}
|
||||
// TODO: remove this in owl 2.0
|
||||
if (!this.env.browser) {
|
||||
this.env.browser = browser;
|
||||
}
|
||||
@@ -209,6 +227,24 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
||||
if (constr.style) {
|
||||
this.__applyStyles(constr);
|
||||
}
|
||||
|
||||
// DevTools hooks
|
||||
onMounted(() => {
|
||||
this.__devtools__ = {
|
||||
depth: this.__owl__.depth,
|
||||
state: this.__owl__.observer,
|
||||
tag: this.constructor.name
|
||||
};
|
||||
this.__devtools__.defaultProps = defaultProps;
|
||||
this.__devtools__.props = this.props;
|
||||
this.__devtools__.template = template;
|
||||
this.el.__owl_devtools__ = this.__devtools__;
|
||||
})
|
||||
onPatched(() => {
|
||||
this.__devtools__.depth = this.__owl__.depth,
|
||||
this.el.__owl_devtools__ = this.__devtools__;
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -333,7 +369,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
||||
message += `\nMaybe the DOM is not ready yet? (in that case, you can use owl.utils.whenReady)`;
|
||||
throw new Error(message);
|
||||
}
|
||||
const fiber = new Fiber(null, this, true, target, position);
|
||||
const fiber = new Fiber(null, this, false, target, position);
|
||||
fiber.shouldPatch = false;
|
||||
if (!__owl__.vnode) {
|
||||
this.__prepareAndRender(fiber, () => {});
|
||||
|
||||
@@ -115,6 +115,16 @@ export function useContextWithCB(ctx: Context, component: Component, method): an
|
||||
__owl__.observer = new Observer();
|
||||
__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;
|
||||
const renderFn = __owl__.renderFn;
|
||||
|
||||
@@ -21,7 +21,6 @@ export class Observer {
|
||||
rev: number = 1;
|
||||
allowMutations: boolean = true;
|
||||
weakMap: WeakMap<any, any> = new WeakMap();
|
||||
|
||||
notifyCB() {}
|
||||
|
||||
observe<T>(value: T, parent?: any): T {
|
||||
|
||||
+1
-21
@@ -1,4 +1,4 @@
|
||||
import { Component, Env } from "./component/component";
|
||||
import { Component } from "./component/component";
|
||||
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
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -22,7 +22,6 @@ import { Router } from "./router/router";
|
||||
export { Component, mount } from "./component/component";
|
||||
export { QWeb };
|
||||
export { config };
|
||||
export { browser } from "./browser";
|
||||
|
||||
export const Context = _context.Context;
|
||||
export const useState = _hooks.useState;
|
||||
|
||||
+1
-3
@@ -70,7 +70,6 @@ const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
|
||||
|
||||
const lineBreakRE = /[\r\n]/;
|
||||
const whitespaceRE = /\s+/g;
|
||||
const translationRE = /^(\s*)([\s\S]+?)(\s*)$/;
|
||||
|
||||
const NODE_HOOKS_PARAMS = {
|
||||
create: "(_, n)",
|
||||
@@ -497,8 +496,7 @@ export class QWeb extends EventBus {
|
||||
}
|
||||
if (this.translateFn) {
|
||||
if ((node.parentNode as any).getAttribute("t-translation") !== "off") {
|
||||
const match = translationRE.exec(text);
|
||||
text = match[1] + this.translateFn(match[2]) + match[3];
|
||||
text = this.translateFn(text);
|
||||
}
|
||||
}
|
||||
if (ctx.parentNode) {
|
||||
|
||||
+2
-1
@@ -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 { onWillUpdateProps } from "./hooks";
|
||||
|
||||
|
||||
@@ -600,51 +600,4 @@ describe("unmounting and remounting", () => {
|
||||
await parent.render();
|
||||
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
@@ -289,26 +289,7 @@ describe("Context", () => {
|
||||
expect(testContext.subscriptions.update.length).toBe(0);
|
||||
});
|
||||
|
||||
test.skip("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).
|
||||
*/
|
||||
test("concurrent renderings", async () => {
|
||||
const testContext = new Context({ x: { n: 1 }, key: "x" });
|
||||
const def = makeDeferred();
|
||||
let stateC;
|
||||
|
||||
@@ -9,10 +9,8 @@ import {
|
||||
onWillPatch,
|
||||
onWillStart,
|
||||
onWillUpdateProps,
|
||||
useEnv,
|
||||
useSubEnv,
|
||||
useExternalListener,
|
||||
useComponent,
|
||||
} from "../src/hooks";
|
||||
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 () => {
|
||||
class TestComponent extends Component {
|
||||
static template = xml`<div><t t-esc="env.val"/></div>`;
|
||||
@@ -550,19 +535,6 @@ describe("hooks", () => {
|
||||
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 () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<div><t t-esc="env.val"/></div>`;
|
||||
|
||||
@@ -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`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
|
||||
@@ -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>'
|
||||
);
|
||||
});
|
||||
|
||||
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", () => {
|
||||
|
||||
+1
-105
@@ -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 { useState } from "../src/hooks";
|
||||
import { xml } from "../src/tags";
|
||||
@@ -1241,108 +1241,4 @@ describe("various scenarios", () => {
|
||||
await nextTick();
|
||||
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>");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user