Compare commits

...

24 Commits

Author SHA1 Message Date
Géry Debongnie c5a2f52afb [REL] v1.0.9
# v1.0.9

- qweb fix: add support for svg namespace in t-raw
- qweb fix: properly handle subtemplates in shared templates
2020-06-02 10:38:32 +02:00
Michael Mattiello (mcm) 9fd8315c53 [FIX] qweb: support of svg in t-raw
Before this commit, t-raw a string containing `<svg>` did not display
the svg element. This was because of missing namespace in vnode's data.

Now, `vdom.addNS` is called to add the svg namespace when necessary.
2020-05-25 14:02:34 +02:00
Géry Debongnie 85318b3ae6 [FIX] qweb: properly handle subtemplates in shared templates
With QWeb, we can register globally templates (using the xml tag or
the registerTemplate function). However, these templates, once
compiled, can generate sub template compiled functions. Before this
commit, these sub functions were local to a specific instance.

This means that creating a new QWeb instance and rendering a global
parent template would crash, since it was unable to find the actual sub
function.

This commit fixes the issue: the sub functions are now shared
statically, but with a unique ID, so we do not have issues with sub
functions having a same name in different QWeb instance.

closes #701
2020-05-25 11:43:24 +02:00
Géry Debongnie 4b961cbffe [REL] v1.0.8
# v1.0.8

- qweb fix: do not override t-att-class with class attribute in some cases
- component fix: refuse to mount a destroyed component
- hooks type improvement: useRef is now generic, and can capture the type of a component
- qweb fix: t-esc inside t-call using outside t-set
2020-05-18 09:07:31 +02:00
Aaron Bohy 2af8cccd65 [FIX] qweb: t-esc inside t-call using outside t-set
Before this commit, the generated code was incorrect, and crashed,
when there was a t-esc="abc" in a subtemplate, with t-set="abc"
done outside the subtemplate, with syntax <t t-set="abc">value</t>.
2020-05-15 13:47:57 +02:00
Géry Debongnie b8d3618afa [IMP] hooks: improve useRef typings
With this commit, we can get the type for the target component for a
ref.

By default, if the generic type is not given, the ref will simply use
the base Component type.
2020-04-30 16:23:36 +02:00
Géry Debongnie 559fadb62a [FIX] component: properly handle mounting a destroyed component
part of #685
2020-04-22 09:29:39 +02:00
Géry Debongnie c36333dbbc [CLEANUP] update prettier to v2.0.4 2020-04-21 16:23:31 +02:00
Géry Debongnie b5c3422b4d [FIX] qweb: do not override t-att-class with class
There was some code in qweb to make sure that we support setting class
and t-att-class on the same html element:

<div class="some class" t-att-class="{b: true}">...</div>

But the code did not work in the other direction:

<div t-att-class="{b: true}" class="some class">...</div>

With this commit, we just add the missing if statement

closes #664
2020-04-21 16:23:31 +02:00
Géry Debongnie 23ce19e57a [REL] v1.0.7 2020-04-17 15:52:52 +02:00
Géry Debongnie aaf40e9bec [IMP] tooling: add iife build to npm package
This is useful to link to the iife version in some other projects.
2020-04-17 15:47:20 +02:00
Géry Debongnie 211f6ebdd6 [REL] v1.0.6 2020-04-17 15:16:24 +02:00
Géry Debongnie 1707bd240d [IMP] add browser bindings to standard environment
This could be done by each application, but it does cost only a few
lines of code, and it helps standardizing the Owl ecosystem.

For example, some library (such as o_spreadsheet) needs to mock side
effects, and Odoo also needs to do that, so this prevents duplicated effort.

closes #686
2020-04-17 13:39:46 +02:00
Géry Debongnie 142b69823f [IMP] types: do not make Env an indexed type
Before this commit, Env was an indexed type, this means that one could
write env.anything, and it would accept it as a valid type. This is
actually quite dangerous, because we lose the typing advantages for all
keys that are properly defined.

For example, if a component is defined as:

class MyComponent extends Component<Props> {
 ...
}

Then Typescript will let it use anything from the environment, even if
it is wrong.  So, most properly typed Typescript applications should use
instead a sub environment:

interface MyAppEnv extends Env {
  someKey: someValue
}

Then, the component should be defined this way:

class MyComponent extends Component<Props, MyAppEnv> {
 ...
}

Before this commit, any typos in the environment accesses would not be
noticed by typescript.
2020-04-17 13:39:46 +02:00
Joseph Caburnay 94c8bce810 [IMP] allow anonymous Component extensions
It is possible that a Component is extended dynamically and if this is
the case, the class that extends it can be anonymous, with property
name=''. If this is the case, current implementation interprets the empty
string to be false so the while loop is terminated without further
scanning the super classes.

In this proposal, we allow anonymous class to be scanned until its
Component ancestor. Basically, the anonymous class assumes the name of
it super.
2020-04-15 08:54:02 +02:00
Géry Debongnie ae172d42e7 [IMP] slots: add new t-set-slot directive
This new t-set-slot directive is meant to replace t-set when we need to
define the content of a sub slot. All new code should use that
directive.

The old t-set directive is still supported for now, but this should be
removed when we publish Owl 2.0.
2020-04-06 15:42:32 +02:00
Géry Debongnie ddf30a8a97 [CLEANUP] run prettier on the codebase 2020-04-06 15:42:32 +02:00
Géry Debongnie 8d0d8538ad [FIX] slots: better heuristic to determine named slot content
Unfortunately, we chose to use the directive `t-set` to define sub slot
contents in a template.

The goal was to reuse a directive for a similar use case: defining sub
template is almost the same as defining a slot content.

Obviously, this introduces a name conflict: the inner content of a
component cannot use t-set t-value anymore (nor t-set with a body
value), since they are interpreted as slot names.

We mitigate the issue here by only interpreting as slot content the
`t-set` statement located immediately below the parent component tag
name and with a body content.

However, a real fix need to introduce an additional directive to resolve
the ambiguity.
2020-04-06 15:42:32 +02:00
Joseph Caburnay 2b0315c03f [FIX] hooks: order of brackets for multiple async hooks 2020-03-30 08:46:16 +02:00
lucas c469cac315 [FIX] qweb: protect scope in t-call writing recursively
Have something like
```xml
 <div t-name="Parent">
   <t t-call="nodeTemplate">
     <t t-set="recursive_idx" t-value="1"/>
     <t t-set="node" t-value="root"/>
   </t>
 </div>

 <div t-name="nodeTemplate">
   <t t-set="recursive_idx" t-value="recursive_idx + 1"/>
   <p><t t-esc="node.val"/> <t t-esc="recursive_idx"/></p>
   <t t-foreach="node.children or []" t-as="subtree">
     <t t-call="nodeTemplate">
       <t t-set="node" t-value="subtree"/>
     </t>
   </t>
 </div>
```

Where we want to propagate a recursion index through recursive t-calls

Before this commit, it did not work as we protected the scope in order
to not leak, in the wronf manner. Namely the protected scope only took
firt level prototype properties of the original scope.

After this commit, this case works as we mark the scope as read only

solves #672
2020-03-12 13:15:28 +01:00
Lucas Perais (lpe) b4ad14edc0 [FIX] component: self mounting position keeps the reference
Have

```xml
<body t-name="webclient" />
```
and
```js

const comp = new WebClient();
comp.mount(document.body, {position: 'self'});
```

Before this commit, the body that was there before anything had happened
was *replaced* by the new body node created by the WebClient OWL component

After this commit, we ensure that the element body is the same at reference level
2020-03-05 16:35:33 +01:00
Aaron Bohy 718e5264ae [FIX] test: missing await 2020-03-03 10:42:22 +01:00
Aaron Bohy f5d019bb69 [FIX] component: concurrency issue with cancelled fiber
Since ee956a197, the rendering is skipped if the currentFiber is
completed. Unfortunately, cancelled fibers remain set in __owl__,
so when a fiber is cancelled, subsequent calls to render are
skipped.

It would be nice to reset __owl__.currentFiber to null when the
fiber is cancelled, but when trying to do so, a lot of tests fail.

Part of issue #622
Closes #665
2020-03-02 11:35:47 +01:00
Géry Debongnie afa36f52a0 [DOC] doc: fix t-debug example
also, remove a useless console.log

