mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] tags: introduce xml tag
Big change! This commit introduces an xml function tag to easily define inline templates. This is a pretty big change toward single file owl components Part of #284
This commit is contained in:
@@ -18,20 +18,16 @@ related projects. OWL's main features are:
|
||||
|
||||
Here is a short example to illustrate interactive components:
|
||||
|
||||
```xml
|
||||
<templates>
|
||||
<button t-name="Counter" t-on-click="increment">
|
||||
Click Me! [<t t-esc="state.value"/>]
|
||||
</button>
|
||||
<div t-name="App">
|
||||
<span>Hello Owl</span>
|
||||
<Counter />
|
||||
</div>
|
||||
</templates>
|
||||
```
|
||||
|
||||
```javascript
|
||||
class Counter extends owl.Component {
|
||||
import { Component, QWeb } from 'owl'
|
||||
import { xml } from 'owl/tags'
|
||||
|
||||
class Counter extends Component {
|
||||
static template = xml`
|
||||
<button t-on-click="increment">
|
||||
Click Me! [<t t-esc="state.value"/>]
|
||||
</button>`;
|
||||
|
||||
state = { value: 0 };
|
||||
|
||||
increment() {
|
||||
@@ -39,17 +35,22 @@ class Counter extends owl.Component {
|
||||
}
|
||||
}
|
||||
|
||||
class App extends owl.Component {
|
||||
class App extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<span>Hello Owl</span>
|
||||
<Counter />
|
||||
</div>`;
|
||||
|
||||
static components = { Counter };
|
||||
|
||||
}
|
||||
|
||||
const qweb = new owl.QWeb(TEMPLATES);
|
||||
const app = new App({ qweb });
|
||||
const app = new App({ qweb: new QWeb() });
|
||||
app.mount(document.body);
|
||||
```
|
||||
|
||||
Note that we assume here that the xml templates are available in the `TEMPLATES`
|
||||
string. More interesting examples can be found on the
|
||||
More interesting examples can be found on the
|
||||
[playground](https://odoo.github.io/owl/playground) application.
|
||||
|
||||
## OWL's Design Principles
|
||||
|
||||
+7
-7
@@ -72,7 +72,7 @@ Note that this code is written in ESNext style, so it will only run on the
|
||||
latest browsers without a transpilation step.
|
||||
|
||||
This example show how a component should be defined: it simply subclasses the
|
||||
Component class. If no `template` key is defined, then
|
||||
Component class. If no static `template` key is defined, then
|
||||
Owl will use the component's name as template name. Here,
|
||||
a state object is defined. It is not mandatory to use the state object, but it
|
||||
is certainly encouraged. The state object is [observed](observer.md), and any
|
||||
@@ -96,9 +96,6 @@ find a template with the component name (or one of its ancestor).
|
||||
|
||||
- **`env`** (Object): the component environment, which contains a QWeb instance.
|
||||
|
||||
- **`template`** (string, optional): if given, this is the name of the QWeb template that will render
|
||||
the component.
|
||||
|
||||
- **`state`** (Object): this is the location of the component's state, if there is
|
||||
any. After the willStart method, the `state` property is observed, and each
|
||||
change will cause the component to rerender itself.
|
||||
@@ -113,7 +110,10 @@ find a template with the component name (or one of its ancestor).
|
||||
|
||||
### Static Properties
|
||||
|
||||
- **`components`** (Object, optional): if given, this is an object that contains
|
||||
- **`template`** (string, optional): if given, this is the name of the QWeb template that will render the component. Note that there is a helper `xml` to
|
||||
make it easy to define an inline template.
|
||||
|
||||
* **`components`** (Object, optional): if given, this is an object that contains
|
||||
the classes of any sub components needed by the template. This is the main way
|
||||
used by Owl to be able to create sub components.
|
||||
|
||||
@@ -123,7 +123,7 @@ find a template with the component name (or one of its ancestor).
|
||||
}
|
||||
```
|
||||
|
||||
- **`props`** (Object, optional): if given, this is an object that describes the
|
||||
* **`props`** (Object, optional): if given, this is an object that describes the
|
||||
type and shape of the (actual) props given to the component. If Owl mode is
|
||||
`dev`, this will be used to validate the props each time the component is
|
||||
created/updated. See [Props Validation](#props-validation) for more information.
|
||||
@@ -137,7 +137,7 @@ find a template with the component name (or one of its ancestor).
|
||||
}
|
||||
```
|
||||
|
||||
* **`defaultProps`** (Object, optional): if given, this object define default
|
||||
- **`defaultProps`** (Object, optional): if given, this object define default
|
||||
values for (top-level) props. Whenever `props` are given to the object, they
|
||||
will be altered to add default value (if missing). Note that it does not
|
||||
change the initial object, a new object will be created instead.
|
||||
|
||||
+1
-1
@@ -73,9 +73,9 @@ Let us now add the javascript to make it work, in `app.js`:
|
||||
|
||||
```javascript
|
||||
class ClickCounter extends owl.Component {
|
||||
static template = "clickcounter";
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.template = "clickcounter";
|
||||
this.state = { value: 0 };
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -135,7 +135,7 @@ It's API is quite simple:
|
||||
having a reference to the actual QWeb instance.
|
||||
|
||||
```js
|
||||
QWeb.registerTemplate('mytemplate', `<div>some template`);
|
||||
QWeb.registerTemplate("mytemplate", `<div>some template`);
|
||||
```
|
||||
|
||||
- **`registerComponent(name, Component)`**: static function to register an OWL Component
|
||||
|
||||
@@ -19,6 +19,8 @@ owl
|
||||
store
|
||||
Store
|
||||
ConnectedComponent
|
||||
tags
|
||||
xml
|
||||
utils
|
||||
debounce
|
||||
escape
|
||||
@@ -36,6 +38,7 @@ owl
|
||||
- [QWeb](qweb.md)
|
||||
- [Router](router.md)
|
||||
- [Store](store.md)
|
||||
- [Tags](tags.md)
|
||||
- [Utils](utils.md)
|
||||
- [Virtual DOM](vdom.md)
|
||||
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
# 🦉 Tags 🦉
|
||||
|
||||
Tags are very small helper to make it easy to write inline templates. There is
|
||||
only one currently available tag: `xml`, but we plan to add other tags later,
|
||||
such as a `css` tag, which will be used to write single file components.
|
||||
|
||||
## XML tag
|
||||
|
||||
Without tags, creating a standalone component would look like this:
|
||||
|
||||
```js
|
||||
import { Component } from 'owl'
|
||||
|
||||
const name = 'some-unique-name';
|
||||
const template = `
|
||||
<div>
|
||||
<span t-if="somecondition">text</span>
|
||||
<button t-on-click="someMethod">Click</button>
|
||||
</div>
|
||||
`;
|
||||
QWeb.registerTemplate(name, template);
|
||||
|
||||
class MyComponent extends Component {
|
||||
static template = name;
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
With tags, this process is slightly simplified. The name is uniquely generated,
|
||||
and the template is automatically registered:
|
||||
|
||||
```js
|
||||
import { Component } from 'owl'
|
||||
import { xml } from 'owl/tags'
|
||||
|
||||
class MyComponent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<span t-if="somecondition">text</span>
|
||||
<button t-on-click="someMethod">Click</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
@@ -419,7 +419,7 @@ QWeb.addDirective({
|
||||
|
||||
const clone = <Element>node.cloneNode(true);
|
||||
const slotNodes = clone.querySelectorAll("[t-set]");
|
||||
const slotId = qweb.nextSlotId++;
|
||||
const slotId = QWeb.nextSlotId++;
|
||||
ctx.addLine(`w${componentID}.__owl__.slotId = ${slotId};`);
|
||||
if (slotNodes.length) {
|
||||
for (let i = 0, length = slotNodes.length; i < length; i++) {
|
||||
@@ -428,7 +428,7 @@ QWeb.addDirective({
|
||||
const key = slotNode.getAttribute("t-set")!;
|
||||
slotNode.removeAttribute("t-set");
|
||||
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
|
||||
qweb.slots[`${slotId}_${key}`] = slotFn;
|
||||
QWeb.slots[`${slotId}_${key}`] = slotFn;
|
||||
}
|
||||
}
|
||||
if (clone.childNodes.length) {
|
||||
@@ -437,7 +437,7 @@ QWeb.addDirective({
|
||||
t.appendChild(child);
|
||||
}
|
||||
const slotFn = qweb._compile(`slot_default_template`, t, ctx);
|
||||
qweb.slots[`${slotId}_default`] = slotFn;
|
||||
QWeb.slots[`${slotId}_default`] = slotFn;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -10,6 +10,7 @@ import { QWeb } from "./qweb/index";
|
||||
import { ConnectedComponent } from "./store/connected_component";
|
||||
import { Store } from "./store/store";
|
||||
import * as _utils from "./utils";
|
||||
import * as _tags from "./tags";
|
||||
import { Link } from "./router/Link";
|
||||
import { RouteComponent } from "./router/RouteComponent";
|
||||
import { Router } from "./router/Router";
|
||||
@@ -20,7 +21,7 @@ export const core = { EventBus, Observer };
|
||||
export const router = { Router, RouteComponent, Link };
|
||||
export const store = { Store, ConnectedComponent };
|
||||
export const utils = _utils;
|
||||
|
||||
export const tags = _tags;
|
||||
export const __info__ = {};
|
||||
|
||||
Object.defineProperty(__info__, "mode", {
|
||||
|
||||
@@ -227,7 +227,9 @@ QWeb.addDirective({
|
||||
atNodeEncounter({ ctx, value }): boolean {
|
||||
const slotKey = ctx.generateID();
|
||||
ctx.rootContext.shouldDefineOwner = true;
|
||||
ctx.addLine(`const slot${slotKey} = this.slots[context.__owl__.slotId + '_' + '${value}'];`);
|
||||
ctx.addLine(
|
||||
`const slot${slotKey} = this.constructor.slots[context.__owl__.slotId + '_' + '${value}'];`
|
||||
);
|
||||
ctx.addIf(`slot${slotKey}`);
|
||||
ctx.addLine(
|
||||
`slot${slotKey}.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c${ctx.parentNode}, vars: extra.vars, parent: owner}));`
|
||||
|
||||
+4
-3
@@ -143,15 +143,16 @@ export class QWeb extends EventBus {
|
||||
|
||||
static TEMPLATES: { [name: string]: Template } = {};
|
||||
|
||||
static nextId: number = 1;
|
||||
|
||||
h = h;
|
||||
// dev mode enables better error messages or more costly validations
|
||||
static dev: boolean = false;
|
||||
|
||||
|
||||
// slots contains sub templates defined with t-set inside t-component nodes, and
|
||||
// are meant to be used by the t-slot directive.
|
||||
slots = {};
|
||||
nextSlotId = 1;
|
||||
static slots = {};
|
||||
static nextSlotId = 1;
|
||||
|
||||
// recursiveTemplates contains sub templates called with t-call, but which
|
||||
// ends up in recursive situations. This is very similar to the slot situation,
|
||||
|
||||
+9
-9
@@ -1,18 +1,18 @@
|
||||
import { Component } from "../component/component";
|
||||
import { xml } from "../tags";
|
||||
import { Destination, RouterEnv } from "./Router";
|
||||
|
||||
export const LINK_TEMPLATE_NAME = "__owl__-router-link";
|
||||
export const LINK_TEMPLATE = `
|
||||
<a t-att-class="{'router-link-active': isActive }"
|
||||
t-att-href="href"
|
||||
t-on-click="navigate">
|
||||
<t t-slot="default"/>
|
||||
</a>`;
|
||||
|
||||
type Props = Destination;
|
||||
|
||||
export class Link<Env extends RouterEnv> extends Component<Env, Props, {}> {
|
||||
static template = LINK_TEMPLATE_NAME;
|
||||
static template = xml`
|
||||
<a t-att-class="{'router-link-active': isActive }"
|
||||
t-att-href="href"
|
||||
t-on-click="navigate">
|
||||
<t t-slot="default"/>
|
||||
</a>
|
||||
`;
|
||||
|
||||
href: string = this.env.router.destToPath(this.props);
|
||||
|
||||
async willUpdateProps(nextProps) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Component } from "../component/component";
|
||||
import { xml } from "../tags";
|
||||
|
||||
export const ROUTE_COMPONENT_TEMPLATE_NAME = "__owl__-router-component";
|
||||
export const ROUTE_COMPONENT_TEMPLATE = `
|
||||
export class RouteComponent extends Component<any, {}, {}> {
|
||||
static template = xml`
|
||||
<t t-foreach="routes" t-as="route">
|
||||
<t t-if="env.router.currentRouteName === route.name">
|
||||
<t t-component="{{route.component}}" t-props="env.router.currentParams"/>
|
||||
</t>
|
||||
</t>`;
|
||||
</t>
|
||||
`;
|
||||
|
||||
export class RouteComponent extends Component<any, {}, {}> {
|
||||
static template = ROUTE_COMPONENT_TEMPLATE_NAME;
|
||||
routes: any[] = [];
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Env } from "../component/component";
|
||||
import { QWeb } from "../qweb/index";
|
||||
import { ROUTE_COMPONENT_TEMPLATE, ROUTE_COMPONENT_TEMPLATE_NAME } from "./RouteComponent";
|
||||
import { LINK_TEMPLATE, LINK_TEMPLATE_NAME } from "./Link";
|
||||
import { shallowEqual } from "../utils";
|
||||
|
||||
type NavigationGuard = (info: {
|
||||
@@ -84,10 +82,6 @@ export class Router {
|
||||
this.routes[partialRoute.name] = partialRoute as Route;
|
||||
this.routeIds.push(partialRoute.name);
|
||||
}
|
||||
|
||||
// setup link and directive
|
||||
env.qweb.addTemplate(LINK_TEMPLATE_NAME, LINK_TEMPLATE);
|
||||
env.qweb.addTemplate(ROUTE_COMPONENT_TEMPLATE_NAME, ROUTE_COMPONENT_TEMPLATE);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { QWeb } from "./qweb/index";
|
||||
|
||||
/**
|
||||
* Owl Tags
|
||||
*
|
||||
* We have here a (very) small collection of tag functions:
|
||||
*
|
||||
* - xml
|
||||
*
|
||||
* The plan is to add a few other tags such as css, globalcss.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* XML tag helper for defining templates. With this, one can simply define
|
||||
* an inline template with just the template xml:
|
||||
* ```js
|
||||
* class A extends Component {
|
||||
* static template = xml`<div>some template</div>`;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function xml(strings) {
|
||||
const name = `__template__${QWeb.nextId++}`;
|
||||
QWeb.registerTemplate(name, strings[0]);
|
||||
return name;
|
||||
}
|
||||
@@ -1406,14 +1406,14 @@ exports[`t-slot directive can define and call slots 2`] = `
|
||||
let c2 = [], p2 = {key:2};
|
||||
var vn2 = h('div', p2, c2);
|
||||
c1.push(vn2);
|
||||
const slot3 = this.slots[context.__owl__.slotId + '_' + 'header'];
|
||||
const slot3 = this.constructor.slots[context.__owl__.slotId + '_' + 'header'];
|
||||
if (slot3) {
|
||||
slot3.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c2, vars: extra.vars, parent: owner}));
|
||||
}
|
||||
let c4 = [], p4 = {key:4};
|
||||
var vn4 = h('div', p4, c4);
|
||||
c1.push(vn4);
|
||||
const slot5 = this.slots[context.__owl__.slotId + '_' + 'footer'];
|
||||
const slot5 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer'];
|
||||
if (slot5) {
|
||||
slot5.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c4, vars: extra.vars, parent: owner}));
|
||||
}
|
||||
@@ -1557,7 +1557,7 @@ exports[`t-slot directive slots are rendered with proper context, part 2 1`] = `
|
||||
let c2 = [], p2 = {key:2,attrs:{href: _1}};
|
||||
var vn2 = h('a', p2, c2);
|
||||
result = vn2;
|
||||
const slot3 = this.slots[context.__owl__.slotId + '_' + 'default'];
|
||||
const slot3 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
||||
if (slot3) {
|
||||
slot3.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c2, vars: extra.vars, parent: owner}));
|
||||
}
|
||||
@@ -1661,7 +1661,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 1`] = `
|
||||
let c2 = [], p2 = {key:2,attrs:{href: _1}};
|
||||
var vn2 = h('a', p2, c2);
|
||||
result = vn2;
|
||||
const slot3 = this.slots[context.__owl__.slotId + '_' + 'default'];
|
||||
const slot3 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
||||
if (slot3) {
|
||||
slot3.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c2, vars: extra.vars, parent: owner}));
|
||||
}
|
||||
|
||||
+217
-244
@@ -1,5 +1,6 @@
|
||||
import { Component, Env } from "../../src/component/component";
|
||||
import { QWeb } from "../../src/qweb/qweb";
|
||||
import { xml } from "../../src/tags";
|
||||
import { EventBus } from "../../src/core/event_bus";
|
||||
import {
|
||||
makeDeferred,
|
||||
@@ -119,11 +120,11 @@ describe("basic widget properties", () => {
|
||||
});
|
||||
|
||||
test("widget style and classname", async () => {
|
||||
env.qweb.addTemplate(
|
||||
"StyledWidget",
|
||||
`<div style="font-weight:bold;" class="some-class">world</div>`
|
||||
);
|
||||
class StyledWidget extends Widget {}
|
||||
class StyledWidget extends Widget {
|
||||
static template = xml`
|
||||
<div style="font-weight:bold;" class="some-class">world</div>
|
||||
`;
|
||||
}
|
||||
const widget = new StyledWidget(env);
|
||||
await widget.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe(`<div style="font-weight:bold;" class="some-class">world</div>`);
|
||||
@@ -164,19 +165,17 @@ describe("basic widget properties", () => {
|
||||
test("reconciliation alg is not confused in some specific situation", async () => {
|
||||
// in this test, we set t-key to 4 because it was in conflict with the
|
||||
// template id corresponding to the first child.
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="Parent">
|
||||
<Child />
|
||||
<Child t-key="4"/>
|
||||
</div>
|
||||
<span t-name="Child">child</span>
|
||||
</templates>
|
||||
`);
|
||||
|
||||
class Child extends Component<any, any, any> {}
|
||||
class Child extends Component<any, any, any> {
|
||||
static template = xml`<span>child</span>`;
|
||||
}
|
||||
|
||||
class Parent extends Component<any, any, any> {
|
||||
static template = xml`
|
||||
<div>
|
||||
<Child />
|
||||
<Child t-key="4"/>
|
||||
</div>
|
||||
`;
|
||||
static components = { Child };
|
||||
}
|
||||
|
||||
@@ -226,7 +225,6 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
test("willStart hook is called on subwidget", async () => {
|
||||
let ok = false;
|
||||
env.qweb.addTemplate("ParentWidget", `<div><t t-component="child"/></div>`);
|
||||
class ChildWidget extends Widget {
|
||||
async willStart() {
|
||||
ok = true;
|
||||
@@ -234,6 +232,7 @@ describe("lifecycle hooks", () => {
|
||||
}
|
||||
|
||||
class ParentWidget extends Widget {
|
||||
static template = xml`<div><t t-component="child"/></div>`;
|
||||
static components = { child: ChildWidget };
|
||||
}
|
||||
const widget = new ParentWidget(env);
|
||||
@@ -244,8 +243,6 @@ describe("lifecycle hooks", () => {
|
||||
test("mounted hook is called on subcomponents, in proper order", async () => {
|
||||
const steps: any[] = [];
|
||||
|
||||
env.qweb.addTemplate("ParentWidget", `<div><t t-component="child"/></div>`);
|
||||
|
||||
class ChildWidget extends Widget {
|
||||
mounted() {
|
||||
expect(document.body.contains(this.el)).toBe(true);
|
||||
@@ -254,7 +251,8 @@ describe("lifecycle hooks", () => {
|
||||
}
|
||||
|
||||
class ParentWidget extends Widget {
|
||||
static components = { child: ChildWidget };
|
||||
static template = xml`<div><ChildWidget /></div>`
|
||||
static components = { ChildWidget };
|
||||
mounted() {
|
||||
steps.push("parent:mounted");
|
||||
}
|
||||
@@ -267,12 +265,6 @@ describe("lifecycle hooks", () => {
|
||||
test("mounted hook is called on subsubcomponents, in proper order", async () => {
|
||||
const steps: any[] = [];
|
||||
|
||||
env.qweb.addTemplate(
|
||||
"ParentWidget",
|
||||
`<div><t t-if="state.flag"><t t-component="child"/></t></div>`
|
||||
);
|
||||
env.qweb.addTemplate("ChildWidget", `<div><t t-component="childchild"/></div>`);
|
||||
|
||||
class ChildChildWidget extends Widget {
|
||||
mounted() {
|
||||
steps.push("childchild:mounted");
|
||||
@@ -283,6 +275,7 @@ describe("lifecycle hooks", () => {
|
||||
}
|
||||
|
||||
class ChildWidget extends Widget {
|
||||
static template = xml`<div><t t-component="childchild"/></div>`;
|
||||
static components = { childchild: ChildChildWidget };
|
||||
mounted() {
|
||||
steps.push("child:mounted");
|
||||
@@ -293,6 +286,7 @@ describe("lifecycle hooks", () => {
|
||||
}
|
||||
|
||||
class ParentWidget extends Widget {
|
||||
static template = xml`<div><t t-if="state.flag"><t t-component="child"/></t></div>`;
|
||||
static components = { child: ChildWidget };
|
||||
state = { flag: false };
|
||||
mounted() {
|
||||
@@ -435,12 +429,7 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
test("components are unmounted and destroyed if no longer in DOM", async () => {
|
||||
let steps: string[] = [];
|
||||
env.qweb.addTemplate(
|
||||
"ParentWidget",
|
||||
`<div>
|
||||
<t t-if="state.ok"><t t-component="child"/></t>
|
||||
</div>`
|
||||
);
|
||||
|
||||
class ChildWidget extends Widget {
|
||||
constructor(parent) {
|
||||
super(parent);
|
||||
@@ -457,8 +446,13 @@ describe("lifecycle hooks", () => {
|
||||
}
|
||||
}
|
||||
class ParentWidget extends Widget {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-if="state.ok"><ChildWidget /></t>
|
||||
</div>
|
||||
`;
|
||||
static components = { ChildWidget };
|
||||
state = { ok: true };
|
||||
static components = { child: ChildWidget };
|
||||
}
|
||||
|
||||
const widget = new ParentWidget(env);
|
||||
@@ -471,8 +465,9 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
test("components are unmounted and destroyed if no longer in DOM, even after updateprops", async () => {
|
||||
let childUnmounted = false;
|
||||
env.qweb.addTemplate("ChildWidget", `<span><t t-esc="props.n"/></span>`);
|
||||
|
||||
class ChildWidget extends Widget {
|
||||
static template = xml`<span><t t-esc="props.n"/></span>`;
|
||||
willUnmount() {
|
||||
childUnmounted = true;
|
||||
}
|
||||
@@ -481,16 +476,14 @@ describe("lifecycle hooks", () => {
|
||||
}
|
||||
}
|
||||
|
||||
env.qweb.addTemplate(
|
||||
"ParentWidget",
|
||||
`
|
||||
<div>
|
||||
<div t-if="state.flag">
|
||||
<ChildWidget n="state.n"/>
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
class ParentWidget extends Widget {
|
||||
static template = xml`
|
||||
<div>
|
||||
<div t-if="state.flag">
|
||||
<ChildWidget n="state.n"/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
static components = { ChildWidget };
|
||||
state = { n: 0, flag: true };
|
||||
increment() {
|
||||
@@ -515,7 +508,7 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
test("hooks are called in proper order in widget creation/destruction", async () => {
|
||||
let steps: string[] = [];
|
||||
env.qweb.addTemplate("ParentWidget", `<div><t t-component="child"/></div>`);
|
||||
|
||||
class ChildWidget extends Widget {
|
||||
constructor(parent) {
|
||||
super(parent);
|
||||
@@ -532,6 +525,7 @@ describe("lifecycle hooks", () => {
|
||||
}
|
||||
}
|
||||
class ParentWidget extends Widget {
|
||||
static template = xml`<div><t t-component="child"/></div>`;
|
||||
static components = { child: ChildWidget };
|
||||
constructor(parent) {
|
||||
super(parent);
|
||||
@@ -614,13 +608,13 @@ describe("lifecycle hooks", () => {
|
||||
test("patched hook is called after updateProps", async () => {
|
||||
let n = 0;
|
||||
|
||||
env.qweb.addTemplate("Parent", '<div><Child a="state.a"/></div>');
|
||||
class TestWidget extends Widget {
|
||||
patched() {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
class Parent extends Widget {
|
||||
static template = xml`<div><Child a="state.a"/></div>`;
|
||||
state = { a: 1 };
|
||||
static components = { Child: TestWidget };
|
||||
}
|
||||
@@ -654,17 +648,18 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
test("shouldUpdate hook prevent rerendering", async () => {
|
||||
let shouldUpdate = false;
|
||||
env.qweb.addTemplate("Parent", `<div><Child val="state.val"/></div>`);
|
||||
class TestWidget extends Widget {
|
||||
static template = xml`<div><t t-esc="props.val"/></div>`;
|
||||
shouldUpdate() {
|
||||
return shouldUpdate;
|
||||
}
|
||||
}
|
||||
class Parent extends Widget {
|
||||
state = { val: 42 };
|
||||
static template = xml`<div><Child val="state.val"/></div>`;
|
||||
static components = { Child: TestWidget };
|
||||
state = { val: 42 };
|
||||
}
|
||||
env.qweb.addTemplate("TestWidget", `<div><t t-esc="props.val"/></div>`);
|
||||
|
||||
const widget = new Parent(env);
|
||||
await widget.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><div>42</div></div>");
|
||||
@@ -680,15 +675,6 @@ describe("lifecycle hooks", () => {
|
||||
test("sub widget (inside sub node): hooks are correctly called", async () => {
|
||||
let created = false;
|
||||
let mounted = false;
|
||||
env.qweb.addTemplate(
|
||||
"ParentWidget",
|
||||
`
|
||||
<div>
|
||||
<t t-if="state.flag">
|
||||
<div><t t-component="child"/></div>
|
||||
</t>
|
||||
</div>`
|
||||
);
|
||||
|
||||
class ChildWidget extends Widget {
|
||||
constructor(parent, props) {
|
||||
@@ -700,6 +686,13 @@ describe("lifecycle hooks", () => {
|
||||
}
|
||||
}
|
||||
class ParentWidget extends Widget {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-if="state.flag">
|
||||
<div><t t-component="child"/></div>
|
||||
</t>
|
||||
</div>
|
||||
`;
|
||||
static components = { child: ChildWidget };
|
||||
state = { flag: false };
|
||||
}
|
||||
@@ -716,13 +709,7 @@ describe("lifecycle hooks", () => {
|
||||
|
||||
test("willPatch/patched hook", async () => {
|
||||
const steps: string[] = [];
|
||||
env.qweb.addTemplate(
|
||||
"ParentWidget",
|
||||
`
|
||||
<div>
|
||||
<t t-component="child" v="state.n"/>
|
||||
</div>`
|
||||
);
|
||||
|
||||
class ChildWidget extends Widget {
|
||||
willPatch() {
|
||||
steps.push("child:willPatch");
|
||||
@@ -732,6 +719,11 @@ describe("lifecycle hooks", () => {
|
||||
}
|
||||
}
|
||||
class ParentWidget extends Widget {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-component="child" v="state.n"/>
|
||||
</div>
|
||||
`;
|
||||
static components = { child: ChildWidget };
|
||||
state = { n: 1 };
|
||||
willPatch() {
|
||||
@@ -763,13 +755,6 @@ describe("lifecycle hooks", () => {
|
||||
// we make sure here that willPatch/patched is only called if widget is in
|
||||
// dom, mounted
|
||||
const steps: string[] = [];
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="ParentWidget">
|
||||
<ChildWidget t-if="state.flag" v="state.n" t-keepalive="1"/>
|
||||
</div>
|
||||
</templates>
|
||||
`);
|
||||
|
||||
class ChildWidget extends Widget {
|
||||
willPatch() {
|
||||
@@ -786,6 +771,11 @@ describe("lifecycle hooks", () => {
|
||||
}
|
||||
}
|
||||
class ParentWidget extends Widget {
|
||||
static template = xml`
|
||||
<div>
|
||||
<ChildWidget t-if="state.flag" v="state.n" t-keepalive="1"/>
|
||||
</div>
|
||||
`;
|
||||
static components = { ChildWidget };
|
||||
state = { n: 1, flag: true };
|
||||
}
|
||||
@@ -793,7 +783,7 @@ describe("lifecycle hooks", () => {
|
||||
const widget = new ParentWidget(env);
|
||||
await widget.mount(fixture);
|
||||
|
||||
expect(env.qweb.templates.ParentWidget.fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates[ParentWidget.template].fn.toString()).toMatchSnapshot();
|
||||
expect(steps).toEqual(["child:mounted"]);
|
||||
widget.state.flag = false;
|
||||
await nextTick();
|
||||
@@ -865,21 +855,20 @@ describe("destroy method", () => {
|
||||
});
|
||||
|
||||
test("destroying a widget before being mounted", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="Parent" t-on-some-event="doStuff">
|
||||
<Child />
|
||||
</div>
|
||||
<span t-name="Child">
|
||||
<GrandChild t-if="state.flag" val="something"/>
|
||||
<button t-on-click="doSomething">click</button>
|
||||
</span>
|
||||
<span t-name="GrandChild">
|
||||
<t t-esc="props.val.val"/>
|
||||
</span>
|
||||
</templates>`);
|
||||
class GrandChild extends Component<any, any, any> {}
|
||||
class GrandChild extends Component<any, any, any> {
|
||||
static template = xml`
|
||||
<span>
|
||||
<t t-esc="props.val.val"/>
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
class Child extends Component<any, any, any> {
|
||||
static template = xml`
|
||||
<span>
|
||||
<GrandChild t-if="state.flag" val="something"/>
|
||||
<button t-on-click="doSomething">click</button>
|
||||
</span>
|
||||
`;
|
||||
static components = { GrandChild };
|
||||
state = { val: 33, flag: false };
|
||||
doSomething() {
|
||||
@@ -892,6 +881,11 @@ describe("destroy method", () => {
|
||||
}
|
||||
}
|
||||
class Parent extends Component<any, any, any> {
|
||||
static template = xml`
|
||||
<div t-on-some-event="doStuff">
|
||||
<Child />
|
||||
</div>
|
||||
`;
|
||||
static components = { Child };
|
||||
state = { p: 1 };
|
||||
doStuff() {
|
||||
@@ -985,14 +979,12 @@ describe("composition", () => {
|
||||
|
||||
test("t-refs are bound at proper timing", async () => {
|
||||
expect.assertions(2);
|
||||
env.qweb.addTemplate(
|
||||
"ParentWidget",
|
||||
`
|
||||
class ParentWidget extends Widget {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-foreach="state.list" t-as="elem" t-ref="child" t-key="elem" t-component="Widget"/>
|
||||
</div>`
|
||||
);
|
||||
class ParentWidget extends Widget {
|
||||
</div>
|
||||
`;
|
||||
static components = { Widget };
|
||||
state = { list: <any>[] };
|
||||
willPatch() {
|
||||
@@ -3055,8 +3047,8 @@ describe("t-slot directive", () => {
|
||||
);
|
||||
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates.Dialog.fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.slots["1_header"].toString()).toMatchSnapshot();
|
||||
expect(env.qweb.slots["1_footer"].toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_header"].toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("slots are rendered with proper context", async () => {
|
||||
@@ -3092,7 +3084,7 @@ describe("t-slot directive", () => {
|
||||
expect(fixture.innerHTML).toBe(
|
||||
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
|
||||
);
|
||||
expect(env.qweb.slots["1_footer"].toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("slots are rendered with proper context, part 2", async () => {
|
||||
@@ -3130,7 +3122,7 @@ describe("t-slot directive", () => {
|
||||
expect(fixture.innerHTML).toBe(
|
||||
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User Mathieu</a></li></u></div>'
|
||||
);
|
||||
expect(env.qweb.slots["1_default"].toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("slots are rendered with proper context, part 3", async () => {
|
||||
@@ -3169,7 +3161,7 @@ describe("t-slot directive", () => {
|
||||
expect(fixture.innerHTML).toBe(
|
||||
'<div><u><li><a href="/user/1">User Aaron</a></li><li><a href="/user/2">User Mathieu</a></li></u></div>'
|
||||
);
|
||||
expect(env.qweb.slots["1_default"].toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("slots are rendered with proper context, part 4", async () => {
|
||||
@@ -3202,7 +3194,7 @@ describe("t-slot directive", () => {
|
||||
app.state.user.name = "David";
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe('<div><a href="/user/1">User David</a></div>');
|
||||
expect(env.qweb.slots["1_default"].toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("refs are properly bound in slots", async () => {
|
||||
@@ -3238,7 +3230,7 @@ describe("t-slot directive", () => {
|
||||
expect(fixture.innerHTML).toBe(
|
||||
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
|
||||
);
|
||||
expect(env.qweb.slots["1_footer"].toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("content is the default slot", async () => {
|
||||
@@ -3260,7 +3252,7 @@ describe("t-slot directive", () => {
|
||||
await parent.mount(fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div><div><span>sts rocks</span></div></div>");
|
||||
expect(env.qweb.slots["1_default"].toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("default slot work with text nodes", async () => {
|
||||
@@ -3280,7 +3272,7 @@ describe("t-slot directive", () => {
|
||||
await parent.mount(fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div><div>sts rocks</div></div>");
|
||||
expect(env.qweb.slots["1_default"].toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("multiple roots are allowed in a named slot", async () => {
|
||||
@@ -3305,7 +3297,7 @@ describe("t-slot directive", () => {
|
||||
await parent.mount(fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div><div><span>sts</span><span>rocks</span></div></div>");
|
||||
expect(env.qweb.slots["1_content"].toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_content"].toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("multiple roots are allowed in a default slot", async () => {
|
||||
@@ -3328,7 +3320,7 @@ describe("t-slot directive", () => {
|
||||
await parent.mount(fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div><div><span>sts</span><span>rocks</span></div></div>");
|
||||
expect(env.qweb.slots["1_default"].toString()).toMatchSnapshot();
|
||||
expect(QWeb.slots["1_default"].toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("missing slots are ignored", async () => {
|
||||
@@ -3599,17 +3591,16 @@ describe("t-model directive", () => {
|
||||
});
|
||||
|
||||
test("on a select, initial state", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="SomeComponent">
|
||||
<select t-model="color">
|
||||
<option value="">Please select one</option>
|
||||
<option value="red">Red</option>
|
||||
<option value="blue">Blue</option>
|
||||
</select>
|
||||
</div>
|
||||
</templates>`);
|
||||
class SomeComponent extends Widget {
|
||||
static template = xml`
|
||||
<div>
|
||||
<select t-model="color">
|
||||
<option value="">Please select one</option>
|
||||
<option value="red">Red</option>
|
||||
<option value="blue">Blue</option>
|
||||
</select>
|
||||
</div>
|
||||
`;
|
||||
state = { color: "red" };
|
||||
}
|
||||
const comp = new SomeComponent(env);
|
||||
@@ -3619,15 +3610,13 @@ describe("t-model directive", () => {
|
||||
});
|
||||
|
||||
test("on a sub state key", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="SomeComponent">
|
||||
<input t-model="something.text"/>
|
||||
<span><t t-esc="state.something.text"/></span>
|
||||
</div>
|
||||
</templates>`);
|
||||
|
||||
class SomeComponent extends Widget {
|
||||
static template = xml`
|
||||
<div>
|
||||
<input t-model="something.text"/>
|
||||
<span><t t-esc="state.something.text"/></span>
|
||||
</div>
|
||||
`;
|
||||
state = { something: { text: "" } };
|
||||
}
|
||||
const comp = new SomeComponent(env);
|
||||
@@ -3639,18 +3628,17 @@ describe("t-model directive", () => {
|
||||
await editInput(input, "test");
|
||||
expect(comp.state.something.text).toBe("test");
|
||||
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
||||
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test(".lazy modifier", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="SomeComponent">
|
||||
class SomeComponent extends Widget {
|
||||
static template = xml`
|
||||
<div>
|
||||
<input t-model.lazy="text"/>
|
||||
<span><t t-esc="state.text"/></span>
|
||||
</div>
|
||||
</templates>`);
|
||||
class SomeComponent extends Widget {
|
||||
`;
|
||||
state = { text: "" };
|
||||
}
|
||||
const comp = new SomeComponent(env);
|
||||
@@ -3668,18 +3656,17 @@ describe("t-model directive", () => {
|
||||
await nextTick();
|
||||
expect(comp.state.text).toBe("test");
|
||||
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
||||
expect(env.qweb.templates.SomeComponent.fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test(".trim modifier", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="SomeComponent">
|
||||
<input t-model.trim="text"/>
|
||||
<span><t t-esc="state.text"/></span>
|
||||
</div>
|
||||
</templates>`);
|
||||
class SomeComponent extends Widget {
|
||||
static template = xml`
|
||||
<div t-name="SomeComponent">
|
||||
<input t-model.trim="text"/>
|
||||
<span><t t-esc="state.text"/></span>
|
||||
</div>
|
||||
`;
|
||||
state = { text: "" };
|
||||
}
|
||||
const comp = new SomeComponent(env);
|
||||
@@ -3692,14 +3679,13 @@ describe("t-model directive", () => {
|
||||
});
|
||||
|
||||
test(".number modifier", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="SomeComponent">
|
||||
<input t-model.number="number"/>
|
||||
<span><t t-esc="state.number"/></span>
|
||||
</div>
|
||||
</templates>`);
|
||||
class SomeComponent extends Widget {
|
||||
static template = xml`
|
||||
<div>
|
||||
<input t-model.number="number"/>
|
||||
<span><t t-esc="state.number"/></span>
|
||||
</div>
|
||||
`;
|
||||
state = { number: 0 };
|
||||
}
|
||||
const comp = new SomeComponent(env);
|
||||
@@ -3732,15 +3718,14 @@ describe("environment and plugins", () => {
|
||||
|
||||
test("plugin works as expected", async () => {
|
||||
somePlugin(env);
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="App">
|
||||
class App extends Widget {
|
||||
static template=xml`
|
||||
<div>
|
||||
<t t-if="env.someFlag">Red</t>
|
||||
<t t-else="1">Blue</t>
|
||||
</div>
|
||||
</templates>
|
||||
`);
|
||||
class App extends Widget {}
|
||||
`;
|
||||
}
|
||||
|
||||
const app = new App(env);
|
||||
await app.mount(fixture);
|
||||
@@ -4086,18 +4071,19 @@ describe("top level sub widgets", () => {
|
||||
});
|
||||
|
||||
test("can select a sub widget ", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<t t-name="Parent">
|
||||
<t t-if="env.flag"><Child /></t>
|
||||
<t t-if="!env.flag"><OtherChild /></t>
|
||||
</t>
|
||||
<span t-name="Child">CHILD 1</span>
|
||||
<div t-name="OtherChild">CHILD 2</div>
|
||||
</templates>`);
|
||||
class Child extends Widget {}
|
||||
class OtherChild extends Widget {}
|
||||
class Child extends Widget {
|
||||
static template = xml`<span>CHILD 1</span>`;
|
||||
}
|
||||
class OtherChild extends Widget {
|
||||
static template = xml`<div>CHILD 2</div>`;
|
||||
}
|
||||
class Parent extends Widget {
|
||||
static template = xml`
|
||||
<t>
|
||||
<t t-if="env.flag"><Child /></t>
|
||||
<t t-if="!env.flag"><OtherChild /></t>
|
||||
</t>
|
||||
`;
|
||||
static components = { Child, OtherChild };
|
||||
}
|
||||
(<any>env).flag = true;
|
||||
@@ -4110,22 +4096,23 @@ describe("top level sub widgets", () => {
|
||||
await parent.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>CHILD 2</div>");
|
||||
|
||||
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("can select a sub widget, part 2", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<t t-name="Parent">
|
||||
<t t-if="state.flag"><Child /></t>
|
||||
<t t-if="!state.flag"><OtherChild /></t>
|
||||
</t>
|
||||
<span t-name="Child">CHILD 1</span>
|
||||
<div t-name="OtherChild">CHILD 2</div>
|
||||
</templates>`);
|
||||
class Child extends Widget {}
|
||||
class OtherChild extends Widget {}
|
||||
class Child extends Widget {
|
||||
static template = xml`<span>CHILD 1</span>`;
|
||||
}
|
||||
class OtherChild extends Widget {
|
||||
static template = xml`<div>CHILD 2</div>`;
|
||||
}
|
||||
class Parent extends Widget {
|
||||
static template = xml`
|
||||
<t>
|
||||
<t t-if="state.flag"><Child /></t>
|
||||
<t t-if="!state.flag"><OtherChild /></t>
|
||||
</t>
|
||||
`;
|
||||
state = { flag: true };
|
||||
static components = { Child, OtherChild };
|
||||
}
|
||||
@@ -4140,13 +4127,9 @@ describe("top level sub widgets", () => {
|
||||
|
||||
describe("unmounting and remounting", () => {
|
||||
test("widget can be unmounted and remounted", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="MyWidget">Hey</div>
|
||||
</templates>`);
|
||||
|
||||
const steps: string[] = [];
|
||||
class MyWidget extends Widget {
|
||||
static template = xml`<div>Hey</div>`;
|
||||
async willStart() {
|
||||
steps.push("willstart");
|
||||
}
|
||||
@@ -4173,13 +4156,9 @@ describe("unmounting and remounting", () => {
|
||||
});
|
||||
|
||||
test("widget can be mounted twice without ill effect", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="MyWidget">Hey</div>
|
||||
</templates>`);
|
||||
|
||||
const steps: string[] = [];
|
||||
class MyWidget extends Widget {
|
||||
static template = xml`<div>Hey</div>`;
|
||||
async willStart() {
|
||||
steps.push("willstart");
|
||||
}
|
||||
@@ -4200,16 +4179,11 @@ describe("unmounting and remounting", () => {
|
||||
|
||||
test("state changes in willUnmount do not trigger rerender", async () => {
|
||||
const steps: string[] = [];
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="Parent">
|
||||
<Child t-if="state.flag" val="state.val"/>
|
||||
</div>
|
||||
<span t-name="Child"><t t-esc="props.val"/><t t-esc="state.n"/></span>
|
||||
</templates>
|
||||
`);
|
||||
|
||||
class Child extends Widget {
|
||||
static template = xml`
|
||||
<span><t t-esc="props.val"/><t t-esc="state.n"/></span>
|
||||
`;
|
||||
state = { n: 2 };
|
||||
__render(a, b, c, d) {
|
||||
steps.push("render");
|
||||
@@ -4228,6 +4202,11 @@ describe("unmounting and remounting", () => {
|
||||
}
|
||||
}
|
||||
class Parent extends Widget {
|
||||
static template = xml`
|
||||
<div>
|
||||
<Child t-if="state.flag" val="state.val"/>
|
||||
</div>
|
||||
`;
|
||||
static components = { Child };
|
||||
state = { val: 1, flag: true };
|
||||
}
|
||||
@@ -4243,13 +4222,10 @@ describe("unmounting and remounting", () => {
|
||||
});
|
||||
|
||||
test("state changes in willUnmount will be applied on remount", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="TestWidget"><t t-esc="state.val"/></div>
|
||||
</templates>
|
||||
`);
|
||||
|
||||
class TestWidget extends Widget {
|
||||
static template = xml`
|
||||
<div><t t-esc="state.val"/></div>
|
||||
`;
|
||||
state = { val: 1 };
|
||||
willUnmount() {
|
||||
this.state.val = 3;
|
||||
@@ -4271,15 +4247,14 @@ describe("unmounting and remounting", () => {
|
||||
|
||||
describe("dynamic root nodes", () => {
|
||||
test("template with t-if, part 1", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<t t-name="TestWidget">
|
||||
<t t-if="true"><span>hey</span></t>
|
||||
<t t-if="false"><div>abc</div></t>
|
||||
</t>
|
||||
</templates>
|
||||
`);
|
||||
class TestWidget extends Widget {}
|
||||
class TestWidget extends Widget {
|
||||
static template = xml`
|
||||
<t>
|
||||
<t t-if="true"><span>hey</span></t>
|
||||
<t t-if="false"><div>abc</div></t>
|
||||
</t>
|
||||
`;
|
||||
}
|
||||
|
||||
const widget = new TestWidget(env);
|
||||
await widget.mount(fixture);
|
||||
@@ -4288,15 +4263,14 @@ describe("dynamic root nodes", () => {
|
||||
});
|
||||
|
||||
test("template with t-if, part 2", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<t t-name="TestWidget">
|
||||
<t t-if="false"><span>hey</span></t>
|
||||
<t t-if="true"><div>abc</div></t>
|
||||
</t>
|
||||
</templates>
|
||||
`);
|
||||
class TestWidget extends Widget {}
|
||||
class TestWidget extends Widget {
|
||||
static template = xml`
|
||||
<t>
|
||||
<t t-if="false"><span>hey</span></t>
|
||||
<t t-if="true"><div>abc</div></t>
|
||||
</t>
|
||||
`;
|
||||
}
|
||||
|
||||
const widget = new TestWidget(env);
|
||||
await widget.mount(fixture);
|
||||
@@ -4305,15 +4279,13 @@ describe("dynamic root nodes", () => {
|
||||
});
|
||||
|
||||
test("switching between sub branches dynamically", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<t t-name="TestWidget">
|
||||
<t t-if="state.flag"><span>hey</span></t>
|
||||
<t t-if="!state.flag"><div>abc</div></t>
|
||||
</t>
|
||||
</templates>
|
||||
`);
|
||||
class TestWidget extends Widget {
|
||||
static template = xml`
|
||||
<t>
|
||||
<t t-if="state.flag"><span>hey</span></t>
|
||||
<t t-if="!state.flag"><div>abc</div></t>
|
||||
</t>
|
||||
`;
|
||||
state = { flag: true };
|
||||
}
|
||||
|
||||
@@ -4328,19 +4300,19 @@ describe("dynamic root nodes", () => {
|
||||
});
|
||||
|
||||
test("switching between sub components dynamically", async () => {
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<t t-name="ChildA"><span>hey</span></t>
|
||||
<t t-name="ChildB"><div>abc</div></t>
|
||||
<t t-name="TestWidget">
|
||||
<t t-if="state.flag"><ChildA/></t>
|
||||
<t t-if="!state.flag"><ChildB/></t>
|
||||
</t>
|
||||
</templates>
|
||||
`);
|
||||
class ChildA extends Widget {}
|
||||
class ChildB extends Widget {}
|
||||
class ChildA extends Widget {
|
||||
static template = xml`<span>hey</span>`;
|
||||
}
|
||||
class ChildB extends Widget {
|
||||
static template = xml`<div>abc</div>`;
|
||||
}
|
||||
class TestWidget extends Widget {
|
||||
static template = xml`
|
||||
<t>
|
||||
<t t-if="state.flag"><ChildA/></t>
|
||||
<t t-if="!state.flag"><ChildB/></t>
|
||||
</t>
|
||||
`;
|
||||
static components = { ChildA, ChildB };
|
||||
state = { flag: true };
|
||||
}
|
||||
@@ -4359,17 +4331,13 @@ describe("dynamic root nodes", () => {
|
||||
describe("dynamic t-props", () => {
|
||||
test("basic use", async () => {
|
||||
expect.assertions(4);
|
||||
env.qweb.addTemplates(`
|
||||
<templates>
|
||||
<span t-name="Child">
|
||||
<t t-esc="props.a + props.b"/>
|
||||
</span>
|
||||
<div t-name="Parent">
|
||||
<Child t-props="some.obj"/>
|
||||
</div>
|
||||
</templates>
|
||||
`);
|
||||
|
||||
class Child extends Widget {
|
||||
static template = xml`
|
||||
<span>
|
||||
<t t-esc="props.a + props.b"/>
|
||||
</span>
|
||||
`;
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
expect(props).toEqual({ a: 1, b: 2 });
|
||||
@@ -4377,6 +4345,11 @@ describe("dynamic t-props", () => {
|
||||
}
|
||||
}
|
||||
class Parent extends Widget {
|
||||
static template = xml`
|
||||
<div>
|
||||
<Child t-props="some.obj"/>
|
||||
</div>
|
||||
`;
|
||||
static components = { Child };
|
||||
|
||||
some = { obj: { a: 1, b: 2 } };
|
||||
@@ -4386,6 +4359,6 @@ describe("dynamic t-props", () => {
|
||||
await widget.mount(fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div><span>3</span></div>");
|
||||
expect(env.qweb.templates.Parent.fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
+22
-1
@@ -5,6 +5,27 @@ import "../src/qweb/base_directives";
|
||||
import "../src/qweb/extensions";
|
||||
import "../src/component/directive";
|
||||
|
||||
// Some static cleanup
|
||||
let nextSlotId;
|
||||
let slots;
|
||||
let nextId;
|
||||
let TEMPLATES;
|
||||
|
||||
beforeEach(() => {
|
||||
nextSlotId = QWeb.nextSlotId;
|
||||
slots = Object.assign({}, QWeb.slots);
|
||||
nextId = QWeb.nextId;
|
||||
TEMPLATES = Object.assign({}, QWeb.TEMPLATES);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
QWeb.nextSlotId = nextSlotId;
|
||||
QWeb.slots = slots;
|
||||
QWeb.nextId = nextId;
|
||||
QWeb.TEMPLATES = TEMPLATES;
|
||||
});
|
||||
|
||||
// helpers
|
||||
export function nextMicroTick(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -82,7 +103,7 @@ export function renderToString(qweb: QWeb, t: string, context: EvalContext = {})
|
||||
const node = renderToDOM(qweb, t, context);
|
||||
const result = node instanceof Text ? node.textContent! : node.outerHTML;
|
||||
if (result !== qweb.renderToString(t, context)) {
|
||||
throw new Error("HTML string returned by renderToString helper does not match QWeb render");
|
||||
throw new Error("HTML string returned by renderToString helper does not match QWeb render");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component } from "../../src/component/component";
|
||||
import { Link, LINK_TEMPLATE_NAME } from "../../src/router/Link";
|
||||
import { Link } from "../../src/router/Link";
|
||||
import { RouterEnv } from "../../src/router/Router";
|
||||
import { makeTestEnv, makeTestFixture, nextTick } from "../helpers";
|
||||
import { TestRouter } from "./TestRouter";
|
||||
@@ -50,7 +50,7 @@ describe("Link component", () => {
|
||||
'<div><a href="/about" class="router-link-active">About</a></div>'
|
||||
);
|
||||
|
||||
expect(env.qweb.templates[LINK_TEMPLATE_NAME].fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates[Link.template].fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("do not redirect if right clicking", async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component } from "../../src/component/component";
|
||||
import { RouterEnv } from "../../src/router/Router";
|
||||
import { RouteComponent, ROUTE_COMPONENT_TEMPLATE_NAME } from "../../src/router/RouteComponent";
|
||||
import { RouteComponent } from "../../src/router/RouteComponent";
|
||||
import { makeTestEnv, makeTestFixture, nextTick } from "../helpers";
|
||||
import { TestRouter } from "./TestRouter";
|
||||
|
||||
@@ -52,7 +52,7 @@ describe("RouteComponent", () => {
|
||||
await router.navigate({ to: "users" });
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><span>Users</span></div>");
|
||||
expect(env.qweb.templates[ROUTE_COMPONENT_TEMPLATE_NAME].fn.toString()).toMatchSnapshot();
|
||||
expect(env.qweb.templates[RouteComponent.template].fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("can render parameterized route", async () => {
|
||||
|
||||
@@ -12,11 +12,11 @@ exports[`Link component can render simple cases 1`] = `
|
||||
var vn3 = h('a', p3, c3);
|
||||
result = vn3;
|
||||
if (!context['navigate']) {
|
||||
throw new Error('Missing handler \\\\'' + 'navigate' + \`\\\\' when evaluating template '__owl__-router-link'\`)
|
||||
throw new Error('Missing handler \\\\'' + 'navigate' + \`\\\\' when evaluating template '__template__1'\`)
|
||||
}
|
||||
extra.handlers['click' + 3] = extra.handlers['click' + 3] || context['navigate'].bind(owner);
|
||||
p3.on['click'] = extra.handlers['click' + 3];
|
||||
const slot4 = this.slots[context.__owl__.slotId + '_' + 'default'];
|
||||
const slot4 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
||||
if (slot4) {
|
||||
slot4.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c3, vars: extra.vars, parent: owner}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user