mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| de805d0d80 | |||
| cb38d795f9 | |||
| 8da68e925b | |||
| 474ffa1cd9 | |||
| e73fb462c5 | |||
| 38c7ad9629 | |||
| d0c76c5854 | |||
| e032314739 | |||
| b2db7f21ed | |||
| 7b8ac13d3f | |||
| 06d852fcf9 | |||
| 7e4baf668a | |||
| fe34ba00a6 | |||
| e5e7790530 | |||
| 3bf91afc3f | |||
| 2529aa3ef2 | |||
| 8d25bddda4 | |||
| 81af21a025 | |||
| 9baea2c1cd | |||
| 8e03f9cd9c | |||
| c5a2f52afb | |||
| 9fd8315c53 | |||
| 85318b3ae6 | |||
| 4b961cbffe | |||
| 2af8cccd65 | |||
| b8d3618afa | |||
| 559fadb62a | |||
| c36333dbbc | |||
| b5c3422b4d | |||
| 23ce19e57a | |||
| aaf40e9bec | |||
| 211f6ebdd6 | |||
| 1707bd240d | |||
| 142b69823f | |||
| 94c8bce810 | |||
| ae172d42e7 | |||
| ddf30a8a97 | |||
| 8d0d8538ad | |||
| 2b0315c03f | |||
| c469cac315 | |||
| b4ad14edc0 | |||
| 718e5264ae | |||
| f5d019bb69 | |||
| afa36f52a0 |
@@ -121,8 +121,8 @@ npm install @odoo/owl
|
|||||||
|
|
||||||
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
||||||
|
|
||||||
- [owl-1.0.5.js](https://github.com/odoo/owl/releases/download/v1.0.5/owl.js)
|
- [owl-1.0.11.js](https://github.com/odoo/owl/releases/download/v1.0.11/owl.js)
|
||||||
- [owl-1.0.5.min.js](https://github.com/odoo/owl/releases/download/v1.0.5/owl.min.js)
|
- [owl-1.0.11.min.js](https://github.com/odoo/owl/releases/download/v1.0.11/owl.min.js)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class Parent extends Component {
|
|||||||
static components = { OrderLine };
|
static components = { OrderLine };
|
||||||
orders = useState([
|
orders = useState([
|
||||||
{ id: 1, name: "Coffee", quantity: 0 },
|
{ id: 1, name: "Coffee", quantity: 0 },
|
||||||
{ id: 2, name: "Tea", quantity: 0 }
|
{ id: 2, name: "Tea", quantity: 0 },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
addToOrder(event) {
|
addToOrder(event) {
|
||||||
|
|||||||
+11
-11
@@ -281,7 +281,7 @@ import { Component } from "@odoo/owl";
|
|||||||
import "regenerator-runtime/runtime";
|
import "regenerator-runtime/runtime";
|
||||||
|
|
||||||
export async function nextTick() {
|
export async function nextTick() {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function (resolve) {
|
||||||
setTimeout(() => Component.scheduler.requestAnimationFrame(() => resolve()));
|
setTimeout(() => Component.scheduler.requestAnimationFrame(() => resolve()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -361,26 +361,26 @@ const HtmlWebpackPlugin = require("html-webpack-plugin");
|
|||||||
|
|
||||||
const host = process.env.HOST || "localhost";
|
const host = process.env.HOST || "localhost";
|
||||||
|
|
||||||
module.exports = function(env, argv) {
|
module.exports = function (env, argv) {
|
||||||
const mode = argv.mode || "development";
|
const mode = argv.mode || "development";
|
||||||
return {
|
return {
|
||||||
mode: mode,
|
mode: mode,
|
||||||
entry: "./src/main.js",
|
entry: "./src/main.js",
|
||||||
output: {
|
output: {
|
||||||
filename: "main.js",
|
filename: "main.js",
|
||||||
path: path.resolve(__dirname, "dist")
|
path: path.resolve(__dirname, "dist"),
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.jsx?$/,
|
test: /\.jsx?$/,
|
||||||
loader: "babel-loader",
|
loader: "babel-loader",
|
||||||
exclude: /node_modules/
|
exclude: /node_modules/,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".js", ".jsx"]
|
extensions: [".js", ".jsx"],
|
||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
contentBase: path.resolve(__dirname, "public/index.html"),
|
contentBase: path.resolve(__dirname, "public/index.html"),
|
||||||
@@ -388,14 +388,14 @@ module.exports = function(env, argv) {
|
|||||||
hot: true,
|
hot: true,
|
||||||
host,
|
host,
|
||||||
port: 3000,
|
port: 3000,
|
||||||
publicPath: "/"
|
publicPath: "/",
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
inject: true,
|
inject: true,
|
||||||
template: path.resolve(__dirname, "public/index.html")
|
template: path.resolve(__dirname, "public/index.html"),
|
||||||
})
|
}),
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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:
|
just put the following code:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
(function() {
|
(function () {
|
||||||
console.log("hello owl", owl.__info__.version);
|
console.log("hello owl", owl.__info__.version);
|
||||||
})();
|
})();
|
||||||
```
|
```
|
||||||
@@ -170,13 +170,13 @@ class App extends Component {
|
|||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "buy milk",
|
title: "buy milk",
|
||||||
isCompleted: true
|
isCompleted: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: "clean house",
|
title: "clean house",
|
||||||
isCompleted: false
|
isCompleted: false,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -562,23 +562,23 @@ const actions = {
|
|||||||
const task = {
|
const task = {
|
||||||
id: state.nextId++,
|
id: state.nextId++,
|
||||||
title: title,
|
title: title,
|
||||||
isCompleted: false
|
isCompleted: false,
|
||||||
};
|
};
|
||||||
state.tasks.push(task);
|
state.tasks.push(task);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleTask({ state }, id) {
|
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;
|
task.isCompleted = !task.isCompleted;
|
||||||
},
|
},
|
||||||
deleteTask({ state }, id) {
|
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);
|
state.tasks.splice(index, 1);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const initialState = {
|
const initialState = {
|
||||||
nextId: 1,
|
nextId: 1,
|
||||||
tasks: []
|
tasks: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -616,7 +616,7 @@ class App extends Component {
|
|||||||
static components = { Task };
|
static components = { Task };
|
||||||
|
|
||||||
inputRef = useRef("add-input");
|
inputRef = useRef("add-input");
|
||||||
tasks = useStore(state => state.tasks);
|
tasks = useStore((state) => state.tasks);
|
||||||
dispatch = useDispatch();
|
dispatch = useDispatch();
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -811,7 +811,7 @@ For reference, here is the final code:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
(function() {
|
(function () {
|
||||||
const { Component, Store } = owl;
|
const { Component, Store } = owl;
|
||||||
const { xml } = owl.tags;
|
const { xml } = owl.tags;
|
||||||
const { whenReady } = owl.utils;
|
const { whenReady } = owl.utils;
|
||||||
@@ -827,24 +827,24 @@ For reference, here is the final code:
|
|||||||
const task = {
|
const task = {
|
||||||
id: state.nextId++,
|
id: state.nextId++,
|
||||||
title: title,
|
title: title,
|
||||||
isCompleted: false
|
isCompleted: false,
|
||||||
};
|
};
|
||||||
state.tasks.push(task);
|
state.tasks.push(task);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleTask({ state }, id) {
|
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;
|
task.isCompleted = !task.isCompleted;
|
||||||
},
|
},
|
||||||
deleteTask({ state }, id) {
|
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);
|
state.tasks.splice(index, 1);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
nextId: 1,
|
nextId: 1,
|
||||||
tasks: []
|
tasks: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -897,7 +897,7 @@ For reference, here is the final code:
|
|||||||
static components = { Task };
|
static components = { Task };
|
||||||
|
|
||||||
inputRef = useRef("add-input");
|
inputRef = useRef("add-input");
|
||||||
tasks = useStore(state => state.tasks);
|
tasks = useStore((state) => state.tasks);
|
||||||
filter = useState({ value: "all" });
|
filter = useState({ value: "all" });
|
||||||
dispatch = useDispatch();
|
dispatch = useDispatch();
|
||||||
|
|
||||||
@@ -916,9 +916,9 @@ For reference, here is the final code:
|
|||||||
get displayedTasks() {
|
get displayedTasks() {
|
||||||
switch (this.filter.value) {
|
switch (this.filter.value) {
|
||||||
case "active":
|
case "active":
|
||||||
return this.tasks.filter(t => !t.isCompleted);
|
return this.tasks.filter((t) => !t.isCompleted);
|
||||||
case "completed":
|
case "completed":
|
||||||
return this.tasks.filter(t => t.isCompleted);
|
return this.tasks.filter((t) => t.isCompleted);
|
||||||
case "all":
|
case "all":
|
||||||
return this.tasks;
|
return this.tasks;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,11 +257,11 @@ to the store like in redux, with the `useStore` hook (see the [store documentati
|
|||||||
const actions = {
|
const actions = {
|
||||||
increment({ state }, val) {
|
increment({ state }, val) {
|
||||||
state.counter.value += val;
|
state.counter.value += val;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
counter: { value: 0 }
|
counter: { value: 0 },
|
||||||
};
|
};
|
||||||
const store = new owl.Store({ state, actions });
|
const store = new owl.Store({ state, actions });
|
||||||
|
|
||||||
@@ -270,7 +270,7 @@ class Counter extends Component {
|
|||||||
<button t-name="Counter" t-on-click="dispatch('increment')">
|
<button t-name="Counter" t-on-click="dispatch('increment')">
|
||||||
Click Me! [<t t-esc="counter.value"/>]
|
Click Me! [<t t-esc="counter.value"/>]
|
||||||
</button>`;
|
</button>`;
|
||||||
counter = useStore(state => state.counter);
|
counter = useStore((state) => state.counter);
|
||||||
dispatch = useDispatch();
|
dispatch = useDispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ to be called in the constructor.
|
|||||||
class Counter extends owl.Component {
|
class Counter extends owl.Component {
|
||||||
static props = {
|
static props = {
|
||||||
initialValue: Number,
|
initialValue: Number,
|
||||||
optional: true
|
optional: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -217,7 +217,7 @@ to be called in the constructor.
|
|||||||
```js
|
```js
|
||||||
class Counter extends owl.Component {
|
class Counter extends owl.Component {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
initialValue: 0
|
initialValue: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -804,8 +804,8 @@ class RootNode extends Component {
|
|||||||
children: [
|
children: [
|
||||||
{ label: "b" },
|
{ label: "b" },
|
||||||
{ label: "c", children: [{ label: "d" }, { label: "e" }] },
|
{ label: "c", children: [{ label: "d" }, { label: "e" }] },
|
||||||
{ label: "f", children: [{ label: "g" }] }
|
{ label: "f", children: [{ label: "g" }] },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
- [Setting an Environment](#setting-an-environment)
|
- [Setting an Environment](#setting-an-environment)
|
||||||
- [Using a sub environment](#using-a-sub-environment)
|
- [Using a sub environment](#using-a-sub-environment)
|
||||||
- [Content of an Environment](#content-of-an-environment)
|
- [Content of an Environment](#content-of-an-environment)
|
||||||
|
- [Special keys](#special-keys)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ Some good use cases for additional keys in the environment are:
|
|||||||
|
|
||||||
- some configuration keys,
|
- some configuration keys,
|
||||||
- session information,
|
- 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
|
Doing it this way means that components are easily testable: we can simply
|
||||||
create a test environment with mock services.
|
create a test environment with mock services.
|
||||||
@@ -112,10 +113,10 @@ async function myEnv() {
|
|||||||
qweb: qweb,
|
qweb: qweb,
|
||||||
services: {
|
services: {
|
||||||
localStorage: localStorage,
|
localStorage: localStorage,
|
||||||
rpc: rpc
|
rpc: rpc,
|
||||||
},
|
},
|
||||||
debug: false,
|
debug: false,
|
||||||
inMobileMode: true
|
inMobileMode: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,3 +126,26 @@ async function start() {
|
|||||||
await app.mount(document.body);
|
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`
|
||||||
|
|||||||
@@ -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.
|
useful, for example, to log the error somewhere.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
env.qweb.on("error", null, function(error) {
|
env.qweb.on("error", null, function (error) {
|
||||||
// do something
|
// do something
|
||||||
// react to the error
|
// react to the error
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ triggering events, and callbacks.
|
|||||||
```js
|
```js
|
||||||
const bus = new owl.core.EventBus();
|
const bus = new owl.core.EventBus();
|
||||||
|
|
||||||
bus.on("some-event", null, function(...args) {
|
bus.on("some-event", null, function (...args) {
|
||||||
console.log(...args);
|
console.log(...args);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ function useLoader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onWillStart(() => updateRecord(component.props.id));
|
onWillStart(() => updateRecord(component.props.id));
|
||||||
onWillUpdateProps(nextProps => updateRecord(nextProps.id));
|
onWillUpdateProps((nextProps) => updateRecord(nextProps.id));
|
||||||
|
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class ComponentB extends owl.Component {
|
|||||||
count: {type: Number},
|
count: {type: Number},
|
||||||
messages: {
|
messages: {
|
||||||
type: Array,
|
type: Array,
|
||||||
element: {type: Object, shape: {id: Boolean, text: 'string' }
|
element: {type: Object, shape: {id: Boolean, text: String }
|
||||||
},
|
},
|
||||||
date: Date,
|
date: Date,
|
||||||
combinedVal: [Number, Boolean]
|
combinedVal: [Number, Boolean]
|
||||||
|
|||||||
@@ -114,9 +114,9 @@ For example:
|
|||||||
const translations = {
|
const translations = {
|
||||||
hello: "bonjour",
|
hello: "bonjour",
|
||||||
yes: "oui",
|
yes: "oui",
|
||||||
no: "non"
|
no: "non",
|
||||||
};
|
};
|
||||||
const translateFn = str => translations[str] || str;
|
const translateFn = (str) => translations[str] || str;
|
||||||
|
|
||||||
const qweb = new QWeb({ translateFn });
|
const qweb = new QWeb({ translateFn });
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -532,7 +532,7 @@ The javascript QWeb implementation provides two useful debugging directives:
|
|||||||
|
|
||||||
```xml
|
```xml
|
||||||
<t t-if="a_test">
|
<t t-if="a_test">
|
||||||
<t t-debug="">
|
<t t-debug=""/>
|
||||||
</t>
|
</t>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -37,16 +37,16 @@ rendering context.
|
|||||||
</div>
|
</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
|
```xml
|
||||||
<div t-name="SomeComponent">
|
<div t-name="SomeComponent">
|
||||||
<div>some component</div>
|
<div>some component</div>
|
||||||
<Dialog title="Some Dialog">
|
<Dialog title="Some Dialog">
|
||||||
<t t-set="content">
|
<t t-set-slot="content">
|
||||||
<div>hey</div>
|
<div>hey</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-set="footer">
|
<t t-set-slot="footer">
|
||||||
<button t-on-click="doSomething">ok</button>
|
<button t-on-click="doSomething">ok</button>
|
||||||
</t>
|
</t>
|
||||||
</Dialog>
|
</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
|
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.
|
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
|
## Reference
|
||||||
|
|
||||||
Default slot: the first element inside the component which is not a named slot will
|
Default slot: the first element inside the component which is not a named slot will
|
||||||
|
|||||||
+14
-14
@@ -40,14 +40,14 @@ const actions = {
|
|||||||
state.todos.push({
|
state.todos.push({
|
||||||
id: state.nextId++,
|
id: state.nextId++,
|
||||||
message,
|
message,
|
||||||
isCompleted: false
|
isCompleted: false,
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
todos: [],
|
todos: [],
|
||||||
nextId: 1
|
nextId: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = new owl.Store({ state, actions });
|
const store = new owl.Store({ state, actions });
|
||||||
@@ -90,7 +90,7 @@ const config = {
|
|||||||
state,
|
state,
|
||||||
actions,
|
actions,
|
||||||
getters,
|
getters,
|
||||||
env
|
env,
|
||||||
};
|
};
|
||||||
const store = new Store(config);
|
const store = new Store(config);
|
||||||
```
|
```
|
||||||
@@ -110,7 +110,7 @@ const actions = {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
state.loginState = "error";
|
state.loginState = "error";
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ const actions = {
|
|||||||
state.recordId = recordId;
|
state.recordId = recordId;
|
||||||
const data = await doSomeRPC("/read/", recordId);
|
const data = await doSomeRPC("/read/", recordId);
|
||||||
state.recordData = data;
|
state.recordData = data;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ const actions = {
|
|||||||
const data = await doSomeRPC("/read/", recordId);
|
const data = await doSomeRPC("/read/", recordId);
|
||||||
state.recordId = recordId;
|
state.recordId = recordId;
|
||||||
state.recordData = data;
|
state.recordData = data;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -187,14 +187,14 @@ transform the data contained in the store.
|
|||||||
```js
|
```js
|
||||||
const getters = {
|
const getters = {
|
||||||
getPost({ state }, id) {
|
getPost({ state }, id) {
|
||||||
const post = state.posts.find(p => p.id === id);
|
const post = state.posts.find((p) => p.id === id);
|
||||||
const author = state.authors.find(a => a.id === post.id);
|
const author = state.authors.find((a) => a.id === post.id);
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
author,
|
author,
|
||||||
content: post.content
|
content: post.content,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// somewhere else
|
// somewhere else
|
||||||
@@ -224,11 +224,11 @@ Assume we have this store:
|
|||||||
const actions = {
|
const actions = {
|
||||||
increment({ state }, val) {
|
increment({ state }, val) {
|
||||||
state.counter.value += val;
|
state.counter.value += val;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
counter: { value: 0 }
|
counter: { value: 0 },
|
||||||
};
|
};
|
||||||
const store = new owl.Store({ state, actions });
|
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
|
```js
|
||||||
class Counter extends Component {
|
class Counter extends Component {
|
||||||
counter = useStore(state => state.counter);
|
counter = useStore((state) => state.counter);
|
||||||
dispatch = useDispatch();
|
dispatch = useDispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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).
|
argument, it executes it as soon as the DOM ready (or directly).
|
||||||
|
|
||||||
```js
|
```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 qweb = new owl.QWeb({ templates });
|
||||||
const app = new App({ qweb });
|
const app = new App({ qweb });
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
@@ -29,7 +29,7 @@ Promise.all([loadFile("templates.xml"), owl.utils.whenReady()]).then(function([t
|
|||||||
or alternatively:
|
or alternatively:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
owl.utils.whenReady(function() {
|
owl.utils.whenReady(function () {
|
||||||
const qweb = new owl.QWeb();
|
const qweb = new owl.QWeb();
|
||||||
const app = new App({ qweb });
|
const app = new App({ qweb });
|
||||||
app.mount(document.body);
|
app.mount(document.body);
|
||||||
|
|||||||
+7
-5
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "1.0.5",
|
"version": "1.0.11",
|
||||||
"description": "Odoo Web Library (OWL)",
|
"description": "Odoo Web Library (OWL)",
|
||||||
"main": "dist/owl.js",
|
"main": "dist/owl.js",
|
||||||
"types": "dist/types/index.d.ts",
|
"types": "dist/types/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"dist/types/",
|
"dist/types/",
|
||||||
"dist/owl.js"
|
"dist/owl.js",
|
||||||
|
"dist/owl-iife.js"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.15.3"
|
"node": ">=10.15.3"
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
"pretools:watch": "npm run build",
|
"pretools:watch": "npm run build",
|
||||||
"tools:watch": "npm-run-all --parallel tools:serve \"build:* -- --watch\"",
|
"tools:watch": "npm-run-all --parallel tools:serve \"build:* -- --watch\"",
|
||||||
"prettier": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --write",
|
"prettier": "prettier {src/*.ts,src/**/*.ts,tests/*.ts,tests/**/*.ts,doc/*.md,doc/**/*.md} --write",
|
||||||
"publish": "npm run buildcommonjs && npm publish",
|
"publish": "npm run build && mv dist/owl.js dist/owl-iife.js && npm run buildcommonjs && npm publish",
|
||||||
"release": "node tools/release.js"
|
"release": "node tools/release.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -48,7 +49,7 @@
|
|||||||
"jest-environment-jsdom": "^24.7.1",
|
"jest-environment-jsdom": "^24.7.1",
|
||||||
"live-server": "^1.2.1",
|
"live-server": "^1.2.1",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^2.0.4",
|
||||||
"rollup": "^1.6.0",
|
"rollup": "^1.6.0",
|
||||||
"rollup-plugin-typescript2": "^0.20.1",
|
"rollup-plugin-typescript2": "^0.20.1",
|
||||||
"sass": "^1.16.1",
|
"sass": "^1.16.1",
|
||||||
@@ -77,6 +78,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"printWidth": 100
|
"printWidth": 100,
|
||||||
|
"endOfLine": "auto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
# 🦉 OWL Roadmap 🦉
|
# 🦉 OWL Roadmap 🦉
|
||||||
|
|
||||||
- Current version: 1.0.5
|
- Current version: 1.0.11
|
||||||
- Status: stable
|
- Status: stable
|
||||||
|
|
||||||
This roadmap is only an attempt at predicting Owl's future. Everything may
|
This roadmap is only an attempt at predicting Owl's future. Everything may
|
||||||
@@ -16,6 +16,8 @@ change!
|
|||||||
|
|
||||||
### 2.x (2020? 2021? 2022?)
|
### 2.x (2020? 2021? 2022?)
|
||||||
|
|
||||||
|
- stop support for `t-set` directive to define the content of a slot
|
||||||
|
|
||||||
Maybe:
|
Maybe:
|
||||||
|
|
||||||
- reimplement vdom to use *block* system, like Vue 3, which should make Owl
|
- reimplement vdom to use *block* system, like Vue 3, which should make Owl
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
+44
-16
@@ -7,6 +7,7 @@ import { Fiber } from "./fiber";
|
|||||||
import "./props_validation";
|
import "./props_validation";
|
||||||
import { Scheduler, scheduler } from "./scheduler";
|
import { Scheduler, scheduler } from "./scheduler";
|
||||||
import { activateSheet } from "./styles";
|
import { activateSheet } from "./styles";
|
||||||
|
import { Browser, browser } from "../browser";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Owl Component System
|
* Owl Component System
|
||||||
@@ -34,7 +35,7 @@ import { activateSheet } from "./styles";
|
|||||||
*/
|
*/
|
||||||
export interface Env {
|
export interface Env {
|
||||||
qweb: QWeb;
|
qweb: QWeb;
|
||||||
[key: string]: any;
|
browser: Browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MountPosition = "first-child" | "last-child" | "self";
|
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) {
|
if (!this.env.qweb) {
|
||||||
this.env.qweb = new QWeb();
|
this.env.qweb = new QWeb();
|
||||||
}
|
}
|
||||||
|
if (!this.env.browser) {
|
||||||
|
this.env.browser = browser;
|
||||||
|
}
|
||||||
this.env.qweb.on("update", this, () => {
|
this.env.qweb.on("update", this, () => {
|
||||||
if (this.__owl__.isMounted) {
|
if (this.__owl__.isMounted) {
|
||||||
this.render(true);
|
this.render(true);
|
||||||
@@ -199,7 +203,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
|||||||
renderFn: qweb.render.bind(qweb, template),
|
renderFn: qweb.render.bind(qweb, template),
|
||||||
classObj: null,
|
classObj: null,
|
||||||
refs: null,
|
refs: null,
|
||||||
scope: null
|
scope: null,
|
||||||
};
|
};
|
||||||
if (constr.style) {
|
if (constr.style) {
|
||||||
this.__applyStyles(constr);
|
this.__applyStyles(constr);
|
||||||
@@ -312,6 +316,9 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (__owl__.isDestroyed) {
|
||||||
|
throw new Error("Cannot mount a destroyed component");
|
||||||
|
}
|
||||||
if (__owl__.currentFiber) {
|
if (__owl__.currentFiber) {
|
||||||
const currentFiber = __owl__.currentFiber;
|
const currentFiber = __owl__.currentFiber;
|
||||||
if (currentFiber.target === target && currentFiber.position === position) {
|
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> {
|
async render(force: boolean = false): Promise<void> {
|
||||||
const __owl__ = this.__owl__;
|
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,
|
// 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,
|
// or was unmounted and some state change triggered a render. Either way,
|
||||||
// we do not want to actually render anything in this case.
|
// we do not want to actually render anything in this case.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (__owl__.currentFiber && !__owl__.currentFiber.isRendered) {
|
if (currentFiber && !currentFiber.isRendered && !currentFiber.isCompleted) {
|
||||||
return scheduler.addFiber(__owl__.currentFiber.root);
|
return scheduler.addFiber(currentFiber.root);
|
||||||
}
|
}
|
||||||
// if we aren't mounted at this point, it implies that there is a
|
// if we aren't mounted at this point, it implies that there is a
|
||||||
// currentFiber that is already rendered (isRendered is true), so we are
|
// currentFiber that is already rendered (isRendered is true), so we are
|
||||||
@@ -488,9 +496,9 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
|||||||
}
|
}
|
||||||
this.willUnmount();
|
this.willUnmount();
|
||||||
__owl__.isMounted = false;
|
__owl__.isMounted = false;
|
||||||
if (this.__owl__.currentFiber) {
|
if (__owl__.currentFiber) {
|
||||||
this.__owl__.currentFiber.isCompleted = true;
|
__owl__.currentFiber.isCompleted = true;
|
||||||
this.__owl__.currentFiber.root.counter = 0;
|
__owl__.currentFiber.root.counter = 0;
|
||||||
}
|
}
|
||||||
const children = __owl__.children;
|
const children = __owl__.children;
|
||||||
for (let id in children) {
|
for (let id in children) {
|
||||||
@@ -509,7 +517,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
|||||||
const ev = new OwlEvent(component, eventType, {
|
const ev = new OwlEvent(component, eventType, {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
detail: payload
|
detail: payload,
|
||||||
});
|
});
|
||||||
const triggerHook = this.env[portalSymbol as any];
|
const triggerHook = this.env[portalSymbol as any];
|
||||||
if (triggerHook) {
|
if (triggerHook) {
|
||||||
@@ -544,7 +552,7 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
|||||||
}
|
}
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.willUpdateProps(nextProps),
|
this.willUpdateProps(nextProps),
|
||||||
__owl__.willUpdatePropsCB && __owl__.willUpdatePropsCB(nextProps)
|
__owl__.willUpdatePropsCB && __owl__.willUpdatePropsCB(nextProps),
|
||||||
]);
|
]);
|
||||||
if (fiber.isCompleted) {
|
if (fiber.isCompleted) {
|
||||||
return;
|
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
|
// key. So we fall back on looking for a template matching its name (or
|
||||||
// one of its subclass).
|
// one of its subclass).
|
||||||
|
|
||||||
let template: string;
|
let template: string = p.name;
|
||||||
while ((template = p.name) && !(template in qweb.templates) && p !== Component) {
|
while (!(template in qweb.templates) && p !== Component) {
|
||||||
p = p.__proto__;
|
p = p.__proto__;
|
||||||
|
template = p.name;
|
||||||
}
|
}
|
||||||
if (p === Component) {
|
if (p === Component) {
|
||||||
throw new Error(`Could not find template for component "${this.constructor.name}"`);
|
throw new Error(`Could not find template for component "${this.constructor.name}"`);
|
||||||
@@ -640,16 +649,35 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
|||||||
try {
|
try {
|
||||||
let vnode = __owl__.renderFn!(this, {
|
let vnode = __owl__.renderFn!(this, {
|
||||||
handlers: __owl__.boundHandlers,
|
handlers: __owl__.boundHandlers,
|
||||||
fiber: fiber
|
fiber: fiber,
|
||||||
});
|
});
|
||||||
// we iterate over the children to detect those that no longer belong to the
|
// 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
|
// current rendering: those ones, if not mounted yet, can (and have to) be
|
||||||
// destroyed right now, because they are not in the DOM, and thus we won't
|
// destroyed right now, because they are not in the DOM, and thus we won't
|
||||||
// be notified later on (when patching), that they are removed from the DOM
|
// be notified later on (when patching), that they are removed from the DOM
|
||||||
for (let childKey in __owl__.children) {
|
for (let childKey in __owl__.children) {
|
||||||
let child = __owl__.children[childKey];
|
const child = __owl__.children[childKey];
|
||||||
if (!child.__owl__.isMounted && child.__owl__.parentLastFiberId < fiber.id) {
|
const childOwl = child.__owl__;
|
||||||
child.destroy();
|
if (!childOwl.isMounted && childOwl.parentLastFiberId < fiber.id) {
|
||||||
|
// we only do here a "soft" destroy, meaning that we leave the child
|
||||||
|
// dom node alone, without removing it. Most of the time, it does not
|
||||||
|
// matter, because the child component is already unmounted. However,
|
||||||
|
// if some of its parent have been unmounted, the child could actually
|
||||||
|
// still be attached to its parent, and this may be important if we
|
||||||
|
// want to remount the parent, because the vdom need to match the
|
||||||
|
// actual DOM
|
||||||
|
child.__destroy(childOwl.parent);
|
||||||
|
if (childOwl.pvnode) {
|
||||||
|
// we remove the key here to make sure that the patching algorithm
|
||||||
|
// is able to make the difference between this pvnode and an eventual
|
||||||
|
// other instance of the same component
|
||||||
|
delete childOwl.pvnode.key;
|
||||||
|
// Since the component has been unmounted, we do not want to actually
|
||||||
|
// call a remove hook. This is pretty important, since the t-component
|
||||||
|
// directive actually disabled it, so the vdom algorithm will just
|
||||||
|
// not remove the child elm if we don't remove the hook.
|
||||||
|
delete childOwl.pvnode.data!.hook!.remove;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!vnode) {
|
if (!vnode) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { makeHandlerCode, MODS_CODE } from "../qweb/extensions";
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
const T_COMPONENT_MODS_CODE = Object.assign({}, MODS_CODE, {
|
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) {
|
QWeb.utils.defineProxy = function defineProxy(target, source) {
|
||||||
@@ -18,7 +18,7 @@ QWeb.utils.defineProxy = function defineProxy(target, source) {
|
|||||||
},
|
},
|
||||||
set(val) {
|
set(val) {
|
||||||
source[k] = val;
|
source[k] = val;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -238,7 +238,7 @@ QWeb.addDirective({
|
|||||||
|
|
||||||
// computing the props string representing the props object
|
// computing the props string representing the props object
|
||||||
let propStr = Object.keys(props)
|
let propStr = Object.keys(props)
|
||||||
.map(k => k + ":" + props[k])
|
.map((k) => k + ":" + props[k])
|
||||||
.join(",");
|
.join(",");
|
||||||
let componentID = ctx.generateID();
|
let componentID = ctx.generateID();
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@ QWeb.addDirective({
|
|||||||
let classDef = classAttr
|
let classDef = classAttr
|
||||||
.trim()
|
.trim()
|
||||||
.split(/\s+/)
|
.split(/\s+/)
|
||||||
.map(a => `'${a}':true`)
|
.map((a) => `'${a}':true`)
|
||||||
.join(",");
|
.join(",");
|
||||||
classObj = `_${ctx.generateID()}`;
|
classObj = `_${ctx.generateID()}`;
|
||||||
ctx.addLine(`let ${classObj} = {${classDef}};`);
|
ctx.addLine(`let ${classObj} = {${classDef}};`);
|
||||||
@@ -298,7 +298,7 @@ QWeb.addDirective({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let eventsCode = events
|
let eventsCode = events
|
||||||
.map(function([name, value]) {
|
.map(function ([name, value]) {
|
||||||
const capture = name.match(/\.capture/);
|
const capture = name.match(/\.capture/);
|
||||||
name = capture ? name.replace(/\.capture/, "") : name;
|
name = capture ? name.replace(/\.capture/, "") : name;
|
||||||
const { event, handler } = makeHandlerCode(
|
const { event, handler } = makeHandlerCode(
|
||||||
@@ -400,15 +400,52 @@ QWeb.addDirective({
|
|||||||
|
|
||||||
if (hasSlots) {
|
if (hasSlots) {
|
||||||
const clone = <Element>node.cloneNode(true);
|
const clone = <Element>node.cloneNode(true);
|
||||||
const slotNodes = clone.querySelectorAll("[t-set]");
|
|
||||||
|
// 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 node of clone.children) {
|
||||||
|
if (node.hasAttribute("t-set") && node.hasChildNodes()) {
|
||||||
|
node.setAttribute("t-set-slot", node.getAttribute("t-set")!);
|
||||||
|
node.removeAttribute("t-set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
|
||||||
|
const slotNames = new Set<string>();
|
||||||
const slotId = QWeb.nextSlotId++;
|
const slotId = QWeb.nextSlotId++;
|
||||||
ctx.addLine(`w${componentID}.__owl__.slotId = ${slotId};`);
|
ctx.addLine(`w${componentID}.__owl__.slotId = ${slotId};`);
|
||||||
if (slotNodes.length) {
|
if (slotNodes.length) {
|
||||||
for (let i = 0, length = slotNodes.length; i < length; i++) {
|
for (let i = 0, length = slotNodes.length; i < length; i++) {
|
||||||
const slotNode = slotNodes[i];
|
const slotNode = slotNodes[i];
|
||||||
|
// check if this is defined in a sub component (in which case it should
|
||||||
|
// be ignored)
|
||||||
|
let el = slotNode.parentElement;
|
||||||
|
let isInSubComponent = false;
|
||||||
|
while (el !== clone) {
|
||||||
|
if (
|
||||||
|
el!.hasAttribute("t-component") ||
|
||||||
|
el!.tagName[0] === el!.tagName[0].toUpperCase()
|
||||||
|
) {
|
||||||
|
isInSubComponent = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isInSubComponent) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let key = slotNode.getAttribute("t-set-slot")!;
|
||||||
|
if (slotNames.has(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
slotNames.add(key);
|
||||||
|
slotNode.removeAttribute("t-set-slot");
|
||||||
slotNode.parentElement!.removeChild(slotNode);
|
slotNode.parentElement!.removeChild(slotNode);
|
||||||
const key = slotNode.getAttribute("t-set")!;
|
|
||||||
slotNode.removeAttribute("t-set");
|
|
||||||
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
|
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
|
||||||
QWeb.slots[`${slotId}_${key}`] = slotFn;
|
QWeb.slots[`${slotId}_${key}`] = slotFn;
|
||||||
}
|
}
|
||||||
@@ -448,5 +485,5 @@ QWeb.addDirective({
|
|||||||
ctx.addLine(`w${componentID}.__owl__.parentLastFiberId = extra.fiber.id;`);
|
ctx.addLine(`w${componentID}.__owl__.parentLastFiberId = extra.fiber.id;`);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+17
-3
@@ -193,7 +193,7 @@ export class Fiber {
|
|||||||
|
|
||||||
// build patchQueue
|
// build patchQueue
|
||||||
const patchQueue: Fiber[] = [];
|
const patchQueue: Fiber[] = [];
|
||||||
const doWork: (Fiber) => Fiber | null = function(f) {
|
const doWork: (Fiber) => Fiber | null = function (f) {
|
||||||
patchQueue.push(f);
|
patchQueue.push(f);
|
||||||
return f.child;
|
return f.child;
|
||||||
};
|
};
|
||||||
@@ -225,6 +225,12 @@ export class Fiber {
|
|||||||
`Cannot attach '${component.constructor.name}' to target node (not same tag name)`
|
`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 {
|
} else {
|
||||||
target = component.__owl__.vnode || document.createElement(fiber.vnode!.sel!);
|
target = component.__owl__.vnode || document.createElement(fiber.vnode!.sel!);
|
||||||
}
|
}
|
||||||
@@ -232,12 +238,20 @@ export class Fiber {
|
|||||||
} else {
|
} else {
|
||||||
if (fiber.shouldPatch) {
|
if (fiber.shouldPatch) {
|
||||||
component.__patch(component.__owl__.vnode!, fiber.vnode!);
|
component.__patch(component.__owl__.vnode!, fiber.vnode!);
|
||||||
|
// When updating a Component's props (in directive),
|
||||||
|
// the component has a pvnode AND should be patched.
|
||||||
|
// However, its pvnode.elm may have changed if it is a High Order Component
|
||||||
|
if (component.__owl__.pvnode) {
|
||||||
|
component.__owl__.pvnode.elm = component.__owl__.vnode!.elm;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
component.__patch(document.createElement(fiber.vnode!.sel!), fiber.vnode!);
|
component.__patch(document.createElement(fiber.vnode!.sel!), fiber.vnode!);
|
||||||
component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm;
|
component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
component.__owl__.currentFiber = null;
|
if (fiber === component.__owl__.currentFiber) {
|
||||||
|
component.__owl__.currentFiber = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert into the DOM (mount case)
|
// insert into the DOM (mount case)
|
||||||
@@ -274,7 +288,7 @@ export class Fiber {
|
|||||||
* Cancel a fiber and all its children.
|
* Cancel a fiber and all its children.
|
||||||
*/
|
*/
|
||||||
cancel() {
|
cancel() {
|
||||||
this._walk(f => {
|
this._walk((f) => {
|
||||||
if (!f.isRendered) {
|
if (!f.isRendered) {
|
||||||
f.root.counter--;
|
f.root.counter--;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { QWeb } from "../qweb/index";
|
|||||||
* This is why it is only done in 'dev' mode.
|
* 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;
|
const propsDef = (<any>Widget).props;
|
||||||
if (propsDef instanceof Array) {
|
if (propsDef instanceof Array) {
|
||||||
// list of strings (prop names)
|
// list of strings (prop names)
|
||||||
@@ -37,7 +37,7 @@ QWeb.utils.validateProps = function(Widget, props: Object) {
|
|||||||
if (propsDef[propName] && !propsDef[propName].optional) {
|
if (propsDef[propName] && !propsDef[propName].optional) {
|
||||||
throw new Error(`Missing props '${propName}' (component '${Widget.name}')`);
|
throw new Error(`Missing props '${propName}' (component '${Widget.name}')`);
|
||||||
} else {
|
} else {
|
||||||
break;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let isValid;
|
let isValid;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Fiber } from "./fiber";
|
import { Fiber } from "./fiber";
|
||||||
|
import { browser } from "../browser";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Owl Scheduler Class
|
* Owl Scheduler Class
|
||||||
@@ -19,9 +20,9 @@ interface Task {
|
|||||||
export class Scheduler {
|
export class Scheduler {
|
||||||
tasks: Task[] = [];
|
tasks: Task[] = [];
|
||||||
isRunning: boolean = false;
|
isRunning: boolean = false;
|
||||||
requestAnimationFrame: typeof window.requestAnimationFrame;
|
requestAnimationFrame: Window["requestAnimationFrame"];
|
||||||
|
|
||||||
constructor(requestAnimationFrame) {
|
constructor(requestAnimationFrame: Window["requestAnimationFrame"]) {
|
||||||
this.requestAnimationFrame = requestAnimationFrame;
|
this.requestAnimationFrame = requestAnimationFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ export class Scheduler {
|
|||||||
return reject(fiber.error);
|
return reject(fiber.error);
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
if (!this.isRunning) {
|
if (!this.isRunning) {
|
||||||
this.start();
|
this.start();
|
||||||
@@ -59,7 +60,7 @@ export class Scheduler {
|
|||||||
|
|
||||||
rejectFiber(fiber: Fiber, reason: string) {
|
rejectFiber(fiber: Fiber, reason: string) {
|
||||||
fiber = fiber.root;
|
fiber = fiber.root;
|
||||||
const index = this.tasks.findIndex(t => t.fiber === fiber);
|
const index = this.tasks.findIndex((t) => t.fiber === fiber);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
const [task] = this.tasks.splice(index, 1);
|
const [task] = this.tasks.splice(index, 1);
|
||||||
fiber.cancel();
|
fiber.cancel();
|
||||||
@@ -75,7 +76,7 @@ export class Scheduler {
|
|||||||
flush() {
|
flush() {
|
||||||
let tasks = this.tasks;
|
let tasks = this.tasks;
|
||||||
this.tasks = [];
|
this.tasks = [];
|
||||||
tasks = tasks.filter(task => {
|
tasks = tasks.filter((task) => {
|
||||||
if (task.fiber.isCompleted) {
|
if (task.fiber.isCompleted) {
|
||||||
task.callback();
|
task.callback();
|
||||||
return false;
|
return false;
|
||||||
@@ -109,5 +110,4 @@ export class Scheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const raf = window.requestAnimationFrame.bind(window);
|
export const scheduler = new Scheduler(browser.requestAnimationFrame);
|
||||||
export const scheduler = new Scheduler(raf);
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
export const STYLESHEETS: { [id: string]: HTMLStyleElement } = {};
|
export const STYLESHEETS: { [id: string]: HTMLStyleElement } = {};
|
||||||
|
|
||||||
export function processSheet(str: string): string {
|
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 selectorStack: string[][] = [];
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
let rules: string[] = [];
|
let rules: string[] = [];
|
||||||
|
|||||||
+1
-1
@@ -26,5 +26,5 @@ Object.defineProperty(config, "mode", {
|
|||||||
} else {
|
} else {
|
||||||
console.log(`Owl is now running in 'prod' mode.`);
|
console.log(`Owl is now running in 'prod' mode.`);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+6
-6
@@ -79,9 +79,9 @@ export class Context extends EventBus {
|
|||||||
async __notifyComponents() {
|
async __notifyComponents() {
|
||||||
const rev = ++this.rev;
|
const rev = ++this.rev;
|
||||||
const subscriptions = this.subscriptions.update;
|
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) {
|
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
|
// at this point, each component in the current group has registered a
|
||||||
// top level fiber in the scheduler. It could happen that rendering these
|
// 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
|
// 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);
|
__owl__.observer.notifyCB = component.render.bind(component);
|
||||||
}
|
}
|
||||||
const currentCB = __owl__.observer.notifyCB;
|
const currentCB = __owl__.observer.notifyCB;
|
||||||
__owl__.observer.notifyCB = function() {
|
__owl__.observer.notifyCB = function () {
|
||||||
if (ctx.rev > mapping[id]) {
|
if (ctx.rev > mapping[id]) {
|
||||||
// in this case, the context has been updated since we were rendering
|
// 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
|
// 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;
|
mapping[id] = 0;
|
||||||
const renderFn = __owl__.renderFn;
|
const renderFn = __owl__.renderFn;
|
||||||
__owl__.renderFn = function(comp, params) {
|
__owl__.renderFn = function (comp, params) {
|
||||||
mapping[id] = ctx.rev;
|
mapping[id] = ctx.rev;
|
||||||
return renderFn(comp, params);
|
return renderFn(comp, params);
|
||||||
};
|
};
|
||||||
ctx.on("update", component, async contextRev => {
|
ctx.on("update", component, async (contextRev) => {
|
||||||
if (mapping[id] < contextRev) {
|
if (mapping[id] < contextRev) {
|
||||||
mapping[id] = contextRev;
|
mapping[id] = contextRev;
|
||||||
await method();
|
await method();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const __destroy = component.__destroy;
|
const __destroy = component.__destroy;
|
||||||
component.__destroy = parent => {
|
component.__destroy = (parent) => {
|
||||||
ctx.off("update", component);
|
ctx.off("update", component);
|
||||||
delete mapping[id];
|
delete mapping[id];
|
||||||
__destroy.call(component, parent);
|
__destroy.call(component, parent);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export class EventBus {
|
|||||||
}
|
}
|
||||||
this.subscriptions[eventType].push({
|
this.subscriptions[eventType].push({
|
||||||
owner,
|
owner,
|
||||||
callback
|
callback,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ export class EventBus {
|
|||||||
off(eventType: string, owner: any) {
|
off(eventType: string, owner: any) {
|
||||||
const subs = this.subscriptions[eventType];
|
const subs = this.subscriptions[eventType];
|
||||||
if (subs) {
|
if (subs) {
|
||||||
this.subscriptions[eventType] = subs.filter(s => s.owner !== owner);
|
this.subscriptions[eventType] = subs.filter((s) => s.owner !== owner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,12 @@ export class Observer {
|
|||||||
notifyCB() {}
|
notifyCB() {}
|
||||||
|
|
||||||
observe<T>(value: T, parent?: any): T {
|
observe<T>(value: T, parent?: any): T {
|
||||||
if (value === null || typeof value !== "object" || value instanceof Date) {
|
if (
|
||||||
|
value === null ||
|
||||||
|
typeof value !== "object" ||
|
||||||
|
value instanceof Date ||
|
||||||
|
value instanceof Promise
|
||||||
|
) {
|
||||||
// fun fact: typeof null === 'object'
|
// fun fact: typeof null === 'object'
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@@ -67,14 +72,14 @@ export class Observer {
|
|||||||
self.notifyCB();
|
self.notifyCB();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const metadata = {
|
const metadata = {
|
||||||
value,
|
value,
|
||||||
proxy,
|
proxy,
|
||||||
rev: this.rev,
|
rev: this.rev,
|
||||||
parent
|
parent,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.weakMap.set(value, metadata);
|
this.weakMap.set(value, metadata);
|
||||||
|
|||||||
+13
-13
@@ -36,11 +36,11 @@ export function useState<T>(state: T): T {
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
function makeLifecycleHook(method: string, reverse: boolean = false) {
|
function makeLifecycleHook(method: string, reverse: boolean = false) {
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
return function(cb) {
|
return function (cb) {
|
||||||
const component: Component = Component.current!;
|
const component: Component = Component.current!;
|
||||||
if (component.__owl__[method]) {
|
if (component.__owl__[method]) {
|
||||||
const current = component.__owl__[method];
|
const current = component.__owl__[method];
|
||||||
component.__owl__[method] = function() {
|
component.__owl__[method] = function () {
|
||||||
current.call(component);
|
current.call(component);
|
||||||
cb.call(component);
|
cb.call(component);
|
||||||
};
|
};
|
||||||
@@ -49,11 +49,11 @@ function makeLifecycleHook(method: string, reverse: boolean = false) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return function(cb) {
|
return function (cb) {
|
||||||
const component: Component = Component.current!;
|
const component: Component = Component.current!;
|
||||||
if (component.__owl__[method]) {
|
if (component.__owl__[method]) {
|
||||||
const current = component.__owl__[method];
|
const current = component.__owl__[method];
|
||||||
component.__owl__[method] = function() {
|
component.__owl__[method] = function () {
|
||||||
cb.call(component);
|
cb.call(component);
|
||||||
current.call(component);
|
current.call(component);
|
||||||
};
|
};
|
||||||
@@ -65,12 +65,12 @@ function makeLifecycleHook(method: string, reverse: boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function makeAsyncHook(method: string) {
|
function makeAsyncHook(method: string) {
|
||||||
return function(cb) {
|
return function (cb) {
|
||||||
const component: Component = Component.current!;
|
const component: Component = Component.current!;
|
||||||
if (component.__owl__[method]) {
|
if (component.__owl__[method]) {
|
||||||
const current = component.__owl__[method];
|
const current = component.__owl__[method];
|
||||||
component.__owl__[method] = function(...args) {
|
component.__owl__[method] = function (...args) {
|
||||||
return Promise.all[(current.call(component, ...args), cb.call(component, ...args))];
|
return Promise.all([current.call(component, ...args), cb.call(component, ...args)]);
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
component.__owl__[method] = cb;
|
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
|
* The purpose of this hook is to allow components to get a reference to a sub
|
||||||
* html node or component.
|
* html node or component.
|
||||||
*/
|
*/
|
||||||
interface Ref {
|
interface Ref<C extends Component = Component> {
|
||||||
el: HTMLElement | null;
|
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__;
|
const __owl__ = Component.current!.__owl__;
|
||||||
return {
|
return {
|
||||||
get el(): HTMLElement | null {
|
get el(): HTMLElement | null {
|
||||||
@@ -111,10 +111,10 @@ export function useRef(name: string): Ref {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
get comp(): Component | null {
|
get comp(): C | null {
|
||||||
const val = __owl__.refs && __owl__.refs[name];
|
const val = __owl__.refs && __owl__.refs[name];
|
||||||
return val instanceof Component ? val : null;
|
return val instanceof Component ? (val as C) : null;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -35,6 +35,6 @@ export const hooks = Object.assign({}, _hooks, {
|
|||||||
useContext: _context.useContext,
|
useContext: _context.useContext,
|
||||||
useDispatch: _store.useDispatch,
|
useDispatch: _store.useDispatch,
|
||||||
useGetters: _store.useGetters,
|
useGetters: _store.useGetters,
|
||||||
useStore: _store.useStore
|
useStore: _store.useStore,
|
||||||
});
|
});
|
||||||
export const __info__ = {};
|
export const __info__ = {};
|
||||||
|
|||||||
+4
-4
@@ -26,8 +26,8 @@ export class Portal extends Component<Props> {
|
|||||||
static template = xml`<portal><t t-slot="default"/></portal>`;
|
static template = xml`<portal><t t-slot="default"/></portal>`;
|
||||||
static props = {
|
static props = {
|
||||||
target: {
|
target: {
|
||||||
type: String
|
type: String,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// boolean to indicate whether or not we must listen to 'dom-appended' event
|
// 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
|
// 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
|
// register an handler to those events just before children will trigger them
|
||||||
useSubEnv({
|
useSubEnv({
|
||||||
[portalSymbol]: ev => {
|
[portalSymbol]: (ev) => {
|
||||||
if (!this._handledEvents.has(ev.type)) {
|
if (!this._handledEvents.has(ev.type)) {
|
||||||
this.portal!.elm!.addEventListener(ev.type, this._handlerTunnel);
|
this.portal!.elm!.addEventListener(ev.type, this._handlerTunnel);
|
||||||
this._handledEvents.add(ev.type);
|
this._handledEvents.add(ev.type);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
+24
-22
@@ -49,6 +49,7 @@ function compileValueNode(value: any, node: Element, qweb: QWeb, ctx: Compilatio
|
|||||||
if (ctx.escaping) {
|
if (ctx.escaping) {
|
||||||
let protectID;
|
let protectID;
|
||||||
if (value.hasBody) {
|
if (value.hasBody) {
|
||||||
|
ctx.rootContext.shouldDefineUtils = true;
|
||||||
protectID = ctx.startProtectScope();
|
protectID = ctx.startProtectScope();
|
||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`${exprID} = ${exprID} instanceof utils.VDomArray ? utils.vDomToString(${exprID}) : ${exprID};`
|
`${exprID} = ${exprID} instanceof utils.VDomArray ? utils.vDomToString(${exprID}) : ${exprID};`
|
||||||
@@ -96,7 +97,7 @@ QWeb.addDirective({
|
|||||||
let value = ctx.getValue(node.getAttribute("t-esc")!);
|
let value = ctx.getValue(node.getAttribute("t-esc")!);
|
||||||
compileValueNode(value, node, qweb, ctx.subContext("escaping", true));
|
compileValueNode(value, node, qweb, ctx.subContext("escaping", true));
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
QWeb.addDirective({
|
QWeb.addDirective({
|
||||||
@@ -106,7 +107,7 @@ QWeb.addDirective({
|
|||||||
let value = ctx.getValue(node.getAttribute("t-raw")!);
|
let value = ctx.getValue(node.getAttribute("t-raw")!);
|
||||||
compileValueNode(value, node, qweb, ctx);
|
compileValueNode(value, node, qweb, ctx);
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -163,7 +164,7 @@ QWeb.addDirective({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -179,7 +180,7 @@ QWeb.addDirective({
|
|||||||
},
|
},
|
||||||
finalize({ ctx }) {
|
finalize({ ctx }) {
|
||||||
ctx.closeIf();
|
ctx.closeIf();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
QWeb.addDirective({
|
QWeb.addDirective({
|
||||||
@@ -195,7 +196,7 @@ QWeb.addDirective({
|
|||||||
},
|
},
|
||||||
finalize({ ctx }) {
|
finalize({ ctx }) {
|
||||||
ctx.closeIf();
|
ctx.closeIf();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
QWeb.addDirective({
|
QWeb.addDirective({
|
||||||
@@ -208,7 +209,7 @@ QWeb.addDirective({
|
|||||||
},
|
},
|
||||||
finalize({ ctx }) {
|
finalize({ ctx }) {
|
||||||
ctx.closeIf();
|
ctx.closeIf();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -222,9 +223,6 @@ QWeb.addDirective({
|
|||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
ctx.rootContext.shouldDefineScope = true;
|
ctx.rootContext.shouldDefineScope = true;
|
||||||
ctx.rootContext.shouldDefineUtils = true;
|
ctx.rootContext.shouldDefineUtils = true;
|
||||||
if (node.nodeName !== "t") {
|
|
||||||
throw new Error("Invalid tag for t-call directive (should be 't')");
|
|
||||||
}
|
|
||||||
const subTemplate = node.getAttribute("t-call")!;
|
const subTemplate = node.getAttribute("t-call")!;
|
||||||
const nodeTemplate = qweb.templates[subTemplate];
|
const nodeTemplate = qweb.templates[subTemplate];
|
||||||
if (!nodeTemplate) {
|
if (!nodeTemplate) {
|
||||||
@@ -233,21 +231,22 @@ QWeb.addDirective({
|
|||||||
|
|
||||||
// Step 2: compile target template in sub templates
|
// Step 2: compile target template in sub templates
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
if (!qweb.subTemplates[subTemplate]) {
|
let subId = qweb.subTemplates[subTemplate];
|
||||||
qweb.subTemplates[subTemplate] = true;
|
if (!subId) {
|
||||||
|
subId = QWeb.nextId++;
|
||||||
|
qweb.subTemplates[subTemplate] = subId;
|
||||||
const subTemplateFn = qweb._compile(subTemplate, nodeTemplate.elem, ctx, true);
|
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
|
// Step 3: compile t-call body if necessary
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
let hasBody = node.hasChildNodes();
|
let hasBody = node.hasChildNodes();
|
||||||
let protectID;
|
const protectID = ctx.startProtectScope();
|
||||||
if (hasBody) {
|
if (hasBody) {
|
||||||
// we add a sub scope to protect the ambient scope
|
// we add a sub scope to protect the ambient scope
|
||||||
ctx.addLine(`{`);
|
ctx.addLine(`{`);
|
||||||
ctx.indent();
|
ctx.indent();
|
||||||
protectID = ctx.startProtectScope();
|
|
||||||
const nodeCopy = node.cloneNode(true) as Element;
|
const nodeCopy = node.cloneNode(true) as Element;
|
||||||
for (let attr of ["t-if", "t-else", "t-elif", "t-call"]) {
|
for (let attr of ["t-if", "t-else", "t-elif", "t-call"]) {
|
||||||
nodeCopy.removeAttribute(attr);
|
nodeCopy.removeAttribute(attr);
|
||||||
@@ -268,31 +267,30 @@ QWeb.addDirective({
|
|||||||
|
|
||||||
// Step 4: add the appropriate function call to current component
|
// Step 4: add the appropriate function call to current component
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
const callingScope = hasBody ? "scope" : "Object.assign(Object.create(context), scope)";
|
|
||||||
const parentComponent = `utils.getComponent(context)`;
|
const parentComponent = `utils.getComponent(context)`;
|
||||||
const key = ctx.generateTemplateKey();
|
const key = ctx.generateTemplateKey();
|
||||||
const parentNode = ctx.parentNode ? `c${ctx.parentNode}` : "result";
|
const parentNode = ctx.parentNode ? `c${ctx.parentNode}` : "result";
|
||||||
const extra = `Object.assign({}, extra, {parentNode: ${parentNode}, parent: ${parentComponent}, key: ${key}})`;
|
const extra = `Object.assign({}, extra, {parentNode: ${parentNode}, parent: ${parentComponent}, key: ${key}})`;
|
||||||
if (ctx.parentNode) {
|
if (ctx.parentNode) {
|
||||||
ctx.addLine(`this.subTemplates['${subTemplate}'].call(this, ${callingScope}, ${extra});`);
|
ctx.addLine(`this.constructor.subTemplates['${subId}'].call(this, scope, ${extra});`);
|
||||||
} else {
|
} else {
|
||||||
// this is a t-call with no parentnode, we need to extract the result
|
// this is a t-call with no parentnode, we need to extract the result
|
||||||
ctx.rootContext.shouldDefineResult = true;
|
ctx.rootContext.shouldDefineResult = true;
|
||||||
ctx.addLine(`result = []`);
|
ctx.addLine(`result = []`);
|
||||||
ctx.addLine(`this.subTemplates['${subTemplate}'].call(this, ${callingScope}, ${extra});`);
|
ctx.addLine(`this.constructor.subTemplates['${subId}'].call(this, scope, ${extra});`);
|
||||||
ctx.addLine(`result = result[0]`);
|
ctx.addLine(`result = result[0]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: restore previous scope
|
// Step 5: restore previous scope
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
if (hasBody) {
|
if (hasBody) {
|
||||||
ctx.stopProtectScope(protectID);
|
|
||||||
ctx.dedent();
|
ctx.dedent();
|
||||||
ctx.addLine(`}`);
|
ctx.addLine(`}`);
|
||||||
}
|
}
|
||||||
|
ctx.stopProtectScope(protectID);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -314,9 +312,13 @@ QWeb.addDirective({
|
|||||||
let valuesID = ctx.generateID();
|
let valuesID = ctx.generateID();
|
||||||
ctx.addLine(`let _${keysID} = _${valuesID} = _${arrayID};`);
|
ctx.addLine(`let _${keysID} = _${valuesID} = _${arrayID};`);
|
||||||
ctx.addIf(`!(_${arrayID} instanceof Array)`);
|
ctx.addIf(`!(_${arrayID} instanceof Array)`);
|
||||||
|
ctx.addIf(`typeof _${arrayID}[Symbol.iterator] === 'function'`);
|
||||||
|
ctx.addLine(`_${keysID} = [..._${arrayID}];`)
|
||||||
|
ctx.addElse();
|
||||||
ctx.addLine(`_${keysID} = Object.keys(_${arrayID});`);
|
ctx.addLine(`_${keysID} = Object.keys(_${arrayID});`);
|
||||||
ctx.addLine(`_${valuesID} = Object.values(_${arrayID});`);
|
ctx.addLine(`_${valuesID} = Object.values(_${arrayID});`);
|
||||||
ctx.closeIf();
|
ctx.closeIf();
|
||||||
|
ctx.closeIf();
|
||||||
ctx.addLine(`let _length${keysID} = _${keysID}.length;`);
|
ctx.addLine(`let _length${keysID} = _${keysID}.length;`);
|
||||||
let varsID = ctx.startProtectScope(true);
|
let varsID = ctx.startProtectScope(true);
|
||||||
const loopVar = `i${ctx.loopNumber}`;
|
const loopVar = `i${ctx.loopNumber}`;
|
||||||
@@ -353,7 +355,7 @@ QWeb.addDirective({
|
|||||||
ctx.addLine("}");
|
ctx.addLine("}");
|
||||||
ctx.stopProtectScope(varsID);
|
ctx.stopProtectScope(varsID);
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -364,7 +366,7 @@ QWeb.addDirective({
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
atNodeEncounter({ ctx }) {
|
atNodeEncounter({ ctx }) {
|
||||||
ctx.addLine("debugger;");
|
ctx.addLine("debugger;");
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -376,5 +378,5 @@ QWeb.addDirective({
|
|||||||
atNodeEncounter({ ctx, value }) {
|
atNodeEncounter({ ctx, value }) {
|
||||||
const expr = ctx.formatExpression(value);
|
const expr = ctx.formatExpression(value);
|
||||||
ctx.addLine(`console.log(${expr})`);
|
ctx.addLine(`console.log(${expr})`);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export class CompilationContext {
|
|||||||
const tokens = compileExprToArray(expr, this.variables);
|
const tokens = compileExprToArray(expr, this.variables);
|
||||||
const done = new Set();
|
const done = new Set();
|
||||||
return tokens
|
return tokens
|
||||||
.map(tok => {
|
.map((tok) => {
|
||||||
if (tok.varName) {
|
if (tok.varName) {
|
||||||
if (!done.has(tok.varName)) {
|
if (!done.has(tok.varName)) {
|
||||||
done.add(tok.varName);
|
done.add(tok.varName);
|
||||||
@@ -189,18 +189,19 @@ export class CompilationContext {
|
|||||||
return `(${this.formatExpression(s.slice(2, -2))})`;
|
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 + "`";
|
return "`" + r + "`";
|
||||||
}
|
}
|
||||||
startProtectScope(codeBlock?: boolean): number {
|
startProtectScope(codeBlock?: boolean): number {
|
||||||
const protectID = this.generateID();
|
const protectID = this.generateID();
|
||||||
this.rootContext.protectedScopeNumber++;
|
this.rootContext.protectedScopeNumber++;
|
||||||
this.rootContext.shouldDefineScope = true;
|
this.rootContext.shouldDefineScope = true;
|
||||||
const scopeExpr = codeBlock
|
const scopeExpr = `Object.create(scope);`;
|
||||||
? `Object.create(scope);`
|
|
||||||
: `Object.assign(Object.create(context), scope);`;
|
|
||||||
this.addLine(`let _origScope${protectID} = scope;`);
|
this.addLine(`let _origScope${protectID} = scope;`);
|
||||||
this.addLine(`scope = ${scopeExpr}`);
|
this.addLine(`scope = ${scopeExpr}`);
|
||||||
|
if (!codeBlock) {
|
||||||
|
this.addLine(`scope.__access_mode__ = 'ro';`);
|
||||||
|
}
|
||||||
return protectID;
|
return protectID;
|
||||||
}
|
}
|
||||||
stopProtectScope(protectID: number) {
|
stopProtectScope(protectID: number) {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const WORD_REPLACEMENT = {
|
|||||||
gt: ">",
|
gt: ">",
|
||||||
gte: ">=",
|
gte: ">=",
|
||||||
lt: "<",
|
lt: "<",
|
||||||
lte: "<="
|
lte: "<=",
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface QWebVar {
|
export interface QWebVar {
|
||||||
@@ -77,7 +77,7 @@ const STATIC_TOKEN_MAP: { [key: string]: TKind } = {
|
|||||||
":": "COLON",
|
":": "COLON",
|
||||||
",": "COMMA",
|
",": "COMMA",
|
||||||
"(": "LEFT_PAREN",
|
"(": "LEFT_PAREN",
|
||||||
")": "RIGHT_PAREN"
|
")": "RIGHT_PAREN",
|
||||||
};
|
};
|
||||||
|
|
||||||
// note that the space after typeof is relevant. It makes sure that the formatted
|
// note that the space after typeof is relevant. It makes sure that the formatted
|
||||||
@@ -86,7 +86,7 @@ const OPERATORS = "...,.,===,==,+,!==,!=,!,||,&&,>=,>,<=,<,?,-,*,/,%,typeof ,=>,
|
|||||||
|
|
||||||
type Tokenizer = (expr: string) => Token | false;
|
type Tokenizer = (expr: string) => Token | false;
|
||||||
|
|
||||||
let tokenizeString: Tokenizer = function(expr) {
|
let tokenizeString: Tokenizer = function (expr) {
|
||||||
let s = expr[0];
|
let s = expr[0];
|
||||||
let start = s;
|
let start = s;
|
||||||
if (s !== "'" && s !== '"') {
|
if (s !== "'" && s !== '"') {
|
||||||
@@ -114,7 +114,7 @@ let tokenizeString: Tokenizer = function(expr) {
|
|||||||
return { type: "VALUE", value: s };
|
return { type: "VALUE", value: s };
|
||||||
};
|
};
|
||||||
|
|
||||||
let tokenizeNumber: Tokenizer = function(expr) {
|
let tokenizeNumber: Tokenizer = function (expr) {
|
||||||
let s = expr[0];
|
let s = expr[0];
|
||||||
if (s && s.match(/[0-9]/)) {
|
if (s && s.match(/[0-9]/)) {
|
||||||
let i = 1;
|
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];
|
let s = expr[0];
|
||||||
if (s && s.match(/[a-zA-Z_\$]/)) {
|
if (s && s.match(/[a-zA-Z_\$]/)) {
|
||||||
let i = 1;
|
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];
|
const char = expr[0];
|
||||||
if (char && char in STATIC_TOKEN_MAP) {
|
if (char && char in STATIC_TOKEN_MAP) {
|
||||||
return { type: STATIC_TOKEN_MAP[char], value: char };
|
return { type: STATIC_TOKEN_MAP[char], value: char };
|
||||||
@@ -153,7 +153,7 @@ const tokenizeStatic: Tokenizer = function(expr) {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const tokenizeOperator: Tokenizer = function(expr) {
|
const tokenizeOperator: Tokenizer = function (expr) {
|
||||||
for (let op of OPERATORS) {
|
for (let op of OPERATORS) {
|
||||||
if (expr.startsWith(op)) {
|
if (expr.startsWith(op)) {
|
||||||
return { type: "OPERATOR", value: op };
|
return { type: "OPERATOR", value: op };
|
||||||
@@ -167,7 +167,7 @@ const TOKENIZERS = [
|
|||||||
tokenizeNumber,
|
tokenizeNumber,
|
||||||
tokenizeOperator,
|
tokenizeOperator,
|
||||||
tokenizeSymbol,
|
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 {
|
export function compileExpr(expr: string, scope: { [key: string]: QWebVar }): string {
|
||||||
return compileExprToArray(expr, scope)
|
return compileExprToArray(expr, scope)
|
||||||
.map(t => t.value)
|
.map((t) => t.value)
|
||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-22
@@ -23,7 +23,7 @@ import { QWeb } from "./qweb";
|
|||||||
export const MODS_CODE = {
|
export const MODS_CODE = {
|
||||||
prevent: "e.preventDefault();",
|
prevent: "e.preventDefault();",
|
||||||
self: "if (e.target !== this.elm) {return}",
|
self: "if (e.target !== this.elm) {return}",
|
||||||
stop: "e.stopPropagation();"
|
stop: "e.stopPropagation();",
|
||||||
};
|
};
|
||||||
|
|
||||||
interface HandlerInfo {
|
interface HandlerInfo {
|
||||||
@@ -50,7 +50,7 @@ export function makeHandlerCode(
|
|||||||
let code: string;
|
let code: string;
|
||||||
// check if it is a method with no args, a method with args or an expression
|
// check if it is a method with no args, a method with args or an expression
|
||||||
let args: string = "";
|
let args: string = "";
|
||||||
const name: string = value.replace(/\(.*\)/, function(_args) {
|
const name: string = value.replace(/\(.*\)/, function (_args) {
|
||||||
args = _args.slice(1, -1);
|
args = _args.slice(1, -1);
|
||||||
return "";
|
return "";
|
||||||
});
|
});
|
||||||
@@ -74,7 +74,7 @@ export function makeHandlerCode(
|
|||||||
putInCache = false;
|
putInCache = false;
|
||||||
code = ctx.captureExpression(value);
|
code = ctx.captureExpression(value);
|
||||||
}
|
}
|
||||||
const modCode = mods.map(mod => modcodes[mod]).join("");
|
const modCode = mods.map((mod) => modcodes[mod]).join("");
|
||||||
let handler = `function (e) {if (!context.__owl__.isMounted){return}${modCode}${code}}`;
|
let handler = `function (e) {if (!context.__owl__.isMounted){return}${modCode}${code}}`;
|
||||||
if (putInCache) {
|
if (putInCache) {
|
||||||
const key = ctx.generateTemplateKey(event);
|
const key = ctx.generateTemplateKey(event);
|
||||||
@@ -90,7 +90,7 @@ QWeb.addDirective({
|
|||||||
atNodeCreation({ ctx, fullName, value, nodeID }) {
|
atNodeCreation({ ctx, fullName, value, nodeID }) {
|
||||||
const { event, handler } = makeHandlerCode(ctx, fullName, value, true);
|
const { event, handler } = makeHandlerCode(ctx, fullName, value, true);
|
||||||
ctx.addLine(`p${nodeID}.on['${event}'] = ${handler};`);
|
ctx.addLine(`p${nodeID}.on['${event}'] = ${handler};`);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -105,17 +105,17 @@ QWeb.addDirective({
|
|||||||
ctx.addLine(`const ${refKey} = ${ctx.interpolate(value)};`);
|
ctx.addLine(`const ${refKey} = ${ctx.interpolate(value)};`);
|
||||||
addNodeHook("create", `context.__owl__.refs[${refKey}] = n.elm;`);
|
addNodeHook("create", `context.__owl__.refs[${refKey}] = n.elm;`);
|
||||||
addNodeHook("destroy", `delete context.__owl__.refs[${refKey}];`);
|
addNodeHook("destroy", `delete context.__owl__.refs[${refKey}];`);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// t-transition
|
// t-transition
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
QWeb.utils.nextFrame = function(cb: () => void) {
|
QWeb.utils.nextFrame = function (cb: () => void) {
|
||||||
requestAnimationFrame(() => requestAnimationFrame(cb));
|
requestAnimationFrame(() => requestAnimationFrame(cb));
|
||||||
};
|
};
|
||||||
|
|
||||||
QWeb.utils.transitionInsert = function(vn: VNode, name: string) {
|
QWeb.utils.transitionInsert = function (vn: VNode, name: string) {
|
||||||
const elm = <HTMLElement>vn.elm;
|
const elm = <HTMLElement>vn.elm;
|
||||||
// remove potential duplicated vnode that is currently being removed, to
|
// remove potential duplicated vnode that is currently being removed, to
|
||||||
// prevent from having twice the same node in the DOM during an animation
|
// 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;
|
const elm = <HTMLElement>vn.elm;
|
||||||
elm.setAttribute("data-owl-key", vn.key!);
|
elm.setAttribute("data-owl-key", vn.key!);
|
||||||
|
|
||||||
@@ -209,12 +209,12 @@ QWeb.addDirective({
|
|||||||
let name = value;
|
let name = value;
|
||||||
const hooks = {
|
const hooks = {
|
||||||
insert: `utils.transitionInsert(vn, '${name}');`,
|
insert: `utils.transitionInsert(vn, '${name}');`,
|
||||||
remove: `utils.transitionRemove(vn, '${name}', rm);`
|
remove: `utils.transitionRemove(vn, '${name}', rm);`,
|
||||||
};
|
};
|
||||||
for (let hookName in hooks) {
|
for (let hookName in hooks) {
|
||||||
addNodeHook(hookName, hooks[hookName]);
|
addNodeHook(hookName, hooks[hookName]);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -251,17 +251,20 @@ QWeb.addDirective({
|
|||||||
}
|
}
|
||||||
ctx.closeIf();
|
ctx.closeIf();
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// t-model
|
// t-model
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
QWeb.utils.toNumber = function(val: string): number | string {
|
QWeb.utils.toNumber = function (val: string): number | string {
|
||||||
const n = parseFloat(val);
|
const n = parseFloat(val);
|
||||||
return isNaN(n) ? val : n;
|
return isNaN(n) ? val : n;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasDotAtTheEnd = /\.[\w_]+\s*$/;
|
||||||
|
const hasBracketsAtTheEnd = /\[[^\[]+\]\s*$/;
|
||||||
|
|
||||||
QWeb.addDirective({
|
QWeb.addDirective({
|
||||||
name: "model",
|
name: "model",
|
||||||
priority: 42,
|
priority: 42,
|
||||||
@@ -270,15 +273,41 @@ QWeb.addDirective({
|
|||||||
let handler;
|
let handler;
|
||||||
let event = fullName.includes(".lazy") ? "change" : "input";
|
let event = fullName.includes(".lazy") ? "change" : "input";
|
||||||
|
|
||||||
// we keep here a reference to the "base expression" (if the expression
|
// First step: we need to understand the structure of the expression, and
|
||||||
// is `t-model="some.expr.value", then the base expression is "some.expr").
|
// from it, extract a base expression (that we can capture, which is
|
||||||
// This is necessary so we can capture it in the handler closure.
|
// important because it will be used in a handler later) and a formatted
|
||||||
let expr = ctx.formatExpression(value);
|
// expression (which uses the captured base expression)
|
||||||
const index = expr.lastIndexOf(".");
|
//
|
||||||
const baseExpr = expr.slice(0, index);
|
// Also, we support 2 kinds of values: some.expr.value or some.expr[value]
|
||||||
ctx.addLine(`let expr${nodeID} = ${baseExpr};`);
|
// For the first one, we have:
|
||||||
|
// - base expression = scope[some].expr
|
||||||
|
// - expression = exprX.value (where exprX is the var that captures the base expr)
|
||||||
|
// and for the expression with brackets:
|
||||||
|
// - base expression = scope[some].expr
|
||||||
|
// - expression = exprX[keyX] (where exprX is the var that captures the base expr
|
||||||
|
// and keyX captures scope[value])
|
||||||
|
let expr: string;
|
||||||
|
let baseExpr: string;
|
||||||
|
|
||||||
|
if (hasDotAtTheEnd.test(value)) {
|
||||||
|
// we manage the case where the expr has a dot: some.expr.value
|
||||||
|
const index = value.lastIndexOf(".");
|
||||||
|
baseExpr = value.slice(0, index);
|
||||||
|
ctx.addLine(`let expr${nodeID} = ${ctx.formatExpression(baseExpr)};`);
|
||||||
|
expr = `expr${nodeID}${value.slice(index)}`;
|
||||||
|
} else if (hasBracketsAtTheEnd.test(value)) {
|
||||||
|
// we manage here the case where the expr ends in a bracket expression:
|
||||||
|
// some.expr[value]
|
||||||
|
const index = value.lastIndexOf("[");
|
||||||
|
baseExpr = value.slice(0, index);
|
||||||
|
ctx.addLine(`let expr${nodeID} = ${ctx.formatExpression(baseExpr)};`);
|
||||||
|
let exprKey = value.trimRight().slice(index + 1, -1);
|
||||||
|
ctx.addLine(`let exprKey${nodeID} = ${ctx.formatExpression(exprKey)};`);
|
||||||
|
expr = `expr${nodeID}[exprKey${nodeID}]`;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Invalid t-model expression: "${value}" (it should be assignable)`);
|
||||||
|
}
|
||||||
|
|
||||||
expr = `expr${nodeID}.${expr.slice(index + 1)}`;
|
|
||||||
const key = ctx.generateTemplateKey();
|
const key = ctx.generateTemplateKey();
|
||||||
if (node.tagName === "select") {
|
if (node.tagName === "select") {
|
||||||
ctx.addLine(`p${nodeID}.props = {value: ${expr}};`);
|
ctx.addLine(`p${nodeID}.props = {value: ${expr}};`);
|
||||||
@@ -305,7 +334,7 @@ QWeb.addDirective({
|
|||||||
}
|
}
|
||||||
ctx.addLine(`extra.handlers[${key}] = extra.handlers[${key}] || (${handler});`);
|
ctx.addLine(`extra.handlers[${key}] = extra.handlers[${key}] || (${handler});`);
|
||||||
ctx.addLine(`p${nodeID}.on['${event}'] = extra.handlers[${key}];`);
|
ctx.addLine(`p${nodeID}.on['${event}'] = extra.handlers[${key}];`);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -329,5 +358,5 @@ QWeb.addDirective({
|
|||||||
if (ctx.loopNumber === 0) {
|
if (ctx.loopNumber === 0) {
|
||||||
ctx.rootContext.hasKey0 = ctx.keyStack.pop() as boolean;
|
ctx.rootContext.hasKey0 = ctx.keyStack.pop() as boolean;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+88
-56
@@ -66,8 +66,6 @@ interface QWebConfig {
|
|||||||
// Const/global stuff/helpers
|
// Const/global stuff/helpers
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
const DISABLED_TAGS = ["input", "textarea", "button", "select", "option", "optgroup"];
|
|
||||||
|
|
||||||
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
|
const TRANSLATABLE_ATTRS = ["label", "title", "placeholder", "alt"];
|
||||||
|
|
||||||
const lineBreakRE = /[\r\n]/;
|
const lineBreakRE = /[\r\n]/;
|
||||||
@@ -77,7 +75,7 @@ const NODE_HOOKS_PARAMS = {
|
|||||||
create: "(_, n)",
|
create: "(_, n)",
|
||||||
insert: "vn",
|
insert: "vn",
|
||||||
remove: "(vn, rm)",
|
remove: "(vn, rm)",
|
||||||
destroy: "()"
|
destroy: "()",
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Utils {
|
interface Utils {
|
||||||
@@ -86,10 +84,30 @@ interface Utils {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isComponent(obj) {
|
function isComponent(obj): boolean {
|
||||||
return obj && obj.hasOwnProperty("__owl__");
|
return obj && obj.hasOwnProperty("__owl__");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class VDomArray extends Array {
|
||||||
|
toString() {
|
||||||
|
return vDomToString(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function vDomToString(vdom: VNode[]): string {
|
||||||
|
return vdom
|
||||||
|
.map((vnode) => {
|
||||||
|
if (vnode.sel) {
|
||||||
|
const node = document.createElement(vnode.sel);
|
||||||
|
const result = patch(node, vnode);
|
||||||
|
return (<HTMLElement>result.elm).outerHTML;
|
||||||
|
} else {
|
||||||
|
return vnode.text;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
const UTILS: Utils = {
|
const UTILS: Utils = {
|
||||||
zero: Symbol("zero"),
|
zero: Symbol("zero"),
|
||||||
toObj(expr) {
|
toObj(expr) {
|
||||||
@@ -111,20 +129,8 @@ const UTILS: Utils = {
|
|||||||
addNameSpace(vnode) {
|
addNameSpace(vnode) {
|
||||||
addNS(vnode.data, vnode.children, vnode.sel);
|
addNS(vnode.data, vnode.children, vnode.sel);
|
||||||
},
|
},
|
||||||
VDomArray: class VDomArray extends Array {},
|
VDomArray,
|
||||||
vDomToString: function(vdom: VNode[]): string {
|
vDomToString,
|
||||||
return vdom
|
|
||||||
.map(vnode => {
|
|
||||||
if (vnode.sel) {
|
|
||||||
const node = document.createElement(vnode.sel);
|
|
||||||
const result = patch(node, vnode);
|
|
||||||
return (<HTMLElement>result.elm).outerHTML;
|
|
||||||
} else {
|
|
||||||
return vnode.text;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
},
|
|
||||||
getComponent(obj) {
|
getComponent(obj) {
|
||||||
while (obj && !isComponent(obj)) {
|
while (obj && !isComponent(obj)) {
|
||||||
obj = obj.__proto__;
|
obj = obj.__proto__;
|
||||||
@@ -133,7 +139,11 @@ const UTILS: Utils = {
|
|||||||
},
|
},
|
||||||
getScope(obj, property: string) {
|
getScope(obj, property: string) {
|
||||||
const obj0 = obj;
|
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__;
|
const newObj = obj.__proto__;
|
||||||
if (!newObj || isComponent(newObj)) {
|
if (!newObj || isComponent(newObj)) {
|
||||||
return obj0;
|
return obj0;
|
||||||
@@ -141,7 +151,7 @@ const UTILS: Utils = {
|
|||||||
obj = newObj;
|
obj = newObj;
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseXML(xml: string): Document {
|
function parseXML(xml: string): Document {
|
||||||
@@ -191,7 +201,7 @@ export class QWeb extends EventBus {
|
|||||||
name: 1,
|
name: 1,
|
||||||
att: 1,
|
att: 1,
|
||||||
attf: 1,
|
attf: 1,
|
||||||
translation: 1
|
translation: 1,
|
||||||
};
|
};
|
||||||
static DIRECTIVES: Directive[] = [];
|
static DIRECTIVES: Directive[] = [];
|
||||||
|
|
||||||
@@ -208,10 +218,12 @@ export class QWeb extends EventBus {
|
|||||||
static slots = {};
|
static slots = {};
|
||||||
static nextSlotId = 1;
|
static nextSlotId = 1;
|
||||||
|
|
||||||
// recursiveTemplates contains sub templates called with t-call, but which
|
// subTemplates are stored in two objects: a (local) mapping from a name to an
|
||||||
// ends up in recursive situations. This is very similar to the slot situation,
|
// id, and a (global) mapping from an id to the compiled function. This is
|
||||||
// as in we need to propagate the scope.
|
// necessary to ensure that global templates can be called with more than one
|
||||||
subTemplates = {};
|
// QWeb instance.
|
||||||
|
subTemplates: { [key: string]: number } = {};
|
||||||
|
static subTemplates: { [id: number]: Function } = {};
|
||||||
|
|
||||||
isUpdating: boolean = false;
|
isUpdating: boolean = false;
|
||||||
translateFn?: QWebConfig["translateFn"];
|
translateFn?: QWebConfig["translateFn"];
|
||||||
@@ -235,7 +247,7 @@ export class QWeb extends EventBus {
|
|||||||
QWeb.DIRECTIVE_NAMES[directive.name] = 1;
|
QWeb.DIRECTIVE_NAMES[directive.name] = 1;
|
||||||
QWeb.DIRECTIVES.sort((d1, d2) => d1.priority - d2.priority);
|
QWeb.DIRECTIVES.sort((d1, d2) => d1.priority - d2.priority);
|
||||||
if (directive.extraNames) {
|
if (directive.extraNames) {
|
||||||
directive.extraNames.forEach(n => (QWeb.DIRECTIVE_NAMES[n] = 1));
|
directive.extraNames.forEach((n) => (QWeb.DIRECTIVE_NAMES[n] = 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,11 +311,11 @@ export class QWeb extends EventBus {
|
|||||||
this._processTemplate(elem);
|
this._processTemplate(elem);
|
||||||
const template = {
|
const template = {
|
||||||
elem,
|
elem,
|
||||||
fn: function(this: QWeb, context, extra) {
|
fn: function (this: QWeb, context, extra) {
|
||||||
const compiledFunction = this._compile(name, elem);
|
const compiledFunction = this._compile(name, elem);
|
||||||
template.fn = compiledFunction;
|
template.fn = compiledFunction;
|
||||||
return compiledFunction.call(this, context, extra);
|
return compiledFunction.call(this, context, extra);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
this.templates[name] = template;
|
this.templates[name] = template;
|
||||||
}
|
}
|
||||||
@@ -313,10 +325,10 @@ export class QWeb extends EventBus {
|
|||||||
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
|
for (let i = 0, ilen = tbranch.length; i < ilen; i++) {
|
||||||
let node = tbranch[i];
|
let node = tbranch[i];
|
||||||
let prevElem = node.previousElementSibling!;
|
let prevElem = node.previousElementSibling!;
|
||||||
let pattr = function(name) {
|
let pattr = function (name) {
|
||||||
return prevElem.getAttribute(name);
|
return prevElem.getAttribute(name);
|
||||||
};
|
};
|
||||||
let nattr = function(name) {
|
let nattr = function (name) {
|
||||||
return +!!node.getAttribute(name);
|
return +!!node.getAttribute(name);
|
||||||
};
|
};
|
||||||
if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
|
if (prevElem && (pattr("t-if") || pattr("t-elif"))) {
|
||||||
@@ -326,7 +338,7 @@ export class QWeb extends EventBus {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (
|
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;
|
return a + b;
|
||||||
}) > 1
|
}) > 1
|
||||||
) {
|
) {
|
||||||
@@ -503,10 +515,17 @@ export class QWeb extends EventBus {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.tagName !== "t" && node.hasAttribute("t-call")) {
|
||||||
|
const tCallNode = document.createElement("t");
|
||||||
|
tCallNode.setAttribute("t-call", node.getAttribute("t-call")!);
|
||||||
|
node.removeAttribute("t-call");
|
||||||
|
node.prepend(tCallNode);
|
||||||
|
}
|
||||||
|
|
||||||
const firstLetter = node.tagName[0];
|
const firstLetter = node.tagName[0];
|
||||||
if (firstLetter === firstLetter.toUpperCase()) {
|
if (firstLetter === firstLetter.toUpperCase()) {
|
||||||
// this is a component, we modify in place the xml document to change
|
// this is a component, we modify in place the xml document to change
|
||||||
// <SomeComponent ... /> to <t t-component="SomeComponent" ... />
|
// <SomeComponent ... /> to <SomeComponent t-component="SomeComponent" ... />
|
||||||
node.setAttribute("t-component", node.tagName);
|
node.setAttribute("t-component", node.tagName);
|
||||||
} else if (node.tagName !== "t" && node.hasAttribute("t-component")) {
|
} else if (node.tagName !== "t" && node.hasAttribute("t-component")) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -578,7 +597,7 @@ export class QWeb extends EventBus {
|
|||||||
qweb: this,
|
qweb: this,
|
||||||
ctx,
|
ctx,
|
||||||
fullName,
|
fullName,
|
||||||
value
|
value,
|
||||||
});
|
});
|
||||||
if (isDone) {
|
if (isDone) {
|
||||||
for (let { directive, value, fullName } of finalizers) {
|
for (let { directive, value, fullName } of finalizers) {
|
||||||
@@ -593,7 +612,7 @@ export class QWeb extends EventBus {
|
|||||||
let nodeID = this._compileGenericNode(node, ctx, withHandlers);
|
let nodeID = this._compileGenericNode(node, ctx, withHandlers);
|
||||||
ctx = ctx.withParent(nodeID);
|
ctx = ctx.withParent(nodeID);
|
||||||
let nodeHooks = {};
|
let nodeHooks = {};
|
||||||
let addNodeHook = function(hook, handler) {
|
let addNodeHook = function (hook, handler) {
|
||||||
nodeHooks[hook] = nodeHooks[hook] || [];
|
nodeHooks[hook] = nodeHooks[hook] || [];
|
||||||
nodeHooks[hook].push(handler);
|
nodeHooks[hook].push(handler);
|
||||||
};
|
};
|
||||||
@@ -607,7 +626,7 @@ export class QWeb extends EventBus {
|
|||||||
fullName,
|
fullName,
|
||||||
value,
|
value,
|
||||||
nodeID,
|
nodeID,
|
||||||
addNodeHook
|
addNodeHook,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -660,22 +679,31 @@ export class QWeb extends EventBus {
|
|||||||
const props: string[] = [];
|
const props: string[] = [];
|
||||||
const tattrs: number[] = [];
|
const tattrs: number[] = [];
|
||||||
|
|
||||||
function handleBooleanProps(key, val) {
|
function handleProperties(key, val) {
|
||||||
let isProp = false;
|
let isProp = false;
|
||||||
if (node.nodeName === "input" && key === "checked") {
|
switch (node.nodeName) {
|
||||||
let type = (<Element>node).getAttribute("type");
|
case "input":
|
||||||
if (type === "checkbox" || type === "radio") {
|
let type = (<Element>node).getAttribute("type");
|
||||||
isProp = true;
|
if (type === "checkbox" || type === "radio") {
|
||||||
}
|
if (key === "checked" || key === "indeterminate") {
|
||||||
}
|
isProp = true;
|
||||||
if (node.nodeName === "option" && key === "selected") {
|
}
|
||||||
isProp = true;
|
}
|
||||||
}
|
if (key === "value" || key === "readonly" || key === "disabled") {
|
||||||
if (key === "disabled" && DISABLED_TAGS.indexOf(node.nodeName) > -1) {
|
isProp = true;
|
||||||
isProp = true;
|
}
|
||||||
}
|
break;
|
||||||
if ((key === "readonly" && node.nodeName === "input") || node.nodeName === "textarea") {
|
case "option":
|
||||||
isProp = true;
|
isProp = key === "selected" || key === "disabled";
|
||||||
|
break;
|
||||||
|
case "textarea":
|
||||||
|
isProp = key === "readonly" || key === "disabled";
|
||||||
|
break;
|
||||||
|
case "button":
|
||||||
|
case "select":
|
||||||
|
case "optgroup":
|
||||||
|
isProp = key === "disabled";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (isProp) {
|
if (isProp) {
|
||||||
props.push(`${key}: _${val}`);
|
props.push(`${key}: _${val}`);
|
||||||
@@ -698,10 +726,14 @@ export class QWeb extends EventBus {
|
|||||||
if ((value = value.trim())) {
|
if ((value = value.trim())) {
|
||||||
let classDef = value
|
let classDef = value
|
||||||
.split(/\s+/)
|
.split(/\s+/)
|
||||||
.map(a => `'${escapeQuotes(a)}':true`)
|
.map((a) => `'${escapeQuotes(a)}':true`)
|
||||||
.join(",");
|
.join(",");
|
||||||
classObj = `_${ctx.generateID()}`;
|
if (classObj) {
|
||||||
ctx.addLine(`let ${classObj} = {${classDef}};`);
|
ctx.addLine(`Object.assign(${classObj}, {${classDef}})`);
|
||||||
|
} else {
|
||||||
|
classObj = `_${ctx.generateID()}`;
|
||||||
|
ctx.addLine(`let ${classObj} = {${classDef}};`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.addLine(`let _${attID} = '${escapeQuotes(value)}';`);
|
ctx.addLine(`let _${attID} = '${escapeQuotes(value)}';`);
|
||||||
@@ -710,7 +742,7 @@ export class QWeb extends EventBus {
|
|||||||
name = '"' + name + '"';
|
name = '"' + name + '"';
|
||||||
}
|
}
|
||||||
attrs.push(`${name}: _${attID}`);
|
attrs.push(`${name}: _${attID}`);
|
||||||
handleBooleanProps(name, attID);
|
handleProperties(name, attID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -742,12 +774,12 @@ export class QWeb extends EventBus {
|
|||||||
const attValueID = ctx.generateID();
|
const attValueID = ctx.generateID();
|
||||||
ctx.addLine(`let _${attValueID} = ${formattedValue};`);
|
ctx.addLine(`let _${attValueID} = ${formattedValue};`);
|
||||||
formattedValue = `'${attValue}' + (_${attValueID} ? ' ' + _${attValueID} : '')`;
|
formattedValue = `'${attValue}' + (_${attValueID} ? ' ' + _${attValueID} : '')`;
|
||||||
const attrIndex = attrs.findIndex(att => att.startsWith(attName + ":"));
|
const attrIndex = attrs.findIndex((att) => att.startsWith(attName + ":"));
|
||||||
attrs.splice(attrIndex, 1);
|
attrs.splice(attrIndex, 1);
|
||||||
}
|
}
|
||||||
ctx.addLine(`let _${attID} = ${formattedValue};`);
|
ctx.addLine(`let _${attID} = ${formattedValue};`);
|
||||||
attrs.push(`${attName}: _${attID}`);
|
attrs.push(`${attName}: _${attID}`);
|
||||||
handleBooleanProps(attName, attID);
|
handleProperties(attName, attID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Component } from "../component/component";
|
import { Component } from "../component/component";
|
||||||
import { xml } from "../tags";
|
import { xml } from "../tags";
|
||||||
|
import { EnvWithRouter } from "./router";
|
||||||
|
|
||||||
export class RouteComponent extends Component {
|
export class RouteComponent extends Component<{}, EnvWithRouter> {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<t>
|
<t>
|
||||||
<t
|
<t
|
||||||
|
|||||||
+12
-4
@@ -49,6 +49,10 @@ interface Options {
|
|||||||
mode: Router["mode"];
|
mode: Router["mode"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EnvWithRouter extends Env {
|
||||||
|
router: Router;
|
||||||
|
}
|
||||||
|
|
||||||
const paramRegexp = /\{\{(.*?)\}\}/;
|
const paramRegexp = /\{\{(.*?)\}\}/;
|
||||||
|
|
||||||
export class Router {
|
export class Router {
|
||||||
@@ -60,7 +64,11 @@ export class Router {
|
|||||||
routeIds: string[];
|
routeIds: string[];
|
||||||
env: RouterEnv;
|
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;
|
env.router = this;
|
||||||
this.mode = options.mode;
|
this.mode = options.mode;
|
||||||
this.env = env as RouterEnv;
|
this.env = env as RouterEnv;
|
||||||
@@ -89,7 +97,7 @@ export class Router {
|
|||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
async start() {
|
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);
|
window.addEventListener("popstate", (this as any)._listener);
|
||||||
if (this.mode === "hash") {
|
if (this.mode === "hash") {
|
||||||
window.addEventListener("hashchange", (this as any)._listener);
|
window.addEventListener("hashchange", (this as any)._listener);
|
||||||
@@ -190,7 +198,7 @@ export class Router {
|
|||||||
return {
|
return {
|
||||||
type: "match",
|
type: "match",
|
||||||
route: route,
|
route: route,
|
||||||
params: params
|
params: params,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,7 +223,7 @@ export class Router {
|
|||||||
const result = await route.beforeRouteEnter({
|
const result = await route.beforeRouteEnter({
|
||||||
env: this.env,
|
env: this.env,
|
||||||
from: this.currentRoute,
|
from: this.currentRoute,
|
||||||
to: route
|
to: route,
|
||||||
});
|
});
|
||||||
if (result === false) {
|
if (result === false) {
|
||||||
return { type: "cancelled" };
|
return { type: "cancelled" };
|
||||||
|
|||||||
+14
-10
@@ -24,6 +24,10 @@ import { onWillUpdateProps } from "./hooks";
|
|||||||
// Store Definition
|
// Store Definition
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface EnvWithStore extends Env {
|
||||||
|
store: Store;
|
||||||
|
}
|
||||||
|
|
||||||
export type Action = ({ state, dispatch, env, getters }, ...payload: any) => any;
|
export type Action = ({ state, dispatch, env, getters }, ...payload: any) => any;
|
||||||
export type Getter = ({ state: any, getters }, payload?) => any;
|
export type Getter = ({ state: any, getters }, payload?) => any;
|
||||||
|
|
||||||
@@ -49,7 +53,7 @@ export class Store extends Context {
|
|||||||
if (config.getters) {
|
if (config.getters) {
|
||||||
const firstArg = {
|
const firstArg = {
|
||||||
state: this.state,
|
state: this.state,
|
||||||
getters: this.getters
|
getters: this.getters,
|
||||||
};
|
};
|
||||||
for (let g in config.getters) {
|
for (let g in config.getters) {
|
||||||
this.getters[g] = config.getters[g].bind(this, firstArg);
|
this.getters[g] = config.getters[g].bind(this, firstArg);
|
||||||
@@ -66,7 +70,7 @@ export class Store extends Context {
|
|||||||
dispatch: this.dispatch.bind(this),
|
dispatch: this.dispatch.bind(this),
|
||||||
env: this.env,
|
env: this.env,
|
||||||
state: this.state,
|
state: this.state,
|
||||||
getters: this.getters
|
getters: this.getters,
|
||||||
},
|
},
|
||||||
...payload
|
...payload
|
||||||
);
|
);
|
||||||
@@ -83,7 +87,7 @@ interface SelectorOptions {
|
|||||||
const isStrictEqual = (a, b) => a === b;
|
const isStrictEqual = (a, b) => a === b;
|
||||||
|
|
||||||
export function useStore(selector, options: SelectorOptions = {}): any {
|
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 componentId = component.__owl__.id;
|
||||||
const store = options.store || (component.env.store as Store);
|
const store = options.store || (component.env.store as Store);
|
||||||
if (!(store instanceof Store)) {
|
if (!(store instanceof Store)) {
|
||||||
@@ -109,11 +113,11 @@ export function useStore(selector, options: SelectorOptions = {}): any {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
store.updateFunctions[componentId].push(function(): boolean {
|
store.updateFunctions[componentId].push(function (): boolean {
|
||||||
return selectCompareUpdate(store!.state, component.props);
|
return selectCompareUpdate(store!.state, component.props);
|
||||||
});
|
});
|
||||||
|
|
||||||
useContextWithCB(store, component, function(): Promise<void> | void {
|
useContextWithCB(store, component, function (): Promise<void> | void {
|
||||||
let shouldRender = false;
|
let shouldRender = false;
|
||||||
for (let fn of store.updateFunctions[componentId]) {
|
for (let fn of store.updateFunctions[componentId]) {
|
||||||
shouldRender = fn() || shouldRender;
|
shouldRender = fn() || shouldRender;
|
||||||
@@ -122,12 +126,12 @@ export function useStore(selector, options: SelectorOptions = {}): any {
|
|||||||
return component.render();
|
return component.render();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
onWillUpdateProps(props => {
|
onWillUpdateProps((props) => {
|
||||||
selectCompareUpdate(store.state, props);
|
selectCompareUpdate(store.state, props);
|
||||||
});
|
});
|
||||||
|
|
||||||
const __destroy = component.__destroy;
|
const __destroy = component.__destroy;
|
||||||
component.__destroy = parent => {
|
component.__destroy = (parent) => {
|
||||||
delete store.updateFunctions[componentId];
|
delete store.updateFunctions[componentId];
|
||||||
__destroy.call(component, parent);
|
__destroy.call(component, parent);
|
||||||
};
|
};
|
||||||
@@ -144,16 +148,16 @@ export function useStore(selector, options: SelectorOptions = {}): any {
|
|||||||
},
|
},
|
||||||
has(target, k) {
|
has(target, k) {
|
||||||
return k in result;
|
return k in result;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDispatch(store?: Store): Store["dispatch"] {
|
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);
|
return store.dispatch.bind(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetters(store?: Store): Store["getters"] {
|
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;
|
return store.getters;
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-9
@@ -10,14 +10,16 @@
|
|||||||
* - debounce
|
* - debounce
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { browser } from "./browser";
|
||||||
|
|
||||||
export function whenReady(fn?: any) {
|
export function whenReady(fn?: any) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function (resolve) {
|
||||||
if (document.readyState !== "loading") {
|
if (document.readyState !== "loading") {
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener("DOMContentLoaded", resolve, false);
|
document.addEventListener("DOMContentLoaded", resolve, false);
|
||||||
}
|
}
|
||||||
}).then(fn || function() {});
|
}).then(fn || function () {});
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadedScripts: { [key: string]: Promise<void> } = {};
|
const loadedScripts: { [key: string]: Promise<void> } = {};
|
||||||
@@ -26,14 +28,14 @@ export function loadJS(url: string): Promise<void> {
|
|||||||
if (url in loadedScripts) {
|
if (url in loadedScripts) {
|
||||||
return loadedScripts[url];
|
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");
|
const script = document.createElement("script");
|
||||||
script.type = "text/javascript";
|
script.type = "text/javascript";
|
||||||
script.src = url;
|
script.src = url;
|
||||||
script.onload = function() {
|
script.onload = function () {
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
script.onerror = function() {
|
script.onerror = function () {
|
||||||
reject(`Error loading file '${url}'`);
|
reject(`Error loading file '${url}'`);
|
||||||
};
|
};
|
||||||
const head = document.head || document.getElementsByTagName("head")[0];
|
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> {
|
export async function loadFile(url: string): Promise<string> {
|
||||||
const result = await fetch(url);
|
const result = await browser.fetch(url);
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
throw new Error("Error while fetching xml templates");
|
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 {
|
export function debounce(func: Function, wait: number, immediate?: boolean): Function {
|
||||||
let timeout;
|
let timeout;
|
||||||
return function(this: any) {
|
return function (this: any) {
|
||||||
const context = this;
|
const context = this;
|
||||||
const args = arguments;
|
const args = arguments;
|
||||||
function later() {
|
function later() {
|
||||||
@@ -83,8 +85,8 @@ export function debounce(func: Function, wait: number, immediate?: boolean): Fun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const callNow = immediate && !timeout;
|
const callNow = immediate && !timeout;
|
||||||
clearTimeout(timeout);
|
browser.clearTimeout(timeout);
|
||||||
timeout = setTimeout(later, wait);
|
timeout = browser.setTimeout(later, wait);
|
||||||
if (callNow) {
|
if (callNow) {
|
||||||
func.apply(context, args);
|
func.apply(context, args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { VNode, h } from "./vdom";
|
import { VNode, h, addNS } from "./vdom";
|
||||||
|
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
|
|
||||||
@@ -23,5 +23,9 @@ function htmlToVNode(node: ChildNode): VNode {
|
|||||||
for (let c of node.childNodes) {
|
for (let c of node.childNodes) {
|
||||||
children.push(htmlToVNode(c));
|
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
@@ -33,7 +33,7 @@ function updateProps(oldVnode: VNode, vnode: VNode): void {
|
|||||||
|
|
||||||
export const propsModule = {
|
export const propsModule = {
|
||||||
create: updateProps,
|
create: updateProps,
|
||||||
update: updateProps
|
update: updateProps,
|
||||||
} as Module;
|
} as Module;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -151,7 +151,7 @@ function updateEventListeners(oldVnode: VNode, vnode?: VNode): void {
|
|||||||
export const eventListenersModule = {
|
export const eventListenersModule = {
|
||||||
create: updateEventListeners,
|
create: updateEventListeners,
|
||||||
update: updateEventListeners,
|
update: updateEventListeners,
|
||||||
destroy: updateEventListeners
|
destroy: updateEventListeners,
|
||||||
} as Module;
|
} as Module;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -210,7 +210,7 @@ function updateAttrs(oldVnode: VNode, vnode: VNode): void {
|
|||||||
|
|
||||||
export const attrsModule = {
|
export const attrsModule = {
|
||||||
create: updateAttrs,
|
create: updateAttrs,
|
||||||
update: updateAttrs
|
update: updateAttrs,
|
||||||
} as Module;
|
} as Module;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
+1
-1
@@ -520,7 +520,7 @@ const htmlDomApi = {
|
|||||||
parentNode,
|
parentNode,
|
||||||
nextSibling,
|
nextSibling,
|
||||||
tagName,
|
tagName,
|
||||||
setTextContent
|
setTextContent,
|
||||||
} as DOMAPI;
|
} as DOMAPI;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
+13
-13
@@ -9,7 +9,7 @@ import {
|
|||||||
patchNextFrame,
|
patchNextFrame,
|
||||||
renderToDOM,
|
renderToDOM,
|
||||||
unpatchNextFrame,
|
unpatchNextFrame,
|
||||||
nextTick
|
nextTick,
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -71,7 +71,7 @@ describe("animations", () => {
|
|||||||
qweb.addTemplate("test", `<span t-transition="chimay">blue</span>`);
|
qweb.addTemplate("test", `<span t-transition="chimay">blue</span>`);
|
||||||
|
|
||||||
let def = makeDeferred();
|
let def = makeDeferred();
|
||||||
patchNextFrame(cb => {
|
patchNextFrame((cb) => {
|
||||||
expect(node.className).toBe("chimay-enter chimay-enter-active");
|
expect(node.className).toBe("chimay-enter chimay-enter-active");
|
||||||
cb();
|
cb();
|
||||||
expect(node.className).toBe("chimay-enter-active chimay-enter-to");
|
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>`);
|
qweb.addTemplate("test", `<span t-transition="chimay">blue</span>`);
|
||||||
|
|
||||||
let def = makeDeferred();
|
let def = makeDeferred();
|
||||||
patchNextFrame(cb => {
|
patchNextFrame((cb) => {
|
||||||
expect(node.className).toBe("chimay-enter chimay-enter-active");
|
expect(node.className).toBe("chimay-enter chimay-enter-active");
|
||||||
cb();
|
cb();
|
||||||
expect(node.className).toBe("chimay-enter-active chimay-enter-to");
|
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>`);
|
qweb.addTemplate("test", `<span t-transition="jupiler">blue</span>`);
|
||||||
|
|
||||||
let def = makeDeferred();
|
let def = makeDeferred();
|
||||||
patchNextFrame(cb => {
|
patchNextFrame((cb) => {
|
||||||
expect(node.className).toBe("jupiler-enter jupiler-enter-active");
|
expect(node.className).toBe("jupiler-enter jupiler-enter-active");
|
||||||
cb();
|
cb();
|
||||||
expect(node.className).toBe("");
|
expect(node.className).toBe("");
|
||||||
@@ -139,7 +139,7 @@ describe("animations", () => {
|
|||||||
// insert widget into the DOM
|
// insert widget into the DOM
|
||||||
let def = makeDeferred();
|
let def = makeDeferred();
|
||||||
var spanNode;
|
var spanNode;
|
||||||
patchNextFrame(cb => {
|
patchNextFrame((cb) => {
|
||||||
expect(spanNode.className).toBe("chimay-enter chimay-enter-active");
|
expect(spanNode.className).toBe("chimay-enter chimay-enter-active");
|
||||||
cb();
|
cb();
|
||||||
expect(spanNode.className).toBe("chimay-enter-active chimay-enter-to");
|
expect(spanNode.className).toBe("chimay-enter-active chimay-enter-to");
|
||||||
@@ -155,7 +155,7 @@ describe("animations", () => {
|
|||||||
// remove span from the DOM
|
// remove span from the DOM
|
||||||
def = makeDeferred();
|
def = makeDeferred();
|
||||||
widget.state.hide = true;
|
widget.state.hide = true;
|
||||||
patchNextFrame(cb => {
|
patchNextFrame((cb) => {
|
||||||
expect(spanNode.className).toBe("chimay-leave chimay-leave-active");
|
expect(spanNode.className).toBe("chimay-leave chimay-leave-active");
|
||||||
cb();
|
cb();
|
||||||
expect(spanNode.className).toBe("chimay-leave-active chimay-leave-to");
|
expect(spanNode.className).toBe("chimay-leave-active chimay-leave-to");
|
||||||
@@ -182,7 +182,7 @@ describe("animations", () => {
|
|||||||
// insert widget into the DOM
|
// insert widget into the DOM
|
||||||
let def = makeDeferred();
|
let def = makeDeferred();
|
||||||
var spanNode;
|
var spanNode;
|
||||||
patchNextFrame(cb => {
|
patchNextFrame((cb) => {
|
||||||
expect(spanNode.className).toBe("chimay-enter chimay-enter-active");
|
expect(spanNode.className).toBe("chimay-enter chimay-enter-active");
|
||||||
cb();
|
cb();
|
||||||
expect(spanNode.className).toBe("chimay-enter-active chimay-enter-to");
|
expect(spanNode.className).toBe("chimay-enter-active chimay-enter-to");
|
||||||
@@ -210,7 +210,7 @@ describe("animations", () => {
|
|||||||
|
|
||||||
let def = makeDeferred();
|
let def = makeDeferred();
|
||||||
var spanNode;
|
var spanNode;
|
||||||
patchNextFrame(cb => {
|
patchNextFrame((cb) => {
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
'<div><span class="chimay-enter chimay-enter-active">blue</span></div>'
|
'<div><span class="chimay-enter chimay-enter-active">blue</span></div>'
|
||||||
);
|
);
|
||||||
@@ -250,7 +250,7 @@ describe("animations", () => {
|
|||||||
|
|
||||||
let def = makeDeferred();
|
let def = makeDeferred();
|
||||||
var spanNode;
|
var spanNode;
|
||||||
patchNextFrame(cb => {
|
patchNextFrame((cb) => {
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
'<div><span class="chimay-enter chimay-enter-active">blue</span></div>'
|
'<div><span class="chimay-enter chimay-enter-active">blue</span></div>'
|
||||||
);
|
);
|
||||||
@@ -275,7 +275,7 @@ describe("animations", () => {
|
|||||||
// remove span from the DOM
|
// remove span from the DOM
|
||||||
def = makeDeferred();
|
def = makeDeferred();
|
||||||
widget.state.display = false;
|
widget.state.display = false;
|
||||||
patchNextFrame(cb => {
|
patchNextFrame((cb) => {
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
'<div><span class="chimay-leave chimay-leave-active" data-owl-key="__3__">blue</span></div>'
|
'<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 def = makeDeferred();
|
||||||
let phase = "enter";
|
let phase = "enter";
|
||||||
patchNextFrame(cb => {
|
patchNextFrame((cb) => {
|
||||||
let spans = fixture.querySelectorAll("span");
|
let spans = fixture.querySelectorAll("span");
|
||||||
expect(spans.length).toBe(1);
|
expect(spans.length).toBe(1);
|
||||||
expect(spans[0].className).toBe(`chimay-${phase} chimay-${phase}-active`);
|
expect(spans[0].className).toBe(`chimay-${phase} chimay-${phase}-active`);
|
||||||
@@ -366,7 +366,7 @@ describe("animations", () => {
|
|||||||
|
|
||||||
let def = makeDeferred();
|
let def = makeDeferred();
|
||||||
let phase = "enter";
|
let phase = "enter";
|
||||||
patchNextFrame(cb => {
|
patchNextFrame((cb) => {
|
||||||
let spans = fixture.querySelectorAll("span");
|
let spans = fixture.querySelectorAll("span");
|
||||||
expect(spans.length).toBe(1);
|
expect(spans.length).toBe(1);
|
||||||
expect(spans[0].className).toBe(`chimay-${phase} chimay-${phase}-active`);
|
expect(spans[0].className).toBe(`chimay-${phase} chimay-${phase}-active`);
|
||||||
@@ -413,7 +413,7 @@ describe("animations", () => {
|
|||||||
state = useState({ flag: false });
|
state = useState({ flag: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
patchNextFrame(cb => cb());
|
patchNextFrame((cb) => cb());
|
||||||
|
|
||||||
const widget = new Parent();
|
const widget = new Parent();
|
||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
|
|||||||
@@ -53,8 +53,12 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
|
|||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
if (typeof _2[Symbol.iterator] === 'function') {
|
||||||
_4 = Object.values(_2);
|
_3 = [..._2];
|
||||||
|
} else {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _length3 = _3.length;
|
let _length3 = _3.length;
|
||||||
let _origScope5 = scope;
|
let _origScope5 = scope;
|
||||||
@@ -70,8 +74,12 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
|
|||||||
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_6) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _7 = _8 = _6;
|
let _7 = _8 = _6;
|
||||||
if (!(_6 instanceof Array)) {
|
if (!(_6 instanceof Array)) {
|
||||||
_7 = Object.keys(_6);
|
if (typeof _6[Symbol.iterator] === 'function') {
|
||||||
_8 = Object.values(_6);
|
_7 = [..._6];
|
||||||
|
} else {
|
||||||
|
_7 = Object.keys(_6);
|
||||||
|
_8 = Object.values(_6);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _length7 = _7.length;
|
let _length7 = _7.length;
|
||||||
let _origScope9 = scope;
|
let _origScope9 = scope;
|
||||||
@@ -272,8 +280,12 @@ exports[`composition sub components with some state rendered in a loop 1`] = `
|
|||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
if (typeof _2[Symbol.iterator] === 'function') {
|
||||||
_4 = Object.values(_2);
|
_3 = [..._2];
|
||||||
|
} else {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _length3 = _3.length;
|
let _length3 = _3.length;
|
||||||
let _origScope5 = scope;
|
let _origScope5 = scope;
|
||||||
@@ -558,8 +570,12 @@ exports[`other directives with t-component t-on expression captured in t-foreach
|
|||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
if (typeof _2[Symbol.iterator] === 'function') {
|
||||||
_4 = Object.values(_2);
|
_3 = [..._2];
|
||||||
|
} else {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _length3 = _3.length;
|
let _length3 = _3.length;
|
||||||
let _origScope5 = scope;
|
let _origScope5 = scope;
|
||||||
@@ -600,8 +616,12 @@ exports[`other directives with t-component t-on expression in t-foreach 1`] = `
|
|||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
if (typeof _2[Symbol.iterator] === 'function') {
|
||||||
_4 = Object.values(_2);
|
_3 = [..._2];
|
||||||
|
} else {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _length3 = _3.length;
|
let _length3 = _3.length;
|
||||||
let _origScope5 = scope;
|
let _origScope5 = scope;
|
||||||
@@ -652,8 +672,12 @@ exports[`other directives with t-component t-on expression in t-foreach with t-s
|
|||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
if (typeof _2[Symbol.iterator] === 'function') {
|
||||||
_4 = Object.values(_2);
|
_3 = [..._2];
|
||||||
|
} else {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _length3 = _3.length;
|
let _length3 = _3.length;
|
||||||
let _origScope5 = scope;
|
let _origScope5 = scope;
|
||||||
@@ -705,8 +729,12 @@ exports[`other directives with t-component t-on method call in t-foreach 1`] = `
|
|||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
if (typeof _2[Symbol.iterator] === 'function') {
|
||||||
_4 = Object.values(_2);
|
_3 = [..._2];
|
||||||
|
} else {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _length3 = _3.length;
|
let _length3 = _3.length;
|
||||||
let _origScope5 = scope;
|
let _origScope5 = scope;
|
||||||
@@ -1253,12 +1281,16 @@ exports[`other directives with t-component t-set can't alter from within callee
|
|||||||
if (scope.iter != null) {
|
if (scope.iter != null) {
|
||||||
c2.push({text: scope.iter});
|
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__'}));
|
let _origScope4 = scope;
|
||||||
let c5 = [], p5 = {key:5};
|
scope = Object.create(scope);
|
||||||
let vn5 = h('p', p5, c5);
|
scope.__access_mode__ = 'ro';
|
||||||
c1.push(vn5);
|
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};
|
||||||
|
let vn6 = h('p', p6, c6);
|
||||||
|
c1.push(vn6);
|
||||||
if (scope.iter != null) {
|
if (scope.iter != null) {
|
||||||
c5.push({text: scope.iter});
|
c6.push({text: scope.iter});
|
||||||
}
|
}
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -1280,17 +1312,18 @@ exports[`other directives with t-component t-set can't alter in t-call body 1`]
|
|||||||
if (scope.iter != null) {
|
if (scope.iter != null) {
|
||||||
c2.push({text: scope.iter});
|
c2.push({text: scope.iter});
|
||||||
}
|
}
|
||||||
|
let _origScope4 = scope;
|
||||||
|
scope = Object.create(scope);
|
||||||
|
scope.__access_mode__ = 'ro';
|
||||||
{
|
{
|
||||||
let _origScope4 = scope;
|
|
||||||
scope = Object.assign(Object.create(context), scope);
|
|
||||||
{
|
{
|
||||||
let c__0 = [];
|
let c__0 = [];
|
||||||
utils.getScope(scope, 'iter').iter = 'inCall';
|
utils.getScope(scope, 'iter').iter = 'inCall';
|
||||||
scope[utils.zero] = c__0;
|
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;
|
|
||||||
}
|
}
|
||||||
|
scope = _origScope4;
|
||||||
let c6 = [], p6 = {key:6};
|
let c6 = [], p6 = {key:6};
|
||||||
let vn6 = h('p', p6, c6);
|
let vn6 = h('p', p6, c6);
|
||||||
c1.push(vn6);
|
c1.push(vn6);
|
||||||
@@ -1366,8 +1399,12 @@ exports[`other directives with t-component t-set outside modified in t-foreach 1
|
|||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
if (typeof _2[Symbol.iterator] === 'function') {
|
||||||
_4 = Object.values(_2);
|
_3 = [..._2];
|
||||||
|
} else {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _length3 = _3.length;
|
let _length3 = _3.length;
|
||||||
let _origScope5 = scope;
|
let _origScope5 = scope;
|
||||||
@@ -1400,6 +1437,47 @@ exports[`other directives with t-component t-set outside modified in t-foreach 1
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`props evaluation t-set with a body expression can be used as textual prop 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);
|
||||||
|
let c2 = new utils.VDomArray();
|
||||||
|
c2.push({text: \`42\`});
|
||||||
|
scope.abc = c2
|
||||||
|
// Component 'Child'
|
||||||
|
let w3 = '__4__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__4__']] : false;
|
||||||
|
let props3 = {val:scope.abc};
|
||||||
|
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
|
||||||
|
w3.destroy();
|
||||||
|
w3 = false;
|
||||||
|
}
|
||||||
|
if (w3) {
|
||||||
|
w3.__updateProps(props3, extra.fiber, undefined);
|
||||||
|
let pvnode = w3.__owl__.pvnode;
|
||||||
|
c1.push(pvnode);
|
||||||
|
} else {
|
||||||
|
let componentKey3 = \`Child\`;
|
||||||
|
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['Child'];
|
||||||
|
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
|
||||||
|
w3 = new W3(parent, props3);
|
||||||
|
parent.__owl__.cmap['__4__'] = w3.__owl__.id;
|
||||||
|
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||||
|
let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||||
|
c1.push(pvnode);
|
||||||
|
w3.__owl__.pvnode = pvnode;
|
||||||
|
}
|
||||||
|
w3.__owl__.parentLastFiberId = extra.fiber.id;
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`random stuff/miscellaneous can inject values in tagged templates 1`] = `
|
exports[`random stuff/miscellaneous can inject values in tagged templates 1`] = `
|
||||||
"function anonymous(context, extra
|
"function anonymous(context, extra
|
||||||
) {
|
) {
|
||||||
@@ -1409,7 +1487,11 @@ exports[`random stuff/miscellaneous can inject values in tagged templates 1`] =
|
|||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
let vn1 = h('div', p1, c1);
|
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__'}));
|
let _origScope4 = scope;
|
||||||
|
scope = Object.create(scope);
|
||||||
|
scope.__access_mode__ = 'ro';
|
||||||
|
this.constructor.subTemplates['3'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__5__'}));
|
||||||
|
scope = _origScope4;
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -1471,8 +1553,12 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument
|
|||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
if (typeof _2[Symbol.iterator] === 'function') {
|
||||||
_4 = Object.values(_2);
|
_3 = [..._2];
|
||||||
|
} else {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _length3 = _3.length;
|
let _length3 = _3.length;
|
||||||
let _origScope5 = scope;
|
let _origScope5 = scope;
|
||||||
@@ -1682,6 +1768,33 @@ exports[`t-model directive basic use, on an input 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-model directive basic use, on an input with bracket expression 1`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"__template__1\\"
|
||||||
|
let scope = Object.create(context);
|
||||||
|
let h = this.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
let vn1 = h('div', p1, c1);
|
||||||
|
let c2 = [], p2 = {key:2,on:{}};
|
||||||
|
let vn2 = h('input', p2, c2);
|
||||||
|
c1.push(vn2);
|
||||||
|
let expr2 = scope['state'];
|
||||||
|
let exprKey2 = 'text';
|
||||||
|
p2.props = {value: expr2[exprKey2]};
|
||||||
|
extra.handlers['__3__'] = extra.handlers['__3__'] || ((ev) => {expr2[exprKey2] = ev.target.value});
|
||||||
|
p2.on['input'] = extra.handlers['__3__'];
|
||||||
|
let c4 = [], p4 = {key:4};
|
||||||
|
let vn4 = h('span', p4, c4);
|
||||||
|
c1.push(vn4);
|
||||||
|
let _5 = scope['state'].text;
|
||||||
|
if (_5 != null) {
|
||||||
|
c4.push({text: _5});
|
||||||
|
}
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-model directive basic use, on another key in component 1`] = `
|
exports[`t-model directive basic use, on another key in component 1`] = `
|
||||||
"function anonymous(context, extra
|
"function anonymous(context, extra
|
||||||
) {
|
) {
|
||||||
@@ -1720,8 +1833,12 @@ exports[`t-model directive in a t-foreach 1`] = `
|
|||||||
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _3 = _4 = _2;
|
let _3 = _4 = _2;
|
||||||
if (!(_2 instanceof Array)) {
|
if (!(_2 instanceof Array)) {
|
||||||
_3 = Object.keys(_2);
|
if (typeof _2[Symbol.iterator] === 'function') {
|
||||||
_4 = Object.values(_2);
|
_3 = [..._2];
|
||||||
|
} else {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _length3 = _3.length;
|
let _length3 = _3.length;
|
||||||
let _origScope5 = scope;
|
let _origScope5 = scope;
|
||||||
@@ -1748,6 +1865,50 @@ exports[`t-model directive in a t-foreach 1`] = `
|
|||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`t-model directive in a t-foreach, part 2 1`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"__template__1\\"
|
||||||
|
let scope = Object.create(context);
|
||||||
|
let h = this.h;
|
||||||
|
let c1 = [], p1 = {key:1};
|
||||||
|
let vn1 = h('div', p1, c1);
|
||||||
|
let _2 = scope['state'];
|
||||||
|
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
|
let _3 = _4 = _2;
|
||||||
|
if (!(_2 instanceof Array)) {
|
||||||
|
if (typeof _2[Symbol.iterator] === 'function') {
|
||||||
|
_3 = [..._2];
|
||||||
|
} else {
|
||||||
|
_3 = Object.keys(_2);
|
||||||
|
_4 = Object.values(_2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _length3 = _3.length;
|
||||||
|
let _origScope5 = scope;
|
||||||
|
scope = Object.create(scope);
|
||||||
|
for (let i1 = 0; i1 < _length3; i1++) {
|
||||||
|
scope.thing_first = i1 === 0
|
||||||
|
scope.thing_last = i1 === _length3 - 1
|
||||||
|
scope.thing_index = i1
|
||||||
|
scope.thing = _3[i1]
|
||||||
|
scope.thing_value = _4[i1]
|
||||||
|
let key1 = scope['thing_index'];
|
||||||
|
let c6 = [], p6 = {key:\`\${key1}_6\`,on:{}};
|
||||||
|
let vn6 = h('input', p6, c6);
|
||||||
|
c1.push(vn6);
|
||||||
|
let expr6 = scope['state'];
|
||||||
|
let exprKey6 = scope['thing_index'];
|
||||||
|
let k7 = \`__7__\${key1}__\`;
|
||||||
|
p6.props = {value: expr6[exprKey6]};
|
||||||
|
extra.handlers[k7] = extra.handlers[k7] || ((ev) => {expr6[exprKey6] = ev.target.value});
|
||||||
|
p6.on['input'] = extra.handlers[k7];
|
||||||
|
}
|
||||||
|
scope = _origScope5;
|
||||||
|
return vn1;
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`t-model directive on a select 1`] = `
|
exports[`t-model directive on a select 1`] = `
|
||||||
"function anonymous(context, extra
|
"function anonymous(context, extra
|
||||||
) {
|
) {
|
||||||
@@ -1832,7 +1993,7 @@ exports[`t-model directive on an input type=radio 1`] = `
|
|||||||
let _2 = 'radio';
|
let _2 = 'radio';
|
||||||
let _3 = 'one';
|
let _3 = 'one';
|
||||||
let _4 = 'One';
|
let _4 = 'One';
|
||||||
let c5 = [], p5 = {key:5,attrs:{type: _2,id: _3,value: _4},on:{}};
|
let c5 = [], p5 = {key:5,attrs:{type: _2,id: _3,value: _4},props:{value: _4},on:{}};
|
||||||
let vn5 = h('input', p5, c5);
|
let vn5 = h('input', p5, c5);
|
||||||
c1.push(vn5);
|
c1.push(vn5);
|
||||||
let expr5 = scope['state'];
|
let expr5 = scope['state'];
|
||||||
@@ -1842,7 +2003,7 @@ exports[`t-model directive on an input type=radio 1`] = `
|
|||||||
let _7 = 'radio';
|
let _7 = 'radio';
|
||||||
let _8 = 'two';
|
let _8 = 'two';
|
||||||
let _9 = 'Two';
|
let _9 = 'Two';
|
||||||
let c10 = [], p10 = {key:10,attrs:{type: _7,id: _8,value: _9},on:{}};
|
let c10 = [], p10 = {key:10,attrs:{type: _7,id: _8,value: _9},props:{value: _9},on:{}};
|
||||||
let vn10 = h('input', p10, c10);
|
let vn10 = h('input', p10, c10);
|
||||||
c1.push(vn10);
|
c1.push(vn10);
|
||||||
let expr10 = scope['state'];
|
let expr10 = scope['state'];
|
||||||
|
|||||||
@@ -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`] = `
|
exports[`t-slot directive content is the default slot 1`] = `
|
||||||
"function anonymous(context, extra
|
"function anonymous(context, extra
|
||||||
) {
|
) {
|
||||||
@@ -258,8 +348,12 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
|
|||||||
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _4 = _5 = _3;
|
let _4 = _5 = _3;
|
||||||
if (!(_3 instanceof Array)) {
|
if (!(_3 instanceof Array)) {
|
||||||
_4 = Object.keys(_3);
|
if (typeof _3[Symbol.iterator] === 'function') {
|
||||||
_5 = Object.values(_3);
|
_4 = [..._3];
|
||||||
|
} else {
|
||||||
|
_4 = Object.keys(_3);
|
||||||
|
_5 = Object.values(_3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _length4 = _4.length;
|
let _length4 = _4.length;
|
||||||
let _origScope6 = scope;
|
let _origScope6 = scope;
|
||||||
@@ -355,8 +449,12 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
|
|||||||
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
|
||||||
let _4 = _5 = _3;
|
let _4 = _5 = _3;
|
||||||
if (!(_3 instanceof Array)) {
|
if (!(_3 instanceof Array)) {
|
||||||
_4 = Object.keys(_3);
|
if (typeof _3[Symbol.iterator] === 'function') {
|
||||||
_5 = Object.values(_3);
|
_4 = [..._3];
|
||||||
|
} else {
|
||||||
|
_4 = Object.keys(_3);
|
||||||
|
_5 = Object.values(_3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let _length4 = _4.length;
|
let _length4 = _4.length;
|
||||||
let _origScope6 = scope;
|
let _origScope6 = scope;
|
||||||
@@ -469,6 +567,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`] = `
|
exports[`t-slot directive template can just return a slot 1`] = `
|
||||||
"function anonymous(context, extra
|
"function anonymous(context, extra
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ afterEach(() => {
|
|||||||
|
|
||||||
function children(w: Component): Component[] {
|
function children(w: Component): Component[] {
|
||||||
const childrenMap = w.__owl__.children;
|
const childrenMap = w.__owl__.children;
|
||||||
return Object.keys(childrenMap).map(id => childrenMap[id]);
|
return Object.keys(childrenMap).map((id) => childrenMap[id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("async rendering", () => {
|
describe("async rendering", () => {
|
||||||
@@ -404,7 +404,7 @@ describe("async rendering", () => {
|
|||||||
}
|
}
|
||||||
class ChildB extends Component {
|
class ChildB extends Component {
|
||||||
willStart(): any {
|
willStart(): any {
|
||||||
return new Promise(function() {});
|
return new Promise(function () {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
@@ -1366,6 +1366,28 @@ describe("async rendering", () => {
|
|||||||
await prom;
|
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 () => {
|
test("change state and call manually render: no unnecessary rendering", async () => {
|
||||||
class Widget extends Component {
|
class Widget extends Component {
|
||||||
static template = xml`<div><t t-esc="state.val"/></div>`;
|
static template = xml`<div><t t-esc="state.val"/></div>`;
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Component, Env } from "../../src/component/component";
|
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 { QWeb } from "../../src/qweb/qweb";
|
||||||
import { xml } from "../../src/tags";
|
import { xml } from "../../src/tags";
|
||||||
import { useState, useRef } from "../../src/hooks";
|
|
||||||
import { EventBus } from "../../src/core/event_bus";
|
|
||||||
import {
|
import {
|
||||||
|
editInput,
|
||||||
makeDeferred,
|
makeDeferred,
|
||||||
makeTestFixture,
|
|
||||||
makeTestEnv,
|
makeTestEnv,
|
||||||
|
makeTestFixture,
|
||||||
nextMicroTick,
|
nextMicroTick,
|
||||||
nextTick,
|
nextTick,
|
||||||
normalize,
|
normalize,
|
||||||
editInput
|
|
||||||
} from "../helpers";
|
} from "../helpers";
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -39,7 +39,7 @@ afterEach(() => {
|
|||||||
|
|
||||||
function children(w: Component): Component[] {
|
function children(w: Component): Component[] {
|
||||||
const childrenMap = w.__owl__.children;
|
const childrenMap = w.__owl__.children;
|
||||||
return Object.keys(childrenMap).map(id => childrenMap[id]);
|
return Object.keys(childrenMap).map((id) => childrenMap[id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test components
|
// Test components
|
||||||
@@ -133,7 +133,7 @@ describe("basic widget properties", () => {
|
|||||||
static template = xml`
|
static template = xml`
|
||||||
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
|
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
|
||||||
state = useState({
|
state = useState({
|
||||||
counter: 0
|
counter: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ describe("basic widget properties", () => {
|
|||||||
static template = xml`
|
static template = xml`
|
||||||
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
|
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
|
||||||
state = useState({
|
state = useState({
|
||||||
counter: 0
|
counter: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,7 +537,7 @@ describe("lifecycle hooks", () => {
|
|||||||
"child:mounted",
|
"child:mounted",
|
||||||
"parent:willUnmount",
|
"parent:willUnmount",
|
||||||
"child:willUnmount",
|
"child:willUnmount",
|
||||||
"childchild:willUnmount"
|
"childchild:willUnmount",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -589,7 +589,7 @@ describe("lifecycle hooks", () => {
|
|||||||
"childchild:willPatch",
|
"childchild:willPatch",
|
||||||
"childchild:patched",
|
"childchild:patched",
|
||||||
"child:patched",
|
"child:patched",
|
||||||
"parent:patched"
|
"parent:patched",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -782,7 +782,7 @@ describe("lifecycle hooks", () => {
|
|||||||
"c mounted",
|
"c mounted",
|
||||||
"p mounted",
|
"p mounted",
|
||||||
"p willunmount",
|
"p willunmount",
|
||||||
"c willunmount"
|
"c willunmount",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -960,7 +960,7 @@ describe("lifecycle hooks", () => {
|
|||||||
"parent:willPatch",
|
"parent:willPatch",
|
||||||
"child:willPatch",
|
"child:willPatch",
|
||||||
"child:patched",
|
"child:patched",
|
||||||
"parent:patched"
|
"parent:patched",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1134,7 +1134,7 @@ describe("composition", () => {
|
|||||||
class App extends Component {
|
class App extends Component {
|
||||||
static template = xml`<t t-component="myComponent" t-key="state.child"/>`;
|
static template = xml`<t t-component="myComponent" t-key="state.child"/>`;
|
||||||
state = useState({
|
state = useState({
|
||||||
child: "a"
|
child: "a",
|
||||||
});
|
});
|
||||||
get myComponent() {
|
get myComponent() {
|
||||||
return this.state.child === "a" ? A : B;
|
return this.state.child === "a" ? A : B;
|
||||||
@@ -1158,7 +1158,7 @@ describe("composition", () => {
|
|||||||
class App extends Component {
|
class App extends Component {
|
||||||
static template = xml`<t t-component="myComponent" t-key="state.child"/>`;
|
static template = xml`<t t-component="myComponent" t-key="state.child"/>`;
|
||||||
state = useState({
|
state = useState({
|
||||||
child: "a"
|
child: "a",
|
||||||
});
|
});
|
||||||
get myComponent() {
|
get myComponent() {
|
||||||
return this.state.child === "a" ? A : B;
|
return this.state.child === "a" ? A : B;
|
||||||
@@ -1228,7 +1228,7 @@ describe("composition", () => {
|
|||||||
static template = xml`
|
static template = xml`
|
||||||
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
|
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
|
||||||
state = useState({
|
state = useState({
|
||||||
counter: 0
|
counter: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1327,7 +1327,7 @@ describe("composition", () => {
|
|||||||
static template = xml`
|
static template = xml`
|
||||||
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
|
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
|
||||||
state = useState({
|
state = useState({
|
||||||
counter: 0
|
counter: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1350,7 +1350,7 @@ describe("composition", () => {
|
|||||||
static template = xml`
|
static template = xml`
|
||||||
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
|
<div><t t-esc="state.counter"/><button t-on-click="state.counter++">Inc</button></div>`;
|
||||||
state = useState({
|
state = useState({
|
||||||
counter: 0
|
counter: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1389,7 +1389,7 @@ describe("composition", () => {
|
|||||||
);
|
);
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
state = useState({
|
state = useState({
|
||||||
numbers: [1, 2, 3]
|
numbers: [1, 2, 3],
|
||||||
});
|
});
|
||||||
static components = { ChildWidget };
|
static components = { ChildWidget };
|
||||||
}
|
}
|
||||||
@@ -1429,7 +1429,7 @@ describe("composition", () => {
|
|||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static template = "parent";
|
static template = "parent";
|
||||||
state = useState({
|
state = useState({
|
||||||
numbers: [1, 2, 3]
|
numbers: [1, 2, 3],
|
||||||
});
|
});
|
||||||
static components = { ChildWidget };
|
static components = { ChildWidget };
|
||||||
}
|
}
|
||||||
@@ -1513,8 +1513,8 @@ describe("composition", () => {
|
|||||||
blips: [
|
blips: [
|
||||||
{ a: "a", id: 1 },
|
{ a: "a", id: 1 },
|
||||||
{ b: "b", id: 2 },
|
{ b: "b", id: 2 },
|
||||||
{ c: "c", id: 4 }
|
{ c: "c", id: 4 },
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const parent = new Parent();
|
const parent = new Parent();
|
||||||
@@ -1621,8 +1621,8 @@ describe("composition", () => {
|
|||||||
records: [
|
records: [
|
||||||
{ id: 1, val: 1 },
|
{ id: 1, val: 1 },
|
||||||
{ id: 2, val: 2 },
|
{ id: 2, val: 2 },
|
||||||
{ id: 3, val: 3 }
|
{ id: 3, val: 3 },
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
static components = { ChildWidget };
|
static components = { ChildWidget };
|
||||||
}
|
}
|
||||||
@@ -1725,6 +1725,47 @@ describe("props evaluation ", () => {
|
|||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
expect(normalize(fixture.innerHTML)).toBe("<div><span>42</span></div>");
|
expect(normalize(fixture.innerHTML)).toBe("<div><span>42</span></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("t-set with a body expression can be used as textual prop", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`<span t-esc="props.val"/>`;
|
||||||
|
}
|
||||||
|
class Parent extends Component {
|
||||||
|
static components = { Child };
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<t t-set="abc">42</t>
|
||||||
|
<Child val="abc"/>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const widget = new Parent();
|
||||||
|
await widget.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
|
||||||
|
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-set with a body expression can be passed in props, and then t-raw", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<span>
|
||||||
|
<t t-esc="props.val"/>
|
||||||
|
<t t-raw="props.val"/>
|
||||||
|
</span>`;
|
||||||
|
}
|
||||||
|
class Parent extends Component {
|
||||||
|
static components = { Child };
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<t t-set="abc"><p>43</p></t>
|
||||||
|
<Child val="abc"/>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const widget = new Parent();
|
||||||
|
await widget.mount(fixture);
|
||||||
|
expect(fixture.innerHTML).toBe("<div><span><p>4343</p><p>43</p></span></div>");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("other directives with t-component", () => {
|
describe("other directives with t-component", () => {
|
||||||
@@ -2003,7 +2044,7 @@ describe("other directives with t-component", () => {
|
|||||||
}
|
}
|
||||||
const widget = new ParentWidget();
|
const widget = new ParentWidget();
|
||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
(<HTMLElement>fixture).addEventListener("ev", function(e) {
|
(<HTMLElement>fixture).addEventListener("ev", function (e) {
|
||||||
steps.push(e.defaultPrevented);
|
steps.push(e.defaultPrevented);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2038,7 +2079,7 @@ describe("other directives with t-component", () => {
|
|||||||
}
|
}
|
||||||
const widget = new ParentWidget();
|
const widget = new ParentWidget();
|
||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
(<HTMLElement>fixture).addEventListener("ev", function(e) {
|
(<HTMLElement>fixture).addEventListener("ev", function (e) {
|
||||||
steps.push(e.defaultPrevented);
|
steps.push(e.defaultPrevented);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2976,7 +3017,7 @@ describe("random stuff/miscellaneous", () => {
|
|||||||
"D:mounted",
|
"D:mounted",
|
||||||
"C:mounted",
|
"C:mounted",
|
||||||
"B:mounted",
|
"B:mounted",
|
||||||
"A:mounted"
|
"A:mounted",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// update
|
// update
|
||||||
@@ -2999,7 +3040,7 @@ describe("random stuff/miscellaneous", () => {
|
|||||||
"E:destroy",
|
"E:destroy",
|
||||||
"F:mounted",
|
"F:mounted",
|
||||||
"D:patched",
|
"D:patched",
|
||||||
"C:patched"
|
"C:patched",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3015,6 +3056,85 @@ describe("random stuff/miscellaneous", () => {
|
|||||||
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
||||||
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>42</span></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("update props of component without concrete own node", async () => {
|
||||||
|
class Custom extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div class="widget-subkey">
|
||||||
|
<t t-esc="props.key"/>__<t t-esc="props.subKey"/>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
class Child extends Component {
|
||||||
|
static components = { Custom };
|
||||||
|
static template = xml`
|
||||||
|
<t t-component="Custom"
|
||||||
|
t-key="props.subKey"
|
||||||
|
key="props.key"
|
||||||
|
subKey="props.subKey"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static components = { Child };
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<Child t-key="childProps.key" t-props="childProps"/>
|
||||||
|
</div>`;
|
||||||
|
childProps = {
|
||||||
|
key: 1,
|
||||||
|
subKey: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const parent = new Parent(null);
|
||||||
|
await parent.mount(fixture);
|
||||||
|
expect(fixture.textContent!.trim()).toBe("1__1");
|
||||||
|
|
||||||
|
// First step: change the Custom's instance
|
||||||
|
Object.assign(parent.childProps, {
|
||||||
|
subKey: 2,
|
||||||
|
});
|
||||||
|
parent.render();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.textContent!.trim()).toBe("1__2");
|
||||||
|
|
||||||
|
// Second step, change both Child's and Custom's instance
|
||||||
|
Object.assign(parent.childProps, {
|
||||||
|
key: 2,
|
||||||
|
subKey: 3,
|
||||||
|
});
|
||||||
|
parent.render();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.textContent!.trim()).toBe("2__3");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("two renderings initiated between willPatch and patched", async () => {
|
||||||
|
let app;
|
||||||
|
|
||||||
|
class Panel extends Component {
|
||||||
|
static template = xml`<abc><t t-esc="props.val"/></abc>`;
|
||||||
|
mounted() {
|
||||||
|
app.render();
|
||||||
|
}
|
||||||
|
willUnmount() {
|
||||||
|
app.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main root component
|
||||||
|
class App extends Component {
|
||||||
|
static components = { Panel };
|
||||||
|
static template = xml`<div><Panel t-key="'panel_' + state.panel" val="state.panel"/></div>`;
|
||||||
|
|
||||||
|
state = useState({ panel: "Panel1" });
|
||||||
|
}
|
||||||
|
|
||||||
|
app = new App();
|
||||||
|
await app.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<div><abc>Panel1</abc></div>");
|
||||||
|
app.state.panel = "Panel2";
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe("<div><abc>Panel2</abc></div>");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("widget and observable state", () => {
|
describe("widget and observable state", () => {
|
||||||
@@ -3080,6 +3200,15 @@ describe("can deduce template from name", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<span>Orval</span>");
|
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 () => {
|
test("can find template of parent component, defined by template key", async () => {
|
||||||
class ABC extends Component {
|
class ABC extends Component {
|
||||||
static template = "Achel";
|
static template = "Achel";
|
||||||
@@ -3129,6 +3258,46 @@ describe("t-model directive", () => {
|
|||||||
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
|
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("basic use, on an input with bracket expression", async () => {
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<input t-model="state['text']"/>
|
||||||
|
<span><t t-esc="state.text"/></span>
|
||||||
|
</div>`;
|
||||||
|
state = useState({ text: "" });
|
||||||
|
}
|
||||||
|
const comp = new SomeComponent();
|
||||||
|
await comp.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<div><input><span></span></div>");
|
||||||
|
|
||||||
|
const input = fixture.querySelector("input")!;
|
||||||
|
await editInput(input, "test");
|
||||||
|
expect(comp.state.text).toBe("test");
|
||||||
|
expect(fixture.innerHTML).toBe("<div><input><span>test</span></div>");
|
||||||
|
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws if invalid expression", async () => {
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<input t-model="state"/>
|
||||||
|
</div>`;
|
||||||
|
state = useState({ text: "" });
|
||||||
|
}
|
||||||
|
const comp = new SomeComponent();
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
await comp.mount(fixture);
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.message).toBe(`Invalid t-model expression: "state" (it should be assignable)`);
|
||||||
|
});
|
||||||
|
|
||||||
test("basic use, on another key in component", async () => {
|
test("basic use, on another key in component", async () => {
|
||||||
env.qweb.addTemplates(`
|
env.qweb.addTemplates(`
|
||||||
<templates>
|
<templates>
|
||||||
@@ -3401,7 +3570,7 @@ describe("t-model directive", () => {
|
|||||||
state = useState([
|
state = useState([
|
||||||
{ f: false, id: 1 },
|
{ f: false, id: 1 },
|
||||||
{ f: false, id: 2 },
|
{ f: false, id: 2 },
|
||||||
{ f: false, id: 3 }
|
{ f: false, id: 3 },
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
const comp = new SomeComponent();
|
const comp = new SomeComponent();
|
||||||
@@ -3418,6 +3587,28 @@ describe("t-model directive", () => {
|
|||||||
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
|
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("in a t-foreach, part 2", async () => {
|
||||||
|
class SomeComponent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<t t-foreach="state" t-as="thing" t-key="thing_index" >
|
||||||
|
<input t-model="state[thing_index]"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
state = useState(["zuko", "iroh"]);
|
||||||
|
}
|
||||||
|
const comp = new SomeComponent();
|
||||||
|
await comp.mount(fixture);
|
||||||
|
expect(comp.state).toEqual(["zuko", "iroh"]);
|
||||||
|
|
||||||
|
const input = fixture.querySelectorAll("input")[1]!;
|
||||||
|
input.value = "uncle iroh";
|
||||||
|
input.dispatchEvent(new Event("input"));
|
||||||
|
expect(comp.state).toEqual(["zuko", "uncle iroh"]);
|
||||||
|
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
test("two inputs in a div with a t-key", async () => {
|
test("two inputs in a div with a t-key", async () => {
|
||||||
class SomeComponent extends Component {
|
class SomeComponent extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
@@ -3446,7 +3637,7 @@ describe("environment and plugins", () => {
|
|||||||
let bus = new EventBus();
|
let bus = new EventBus();
|
||||||
|
|
||||||
// definition of a plugin
|
// definition of a plugin
|
||||||
const somePlugin = env => {
|
const somePlugin = (env) => {
|
||||||
env.someFlag = true;
|
env.someFlag = true;
|
||||||
bus.on("some-event", null, () => {
|
bus.on("some-event", null, () => {
|
||||||
env.someFlag = !env.someFlag;
|
env.someFlag = !env.someFlag;
|
||||||
@@ -3475,11 +3666,14 @@ describe("environment and plugins", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("can define specific env for root components", async () => {
|
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>`;
|
static template = xml`<span></span>`;
|
||||||
}
|
}
|
||||||
App1.env = { test: 1 };
|
App1.env = { test: 1 };
|
||||||
class App2 extends Component {
|
class App2 extends Component<{}, AppEnv> {
|
||||||
static template = xml`<span></span>`;
|
static template = xml`<span></span>`;
|
||||||
}
|
}
|
||||||
App2.env = { test: 2 };
|
App2.env = { test: 2 };
|
||||||
@@ -3813,7 +4007,9 @@ describe("t-call", () => {
|
|||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div><p>lucas</p></div>");
|
expect(fixture.innerHTML).toBe("<div><p>lucas</p></div>");
|
||||||
fixture.querySelector("p")!.click();
|
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 () => {
|
test("parent is set within t-call", async () => {
|
||||||
@@ -3835,7 +4031,10 @@ describe("t-call", () => {
|
|||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div><span>lucas</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>lucas</span></div>");
|
||||||
expect(child.__owl__.parent).toBe(parent);
|
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 () => {
|
test("t-call in t-foreach and children component", async () => {
|
||||||
@@ -3877,7 +4076,9 @@ describe("t-call", () => {
|
|||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<span>lucas</span>");
|
expect(fixture.innerHTML).toBe("<span>lucas</span>");
|
||||||
expect(child.__owl__.parent).toBe(parent);
|
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 () => {
|
test("handlers with arguments are properly bound through a t-call", async () => {
|
||||||
@@ -3893,7 +4094,9 @@ describe("t-call", () => {
|
|||||||
}
|
}
|
||||||
const parent = new Parent();
|
const parent = new Parent();
|
||||||
await parent.mount(fixture);
|
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();
|
fixture.querySelector("p")!.click();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ describe("props validation", () => {
|
|||||||
{ type: String, ok: "1", ko: 1 },
|
{ type: String, ok: "1", ko: 1 },
|
||||||
{ type: Object, ok: {}, ko: "1" },
|
{ type: Object, ok: {}, ko: "1" },
|
||||||
{ type: Date, ok: new Date(), ko: "1" },
|
{ type: Date, ok: new Date(), ko: "1" },
|
||||||
{ type: Function, ok: () => {}, ko: "1" }
|
{ type: Function, ok: () => {}, ko: "1" },
|
||||||
];
|
];
|
||||||
|
|
||||||
let props;
|
let props;
|
||||||
@@ -149,7 +149,7 @@ describe("props validation", () => {
|
|||||||
{ type: String, ok: "1", ko: 1 },
|
{ type: String, ok: "1", ko: 1 },
|
||||||
{ type: Object, ok: {}, ko: "1" },
|
{ type: Object, ok: {}, ko: "1" },
|
||||||
{ type: Date, ok: new Date(), ko: "1" },
|
{ type: Date, ok: new Date(), ko: "1" },
|
||||||
{ type: Function, ok: () => {}, ko: "1" }
|
{ type: Function, ok: () => {}, ko: "1" },
|
||||||
];
|
];
|
||||||
|
|
||||||
let props;
|
let props;
|
||||||
@@ -396,7 +396,7 @@ describe("props validation", () => {
|
|||||||
class TestWidget extends Component {
|
class TestWidget extends Component {
|
||||||
static template = xml`<div>hey</div>`;
|
static template = xml`<div>hey</div>`;
|
||||||
static props = {
|
static props = {
|
||||||
p: { type: Object, shape: { id: Number, url: String } }
|
p: { type: Object, shape: { id: Number, url: String } },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
@@ -458,9 +458,9 @@ describe("props validation", () => {
|
|||||||
type: Object,
|
type: Object,
|
||||||
shape: {
|
shape: {
|
||||||
id: Number,
|
id: Number,
|
||||||
url: [Boolean, { type: Array, element: Number }]
|
url: [Boolean, { type: Array, element: Number }],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
@@ -510,10 +510,10 @@ describe("props validation", () => {
|
|||||||
element: {
|
element: {
|
||||||
type: Object,
|
type: Object,
|
||||||
shape: {
|
shape: {
|
||||||
num: { type: Number, optional: true }
|
num: { type: Number, optional: true },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let error;
|
let error;
|
||||||
@@ -539,8 +539,8 @@ describe("props validation", () => {
|
|||||||
class TestComponent extends Component {
|
class TestComponent extends Component {
|
||||||
static props = {
|
static props = {
|
||||||
size: {
|
size: {
|
||||||
validate: e => ["small", "medium", "large"].includes(e)
|
validate: (e) => ["small", "medium", "large"].includes(e),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let error;
|
let error;
|
||||||
@@ -561,13 +561,13 @@ describe("props validation", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("can validate with a custom validator, and a type", () => {
|
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 {
|
class TestComponent extends Component {
|
||||||
static props = {
|
static props = {
|
||||||
n: {
|
n: {
|
||||||
type: Number,
|
type: Number,
|
||||||
validate: validator
|
validate: validator,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let error;
|
let error;
|
||||||
@@ -786,6 +786,31 @@ describe("props validation", () => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div><div>4</div></div>");
|
expect(fixture.innerHTML).toBe("<div><div>4</div></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("mix of optional and mandatory", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static props = {
|
||||||
|
optional: { type: String, optional: true },
|
||||||
|
mandatory: Number,
|
||||||
|
};
|
||||||
|
static template = xml` <div><t t-esc="props.mandatory"/></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
static components = { Child };
|
||||||
|
static template = xml`<div><Child/></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const w = new App(undefined, {});
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
await w.mount(fixture);
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.message).toBe("Missing props 'mandatory' (component 'Child')");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("default props", () => {
|
describe("default props", () => {
|
||||||
|
|||||||
+194
-15
@@ -28,7 +28,7 @@ afterEach(() => {
|
|||||||
|
|
||||||
function children(w: Component): Component[] {
|
function children(w: Component): Component[] {
|
||||||
const childrenMap = w.__owl__.children;
|
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>
|
<templates>
|
||||||
<div t-name="Parent">
|
<div t-name="Parent">
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<t t-set="header"><span>header</span></t>
|
<t t-set-slot="header"><span>header</span></t>
|
||||||
<t t-set="footer"><span>footer</span></t>
|
<t t-set-slot="footer"><span>footer</span></t>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
<div t-name="Dialog">
|
<div t-name="Dialog">
|
||||||
@@ -67,6 +67,38 @@ describe("t-slot directive", () => {
|
|||||||
expect(QWeb.slots["1_footer"].toString()).toMatchSnapshot();
|
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 () => {
|
test("named slots can define a default content", async () => {
|
||||||
class Dialog extends Component {
|
class Dialog extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
@@ -128,7 +160,7 @@ describe("t-slot directive", () => {
|
|||||||
</span>`;
|
</span>`;
|
||||||
}
|
}
|
||||||
class Parent extends Component {
|
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 };
|
static components = { Dialog };
|
||||||
}
|
}
|
||||||
const parent = new Parent();
|
const parent = new Parent();
|
||||||
@@ -143,7 +175,7 @@ describe("t-slot directive", () => {
|
|||||||
<div t-name="Parent">
|
<div t-name="Parent">
|
||||||
<span class="counter"><t t-esc="state.val"/></span>
|
<span class="counter"><t t-esc="state.val"/></span>
|
||||||
<Dialog>
|
<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>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
<span t-name="Dialog"><t t-slot="footer"/></span>
|
<span t-name="Dialog"><t t-slot="footer"/></span>
|
||||||
@@ -192,8 +224,8 @@ describe("t-slot directive", () => {
|
|||||||
state = useState({
|
state = useState({
|
||||||
users: [
|
users: [
|
||||||
{ id: 1, name: "Aaron" },
|
{ id: 1, name: "Aaron" },
|
||||||
{ id: 2, name: "David" }
|
{ id: 2, name: "David" },
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
static components = { Link };
|
static components = { Link };
|
||||||
}
|
}
|
||||||
@@ -236,8 +268,8 @@ describe("t-slot directive", () => {
|
|||||||
state = useState({
|
state = useState({
|
||||||
users: [
|
users: [
|
||||||
{ id: 1, name: "Aaron" },
|
{ id: 1, name: "Aaron" },
|
||||||
{ id: 2, name: "David" }
|
{ id: 2, name: "David" },
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
static components = { Link };
|
static components = { Link };
|
||||||
}
|
}
|
||||||
@@ -302,7 +334,7 @@ describe("t-slot directive", () => {
|
|||||||
<div>
|
<div>
|
||||||
<span class="counter"><t t-esc="state.val"/></span>
|
<span class="counter"><t t-esc="state.val"/></span>
|
||||||
<Dialog>
|
<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>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -376,7 +408,7 @@ describe("t-slot directive", () => {
|
|||||||
<templates>
|
<templates>
|
||||||
<div t-name="Parent">
|
<div t-name="Parent">
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<t t-set="content">
|
<t t-set-slot="content">
|
||||||
<span>sts</span>
|
<span>sts</span>
|
||||||
<span>rocks</span>
|
<span>rocks</span>
|
||||||
</t>
|
</t>
|
||||||
@@ -442,14 +474,14 @@ describe("t-slot directive", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div><span><span>some content</span></span></div>");
|
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;
|
const consoleLog = console.log;
|
||||||
console.log = jest.fn();
|
console.log = jest.fn();
|
||||||
|
|
||||||
env.qweb.addTemplates(`
|
env.qweb.addTemplates(`
|
||||||
<templates>
|
<templates>
|
||||||
<div t-name="Parent">
|
<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>
|
</div>
|
||||||
<span t-name="Dialog">
|
<span t-name="Dialog">
|
||||||
<t t-slot="content"/>
|
<t t-slot="content"/>
|
||||||
@@ -623,8 +655,8 @@ describe("t-slot directive", () => {
|
|||||||
class A extends Component {
|
class A extends Component {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<B>
|
<B>
|
||||||
<t t-set="s1"><C val="1"/></t>
|
<t t-set-slot="s1"><C val="1"/></t>
|
||||||
<t t-set="s2"><C val="2"/></t>
|
<t t-set-slot="s2"><C val="2"/></t>
|
||||||
</B>`;
|
</B>`;
|
||||||
static components = { B, C };
|
static components = { B, C };
|
||||||
}
|
}
|
||||||
@@ -873,4 +905,151 @@ describe("t-slot directive", () => {
|
|||||||
await parent.mount(fixture);
|
await parent.mount(fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div><div><span><p>heyaaron</p></span></div></div>");
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("slot and t-esc", 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-esc="'toph'"/></Dialog></div>`;
|
||||||
|
static components = { Dialog };
|
||||||
|
}
|
||||||
|
const parent = new Parent();
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<div><span>toph</span></div>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("slot and (inline) t-esc", async () => {
|
||||||
|
class Dialog extends Component {
|
||||||
|
static template = xml`<span><t t-slot="default"/></span>`;
|
||||||
|
}
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`<div><Dialog t-esc="'toph'"/></div>`;
|
||||||
|
static components = { Dialog };
|
||||||
|
}
|
||||||
|
const parent = new Parent();
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<div><span>toph</span></div>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("slot and t-call", async () => {
|
||||||
|
env.qweb.addTemplate("sokka", "<p>sokka</p>");
|
||||||
|
class Dialog extends Component {
|
||||||
|
static template = xml`<span><t t-slot="default"/></span>`;
|
||||||
|
}
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`<div><Dialog><t t-call="sokka"/></Dialog></div>`;
|
||||||
|
static components = { Dialog };
|
||||||
|
}
|
||||||
|
const parent = new Parent();
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<div><span><p>sokka</p></span></div>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("slot and (inline) t-call", async () => {
|
||||||
|
env.qweb.addTemplate("sokka", "<p>sokka</p>");
|
||||||
|
class Dialog extends Component {
|
||||||
|
static template = xml`<span><t t-slot="default"/></span>`;
|
||||||
|
}
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`<div><Dialog t-call="sokka"/></div>`;
|
||||||
|
static components = { Dialog };
|
||||||
|
}
|
||||||
|
const parent = new Parent();
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<div><span><p>sokka</p></span></div>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("named slot inside slot", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<t t-slot="brol"/>
|
||||||
|
<t t-slot="default"/>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<Child>
|
||||||
|
<t t-set-slot="brol">
|
||||||
|
<p>A<t t-esc="value"/></p>
|
||||||
|
</t>
|
||||||
|
<Child>
|
||||||
|
<t t-set-slot="brol">
|
||||||
|
<p>B<t t-esc="value"/></p>
|
||||||
|
</t>
|
||||||
|
</Child>
|
||||||
|
</Child>
|
||||||
|
</div>`;
|
||||||
|
static components = { Child };
|
||||||
|
value = "blip";
|
||||||
|
}
|
||||||
|
const parent = new Parent();
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<div><div><p>Ablip</p><div><p>Bblip</p></div></div></div>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("named slots inside slot, again", async () => {
|
||||||
|
class Child extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<child>
|
||||||
|
<t t-slot="brol1">default1</t>
|
||||||
|
<t t-slot="brol2">default2</t>
|
||||||
|
<t t-slot="default"/>
|
||||||
|
</child>`;
|
||||||
|
}
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<Child>
|
||||||
|
<t t-set-slot="brol1">
|
||||||
|
<p>A<t t-esc="value"/></p>
|
||||||
|
</t>
|
||||||
|
<Child>
|
||||||
|
<t t-set-slot="brol2">
|
||||||
|
<p>B<t t-esc="value"/></p>
|
||||||
|
</t>
|
||||||
|
</Child>
|
||||||
|
</Child>
|
||||||
|
</div>`;
|
||||||
|
static components = { Child };
|
||||||
|
value = "blip";
|
||||||
|
}
|
||||||
|
const parent = new Parent();
|
||||||
|
await parent.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><child><p>Ablip</p>default2<child>default1<p>Bblip</p></child></child></div>"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Component, Env } from "../../src/component/component";
|
|||||||
import { useState } from "../../src/hooks";
|
import { useState } from "../../src/hooks";
|
||||||
import { xml } from "../../src/tags";
|
import { xml } from "../../src/tags";
|
||||||
import { makeDeferred, makeTestEnv, makeTestFixture, nextTick, nextMicroTick } from "../helpers";
|
import { makeDeferred, makeTestEnv, makeTestFixture, nextTick, nextMicroTick } from "../helpers";
|
||||||
|
import { scheduler } from "../../src/component/scheduler";
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Setup and helpers
|
// Setup and helpers
|
||||||
@@ -28,14 +29,34 @@ afterEach(() => {
|
|||||||
describe("mount targets", () => {
|
describe("mount targets", () => {
|
||||||
test("can attach a component to an existing node (if same tagname)", async () => {
|
test("can attach a component to an existing node (if same tagname)", async () => {
|
||||||
class App extends Component {
|
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");
|
const div = document.createElement("div");
|
||||||
|
div.classList.add("arbitrary");
|
||||||
|
div.innerHTML = `<p>pre-existing</p>`;
|
||||||
fixture.appendChild(div);
|
fixture.appendChild(div);
|
||||||
|
|
||||||
const app = new App();
|
const app = new App();
|
||||||
await app.mount(div, { position: "self" });
|
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 () => {
|
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 {
|
class Child extends Component {
|
||||||
static template = xml`<span t-esc="state.val"/>`;
|
static template = xml`<span t-esc="state.val"/>`;
|
||||||
state = useState({
|
state = useState({
|
||||||
val: "C1"
|
val: "C1",
|
||||||
});
|
});
|
||||||
constructor(parent, props) {
|
constructor(parent, props) {
|
||||||
super(parent, props);
|
super(parent, props);
|
||||||
@@ -470,4 +491,121 @@ describe("unmounting and remounting", () => {
|
|||||||
expect(steps).toEqual(["1 resolved", "2 resolved"]);
|
expect(steps).toEqual(["1 resolved", "2 resolved"]);
|
||||||
expect(fixture.innerHTML).toBe("<div>Hey</div>");
|
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");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("destroying a sub-component cleans itself from parent's vnode", async () => {
|
||||||
|
class C1 extends Component {
|
||||||
|
static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
|
||||||
|
}
|
||||||
|
class P extends Component {
|
||||||
|
static components = { C1 };
|
||||||
|
static template = xml`<div><div><C1 t-props="state" t-if="state.a"/></div></div>`;
|
||||||
|
state = {
|
||||||
|
a: "first",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const parent = new P();
|
||||||
|
await parent.mount(fixture);
|
||||||
|
expect(fixture.textContent).toBe("first");
|
||||||
|
parent.unmount();
|
||||||
|
parent.state.a = "";
|
||||||
|
parent.mount(fixture);
|
||||||
|
parent.state.a = "fixed";
|
||||||
|
await parent.render();
|
||||||
|
expect(fixture.textContent).toBe("fixed");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("destroying a sub-component cleans itself from parent's vnode, part 2", async () => {
|
||||||
|
class C1 extends Component {
|
||||||
|
static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
|
||||||
|
}
|
||||||
|
class P extends Component {
|
||||||
|
static components = { C1 };
|
||||||
|
static template = xml`<div><div><C1 t-props="state" t-if="state.a"/>some text</div></div>`;
|
||||||
|
state = {
|
||||||
|
a: "first",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const parent = new P();
|
||||||
|
await parent.mount(fixture);
|
||||||
|
expect(fixture.textContent).toBe("firstsome text");
|
||||||
|
parent.unmount();
|
||||||
|
parent.state.a = "";
|
||||||
|
parent.mount(fixture);
|
||||||
|
parent.state.a = "fixed";
|
||||||
|
await parent.render();
|
||||||
|
expect(fixture.textContent).toBe("fixedsome text");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("destroying a sub-component cleans itself from parent's vnode, part 3", async () => {
|
||||||
|
class C1 extends Component {
|
||||||
|
static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class C2 extends Component {
|
||||||
|
static template = xml`<C1 a="props.a"/>`;
|
||||||
|
static components = { C1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
class P extends Component {
|
||||||
|
static components = { C2 };
|
||||||
|
static template = xml`<div><div><C2 t-props="state" t-if="state.a"/></div></div>`;
|
||||||
|
state = {
|
||||||
|
a: "first",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const parent = new P();
|
||||||
|
await parent.mount(fixture);
|
||||||
|
expect(fixture.textContent).toBe("first");
|
||||||
|
parent.unmount();
|
||||||
|
parent.state.a = "";
|
||||||
|
parent.mount(fixture);
|
||||||
|
parent.state.a = "fixed";
|
||||||
|
await parent.render();
|
||||||
|
expect(fixture.textContent).toBe("fixed");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("destroying a sub-component cleans itself from parent's vnode, part 4", async () => {
|
||||||
|
class C1 extends Component {
|
||||||
|
static template = xml`<div><div><t t-esc="props.a"/></div></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class C2 extends Component {
|
||||||
|
static template = xml`<C1 a="props.a"/>`;
|
||||||
|
static components = { C1 };
|
||||||
|
}
|
||||||
|
class P extends Component {
|
||||||
|
static components = { C2 };
|
||||||
|
static template = xml`<div><div><C2 t-props="state" t-if="state.a"/>some text</div></div>`;
|
||||||
|
state = {
|
||||||
|
a: "first",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const parent = new P();
|
||||||
|
await parent.mount(fixture);
|
||||||
|
expect(fixture.textContent).toBe("firstsome text");
|
||||||
|
parent.unmount();
|
||||||
|
parent.state.a = "";
|
||||||
|
parent.mount(fixture);
|
||||||
|
parent.state.a = "fixed";
|
||||||
|
await parent.render();
|
||||||
|
expect(fixture.textContent).toBe("fixedsome text");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ describe("event bus behaviour", () => {
|
|||||||
expect.assertions(1);
|
expect.assertions(1);
|
||||||
const bus = new EventBus();
|
const bus = new EventBus();
|
||||||
const owner = {};
|
const owner = {};
|
||||||
bus.on("event", owner, function(this: any) {
|
bus.on("event", owner, function (this: any) {
|
||||||
expect(this).toBe(owner);
|
expect(this).toBe(owner);
|
||||||
});
|
});
|
||||||
bus.trigger("event");
|
bus.trigger("event");
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe("observer", () => {
|
|||||||
expect(observer.rev).toBe(2);
|
expect(observer.rev).toBe(2);
|
||||||
|
|
||||||
expect(obj2).toEqual({
|
expect(obj2).toEqual({
|
||||||
a: 2
|
a: 2,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ describe("observer", () => {
|
|||||||
expect(observer.rev).toBe(5);
|
expect(observer.rev).toBe(5);
|
||||||
expect(obj).toEqual({
|
expect(obj).toEqual({
|
||||||
a: null,
|
a: null,
|
||||||
b: undefined
|
b: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,6 +68,23 @@ describe("observer", () => {
|
|||||||
expect(obj.date).not.toBe(date);
|
expect(obj.date).not.toBe(date);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("properly handle promises (i.e.: treat them like primitive values", async () => {
|
||||||
|
const observer = new Observer();
|
||||||
|
let resolved = false;
|
||||||
|
const prom = new Promise((r) => r());
|
||||||
|
const obj: any = observer.observe({ prom });
|
||||||
|
expect(obj.prom).toBeInstanceOf(Promise);
|
||||||
|
|
||||||
|
obj.prom.then(() => (resolved = true));
|
||||||
|
|
||||||
|
expect(observer.revNumber(obj)).toBe(1);
|
||||||
|
|
||||||
|
expect(resolved).toBe(false);
|
||||||
|
await Promise.resolve();
|
||||||
|
expect(resolved).toBe(true);
|
||||||
|
expect(observer.revNumber(obj)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
test("can change values in array", () => {
|
test("can change values in array", () => {
|
||||||
const observer = new Observer();
|
const observer = new Observer();
|
||||||
const obj: any = observer.observe({ arr: [1, 2] });
|
const obj: any = observer.observe({ arr: [1, 2] });
|
||||||
|
|||||||
+5
-10
@@ -6,12 +6,7 @@ import { patch } from "../src/vdom";
|
|||||||
import "../src/qweb/base_directives";
|
import "../src/qweb/base_directives";
|
||||||
import "../src/qweb/extensions";
|
import "../src/qweb/extensions";
|
||||||
import "../src/component/directive";
|
import "../src/component/directive";
|
||||||
|
import { browser } from "../src/browser";
|
||||||
// modifies scheduler to make it faster to test components
|
|
||||||
scheduler.requestAnimationFrame = function(callback: FrameRequestCallback) {
|
|
||||||
setTimeout(callback, 1);
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Some static cleanup
|
// Some static cleanup
|
||||||
let nextSlotId;
|
let nextSlotId;
|
||||||
@@ -42,9 +37,8 @@ export function nextMicroTick(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function nextTick(): Promise<void> {
|
export async function nextTick(): Promise<void> {
|
||||||
return new Promise(function(resolve) {
|
await new Promise((resolve) => scheduler.requestAnimationFrame(resolve));
|
||||||
setTimeout(() => scheduler.requestAnimationFrame(() => resolve()));
|
await new Promise((resolve) => setTimeout(resolve));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeTestFixture() {
|
export function makeTestFixture() {
|
||||||
@@ -75,7 +69,8 @@ export function makeDeferred(): Deferred {
|
|||||||
|
|
||||||
export function makeTestEnv(): Env {
|
export function makeTestEnv(): Env {
|
||||||
return {
|
return {
|
||||||
qweb: new QWeb()
|
qweb: new QWeb(),
|
||||||
|
browser: browser,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+40
-7
@@ -10,7 +10,7 @@ import {
|
|||||||
onWillStart,
|
onWillStart,
|
||||||
onWillUpdateProps,
|
onWillUpdateProps,
|
||||||
useSubEnv,
|
useSubEnv,
|
||||||
useExternalListener
|
useExternalListener,
|
||||||
} from "../src/hooks";
|
} from "../src/hooks";
|
||||||
import { xml } from "../src/tags";
|
import { xml } from "../src/tags";
|
||||||
|
|
||||||
@@ -214,7 +214,7 @@ describe("hooks", () => {
|
|||||||
"hook:mounted1",
|
"hook:mounted1",
|
||||||
"hook:mounted2",
|
"hook:mounted2",
|
||||||
"hook:willunmount2",
|
"hook:willunmount2",
|
||||||
"hook:willunmount1"
|
"hook:willunmount1",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@ describe("hooks", () => {
|
|||||||
class WidgetC extends Component {
|
class WidgetC extends Component {
|
||||||
static template = xml`<div class="outer-div">Hello<WidgetB t-ref="mywidgetb" /></div>`;
|
static template = xml`<div class="outer-div">Hello<WidgetB t-ref="mywidgetb" /></div>`;
|
||||||
static components = { WidgetB };
|
static components = { WidgetB };
|
||||||
ref = useRef("mywidgetb");
|
ref = useRef<WidgetB>("mywidgetb");
|
||||||
}
|
}
|
||||||
|
|
||||||
const widget = new WidgetC();
|
const widget = new WidgetC();
|
||||||
@@ -559,20 +559,39 @@ describe("hooks", () => {
|
|||||||
|
|
||||||
test("can use onWillStart, onWillUpdateProps", async () => {
|
test("can use onWillStart, onWillUpdateProps", async () => {
|
||||||
const steps: string[] = [];
|
const steps: string[] = [];
|
||||||
|
async function slow(): Promise<string> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve("slow");
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
function useMyHook() {
|
function useMyHook() {
|
||||||
onWillStart(() => {
|
onWillStart(async () => {
|
||||||
|
steps.push(await slow());
|
||||||
steps.push("onWillStart");
|
steps.push("onWillStart");
|
||||||
});
|
});
|
||||||
onWillUpdateProps(nextProps => {
|
onWillUpdateProps(async (nextProps) => {
|
||||||
expect(nextProps).toEqual({ value: 2 });
|
expect(nextProps).toEqual({ value: 2 });
|
||||||
|
steps.push(await slow());
|
||||||
steps.push("onWillUpdateProps");
|
steps.push("onWillUpdateProps");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function use2ndHook() {
|
||||||
|
onWillStart(() => {
|
||||||
|
steps.push("on2ndStart");
|
||||||
|
});
|
||||||
|
onWillUpdateProps((nextProps) => {
|
||||||
|
expect(nextProps).toEqual({ value: 2 });
|
||||||
|
steps.push("on2ndUpdate");
|
||||||
|
});
|
||||||
|
}
|
||||||
class MyComponent extends Component {
|
class MyComponent extends Component {
|
||||||
static template = xml`<span><t t-esc="props.value"/></span>`;
|
static template = xml`<span><t t-esc="props.value"/></span>`;
|
||||||
constructor(parent, props) {
|
constructor(parent, props) {
|
||||||
super(parent, props);
|
super(parent, props);
|
||||||
useMyHook();
|
useMyHook();
|
||||||
|
use2ndHook();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
@@ -586,12 +605,26 @@ describe("hooks", () => {
|
|||||||
expect(app).not.toHaveProperty("willStart");
|
expect(app).not.toHaveProperty("willStart");
|
||||||
expect(app).not.toHaveProperty("willUpdateProps");
|
expect(app).not.toHaveProperty("willUpdateProps");
|
||||||
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
|
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;
|
app.state.value = 2;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div><span>2</span></div>");
|
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 () => {
|
test("useExternalListener", async () => {
|
||||||
|
|||||||
@@ -486,7 +486,7 @@ describe("Portal: Basic use and DOM placement", () => {
|
|||||||
"parent:mounted",
|
"parent:mounted",
|
||||||
"parent:willPatch",
|
"parent:willPatch",
|
||||||
"child:mounted",
|
"child:mounted",
|
||||||
"parent:patched"
|
"parent:patched",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
parent.state.val = 2;
|
parent.state.val = 2;
|
||||||
@@ -499,7 +499,7 @@ describe("Portal: Basic use and DOM placement", () => {
|
|||||||
"parent:willPatch",
|
"parent:willPatch",
|
||||||
"child:willPatch",
|
"child:willPatch",
|
||||||
"child:patched",
|
"child:patched",
|
||||||
"parent:patched"
|
"parent:patched",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
parent.state.hasChild = false;
|
parent.state.hasChild = false;
|
||||||
@@ -515,7 +515,7 @@ describe("Portal: Basic use and DOM placement", () => {
|
|||||||
"parent:patched",
|
"parent:patched",
|
||||||
"parent:willPatch",
|
"parent:willPatch",
|
||||||
"child:willUnmount",
|
"child:willUnmount",
|
||||||
"parent:patched"
|
"parent:patched",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -741,7 +741,7 @@ describe("Portal: Events handling", () => {
|
|||||||
steps.push(ev.type as string);
|
steps.push(ev.type as string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const bodyListener = ev => {
|
const bodyListener = (ev) => {
|
||||||
steps.push(`body: ${ev.type}`);
|
steps.push(`body: ${ev.type}`);
|
||||||
};
|
};
|
||||||
document.body.addEventListener("click", bodyListener);
|
document.body.addEventListener("click", bodyListener);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+251
-53
@@ -195,7 +195,7 @@ describe("t-esc", () => {
|
|||||||
v2: undefined,
|
v2: undefined,
|
||||||
v3: null,
|
v3: null,
|
||||||
v4: 0,
|
v4: 0,
|
||||||
v5: ""
|
v5: "",
|
||||||
};
|
};
|
||||||
expect(renderToString(qweb, "test", vals)).toBe(
|
expect(renderToString(qweb, "test", vals)).toBe(
|
||||||
"<div><p>false</p><p></p><p></p><p>0</p><p></p></div>"
|
"<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] } });
|
const result = renderToString(qweb, "test", { state: { list: [1, 2] } });
|
||||||
expect(result).toBe("<span>1,2</span>");
|
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", () => {
|
describe("t-raw", () => {
|
||||||
@@ -486,7 +493,7 @@ describe("t-if", () => {
|
|||||||
cond3: false,
|
cond3: false,
|
||||||
cond4: false,
|
cond4: false,
|
||||||
m: 5,
|
m: 5,
|
||||||
n: 2
|
n: 2,
|
||||||
};
|
};
|
||||||
expect(normalize(renderToString(qweb, "test", context))).toBe("<div>andormgtnlt</div>");
|
expect(normalize(renderToString(qweb, "test", context))).toBe("<div>andormgtnlt</div>");
|
||||||
});
|
});
|
||||||
@@ -625,7 +632,7 @@ describe("attributes", () => {
|
|||||||
test("object", () => {
|
test("object", () => {
|
||||||
qweb.addTemplate("test", `<div t-att="value"/>`);
|
qweb.addTemplate("test", `<div t-att="value"/>`);
|
||||||
const result = renderToString(qweb, "test", {
|
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>`);
|
expect(result).toBe(`<div a="1" b="2" c="3"></div>`);
|
||||||
});
|
});
|
||||||
@@ -684,7 +691,7 @@ describe("attributes", () => {
|
|||||||
const result = renderToString(qweb, "test", {
|
const result = renderToString(qweb, "test", {
|
||||||
value1: 0,
|
value1: 0,
|
||||||
value2: 1,
|
value2: 1,
|
||||||
value3: 2
|
value3: 2,
|
||||||
});
|
});
|
||||||
expect(result).toBe(`<div foo="a 0 is 1 of 2 ]"></div>`);
|
expect(result).toBe(`<div foo="a 0 is 1 of 2 ]"></div>`);
|
||||||
});
|
});
|
||||||
@@ -703,7 +710,7 @@ describe("attributes", () => {
|
|||||||
const result = renderToString(qweb, "test", {
|
const result = renderToString(qweb, "test", {
|
||||||
bar: 0,
|
bar: 0,
|
||||||
baz: 1,
|
baz: 1,
|
||||||
qux: { qux: "<>" }
|
qux: { qux: "<>" },
|
||||||
});
|
});
|
||||||
const expected = '<div foo="<foo" bar="0" baz="<1>" qux="<>"></div>';
|
const expected = '<div foo="<foo" bar="0" baz="<1>" qux="<>"></div>';
|
||||||
expect(result).toBe(expected);
|
expect(result).toBe(expected);
|
||||||
@@ -715,6 +722,12 @@ describe("attributes", () => {
|
|||||||
expect(result).toBe(`<div class="hello world"></div>`);
|
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", () => {
|
test("class and t-attf-class with ternary operation", () => {
|
||||||
qweb.addTemplate("test", `<div class="hello" t-attf-class="{{value ? 'world' : ''}}"/>`);
|
qweb.addTemplate("test", `<div class="hello" t-attf-class="{{value ? 'world' : ''}}"/>`);
|
||||||
const result = renderToString(qweb, "test", { value: true });
|
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>');
|
qweb.addTemplate("caller", '<div><t t-call="_basic-callee"/></div>');
|
||||||
const expected = "<div><span>ok</span></div>";
|
const expected = "<div><span>ok</span></div>";
|
||||||
expect(renderToString(qweb, "caller")).toBe(expected);
|
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", () => {
|
test("basic caller, no parent node", () => {
|
||||||
@@ -742,7 +755,7 @@ describe("t-call (template calling", () => {
|
|||||||
qweb.addTemplate("caller", '<t t-call="_basic-callee"/>');
|
qweb.addTemplate("caller", '<t t-call="_basic-callee"/>');
|
||||||
const expected = "<div>ok</div>";
|
const expected = "<div>ok</div>";
|
||||||
expect(renderToString(qweb, "caller")).toBe(expected);
|
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", () => {
|
test("t-call with t-if", () => {
|
||||||
@@ -750,13 +763,14 @@ describe("t-call (template calling", () => {
|
|||||||
qweb.addTemplate("caller", '<div><t t-if="flag" t-call="sub"/></div>');
|
qweb.addTemplate("caller", '<div><t t-if="flag" t-call="sub"/></div>');
|
||||||
const expected = "<div><span>ok</span></div>";
|
const expected = "<div><span>ok</span></div>";
|
||||||
expect(renderToString(qweb, "caller", { flag: true })).toBe(expected);
|
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", () => {
|
test("t-call allowed on a non t node", () => {
|
||||||
qweb.addTemplate("_basic-callee", "<t>ok</t>");
|
qweb.addTemplate("_basic-callee", "<span>ok</span>");
|
||||||
qweb.addTemplate("caller", '<div t-call="_basic-callee"/>');
|
qweb.addTemplate("caller", '<div t-call="_basic-callee"/>');
|
||||||
expect(() => renderToString(qweb, "caller")).toThrow("Invalid tag");
|
const expected = "<div><span>ok</span></div>";
|
||||||
|
expect(renderToString(qweb, "caller")).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("with unused body", () => {
|
test("with unused body", () => {
|
||||||
@@ -885,7 +899,8 @@ describe("t-call (template calling", () => {
|
|||||||
`);
|
`);
|
||||||
const expected = "<div><span>hey</span></div>";
|
const expected = "<div><span>hey</span></div>";
|
||||||
expect(renderToString(qweb, "recursive")).toBe(expected);
|
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();
|
expect(recursiveFn.toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -913,7 +928,8 @@ describe("t-call (template calling", () => {
|
|||||||
expect(renderToString(qweb, "Parent", { root }, { fiber: { vars: {}, scope: {} } })).toBe(
|
expect(renderToString(qweb, "Parent", { root }, { fiber: { vars: {}, scope: {} } })).toBe(
|
||||||
expected
|
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();
|
expect(recursiveFn.toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -940,7 +956,41 @@ describe("t-call (template calling", () => {
|
|||||||
const expected =
|
const expected =
|
||||||
"<div><div><p>a</p><div><p>b</p><div><p>d</p></div></div><div><p>c</p></div></div></div>";
|
"<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);
|
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();
|
expect(recursiveFn.toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1012,6 +1062,32 @@ describe("t-call (template calling", () => {
|
|||||||
const context = { list: [{ val: 1 }, { val: 2 }, { val: 3 }] };
|
const context = { list: [{ val: 1 }, { val: 2 }, { val: 3 }] };
|
||||||
expect(trim(renderToString(qweb, "wrapper", context))).toBe(expected);
|
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>");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-call with t-set inside and body text content", () => {
|
||||||
|
qweb.addTemplate("sub", `<p><t t-esc="val"/></p>`);
|
||||||
|
qweb.addTemplate(
|
||||||
|
"main",
|
||||||
|
`
|
||||||
|
<div>
|
||||||
|
<t t-call="sub">
|
||||||
|
<t t-set="val">yip yip</t>
|
||||||
|
</t>
|
||||||
|
</div>`
|
||||||
|
);
|
||||||
|
const expected = "<div><p>yip yip</p></div>";
|
||||||
|
expect(renderToString(qweb, "main")).toBe(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("foreach", () => {
|
describe("foreach", () => {
|
||||||
@@ -1102,6 +1178,63 @@ describe("foreach", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("t-call without body in t-foreach in t-foreach", () => {
|
||||||
|
qweb.addTemplate(
|
||||||
|
"test_called",
|
||||||
|
`<t>
|
||||||
|
<t t-set="c" t-value="'x' + '_' + a + '_'+ b" />
|
||||||
|
[<t t-esc="a" />]
|
||||||
|
[<t t-esc="b" />]
|
||||||
|
[<t t-esc="c" />]
|
||||||
|
</t>`
|
||||||
|
);
|
||||||
|
qweb.addTemplate(
|
||||||
|
"test",
|
||||||
|
`<div>
|
||||||
|
<t t-foreach="numbers" t-as="a">
|
||||||
|
<t t-foreach="letters" t-as="b">
|
||||||
|
<t t-call="test_called" />
|
||||||
|
</t>
|
||||||
|
<span t-esc="c"/>
|
||||||
|
</t>
|
||||||
|
<span>[<t t-esc="a" />][<t t-esc="b" />][<t t-esc="c" />]</span>
|
||||||
|
</div>`
|
||||||
|
);
|
||||||
|
const context = { numbers: [1, 2, 3], letters: ["a", "b"] };
|
||||||
|
expect(renderToString(qweb, "test", context)).toBe(
|
||||||
|
"<div> [1] [a] [x_1_a] [1] [b] [x_1_b] <span></span> [2] [a] [x_2_a] [2] [b] [x_2_b] <span></span> [3] [a] [x_3_a] [3] [b] [x_3_b] <span></span><span>[][][]</span></div>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-call with body in t-foreach in t-foreach", () => {
|
||||||
|
qweb.addTemplate(
|
||||||
|
"test_called",
|
||||||
|
`<t>
|
||||||
|
[<t t-esc="a" />]
|
||||||
|
[<t t-esc="b" />]
|
||||||
|
[<t t-esc="c" />]
|
||||||
|
</t>`
|
||||||
|
);
|
||||||
|
qweb.addTemplate(
|
||||||
|
"test",
|
||||||
|
`<div>
|
||||||
|
<t t-foreach="numbers" t-as="a">
|
||||||
|
<t t-foreach="letters" t-as="b">
|
||||||
|
<t t-call="test_called" >
|
||||||
|
<t t-set="c" t-value="'x' + '_' + a + '_'+ b" />
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
<span t-esc="c"/>
|
||||||
|
</t>
|
||||||
|
<span>[<t t-esc="a" />][<t t-esc="b" />][<t t-esc="c" />]</span>
|
||||||
|
</div>`
|
||||||
|
);
|
||||||
|
const context = { numbers: [1, 2, 3], letters: ["a", "b"] };
|
||||||
|
expect(renderToString(qweb, "test", context)).toBe(
|
||||||
|
"<div> [1] [a] [x_1_a] [1] [b] [x_1_b] <span></span> [2] [a] [x_2_a] [2] [b] [x_2_b] <span></span> [3] [a] [x_3_a] [3] [b] [x_3_b] <span></span><span>[][][]</span></div>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("throws error if invalid loop expression", () => {
|
test("throws error if invalid loop expression", () => {
|
||||||
qweb.addTemplate(
|
qweb.addTemplate(
|
||||||
"test",
|
"test",
|
||||||
@@ -1130,6 +1263,44 @@ describe("foreach", () => {
|
|||||||
);
|
);
|
||||||
console.warn = consoleWarn;
|
console.warn = consoleWarn;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('t-foreach supports custom iterators', () => {
|
||||||
|
qweb.addTemplate(
|
||||||
|
"test",
|
||||||
|
`
|
||||||
|
<div>
|
||||||
|
<t t-foreach="iterator" t-as="item">
|
||||||
|
<t t-esc="item"/>
|
||||||
|
</t>
|
||||||
|
</div>`
|
||||||
|
);
|
||||||
|
|
||||||
|
let iterator: any = [1, 2, 3];
|
||||||
|
let result = trim(renderToString(qweb, "test", { iterator } ));
|
||||||
|
let expected = `<div>123</div>`;
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
|
||||||
|
iterator = { 1: 1, 2: 2, 3:3 };
|
||||||
|
result = trim(renderToString(qweb, "test", { iterator } ));
|
||||||
|
expected = `<div>123</div>`;
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
|
||||||
|
iterator = new Set([1, 2, 3]);
|
||||||
|
result = trim(renderToString(qweb, "test", { iterator } ));
|
||||||
|
expected = `<div>123</div>`;
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
|
||||||
|
iterator = {}
|
||||||
|
iterator[Symbol.iterator] = function* () {
|
||||||
|
yield 1;
|
||||||
|
yield 2;
|
||||||
|
yield 3;
|
||||||
|
}
|
||||||
|
result = trim(renderToString(qweb, "test", { iterator } ));
|
||||||
|
expected = `<div>123</div>`;
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("misc", () => {
|
describe("misc", () => {
|
||||||
@@ -1197,7 +1368,7 @@ describe("t-on", () => {
|
|||||||
{
|
{
|
||||||
add() {
|
add() {
|
||||||
a = 3;
|
a = 3;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1220,7 +1391,7 @@ describe("t-on", () => {
|
|||||||
},
|
},
|
||||||
handleDblClick() {
|
handleDblClick() {
|
||||||
steps.push("dblclick");
|
steps.push("dblclick");
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1240,7 +1411,7 @@ describe("t-on", () => {
|
|||||||
{
|
{
|
||||||
add(n) {
|
add(n) {
|
||||||
a = a + n;
|
a = a + n;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1257,7 +1428,7 @@ describe("t-on", () => {
|
|||||||
{
|
{
|
||||||
add({ val }) {
|
add({ val }) {
|
||||||
a = a + val;
|
a = a + val;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1274,7 +1445,7 @@ describe("t-on", () => {
|
|||||||
{
|
{
|
||||||
doSomething(arg) {
|
doSomething(arg) {
|
||||||
expect(arg).toEqual({});
|
expect(arg).toEqual({});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1290,7 +1461,7 @@ describe("t-on", () => {
|
|||||||
{
|
{
|
||||||
doSomething(arg) {
|
doSomething(arg) {
|
||||||
expect(arg).toEqual({});
|
expect(arg).toEqual({});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1312,7 +1483,7 @@ describe("t-on", () => {
|
|||||||
{
|
{
|
||||||
activate(action) {
|
activate(action) {
|
||||||
expect(action).toBe("someval");
|
expect(action).toBe("someval");
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1325,7 +1496,7 @@ describe("t-on", () => {
|
|||||||
let owner = {
|
let owner = {
|
||||||
add() {
|
add() {
|
||||||
expect(this).toBe(owner);
|
expect(this).toBe(owner);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||||
(<HTMLElement>node).click();
|
(<HTMLElement>node).click();
|
||||||
@@ -1335,8 +1506,8 @@ describe("t-on", () => {
|
|||||||
qweb.addTemplate("test", `<button t-on-click="state.counter++">Click</button>`);
|
qweb.addTemplate("test", `<button t-on-click="state.counter++">Click</button>`);
|
||||||
let owner = {
|
let owner = {
|
||||||
state: {
|
state: {
|
||||||
counter: 0
|
counter: 0,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||||
expect(owner.state.counter).toBe(0);
|
expect(owner.state.counter).toBe(0);
|
||||||
@@ -1349,10 +1520,10 @@ describe("t-on", () => {
|
|||||||
let owner = {
|
let owner = {
|
||||||
state: {
|
state: {
|
||||||
counter: 0,
|
counter: 0,
|
||||||
incrementCounter: inc => {
|
incrementCounter: (inc) => {
|
||||||
owner.state.counter += inc;
|
owner.state.counter += inc;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||||
expect(owner.state.counter).toBe(0);
|
expect(owner.state.counter).toBe(0);
|
||||||
@@ -1364,8 +1535,8 @@ describe("t-on", () => {
|
|||||||
qweb.addTemplate("test", `<button t-on-click="state.flag = !state.flag">Toggle</button>`);
|
qweb.addTemplate("test", `<button t-on-click="state.flag = !state.flag">Toggle</button>`);
|
||||||
let owner = {
|
let owner = {
|
||||||
state: {
|
state: {
|
||||||
flag: true
|
flag: true,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||||
expect(owner.state.flag).toBe(true);
|
expect(owner.state.flag).toBe(true);
|
||||||
@@ -1382,8 +1553,8 @@ describe("t-on", () => {
|
|||||||
return n + 1;
|
return n + 1;
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
n: 11
|
n: 11,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||||
expect(owner.state.n).toBe(11);
|
expect(owner.state.n).toBe(11);
|
||||||
@@ -1399,7 +1570,7 @@ describe("t-on", () => {
|
|||||||
let owner = {
|
let owner = {
|
||||||
update() {
|
update() {
|
||||||
expect(this).toBe(owner);
|
expect(this).toBe(owner);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const node = renderToDOM(qweb, "main", owner, { handlers: [] });
|
const node = renderToDOM(qweb, "main", owner, { handlers: [] });
|
||||||
@@ -1416,7 +1587,7 @@ describe("t-on", () => {
|
|||||||
expect(this).toBe(owner);
|
expect(this).toBe(owner);
|
||||||
expect(val).toBe(444);
|
expect(val).toBe(444);
|
||||||
},
|
},
|
||||||
value: 444
|
value: 444,
|
||||||
};
|
};
|
||||||
|
|
||||||
const node = renderToDOM(qweb, "main", owner, { handlers: [] });
|
const node = renderToDOM(qweb, "main", owner, { handlers: [] });
|
||||||
@@ -1445,7 +1616,7 @@ describe("t-on", () => {
|
|||||||
onClickPreventedAndStopped(e) {
|
onClickPreventedAndStopped(e) {
|
||||||
expect(e.defaultPrevented).toBe(true);
|
expect(e.defaultPrevented).toBe(true);
|
||||||
expect(e.cancelBubble).toBe(true);
|
expect(e.cancelBubble).toBe(true);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||||
|
|
||||||
@@ -1471,7 +1642,7 @@ describe("t-on", () => {
|
|||||||
},
|
},
|
||||||
onClickSelf(e) {
|
onClickSelf(e) {
|
||||||
steps.push("onClickSelf");
|
steps.push("onClickSelf");
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||||
|
|
||||||
@@ -1495,10 +1666,10 @@ describe("t-on", () => {
|
|||||||
);
|
);
|
||||||
let steps: boolean[] = [];
|
let steps: boolean[] = [];
|
||||||
let owner = {
|
let owner = {
|
||||||
onClick() {}
|
onClick() {},
|
||||||
};
|
};
|
||||||
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||||
(<HTMLElement>node).addEventListener("click", function(e) {
|
(<HTMLElement>node).addEventListener("click", function (e) {
|
||||||
steps.push(e.defaultPrevented);
|
steps.push(e.defaultPrevented);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1520,10 +1691,10 @@ describe("t-on", () => {
|
|||||||
);
|
);
|
||||||
let steps: boolean[] = [];
|
let steps: boolean[] = [];
|
||||||
let owner = {
|
let owner = {
|
||||||
onClick() {}
|
onClick() {},
|
||||||
};
|
};
|
||||||
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||||
(<HTMLElement>node).addEventListener("click", function(e) {
|
(<HTMLElement>node).addEventListener("click", function (e) {
|
||||||
steps.push(e.defaultPrevented);
|
steps.push(e.defaultPrevented);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1551,13 +1722,13 @@ describe("t-on", () => {
|
|||||||
const owner = {
|
const owner = {
|
||||||
projects: [
|
projects: [
|
||||||
{ id: 1, name: "Project 1" },
|
{ id: 1, name: "Project 1" },
|
||||||
{ id: 2, name: "Project 2" }
|
{ id: 2, name: "Project 2" },
|
||||||
],
|
],
|
||||||
|
|
||||||
onEdit(projectId, ev) {
|
onEdit(projectId, ev) {
|
||||||
expect(ev.defaultPrevented).toBe(true);
|
expect(ev.defaultPrevented).toBe(true);
|
||||||
steps.push(projectId);
|
steps.push(projectId);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const node = <HTMLElement>renderToDOM(qweb, "test", owner, { handlers: [] });
|
const node = <HTMLElement>renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||||
@@ -1582,7 +1753,7 @@ describe("t-on", () => {
|
|||||||
);
|
);
|
||||||
const node = renderToDOM(qweb, "test", {}, { handlers: [] });
|
const node = renderToDOM(qweb, "test", {}, { handlers: [] });
|
||||||
|
|
||||||
node.addEventListener("click", e => {
|
node.addEventListener("click", (e) => {
|
||||||
expect(e.defaultPrevented).toBe(true);
|
expect(e.defaultPrevented).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1598,7 +1769,7 @@ describe("t-on", () => {
|
|||||||
text: "Click here",
|
text: "Click here",
|
||||||
onClick() {
|
onClick() {
|
||||||
steps.push("onClick");
|
steps.push("onClick");
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const node = <HTMLElement>renderToDOM(qweb, "test", owner, { handlers: [] });
|
const node = <HTMLElement>renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||||
@@ -1617,7 +1788,7 @@ describe("t-on", () => {
|
|||||||
html: "Click <b>here</b>",
|
html: "Click <b>here</b>",
|
||||||
onClick() {
|
onClick() {
|
||||||
steps.push("onClick");
|
steps.push("onClick");
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const node = <HTMLElement>renderToDOM(qweb, "test", owner, { handlers: [] });
|
const node = <HTMLElement>renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||||
@@ -1644,7 +1815,7 @@ describe("t-on", () => {
|
|||||||
},
|
},
|
||||||
doSomething() {
|
doSomething() {
|
||||||
steps.push("normal");
|
steps.push("normal");
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
const node = renderToDOM(qweb, "test", owner, { handlers: [] });
|
||||||
|
|
||||||
@@ -1717,7 +1888,7 @@ describe("loading templates", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("special cases for some boolean html attributes/properties", () => {
|
describe("special cases for some specific html attributes/properties", () => {
|
||||||
test("input type= checkbox, with t-att-checked", () => {
|
test("input type= checkbox, with t-att-checked", () => {
|
||||||
qweb.addTemplate("test", `<input type="checkbox" t-att-checked="flag"/>`);
|
qweb.addTemplate("test", `<input type="checkbox" t-att-checked="flag"/>`);
|
||||||
const result = renderToString(qweb, "test", { flag: true });
|
const result = renderToString(qweb, "test", { flag: true });
|
||||||
@@ -1744,6 +1915,33 @@ describe("special cases for some boolean html attributes/properties", () => {
|
|||||||
);
|
);
|
||||||
renderToString(qweb, "test", { flag: true });
|
renderToString(qweb, "test", { flag: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("input with t-att-value", () => {
|
||||||
|
// render input with initial value
|
||||||
|
qweb.addTemplate("test", `<input t-att-value="v"/>`);
|
||||||
|
const vnode1 = qweb.render("test", { v: "zucchini" });
|
||||||
|
const vnode2 = patch(document.createElement("input"), vnode1);
|
||||||
|
let elm = vnode2.elm as HTMLInputElement;
|
||||||
|
expect(elm.value).toBe("zucchini");
|
||||||
|
|
||||||
|
// change value manually in input, to simulate user input
|
||||||
|
elm.value = "tomato";
|
||||||
|
expect(elm.value).toBe("tomato");
|
||||||
|
|
||||||
|
// rerender with a different value, and patch actual dom, to check that
|
||||||
|
// input value was properly reset by owl
|
||||||
|
const vnode3 = qweb.render("test", { v: "potato" });
|
||||||
|
patch(vnode2, vnode3);
|
||||||
|
expect(elm.value).toBe("potato");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("input of type checkbox with t-att-indeterminate", () => {
|
||||||
|
qweb.addTemplate("test", `<input type="checkbox" t-att-indeterminate="v"/>`);
|
||||||
|
const vnode1 = qweb.render("test", { v: true });
|
||||||
|
const vnode2 = patch(document.createElement("input"), vnode1);
|
||||||
|
let elm = vnode2.elm as HTMLInputElement;
|
||||||
|
expect(elm.indeterminate).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("whitespace handling", () => {
|
describe("whitespace handling", () => {
|
||||||
@@ -1808,7 +2006,7 @@ describe("t-key", () => {
|
|||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
renderToString(qweb, "test", {
|
renderToString(qweb, "test", {
|
||||||
beers: [{ id: 12, name: "Chimay Rouge" }]
|
beers: [{ id: 12, name: "Chimay Rouge" }],
|
||||||
})
|
})
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -1921,9 +2119,9 @@ describe("properly support svg", () => {
|
|||||||
describe("translation support", () => {
|
describe("translation support", () => {
|
||||||
test("can translate node content", () => {
|
test("can translate node content", () => {
|
||||||
const translations = {
|
const translations = {
|
||||||
word: "mot"
|
word: "mot",
|
||||||
};
|
};
|
||||||
const translateFn = expr => translations[expr] || expr;
|
const translateFn = (expr) => translations[expr] || expr;
|
||||||
const qweb = new QWeb({ translateFn });
|
const qweb = new QWeb({ translateFn });
|
||||||
qweb.addTemplate("test", "<div>word</div>");
|
qweb.addTemplate("test", "<div>word</div>");
|
||||||
expect(renderToString(qweb, "test")).toBe("<div>mot</div>");
|
expect(renderToString(qweb, "test")).toBe("<div>mot</div>");
|
||||||
@@ -1931,9 +2129,9 @@ describe("translation support", () => {
|
|||||||
|
|
||||||
test("does not translate node content if disabled", () => {
|
test("does not translate node content if disabled", () => {
|
||||||
const translations = {
|
const translations = {
|
||||||
word: "mot"
|
word: "mot",
|
||||||
};
|
};
|
||||||
const translateFn = expr => translations[expr] || expr;
|
const translateFn = (expr) => translations[expr] || expr;
|
||||||
const qweb = new QWeb({ translateFn });
|
const qweb = new QWeb({ translateFn });
|
||||||
qweb.addTemplate(
|
qweb.addTemplate(
|
||||||
"test",
|
"test",
|
||||||
@@ -1948,9 +2146,9 @@ describe("translation support", () => {
|
|||||||
|
|
||||||
test("some attributes are translated", () => {
|
test("some attributes are translated", () => {
|
||||||
const translations = {
|
const translations = {
|
||||||
word: "mot"
|
word: "mot",
|
||||||
};
|
};
|
||||||
const translateFn = expr => translations[expr] || expr;
|
const translateFn = (expr) => translations[expr] || expr;
|
||||||
const qweb = new QWeb({ translateFn });
|
const qweb = new QWeb({ translateFn });
|
||||||
qweb.addTemplate(
|
qweb.addTemplate(
|
||||||
"test",
|
"test",
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ describe("tokenizer", () => {
|
|||||||
|
|
||||||
expect(tokenize("{}")).toEqual([
|
expect(tokenize("{}")).toEqual([
|
||||||
{ type: "LEFT_BRACE", value: "{" },
|
{ type: "LEFT_BRACE", value: "{" },
|
||||||
{ type: "RIGHT_BRACE", value: "}" }
|
{ type: "RIGHT_BRACE", value: "}" },
|
||||||
]);
|
]);
|
||||||
expect(tokenize("{ }}")).toEqual([
|
expect(tokenize("{ }}")).toEqual([
|
||||||
{ type: "LEFT_BRACE", value: "{" },
|
{ type: "LEFT_BRACE", value: "{" },
|
||||||
{ type: "RIGHT_BRACE", value: "}" },
|
{ type: "RIGHT_BRACE", value: "}" },
|
||||||
{ type: "RIGHT_BRACE", value: "}" }
|
{ type: "RIGHT_BRACE", value: "}" },
|
||||||
]);
|
]);
|
||||||
expect(tokenize("a")).toEqual([{ type: "SYMBOL", value: "a" }]);
|
expect(tokenize("a")).toEqual([{ type: "SYMBOL", value: "a" }]);
|
||||||
expect(tokenize("true")).toEqual([{ type: "SYMBOL", value: "true" }]);
|
expect(tokenize("true")).toEqual([{ type: "SYMBOL", value: "true" }]);
|
||||||
@@ -25,15 +25,15 @@ describe("tokenizer", () => {
|
|||||||
{ type: "SYMBOL", value: "a" },
|
{ type: "SYMBOL", value: "a" },
|
||||||
{ type: "COLON", value: ":" },
|
{ type: "COLON", value: ":" },
|
||||||
{ type: "VALUE", value: "2" },
|
{ type: "VALUE", value: "2" },
|
||||||
{ type: "RIGHT_BRACE", value: "}" }
|
{ type: "RIGHT_BRACE", value: "}" },
|
||||||
]);
|
]);
|
||||||
expect(tokenize("a,")).toEqual([
|
expect(tokenize("a,")).toEqual([
|
||||||
{ type: "SYMBOL", value: "a" },
|
{ type: "SYMBOL", value: "a" },
|
||||||
{ type: "COMMA", value: "," }
|
{ type: "COMMA", value: "," },
|
||||||
]);
|
]);
|
||||||
expect(tokenize("][")).toEqual([
|
expect(tokenize("][")).toEqual([
|
||||||
{ type: "RIGHT_BRACKET", value: "]" },
|
{ 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: "!==" },
|
{ type: "OPERATOR", value: "!==" },
|
||||||
{ type: "OPERATOR", value: "!=" }
|
{ type: "OPERATOR", value: "!=" },
|
||||||
]);
|
]);
|
||||||
expect(tokenize("typeof a")).toEqual([
|
expect(tokenize("typeof a")).toEqual([
|
||||||
{ type: "OPERATOR", value: "typeof " },
|
{ type: "OPERATOR", value: "typeof " },
|
||||||
{ type: "SYMBOL", value: "a" }
|
{ type: "SYMBOL", value: "a" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(tokenize("a...1")).toEqual([
|
expect(tokenize("a...1")).toEqual([
|
||||||
{ type: "SYMBOL", value: "a" },
|
{ type: "SYMBOL", value: "a" },
|
||||||
{ type: "OPERATOR", value: "..." },
|
{ type: "OPERATOR", value: "..." },
|
||||||
{ type: "VALUE", value: "1" }
|
{ type: "VALUE", value: "1" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(tokenize("a in b")).toEqual([
|
expect(tokenize("a in b")).toEqual([
|
||||||
{ type: "SYMBOL", value: "a" },
|
{ type: "SYMBOL", value: "a" },
|
||||||
{ type: "OPERATOR", value: "in " },
|
{ 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"' }]);
|
||||||
expect(tokenize('"hello ged"}')).toEqual([
|
expect(tokenize('"hello ged"}')).toEqual([
|
||||||
{ type: "VALUE", value: '"hello ged"' },
|
{ type: "VALUE", value: '"hello ged"' },
|
||||||
{ type: "RIGHT_BRACE", value: "}" }
|
{ type: "RIGHT_BRACE", value: "}" },
|
||||||
]);
|
]);
|
||||||
expect(tokenize('"hello \\"ged\\""')).toEqual([{ type: "VALUE", value: '"hello \\"ged\\""' }]);
|
expect(tokenize('"hello \\"ged\\""')).toEqual([{ type: "VALUE", value: '"hello \\"ged\\""' }]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ describe("Link component", () => {
|
|||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ name: "about", path: "/about" },
|
{ name: "about", path: "/about" },
|
||||||
{ name: "users", path: "/users" }
|
{ name: "users", path: "/users" },
|
||||||
];
|
];
|
||||||
|
|
||||||
router = new TestRouter(env, routes, { mode: "history" });
|
router = new TestRouter(env, routes, { mode: "history" });
|
||||||
@@ -71,7 +71,7 @@ describe("Link component", () => {
|
|||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ name: "about", path: "/about" },
|
{ name: "about", path: "/about" },
|
||||||
{ name: "users", path: "/users" }
|
{ name: "users", path: "/users" },
|
||||||
];
|
];
|
||||||
|
|
||||||
router = new TestRouter(env, routes, { mode: "history" });
|
router = new TestRouter(env, routes, { mode: "history" });
|
||||||
@@ -81,7 +81,7 @@ describe("Link component", () => {
|
|||||||
|
|
||||||
expect(window.location.pathname).toBe("/users");
|
expect(window.location.pathname).toBe("/users");
|
||||||
var evt = new MouseEvent("click", {
|
var evt = new MouseEvent("click", {
|
||||||
button: 1
|
button: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
fixture.querySelector("a")!.dispatchEvent(evt);
|
fixture.querySelector("a")!.dispatchEvent(evt);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ describe("RouteComponent", () => {
|
|||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ name: "about", path: "/about", component: About },
|
{ 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" });
|
router = new TestRouter(env, routes, { mode: "history" });
|
||||||
|
|||||||
+10
-10
@@ -21,7 +21,7 @@ describe("router miscellaneous", () => {
|
|||||||
test("validate routes shape", () => {
|
test("validate routes shape", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
router = new TestRouter(env, [
|
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"}`);
|
}).toThrow(`Invalid destination: {"abc":"hey"}`);
|
||||||
});
|
});
|
||||||
@@ -133,7 +133,7 @@ describe("getRouteParams", () => {
|
|||||||
test("match some parameterized routes", () => {
|
test("match some parameterized routes", () => {
|
||||||
router = new TestRouter(env, []);
|
router = new TestRouter(env, []);
|
||||||
expect(router["getRouteParams"]({ path: "/invoices/{{id}}" } as Route, "/invoices/3")).toEqual({
|
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" });
|
router = new TestRouter(env, [], { mode: "hash" });
|
||||||
expect(router["getRouteParams"]({ path: "/invoices/{{id}}" } as Route, "#/invoices/3")).toEqual(
|
expect(router["getRouteParams"]({ path: "/invoices/{{id}}" } as Route, "#/invoices/3")).toEqual(
|
||||||
{
|
{
|
||||||
id: "3"
|
id: "3",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -151,7 +151,7 @@ describe("getRouteParams", () => {
|
|||||||
expect(
|
expect(
|
||||||
router["getRouteParams"]({ path: "/invoices/{{id.number}}" } as Route, "/invoices/3")
|
router["getRouteParams"]({ path: "/invoices/{{id.number}}" } as Route, "/invoices/3")
|
||||||
).toEqual({
|
).toEqual({
|
||||||
id: 3
|
id: 3,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ describe("getRouteParams", () => {
|
|||||||
expect(
|
expect(
|
||||||
router["getRouteParams"]({ path: "/invoices/{{id.number}}" } as Route, "#/invoices/3")
|
router["getRouteParams"]({ path: "/invoices/{{id.number}}" } as Route, "#/invoices/3")
|
||||||
).toEqual({
|
).toEqual({
|
||||||
id: 3
|
id: 3,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -169,7 +169,7 @@ describe("redirect", () => {
|
|||||||
test("can redirect to other route", async () => {
|
test("can redirect to other route", async () => {
|
||||||
router = new TestRouter(env, [
|
router = new TestRouter(env, [
|
||||||
{ name: "routea", path: "/some/path" },
|
{ 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();
|
await router.start();
|
||||||
@@ -182,7 +182,7 @@ describe("redirect", () => {
|
|||||||
test("can redirect to other path", async () => {
|
test("can redirect to other path", async () => {
|
||||||
router = new TestRouter(env, [
|
router = new TestRouter(env, [
|
||||||
{ name: "routea", path: "/some/path" },
|
{ 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);
|
router.navigate = jest.fn(router.navigate);
|
||||||
|
|
||||||
@@ -214,7 +214,7 @@ describe("beforeRouteEnter", () => {
|
|||||||
const guard = jest.fn(() => false);
|
const guard = jest.fn(() => false);
|
||||||
router = new TestRouter(env, [
|
router = new TestRouter(env, [
|
||||||
{ name: "routea", path: "/some/patha" },
|
{ name: "routea", path: "/some/patha" },
|
||||||
{ name: "routeb", path: "/some/pathb", beforeRouteEnter: guard }
|
{ name: "routeb", path: "/some/pathb", beforeRouteEnter: guard },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await router.start();
|
await router.start();
|
||||||
@@ -235,7 +235,7 @@ describe("beforeRouteEnter", () => {
|
|||||||
router = new TestRouter(env, [
|
router = new TestRouter(env, [
|
||||||
{ name: "routea", path: "/some/patha" },
|
{ name: "routea", path: "/some/patha" },
|
||||||
{ name: "routeb", path: "/some/pathb", beforeRouteEnter: guard },
|
{ name: "routeb", path: "/some/pathb", beforeRouteEnter: guard },
|
||||||
{ name: "routec", path: "/some/pathc" }
|
{ name: "routec", path: "/some/pathc" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await router.start();
|
await router.start();
|
||||||
@@ -255,7 +255,7 @@ describe("beforeRouteEnter", () => {
|
|||||||
});
|
});
|
||||||
router = new TestRouter(env, [
|
router = new TestRouter(env, [
|
||||||
{ name: "landing", path: "/", beforeRouteEnter: guard },
|
{ name: "landing", path: "/", beforeRouteEnter: guard },
|
||||||
{ name: "otherroute", path: "/some/other/route" }
|
{ name: "otherroute", path: "/some/other/route" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(window.location.pathname).toBe("/");
|
expect(window.location.pathname).toBe("/");
|
||||||
|
|||||||
+28
-28
@@ -8,7 +8,7 @@ describe("basic use", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
inc({ state }, delta) {
|
inc({ state }, delta) {
|
||||||
state.n += delta;
|
state.n += delta;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ describe("basic use", () => {
|
|||||||
state.n1 += delta1;
|
state.n1 += delta1;
|
||||||
state.n2 += delta2;
|
state.n2 += delta2;
|
||||||
state.n3 += delta3;
|
state.n3 += delta3;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ describe("basic use", () => {
|
|||||||
},
|
},
|
||||||
inc100({ dispatch }) {
|
inc100({ dispatch }) {
|
||||||
dispatch("inc", 100);
|
dispatch("inc", 100);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ describe("basic use", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
inc({ state }) {
|
inc({ state }) {
|
||||||
return ++state.n;
|
return ++state.n;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ describe("basic use", () => {
|
|||||||
async setTo10({ dispatch }) {
|
async setTo10({ dispatch }) {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
dispatch("setN", 10);
|
dispatch("setN", 10);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ describe("basic use", () => {
|
|||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
dispatch("setN", 10);
|
dispatch("setN", 10);
|
||||||
return 5;
|
return 5;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ describe("basic use", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
someaction({ env }) {
|
someaction({ env }) {
|
||||||
expect(env).toBe(someEnv);
|
expect(env).toBe(someEnv);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state: {}, actions, env: someEnv });
|
const store = new Store({ state: {}, actions, env: someEnv });
|
||||||
|
|
||||||
@@ -145,15 +145,15 @@ describe("basic use", () => {
|
|||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "bertinchamps",
|
name: "bertinchamps",
|
||||||
tasterID: 1
|
tasterID: 1,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
tasters: {
|
tasters: {
|
||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "aaron"
|
name: "aaron",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const getters: { [key: string]: Getter } = {
|
const getters: { [key: string]: Getter } = {
|
||||||
beerTasterName({ state }, beerID) {
|
beerTasterName({ state }, beerID) {
|
||||||
@@ -161,7 +161,7 @@ describe("basic use", () => {
|
|||||||
},
|
},
|
||||||
bestBeerName({ state }) {
|
bestBeerName({ state }) {
|
||||||
return state.beers[1].name;
|
return state.beers[1].name;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions: {}, getters });
|
const store = new Store({ state, actions: {}, getters });
|
||||||
expect(store.getters).toBeDefined();
|
expect(store.getters).toBeDefined();
|
||||||
@@ -176,15 +176,15 @@ describe("basic use", () => {
|
|||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "bertinchamps",
|
name: "bertinchamps",
|
||||||
tasterID: 1
|
tasterID: 1,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
tasters: {
|
tasters: {
|
||||||
1: {
|
1: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "aaron"
|
name: "aaron",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const getters = {
|
const getters = {
|
||||||
beerTasterName({ state }, beerID) {
|
beerTasterName({ state }, beerID) {
|
||||||
@@ -192,14 +192,14 @@ describe("basic use", () => {
|
|||||||
},
|
},
|
||||||
bestBeerName({ state }) {
|
bestBeerName({ state }) {
|
||||||
return state.beers[1].name;
|
return state.beers[1].name;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const actions = {
|
const actions = {
|
||||||
action({ getters }) {
|
action({ getters }) {
|
||||||
expect(getters).toBeDefined();
|
expect(getters).toBeDefined();
|
||||||
expect(getters.bestBeerName()).toBe("bertinchamps");
|
expect(getters.bestBeerName()).toBe("bertinchamps");
|
||||||
expect(getters.beerTasterName(1)).toBe("aaron");
|
expect(getters.beerTasterName(1)).toBe("aaron");
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions, getters });
|
const store = new Store({ state, actions, getters });
|
||||||
store.dispatch("action");
|
store.dispatch("action");
|
||||||
@@ -215,7 +215,7 @@ describe("basic use", () => {
|
|||||||
},
|
},
|
||||||
c({}, i) {
|
c({}, i) {
|
||||||
return `c${i}`;
|
return `c${i}`;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ getters, state: {}, actions: {} });
|
const store = new Store({ getters, state: {}, actions: {} });
|
||||||
|
|
||||||
@@ -226,7 +226,7 @@ describe("basic use", () => {
|
|||||||
describe("advanced state properties", () => {
|
describe("advanced state properties", () => {
|
||||||
test("state in the store is reference equal after mutation", async () => {
|
test("state in the store is reference equal after mutation", async () => {
|
||||||
const actions = {
|
const actions = {
|
||||||
donothing() {}
|
donothing() {},
|
||||||
};
|
};
|
||||||
const store = new Store({ state: {}, actions });
|
const store = new Store({ state: {}, actions });
|
||||||
const state = store.state;
|
const state = store.state;
|
||||||
@@ -242,7 +242,7 @@ describe("advanced state properties", () => {
|
|||||||
expect(state.a.length).toBe(3);
|
expect(state.a.length).toBe(3);
|
||||||
const l = state.a.push(53);
|
const l = state.a.push(53);
|
||||||
expect(l).toBe(4);
|
expect(l).toBe(4);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
store.dispatch("m");
|
store.dispatch("m");
|
||||||
@@ -253,11 +253,11 @@ describe("advanced state properties", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
dosomething({ state }) {
|
dosomething({ state }) {
|
||||||
Object.assign(state.westmalle, { a: 3, b: 4 });
|
Object.assign(state.westmalle, { a: 3, b: 4 });
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({
|
const store = new Store({
|
||||||
state: { westmalle: { a: 1, b: 2 } },
|
state: { westmalle: { a: 1, b: 2 } },
|
||||||
actions
|
actions,
|
||||||
});
|
});
|
||||||
store.dispatch("dosomething");
|
store.dispatch("dosomething");
|
||||||
expect(store.state.westmalle).toEqual({ a: 3, b: 4 });
|
expect(store.state.westmalle).toEqual({ a: 3, b: 4 });
|
||||||
@@ -267,7 +267,7 @@ describe("advanced state properties", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
inc({ state }) {
|
inc({ state }) {
|
||||||
state.counter++;
|
state.counter++;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const state = { counter: 0 };
|
const state = { counter: 0 };
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
@@ -284,7 +284,7 @@ describe("updates triggered by the store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
inc({ state }, delta) {
|
inc({ state }, delta) {
|
||||||
state.n += delta;
|
state.n += delta;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
store.on("update", null, () => updateCounter++);
|
store.on("update", null, () => updateCounter++);
|
||||||
@@ -308,7 +308,7 @@ describe("updates triggered by the store", () => {
|
|||||||
noop2({ state }) {
|
noop2({ state }) {
|
||||||
const val = state.n;
|
const val = state.n;
|
||||||
state.n = val;
|
state.n = val;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
store.on("update", null, () => updateCounter++);
|
store.on("update", null, () => updateCounter++);
|
||||||
|
|||||||
+93
-93
@@ -1,5 +1,5 @@
|
|||||||
import { Component, Env } from "../src/component/component";
|
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 { useState } from "../src/hooks";
|
||||||
import { xml } from "../src/tags";
|
import { xml } from "../src/tags";
|
||||||
import { shallowEqual } from "../src/utils";
|
import { shallowEqual } from "../src/utils";
|
||||||
@@ -25,7 +25,7 @@ describe("connecting a component to store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
addTodo({ state }, msg) {
|
addTodo({ state }, msg) {
|
||||||
state.todos.push({ msg, id: nextId++ });
|
state.todos.push({ msg, id: nextId++ });
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ describe("connecting a component to store", () => {
|
|||||||
<div>
|
<div>
|
||||||
<span t-foreach="todos" t-key="todo.id" t-as="todo"><t t-esc="todo.msg"/></span>
|
<span t-foreach="todos" t-key="todo.id" t-as="todo"><t t-esc="todo.msg"/></span>
|
||||||
</div>`;
|
</div>`;
|
||||||
todos = useStore(state => state.todos);
|
todos = useStore((state) => state.todos);
|
||||||
}
|
}
|
||||||
|
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
@@ -56,7 +56,7 @@ describe("connecting a component to store", () => {
|
|||||||
},
|
},
|
||||||
setNotNull({ state }) {
|
setNotNull({ state }) {
|
||||||
state.nullValue = "ok";
|
state.nullValue = "ok";
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
@@ -70,15 +70,15 @@ describe("connecting a component to store", () => {
|
|||||||
nullValue: string;
|
nullValue: string;
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.isBoolean = useStore(state => state.isBoolean, {
|
this.isBoolean = useStore((state) => state.isBoolean, {
|
||||||
onUpdate: isBoolean => {
|
onUpdate: (isBoolean) => {
|
||||||
this.isBoolean = isBoolean;
|
this.isBoolean = isBoolean;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
this.nullValue = useStore(state => state.nullValue, {
|
this.nullValue = useStore((state) => state.nullValue, {
|
||||||
onUpdate: nullValue => {
|
onUpdate: (nullValue) => {
|
||||||
this.nullValue = nullValue;
|
this.nullValue = nullValue;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,15 +105,15 @@ describe("connecting a component to store", () => {
|
|||||||
class App extends Component {
|
class App extends Component {
|
||||||
static template = xml`<div t-esc="mapAdd"/>`;
|
static template = xml`<div t-esc="mapAdd"/>`;
|
||||||
storeProps = {
|
storeProps = {
|
||||||
array: useStore(state => {
|
array: useStore((state) => {
|
||||||
if (state.useSmallArray) {
|
if (state.useSmallArray) {
|
||||||
return state.smallerArray;
|
return state.smallerArray;
|
||||||
}
|
}
|
||||||
return state.biggerArray;
|
return state.biggerArray;
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
get mapAdd() {
|
get mapAdd() {
|
||||||
return this.storeProps.array.map(a => {
|
return this.storeProps.array.map((a) => {
|
||||||
return a + 1;
|
return a + 1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ describe("connecting a component to store", () => {
|
|||||||
test("throw error if no store is found", async () => {
|
test("throw error if no store is found", async () => {
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
static template = xml`<div></div>`;
|
static template = xml`<div></div>`;
|
||||||
todos = useStore(state => state.todos);
|
todos = useStore((state) => state.todos);
|
||||||
}
|
}
|
||||||
|
|
||||||
let error;
|
let error;
|
||||||
@@ -153,7 +153,7 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
static template = xml`<div/>`;
|
static template = xml`<div/>`;
|
||||||
storeState = useStore(state => state.a);
|
storeState = useStore((state) => state.a);
|
||||||
}
|
}
|
||||||
|
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
@@ -170,7 +170,7 @@ describe("connecting a component to store", () => {
|
|||||||
doSomething({ state }) {
|
doSomething({ state }) {
|
||||||
state.a = 2;
|
state.a = 2;
|
||||||
state.b = 3;
|
state.b = 3;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
@@ -180,8 +180,8 @@ describe("connecting a component to store", () => {
|
|||||||
<span t-esc="a.value"/>
|
<span t-esc="a.value"/>
|
||||||
<span t-esc="b.value"/>
|
<span t-esc="b.value"/>
|
||||||
</div>`;
|
</div>`;
|
||||||
a = useStore(state => ({ value: state.a }));
|
a = useStore((state) => ({ value: state.a }));
|
||||||
b = useStore(state => ({ value: state.b }));
|
b = useStore((state) => ({ value: state.b }));
|
||||||
}
|
}
|
||||||
App.prototype.__render = jest.fn(App.prototype.__render);
|
App.prototype.__render = jest.fn(App.prototype.__render);
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ describe("connecting a component to store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
addTodo({ state }, msg) {
|
addTodo({ state }, msg) {
|
||||||
state.todos.push({ msg, id: nextId++ });
|
state.todos.push({ msg, id: nextId++ });
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@ describe("connecting a component to store", () => {
|
|||||||
<div>
|
<div>
|
||||||
<span t-foreach="todos" t-key="todo.id" t-as="todo"><t t-esc="todo.msg"/></span>
|
<span t-foreach="todos" t-key="todo.id" t-as="todo"><t t-esc="todo.msg"/></span>
|
||||||
</div>`;
|
</div>`;
|
||||||
todos = useStore(state => state.todos);
|
todos = useStore((state) => state.todos);
|
||||||
}
|
}
|
||||||
App.prototype.__render = jest.fn(App.prototype.__render);
|
App.prototype.__render = jest.fn(App.prototype.__render);
|
||||||
|
|
||||||
@@ -241,13 +241,13 @@ describe("connecting a component to store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
addTodo({ state }, msg) {
|
addTodo({ state }, msg) {
|
||||||
state.todos.push({ msg, id: nextId++ });
|
state.todos.push({ msg, id: nextId++ });
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
static template = xml`<div><t t-esc="nbrTodos.value"/></div>`;
|
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;
|
(<any>env).store = store;
|
||||||
@@ -270,12 +270,12 @@ describe("connecting a component to store", () => {
|
|||||||
},
|
},
|
||||||
incrementA({ state }) {
|
incrementA({ state }) {
|
||||||
state.a++;
|
state.a++;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
static template = xml`<div><t t-esc="nbrTodos.value"/></div>`;
|
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);
|
App.prototype.__render = jest.fn(App.prototype.__render);
|
||||||
|
|
||||||
@@ -303,7 +303,7 @@ describe("connecting a component to store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
addTodo({ state }, msg) {
|
addTodo({ state }, msg) {
|
||||||
state.todos.push({ msg, id: nextId++ });
|
state.todos.push({ msg, id: nextId++ });
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
@@ -312,7 +312,7 @@ describe("connecting a component to store", () => {
|
|||||||
<div>
|
<div>
|
||||||
<span t-foreach="todos" t-key="todo.id" t-as="todo"><t t-esc="todo.msg"/></span>
|
<span t-foreach="todos" t-key="todo.id" t-as="todo"><t t-esc="todo.msg"/></span>
|
||||||
</div>`;
|
</div>`;
|
||||||
todos = useStore(state => state.todos, { store });
|
todos = useStore((state) => state.todos, { store });
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = new App();
|
const app = new App();
|
||||||
@@ -331,8 +331,8 @@ describe("connecting a component to store", () => {
|
|||||||
actions: {
|
actions: {
|
||||||
inc({ state }) {
|
inc({ state }) {
|
||||||
state.value++;
|
state.value++;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
|
|
||||||
@@ -342,7 +342,7 @@ describe("connecting a component to store", () => {
|
|||||||
<button t-on-click="dispatch('inc')">Inc</button>
|
<button t-on-click="dispatch('inc')">Inc</button>
|
||||||
<span><t t-esc="storeState.value"/></span>
|
<span><t t-esc="storeState.value"/></span>
|
||||||
</div>`;
|
</div>`;
|
||||||
storeState = useStore(state => state);
|
storeState = useStore((state) => state);
|
||||||
dispatch = useDispatch();
|
dispatch = useDispatch();
|
||||||
}
|
}
|
||||||
const app = new App();
|
const app = new App();
|
||||||
@@ -360,15 +360,15 @@ describe("connecting a component to store", () => {
|
|||||||
const state = {
|
const state = {
|
||||||
todos: [
|
todos: [
|
||||||
{ id: 1, text: "jupiler" },
|
{ id: 1, text: "jupiler" },
|
||||||
{ id: 2, text: "chimay" }
|
{ id: 2, text: "chimay" },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions: {} });
|
const store = new Store({ state, actions: {} });
|
||||||
|
|
||||||
class TodoItem extends Component {
|
class TodoItem extends Component {
|
||||||
static template = xml`<span><t t-esc="todo.text"/></span>`;
|
static template = xml`<span><t t-esc="todo.text"/></span>`;
|
||||||
todo = useStore((state, props) => {
|
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 = {
|
const actions = {
|
||||||
addTodo({ state }, text) {
|
addTodo({ state }, text) {
|
||||||
state.todos.push({ text, id: nextId++ });
|
state.todos.push({ text, id: nextId++ });
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
|
|
||||||
class TodoItem extends Component {
|
class TodoItem extends Component {
|
||||||
static template = xml`<span><t t-esc="todo.text"/></span>`;
|
static template = xml`<span><t t-esc="todo.text"/></span>`;
|
||||||
todo = useStore((state, props) => {
|
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"/>
|
<TodoItem t-foreach="todos" t-as="todo" id="todo.id" t-key="todo.id"/>
|
||||||
</div>`;
|
</div>`;
|
||||||
static components = { TodoItem };
|
static components = { TodoItem };
|
||||||
todos = useStore(state => state.todos);
|
todos = useStore((state) => state.todos);
|
||||||
}
|
}
|
||||||
|
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
@@ -431,16 +431,16 @@ describe("connecting a component to store", () => {
|
|||||||
importantID: 1,
|
importantID: 1,
|
||||||
todos: [
|
todos: [
|
||||||
{ id: 1, text: "jupiler" },
|
{ id: 1, text: "jupiler" },
|
||||||
{ id: 2, text: "bertinchamps" }
|
{ id: 2, text: "bertinchamps" },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
const getters = {
|
const getters = {
|
||||||
importantTodoText({ state }) {
|
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) {
|
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 });
|
const store = new Store({ state, getters });
|
||||||
|
|
||||||
@@ -452,10 +452,10 @@ describe("connecting a component to store", () => {
|
|||||||
</div>`;
|
</div>`;
|
||||||
getters = useGetters();
|
getters = useGetters();
|
||||||
storeProps = useStore((state, props) => {
|
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 {
|
return {
|
||||||
activeTodoText: this.getters.text(todo.id),
|
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"/>
|
<TodoItem id="todo.id" t-key="todo.id"/>
|
||||||
</t>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
todos = useStore(state => state.todos);
|
todos = useStore((state) => state.todos);
|
||||||
}
|
}
|
||||||
|
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
@@ -513,13 +513,13 @@ describe("connecting a component to store", () => {
|
|||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
// we have here a new object
|
// we have here a new object
|
||||||
data = useStore(state => ({ beers: state.beers, otherKey: 1 }));
|
data = useStore((state) => ({ beers: state.beers, otherKey: 1 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
addBeer({ state }, name) {
|
addBeer({ state }, name) {
|
||||||
state.beers.push({ name });
|
state.beers.push({ name });
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = { beers: [{ name: "jupiler" }] };
|
const state = { beers: [{ name: "jupiler" }] };
|
||||||
@@ -542,9 +542,9 @@ describe("connecting a component to store", () => {
|
|||||||
class Beer extends Component {
|
class Beer extends Component {
|
||||||
static template = xml`<span><t t-esc="beer.name"/></span>`;
|
static template = xml`<span><t t-esc="beer.name"/></span>`;
|
||||||
beer = useStore((state, props) => state.beers[props.id], {
|
beer = useStore((state, props) => state.beers[props.id], {
|
||||||
onUpdate: result => {
|
onUpdate: (result) => {
|
||||||
++counter;
|
++counter;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,7 +558,7 @@ describe("connecting a component to store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
renameBeer({ state }, { id, name }) {
|
renameBeer({ state }, { id, name }) {
|
||||||
state.beers[id].name = name;
|
state.beers[id].name = name;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
@@ -609,7 +609,7 @@ describe("connecting a component to store", () => {
|
|||||||
data = useStore((state, props) => ({
|
data = useStore((state, props) => ({
|
||||||
selected: state.beers[props.id],
|
selected: state.beers[props.id],
|
||||||
consumed: state.beers[state.consumedID] || null,
|
consumed: state.beers[state.consumedID] || null,
|
||||||
taster: state.taster
|
taster: state.taster,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,14 +622,14 @@ describe("connecting a component to store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
consume({ state }, beerId) {
|
consume({ state }, beerId) {
|
||||||
state.consumedID = beerId;
|
state.consumedID = beerId;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const state = {
|
const state = {
|
||||||
beers: {
|
beers: {
|
||||||
1: { name: "jupiler" }
|
1: { name: "jupiler" },
|
||||||
},
|
},
|
||||||
consumedID: null,
|
consumedID: null,
|
||||||
taster: "aaron"
|
taster: "aaron",
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
(<any>env).store = store;
|
(<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.selected">selected:<t t-esc="info.selected.name"/></span>
|
||||||
<span t-if="info.consumed">consumed:<t t-esc="info.consumed.name"/></span>
|
<span t-if="info.consumed">consumed:<t t-esc="info.consumed.name"/></span>
|
||||||
</div>`;
|
</div>`;
|
||||||
info = useStore(function(state, props) {
|
info = useStore(function (state, props) {
|
||||||
return {
|
return {
|
||||||
selected: state.beers[props.id],
|
selected: state.beers[props.id],
|
||||||
consumed: state.beers[state.consumedID] || null,
|
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 }) {
|
renameBeer({ state }, { beerId, name }) {
|
||||||
state.beers[beerId].name = name;
|
state.beers[beerId].name = name;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const state = {
|
const state = {
|
||||||
beers: {
|
beers: {
|
||||||
1: { name: "jupiler" }
|
1: { name: "jupiler" },
|
||||||
},
|
},
|
||||||
consumedID: null,
|
consumedID: null,
|
||||||
taster: "aaron"
|
taster: "aaron",
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
@@ -747,11 +747,11 @@ describe("connecting a component to store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
setValue({ state }, val) {
|
setValue({ state }, val) {
|
||||||
state.x.val = val;
|
state.x.val = val;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static template = xml`<div><t t-esc="x.val"/></div>`;
|
static template = xml`<div><t t-esc="x.val"/></div>`;
|
||||||
x = useStore(state => {
|
x = useStore((state) => {
|
||||||
return Object.assign({}, state.x);
|
return Object.assign({}, state.x);
|
||||||
});
|
});
|
||||||
dispatch = useDispatch();
|
dispatch = useDispatch();
|
||||||
@@ -782,11 +782,11 @@ describe("connecting a component to store", () => {
|
|||||||
static template = xml`<div><Child key="state.current"/></div>`;
|
static template = xml`<div><Child key="state.current"/></div>`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
|
|
||||||
state = useStore(state => {
|
state = useStore((state) => {
|
||||||
steps.push("parent");
|
steps.push("parent");
|
||||||
return {
|
return {
|
||||||
current: state.current,
|
current: state.current,
|
||||||
isvisible: state.isvisible
|
isvisible: state.isvisible,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -795,7 +795,7 @@ describe("connecting a component to store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
setCurrent({ state }, c) {
|
setCurrent({ state }, c) {
|
||||||
state.current = c;
|
state.current = c;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
@@ -831,7 +831,7 @@ describe("connecting a component to store", () => {
|
|||||||
<Child t-if="state.flag" someId="state.someId"/>
|
<Child t-if="state.flag" someId="state.someId"/>
|
||||||
</div>`;
|
</div>`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
state = useStore(s => {
|
state = useStore((s) => {
|
||||||
steps.push("parent");
|
steps.push("parent");
|
||||||
return { flag: s.flag, someId: s.someId };
|
return { flag: s.flag, someId: s.someId };
|
||||||
});
|
});
|
||||||
@@ -846,7 +846,7 @@ describe("connecting a component to store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
setFlagToFalse({ state }) {
|
setFlagToFalse({ state }) {
|
||||||
state.flag = false;
|
state.flag = false;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
@@ -874,18 +874,18 @@ describe("connecting a component to store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
editTodo({ state }) {
|
editTodo({ state }) {
|
||||||
state.todos[1].title = "abc";
|
state.todos[1].title = "abc";
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const todos = { 1: { id: 1, title: "kikoou" } };
|
const todos = { 1: { id: 1, title: "kikoou" } };
|
||||||
const state = {
|
const state = {
|
||||||
todos
|
todos,
|
||||||
};
|
};
|
||||||
const store = new Store({
|
const store = new Store({
|
||||||
state,
|
state,
|
||||||
actions
|
actions,
|
||||||
});
|
});
|
||||||
|
|
||||||
class TodoItem extends Component {
|
class TodoItem extends Component<{}, EnvWithStore> {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div class="todo">
|
<div class="todo">
|
||||||
<t t-esc="state.todo.title"/>
|
<t t-esc="state.todo.title"/>
|
||||||
@@ -894,7 +894,7 @@ describe("connecting a component to store", () => {
|
|||||||
state = useStore((state, props) => {
|
state = useStore((state, props) => {
|
||||||
steps.push("item:usestore");
|
steps.push("item:usestore");
|
||||||
return {
|
return {
|
||||||
todo: state.todos[props.id]
|
todo: state.todos[props.id],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -914,7 +914,7 @@ describe("connecting a component to store", () => {
|
|||||||
</t>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
static components = { TodoItem };
|
static components = { TodoItem };
|
||||||
state = useStore(state => {
|
state = useStore((state) => {
|
||||||
steps.push("app:usestore");
|
steps.push("app:usestore");
|
||||||
return { todos: state.todos };
|
return { todos: state.todos };
|
||||||
});
|
});
|
||||||
@@ -947,18 +947,18 @@ describe("connecting a component to store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
removeTodo({ state }) {
|
removeTodo({ state }) {
|
||||||
delete state.todos[1];
|
delete state.todos[1];
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const todos = { 1: { id: 1, title: "kikoou" } };
|
const todos = { 1: { id: 1, title: "kikoou" } };
|
||||||
const state = {
|
const state = {
|
||||||
todos
|
todos,
|
||||||
};
|
};
|
||||||
const store = new Store({
|
const store = new Store({
|
||||||
state,
|
state,
|
||||||
actions
|
actions,
|
||||||
});
|
});
|
||||||
|
|
||||||
class TodoItem extends Component {
|
class TodoItem extends Component<{}, EnvWithStore> {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div class="todo">
|
<div class="todo">
|
||||||
<t t-esc="state.todo.title"/>
|
<t t-esc="state.todo.title"/>
|
||||||
@@ -967,7 +967,7 @@ describe("connecting a component to store", () => {
|
|||||||
state = useStore((state, props) => {
|
state = useStore((state, props) => {
|
||||||
steps.push("item:usestore");
|
steps.push("item:usestore");
|
||||||
return {
|
return {
|
||||||
todo: state.todos[props.id]
|
todo: state.todos[props.id],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -988,7 +988,7 @@ describe("connecting a component to store", () => {
|
|||||||
</t>
|
</t>
|
||||||
</div>`;
|
</div>`;
|
||||||
static components = { TodoItem };
|
static components = { TodoItem };
|
||||||
state = useStore(state => {
|
state = useStore((state) => {
|
||||||
steps.push("app:usestore");
|
steps.push("app:usestore");
|
||||||
return { todos: state.todos };
|
return { todos: state.todos };
|
||||||
});
|
});
|
||||||
@@ -1016,7 +1016,7 @@ describe("connecting a component to store", () => {
|
|||||||
"item:usestore",
|
"item:usestore",
|
||||||
"item:render",
|
"item:render",
|
||||||
"app:usestore",
|
"app:usestore",
|
||||||
"app:render"
|
"app:render",
|
||||||
]);
|
]);
|
||||||
expect(fixture.innerHTML).toBe('<div class="todoapp"></div>');
|
expect(fixture.innerHTML).toBe('<div class="todoapp"></div>');
|
||||||
});
|
});
|
||||||
@@ -1026,7 +1026,7 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
static template = xml`<div><t t-esc="store.msg"/></div>`;
|
static template = xml`<div><t t-esc="store.msg"/></div>`;
|
||||||
store = useStore(s => ({ msg: s.msg }));
|
store = useStore((s) => ({ msg: s.msg }));
|
||||||
|
|
||||||
willPatch() {
|
willPatch() {
|
||||||
steps.push("willpatch");
|
steps.push("willpatch");
|
||||||
@@ -1040,7 +1040,7 @@ describe("connecting a component to store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
setMsg({ state }, c) {
|
setMsg({ state }, c) {
|
||||||
state.msg = c;
|
state.msg = c;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
@@ -1061,7 +1061,7 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`<div><t t-esc="store.val"/></div>`;
|
static template = xml`<div><t t-esc="store.val"/></div>`;
|
||||||
store = useStore(s => s);
|
store = useStore((s) => s);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
@@ -1099,7 +1099,7 @@ describe("connecting a component to store", () => {
|
|||||||
|
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`<div><t t-esc="store.val"/></div>`;
|
static template = xml`<div><t t-esc="store.val"/></div>`;
|
||||||
store = useStore(s => {
|
store = useStore((s) => {
|
||||||
steps.push("child selector");
|
steps.push("child selector");
|
||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
@@ -1107,7 +1107,7 @@ describe("connecting a component to store", () => {
|
|||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static template = xml`<div><Child t-if="store.child" /></div>`;
|
static template = xml`<div><Child t-if="store.child" /></div>`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
store = useStore(s => {
|
store = useStore((s) => {
|
||||||
steps.push("parent selector");
|
steps.push("parent selector");
|
||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
@@ -1118,7 +1118,7 @@ describe("connecting a component to store", () => {
|
|||||||
const actions = {
|
const actions = {
|
||||||
toggleChild({ state }) {
|
toggleChild({ state }) {
|
||||||
state.child = !state.child;
|
state.child = !state.child;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
(<any>env).store = store;
|
(<any>env).store = store;
|
||||||
@@ -1137,18 +1137,18 @@ describe("connecting a component to store", () => {
|
|||||||
test("dispatch an action", async () => {
|
test("dispatch an action", async () => {
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
static template = xml`<div><t t-esc="store.counter"/></div>`;
|
static template = xml`<div><t t-esc="store.counter"/></div>`;
|
||||||
store = useStore(state => state);
|
store = useStore((state) => state);
|
||||||
dispatch = useDispatch();
|
dispatch = useDispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
counter: 0
|
counter: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
inc({ state }) {
|
inc({ state }) {
|
||||||
return ++state.counter;
|
return ++state.counter;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = new Store({ state, actions });
|
const store = new Store({ state, actions });
|
||||||
@@ -1185,21 +1185,21 @@ describe("various scenarios", () => {
|
|||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
delete state.attachments[100];
|
delete state.attachments[100];
|
||||||
state.messages[10].attachmentIds = [];
|
state.messages[10].attachmentIds = [];
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const state = {
|
const state = {
|
||||||
attachments: {
|
attachments: {
|
||||||
100: {
|
100: {
|
||||||
id: 100,
|
id: 100,
|
||||||
name: "text.txt"
|
name: "text.txt",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
10: {
|
10: {
|
||||||
attachmentIds: [100],
|
attachmentIds: [100],
|
||||||
id: 10
|
id: 10,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const store = new Store({ actions, state });
|
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"/>
|
<Attachment t-foreach="store.attachmentIds" t-key="attachmentId" t-as="attachmentId" id="attachmentId"/>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
store = useStore(state => ({ attachmentIds: state.messages[10].attachmentIds }));
|
store = useStore((state) => ({ attachmentIds: state.messages[10].attachmentIds }));
|
||||||
static components = { Attachment };
|
static components = { Attachment };
|
||||||
state = { isAttachmentDeleted: false };
|
state = { isAttachmentDeleted: false };
|
||||||
dispatch = useDispatch();
|
dispatch = useDispatch();
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ debugOwl(owl, {});
|
|||||||
test("can log full lifecycle", async () => {
|
test("can log full lifecycle", async () => {
|
||||||
const steps: string[] = [];
|
const steps: string[] = [];
|
||||||
const log = console.log;
|
const log = console.log;
|
||||||
console.log = arg => steps.push(arg);
|
console.log = (arg) => steps.push(arg);
|
||||||
|
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`<div>child</div>`;
|
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] Child<id=2> rendering template",
|
||||||
"[OWL_DEBUG] Parent<id=1> willPatch",
|
"[OWL_DEBUG] Parent<id=1> willPatch",
|
||||||
"[OWL_DEBUG] Child<id=2> mounted",
|
"[OWL_DEBUG] Child<id=2> mounted",
|
||||||
"[OWL_DEBUG] Parent<id=1> patched"
|
"[OWL_DEBUG] Parent<id=1> patched",
|
||||||
]);
|
]);
|
||||||
console.log = log;
|
console.log = log;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ debugOwl(owl, { logScheduler: true });
|
|||||||
test("can log scheduler start and stop", async () => {
|
test("can log scheduler start and stop", async () => {
|
||||||
const steps: string[] = [];
|
const steps: string[] = [];
|
||||||
const log = console.log;
|
const log = console.log;
|
||||||
console.log = arg => steps.push(arg);
|
console.log = (arg) => steps.push(arg);
|
||||||
|
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`<div>child</div>`;
|
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> rendering template",
|
||||||
"[OWL_DEBUG] Child<id=2> mounted",
|
"[OWL_DEBUG] Child<id=2> mounted",
|
||||||
"[OWL_DEBUG] Parent<id=1> mounted",
|
"[OWL_DEBUG] Parent<id=1> mounted",
|
||||||
"[OWL_DEBUG] scheduler: stop running tasks queue"
|
"[OWL_DEBUG] scheduler: stop running tasks queue",
|
||||||
]);
|
]);
|
||||||
console.log = log;
|
console.log = log;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ debugOwl(owl, { logScheduler: true });
|
|||||||
test("log a specific message for render method calls if component is not mounted", async () => {
|
test("log a specific message for render method calls if component is not mounted", async () => {
|
||||||
const steps: string[] = [];
|
const steps: string[] = [];
|
||||||
const log = console.log;
|
const log = console.log;
|
||||||
console.log = arg => steps.push(arg);
|
console.log = (arg) => steps.push(arg);
|
||||||
|
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static template = xml`<div><t t-esc="state.value"/></div>`;
|
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] Parent<id=1> mounted",
|
||||||
"[OWL_DEBUG] scheduler: stop running tasks queue",
|
"[OWL_DEBUG] scheduler: stop running tasks queue",
|
||||||
"[OWL_DEBUG] Parent<id=1> willUnmount",
|
"[OWL_DEBUG] Parent<id=1> willUnmount",
|
||||||
"[OWL_DEBUG] Parent<id=1> render (warning: component is not mounted, this render has no effect)"
|
"[OWL_DEBUG] Parent<id=1> render (warning: component is not mounted, this render has no effect)",
|
||||||
]);
|
]);
|
||||||
console.log = log;
|
console.log = log;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ debugOwl(owl, { logScheduler: true });
|
|||||||
test("log a sub component with non stringifiable props", async () => {
|
test("log a sub component with non stringifiable props", async () => {
|
||||||
const steps: string[] = [];
|
const steps: string[] = [];
|
||||||
const log = console.log;
|
const log = console.log;
|
||||||
console.log = arg => steps.push(arg);
|
console.log = (arg) => steps.push(arg);
|
||||||
|
|
||||||
class Child extends Component {
|
class Child extends Component {
|
||||||
static template = xml`<span><t t-esc="props.obj.val"/></span>`;
|
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] Parent<id=1> mounted",
|
||||||
"[OWL_DEBUG] scheduler: stop running tasks queue",
|
"[OWL_DEBUG] scheduler: stop running tasks queue",
|
||||||
"[OWL_DEBUG] Parent<id=1> willUnmount",
|
"[OWL_DEBUG] Parent<id=1> willUnmount",
|
||||||
"[OWL_DEBUG] Child<id=2> willUnmount"
|
"[OWL_DEBUG] Child<id=2> willUnmount",
|
||||||
]);
|
]);
|
||||||
console.log = log;
|
console.log = log;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -60,14 +60,14 @@ function getFiles(path: string[] = []): FileData[] {
|
|||||||
if (path.length === 0) {
|
if (path.length === 0) {
|
||||||
const baseFiles: FileData[] = [
|
const baseFiles: FileData[] = [
|
||||||
{ name: "README.md", path: [], links: [], sections: [], fullName: "README.md" },
|
{ 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 rest = getFiles(["doc"]);
|
||||||
const result = baseFiles.concat(rest);
|
const result = baseFiles.concat(rest);
|
||||||
result.forEach(addMardownData);
|
result.forEach(addMardownData);
|
||||||
return result;
|
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()) {
|
if (f.isDirectory()) {
|
||||||
return getFiles(path.concat(f.name));
|
return getFiles(path.concat(f.name));
|
||||||
}
|
}
|
||||||
@@ -78,8 +78,8 @@ function getFiles(path: string[] = []): FileData[] {
|
|||||||
path,
|
path,
|
||||||
links: [],
|
links: [],
|
||||||
sections: [],
|
sections: [],
|
||||||
fullName
|
fullName,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
return Array.prototype.concat(...files);
|
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
|
// 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) {
|
if (!target) {
|
||||||
return false;
|
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
|
// Step 5: if necessary, check if there is a corresponding link inside the target
|
||||||
// link name
|
// link name
|
||||||
if (hash) {
|
if (hash) {
|
||||||
if (!target.sections.find(s => s.slug === hash)) {
|
if (!target.sections.find((s) => s.slug === hash)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ function slugify(str) {
|
|||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/\//g, "") // remove /
|
.replace(/\//g, "") // remove /
|
||||||
.replace(/\s+/g, "-") // Replace spaces with -
|
.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(/&/g, "-and-") // Replace & with ‘and’
|
||||||
.replace(/[^\w\-]+/g, "") // Remove all non-word characters
|
.replace(/[^\w\-]+/g, "") // Remove all non-word characters
|
||||||
.replace(/\-\-+/g, "-") // Replace multiple - with single -
|
.replace(/\-\-+/g, "-") // Replace multiple - with single -
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { escape, debounce } from "../src/utils";
|
import { escape, debounce } from "../src/utils";
|
||||||
|
import { browser } from "../src/browser";
|
||||||
|
|
||||||
describe("escape", () => {
|
describe("escape", () => {
|
||||||
test("normal strings", () => {
|
test("normal strings", () => {
|
||||||
@@ -15,6 +16,10 @@ describe("escape", () => {
|
|||||||
describe("debounce", () => {
|
describe("debounce", () => {
|
||||||
test("works as expected", () => {
|
test("works as expected", () => {
|
||||||
jest.useFakeTimers();
|
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 n = 0;
|
||||||
let f = debounce(() => n++, 100);
|
let f = debounce(() => n++, 100);
|
||||||
expect(n).toBe(0);
|
expect(n).toBe(0);
|
||||||
|
|||||||
+199
-192
File diff suppressed because it is too large
Load Diff
@@ -1169,7 +1169,7 @@ const RESPONSIVE_CSS = `body {
|
|||||||
const SLOTS = `// We show here how slots can be used to create generic components.
|
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
|
// 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).
|
// 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
|
// 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
|
// 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">
|
<div t-name="App" class="main">
|
||||||
<Card title="'Title card A'">
|
<Card title="'Title card A'">
|
||||||
<t t-set="content">Content of card 1... [<t t-esc="state.a"/>]</t>
|
<t t-set-slot="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="footer"><button t-on-click="inc('a', 1)">Increment A</button></t>
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="'Title card B'">
|
<Card title="'Title card B'">
|
||||||
<t t-set="content">
|
<t t-set-slot="content">
|
||||||
<div>Card 2... [<t t-esc="state.b"/>]</div>
|
<div>Card 2... [<t t-esc="state.b"/>]</div>
|
||||||
<Counter />
|
<Counter />
|
||||||
</t>
|
</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>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</templates>`;
|
</templates>`;
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ async function startRelease() {
|
|||||||
draft: true // todo: remove this someday
|
draft: true // todo: remove this someday
|
||||||
};
|
};
|
||||||
const result = await createRelease(token, options);
|
const result = await createRelease(token, options);
|
||||||
console.log(result);
|
|
||||||
await ask("Ready for next step...");
|
await ask("Ready for next step...");
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user