closes #661
2020-02-24 11:05:12 +01:00
73 changed files with 1443 additions and 852 deletions
+2 -2
View File
@@ -121,8 +121,8 @@ npm install @odoo/owl
If you want to use a simple `<script>` tag, the last release can be downloaded here:
- [owl-1.0.5.js](https://github.com/odoo/owl/releases/download/v1.0.5/owl.js)
- [owl-1.0.5.min.js](https://github.com/odoo/owl/releases/download/v1.0.5/owl.min.js)
- [owl-1.0.9.js](https://github.com/odoo/owl/releases/download/v1.0.9/owl.js)
- [owl-1.0.9.min.js](https://github.com/odoo/owl/releases/download/v1.0.9/owl.min.js)
## License
+1 -1
View File
@@ -111,7 +111,7 @@ class Parent extends Component {
static components = { OrderLine };
orders = useState([
{ id: 1, name: "Coffee", quantity: 0 },
{ id: 2, name: "Tea", quantity: 0 }
{ id: 2, name: "Tea", quantity: 0 },
]);
addToOrder(event) {
+11 -11
View File
@@ -281,7 +281,7 @@ import { Component } from "@odoo/owl";
import "regenerator-runtime/runtime";
export async function nextTick() {
return new Promise(function(resolve) {
return new Promise(function (resolve) {
setTimeout(() => Component.scheduler.requestAnimationFrame(() => resolve()));
});
}
@@ -361,26 +361,26 @@ const HtmlWebpackPlugin = require("html-webpack-plugin");
const host = process.env.HOST || "localhost";
module.exports = function(env, argv) {
module.exports = function (env, argv) {
const mode = argv.mode || "development";
return {
mode: mode,
entry: "./src/main.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist")
path: path.resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: "babel-loader",
exclude: /node_modules/
}
]
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [".js", ".jsx"]
extensions: [".js", ".jsx"],
},
devServer: {
contentBase: path.resolve(__dirname, "public/index.html"),
@@ -388,14 +388,14 @@ module.exports = function(env, argv) {
hot: true,
host,
port: 3000,
publicPath: "/"
publicPath: "/",
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: path.resolve(__dirname, "public/index.html")
})
]
template: path.resolve(__dirname, "public/index.html"),
}),
],
};
};
```
+19 -19
View File
@@ -62,7 +62,7 @@ our application. `app.js` is where we will write all our code. For now, let's
just put the following code:
```js
(function() {
(function () {
console.log("hello owl", owl.__info__.version);
})();
```
@@ -170,13 +170,13 @@ class App extends Component {
{
id: 1,
title: "buy milk",
isCompleted: true
isCompleted: true,
},
{
id: 2,
title: "clean house",
isCompleted: false
}
isCompleted: false,
},
];
}
```
@@ -562,23 +562,23 @@ const actions = {
const task = {
id: state.nextId++,
title: title,
isCompleted: false
isCompleted: false,
};
state.tasks.push(task);
}
},
toggleTask({ state }, id) {
const task = state.tasks.find(t => t.id === id);
const task = state.tasks.find((t) => t.id === id);
task.isCompleted = !task.isCompleted;
},
deleteTask({ state }, id) {
const index = state.tasks.findIndex(t => t.id === id);
const index = state.tasks.findIndex((t) => t.id === id);
state.tasks.splice(index, 1);
}
},
};
const initialState = {
nextId: 1,
tasks: []
tasks: [],
};
// -------------------------------------------------------------------------
@@ -616,7 +616,7 @@ class App extends Component {
static components = { Task };
inputRef = useRef("add-input");
tasks = useStore(state => state.tasks);
tasks = useStore((state) => state.tasks);
dispatch = useDispatch();
mounted() {
@@ -811,7 +811,7 @@ For reference, here is the final code:
```
```js
(function() {
(function () {
const { Component, Store } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
@@ -827,24 +827,24 @@ For reference, here is the final code:
const task = {
id: state.nextId++,
title: title,
isCompleted: false
isCompleted: false,
};
state.tasks.push(task);
}
},
toggleTask({ state }, id) {
const task = state.tasks.find(t => t.id === id);
const task = state.tasks.find((t) => t.id === id);
task.isCompleted = !task.isCompleted;
},
deleteTask({ state }, id) {
const index = state.tasks.findIndex(t => t.id === id);
const index = state.tasks.findIndex((t) => t.id === id);
state.tasks.splice(index, 1);
}
},
};
const initialState = {
nextId: 1,
tasks: []
tasks: [],
};
// -------------------------------------------------------------------------
@@ -897,7 +897,7 @@ For reference, here is the final code:
static components = { Task };
inputRef = useRef("add-input");
tasks = useStore(state => state.tasks);
tasks = useStore((state) => state.tasks);
filter = useState({ value: "all" });
dispatch = useDispatch();
@@ -916,9 +916,9 @@ For reference, here is the final code:
get displayedTasks() {
switch (this.filter.value) {
case "active":
return this.tasks.filter(t => !t.isCompleted);
return this.tasks.filter((t) => !t.isCompleted);
case "completed":
return this.tasks.filter(t => t.isCompleted);
return this.tasks.filter((t) => t.isCompleted);
case "all":
return this.tasks;
}
+3 -3
View File
@@ -257,11 +257,11 @@ to the store like in redux, with the `useStore` hook (see the [store documentati
const actions = {
increment({ state }, val) {
state.counter.value += val;
}
},
};
const state = {
counter: { value: 0 }
counter: { value: 0 },
};
const store = new owl.Store({ state, actions });
@@ -270,7 +270,7 @@ class Counter extends Component {
<button t-name="Counter" t-on-click="dispatch('increment')">
Click Me! [<t t-esc="counter.value"/>]
</button>`;
counter = useStore(state => state.counter);
counter = useStore((state) => state.counter);
dispatch = useDispatch();
}
+4 -4
View File
@@ -204,7 +204,7 @@ to be called in the constructor.
class Counter extends owl.Component {
static props = {
initialValue: Number,
optional: true
optional: true,
};
}
```
@@ -217,7 +217,7 @@ to be called in the constructor.
```js
class Counter extends owl.Component {
static defaultProps = {
initialValue: 0
initialValue: 0,
};
}
```
@@ -804,8 +804,8 @@ class RootNode extends Component {
children: [
{ label: "b" },
{ label: "c", children: [{ label: "d" }, { label: "e" }] },
{ label: "f", children: [{ label: "g" }] }
]
{ label: "f", children: [{ label: "g" }] },
],
};
}
```
+27 -3
View File
@@ -6,6 +6,7 @@
- [Setting an Environment](#setting-an-environment)
- [Using a sub environment](#using-a-sub-environment)
- [Content of an Environment](#content-of-an-environment)
- [Special keys](#special-keys)
## Overview
@@ -93,7 +94,7 @@ Some good use cases for additional keys in the environment are:
- some configuration keys,
- session information,
- generic services (such as doing rpcs, or accessing local storage).
- generic services (such as doing rpcs).
Doing it this way means that components are easily testable: we can simply
create a test environment with mock services.
@@ -112,10 +113,10 @@ async function myEnv() {
qweb: qweb,
services: {
localStorage: localStorage,
rpc: rpc
rpc: rpc,
},
debug: false,
inMobileMode: true
inMobileMode: true,
};
}
@@ -125,3 +126,26 @@ async function start() {
await app.mount(document.body);
}
```
## Special Keys
There are two special key/value added by Owl if not provided in the environment:
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. 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`
+1 -1
View File
@@ -74,7 +74,7 @@ broadcasted to the application by an event on the `qweb` instance. It may be
useful, for example, to log the error somewhere.
```js
env.qweb.on("error", null, function(error) {
env.qweb.on("error", null, function (error) {
// do something
// react to the error
});
+1 -1
View File
@@ -7,7 +7,7 @@ triggering events, and callbacks.
```js
const bus = new owl.core.EventBus();
bus.on("some-event", null, function(...args) {
bus.on("some-event", null, function (...args) {
console.log(...args);
});
+1 -1
View File
@@ -259,7 +259,7 @@ function useLoader() {
}
onWillStart(() => updateRecord(component.props.id));
onWillUpdateProps(nextProps => updateRecord(nextProps.id));
onWillUpdateProps((nextProps) => updateRecord(nextProps.id));
return record;
}
+2 -2
View File
@@ -114,9 +114,9 @@ For example:
const translations = {
hello: "bonjour",
yes: "oui",
no: "non"
no: "non",
};
const translateFn = str => translations[str] || str;
const translateFn = (str) => translations[str] || str;
const qweb = new QWeb({ translateFn });
```
+1 -1
View File
@@ -532,7 +532,7 @@ The javascript QWeb implementation provides two useful debugging directives:
```xml
<t t-if="a_test">
<t t-debug="">
<t t-debug=""/>
</t>
```
+6 -3
View File
@@ -37,16 +37,16 @@ rendering context.
</div>
```
Slots are defined by the caller, with the `t-set` directive:
Slots are defined by the caller, with the `t-set-slot` directive:
```xml
<div t-name="SomeComponent">
<div>some component</div>
<Dialog title="Some Dialog">
<t t-set="content">
<t t-set-slot="content">
<div>hey</div>
</t>
<t t-set="footer">
<t t-set-slot="footer">
<button t-on-click="doSomething">ok</button>
</t>
</Dialog>
@@ -57,6 +57,9 @@ In this example, the component `Dialog` will render the slots `content` and `foo
with its parent as rendering context. This means that clicking on the button
will execute the `doSomething` method on the parent, not on the dialog.
Note: Owl previously used the `t-set` directive to define the content of a slot.
This is deprecated and should no longer be used in new code.
## Reference
Default slot: the first element inside the component which is not a named slot will
+14 -14
View File
@@ -40,14 +40,14 @@ const actions = {
state.todos.push({
id: state.nextId++,
message,
isCompleted: false
isCompleted: false,
});
}
},
};
const state = {
todos: [],
nextId: 1
nextId: 1,
};
const store = new owl.Store({ state, actions });
@@ -90,7 +90,7 @@ const config = {
state,
actions,
getters,
env
env,
};
const store = new Store(config);
```
@@ -110,7 +110,7 @@ const actions = {
} catch (e) {
state.loginState = "error";
}
}
},
};
```
@@ -144,7 +144,7 @@ const actions = {
state.recordId = recordId;
const data = await doSomeRPC("/read/", recordId);
state.recordData = data;
}
},
};
```
@@ -159,7 +159,7 @@ const actions = {
const data = await doSomeRPC("/read/", recordId);
state.recordId = recordId;
state.recordData = data;
}
},
};
```
@@ -187,14 +187,14 @@ transform the data contained in the store.
```js
const getters = {
getPost({ state }, id) {
const post = state.posts.find(p => p.id === id);
const author = state.authors.find(a => a.id === post.id);
const post = state.posts.find((p) => p.id === id);
const author = state.authors.find((a) => a.id === post.id);
return {
id,
author,
content: post.content
content: post.content,
};
}
},
};
// somewhere else
@@ -224,11 +224,11 @@ Assume we have this store:
const actions = {
increment({ state }, val) {
state.counter.value += val;
}
},
};
const state = {
counter: { value: 0 }
counter: { value: 0 },
};
const store = new owl.Store({ state, actions });
```
@@ -245,7 +245,7 @@ A counter component can then select this value and dispatch an action like this:
```js
class Counter extends Component {
counter = useStore(state => state.counter);
counter = useStore((state) => state.counter);
dispatch = useDispatch();
}
+2 -2
View File
@@ -19,7 +19,7 @@ not ready yet, resolved directly otherwise). If called with a callback as
argument, it executes it as soon as the DOM ready (or directly).
```js
Promise.all([loadFile("templates.xml"), owl.utils.whenReady()]).then(function([templates]) {
Promise.all([loadFile("templates.xml"), owl.utils.whenReady()]).then(function ([templates]) {
const qweb = new owl.QWeb({ templates });
const app = new App({ qweb });
app.mount(document.body);
@@ -29,7 +29,7 @@ Promise.all([loadFile("templates.xml"), owl.utils.whenReady()]).then(function([t
or alternatively:
```js
owl.utils.whenReady(function() {
owl.utils.whenReady(function () {
const qweb = new owl.QWeb();
const app = new App({ qweb });
app.mount(document.body);
+7 -5
View File
@@ -1,12 +1,13 @@
{
"name": "@odoo/owl",
"version": "1.0.5",
"version": "1.0.9",
"description": "Odoo Web Library (OWL)",
"main": "dist/owl.js",
"types": "dist/types/index.d.ts",
"files": [
"dist/types/",
"dist/owl.js"
"dist/owl.js",
"dist/owl-iife.js"
],
"engines": {
"node": ">=10.15.3"
@@ -24,7 +25,7 @@
"pretools:watch": "npm run build",
"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",
"publish": "npm run buildcommonjs && npm publish",
"publish": "mv dist/owl.js dist/owl-iife.js && npm run buildcommonjs && npm publish",
"release": "node tools/release.js"
},
"repository": {
@@ -48,7 +49,7 @@
"jest-environment-jsdom": "^24.7.1",
"live-server": "^1.2.1",
"npm-run-all": "^4.1.5",
"prettier": "^1.19.1",
"prettier": "^2.0.4",
"rollup": "^1.6.0",
"rollup-plugin-typescript2": "^0.20.1",
"sass": "^1.16.1",
@@ -77,6 +78,7 @@
]
},
"prettier": {
"printWidth": 100
"printWidth": 100,
"endOfLine": "auto"
}
}
+3 -1
View File
@@ -1,6 +1,6 @@
# 🦉 OWL Roadmap 🦉
- Current version: 1.0.5
- Current version: 1.0.9
- Status: stable
This roadmap is only an attempt at predicting Owl's future. Everything may
@@ -16,6 +16,8 @@ change!
### 2.x (2020? 2021? 2022?)
- stop support for `t-set` directive to define the content of a slot
Maybe:
- reimplement vdom to use *block* system, like Vue 3, which should make Owl
+23
View File
@@ -0,0 +1,23 @@
export interface Browser {
setTimeout: Window["setTimeout"];
clearTimeout: Window["clearTimeout"];
setInterval: Window["setInterval"];
clearInterval: Window["clearInterval"];
requestAnimationFrame: Window["requestAnimationFrame"];
random: Math["random"];
Date: typeof Date;
fetch: Window["fetch"];
localStorage: Window["localStorage"];
}
export const browser: Browser = {
setTimeout: window.setTimeout.bind(window),
clearTimeout: window.clearTimeout.bind(window),
setInterval: window.setInterval.bind(window),
clearInterval: window.clearInterval.bind(window),
requestAnimationFrame: window.requestAnimationFrame.bind(window),
random: Math.random,
Date: window.Date,
fetch: (window.fetch || (() => {})).bind(window),
localStorage: window.localStorage,
};
+19 -10
View File
@@ -7,6 +7,7 @@ import { Fiber } from "./fiber";
import "./props_validation";
import { Scheduler, scheduler } from "./scheduler";
import { activateSheet } from "./styles";
import { Browser, browser } from "../browser";
/**
* Owl Component System
@@ -34,7 +35,7 @@ import { activateSheet } from "./styles";
*/
export interface Env {
qweb: QWeb;
[key: string]: any;
browser: Browser;
}
export type MountPosition = "first-child" | "last-child" | "self";
@@ -158,6 +159,9 @@ export class Component<Props extends {} = any, T extends Env = Env> {
if (!this.env.qweb) {
this.env.qweb = new QWeb();
}
if (!this.env.browser) {
this.env.browser = browser;
}
this.env.qweb.on("update", this, () => {
if (this.__owl__.isMounted) {
this.render(true);
@@ -199,7 +203,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
renderFn: qweb.render.bind(qweb, template),
classObj: null,
refs: null,
scope: null
scope: null,
};
if (constr.style) {
this.__applyStyles(constr);
@@ -312,6 +316,9 @@ export class Component<Props extends {} = any, T extends Env = Env> {
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) {
@@ -357,14 +364,15 @@ export class Component<Props extends {} = any, T extends Env = Env> {
*/
async render(force: boolean = false): Promise<void> {
const __owl__ = this.__owl__;
if (!__owl__.isMounted && !__owl__.currentFiber) {
const currentFiber = __owl__.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;
}
if (__owl__.currentFiber && !__owl__.currentFiber.isRendered) {
return scheduler.addFiber(__owl__.currentFiber.root);
if (currentFiber && !currentFiber.isRendered && !currentFiber.isCompleted) {
return scheduler.addFiber(currentFiber.root);
}
// 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
@@ -509,7 +517,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
const ev = new OwlEvent(component, eventType, {
bubbles: true,
cancelable: true,
detail: payload
detail: payload,
});
const triggerHook = this.env[portalSymbol as any];
if (triggerHook) {
@@ -544,7 +552,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
}
await Promise.all([
this.willUpdateProps(nextProps),
__owl__.willUpdatePropsCB && __owl__.willUpdatePropsCB(nextProps)
__owl__.willUpdatePropsCB && __owl__.willUpdatePropsCB(nextProps),
]);
if (fiber.isCompleted) {
return;
@@ -603,9 +611,10 @@ export class Component<Props extends {} = any, T extends Env = Env> {
// key. So we fall back on looking for a template matching its name (or
// one of its subclass).
let template: string;
while ((template = p.name) && !(template in qweb.templates) && p !== Component) {
let template: string = p.name;
while (!(template in qweb.templates) && p !== Component) {
p = p.__proto__;
template = p.name;
}
if (p === Component) {
throw new Error(`Could not find template for component "${this.constructor.name}"`);
@@ -640,7 +649,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
try {
let vnode = __owl__.renderFn!(this, {
handlers: __owl__.boundHandlers,
fiber: fiber
fiber: fiber,
});
// we iterate over the children to detect those that no longer belong to the
// current rendering: those ones, if not mounted yet, can (and have to) be
+30 -9
View File
@@ -7,7 +7,7 @@ import { makeHandlerCode, MODS_CODE } from "../qweb/extensions";
//------------------------------------------------------------------------------
const T_COMPONENT_MODS_CODE = Object.assign({}, MODS_CODE, {
self: "if (e.target !== vn.elm) {return}"
self: "if (e.target !== vn.elm) {return}",
});
QWeb.utils.defineProxy = function defineProxy(target, source) {
@@ -18,7 +18,7 @@ QWeb.utils.defineProxy = function defineProxy(target, source) {
},
set(val) {
source[k] = val;
}
},
});
}
};
@@ -238,7 +238,7 @@ QWeb.addDirective({
// computing the props string representing the props object
let propStr = Object.keys(props)
.map(k => k + ":" + props[k])
.map((k) => k + ":" + props[k])
.join(",");
let componentID = ctx.generateID();
@@ -280,7 +280,7 @@ QWeb.addDirective({
let classDef = classAttr
.trim()
.split(/\s+/)
.map(a => `'${a}':true`)
.map((a) => `'${a}':true`)
.join(",");
classObj = `_${ctx.generateID()}`;
ctx.addLine(`let ${classObj} = {${classDef}};`);
@@ -298,7 +298,7 @@ QWeb.addDirective({
}
}
let eventsCode = events
.map(function([name, value]) {
.map(function ([name, value]) {
const capture = name.match(/\.capture/);
name = capture ? name.replace(/\.capture/, "") : name;
const { event, handler } = makeHandlerCode(
@@ -400,15 +400,36 @@ QWeb.addDirective({
if (hasSlots) {
const clone = <Element>node.cloneNode(true);
const slotNodes = clone.querySelectorAll("[t-set]");
const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
// The next code is a fallback for compatibility reason. It accepts t-set
// elements that are direct children with a non empty body as nodes defining
// the content of a slot.
//
// This is wrong, but is necessary to prevent breaking all existing Owl
// code using slots. This will be removed in v2.0 someday. Meanwhile,
// please use t-set-slot everywhere you need to set the content of a
// slot.
for (let el of clone.children) {
if (el.getAttribute("t-set") && el.hasChildNodes()) {
slotNodes.push(el);
}
}
const slotId = QWeb.nextSlotId++;
ctx.addLine(`w${componentID}.__owl__.slotId = ${slotId};`);
if (slotNodes.length) {
for (let i = 0, length = slotNodes.length; i < length; i++) {
const slotNode = slotNodes[i];
slotNode.parentElement!.removeChild(slotNode);
const key = slotNode.getAttribute("t-set")!;
slotNode.removeAttribute("t-set");
let key = slotNode.getAttribute("t-set-slot")!;
slotNode.removeAttribute("t-set-slot");
// here again, this code should be removed when we stop supporting
// using t-set to define the content of named slots.
if (!key) {
key = slotNode.getAttribute("t-set")!;
slotNode.removeAttribute("t-set");
}
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
QWeb.slots[`${slotId}_${key}`] = slotFn;
}
@@ -448,5 +469,5 @@ QWeb.addDirective({
ctx.addLine(`w${componentID}.__owl__.parentLastFiberId = extra.fiber.id;`);
return true;
}
},
});
+8 -2
View File
@@ -193,7 +193,7 @@ export class Fiber {
// build patchQueue
const patchQueue: Fiber[] = [];
const doWork: (Fiber) => Fiber | null = function(f) {
const doWork: (Fiber) => Fiber | null = function (f) {
patchQueue.push(f);
return f.child;
};
@@ -225,6 +225,12 @@ export class Fiber {
`Cannot attach '${component.constructor.name}' to target node (not same tag name)`
);
}
// In self mode, we *know* we are to take possession of the target
// Hence we manually create the corresponding VNode and copy the "key" in data
const selfVnodeData = fiber.vnode!.data ? { key: fiber.vnode!.data.key } : {};
const selfVnode = h(fiber.vnode!.sel, selfVnodeData);
selfVnode.elm = target;
target = selfVnode;
} else {
target = component.__owl__.vnode || document.createElement(fiber.vnode!.sel!);
}
@@ -274,7 +280,7 @@ export class Fiber {
* Cancel a fiber and all its children.
*/
cancel() {
this._walk(f => {
this._walk((f) => {
if (!f.isRendered) {
f.root.counter--;
}
+1 -1
View File
@@ -11,7 +11,7 @@ import { QWeb } from "../qweb/index";
* This is why it is only done in 'dev' mode.
*/
QWeb.utils.validateProps = function(Widget, props: Object) {
QWeb.utils.validateProps = function (Widget, props: Object) {
const propsDef = (<any>Widget).props;
if (propsDef instanceof Array) {
// list of strings (prop names)
+7 -7
View File
@@ -1,4 +1,5 @@
import { Fiber } from "./fiber";
import { browser } from "../browser";
/**
* Owl Scheduler Class
@@ -19,9 +20,9 @@ interface Task {
export class Scheduler {
tasks: Task[] = [];
isRunning: boolean = false;
requestAnimationFrame: typeof window.requestAnimationFrame;
requestAnimationFrame: Window["requestAnimationFrame"];
constructor(requestAnimationFrame) {
constructor(requestAnimationFrame: Window["requestAnimationFrame"]) {
this.requestAnimationFrame = requestAnimationFrame;
}
@@ -49,7 +50,7 @@ export class Scheduler {
return reject(fiber.error);
}
resolve();
}
},
});
if (!this.isRunning) {
this.start();
@@ -59,7 +60,7 @@ export class Scheduler {
rejectFiber(fiber: Fiber, reason: string) {
fiber = fiber.root;
const index = this.tasks.findIndex(t => t.fiber === fiber);
const index = this.tasks.findIndex((t) => t.fiber === fiber);
if (index >= 0) {
const [task] = this.tasks.splice(index, 1);
fiber.cancel();
@@ -75,7 +76,7 @@ export class Scheduler {
flush() {
let tasks = this.tasks;
this.tasks = [];
tasks = tasks.filter(task => {
tasks = tasks.filter((task) => {
if (task.fiber.isCompleted) {
task.callback();
return false;
@@ -109,5 +110,4 @@ export class Scheduler {
}
}
const raf = window.requestAnimationFrame.bind(window);
export const scheduler = new Scheduler(raf);
export const scheduler = new Scheduler(browser.requestAnimationFrame);
+1 -1
View File
@@ -8,7 +8,7 @@
export const STYLESHEETS: { [id: string]: HTMLStyleElement } = {};
export function processSheet(str: string): string {
const tokens = str.split(/(\{|\}|;)/).map(s => s.trim());
const tokens = str.split(/(\{|\}|;)/).map((s) => s.trim());
const selectorStack: string[][] = [];
const parts: string[] = [];
let rules: string[] = [];
+1 -1
View File
@@ -26,5 +26,5 @@ Object.defineProperty(config, "mode", {
} else {
console.log(`Owl is now running in 'prod' mode.`);
}
}
},
});
+6 -6
View File
@@ -79,9 +79,9 @@ export class Context extends EventBus {
async __notifyComponents() {
const rev = ++this.rev;
const subscriptions = this.subscriptions.update;
const groups = partitionBy(subscriptions, s => (s.owner ? s.owner.__owl__.depth : -1));
const groups = partitionBy(subscriptions, (s) => (s.owner ? s.owner.__owl__.depth : -1));
for (let group of groups) {
const proms = group.map(sub => sub.callback.call(sub.owner, rev));
const proms = group.map((sub) => sub.callback.call(sub.owner, rev));
// at this point, each component in the current group has registered a
// top level fiber in the scheduler. It could happen that rendering these
// components is done (if they have no children). This is why we manually
@@ -116,7 +116,7 @@ export function useContextWithCB(ctx: Context, component: Component, method): an
__owl__.observer.notifyCB = component.render.bind(component);
}
const currentCB = __owl__.observer.notifyCB;
__owl__.observer.notifyCB = function() {
__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
@@ -128,18 +128,18 @@ export function useContextWithCB(ctx: Context, component: Component, method): an
mapping[id] = 0;
const renderFn = __owl__.renderFn;
__owl__.renderFn = function(comp, params) {
__owl__.renderFn = function (comp, params) {
mapping[id] = ctx.rev;
return renderFn(comp, params);
};
ctx.on("update", component, async contextRev => {
ctx.on("update", component, async (contextRev) => {
if (mapping[id] < contextRev) {
mapping[id] = contextRev;
await method();
}
});
const __destroy = component.__destroy;
component.__destroy = parent => {
component.__destroy = (parent) => {
ctx.off("update", component);
delete mapping[id];
__destroy.call(component, parent);
+2 -2
View File
@@ -44,7 +44,7 @@ export class EventBus {
}
this.subscriptions[eventType].push({
owner,
callback
callback,
});
}
@@ -54,7 +54,7 @@ export class EventBus {
off(eventType: string, owner: any) {
const subs = this.subscriptions[eventType];
if (subs) {
this.subscriptions[eventType] = subs.filter(s => s.owner !== owner);
this.subscriptions[eventType] = subs.filter((s) => s.owner !== owner);
}
}
+2 -2
View File
@@ -67,14 +67,14 @@ export class Observer {
self.notifyCB();
}
return true;
}
},
});
const metadata = {
value,
proxy,
rev: this.rev,
parent
parent,
};
this.weakMap.set(value, metadata);
+13 -13
View File
@@ -36,11 +36,11 @@ export function useState<T>(state: T): T {
// -----------------------------------------------------------------------------
function makeLifecycleHook(method: string, reverse: boolean = false) {
if (reverse) {
return function(cb) {
return function (cb) {
const component: Component = Component.current!;
if (component.__owl__[method]) {
const current = component.__owl__[method];
component.__owl__[method] = function() {
component.__owl__[method] = function () {
current.call(component);
cb.call(component);
};
@@ -49,11 +49,11 @@ function makeLifecycleHook(method: string, reverse: boolean = false) {
}
};
} else {
return function(cb) {
return function (cb) {
const component: Component = Component.current!;
if (component.__owl__[method]) {
const current = component.__owl__[method];
component.__owl__[method] = function() {
component.__owl__[method] = function () {
cb.call(component);
current.call(component);
};
@@ -65,12 +65,12 @@ function makeLifecycleHook(method: string, reverse: boolean = false) {
}
function makeAsyncHook(method: string) {
return function(cb) {
return function (cb) {
const component: Component = Component.current!;
if (component.__owl__[method]) {
const current = component.__owl__[method];
component.__owl__[method] = function(...args) {
return Promise.all[(current.call(component, ...args), cb.call(component, ...args))];
component.__owl__[method] = function (...args) {
return Promise.all([current.call(component, ...args), cb.call(component, ...args)]);
};
} else {
component.__owl__[method] = cb;
@@ -94,12 +94,12 @@ export const onWillUpdateProps = makeAsyncHook("willUpdatePropsCB");
* The purpose of this hook is to allow components to get a reference to a sub
* html node or component.
*/
interface Ref {
interface Ref<C extends Component = Component> {
el: HTMLElement | null;
comp: Component | null;
comp: C | null;
}
export function useRef(name: string): Ref {
export function useRef<C extends Component = Component>(name: string): Ref<C> {
const __owl__ = Component.current!.__owl__;
return {
get el(): HTMLElement | null {
@@ -111,10 +111,10 @@ export function useRef(name: string): Ref {
}
return null;
},
get comp(): Component | null {
get comp(): C | null {
const val = __owl__.refs && __owl__.refs[name];
return val instanceof Component ? val : null;
}
return val instanceof Component ? (val as C) : null;
},
};
}
+1 -1
View File
@@ -35,6 +35,6 @@ export const hooks = Object.assign({}, _hooks, {
useContext: _context.useContext,
useDispatch: _store.useDispatch,
useGetters: _store.useGetters,
useStore: _store.useStore
useStore: _store.useStore,
});
export const __info__ = {};
+4 -4
View File
@@ -26,8 +26,8 @@ export class Portal extends Component<Props> {
static template = xml`<portal><t t-slot="default"/></portal>`;
static props = {
target: {
type: String
}
type: String,
},
};
// boolean to indicate whether or not we must listen to 'dom-appended' event
@@ -55,12 +55,12 @@ export class Portal extends Component<Props> {
// put a callback in the env that is propagated to children s.t. portal can
// register an handler to those events just before children will trigger them
useSubEnv({
[portalSymbol]: ev => {
[portalSymbol]: (ev) => {
if (!this._handledEvents.has(ev.type)) {
this.portal!.elm!.addEventListener(ev.type, this._handlerTunnel);
this._handledEvents.add(ev.type);
}
}
},
});
}
/**
+22 -15
View File
@@ -49,6 +49,7 @@ function compileValueNode(value: any, node: Element, qweb: QWeb, ctx: Compilatio
if (ctx.escaping) {
let protectID;
if (value.hasBody) {
ctx.rootContext.shouldDefineUtils = true;
protectID = ctx.startProtectScope();
ctx.addLine(
`${exprID} = ${exprID} instanceof utils.VDomArray ? utils.vDomToString(${exprID}) : ${exprID};`
@@ -96,7 +97,7 @@ QWeb.addDirective({
let value = ctx.getValue(node.getAttribute("t-esc")!);
compileValueNode(value, node, qweb, ctx.subContext("escaping", true));
return true;
}
},
});
QWeb.addDirective({
@@ -106,7 +107,7 @@ QWeb.addDirective({
let value = ctx.getValue(node.getAttribute("t-raw")!);
compileValueNode(value, node, qweb, ctx);
return true;
}
},
});
//------------------------------------------------------------------------------
@@ -163,7 +164,7 @@ QWeb.addDirective({
}
}
return true;
}
},
});
//------------------------------------------------------------------------------
@@ -179,7 +180,7 @@ QWeb.addDirective({
},
finalize({ ctx }) {
ctx.closeIf();
}
},
});
QWeb.addDirective({
@@ -195,7 +196,7 @@ QWeb.addDirective({
},
finalize({ ctx }) {
ctx.closeIf();
}
},
});
QWeb.addDirective({
@@ -208,7 +209,7 @@ QWeb.addDirective({
},
finalize({ ctx }) {
ctx.closeIf();
}
},
});
//------------------------------------------------------------------------------
@@ -233,10 +234,12 @@ QWeb.addDirective({
// Step 2: compile target template in sub templates
// ------------------------------------------------
if (!qweb.subTemplates[subTemplate]) {
qweb.subTemplates[subTemplate] = true;
let subId = qweb.subTemplates[subTemplate];
if (!subId) {
subId = QWeb.nextId++;
qweb.subTemplates[subTemplate] = subId;
const subTemplateFn = qweb._compile(subTemplate, nodeTemplate.elem, ctx, true);
qweb.subTemplates[subTemplate] = subTemplateFn;
QWeb.subTemplates[subId] = subTemplateFn;
}
// Step 3: compile t-call body if necessary
@@ -274,12 +277,16 @@ QWeb.addDirective({
const parentNode = ctx.parentNode ? `c${ctx.parentNode}` : "result";
const extra = `Object.assign({}, extra, {parentNode: ${parentNode}, parent: ${parentComponent}, key: ${key}})`;
if (ctx.parentNode) {
ctx.addLine(`this.subTemplates['${subTemplate}'].call(this, ${callingScope}, ${extra});`);
ctx.addLine(
`this.constructor.subTemplates['${subId}'].call(this, ${callingScope}, ${extra});`
);
} else {
// this is a t-call with no parentnode, we need to extract the result
ctx.rootContext.shouldDefineResult = true;
ctx.addLine(`result = []`);
ctx.addLine(`this.subTemplates['${subTemplate}'].call(this, ${callingScope}, ${extra});`);
ctx.addLine(
`this.constructor.subTemplates['${subId}'].call(this, ${callingScope}, ${extra});`
);
ctx.addLine(`result = result[0]`);
}
@@ -292,7 +299,7 @@ QWeb.addDirective({
}
return true;
}
},
});
//------------------------------------------------------------------------------
@@ -353,7 +360,7 @@ QWeb.addDirective({
ctx.addLine("}");
ctx.stopProtectScope(varsID);
return true;
}
},
});
//------------------------------------------------------------------------------
@@ -364,7 +371,7 @@ QWeb.addDirective({
priority: 1,
atNodeEncounter({ ctx }) {
ctx.addLine("debugger;");
}
},
});
//------------------------------------------------------------------------------
@@ -376,5 +383,5 @@ QWeb.addDirective({
atNodeEncounter({ ctx, value }) {
const expr = ctx.formatExpression(value);
ctx.addLine(`console.log(${expr})`);
}
},
});
+6 -5
View File
@@ -162,7 +162,7 @@ export class CompilationContext {
const tokens = compileExprToArray(expr, this.variables);
const done = new Set();
return tokens
.map(tok => {
.map((tok) => {
if (tok.varName) {
if (!done.has(tok.varName)) {
done.add(tok.varName);
@@ -189,18 +189,19 @@ export class CompilationContext {
return `(${this.formatExpression(s.slice(2, -2))})`;
}
let r = s.replace(/\{\{.*?\}\}/g, s => "${" + this.formatExpression(s.slice(2, -2)) + "}");
let r = s.replace(/\{\{.*?\}\}/g, (s) => "${" + this.formatExpression(s.slice(2, -2)) + "}");
return "`" + r + "`";
}
startProtectScope(codeBlock?: boolean): number {
const protectID = this.generateID();
this.rootContext.protectedScopeNumber++;
this.rootContext.shouldDefineScope = true;
const scopeExpr = codeBlock
? `Object.create(scope);`
: `Object.assign(Object.create(context), scope);`;
const scopeExpr = `Object.create(scope);`;
this.addLine(`let _origScope${protectID} = scope;`);
this.addLine(`scope = ${scopeExpr}`);
if (!codeBlock) {
this.addLine(`scope.__access_mode__ = 'ro';`);
}
return protectID;
}
stopProtectScope(protectID: number) {
+9 -9
View File
@@ -35,7 +35,7 @@ const WORD_REPLACEMENT = {
gt: ">",
gte: ">=",
lt: "<",
lte: "<="
lte: "<=",
};
export interface QWebVar {
@@ -77,7 +77,7 @@ const STATIC_TOKEN_MAP: { [key: string]: TKind } = {
":": "COLON",
",": "COMMA",
"(": "LEFT_PAREN",
")": "RIGHT_PAREN"
")": "RIGHT_PAREN",
};
// note that the space after typeof is relevant. It makes sure that the formatted
@@ -86,7 +86,7 @@ const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,
type Tokenizer = (expr: string) => Token | false;
let tokenizeString: Tokenizer = function(expr) {
let tokenizeString: Tokenizer = function (expr) {
let s = expr[0];
let start = s;
if (s !== "'" && s !== '"') {
@@ -114,7 +114,7 @@ let tokenizeString: Tokenizer = function(expr) {
return { type: "VALUE", value: s };
};
let tokenizeNumber: Tokenizer = function(expr) {
let tokenizeNumber: Tokenizer = function (expr) {
let s = expr[0];
if (s && s.match(/[0-9]/)) {
let i = 1;
@@ -128,7 +128,7 @@ let tokenizeNumber: Tokenizer = function(expr) {
}
};
let tokenizeSymbol: Tokenizer = function(expr) {
let tokenizeSymbol: Tokenizer = function (expr) {
let s = expr[0];
if (s && s.match(/[a-zA-Z_\$]/)) {
let i = 1;
@@ -145,7 +145,7 @@ let tokenizeSymbol: Tokenizer = function(expr) {
}
};
const tokenizeStatic: Tokenizer = function(expr) {
const tokenizeStatic: Tokenizer = function (expr) {
const char = expr[0];
if (char && char in STATIC_TOKEN_MAP) {
return { type: STATIC_TOKEN_MAP[char], value: char };
@@ -153,7 +153,7 @@ const tokenizeStatic: Tokenizer = function(expr) {
return false;
};
const tokenizeOperator: Tokenizer = function(expr) {
const tokenizeOperator: Tokenizer = function (expr) {
for (let op of OPERATORS) {
if (expr.startsWith(op)) {
return { type: "OPERATOR", value: op };
@@ -167,7 +167,7 @@ const TOKENIZERS = [
tokenizeNumber,
tokenizeOperator,
tokenizeSymbol,
tokenizeStatic
tokenizeStatic,
];
/**
@@ -284,6 +284,6 @@ export function compileExprToArray(expr: string, scope: { [key: string]: QWebVar
export function compileExpr(expr: string, scope: { [key: string]: QWebVar }): string {
return compileExprToArray(expr, scope)
.map(t => t.value)
.map((t) => t.value)
.join("");
}
+14 -14
View File
@@ -23,7 +23,7 @@ import { QWeb } from "./qweb";
export const MODS_CODE = {
prevent: "e.preventDefault();",
self: "if (e.target !== this.elm) {return}",
stop: "e.stopPropagation();"
stop: "e.stopPropagation();",
};
interface HandlerInfo {
@@ -50,7 +50,7 @@ export function makeHandlerCode(
let code: string;
// check if it is a method with no args, a method with args or an expression
let args: string = "";
const name: string = value.replace(/\(.*\)/, function(_args) {
const name: string = value.replace(/\(.*\)/, function (_args) {
args = _args.slice(1, -1);
return "";
});
@@ -74,7 +74,7 @@ export function makeHandlerCode(
putInCache = false;
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__.isMounted){return}${modCode}${code}}`;
if (putInCache) {
const key = ctx.generateTemplateKey(event);
@@ -90,7 +90,7 @@ QWeb.addDirective({
atNodeCreation({ ctx, fullName, value, nodeID }) {
const { event, handler } = makeHandlerCode(ctx, fullName, value, true);
ctx.addLine(`p${nodeID}.on['${event}'] = ${handler};`);
}
},
});
//------------------------------------------------------------------------------
@@ -105,17 +105,17 @@ QWeb.addDirective({
ctx.addLine(`const ${refKey} = ${ctx.interpolate(value)};`);
addNodeHook("create", `context.__owl__.refs[${refKey}] = n.elm;`);
addNodeHook("destroy", `delete context.__owl__.refs[${refKey}];`);
}
},
});
//------------------------------------------------------------------------------
// t-transition
//------------------------------------------------------------------------------
QWeb.utils.nextFrame = function(cb: () => void) {
QWeb.utils.nextFrame = function (cb: () => void) {
requestAnimationFrame(() => requestAnimationFrame(cb));
};
QWeb.utils.transitionInsert = function(vn: VNode, name: string) {
QWeb.utils.transitionInsert = function (vn: VNode, name: string) {
const elm = <HTMLElement>vn.elm;
// remove potential duplicated vnode that is currently being removed, to
// prevent from having twice the same node in the DOM during an animation
@@ -139,7 +139,7 @@ QWeb.utils.transitionInsert = function(vn: VNode, name: string) {
});
};
QWeb.utils.transitionRemove = function(vn: VNode, name: string, rm: () => void) {
QWeb.utils.transitionRemove = function (vn: VNode, name: string, rm: () => void) {
const elm = <HTMLElement>vn.elm;
elm.setAttribute("data-owl-key", vn.key!);
@@ -209,12 +209,12 @@ QWeb.addDirective({
let name = value;
const hooks = {
insert: `utils.transitionInsert(vn, '${name}');`,
remove: `utils.transitionRemove(vn, '${name}', rm);`
remove: `utils.transitionRemove(vn, '${name}', rm);`,
};
for (let hookName in hooks) {
addNodeHook(hookName, hooks[hookName]);
}
}
},
});
//------------------------------------------------------------------------------
@@ -251,13 +251,13 @@ QWeb.addDirective({
}
ctx.closeIf();
return true;
}
},
});
//------------------------------------------------------------------------------
// t-model
//------------------------------------------------------------------------------
QWeb.utils.toNumber = function(val: string): number | string {
QWeb.utils.toNumber = function (val: string): number | string {
const n = parseFloat(val);
return isNaN(n) ? val : n;
};
@@ -305,7 +305,7 @@ QWeb.addDirective({
}
ctx.addLine(`extra.handlers[${key}] = extra.handlers[${key}] || (${handler});`);
ctx.addLine(`p${nodeID}.on['${event}'] = extra.handlers[${key}];`);
}
},
});
//------------------------------------------------------------------------------
@@ -329,5 +329,5 @@ QWeb.addDirective({
if (ctx.loopNumber === 0) {
ctx.rootContext.hasKey0 = ctx.keyStack.pop() as boolean;
}
}
},
});
+33 -23
View File
@@ -77,7 +77,7 @@ const NODE_HOOKS_PARAMS = {
create: "(_, n)",
insert: "vn",
remove: "(vn, rm)",
destroy: "()"
destroy: "()",
};
interface Utils {
@@ -112,9 +112,9 @@ const UTILS: Utils = {
addNS(vnode.data, vnode.children, vnode.sel);
},
VDomArray: class VDomArray extends Array {},
vDomToString: function(vdom: VNode[]): string {
vDomToString: function (vdom: VNode[]): string {
return vdom
.map(vnode => {
.map((vnode) => {
if (vnode.sel) {
const node = document.createElement(vnode.sel);
const result = patch(node, vnode);
@@ -133,7 +133,11 @@ const UTILS: Utils = {
},
getScope(obj, property: string) {
const obj0 = obj;
while (obj && !obj.hasOwnProperty(property)) {
while (
obj &&
!obj.hasOwnProperty(property) &&
!(obj.hasOwnProperty("__access_mode__") && obj.__access_mode__ === "ro")
) {
const newObj = obj.__proto__;
if (!newObj || isComponent(newObj)) {
return obj0;
@@ -141,7 +145,7 @@ const UTILS: Utils = {
obj = newObj;
}
return obj;
}
},
};
function parseXML(xml: string): Document {
@@ -191,7 +195,7 @@ export class QWeb extends EventBus {
name: 1,
att: 1,
attf: 1,
translation: 1
translation: 1,
};
static DIRECTIVES: Directive[] = [];
@@ -208,10 +212,12 @@ export class QWeb extends EventBus {
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,
// as in we need to propagate the scope.
subTemplates = {};
// subTemplates are stored in two objects: a (local) mapping from a name to an
// id, and a (global) mapping from an id to the compiled function. This is
// necessary to ensure that global templates can be called with more than one
// QWeb instance.
subTemplates: {[key: string]: number} = {};
static subTemplates: {[id: number]: Function} = {};
isUpdating: boolean = false;
translateFn?: QWebConfig["translateFn"];
@@ -235,7 +241,7 @@ export class QWeb extends EventBus {
QWeb.DIRECTIVE_NAMES[directive.name] = 1;
QWeb.DIRECTIVES.sort((d1, d2) => d1.priority - d2.priority);
if (directive.extraNames) {
directive.extraNames.forEach(n => (QWeb.DIRECTIVE_NAMES[n] = 1));
directive.extraNames.forEach((n) => (QWeb.DIRECTIVE_NAMES[n] = 1));
}
}
@@ -299,11 +305,11 @@ export class QWeb extends EventBus {
this._processTemplate(elem);
const template = {
elem,
fn: function(this: QWeb, context, extra) {
fn: function (this: QWeb, context, extra) {
const compiledFunction = this._compile(name, elem);
template.fn = compiledFunction;
return compiledFunction.call(this, context, extra);
}
},
};
this.templates[name] = template;
}
@@ -313,10 +319,10 @@ export class QWeb extends EventBus {
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
let node = tbranch[i];
let prevElem = node.previousElementSibling!;
let pattr = function(name) {
let pattr = function (name) {
return prevElem.getAttribute(name);
};
let nattr = function(name) {
let nattr = function (name) {
return +!!node.getAttribute(name);
};
if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
@@ -326,7 +332,7 @@ export class QWeb extends EventBus {
);
}
if (
["t-if", "t-elif", "t-else"].map(nattr).reduce(function(a, b) {
["t-if", "t-elif", "t-else"].map(nattr).reduce(function (a, b) {
return a + b;
}) > 1
) {
@@ -578,7 +584,7 @@ export class QWeb extends EventBus {
qweb: this,
ctx,
fullName,
value
value,
});
if (isDone) {
for (let { directive, value, fullName } of finalizers) {
@@ -593,7 +599,7 @@ export class QWeb extends EventBus {
let nodeID = this._compileGenericNode(node, ctx, withHandlers);
ctx = ctx.withParent(nodeID);
let nodeHooks = {};
let addNodeHook = function(hook, handler) {
let addNodeHook = function (hook, handler) {
nodeHooks[hook] = nodeHooks[hook] || [];
nodeHooks[hook].push(handler);
};
@@ -607,7 +613,7 @@ export class QWeb extends EventBus {
fullName,
value,
nodeID,
addNodeHook
addNodeHook,
});
}
}
@@ -698,10 +704,14 @@ export class QWeb extends EventBus {
if ((value = value.trim())) {
let classDef = value
.split(/\s+/)
.map(a => `'${escapeQuotes(a)}':true`)
.map((a) => `'${escapeQuotes(a)}':true`)
.join(",");
classObj = `_${ctx.generateID()}`;
ctx.addLine(`let ${classObj} = {${classDef}};`);
if (classObj) {
ctx.addLine(`Object.assign(${classObj}, {${classDef}})`);
} else {
classObj = `_${ctx.generateID()}`;
ctx.addLine(`let ${classObj} = {${classDef}};`);
}
}
} else {
ctx.addLine(`let _${attID} = '${escapeQuotes(value)}';`);
@@ -742,7 +752,7 @@ export class QWeb extends EventBus {
const attValueID = ctx.generateID();
ctx.addLine(`let _${attValueID} = ${formattedValue};`);
formattedValue = `'${attValue}' + (_${attValueID} ? ' ' + _${attValueID} : '')`;
const attrIndex = attrs.findIndex(att => att.startsWith(attName + ":"));
const attrIndex = attrs.findIndex((att) => att.startsWith(attName + ":"));
attrs.splice(attrIndex, 1);
}
ctx.addLine(`let _${attID} = ${formattedValue};`);
+2 -1
View File
@@ -1,7 +1,8 @@
import { Component } from "../component/component";
import { xml } from "../tags";
import { EnvWithRouter } from "./router";
export class RouteComponent extends Component {
export class RouteComponent extends Component<{}, EnvWithRouter> {
static template = xml`
<t>
<t
+12 -4
View File
@@ -49,6 +49,10 @@ interface Options {
mode: Router["mode"];
}
export interface EnvWithRouter extends Env {
router: Router;
}
const paramRegexp = /\{\{(.*?)\}\}/;
export class Router {
@@ -60,7 +64,11 @@ export class Router {
routeIds: string[];
env: RouterEnv;
constructor(env: Env, routes: Partial<Route>[], options: Options = { mode: "history" }) {
constructor(
env: Partial<EnvWithRouter>,
routes: Partial<Route>[],
options: Options = { mode: "history" }
) {
env.router = this;
this.mode = options.mode;
this.env = env as RouterEnv;
@@ -89,7 +97,7 @@ export class Router {
//--------------------------------------------------------------------------
async start() {
(this as any)._listener = ev => this._navigate(this.currentPath(), ev);
(this as any)._listener = (ev) => this._navigate(this.currentPath(), ev);
window.addEventListener("popstate", (this as any)._listener);
if (this.mode === "hash") {
window.addEventListener("hashchange", (this as any)._listener);
@@ -190,7 +198,7 @@ export class Router {
return {
type: "match",
route: route,
params: params
params: params,
};
}
}
@@ -215,7 +223,7 @@ export class Router {
const result = await route.beforeRouteEnter({
env: this.env,
from: this.currentRoute,
to: route
to: route,
});
if (result === false) {
return { type: "cancelled" };
+14 -10
View File
@@ -24,6 +24,10 @@ import { onWillUpdateProps } from "./hooks";
// Store Definition
//------------------------------------------------------------------------------
export interface EnvWithStore extends Env {
store: Store;
}
export type Action = ({ state, dispatch, env, getters }, ...payload: any) => any;
export type Getter = ({ state: any, getters }, payload?) => any;
@@ -49,7 +53,7 @@ export class Store extends Context {
if (config.getters) {
const firstArg = {
state: this.state,
getters: this.getters
getters: this.getters,
};
for (let g in config.getters) {
this.getters[g] = config.getters[g].bind(this, firstArg);
@@ -66,7 +70,7 @@ export class Store extends Context {
dispatch: this.dispatch.bind(this),
env: this.env,
state: this.state,
getters: this.getters
getters: this.getters,
},
...payload
);
@@ -83,7 +87,7 @@ interface SelectorOptions {
const isStrictEqual = (a, b) => a === b;
export function useStore(selector, options: SelectorOptions = {}): any {
const component: Component = Component.current!;
const component = Component.current as Component<any, EnvWithStore>;
const componentId = component.__owl__.id;
const store = options.store || (component.env.store as Store);
if (!(store instanceof Store)) {
@@ -109,11 +113,11 @@ export function useStore(selector, options: SelectorOptions = {}): any {
}
return false;
}
store.updateFunctions[componentId].push(function(): boolean {
store.updateFunctions[componentId].push(function (): boolean {
return selectCompareUpdate(store!.state, component.props);
});
useContextWithCB(store, component, function(): Promise<void> | void {
useContextWithCB(store, component, function (): Promise<void> | void {
let shouldRender = false;
for (let fn of store.updateFunctions[componentId]) {
shouldRender = fn() || shouldRender;
@@ -122,12 +126,12 @@ export function useStore(selector, options: SelectorOptions = {}): any {
return component.render();
}
});
onWillUpdateProps(props => {
onWillUpdateProps((props) => {
selectCompareUpdate(store.state, props);
});
const __destroy = component.__destroy;
component.__destroy = parent => {
component.__destroy = (parent) => {
delete store.updateFunctions[componentId];
__destroy.call(component, parent);
};
@@ -144,16 +148,16 @@ export function useStore(selector, options: SelectorOptions = {}): any {
},
has(target, k) {
return k in result;
}
},
});
}
export function useDispatch(store?: Store): Store["dispatch"] {
store = store || (Component.current!.env.store as Store);
store = store || (Component.current!.env as EnvWithStore).store;
return store.dispatch.bind(store);
}
export function useGetters(store?: Store): Store["getters"] {
store = store || (Component.current!.env.store as Store);
store = store || (Component.current!.env as EnvWithStore).store;
return store.getters;
}
+11 -9
View File
@@ -10,14 +10,16 @@
* - debounce
*/
import { browser } from "./browser";
export function whenReady(fn?: any) {
return new Promise(function(resolve) {
return new Promise(function (resolve) {
if (document.readyState !== "loading") {
resolve();
} else {
document.addEventListener("DOMContentLoaded", resolve, false);
}
}).then(fn || function() {});
}).then(fn || function () {});
}
const loadedScripts: { [key: string]: Promise<void> } = {};
@@ -26,14 +28,14 @@ export function loadJS(url: string): Promise<void> {
if (url in loadedScripts) {
return loadedScripts[url];
}
const promise: Promise<void> = new Promise(function(resolve, reject) {
const promise: Promise<void> = new Promise(function (resolve, reject) {
const script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
script.onload = function() {
script.onload = function () {
resolve();
};
script.onerror = function() {
script.onerror = function () {
reject(`Error loading file '${url}'`);
};
const head = document.head || document.getElementsByTagName("head")[0];
@@ -44,7 +46,7 @@ export function loadJS(url: string): Promise<void> {
}
export async function loadFile(url: string): Promise<string> {
const result = await fetch(url);
const result = await browser.fetch(url);
if (!result.ok) {
throw new Error("Error while fetching xml templates");
}
@@ -73,7 +75,7 @@ export function escape(str: string | number | undefined): string {
*/
export function debounce(func: Function, wait: number, immediate?: boolean): Function {
let timeout;
return function(this: any) {
return function (this: any) {
const context = this;
const args = arguments;
function later() {
@@ -83,8 +85,8 @@ export function debounce(func: Function, wait: number, immediate?: boolean): Fun
}
}
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
browser.clearTimeout(timeout);
timeout = browser.setTimeout(later, wait);
if (callNow) {
func.apply(context, args);
}
+6 -2
View File
@@ -1,4 +1,4 @@
import { VNode, h } from "./vdom";
import { VNode, h, addNS } from "./vdom";
const parser = new DOMParser();
@@ -23,5 +23,9 @@ function htmlToVNode(node: ChildNode): VNode {
for (let c of node.childNodes) {
children.push(htmlToVNode(c));
}
return h((node as Element).tagName, { attrs }, children);
const vnode = h((node as Element).tagName, { attrs }, children);
if (vnode.sel === "svg") {
addNS(vnode.data, (vnode as any).children, vnode.sel);
}
return vnode;
}
+3 -3
View File
@@ -33,7 +33,7 @@ function updateProps(oldVnode: VNode, vnode: VNode): void {
export const propsModule = {
create: updateProps,
update: updateProps
update: updateProps,
} as Module;
//------------------------------------------------------------------------------
@@ -151,7 +151,7 @@ function updateEventListeners(oldVnode: VNode, vnode?: VNode): void {
export const eventListenersModule = {
create: updateEventListeners,
update: updateEventListeners,
destroy: updateEventListeners
destroy: updateEventListeners,
} as Module;
//------------------------------------------------------------------------------
@@ -210,7 +210,7 @@ function updateAttrs(oldVnode: VNode, vnode: VNode): void {
export const attrsModule = {
create: updateAttrs,
update: updateAttrs
update: updateAttrs,
} as Module;
//------------------------------------------------------------------------------
+1 -1
View File
@@ -520,7 +520,7 @@ const htmlDomApi = {
parentNode,
nextSibling,
tagName,
setTextContent
setTextContent,
} as DOMAPI;
//------------------------------------------------------------------------------
+13 -13
View File
@@ -9,7 +9,7 @@ import {
patchNextFrame,
renderToDOM,
unpatchNextFrame,
nextTick
nextTick,
} from "./helpers";
//------------------------------------------------------------------------------
@@ -71,7 +71,7 @@ describe("animations", () => {
qweb.addTemplate("test", `<span t-transition="chimay">blue</span>`);
let def = makeDeferred();
patchNextFrame(cb => {
patchNextFrame((cb) => {
expect(node.className).toBe("chimay-enter chimay-enter-active");
cb();
expect(node.className).toBe("chimay-enter-active chimay-enter-to");
@@ -91,7 +91,7 @@ describe("animations", () => {
qweb.addTemplate("test", `<span t-transition="chimay">blue</span>`);
let def = makeDeferred();
patchNextFrame(cb => {
patchNextFrame((cb) => {
expect(node.className).toBe("chimay-enter chimay-enter-active");
cb();
expect(node.className).toBe("chimay-enter-active chimay-enter-to");
@@ -112,7 +112,7 @@ describe("animations", () => {
qweb.addTemplate("test", `<span t-transition="jupiler">blue</span>`);
let def = makeDeferred();
patchNextFrame(cb => {
patchNextFrame((cb) => {
expect(node.className).toBe("jupiler-enter jupiler-enter-active");
cb();
expect(node.className).toBe("");
@@ -139,7 +139,7 @@ describe("animations", () => {
// insert widget into the DOM
let def = makeDeferred();
var spanNode;
patchNextFrame(cb => {
patchNextFrame((cb) => {
expect(spanNode.className).toBe("chimay-enter chimay-enter-active");
cb();
expect(spanNode.className).toBe("chimay-enter-active chimay-enter-to");
@@ -155,7 +155,7 @@ describe("animations", () => {
// remove span from the DOM
def = makeDeferred();
widget.state.hide = true;
patchNextFrame(cb => {
patchNextFrame((cb) => {
expect(spanNode.className).toBe("chimay-leave chimay-leave-active");
cb();
expect(spanNode.className).toBe("chimay-leave-active chimay-leave-to");
@@ -182,7 +182,7 @@ describe("animations", () => {
// insert widget into the DOM
let def = makeDeferred();
var spanNode;
patchNextFrame(cb => {
patchNextFrame((cb) => {
expect(spanNode.className).toBe("chimay-enter chimay-enter-active");
cb();
expect(spanNode.className).toBe("chimay-enter-active chimay-enter-to");
@@ -210,7 +210,7 @@ describe("animations", () => {
let def = makeDeferred();
var spanNode;
patchNextFrame(cb => {
patchNextFrame((cb) => {
expect(fixture.innerHTML).toBe(
'<div><span class="chimay-enter chimay-enter-active">blue</span></div>'
);
@@ -250,7 +250,7 @@ describe("animations", () => {
let def = makeDeferred();
var spanNode;
patchNextFrame(cb => {
patchNextFrame((cb) => {
expect(fixture.innerHTML).toBe(
'<div><span class="chimay-enter chimay-enter-active">blue</span></div>'
);
@@ -275,7 +275,7 @@ describe("animations", () => {
// remove span from the DOM
def = makeDeferred();
widget.state.display = false;
patchNextFrame(cb => {
patchNextFrame((cb) => {
expect(fixture.innerHTML).toBe(
'<div><span class="chimay-leave chimay-leave-active" data-owl-key="__3__">blue</span></div>'
);
@@ -315,7 +315,7 @@ describe("animations", () => {
let def = makeDeferred();
let phase = "enter";
patchNextFrame(cb => {
patchNextFrame((cb) => {
let spans = fixture.querySelectorAll("span");
expect(spans.length).toBe(1);
expect(spans[0].className).toBe(`chimay-${phase} chimay-${phase}-active`);
@@ -366,7 +366,7 @@ describe("animations", () => {
let def = makeDeferred();
let phase = "enter";
patchNextFrame(cb => {
patchNextFrame((cb) => {
let spans = fixture.querySelectorAll("span");
expect(spans.length).toBe(1);
expect(spans[0].className).toBe(`chimay-${phase} chimay-${phase}-active`);
@@ -413,7 +413,7 @@ describe("animations", () => {
state = useState({ flag: false });
}
patchNextFrame(cb => cb());
patchNextFrame((cb) => cb());
const widget = new Parent();
await widget.mount(fixture);
@@ -1253,7 +1253,7 @@ exports[`other directives with t-component t-set can't alter from within callee
if (scope.iter != null) {
c2.push({text: scope.iter});
}
this.subTemplates['ChildWidget'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
this.constructor.subTemplates['2'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
let c5 = [], p5 = {key:5};
let vn5 = h('p', p5, c5);
c1.push(vn5);
@@ -1282,13 +1282,14 @@ exports[`other directives with t-component t-set can't alter in t-call body 1`]
}
{
let _origScope4 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'iter').iter = 'inCall';
scope[utils.zero] = c__0;
}
this.subTemplates['ChildWidget'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
scope = _origScope4;
}
let c6 = [], p6 = {key:6};
@@ -1409,7 +1410,7 @@ exports[`random stuff/miscellaneous can inject values in tagged templates 1`] =
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.subTemplates['__template__1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
this.constructor.subTemplates['3'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
return vn1;
}"
`;
@@ -90,6 +90,96 @@ exports[`t-slot directive can define and call slots 4`] = `
}"
`;
exports[`t-slot directive can define and call slots using old t-set keyword 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__2\\"
let utils = this.constructor.utils;
let QWeb = this.constructor;
let parent = context;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
// Component 'Dialog'
let w2 = '__3__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__3__']] : false;
let props2 = {};
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
w2.destroy();
w2 = false;
}
if (w2) {
w2.__updateProps(props2, extra.fiber, Object.assign(Object.create(context), scope));
let pvnode = w2.__owl__.pvnode;
c1.push(pvnode);
} else {
let componentKey2 = \`Dialog\`;
let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Dialog'];
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
w2 = new W2(parent, props2);
parent.__owl__.cmap['__3__'] = w2.__owl__.id;
w2.__owl__.slotId = 1;
let fiber = w2.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}});
c1.push(pvnode);
w2.__owl__.pvnode = pvnode;
}
w2.__owl__.parentLastFiberId = extra.fiber.id;
return vn1;
}"
`;
exports[`t-slot directive can define and call slots using old t-set keyword 2`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__1\\"
let h = this.h;
let c6 = [], p6 = {key:6};
let vn6 = h('div', p6, c6);
let c7 = [], p7 = {key:7};
let vn7 = h('div', p7, c7);
c6.push(vn7);
const slot8 = this.constructor.slots[context.__owl__.slotId + '_' + 'header'];
if (slot8) {
slot8.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c7, parent: extra.parent || context}));
}
let c9 = [], p9 = {key:9};
let vn9 = h('div', p9, c9);
c6.push(vn9);
const slot10 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer'];
if (slot10) {
slot10.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c9, parent: extra.parent || context}));
}
return vn6;
}"
`;
exports[`t-slot directive can define and call slots using old t-set keyword 3`] = `
"function anonymous(context, extra
) {
// Template name: \\"slot_header_template\\"
let h = this.h;
let c1 = extra.parentNode;
let c4 = [], p4 = {key:4};
let vn4 = h('span', p4, c4);
c1.push(vn4);
c4.push({text: \`header\`});
}"
`;
exports[`t-slot directive can define and call slots using old t-set keyword 4`] = `
"function anonymous(context, extra
) {
// Template name: \\"slot_footer_template\\"
let h = this.h;
let c1 = extra.parentNode;
let c5 = [], p5 = {key:5};
let vn5 = h('span', p5, c5);
c1.push(vn5);
c5.push({text: \`footer\`});
}"
`;
exports[`t-slot directive content is the default slot 1`] = `
"function anonymous(context, extra
) {
@@ -469,6 +559,21 @@ exports[`t-slot directive slots are rendered with proper context, part 4 2`] = `
}"
`;
exports[`t-slot directive t-set t-value in a slot 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"__template__1\\"
let h = this.h;
let c4 = [], p4 = {key:4};
let vn4 = h('span', p4, c4);
const slot5 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
if (slot5) {
slot5.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c4, parent: extra.parent || context}));
}
return vn4;
}"
`;
exports[`t-slot directive template can just return a slot 1`] = `
"function anonymous(context, extra
) {
+24 -2
View File
@@ -27,7 +27,7 @@ afterEach(() => {
function children(w: Component): Component[] {
const childrenMap = w.__owl__.children;
return Object.keys(childrenMap).map(id => childrenMap[id]);
return Object.keys(childrenMap).map((id) => childrenMap[id]);
}
describe("async rendering", () => {
@@ -404,7 +404,7 @@ describe("async rendering", () => {
}
class ChildB extends Component {
willStart(): any {
return new Promise(function() {});
return new Promise(function () {});
}
}
class Parent extends Component {
@@ -1366,6 +1366,28 @@ describe("async rendering", () => {
await prom;
});
test("concurrent renderings scenario 17", async () => {
class Parent extends Component {
static template = xml`<span><t t-esc="state.value"/></span>`;
state = useState({ value: 1 });
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<span>1</span>");
parent.state.value = 2;
parent.__owl__.currentFiber!.cancel();
parent.state.value = 3; // update value directly
await nextTick();
expect(fixture.innerHTML).toBe("<span>3</span>");
parent.state.value = 4; // update value after a tick
await nextTick();
expect(fixture.innerHTML).toBe("<span>4</span>");
});
test("change state and call manually render: no unnecessary rendering", async () => {
class Widget extends Component {
static template = xml`<div><t t-esc="state.val"/></div>`;
+55 -34
View File
@@ -1,16 +1,16 @@
import { Component, Env } from "../../src/component/component";
import { EventBus } from "../../src/core/event_bus";
import { useRef, useState } from "../../src/hooks";
import { QWeb } from "../../src/qweb/qweb";
import { xml } from "../../src/tags";
import { useState, useRef } from "../../src/hooks";
import { EventBus } from "../../src/core/event_bus";
import {
editInput,
makeDeferred,
makeTestFixture,
makeTestEnv,
makeTestFixture,
nextMicroTick,
nextTick,
normalize,
editInput
} from "../helpers";
//------------------------------------------------------------------------------
@@ -39,7 +39,7 @@ afterEach(() => {
function children(w: Component): Component[] {
const childrenMap = w.__owl__.children;
return Object.keys(childrenMap).map(id => childrenMap[id]);
return Object.keys(childrenMap).map((id) => childrenMap[id]);
}
// Test components
@@ -133,7 +133,7 @@ describe("basic widget properties", () => {
static template = xml`
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
state = useState({
counter: 0
counter: 0,
});
}
@@ -167,7 +167,7 @@ describe("basic widget properties", () => {
static template = xml`
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
state = useState({
counter: 0
counter: 0,
});
}
@@ -537,7 +537,7 @@ describe("lifecycle hooks", () => {
"child:mounted",
"parent:willUnmount",
"child:willUnmount",
"childchild:willUnmount"
"childchild:willUnmount",
]);
});
@@ -589,7 +589,7 @@ describe("lifecycle hooks", () => {
"childchild:willPatch",
"childchild:patched",
"child:patched",
"parent:patched"
"parent:patched",
]);
});
@@ -782,7 +782,7 @@ describe("lifecycle hooks", () => {
"c mounted",
"p mounted",
"p willunmount",
"c willunmount"
"c willunmount",
]);
});
@@ -960,7 +960,7 @@ describe("lifecycle hooks", () => {
"parent:willPatch",
"child:willPatch",
"child:patched",
"parent:patched"
"parent:patched",
]);
});
});
@@ -1134,7 +1134,7 @@ describe("composition", () => {
class App extends Component {
static template = xml`<t t-component="myComponent" t-key="state.child"/>`;
state = useState({
child: "a"
child: "a",
});
get myComponent() {
return this.state.child === "a" ? A : B;
@@ -1158,7 +1158,7 @@ describe("composition", () => {
class App extends Component {
static template = xml`<t t-component="myComponent" t-key="state.child"/>`;
state = useState({
child: "a"
child: "a",
});
get myComponent() {
return this.state.child === "a" ? A : B;
@@ -1228,7 +1228,7 @@ describe("composition", () => {
static template = xml`
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
state = useState({
counter: 0
counter: 0,
});
}
@@ -1327,7 +1327,7 @@ describe("composition", () => {
static template = xml`
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
state = useState({
counter: 0
counter: 0,
});
}
@@ -1350,7 +1350,7 @@ describe("composition", () => {
static template = xml`
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
state = useState({
counter: 0
counter: 0,
});
}
@@ -1389,7 +1389,7 @@ describe("composition", () => {
);
class Parent extends Component {
state = useState({
numbers: [1, 2, 3]
numbers: [1, 2, 3],
});
static components = { ChildWidget };
}
@@ -1429,7 +1429,7 @@ describe("composition", () => {
class Parent extends Component {
static template = "parent";
state = useState({
numbers: [1, 2, 3]
numbers: [1, 2, 3],
});
static components = { ChildWidget };
}
@@ -1513,8 +1513,8 @@ describe("composition", () => {
blips: [
{ a: "a", id: 1 },
{ b: "b", id: 2 },
{ c: "c", id: 4 }
]
{ c: "c", id: 4 },
],
});
}
const parent = new Parent();
@@ -1621,8 +1621,8 @@ describe("composition", () => {
records: [
{ id: 1, val: 1 },
{ id: 2, val: 2 },
{ id: 3, val: 3 }
]
{ id: 3, val: 3 },
],
});
static components = { ChildWidget };
}
@@ -2003,7 +2003,7 @@ describe("other directives with t-component", () => {
}
const widget = new ParentWidget();
await widget.mount(fixture);
(<HTMLElement>fixture).addEventListener("ev", function(e) {
(<HTMLElement>fixture).addEventListener("ev", function (e) {
steps.push(e.defaultPrevented);
});
@@ -2038,7 +2038,7 @@ describe("other directives with t-component", () => {
}
const widget = new ParentWidget();
await widget.mount(fixture);
(<HTMLElement>fixture).addEventListener("ev", function(e) {
(<HTMLElement>fixture).addEventListener("ev", function (e) {
steps.push(e.defaultPrevented);
});
@@ -2976,7 +2976,7 @@ describe("random stuff/miscellaneous", () => {
"D:mounted",
"C:mounted",
"B:mounted",
"A:mounted"
"A:mounted",
]);
// update
@@ -2999,7 +2999,7 @@ describe("random stuff/miscellaneous", () => {
"E:destroy",
"F:mounted",
"D:patched",
"C:patched"
"C:patched",
]);
});
@@ -3080,6 +3080,15 @@ describe("can deduce template from name", () => {
expect(fixture.innerHTML).toBe("<span>Orval</span>");
});
test("can find template of anonymous component", async () => {
class ABC extends Component {}
const Anonymous = class extends ABC {};
env.qweb.addTemplate("ABC", "<span>Orval</span>");
const def = new Anonymous();
await def.mount(fixture);
expect(fixture.innerHTML).toBe("<span>Orval</span>");
});
test("can find template of parent component, defined by template key", async () => {
class ABC extends Component {
static template = "Achel";
@@ -3401,7 +3410,7 @@ describe("t-model directive", () => {
state = useState([
{ f: false, id: 1 },
{ f: false, id: 2 },
{ f: false, id: 3 }
{ f: false, id: 3 },
]);
}
const comp = new SomeComponent();
@@ -3446,7 +3455,7 @@ describe("environment and plugins", () => {
let bus = new EventBus();
// definition of a plugin
const somePlugin = env => {
const somePlugin = (env) => {
env.someFlag = true;
bus.on("some-event", null, () => {
env.someFlag = !env.someFlag;
@@ -3475,11 +3484,14 @@ describe("environment and plugins", () => {
});
test("can define specific env for root components", async () => {
class App1 extends Component {
interface AppEnv extends Env {
test: number;
}
class App1 extends Component<{}, AppEnv> {
static template = xml`<span></span>`;
}
App1.env = { test: 1 };
class App2 extends Component {
class App2 extends Component<{}, AppEnv> {
static template = xml`<span></span>`;
}
App2.env = { test: 2 };
@@ -3813,7 +3825,9 @@ describe("t-call", () => {
expect(fixture.innerHTML).toBe("<div><p>lucas</p></div>");
fixture.querySelector("p")!.click();
expect(env.qweb.subTemplates["sub"].toString()).toMatchSnapshot();
const subId = env.qweb.subTemplates["sub"];
const subFn = QWeb.subTemplates[subId] as any;
expect(subFn.toString()).toMatchSnapshot();
});
test("parent is set within t-call", async () => {
@@ -3835,7 +3849,10 @@ describe("t-call", () => {
expect(fixture.innerHTML).toBe("<div><span>lucas</span></div>");
expect(child.__owl__.parent).toBe(parent);
expect(env.qweb.subTemplates["sub"].toString()).toMatchSnapshot();
const subId = env.qweb.subTemplates["sub"];
const subFn = QWeb.subTemplates[subId] as any;
expect(subFn.toString()).toMatchSnapshot();
});
test("t-call in t-foreach and children component", async () => {
@@ -3877,7 +3894,9 @@ describe("t-call", () => {
expect(fixture.innerHTML).toBe("<span>lucas</span>");
expect(child.__owl__.parent).toBe(parent);
expect(env.qweb.subTemplates["sub"].toString()).toMatchSnapshot();
const subId = env.qweb.subTemplates["sub"];
const subFn = QWeb.subTemplates[subId] as any;
expect(subFn.toString()).toMatchSnapshot();
});
test("handlers with arguments are properly bound through a t-call", async () => {
@@ -3893,7 +3912,9 @@ describe("t-call", () => {
}
const parent = new Parent();
await parent.mount(fixture);
expect(env.qweb.subTemplates["sub"].toString()).toMatchSnapshot();
const subId = env.qweb.subTemplates["sub"];
const subFn = QWeb.subTemplates[subId] as any;
expect(subFn.toString()).toMatchSnapshot();
fixture.querySelector("p")!.click();
});
+15 -15
View File
@@ -92,7 +92,7 @@ describe("props validation", () => {
{ type: String, ok: "1", ko: 1 },
{ type: Object, ok: {}, ko: "1" },
{ type: Date, ok: new Date(), ko: "1" },
{ type: Function, ok: () => {}, ko: "1" }
{ type: Function, ok: () => {}, ko: "1" },
];
let props;
@@ -149,7 +149,7 @@ describe("props validation", () => {
{ type: String, ok: "1", ko: 1 },
{ type: Object, ok: {}, ko: "1" },
{ type: Date, ok: new Date(), ko: "1" },
{ type: Function, ok: () => {}, ko: "1" }
{ type: Function, ok: () => {}, ko: "1" },
];
let props;
@@ -396,7 +396,7 @@ describe("props validation", () => {
class TestWidget extends Component {
static template = xml`<div>hey</div>`;
static props = {
p: { type: Object, shape: { id: Number, url: String } }
p: { type: Object, shape: { id: Number, url: String } },
};
}
class Parent extends Component {
@@ -458,9 +458,9 @@ describe("props validation", () => {
type: Object,
shape: {
id: Number,
url: [Boolean, { type: Array, element: Number }]
}
}
url: [Boolean, { type: Array, element: Number }],
},
},
};
}
class Parent extends Component {
@@ -510,10 +510,10 @@ describe("props validation", () => {
element: {
type: Object,
shape: {
num: { type: Number, optional: true }
}
}
}
num: { type: Number, optional: true },
},
},
},
};
}
let error;
@@ -539,8 +539,8 @@ describe("props validation", () => {
class TestComponent extends Component {
static props = {
size: {
validate: e => ["small", "medium", "large"].includes(e)
}
validate: (e) => ["small", "medium", "large"].includes(e),
},
};
}
let error;
@@ -561,13 +561,13 @@ describe("props validation", () => {
});
test("can validate with a custom validator, and a type", () => {
const validator = jest.fn(n => 0 <= n && n <= 10);
const validator = jest.fn((n) => 0 <= n && n <= 10);
class TestComponent extends Component {
static props = {
n: {
type: Number,
validate: validator
}
validate: validator,
},
};
}
let error;
+71 -15
View File
@@ -28,7 +28,7 @@ afterEach(() => {
function children(w: Component): Component[] {
const childrenMap = w.__owl__.children;
return Object.keys(childrenMap).map(id => childrenMap[id]);
return Object.keys(childrenMap).map((id) => childrenMap[id]);
}
//------------------------------------------------------------------------------
@@ -41,8 +41,8 @@ describe("t-slot directive", () => {
<templates>
<div t-name="Parent">
<Dialog>
<t t-set="header"><span>header</span></t>
<t t-set="footer"><span>footer</span></t>
<t t-set-slot="header"><span>header</span></t>
<t t-set-slot="footer"><span>footer</span></t>
</Dialog>
</div>
<div t-name="Dialog">
@@ -67,6 +67,38 @@ describe("t-slot directive", () => {
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
});
test("can define and call slots using old t-set keyword", async () => {
// NOTE: this test should be removed once we stop supporting the t-set directive
// for defining slot content.
class Dialog extends Component {
static template = xml`
<div>
<div><t t-slot="header"/></div>
<div><t t-slot="footer"/></div>
</div>`;
}
class Parent extends Component {
static template = xml`
<div>
<Dialog>
<t t-set="header"><span>header</span></t>
<t t-set="footer"><span>footer</span></t>
</Dialog>
</div>`;
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe(
"<div><div><div><span>header</span></div><div><span>footer</span></div></div></div>"
);
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot();
expect(QWeb.slots["1_header"].toString()).toMatchSnapshot();
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
});
test("named slots can define a default content", async () => {
class Dialog extends Component {
static template = xml`
@@ -128,7 +160,7 @@ describe("t-slot directive", () => {
</span>`;
}
class Parent extends Component {
static template = xml`<div><Dialog><t t-set="header">hey</t></Dialog></div>`;
static template = xml`<div><Dialog><t t-set-slot="header">hey</t></Dialog></div>`;
static components = { Dialog };
}
const parent = new Parent();
@@ -143,7 +175,7 @@ describe("t-slot directive", () => {
<div t-name="Parent">
<span class="counter"><t t-esc="state.val"/></span>
<Dialog>
<t t-set="footer"><button t-on-click="doSomething">do something</button></t>
<t t-set-slot="footer"><button t-on-click="doSomething">do something</button></t>
</Dialog>
</div>
<span t-name="Dialog"><t t-slot="footer"/></span>
@@ -192,8 +224,8 @@ describe("t-slot directive", () => {
state = useState({
users: [
{ id: 1, name: "Aaron" },
{ id: 2, name: "David" }
]
{ id: 2, name: "David" },
],
});
static components = { Link };
}
@@ -236,8 +268,8 @@ describe("t-slot directive", () => {
state = useState({
users: [
{ id: 1, name: "Aaron" },
{ id: 2, name: "David" }
]
{ id: 2, name: "David" },
],
});
static components = { Link };
}
@@ -302,7 +334,7 @@ describe("t-slot directive", () => {
<div>
<span class="counter"><t t-esc="state.val"/></span>
<Dialog>
<t t-set="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
<t t-set-slot="footer"><button t-ref="myButton" t-on-click="doSomething">do something</button></t>
</Dialog>
</div>
`;
@@ -376,7 +408,7 @@ describe("t-slot directive", () => {
<templates>
<div t-name="Parent">
<Dialog>
<t t-set="content">
<t t-set-slot="content">
<span>sts</span>
<span>rocks</span>
</t>
@@ -442,14 +474,14 @@ describe("t-slot directive", () => {
expect(fixture.innerHTML).toBe("<div><span><span>some content</span></span></div>");
});
test("t-debug on a t-set (defining a slot)", async () => {
test("t-debug on a t-set-slot (defining a slot)", async () => {
const consoleLog = console.log;
console.log = jest.fn();
env.qweb.addTemplates(`
<templates>
<div t-name="Parent">
<Dialog><t t-set="content" t-debug="">abc</t></Dialog>
<Dialog><t t-set-slot="content" t-debug="">abc</t></Dialog>
</div>
<span t-name="Dialog">
<t t-slot="content"/>
@@ -623,8 +655,8 @@ describe("t-slot directive", () => {
class A extends Component {
static template = xml`
<B>
<t t-set="s1"><C val="1"/></t>
<t t-set="s2"><C val="2"/></t>
<t t-set-slot="s1"><C val="1"/></t>
<t t-set-slot="s2"><C val="2"/></t>
</B>`;
static components = { B, C };
}
@@ -873,4 +905,28 @@ describe("t-slot directive", () => {
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><div><span><p>heyaaron</p></span></div></div>");
});
test("t-set t-value in a slot", async () => {
class Dialog extends Component {
static template = xml`
<span>
<t t-slot="default"/>
</span>`;
}
class Parent extends Component {
static template = xml`
<div>
<Dialog>
<t t-set="rainbow" t-value="'dash'"/>
<t t-esc="rainbow"/>
</Dialog>
</div>`;
static components = { Dialog };
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>dash</span></div>");
expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot();
});
});
+42 -3
View File
@@ -2,6 +2,7 @@ import { Component, Env } from "../../src/component/component";
import { useState } from "../../src/hooks";
import { xml } from "../../src/tags";
import { makeDeferred, makeTestEnv, makeTestFixture, nextTick, nextMicroTick } from "../helpers";
import { scheduler } from "../../src/component/scheduler";
//------------------------------------------------------------------------------
// Setup and helpers
@@ -28,14 +29,34 @@ afterEach(() => {
describe("mount targets", () => {
test("can attach a component to an existing node (if same tagname)", async () => {
class App extends Component {
static template = xml`<div>app</div>`;
static template = xml`<div t-att-class="state.customClass">app<p>another tag</p></div>`;
state = useState({ customClass: "custom" });
}
const div = document.createElement("div");
div.classList.add("arbitrary");
div.innerHTML = `<p>pre-existing</p>`;
fixture.appendChild(div);
const app = new App();
await app.mount(div, { position: "self" });
expect(fixture.innerHTML).toBe("<div>app</div>");
expect(fixture.innerHTML).toBe(
`<div class="arbitrary custom"><p>pre-existing</p>app<p>another tag</p></div>`
);
expect(div).toBe(app.el);
app.state.customClass = "custom2";
await nextTick();
expect(fixture.innerHTML).toBe(
`<div class="arbitrary custom2"><p>pre-existing</p>app<p>another tag</p></div>`
);
expect(div).toBe(app.el);
app.unmount();
// This assert is a best guess
// The use case it covers was not really thought through
// and may change in the future
expect(fixture.innerHTML).toBe("");
});
test("cannot attach a component to an existing node (if not same tagname)", async () => {
@@ -315,7 +336,7 @@ describe("unmounting and remounting", () => {
class Child extends Component {
static template = xml`<span t-esc="state.val"/>`;
state = useState({
val: "C1"
val: "C1",
});
constructor(parent, props) {
super(parent, props);
@@ -470,4 +491,22 @@ describe("unmounting and remounting", () => {
expect(steps).toEqual(["1 resolved", "2 resolved"]);
expect(fixture.innerHTML).toBe("<div>Hey</div>");
});
test("mounting a destroyed widget", async () => {
class MyWidget extends Component {
static template = xml`<div>Hey</div>`;
}
const w = new MyWidget();
w.destroy(); // because, why not
let error;
try {
await w.mount(fixture);
} catch (e) {
error = e;
}
expect(scheduler.tasks.length).toBe(0);
expect(error).toBeDefined();
expect(error.message).toBe("Cannot mount a destroyed component");
});
});
+1 -1
View File
@@ -14,7 +14,7 @@ describe("event bus behaviour", () => {
expect.assertions(1);
const bus = new EventBus();
const owner = {};
bus.on("event", owner, function(this: any) {
bus.on("event", owner, function (this: any) {
expect(this).toBe(owner);
});
bus.trigger("event");
+2 -2
View File
@@ -22,7 +22,7 @@ describe("observer", () => {
expect(observer.rev).toBe(2);
expect(obj2).toEqual({
a: 2
a: 2,
});
});
@@ -47,7 +47,7 @@ describe("observer", () => {
expect(observer.rev).toBe(5);
expect(obj).toEqual({
a: null,
b: undefined
b: undefined,
});
});
+5 -3
View File
@@ -6,9 +6,10 @@ import { patch } from "../src/vdom";
import "../src/qweb/base_directives";
import "../src/qweb/extensions";
import "../src/component/directive";
import { browser } from "../src/browser";
// modifies scheduler to make it faster to test components
scheduler.requestAnimationFrame = function(callback: FrameRequestCallback) {
scheduler.requestAnimationFrame = function (callback: FrameRequestCallback) {
setTimeout(callback, 1);
return 1;
};
@@ -42,7 +43,7 @@ export function nextMicroTick(): Promise<void> {
}
export async function nextTick(): Promise<void> {
return new Promise(function(resolve) {
return new Promise(function (resolve) {
setTimeout(() => scheduler.requestAnimationFrame(() => resolve()));
});
}
@@ -75,7 +76,8 @@ export function makeDeferred(): Deferred {
export function makeTestEnv(): Env {
return {
qweb: new QWeb()
qweb: new QWeb(),
browser: browser,
};
}
+40 -7
View File
@@ -10,7 +10,7 @@ import {
onWillStart,
onWillUpdateProps,
useSubEnv,
useExternalListener
useExternalListener,
} from "../src/hooks";
import { xml } from "../src/tags";
@@ -214,7 +214,7 @@ describe("hooks", () => {
"hook:mounted1",
"hook:mounted2",
"hook:willunmount2",
"hook:willunmount1"
"hook:willunmount1",
]);
});
@@ -267,7 +267,7 @@ describe("hooks", () => {
class WidgetC extends Component {
static template = xml`<div class="outer-div">Hello<WidgetB t-ref="mywidgetb" /></div>`;
static components = { WidgetB };
ref = useRef("mywidgetb");
ref = useRef<WidgetB>("mywidgetb");
}
const widget = new WidgetC();
@@ -559,20 +559,39 @@ describe("hooks", () => {
test("can use onWillStart, onWillUpdateProps", async () => {
const steps: string[] = [];
async function slow(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve("slow");
}, 0);
});
}
function useMyHook() {
onWillStart(() => {
onWillStart(async () => {
steps.push(await slow());
steps.push("onWillStart");
});
onWillUpdateProps(nextProps => {
onWillUpdateProps(async (nextProps) => {
expect(nextProps).toEqual({ value: 2 });
steps.push(await slow());
steps.push("onWillUpdateProps");
});
}
function use2ndHook() {
onWillStart(() => {
steps.push("on2ndStart");
});
onWillUpdateProps((nextProps) => {
expect(nextProps).toEqual({ value: 2 });
steps.push("on2ndUpdate");
});
}
class MyComponent extends Component {
static template = xml`<span><t t-esc="props.value"/></span>`;
constructor(parent, props) {
super(parent, props);
useMyHook();
use2ndHook();
}
}
class App extends Component {
@@ -586,12 +605,26 @@ describe("hooks", () => {
expect(app).not.toHaveProperty("willStart");
expect(app).not.toHaveProperty("willUpdateProps");
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
expect(steps).toEqual(["onWillStart"]);
// NOTE: 'on2ndStart' appears first in the list even though
// the 'use2ndHook' is declared after 'useMyHook'. This is
// because Promise.all is used to call the callbacks specified
// in the hooks, which runs them simultaneously.
// Additionally, 'slow' should be listed before 'onWillStart'
// because call to `slow` is awaited.
expect(steps).toEqual(["on2ndStart", "slow", "onWillStart"]);
app.state.value = 2;
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>2</span></div>");
expect(steps).toEqual(["onWillStart", "onWillUpdateProps"]);
expect(steps).toEqual([
"on2ndStart",
"slow",
"onWillStart",
"on2ndUpdate",
"slow",
"onWillUpdateProps",
]);
});
test("useExternalListener", async () => {
+4 -4
View File
@@ -486,7 +486,7 @@ describe("Portal: Basic use and DOM placement", () => {
"parent:mounted",
"parent:willPatch",
"child:mounted",
"parent:patched"
"parent:patched",
]);
parent.state.val = 2;
@@ -499,7 +499,7 @@ describe("Portal: Basic use and DOM placement", () => {
"parent:willPatch",
"child:willPatch",
"child:patched",
"parent:patched"
"parent:patched",
]);
parent.state.hasChild = false;
@@ -515,7 +515,7 @@ describe("Portal: Basic use and DOM placement", () => {
"parent:patched",
"parent:willPatch",
"child:willUnmount",
"parent:patched"
"parent:patched",
]);
});
@@ -741,7 +741,7 @@ describe("Portal: Events handling", () => {
steps.push(ev.type as string);
}
}
const bodyListener = ev => {
const bodyListener = (ev) => {
steps.push(`body: ${ev.type}`);
};
document.body.addEventListener("click", bodyListener);
+227 -113
View File
@@ -1,5 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`attributes class and t-att-class should combine together 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"test\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let _1 = utils.toObj(scope['value']);
Object.assign(_1, {'hello':true})
let c3 = [], p3 = {key:3,class:_1};
let vn3 = h('div', p3, c3);
return vn3;
}"
`;
exports[`attributes class and t-attf-class with ternary operation 1`] = `
"function anonymous(context, extra
) {
@@ -734,7 +749,7 @@ exports[`loading templates can load a few templates from a xml string 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('ul', p1, c1);
this.subTemplates['items'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
return vn1;
}"
`;
@@ -774,35 +789,37 @@ exports[`misc global 1`] = `
}
{
let _origScope10 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
{
let _origScope13 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'foo').foo = 'aaa';
scope[utils.zero] = c__0;
}
let k14 = \`__14__\${key1}__\`;
this.subTemplates['_callee-uses-foo'].call(this, scope, Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k14}));
this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k14}));
scope = _origScope13;
}
let k15 = \`__15__\${key1}__\`;
this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k15}));
this.constructor.subTemplates['2'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k15}));
utils.getScope(scope, 'foo').foo = 'bbb';
let k16 = \`__16__\${key1}__\`;
this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k16}));
this.constructor.subTemplates['2'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c__0, parent: utils.getComponent(context), key: k16}));
scope[utils.zero] = c__0;
}
let k17 = \`__17__\${key1}__\`;
this.subTemplates['_callee-asc'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: k17}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: k17}));
scope = _origScope10;
}
}
scope = _origScope5;
this.subTemplates['_callee-asc-toto'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__20__'}));
this.constructor.subTemplates['3'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__20__'}));
return vn1;
}"
`;
@@ -1091,25 +1108,11 @@ exports[`t-call (template calling basic caller 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.subTemplates['_basic-callee'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
return vn1;
}"
`;
exports[`t-call (template calling basic caller 2`] = `
"function anonymous(context, extra
) {
// Template name: \\"_basic-callee\\"
let h = this.h;
let c1 = extra.parentNode;
let key0 = extra.key || \\"\\";
let c2 = [], p2 = {key:\`\${key0}_2\`};
let vn2 = h('span', p2, c2);
c1.push(vn2);
c2.push({text: \`ok\`});
}"
`;
exports[`t-call (template calling basic caller, no parent node 1`] = `
"function anonymous(context, extra
) {
@@ -1119,26 +1122,12 @@ exports[`t-call (template calling basic caller, no parent node 1`] = `
let result;
let h = this.h;
result = []
this.subTemplates['_basic-callee'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__3__'}));
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__3__'}));
result = result[0]
return result;
}"
`;
exports[`t-call (template calling basic caller, no parent node 2`] = `
"function anonymous(context, extra
) {
// Template name: \\"_basic-callee\\"
let h = this.h;
let c1 = extra.parentNode;
let key0 = extra.key || \\"\\";
let c2 = [], p2 = {key:\`\${key0}_2\`};
let vn2 = h('div', p2, c2);
c1.push(vn2);
c2.push({text: \`ok\`});
}"
`;
exports[`t-call (template calling call with several sub nodes on same line 1`] = `
"function anonymous(context, extra
) {
@@ -1150,7 +1139,8 @@ exports[`t-call (template calling call with several sub nodes on same line 1`] =
let vn1 = h('div', p1, c1);
{
let _origScope3 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
let c4 = [], p4 = {key:4};
@@ -1164,28 +1154,14 @@ exports[`t-call (template calling call with several sub nodes on same line 1`] =
c5.push({text: \`yay\`});
scope[utils.zero] = c__0;
}
this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'}));
scope = _origScope3;
}
return vn1;
}"
`;
exports[`t-call (template calling call with several sub nodes on same line 2`] = `
"function anonymous(context, extra
) {
// Template name: \\"SubTemplate\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = extra.parentNode;
let key0 = extra.key || \\"\\";
let c2 = [], p2 = {key:\`\${key0}_2\`};
let vn2 = h('div', p2, c2);
c1.push(vn2);
c2.push(...scope[utils.zero]);
}"
`;
exports[`t-call (template calling call with several sub nodes on same line 2`] = `"1"`;
exports[`t-call (template calling cascading t-call t-raw='0' 1`] = `
"function anonymous(context, extra
@@ -1198,7 +1174,8 @@ exports[`t-call (template calling cascading t-call t-raw='0' 1`] = `
let vn1 = h('div', p1, c1);
{
let _origScope12 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
let c13 = [], p13 = {key:13};
@@ -1212,7 +1189,7 @@ exports[`t-call (template calling cascading t-call t-raw='0' 1`] = `
c14.push({text: \`yay\`});
scope[utils.zero] = c__0;
}
this.subTemplates['SubTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__15__'}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__15__'}));
scope = _origScope12;
}
return vn1;
@@ -1229,7 +1206,7 @@ exports[`t-call (template calling inherit context 1`] = `
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
scope.foo = 1;
this.subTemplates['_callee-uses-foo'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__2__'}));
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__2__'}));
return vn1;
}"
`;
@@ -1248,7 +1225,7 @@ exports[`t-call (template calling recursive template, part 1 1`] = `
c1.push(vn2);
c2.push({text: \`hey\`});
if (false) {
this.subTemplates['recursive'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'}));
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__6__'}));
}
return vn1;
}"
@@ -1272,7 +1249,7 @@ exports[`t-call (template calling recursive template, part 1 2`] = `
c4.push({text: \`hey\`});
if (false) {
let k5 = \`__5__\${key0}__\`;
this.subTemplates['recursive'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c3, parent: utils.getComponent(context), key: k5}));
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c3, parent: utils.getComponent(context), key: k5}));
}
}"
`;
@@ -1288,13 +1265,14 @@ exports[`t-call (template calling recursive template, part 2 1`] = `
let vn1 = h('div', p1, c1);
{
let _origScope11 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'node').node = scope['root'];
scope[utils.zero] = c__0;
}
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
scope = _origScope11;
}
return vn1;
@@ -1339,14 +1317,15 @@ exports[`t-call (template calling recursive template, part 2 2`] = `
let key1 = i1;
{
let _origScope9 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'node').node = scope['subtree'];
scope[utils.zero] = c__0;
}
let k10 = \`__10__\${key0}__\${key1}__\`;
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c2, parent: utils.getComponent(context), key: k10}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c2, parent: utils.getComponent(context), key: k10}));
scope = _origScope9;
}
}
@@ -1365,13 +1344,14 @@ exports[`t-call (template calling recursive template, part 3 1`] = `
let vn1 = h('div', p1, c1);
{
let _origScope11 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'node').node = scope['root'];
scope[utils.zero] = c__0;
}
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
scope = _origScope11;
}
return vn1;
@@ -1416,14 +1396,100 @@ exports[`t-call (template calling recursive template, part 3 2`] = `
let key1 = i1;
{
let _origScope9 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'node').node = scope['subtree'];
scope[utils.zero] = c__0;
}
let k10 = \`__10__\${key0}__\${key1}__\`;
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c2, parent: utils.getComponent(context), key: k10}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c2, parent: utils.getComponent(context), key: k10}));
scope = _origScope9;
}
}
scope = _origScope8;
}"
`;
exports[`t-call (template calling recursive template, part 4: with t-set recursive index 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"Parent\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
{
let _origScope11 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'recursive_idx').recursive_idx = 1;
utils.getScope(scope, 'node').node = scope['root'];
scope[utils.zero] = c__0;
}
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
scope = _origScope11;
}
return vn1;
}"
`;
exports[`t-call (template calling recursive template, part 4: with t-set recursive index 2`] = `
"function anonymous(context, extra
) {
// Template name: \\"nodeTemplate\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = extra.parentNode;
let key0 = extra.key || \\"\\";
let c2 = [], p2 = {key:\`\${key0}_2\`};
let vn2 = h('div', p2, c2);
c1.push(vn2);
scope.recursive_idx = scope.recursive_idx+1;
let c3 = [], p3 = {key:\`\${key0}_3\`};
let vn3 = h('p', p3, c3);
c2.push(vn3);
let _4 = scope['node'].val;
if (_4 != null) {
c3.push({text: _4});
}
c3.push({text: \` \`});
if (scope.recursive_idx != null) {
c3.push({text: scope.recursive_idx});
}
let _5 = scope['node'].children||[];
if (!_5) { throw new Error('QWeb error: Invalid loop expression')}
let _6 = _7 = _5;
if (!(_5 instanceof Array)) {
_6 = Object.keys(_5);
_7 = Object.values(_5);
}
let _length6 = _6.length;
let _origScope8 = scope;
scope = Object.create(scope);
for (let i1 = 0; i1 < _length6; i1++) {
scope.subtree_first = i1 === 0
scope.subtree_last = i1 === _length6 - 1
scope.subtree_index = i1
scope.subtree = _6[i1]
scope.subtree_value = _7[i1]
let key1 = i1;
{
let _origScope9 = scope;
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'node').node = scope['subtree'];
scope[utils.zero] = c__0;
}
let k10 = \`__10__\${key0}__\${key1}__\`;
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c2, parent: utils.getComponent(context), key: k10}));
scope = _origScope9;
}
}
@@ -1442,13 +1508,14 @@ exports[`t-call (template calling scoped parameters 1`] = `
let vn1 = h('div', p1, c1);
{
let _origScope2 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'foo').foo = 42;
scope[utils.zero] = c__0;
}
this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
scope = _origScope2;
}
if (scope.foo != null) {
@@ -1468,26 +1535,12 @@ exports[`t-call (template calling t-call with t-if 1`] = `
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
if (scope['flag']) {
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
}
return vn1;
}"
`;
exports[`t-call (template calling t-call with t-if 2`] = `
"function anonymous(context, extra
) {
// Template name: \\"sub\\"
let h = this.h;
let c1 = extra.parentNode;
let key0 = extra.key || \\"\\";
let c2 = [], p2 = {key:\`\${key0}_2\`};
let vn2 = h('span', p2, c2);
c1.push(vn2);
c2.push({text: \`ok\`});
}"
`;
exports[`t-call (template calling t-call with t-set inside and outside 1`] = `
"function anonymous(context, extra
) {
@@ -1517,14 +1570,15 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = `
utils.getScope(scope, 'val').val = scope['v'].val;
{
let _origScope8 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'val3').val3 = scope.val*3;
scope[utils.zero] = c__0;
}
let k9 = \`__9__\${key1}__\`;
this.subTemplates['sub'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: k9}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: k9}));
scope = _origScope8;
}
}
@@ -1543,7 +1597,7 @@ exports[`t-call (template calling t-call with t-set inside and outside. 2 1`] =
let c1 = [], p1 = {key:1};
let vn1 = h('p', p1, c1);
scope.w = 'fromwrapper';
this.subTemplates['main'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__11__'}));
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__11__'}));
return vn1;
}"
`;
@@ -1559,18 +1613,19 @@ exports[`t-call (template calling t-call, conditional and t-set in t-call body 1
let vn1 = h('div', p1, c1);
scope.v1 = 'elif';
if (scope.v1==='if') {
this.subTemplates['callee1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
}
else if (scope.v1==='elif') {
{
let _origScope6 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'v').v = 'success';
scope[utils.zero] = c__0;
}
this.subTemplates['callee2'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__7__'}));
this.constructor.subTemplates['2'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__7__'}));
scope = _origScope6;
}
}
@@ -1587,7 +1642,35 @@ exports[`t-call (template calling t-call, global templates 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.subTemplates['john'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
return vn1;
}"
`;
exports[`t-call (template calling two different QWeb instances, and shared templates 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"main\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
return vn1;
}"
`;
exports[`t-call (template calling two different QWeb instances, and shared templates 2`] = `
"function anonymous(context, extra
) {
// Template name: \\"main\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__3__'}));
return vn1;
}"
`;
@@ -1602,14 +1685,15 @@ exports[`t-call (template calling with unused body 1`] = `
let h = this.h;
{
let _origScope3 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
c__0.push({text: \`WHEEE\`});
scope[utils.zero] = c__0;
}
result = []
this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
result = result[0]
scope = _origScope3;
}
@@ -1627,14 +1711,15 @@ exports[`t-call (template calling with unused setbody 1`] = `
let h = this.h;
{
let _origScope3 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'qux').qux = 3;
scope[utils.zero] = c__0;
}
result = []
this.subTemplates['_basic-callee'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
result = result[0]
scope = _origScope3;
}
@@ -1652,14 +1737,15 @@ exports[`t-call (template calling with used body 1`] = `
let h = this.h;
{
let _origScope3 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
c__0.push({text: \`ok\`});
scope[utils.zero] = c__0;
}
result = []
this.subTemplates['_callee-printsbody'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: result, parent: utils.getComponent(context), key: '__4__'}));
result = result[0]
scope = _origScope3;
}
@@ -1678,13 +1764,14 @@ exports[`t-call (template calling with used set body 1`] = `
let vn1 = h('span', p1, c1);
{
let _origScope3 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
utils.getScope(scope, 'foo').foo = 'ok';
scope[utils.zero] = c__0;
}
this.subTemplates['_callee-uses-foo'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
scope = _origScope3;
}
return vn1;
@@ -1822,6 +1909,23 @@ exports[`t-esc literal 1`] = `
}"
`;
exports[`t-esc t-esc inside t-call, with t-set outside 1`] = `
"function anonymous(context, extra
) {
// Template name: \\"main\\"
let utils = this.constructor.utils;
let scope = Object.create(context);
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
let c2 = new utils.VDomArray();
c2.push({text: \`Hi\`});
scope.v = c2
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
return vn1;
}"
`;
exports[`t-esc t-esc is escaped 1`] = `
"function anonymous(context, extra
) {
@@ -1839,7 +1943,8 @@ exports[`t-esc t-esc is escaped 1`] = `
scope.var = c2
if (scope.var != null) {
let _origScope4 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
scope.var = scope.var instanceof utils.VDomArray ? utils.vDomToString(scope.var) : scope.var;
c1.push({text: scope.var});
scope = _origScope4;
@@ -1875,7 +1980,8 @@ exports[`t-esc t-esc=0 is escaped 1`] = `
let vn1 = h('div', p1, c1);
{
let _origScope3 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
{
let c__0 = [];
let c4 = [], p4 = {key:4};
@@ -1884,7 +1990,7 @@ exports[`t-esc t-esc=0 is escaped 1`] = `
c4.push({text: \`escaped\`});
scope[utils.zero] = c__0;
}
this.subTemplates['test'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
this.constructor.subTemplates['1'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
scope = _origScope3;
}
return vn1;
@@ -2680,7 +2786,7 @@ exports[`t-on t-on with t-call 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
return vn1;
}"
`;
@@ -2694,7 +2800,7 @@ exports[`t-on t-on, with arguments and t-call 1`] = `
let h = this.h;
let c1 = [], p1 = {key:1};
let vn1 = h('div', p1, c1);
this.subTemplates['sub'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
this.constructor.subTemplates['1'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__4__'}));
return vn1;
}"
`;
@@ -2948,7 +3054,8 @@ exports[`t-set set from body literal 1`] = `
scope.value = c1
if (scope.value != null) {
let _origScope2 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
scope.value = scope.value instanceof utils.VDomArray ? utils.vDomToString(scope.value) : scope.value;
let vn3 = {text: scope.value};
result = vn3
@@ -2975,7 +3082,8 @@ exports[`t-set set from body lookup 1`] = `
scope.stuff = c2
if (scope.stuff != null) {
let _origScope4 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
scope.stuff = scope.stuff instanceof utils.VDomArray ? utils.vDomToString(scope.stuff) : scope.stuff;
c1.push({text: scope.stuff});
scope = _origScope4;
@@ -3123,7 +3231,8 @@ exports[`t-set t-set with content and sub t-esc 1`] = `
scope.setvar = c2
if (scope.setvar != null) {
let _origScope4 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
scope.setvar = scope.setvar instanceof utils.VDomArray ? utils.vDomToString(scope.setvar) : scope.setvar;
c1.push({text: scope.setvar});
scope = _origScope4;
@@ -3215,7 +3324,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 1`] = `
}
if (scope.ourvar != null) {
let _origScope3 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar;
c1.push({text: scope.ourvar});
scope = _origScope3;
@@ -3243,7 +3353,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 2`] = `
}
if (scope.ourvar != null) {
let _origScope3 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar;
c1.push({text: scope.ourvar});
scope = _origScope3;
@@ -3271,7 +3382,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 1`] = `
}
if (scope.ourvar != null) {
let _origScope3 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar;
c1.push({text: scope.ourvar});
scope = _origScope3;
@@ -3299,7 +3411,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 2`] = `
}
if (scope.ourvar != null) {
let _origScope3 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar;
c1.push({text: scope.ourvar});
scope = _origScope3;
@@ -3325,7 +3438,8 @@ exports[`t-set value priority 1`] = `
}
if (scope.value != null) {
let _origScope3 = scope;
scope = Object.assign(Object.create(context), scope);
scope = Object.create(scope);
scope.__access_mode__ = 'ro';
scope.value = scope.value instanceof utils.VDomArray ? utils.vDomToString(scope.value) : scope.value;
c1.push({text: scope.value});
scope = _origScope3;
+109 -49
View File
@@ -195,7 +195,7 @@ describe("t-esc", () => {
v2: undefined,
v3: null,
v4: 0,
v5: ""
v5: "",
};
expect(renderToString(qweb, "test", vals)).toBe(
"<div><p>false</p><p></p><p></p><p>0</p><p></p></div>"
@@ -207,6 +207,13 @@ describe("t-esc", () => {
const result = renderToString(qweb, "test", { state: { list: [1, 2] } });
expect(result).toBe("<span>1,2</span>");
});
test("t-esc inside t-call, with t-set outside", () => {
qweb.addTemplate("main", `<div><t t-set="v">Hi</t><t t-call="sub"/></div>`);
qweb.addTemplate("sub", `<span t-esc="v"/>`);
const result = renderToString(qweb, "main");
expect(result).toBe("<div><span>Hi</span></div>");
});
});
describe("t-raw", () => {
@@ -486,7 +493,7 @@ describe("t-if", () => {
cond3: false,
cond4: false,
m: 5,
n: 2
n: 2,
};
expect(normalize(renderToString(qweb, "test", context))).toBe("<div>andormgtnlt</div>");
});
@@ -625,7 +632,7 @@ describe("attributes", () => {
test("object", () => {
qweb.addTemplate("test", `<div t-att="value"/>`);
const result = renderToString(qweb, "test", {
value: { a: 1, b: 2, c: 3 }
value: { a: 1, b: 2, c: 3 },
});
expect(result).toBe(`<div a="1" b="2" c="3"></div>`);
});
@@ -684,7 +691,7 @@ describe("attributes", () => {
const result = renderToString(qweb, "test", {
value1: 0,
value2: 1,
value3: 2
value3: 2,
});
expect(result).toBe(`<div foo="a 0 is 1 of 2 ]"></div>`);
});
@@ -703,7 +710,7 @@ describe("attributes", () => {
const result = renderToString(qweb, "test", {
bar: 0,
baz: 1,
qux: { qux: "<>" }
qux: { qux: "<>" },
});
const expected = '<div foo="<foo" bar="0" baz="<1>" qux="<>"></div>';
expect(result).toBe(expected);
@@ -715,6 +722,12 @@ describe("attributes", () => {
expect(result).toBe(`<div class="hello world"></div>`);
});
test("class and t-att-class should combine together", () => {
qweb.addTemplate("test", `<div t-att-class="value" class="hello" />`);
const result = renderToString(qweb, "test", { value: "world" });
expect(result).toBe(`<div class="world hello"></div>`);
});
test("class and t-attf-class with ternary operation", () => {
qweb.addTemplate("test", `<div class="hello" t-attf-class="{{value ? 'world' : ''}}"/>`);
const result = renderToString(qweb, "test", { value: true });
@@ -734,7 +747,7 @@ describe("t-call (template calling", () => {
qweb.addTemplate("caller", '<div><t t-call="_basic-callee"/></div>');
const expected = "<div><span>ok</span></div>";
expect(renderToString(qweb, "caller")).toBe(expected);
expect(qweb.subTemplates["_basic-callee"].toString()).toMatchSnapshot();
expect(qweb.subTemplates["_basic-callee"]).toBeTruthy();
});
test("basic caller, no parent node", () => {
@@ -742,7 +755,7 @@ describe("t-call (template calling", () => {
qweb.addTemplate("caller", '<t t-call="_basic-callee"/>');
const expected = "<div>ok</div>";
expect(renderToString(qweb, "caller")).toBe(expected);
expect(qweb.subTemplates["_basic-callee"].toString()).toMatchSnapshot();
expect(qweb.subTemplates["_basic-callee"]).toBeTruthy();
});
test("t-call with t-if", () => {
@@ -750,7 +763,7 @@ describe("t-call (template calling", () => {
qweb.addTemplate("caller", '<div><t t-if="flag" t-call="sub"/></div>');
const expected = "<div><span>ok</span></div>";
expect(renderToString(qweb, "caller", { flag: true })).toBe(expected);
expect(qweb.subTemplates["sub"].toString()).toMatchSnapshot();
expect(qweb.subTemplates["sub"]).toBeTruthy();
});
test("t-call not allowed on a non t node", () => {
@@ -885,7 +898,8 @@ describe("t-call (template calling", () => {
`);
const expected = "<div><span>hey</span></div>";
expect(renderToString(qweb, "recursive")).toBe(expected);
const recursiveFn = Object.values(qweb.subTemplates)[0] as any;
const subId = qweb.subTemplates["recursive"];
const recursiveFn = QWeb.subTemplates[subId] as any;
expect(recursiveFn.toString()).toMatchSnapshot();
});
@@ -913,7 +927,8 @@ describe("t-call (template calling", () => {
expect(renderToString(qweb, "Parent", { root }, { fiber: { vars: {}, scope: {} } })).toBe(
expected
);
const recursiveFn = Object.values(qweb.subTemplates)[0] as any;
const subId = qweb.subTemplates["nodeTemplate"];
const recursiveFn = QWeb.subTemplates[subId] as any;
expect(recursiveFn.toString()).toMatchSnapshot();
});
@@ -940,7 +955,41 @@ describe("t-call (template calling", () => {
const expected =
"<div><div><p>a</p><div><p>b</p><div><p>d</p></div></div><div><p>c</p></div></div></div>";
expect(renderToString(qweb, "Parent", { root }, { fiber: {} })).toBe(expected);
const recursiveFn = Object.values(qweb.subTemplates)[0] as any;
const subId = qweb.subTemplates["nodeTemplate"];
const recursiveFn = QWeb.subTemplates[subId] as any;
expect(recursiveFn.toString()).toMatchSnapshot();
});
test("recursive template, part 4: with t-set recursive index", () => {
qweb.addTemplates(`
<templates>
<div t-name="Parent">
<t t-call="nodeTemplate">
<t t-set="recursive_idx" t-value="1"/>
<t t-set="node" t-value="root"/>
</t>
</div>
<div t-name="nodeTemplate">
<t t-set="recursive_idx" t-value="recursive_idx + 1"/>
<p><t t-esc="node.val"/> <t t-esc="recursive_idx"/></p>
<t t-foreach="node.children or []" t-as="subtree">
<t t-call="nodeTemplate">
<t t-set="node" t-value="subtree"/>
</t>
</t>
</div>
</templates>
`);
const root = {
val: "a",
children: [{ val: "b", children: [{ val: "c", children: [{ val: "d" }] }] }],
};
const expected =
"<div><div><p>a 2</p><div><p>b 3</p><div><p>c 4</p><div><p>d 5</p></div></div></div></div></div>";
expect(renderToString(qweb, "Parent", { root })).toBe(expected);
const subId = qweb.subTemplates["nodeTemplate"];
const recursiveFn = QWeb.subTemplates[subId] as any;
expect(recursiveFn.toString()).toMatchSnapshot();
});
@@ -1012,6 +1061,17 @@ describe("t-call (template calling", () => {
const context = { list: [{ val: 1 }, { val: 2 }, { val: 3 }] };
expect(trim(renderToString(qweb, "wrapper", context))).toBe(expected);
});
test("two different QWeb instances, and shared templates", () => {
QWeb.registerTemplate("sub", `<span>ok</span>`);
QWeb.registerTemplate("main", `<div><t t-call="sub"/></div>`);
const qweb1 = new QWeb();
const qweb2 = new QWeb();
expect(renderToString(qweb1, "main")).toBe("<div><span>ok</span></div>");
expect(renderToString(qweb2, "main")).toBe("<div><span>ok</span></div>");
});
});
describe("foreach", () => {
@@ -1197,7 +1257,7 @@ describe("t-on", () => {
{
add() {
a = 3;
}
},
},
{ handlers: [] }
);
@@ -1220,7 +1280,7 @@ describe("t-on", () => {
},
handleDblClick() {
steps.push("dblclick");
}
},
},
{ handlers: [] }
);
@@ -1240,7 +1300,7 @@ describe("t-on", () => {
{
add(n) {
a = a + n;
}
},
},
{ handlers: [] }
);
@@ -1257,7 +1317,7 @@ describe("t-on", () => {
{
add({ val }) {
a = a + val;
}
},
},
{ handlers: [] }
);
@@ -1274,7 +1334,7 @@ describe("t-on", () => {
{
doSomething(arg) {
expect(arg).toEqual({});
}
},
},
{ handlers: [] }
);
@@ -1290,7 +1350,7 @@ describe("t-on", () => {
{
doSomething(arg) {
expect(arg).toEqual({});
}
},
},
{ handlers: [] }
);
@@ -1312,7 +1372,7 @@ describe("t-on", () => {
{
activate(action) {
expect(action).toBe("someval");
}
},
},
{ handlers: [] }
);
@@ -1325,7 +1385,7 @@ describe("t-on", () => {
let owner = {
add() {
expect(this).toBe(owner);
}
},
};
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
(<HTMLElement>node).click();
@@ -1335,8 +1395,8 @@ describe("t-on", () => {
qweb.addTemplate("test", `<button t-on-click="state.counter++">Click</button>`);
let owner = {
state: {
counter: 0
}
counter: 0,
},
};
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
expect(owner.state.counter).toBe(0);
@@ -1349,10 +1409,10 @@ describe("t-on", () => {
let owner = {
state: {
counter: 0,
incrementCounter: inc => {
incrementCounter: (inc) => {
owner.state.counter += inc;
}
}
},
},
};
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
expect(owner.state.counter).toBe(0);
@@ -1364,8 +1424,8 @@ describe("t-on", () => {
qweb.addTemplate("test", `<button t-on-click="state.flag = !state.flag">Toggle</button>`);
let owner = {
state: {
flag: true
}
flag: true,
},
};
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
expect(owner.state.flag).toBe(true);
@@ -1382,8 +1442,8 @@ describe("t-on", () => {
return n + 1;
},
state: {
n: 11
}
n: 11,
},
};
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
expect(owner.state.n).toBe(11);
@@ -1399,7 +1459,7 @@ describe("t-on", () => {
let owner = {
update() {
expect(this).toBe(owner);
}
},
};
const node = renderToDOM(qweb, "main", owner, { handlers: [] });
@@ -1416,7 +1476,7 @@ describe("t-on", () => {
expect(this).toBe(owner);
expect(val).toBe(444);
},
value: 444
value: 444,
};
const node = renderToDOM(qweb, "main", owner, { handlers: [] });
@@ -1445,7 +1505,7 @@ describe("t-on", () => {
onClickPreventedAndStopped(e) {
expect(e.defaultPrevented).toBe(true);
expect(e.cancelBubble).toBe(true);
}
},
};
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
@@ -1471,7 +1531,7 @@ describe("t-on", () => {
},
onClickSelf(e) {
steps.push("onClickSelf");
}
},
};
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
@@ -1495,10 +1555,10 @@ describe("t-on", () => {
);
let steps: boolean[] = [];
let owner = {
onClick() {}
onClick() {},
};
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
(<HTMLElement>node).addEventListener("click", function(e) {
(<HTMLElement>node).addEventListener("click", function (e) {
steps.push(e.defaultPrevented);
});
@@ -1520,10 +1580,10 @@ describe("t-on", () => {
);
let steps: boolean[] = [];
let owner = {
onClick() {}
onClick() {},
};
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
(<HTMLElement>node).addEventListener("click", function(e) {
(<HTMLElement>node).addEventListener("click", function (e) {
steps.push(e.defaultPrevented);
});
@@ -1551,13 +1611,13 @@ describe("t-on", () => {
const owner = {
projects: [
{ id: 1, name: "Project 1" },
{ id: 2, name: "Project 2" }
{ id: 2, name: "Project 2" },
],
onEdit(projectId, ev) {
expect(ev.defaultPrevented).toBe(true);
steps.push(projectId);
}
},
};
const node = <HTMLElement>renderToDOM(qweb, "test", owner, { handlers: [] });
@@ -1582,7 +1642,7 @@ describe("t-on", () => {
);
const node = renderToDOM(qweb, "test", {}, { handlers: [] });
node.addEventListener("click", e => {
node.addEventListener("click", (e) => {
expect(e.defaultPrevented).toBe(true);
});
@@ -1598,7 +1658,7 @@ describe("t-on", () => {
text: "Click here",
onClick() {
steps.push("onClick");
}
},
};
const node = <HTMLElement>renderToDOM(qweb, "test", owner, { handlers: [] });
@@ -1617,7 +1677,7 @@ describe("t-on", () => {
html: "Click <b>here</b>",
onClick() {
steps.push("onClick");
}
},
};
const node = <HTMLElement>renderToDOM(qweb, "test", owner, { handlers: [] });
@@ -1644,7 +1704,7 @@ describe("t-on", () => {
},
doSomething() {
steps.push("normal");
}
},
};
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
@@ -1808,7 +1868,7 @@ describe("t-key", () => {
);
expect(
renderToString(qweb, "test", {
beers: [{ id: 12, name: "Chimay Rouge" }]
beers: [{ id: 12, name: "Chimay Rouge" }],
})
).toMatchSnapshot();
});
@@ -1921,9 +1981,9 @@ describe("properly support svg", () => {
describe("translation support", () => {
test("can translate node content", () => {
const translations = {
word: "mot"
word: "mot",
};
const translateFn = expr => translations[expr] || expr;
const translateFn = (expr) => translations[expr] || expr;
const qweb = new QWeb({ translateFn });
qweb.addTemplate("test", "<div>word</div>");
expect(renderToString(qweb, "test")).toBe("<div>mot</div>");
@@ -1931,9 +1991,9 @@ describe("translation support", () => {
test("does not translate node content if disabled", () => {
const translations = {
word: "mot"
word: "mot",
};
const translateFn = expr => translations[expr] || expr;
const translateFn = (expr) => translations[expr] || expr;
const qweb = new QWeb({ translateFn });
qweb.addTemplate(
"test",
@@ -1948,9 +2008,9 @@ describe("translation support", () => {
test("some attributes are translated", () => {
const translations = {
word: "mot"
word: "mot",
};
const translateFn = expr => translations[expr] || expr;
const translateFn = (expr) => translations[expr] || expr;
const qweb = new QWeb({ translateFn });
qweb.addTemplate(
"test",
+10 -10
View File
@@ -6,12 +6,12 @@ describe("tokenizer", () => {
expect(tokenize("{}")).toEqual([
{ type: "LEFT_BRACE", value: "{" },
{ type: "RIGHT_BRACE", value: "}" }
{ type: "RIGHT_BRACE", value: "}" },
]);
expect(tokenize("{ }}")).toEqual([
{ type: "LEFT_BRACE", value: "{" },
{ type: "RIGHT_BRACE", value: "}" },
{ type: "RIGHT_BRACE", value: "}" }
{ type: "RIGHT_BRACE", value: "}" },
]);
expect(tokenize("a")).toEqual([{ type: "SYMBOL", value: "a" }]);
expect(tokenize("true")).toEqual([{ type: "SYMBOL", value: "true" }]);
@@ -25,15 +25,15 @@ describe("tokenizer", () => {
{ type: "SYMBOL", value: "a" },
{ type: "COLON", value: ":" },
{ type: "VALUE", value: "2" },
{ type: "RIGHT_BRACE", value: "}" }
{ type: "RIGHT_BRACE", value: "}" },
]);
expect(tokenize("a,")).toEqual([
{ type: "SYMBOL", value: "a" },
{ type: "COMMA", value: "," }
{ type: "COMMA", value: "," },
]);
expect(tokenize("][")).toEqual([
{ type: "RIGHT_BRACKET", value: "]" },
{ type: "LEFT_BRACKET", value: "[" }
{ type: "LEFT_BRACKET", value: "[" },
]);
});
@@ -44,23 +44,23 @@ describe("tokenizer", () => {
{ type: "OPERATOR", value: "<" },
{ type: "OPERATOR", value: ">" },
{ type: "OPERATOR", value: "!==" },
{ type: "OPERATOR", value: "!=" }
{ type: "OPERATOR", value: "!=" },
]);
expect(tokenize("typeof a")).toEqual([
{ type: "OPERATOR", value: "typeof " },
{ type: "SYMBOL", value: "a" }
{ type: "SYMBOL", value: "a" },
]);
expect(tokenize("a...1")).toEqual([
{ type: "SYMBOL", value: "a" },
{ type: "OPERATOR", value: "..." },
{ type: "VALUE", value: "1" }
{ type: "VALUE", value: "1" },
]);
expect(tokenize("a in b")).toEqual([
{ type: "SYMBOL", value: "a" },
{ type: "OPERATOR", value: "in " },
{ type: "SYMBOL", value: "b" }
{ type: "SYMBOL", value: "b" },
]);
});
@@ -76,7 +76,7 @@ describe("tokenizer", () => {
expect(tokenize('"hello ged"')).toEqual([{ type: "VALUE", value: '"hello ged"' }]);
expect(tokenize('"hello ged"}')).toEqual([
{ type: "VALUE", value: '"hello ged"' },
{ type: "RIGHT_BRACE", value: "}" }
{ type: "RIGHT_BRACE", value: "}" },
]);
expect(tokenize('"hello \\"ged\\""')).toEqual([{ type: "VALUE", value: '"hello \\"ged\\""' }]);
});
+3 -3
View File
@@ -37,7 +37,7 @@ describe("Link component", () => {
const routes = [
{ name: "about", path: "/about" },
{ name: "users", path: "/users" }
{ name: "users", path: "/users" },
];
router = new TestRouter(env, routes, { mode: "history" });
@@ -71,7 +71,7 @@ describe("Link component", () => {
const routes = [
{ name: "about", path: "/about" },
{ name: "users", path: "/users" }
{ name: "users", path: "/users" },
];
router = new TestRouter(env, routes, { mode: "history" });
@@ -81,7 +81,7 @@ describe("Link component", () => {
expect(window.location.pathname).toBe("/users");
var evt = new MouseEvent("click", {
button: 1
button: 1,
});
fixture.querySelector("a")!.dispatchEvent(evt);
+1 -1
View File
@@ -41,7 +41,7 @@ describe("RouteComponent", () => {
const routes = [
{ name: "about", path: "/about", component: About },
{ name: "users", path: "/users", component: Users }
{ name: "users", path: "/users", component: Users },
];
router = new TestRouter(env, routes, { mode: "history" });
+10 -10
View File
@@ -21,7 +21,7 @@ describe("router miscellaneous", () => {
test("validate routes shape", () => {
expect(() => {
router = new TestRouter(env, [
{ name: "someroute", path: "/some/path", redirect: { abc: "hey" } as Destination }
{ name: "someroute", path: "/some/path", redirect: { abc: "hey" } as Destination },
]);
}).toThrow(`Invalid destination: {"abc":"hey"}`);
});
@@ -133,7 +133,7 @@ describe("getRouteParams", () => {
test("match some parameterized routes", () => {
router = new TestRouter(env, []);
expect(router["getRouteParams"]({ path: "/invoices/{{id}}" } as Route, "/invoices/3")).toEqual({
id: "3"
id: "3",
});
});
@@ -141,7 +141,7 @@ describe("getRouteParams", () => {
router = new TestRouter(env, [], { mode: "hash" });
expect(router["getRouteParams"]({ path: "/invoices/{{id}}" } as Route, "#/invoices/3")).toEqual(
{
id: "3"
id: "3",
}
);
});
@@ -151,7 +151,7 @@ describe("getRouteParams", () => {
expect(
router["getRouteParams"]({ path: "/invoices/{{id.number}}" } as Route, "/invoices/3")
).toEqual({
id: 3
id: 3,
});
});
@@ -160,7 +160,7 @@ describe("getRouteParams", () => {
expect(
router["getRouteParams"]({ path: "/invoices/{{id.number}}" } as Route, "#/invoices/3")
).toEqual({
id: 3
id: 3,
});
});
});
@@ -169,7 +169,7 @@ describe("redirect", () => {
test("can redirect to other route", async () => {
router = new TestRouter(env, [
{ name: "routea", path: "/some/path" },
{ name: "routeb", path: "/some/other/path", redirect: { to: "routea" } }
{ name: "routeb", path: "/some/other/path", redirect: { to: "routea" } },
]);
await router.start();
@@ -182,7 +182,7 @@ describe("redirect", () => {
test("can redirect to other path", async () => {
router = new TestRouter(env, [
{ name: "routea", path: "/some/path" },
{ name: "routeb", path: "/some/other/path", redirect: { path: "/some/path" } }
{ name: "routeb", path: "/some/other/path", redirect: { path: "/some/path" } },
]);
router.navigate = jest.fn(router.navigate);
@@ -214,7 +214,7 @@ describe("beforeRouteEnter", () => {
const guard = jest.fn(() => false);
router = new TestRouter(env, [
{ name: "routea", path: "/some/patha" },
{ name: "routeb", path: "/some/pathb", beforeRouteEnter: guard }
{ name: "routeb", path: "/some/pathb", beforeRouteEnter: guard },
]);
await router.start();
@@ -235,7 +235,7 @@ describe("beforeRouteEnter", () => {
router = new TestRouter(env, [
{ name: "routea", path: "/some/patha" },
{ name: "routeb", path: "/some/pathb", beforeRouteEnter: guard },
{ name: "routec", path: "/some/pathc" }
{ name: "routec", path: "/some/pathc" },
]);
await router.start();
@@ -255,7 +255,7 @@ describe("beforeRouteEnter", () => {
});
router = new TestRouter(env, [
{ name: "landing", path: "/", beforeRouteEnter: guard },
{ name: "otherroute", path: "/some/other/route" }
{ name: "otherroute", path: "/some/other/route" },
]);
expect(window.location.pathname).toBe("/");
+28 -28
View File
@@ -8,7 +8,7 @@ describe("basic use", () => {
const actions = {
inc({ state }, delta) {
state.n += delta;
}
},
};
const store = new Store({ state, actions });
@@ -25,7 +25,7 @@ describe("basic use", () => {
state.n1 += delta1;
state.n2 += delta2;
state.n3 += delta3;
}
},
};
const store = new Store({ state, actions });
@@ -47,7 +47,7 @@ describe("basic use", () => {
},
inc100({ dispatch }) {
dispatch("inc", 100);
}
},
};
const store = new Store({ state, actions });
@@ -61,7 +61,7 @@ describe("basic use", () => {
const actions = {
inc({ state }) {
return ++state.n;
}
},
};
const store = new Store({ state, actions });
@@ -87,7 +87,7 @@ describe("basic use", () => {
async setTo10({ dispatch }) {
await Promise.resolve();
dispatch("setN", 10);
}
},
};
const store = new Store({ state, actions });
@@ -115,7 +115,7 @@ describe("basic use", () => {
await Promise.resolve();
dispatch("setN", 10);
return 5;
}
},
};
const store = new Store({ state, actions });
@@ -132,7 +132,7 @@ describe("basic use", () => {
const actions = {
someaction({ env }) {
expect(env).toBe(someEnv);
}
},
};
const store = new Store({ state: {}, actions, env: someEnv });
@@ -145,15 +145,15 @@ describe("basic use", () => {
1: {
id: 1,
name: "bertinchamps",
tasterID: 1
}
tasterID: 1,
},
},
tasters: {
1: {
id: 1,
name: "aaron"
}
}
name: "aaron",
},
},
};
const getters: { [key: string]: Getter } = {
beerTasterName({ state }, beerID) {
@@ -161,7 +161,7 @@ describe("basic use", () => {
},
bestBeerName({ state }) {
return state.beers[1].name;
}
},
};
const store = new Store({ state, actions: {}, getters });
expect(store.getters).toBeDefined();
@@ -176,15 +176,15 @@ describe("basic use", () => {
1: {
id: 1,
name: "bertinchamps",
tasterID: 1
}
tasterID: 1,
},
},
tasters: {
1: {
id: 1,
name: "aaron"
}
}
name: "aaron",
},
},
};
const getters = {
beerTasterName({ state }, beerID) {
@@ -192,14 +192,14 @@ describe("basic use", () => {
},
bestBeerName({ state }) {
return state.beers[1].name;
}
},
};
const actions = {
action({ getters }) {
expect(getters).toBeDefined();
expect(getters.bestBeerName()).toBe("bertinchamps");
expect(getters.beerTasterName(1)).toBe("aaron");
}
},
};
const store = new Store({ state, actions, getters });
store.dispatch("action");
@@ -215,7 +215,7 @@ describe("basic use", () => {
},
c({}, i) {
return `c${i}`;
}
},
};
const store = new Store({ getters, state: {}, actions: {} });
@@ -226,7 +226,7 @@ describe("basic use", () => {
describe("advanced state properties", () => {
test("state in the store is reference equal after mutation", async () => {
const actions = {
donothing() {}
donothing() {},
};
const store = new Store({ state: {}, actions });
const state = store.state;
@@ -242,7 +242,7 @@ describe("advanced state properties", () => {
expect(state.a.length).toBe(3);
const l = state.a.push(53);
expect(l).toBe(4);
}
},
};
const store = new Store({ state, actions });
store.dispatch("m");
@@ -253,11 +253,11 @@ describe("advanced state properties", () => {
const actions = {
dosomething({ state }) {
Object.assign(state.westmalle, { a: 3, b: 4 });
}
},
};
const store = new Store({
state: { westmalle: { a: 1, b: 2 } },
actions
actions,
});
store.dispatch("dosomething");
expect(store.state.westmalle).toEqual({ a: 3, b: 4 });
@@ -267,7 +267,7 @@ describe("advanced state properties", () => {
const actions = {
inc({ state }) {
state.counter++;
}
},
};
const state = { counter: 0 };
const store = new Store({ state, actions });
@@ -284,7 +284,7 @@ describe("updates triggered by the store", () => {
const actions = {
inc({ state }, delta) {
state.n += delta;
}
},
};
const store = new Store({ state, actions });
store.on("update", null, () => updateCounter++);
@@ -308,7 +308,7 @@ describe("updates triggered by the store", () => {
noop2({ state }) {
const val = state.n;
state.n = val;
}
},
};
const store = new Store({ state, actions });
store.on("update", null, () => updateCounter++);
+93 -93
View File
@@ -1,5 +1,5 @@
import { Component, Env } from "../src/component/component";
import { Store, useStore, useDispatch, useGetters } from "../src/store";
import { Store, useStore, useDispatch, useGetters, EnvWithStore } from "../src/store";
import { useState } from "../src/hooks";
import { xml } from "../src/tags";
import { shallowEqual } from "../src/utils";
@@ -25,7 +25,7 @@ describe("connecting a component to store", () => {
const actions = {
addTodo({ state }, msg) {
state.todos.push({ msg, id: nextId++ });
}
},
};
const store = new Store({ state, actions });
@@ -34,7 +34,7 @@ describe("connecting a component to store", () => {
<div>
<span t-foreach="todos" t-key="todo.id" t-as="todo"><t t-esc="todo.msg"/></span>
</div>`;
todos = useStore(state => state.todos);
todos = useStore((state) => state.todos);
}
(<any>env).store = store;
@@ -56,7 +56,7 @@ describe("connecting a component to store", () => {
},
setNotNull({ state }) {
state.nullValue = "ok";
}
},
};
const store = new Store({ state, actions });
@@ -70,15 +70,15 @@ describe("connecting a component to store", () => {
nullValue: string;
constructor() {
super();
this.isBoolean = useStore(state => state.isBoolean, {
onUpdate: isBoolean => {
this.isBoolean = useStore((state) => state.isBoolean, {
onUpdate: (isBoolean) => {
this.isBoolean = isBoolean;
}
},
});
this.nullValue = useStore(state => state.nullValue, {
onUpdate: nullValue => {
this.nullValue = useStore((state) => state.nullValue, {
onUpdate: (nullValue) => {
this.nullValue = nullValue;
}
},
});
}
}
@@ -105,15 +105,15 @@ describe("connecting a component to store", () => {
class App extends Component {
static template = xml`<div t-esc="mapAdd"/>`;
storeProps = {
array: useStore(state => {
array: useStore((state) => {
if (state.useSmallArray) {
return state.smallerArray;
}
return state.biggerArray;
})
}),
};
get mapAdd() {
return this.storeProps.array.map(a => {
return this.storeProps.array.map((a) => {
return a + 1;
});
}
@@ -133,7 +133,7 @@ describe("connecting a component to store", () => {
test("throw error if no store is found", async () => {
class App extends Component {
static template = xml`<div></div>`;
todos = useStore(state => state.todos);
todos = useStore((state) => state.todos);
}
let error;
@@ -153,7 +153,7 @@ describe("connecting a component to store", () => {
class App extends Component {
static template = xml`<div/>`;
storeState = useStore(state => state.a);
storeState = useStore((state) => state.a);
}
(<any>env).store = store;
@@ -170,7 +170,7 @@ describe("connecting a component to store", () => {
doSomething({ state }) {
state.a = 2;
state.b = 3;
}
},
};
const store = new Store({ state, actions });
@@ -180,8 +180,8 @@ describe("connecting a component to store", () => {
<span t-esc="a.value"/>
<span t-esc="b.value"/>
</div>`;
a = useStore(state => ({ value: state.a }));
b = useStore(state => ({ value: state.b }));
a = useStore((state) => ({ value: state.a }));
b = useStore((state) => ({ value: state.b }));
}
App.prototype.__render = jest.fn(App.prototype.__render);
@@ -204,7 +204,7 @@ describe("connecting a component to store", () => {
const actions = {
addTodo({ state }, msg) {
state.todos.push({ msg, id: nextId++ });
}
},
};
const store = new Store({ state, actions });
@@ -213,7 +213,7 @@ describe("connecting a component to store", () => {
<div>
<span t-foreach="todos" t-key="todo.id" t-as="todo"><t t-esc="todo.msg"/></span>
</div>`;
todos = useStore(state => state.todos);
todos = useStore((state) => state.todos);
}
App.prototype.__render = jest.fn(App.prototype.__render);
@@ -241,13 +241,13 @@ describe("connecting a component to store", () => {
const actions = {
addTodo({ state }, msg) {
state.todos.push({ msg, id: nextId++ });
}
},
};
const store = new Store({ state, actions });
class App extends Component {
static template = xml`<div><t t-esc="nbrTodos.value"/></div>`;
nbrTodos = useStore(state => ({ value: state.todos.length }));
nbrTodos = useStore((state) => ({ value: state.todos.length }));
}
(<any>env).store = store;
@@ -270,12 +270,12 @@ describe("connecting a component to store", () => {
},
incrementA({ state }) {
state.a++;
}
},
};
const store = new Store({ state, actions });
class App extends Component {
static template = xml`<div><t t-esc="nbrTodos.value"/></div>`;
nbrTodos = useStore(state => ({ value: state.todos.length }), { isEqual: shallowEqual });
nbrTodos = useStore((state) => ({ value: state.todos.length }), { isEqual: shallowEqual });
}
App.prototype.__render = jest.fn(App.prototype.__render);
@@ -303,7 +303,7 @@ describe("connecting a component to store", () => {
const actions = {
addTodo({ state }, msg) {
state.todos.push({ msg, id: nextId++ });
}
},
};
const store = new Store({ state, actions });
@@ -312,7 +312,7 @@ describe("connecting a component to store", () => {
<div>
<span t-foreach="todos" t-key="todo.id" t-as="todo"><t t-esc="todo.msg"/></span>
</div>`;
todos = useStore(state => state.todos, { store });
todos = useStore((state) => state.todos, { store });
}
const app = new App();
@@ -331,8 +331,8 @@ describe("connecting a component to store", () => {
actions: {
inc({ state }) {
state.value++;
}
}
},
},
});
(<any>env).store = store;
@@ -342,7 +342,7 @@ describe("connecting a component to store", () => {
<button t-on-click="dispatch('inc')">Inc</button>
<span><t t-esc="storeState.value"/></span>
</div>`;
storeState = useStore(state => state);
storeState = useStore((state) => state);
dispatch = useDispatch();
}
const app = new App();
@@ -360,15 +360,15 @@ describe("connecting a component to store", () => {
const state = {
todos: [
{ id: 1, text: "jupiler" },
{ id: 2, text: "chimay" }
]
{ id: 2, text: "chimay" },
],
};
const store = new Store({ state, actions: {} });
class TodoItem extends Component {
static template = xml`<span><t t-esc="todo.text"/></span>`;
todo = useStore((state, props) => {
return state.todos.find(t => t.id === props.todoId);
return state.todos.find((t) => t.id === props.todoId);
});
}
@@ -395,14 +395,14 @@ describe("connecting a component to store", () => {
const actions = {
addTodo({ state }, text) {
state.todos.push({ text, id: nextId++ });
}
},
};
const store = new Store({ state, actions });
class TodoItem extends Component {
static template = xml`<span><t t-esc="todo.text"/></span>`;
todo = useStore((state, props) => {
return state.todos.find(t => t.id === props.id);
return state.todos.find((t) => t.id === props.id);
});
}
@@ -412,7 +412,7 @@ describe("connecting a component to store", () => {
<TodoItem t-foreach="todos" t-as="todo" id="todo.id" t-key="todo.id"/>
</div>`;
static components = { TodoItem };
todos = useStore(state => state.todos);
todos = useStore((state) => state.todos);
}
(<any>env).store = store;
@@ -431,16 +431,16 @@ describe("connecting a component to store", () => {
importantID: 1,
todos: [
{ id: 1, text: "jupiler" },
{ id: 2, text: "bertinchamps" }
]
{ id: 2, text: "bertinchamps" },
],
};
const getters = {
importantTodoText({ state }) {
return state.todos.find(todo => todo.id === state.importantID).text;
return state.todos.find((todo) => todo.id === state.importantID).text;
},
text({ state }, id) {
return state.todos.find(todo => todo.id === id).text;
}
return state.todos.find((todo) => todo.id === id).text;
},
};
const store = new Store({ state, getters });
@@ -452,10 +452,10 @@ describe("connecting a component to store", () => {
</div>`;
getters = useGetters();
storeProps = useStore((state, props) => {
const todo = state.todos.find(t => t.id === props.id);
const todo = state.todos.find((t) => t.id === props.id);
return {
activeTodoText: this.getters.text(todo.id),
importantTodoText: this.getters.importantTodoText()
importantTodoText: this.getters.importantTodoText(),
};
});
}
@@ -468,7 +468,7 @@ describe("connecting a component to store", () => {
<TodoItem id="todo.id" t-key="todo.id"/>
</t>
</div>`;
todos = useStore(state => state.todos);
todos = useStore((state) => state.todos);
}
(<any>env).store = store;
@@ -513,13 +513,13 @@ describe("connecting a component to store", () => {
</div>`;
// we have here a new object
data = useStore(state => ({ beers: state.beers, otherKey: 1 }));
data = useStore((state) => ({ beers: state.beers, otherKey: 1 }));
}
const actions = {
addBeer({ state }, name) {
state.beers.push({ name });
}
},
};
const state = { beers: [{ name: "jupiler" }] };
@@ -542,9 +542,9 @@ describe("connecting a component to store", () => {
class Beer extends Component {
static template = xml`<span><t t-esc="beer.name"/></span>`;
beer = useStore((state, props) => state.beers[props.id], {
onUpdate: result => {
onUpdate: (result) => {
++counter;
}
},
});
}
@@ -558,7 +558,7 @@ describe("connecting a component to store", () => {
const actions = {
renameBeer({ state }, { id, name }) {
state.beers[id].name = name;
}
},
};
const store = new Store({ state, actions });
(<any>env).store = store;
@@ -609,7 +609,7 @@ describe("connecting a component to store", () => {
data = useStore((state, props) => ({
selected: state.beers[props.id],
consumed: state.beers[state.consumedID] || null,
taster: state.taster
taster: state.taster,
}));
}
@@ -622,14 +622,14 @@ describe("connecting a component to store", () => {
const actions = {
consume({ state }, beerId) {
state.consumedID = beerId;
}
},
};
const state = {
beers: {
1: { name: "jupiler" }
1: { name: "jupiler" },
},
consumedID: null,
taster: "aaron"
taster: "aaron",
};
const store = new Store({ state, actions });
(<any>env).store = store;
@@ -665,11 +665,11 @@ describe("connecting a component to store", () => {
<span t-if="info.selected">selected:<t t-esc="info.selected.name"/></span>
<span t-if="info.consumed">consumed:<t t-esc="info.consumed.name"/></span>
</div>`;
info = useStore(function(state, props) {
info = useStore(function (state, props) {
return {
selected: state.beers[props.id],
consumed: state.beers[state.consumedID] || null,
taster: state.taster
taster: state.taster,
};
});
}
@@ -689,14 +689,14 @@ describe("connecting a component to store", () => {
},
renameBeer({ state }, { beerId, name }) {
state.beers[beerId].name = name;
}
},
};
const state = {
beers: {
1: { name: "jupiler" }
1: { name: "jupiler" },
},
consumedID: null,
taster: "aaron"
taster: "aaron",
};
const store = new Store({ state, actions });
(<any>env).store = store;
@@ -747,11 +747,11 @@ describe("connecting a component to store", () => {
const actions = {
setValue({ state }, val) {
state.x.val = val;
}
},
};
class Parent extends Component {
static template = xml`<div><t t-esc="x.val"/></div>`;
x = useStore(state => {
x = useStore((state) => {
return Object.assign({}, state.x);
});
dispatch = useDispatch();
@@ -782,11 +782,11 @@ describe("connecting a component to store", () => {
static template = xml`<div><Child key="state.current"/></div>`;
static components = { Child };
state = useStore(state => {
state = useStore((state) => {
steps.push("parent");
return {
current: state.current,
isvisible: state.isvisible
isvisible: state.isvisible,
};
});
}
@@ -795,7 +795,7 @@ describe("connecting a component to store", () => {
const actions = {
setCurrent({ state }, c) {
state.current = c;
}
},
};
const store = new Store({ state, actions });
@@ -831,7 +831,7 @@ describe("connecting a component to store", () => {
<Child t-if="state.flag" someId="state.someId"/>
</div>`;
static components = { Child };
state = useStore(s => {
state = useStore((s) => {
steps.push("parent");
return { flag: s.flag, someId: s.someId };
});
@@ -846,7 +846,7 @@ describe("connecting a component to store", () => {
const actions = {
setFlagToFalse({ state }) {
state.flag = false;
}
},
};
const store = new Store({ state, actions });
@@ -874,18 +874,18 @@ describe("connecting a component to store", () => {
const actions = {
editTodo({ state }) {
state.todos[1].title = "abc";
}
},
};
const todos = { 1: { id: 1, title: "kikoou" } };
const state = {
todos
todos,
};
const store = new Store({
state,
actions
actions,
});
class TodoItem extends Component {
class TodoItem extends Component<{}, EnvWithStore> {
static template = xml`
<div class="todo">
<t t-esc="state.todo.title"/>
@@ -894,7 +894,7 @@ describe("connecting a component to store", () => {
state = useStore((state, props) => {
steps.push("item:usestore");
return {
todo: state.todos[props.id]
todo: state.todos[props.id],
};
});
@@ -914,7 +914,7 @@ describe("connecting a component to store", () => {
</t>
</div>`;
static components = { TodoItem };
state = useStore(state => {
state = useStore((state) => {
steps.push("app:usestore");
return { todos: state.todos };
});
@@ -947,18 +947,18 @@ describe("connecting a component to store", () => {
const actions = {
removeTodo({ state }) {
delete state.todos[1];
}
},
};
const todos = { 1: { id: 1, title: "kikoou" } };
const state = {
todos
todos,
};
const store = new Store({
state,
actions
actions,
});
class TodoItem extends Component {
class TodoItem extends Component<{}, EnvWithStore> {
static template = xml`
<div class="todo">
<t t-esc="state.todo.title"/>
@@ -967,7 +967,7 @@ describe("connecting a component to store", () => {
state = useStore((state, props) => {
steps.push("item:usestore");
return {
todo: state.todos[props.id]
todo: state.todos[props.id],
};
});
@@ -988,7 +988,7 @@ describe("connecting a component to store", () => {
</t>
</div>`;
static components = { TodoItem };
state = useStore(state => {
state = useStore((state) => {
steps.push("app:usestore");
return { todos: state.todos };
});
@@ -1016,7 +1016,7 @@ describe("connecting a component to store", () => {
"item:usestore",
"item:render",
"app:usestore",
"app:render"
"app:render",
]);
expect(fixture.innerHTML).toBe('<div class="todoapp"></div>');
});
@@ -1026,7 +1026,7 @@ describe("connecting a component to store", () => {
class App extends Component {
static template = xml`<div><t t-esc="store.msg"/></div>`;
store = useStore(s => ({ msg: s.msg }));
store = useStore((s) => ({ msg: s.msg }));
willPatch() {
steps.push("willpatch");
@@ -1040,7 +1040,7 @@ describe("connecting a component to store", () => {
const actions = {
setMsg({ state }, c) {
state.msg = c;
}
},
};
const store = new Store({ state, actions });
@@ -1061,7 +1061,7 @@ describe("connecting a component to store", () => {
class Child extends Component {
static template = xml`<div><t t-esc="store.val"/></div>`;
store = useStore(s => s);
store = useStore((s) => s);
}
class Parent extends Component {
@@ -1099,7 +1099,7 @@ describe("connecting a component to store", () => {
class Child extends Component {
static template = xml`<div><t t-esc="store.val"/></div>`;
store = useStore(s => {
store = useStore((s) => {
steps.push("child selector");
return s;
});
@@ -1107,7 +1107,7 @@ describe("connecting a component to store", () => {
class Parent extends Component {
static template = xml`<div><Child t-if="store.child" /></div>`;
static components = { Child };
store = useStore(s => {
store = useStore((s) => {
steps.push("parent selector");
return s;
});
@@ -1118,7 +1118,7 @@ describe("connecting a component to store", () => {
const actions = {
toggleChild({ state }) {
state.child = !state.child;
}
},
};
const store = new Store({ state, actions });
(<any>env).store = store;
@@ -1137,18 +1137,18 @@ describe("connecting a component to store", () => {
test("dispatch an action", async () => {
class App extends Component {
static template = xml`<div><t t-esc="store.counter"/></div>`;
store = useStore(state => state);
store = useStore((state) => state);
dispatch = useDispatch();
}
const state = {
counter: 0
counter: 0,
};
const actions = {
inc({ state }) {
return ++state.counter;
}
},
};
const store = new Store({ state, actions });
@@ -1185,21 +1185,21 @@ describe("various scenarios", () => {
await Promise.resolve();
delete state.attachments[100];
state.messages[10].attachmentIds = [];
}
},
};
const state = {
attachments: {
100: {
id: 100,
name: "text.txt"
}
name: "text.txt",
},
},
messages: {
10: {
attachmentIds: [100],
id: 10
}
}
id: 10,
},
},
};
const store = new Store({ actions, state });
@@ -1220,7 +1220,7 @@ describe("various scenarios", () => {
<Attachment t-foreach="store.attachmentIds" t-key="attachmentId" t-as="attachmentId" id="attachmentId"/>
</div>`;
store = useStore(state => ({ attachmentIds: state.messages[10].attachmentIds }));
store = useStore((state) => ({ attachmentIds: state.messages[10].attachmentIds }));
static components = { Attachment };
state = { isAttachmentDeleted: false };
dispatch = useDispatch();
+2 -2
View File
@@ -20,7 +20,7 @@ debugOwl(owl, {});
test("can log full lifecycle", async () => {
const steps: string[] = [];
const log = console.log;
console.log = arg => steps.push(arg);
console.log = (arg) => steps.push(arg);
class Child extends Component {
static template = xml`<div>child</div>`;
@@ -51,7 +51,7 @@ test("can log full lifecycle", async () => {
"[OWL_DEBUG] Child<id=2> rendering template",
"[OWL_DEBUG] Parent<id=1> willPatch",
"[OWL_DEBUG] Child<id=2> mounted",
"[OWL_DEBUG] Parent<id=1> patched"
"[OWL_DEBUG] Parent<id=1> patched",
]);
console.log = log;
});
+2 -2
View File
@@ -19,7 +19,7 @@ debugOwl(owl, { logScheduler: true });
test("can log scheduler start and stop", async () => {
const steps: string[] = [];
const log = console.log;
console.log = arg => steps.push(arg);
console.log = (arg) => steps.push(arg);
class Child extends Component {
static template = xml`<div>child</div>`;
@@ -44,7 +44,7 @@ test("can log scheduler start and stop", async () => {
"[OWL_DEBUG] Child<id=2> rendering template",
"[OWL_DEBUG] Child<id=2> mounted",
"[OWL_DEBUG] Parent<id=1> mounted",
"[OWL_DEBUG] scheduler: stop running tasks queue"
"[OWL_DEBUG] scheduler: stop running tasks queue",
]);
console.log = log;
});
+2 -2
View File
@@ -19,7 +19,7 @@ debugOwl(owl, { logScheduler: true });
test("log a specific message for render method calls if component is not mounted", async () => {
const steps: string[] = [];
const log = console.log;
console.log = arg => steps.push(arg);
console.log = (arg) => steps.push(arg);
class Parent extends Component {
static template = xml`<div><t t-esc="state.value"/></div>`;
@@ -40,7 +40,7 @@ test("log a specific message for render method calls if component is not mounted
"[OWL_DEBUG] Parent<id=1> mounted",
"[OWL_DEBUG] scheduler: stop running tasks queue",
"[OWL_DEBUG] Parent<id=1> willUnmount",
"[OWL_DEBUG] Parent<id=1> render (warning: component is not mounted, this render has no effect)"
"[OWL_DEBUG] Parent<id=1> render (warning: component is not mounted, this render has no effect)",
]);
console.log = log;
});
+2 -2
View File
@@ -19,7 +19,7 @@ debugOwl(owl, { logScheduler: true });
test("log a sub component with non stringifiable props", async () => {
const steps: string[] = [];
const log = console.log;
console.log = arg => steps.push(arg);
console.log = (arg) => steps.push(arg);
class Child extends Component {
static template = xml`<span><t t-esc="props.obj.val"/></span>`;
@@ -51,7 +51,7 @@ test("log a sub component with non stringifiable props", async () => {
"[OWL_DEBUG] Parent<id=1> mounted",
"[OWL_DEBUG] scheduler: stop running tasks queue",
"[OWL_DEBUG] Parent<id=1> willUnmount",
"[OWL_DEBUG] Child<id=2> willUnmount"
"[OWL_DEBUG] Child<id=2> willUnmount",
]);
console.log = log;
});
+7 -7
View File
@@ -60,14 +60,14 @@ function getFiles(path: string[] = []): FileData[] {
if (path.length === 0) {
const baseFiles: FileData[] = [
{ name: "README.md", path: [], links: [], sections: [], fullName: "README.md" },
{ name: "roadmap.md", path: [], links: [], sections: [], fullName: "roadmap.md" }
{ name: "roadmap.md", path: [], links: [], sections: [], fullName: "roadmap.md" },
];
const rest = getFiles(["doc"]);
const result = baseFiles.concat(rest);
result.forEach(addMardownData);
return result;
}
const files = fs.readdirSync(path.join("/"), { withFileTypes: true }).map(f => {
const files = fs.readdirSync(path.join("/"), { withFileTypes: true }).map((f) => {
if (f.isDirectory()) {
return getFiles(path.concat(f.name));
}
@@ -78,8 +78,8 @@ function getFiles(path: string[] = []): FileData[] {
path,
links: [],
sections: [],
fullName
}
fullName,
},
];
});
return Array.prototype.concat(...files);
@@ -127,7 +127,7 @@ export function isLinkValid(link: MarkDownLink, current: FileData, files: FileDa
}
// Step 4: check if there is a matching file
let target: FileData | undefined = files.find(f => f.fullName === linkFullName);
let target: FileData | undefined = files.find((f) => f.fullName === linkFullName);
if (!target) {
return false;
}
@@ -135,7 +135,7 @@ export function isLinkValid(link: MarkDownLink, current: FileData, files: FileDa
// Step 5: if necessary, check if there is a corresponding link inside the target
// link name
if (hash) {
if (!target.sections.find(s => s.slug === hash)) {
if (!target.sections.find((s) => s.slug === hash)) {
return false;
}
}
@@ -153,7 +153,7 @@ function slugify(str) {
.toLowerCase()
.replace(/\//g, "") // remove /
.replace(/\s+/g, "-") // Replace spaces with -
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
.replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters
.replace(/&/g, "-and-") // Replace & with ‘and’
.replace(/[^\w\-]+/g, "") // Remove all non-word characters
.replace(/\-\-+/g, "-") // Replace multiple - with single -
+5
View File
@@ -1,4 +1,5 @@
import { escape, debounce } from "../src/utils";
import { browser } from "../src/browser";
describe("escape", () => {
test("normal strings", () => {
@@ -15,6 +16,10 @@ describe("escape", () => {
describe("debounce", () => {
test("works as expected", () => {
jest.useFakeTimers();
// need to reset them on browser because they were mocked in window, but not
// on browser object
browser.setTimeout = window.setTimeout.bind(window);
browser.clearTimeout = window.clearTimeout.bind(window);
let n = 0;
let f = debounce(() => n++, 100);
expect(n).toBe(0);
+199 -192
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -1169,7 +1169,7 @@ const RESPONSIVE_CSS = `body {
const SLOTS = `// We show here how slots can be used to create generic components.
// In this example, the Card component is basically only a container. It is not
// aware of its content. It just knows where it should be (with t-slot).
// The parent component define the content with t-set.
// The parent component define the content with t-set-slot.
//
// Note that the t-on-click event, defined in the App template, is executed in
// the context of the App component, even though it is inside the Card component
@@ -1235,15 +1235,15 @@ const SLOTS_XML = `<templates>
<div t-name="App" class="main">
<Card title="'Title card A'">
<t t-set="content">Content of card 1... [<t t-esc="state.a"/>]</t>
<t t-set="footer"><button t-on-click="inc('a', 1)">Increment A</button></t>
<t t-set-slot="content">Content of card 1... [<t t-esc="state.a"/>]</t>
<t t-set-slot="footer"><button t-on-click="inc('a', 1)">Increment A</button></t>
</Card>
<Card title="'Title card B'">
<t t-set="content">
<t t-set-slot="content">
<div>Card 2... [<t t-esc="state.b"/>]</div>
<Counter />
</t>
<t t-set="footer"><button t-on-click="inc('b', -1)">Decrement B</button></t>
<t t-set-slot="footer"><button t-on-click="inc('b', -1)">Decrement B</button></t>
</Card>
</div>
</templates>`;
-1
View File
@@ -106,7 +106,6 @@ async function startRelease() {
draft: true // todo: remove this someday
};
const result = await createRelease(token, options);
console.log(result);
await ask("Ready for next step...");
// ---------------------------------------------------------------------------