mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e2202c02a | |||
| c8a27aa1a3 | |||
| 323cb61d2c | |||
| ac9cc91701 | |||
| d83cfc12ee | |||
| cb07c99d40 | |||
| d615ffd81b | |||
| 8d2b250fef | |||
| 7626cc01b3 | |||
| 392185ab67 | |||
| 392dc0131a | |||
| 4e8e9dee3e | |||
| 25d4cb2aab | |||
| 4724652533 | |||
| b08cc1d084 | |||
| a68d7774c6 | |||
| ed9d820d9e | |||
| 4a2d019161 | |||
| 89eae191b1 | |||
| 3fbc02986c | |||
| a28ce440dc | |||
| 8e018fbbda | |||
| 33f7fc8e83 | |||
| 6a833c54ec | |||
| 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 |
@@ -0,0 +1,27 @@
|
|||||||
|
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||||
|
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||||
|
|
||||||
|
name: Node.js CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [10.x, 12.x, 14.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: npm install
|
||||||
|
- run: npm run test
|
||||||
|
- run: npm run prettier
|
||||||
@@ -27,3 +27,5 @@ node_modules
|
|||||||
/tools/owl.js
|
/tools/owl.js
|
||||||
|
|
||||||
release-notes.md
|
release-notes.md
|
||||||
|
|
||||||
|
.rpt2_cache
|
||||||
@@ -41,7 +41,7 @@ find some more additional information [here](doc/miscellaneous/comparison.md).
|
|||||||
Here is a short example to illustrate interactive components:
|
Here is a short example to illustrate interactive components:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const { Component, useState } = owl;
|
const { Component, useState, mount } = owl;
|
||||||
const { xml } = owl.tags;
|
const { xml } = owl.tags;
|
||||||
|
|
||||||
class Counter extends Component {
|
class Counter extends Component {
|
||||||
@@ -63,8 +63,7 @@ class App extends Component {
|
|||||||
static components = { Counter };
|
static components = { Counter };
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the counter component is made reactive with the [`useState` hook](doc/reference/hooks.md#usestate).
|
Note that the counter component is made reactive with the [`useState` hook](doc/reference/hooks.md#usestate).
|
||||||
@@ -121,8 +120,7 @@ npm install @odoo/owl
|
|||||||
|
|
||||||
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
||||||
|
|
||||||
- [owl-1.0.6.js](https://github.com/odoo/owl/releases/download/v1.0.6/owl.js)
|
- [owl-1.0.13](https://github.com/odoo/owl/releases/tag/v1.0.13)
|
||||||
- [owl-1.0.6.min.js](https://github.com/odoo/owl/releases/download/v1.0.6/owl.min.js)
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -85,8 +85,7 @@ afterEach(() => {
|
|||||||
describe("SomeComponent", () => {
|
describe("SomeComponent", () => {
|
||||||
test("component behaves as expected", async () => {
|
test("component behaves as expected", async () => {
|
||||||
const props = {...}; // depends on the component
|
const props = {...}; // depends on the component
|
||||||
const comp = new SomeComponent(null, props);
|
const comp = await mount(SomeComponent, { target: fixture, props });
|
||||||
await comp.mount(fixture);
|
|
||||||
|
|
||||||
// do some assertions
|
// do some assertions
|
||||||
expect(...).toBe(...);
|
expect(...).toBe(...);
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class OrderLine extends Component {
|
|||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
add() {
|
add() {
|
||||||
this.trigger("add-to-order", { line: props.line });
|
this.trigger("add-to-order", { line: this.props.line });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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) {
|
||||||
|
|||||||
+19
-22
@@ -54,7 +54,7 @@ Now, `index.html` should contain the following:
|
|||||||
And `app.js` should look like this:
|
And `app.js` should look like this:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const { Component } = owl;
|
const { Component, mount } = owl;
|
||||||
const { xml } = owl.tags;
|
const { xml } = owl.tags;
|
||||||
const { whenReady } = owl.utils;
|
const { whenReady } = owl.utils;
|
||||||
|
|
||||||
@@ -65,8 +65,7 @@ class App extends Component {
|
|||||||
|
|
||||||
// Setup code
|
// Setup code
|
||||||
function setup() {
|
function setup() {
|
||||||
const app = new App();
|
mount(App, target: { document.body })
|
||||||
app.mount(document.body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
whenReady(setup);
|
whenReady(setup);
|
||||||
@@ -124,7 +123,7 @@ Here is the content of `app.js` and `main.js`:
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// app.js ----------------------------------------------------------------------
|
// app.js ----------------------------------------------------------------------
|
||||||
const { Component } = owl;
|
const { Component, mount } = owl;
|
||||||
const { xml } = owl.tags;
|
const { xml } = owl.tags;
|
||||||
|
|
||||||
export class App extends Component {
|
export class App extends Component {
|
||||||
@@ -135,8 +134,7 @@ export class App extends Component {
|
|||||||
import { App } from "./app.js";
|
import { App } from "./app.js";
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
owl.utils.whenReady(setup);
|
owl.utils.whenReady(setup);
|
||||||
@@ -240,12 +238,11 @@ export class App extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// src/main.js -----------------------------------------------------------------
|
// src/main.js -----------------------------------------------------------------
|
||||||
import { utils } from "@odoo/owl";
|
import { utils, mount } from "@odoo/owl";
|
||||||
import { App } from "./components/App";
|
import { App } from "./components/App";
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.whenReady(setup);
|
utils.whenReady(setup);
|
||||||
@@ -253,6 +250,7 @@ utils.whenReady(setup);
|
|||||||
// tests/components/App.test.js ------------------------------------------------
|
// tests/components/App.test.js ------------------------------------------------
|
||||||
import { App } from "../../src/components/App";
|
import { App } from "../../src/components/App";
|
||||||
import { makeTestFixture, nextTick, click } from "../helpers";
|
import { makeTestFixture, nextTick, click } from "../helpers";
|
||||||
|
import { mount } from "@odoo/owl";
|
||||||
|
|
||||||
let fixture;
|
let fixture;
|
||||||
|
|
||||||
@@ -266,8 +264,7 @@ afterEach(() => {
|
|||||||
|
|
||||||
describe("App", () => {
|
describe("App", () => {
|
||||||
test("Works as expected...", async () => {
|
test("Works as expected...", async () => {
|
||||||
const app = new App();
|
await mount(App, { target: fixture });
|
||||||
await app.mount(fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("<div>Hello Owl</div>");
|
expect(fixture.innerHTML).toBe("<div>Hello Owl</div>");
|
||||||
|
|
||||||
click(fixture, "div");
|
click(fixture, "div");
|
||||||
@@ -281,7 +278,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 +358,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 +385,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);
|
||||||
})();
|
})();
|
||||||
```
|
```
|
||||||
@@ -83,7 +83,7 @@ a single root component. Let us start by defining an `App` component. Replace th
|
|||||||
content of the function in `app.js` by the following code:
|
content of the function in `app.js` by the following code:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const { Component } = owl;
|
const { Component, mount } = owl;
|
||||||
const { xml } = owl.tags;
|
const { xml } = owl.tags;
|
||||||
const { whenReady } = owl.utils;
|
const { whenReady } = owl.utils;
|
||||||
|
|
||||||
@@ -94,8 +94,7 @@ class App extends Component {
|
|||||||
|
|
||||||
// Setup code
|
// Setup code
|
||||||
function setup() {
|
function setup() {
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
whenReady(setup);
|
whenReady(setup);
|
||||||
@@ -170,13 +169,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,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -279,8 +278,7 @@ class App extends Component {
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
function setup() {
|
function setup() {
|
||||||
owl.config.mode = "dev";
|
owl.config.mode = "dev";
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
whenReady(setup);
|
whenReady(setup);
|
||||||
@@ -562,23 +560,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 +614,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() {
|
||||||
@@ -639,8 +637,7 @@ function setup() {
|
|||||||
owl.config.mode = "dev";
|
owl.config.mode = "dev";
|
||||||
const store = new Store({ actions, state: initialState });
|
const store = new Store({ actions, state: initialState });
|
||||||
App.env.store = store;
|
App.env.store = store;
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
whenReady(setup);
|
whenReady(setup);
|
||||||
@@ -666,9 +663,8 @@ function makeStore() {
|
|||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
owl.config.mode = "dev";
|
owl.config.mode = "dev";
|
||||||
App.env.store = makeStore();
|
const env = {store = makeStore()};
|
||||||
const app = new App();
|
mount(App, { target: document.body, env });
|
||||||
app.mount(document.body);
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -811,7 +807,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 +823,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 +893,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 +912,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;
|
||||||
}
|
}
|
||||||
@@ -943,9 +939,8 @@ For reference, here is the final code:
|
|||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
owl.config.mode = "dev";
|
owl.config.mode = "dev";
|
||||||
App.env.store = makeStore();
|
const env = {store = makeStore()};
|
||||||
const app = new App();
|
mount(App, { target: document.body, env });
|
||||||
app.mount(document.body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
whenReady(setup);
|
whenReady(setup);
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,14 +61,14 @@ because a lot of the state is hidden in their internals.
|
|||||||
|
|
||||||
React or Vue have a huge community, and a lot of effort have been made into their
|
React or Vue have a huge community, and a lot of effort have been made into their
|
||||||
tooling. This is wonderful, but at the same time, a pretty big issue for Odoo:
|
tooling. This is wonderful, but at the same time, a pretty big issue for Odoo:
|
||||||
since the assets are totally dynamic (and could change whenever the user install
|
since the assets are totally dynamic (and could change whenever the user installs
|
||||||
or remove an addon), we need to have all that kind of tooling on the production
|
or removes an addon), we need to have all that kind of tooling on the production
|
||||||
servers. This is certainly not ideal.
|
servers. This is certainly not ideal.
|
||||||
|
|
||||||
Also, this makes it very complicated to setup Vue or React tools: Odoo code is
|
Also, this makes it very complicated to setup Vue or React tools: Odoo code is
|
||||||
not a simple file that import other files. It changes all the time, assets
|
not a simple file that import other files. It changes all the time, assets
|
||||||
are bundled differently in different contexts. This is the reason why Odoo has
|
are bundled differently in different contexts. This is the reason why Odoo has
|
||||||
its own module system, which are resolve at runtime, by the browser. The
|
its own module system, which are resolved at runtime, by the browser. The
|
||||||
dynamic nature of Odoo means that we often need to delay work as late as possible
|
dynamic nature of Odoo means that we often need to delay work as late as possible
|
||||||
(in other word, we want a JIT user interface!)
|
(in other word, we want a JIT user interface!)
|
||||||
|
|
||||||
@@ -78,12 +78,12 @@ deploy. Using React without JSX, or Vue without vue file is not very appealing.
|
|||||||
At the same time, Owl is designed to solve this issue: it compiles templates
|
At the same time, Owl is designed to solve this issue: it compiles templates
|
||||||
by the browser, it doesn't need much code for that, since we use the XML parser
|
by the browser, it doesn't need much code for that, since we use the XML parser
|
||||||
built into each browser. Owl works with or without any additional tooling. It
|
built into each browser. Owl works with or without any additional tooling. It
|
||||||
can use template strings to write single file component, and is easy to integrate
|
can use template strings to write single file components, and is easy to integrate
|
||||||
in any html page, with a simple `<script>` tag.
|
in any html page, with a simple `<script>` tag.
|
||||||
|
|
||||||
## Template based
|
## Template based
|
||||||
|
|
||||||
Odoo stores template as XML document in a database. This is very powerful, since
|
Odoo stores templates as XML documents in a database. This is very powerful, since
|
||||||
this allow the use of xpaths to customize other templates. This is a very
|
this allow the use of xpaths to customize other templates. This is a very
|
||||||
important feature of odoo, and one of the key to Odoo modularity.
|
important feature of odoo, and one of the key to Odoo modularity.
|
||||||
|
|
||||||
@@ -104,12 +104,12 @@ awkward, and very confusing.
|
|||||||
## Developer Experience
|
## Developer Experience
|
||||||
|
|
||||||
This brings us to the following point: developer experience. We see this choice
|
This brings us to the following point: developer experience. We see this choice
|
||||||
as an investment for the future, and we want to make onboarding developer as
|
as an investment for the future, and we want to make onboarding developers as
|
||||||
easy as possible.
|
easy as possible.
|
||||||
|
|
||||||
While many javascript professionals clearly think that react/vue is not difficult
|
While many javascript professionals clearly think that react/vue is not difficult
|
||||||
(which is true to some extent), it is alsy true that many non js specialists are
|
(which is true to some extent), it is alsy true that many non js specialists are
|
||||||
overwhelmed with the frontend world: functional component, hooks, and many other
|
overwhelmed with the frontend world: functional components, hooks, and many other
|
||||||
fancy words. Also, what is available in the compilation context may be difficult,
|
fancy words. Also, what is available in the compilation context may be difficult,
|
||||||
there is a lot of black magic going on in pretty much every framework. Vue
|
there is a lot of black magic going on in pretty much every framework. Vue
|
||||||
somehow join various namespaces into one, under the hood, and add various internal
|
somehow join various namespaces into one, under the hood, and add various internal
|
||||||
@@ -135,7 +135,7 @@ needs: Odoo will fetch templates from the database and need to compile them only
|
|||||||
at the last possible moment, so we can apply all necessary xpaths.
|
at the last possible moment, so we can apply all necessary xpaths.
|
||||||
|
|
||||||
Even more: Odoo needs to be able to generate (and compile) templates at runtime.
|
Even more: Odoo needs to be able to generate (and compile) templates at runtime.
|
||||||
Currently, Odoo form views interpret a xml description. But the form view code
|
Currently, Odoo form views interpret an xml description. But the form view code
|
||||||
then needs to do a lot of complicated operations. With Owl, we will be able to
|
then needs to do a lot of complicated operations. With Owl, we will be able to
|
||||||
transform a view description into a QWeb template, then compile that and use it
|
transform a view description into a QWeb template, then compile that and use it
|
||||||
immediately.
|
immediately.
|
||||||
@@ -147,16 +147,16 @@ For example, the reactivity system. We like the way Vue did it, but it has a
|
|||||||
flaw: it is not really optional. There is actually a way to opt out of the reactivity
|
flaw: it is not really optional. There is actually a way to opt out of the reactivity
|
||||||
system by freezing the state, but then, it is freezed.
|
system by freezing the state, but then, it is freezed.
|
||||||
|
|
||||||
And there certainly are situations where we need a state, which is not readonly,
|
And there certainly are situations where we need a state, which is not read-only,
|
||||||
and not observed. For example, imagine a spreadsheet component. It may have a
|
and not observed. For example, imagine a spreadsheet component. It may have a
|
||||||
very large internal state, and it knows exactly when it needs to be rendered
|
very large internal state, and it knows exactly when it needs to be rendered
|
||||||
(basically, whenever the user perform some action). Then, observing its state
|
(basically, whenever the user performs some action). Then, observing its state
|
||||||
is a net performance loss, both for the CPU and the memory.
|
is a net performance loss, both for the CPU and the memory.
|
||||||
|
|
||||||
## Concurrency
|
## Concurrency
|
||||||
|
|
||||||
Many applications are happy to simply display a spinner whenever a new asynchronous
|
Many applications are happy to simply display a spinner whenever a new asynchronous
|
||||||
action is performed, but Odoo want a different user experience: most asynchronous
|
action is performed, but Odoo wants a different user experience: most asynchronous
|
||||||
state changes are not displayed until ready. This is sometimes called a concurrent
|
state changes are not displayed until ready. This is sometimes called a concurrent
|
||||||
mode: the UI is rendered in memory, and displayed only when it is ready (and
|
mode: the UI is rendered in memory, and displayed only when it is ready (and
|
||||||
only if it has not been cancelled by subsequent user actions).
|
only if it has not been cancelled by subsequent user actions).
|
||||||
@@ -175,6 +175,6 @@ that current standard frameworks are not tailored to our needs. It is perfectly
|
|||||||
fine, because they each chose a different set of tradeoffs.
|
fine, because they each chose a different set of tradeoffs.
|
||||||
|
|
||||||
However, we feel that there is still room in the framework world for something
|
However, we feel that there is still room in the framework world for something
|
||||||
that is different. For a framework that make choices compatible with Odoo.
|
that is different. For a framework that makes choices compatible with Odoo.
|
||||||
|
|
||||||
And that is why we built Owl 🦉.
|
And that is why we built Owl 🦉.
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ provided by Owl.
|
|||||||
- [Event Handling](reference/event_handling.md)
|
- [Event Handling](reference/event_handling.md)
|
||||||
- [Error Handling](reference/error_handling.md)
|
- [Error Handling](reference/error_handling.md)
|
||||||
- [Hooks](reference/hooks.md)
|
- [Hooks](reference/hooks.md)
|
||||||
|
- [Mounting a component](reference/mounting.md)
|
||||||
- [Miscellaneous Components](reference/misc.md)
|
- [Miscellaneous Components](reference/misc.md)
|
||||||
- [Observer](reference/observer.md)
|
- [Observer](reference/observer.md)
|
||||||
- [Props](reference/props.md)
|
- [Props](reference/props.md)
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -270,6 +270,10 @@ We explain here all the public methods of the `Component` class.
|
|||||||
// app is now visible
|
// app is now visible
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that the normal way of mounting an application is by using the `mount`
|
||||||
|
method on a component class, not by creating the instance by hand. See the
|
||||||
|
documentation on [mounting applications](mounting.md).
|
||||||
|
|
||||||
* **`unmount()`**: in case a component needs to be detached/removed from the DOM, this
|
* **`unmount()`**: in case a component needs to be detached/removed from the DOM, this
|
||||||
method can be used. Most applications should not call `unmount`, this is more
|
method can be used. Most applications should not call `unmount`, this is more
|
||||||
useful to the underlying component system.
|
useful to the underlying component system.
|
||||||
@@ -804,8 +808,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" }] },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
+19
-2
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
The Owl framework is designed to work in many situations. However, it is
|
The Owl framework is designed to work in many situations. However, it is
|
||||||
sometimes necessary to customize some behaviour. This is done by using the
|
sometimes necessary to customize some behaviour. This is done by using the
|
||||||
global `config` object. It currently has one key:
|
global `config` object. It provides two settings:
|
||||||
|
|
||||||
- [`mode`](#mode).
|
- [`mode`](#mode) (default value: `prod`),
|
||||||
|
- [`enableTransitions`](#enabletransitions) (default value: `true`).
|
||||||
|
|
||||||
## Mode
|
## Mode
|
||||||
|
|
||||||
@@ -25,3 +26,19 @@ So, changing this setting is best done at startup.
|
|||||||
|
|
||||||
An important job done by the `dev` mode is to validate props for each component
|
An important job done by the `dev` mode is to validate props for each component
|
||||||
creation and update. Also, extra props will cause an error.
|
creation and update. Also, extra props will cause an error.
|
||||||
|
|
||||||
|
## `enableTransitions`
|
||||||
|
|
||||||
|
Transitions are usually nice, but they can cause issues in some specific cases,
|
||||||
|
such as automated tests. It is uncomfortable having to wait for a transition
|
||||||
|
to end before moving to the next step.
|
||||||
|
|
||||||
|
To solve this issue, Owl can be configured to ignore the `t-transition` directive.
|
||||||
|
To do that, one only needs to set the `enableTransitions` flag to false:
|
||||||
|
|
||||||
|
```js
|
||||||
|
owl.config.enableTransitions = false;
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that it suffers from the same drawback as the "dev" mode: all compiled
|
||||||
|
templates, if any, will keep their current behaviours.
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ exported as `owl.core.EventBus`.
|
|||||||
Component misc
|
Component misc
|
||||||
Context AsyncRoot
|
Context AsyncRoot
|
||||||
QWeb Portal
|
QWeb Portal
|
||||||
Store router
|
mount router
|
||||||
useState Link
|
Store Link
|
||||||
config RouteComponent
|
useState RouteComponent
|
||||||
mode Router
|
config Router
|
||||||
|
mode
|
||||||
core tags
|
core tags
|
||||||
EventBus css
|
EventBus css
|
||||||
Observer xml
|
Observer xml
|
||||||
|
|||||||
@@ -49,15 +49,14 @@ The correct way to customize an environment is to simply set it up on the root
|
|||||||
component class, before the first component is created:
|
component class, before the first component is created:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
App.env = {
|
const env = {
|
||||||
_t: myTranslateFunction,
|
_t: myTranslateFunction,
|
||||||
user: {...},
|
user: {...},
|
||||||
services: {
|
services: {
|
||||||
...
|
...
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const app = new App();
|
mount(App, { target: document.body, env });
|
||||||
app.mount(document.body);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
It is also possible to simply share an environment between all root components,
|
It is also possible to simply share an environment between all root components,
|
||||||
@@ -113,17 +112,16 @@ 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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function start() {
|
async function start() {
|
||||||
App.env = await myEnv();
|
const env = await myEnv();
|
||||||
const app = new App();
|
mount(App, { target: document.body, env });
|
||||||
await app.mount(document.body);
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,20 @@ The `t-on` directive allows to prebind its arguments. For example,
|
|||||||
Here, `expr` is a valid Owl expression, so it could be `true` or some variable
|
Here, `expr` is a valid Owl expression, so it could be `true` or some variable
|
||||||
from the rendering context.
|
from the rendering context.
|
||||||
|
|
||||||
|
### Type Hinting
|
||||||
|
|
||||||
|
Note that if you work with Typescript, the `trigger` method is generic on the type of the payload.
|
||||||
|
|
||||||
|
You can then describe the type of the event, so you will see typing errors...
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
this.trigger<MyCustomPayload>("my-custom-event", payload);
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
myCustomEventHandler(ev: OwlEvent<MyCustomPayload>) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
## Inline Event Handlers
|
## Inline Event Handlers
|
||||||
|
|
||||||
One can also directly specify inline statements. For example,
|
One can also directly specify inline statements. For example,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ workflow to help the user put in some data, which it could use later on.
|
|||||||
JavaScript:
|
JavaScript:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const { Component } = owl;
|
const { Component, mount } = owl;
|
||||||
const { Portal } = owl.misc;
|
const { Portal } = owl.misc;
|
||||||
|
|
||||||
class TeleportedComponent extends Component {}
|
class TeleportedComponent extends Component {}
|
||||||
@@ -51,8 +51,7 @@ class App extends Component {
|
|||||||
static components = { Portal, TeleportedComponent };
|
static components = { Portal, TeleportedComponent };
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
XML:
|
XML:
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# 🦉 Mounting an application 🦉
|
||||||
|
|
||||||
|
## Content
|
||||||
|
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [API](#api)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Mounting an Owl application is done by using the `mount` method (available in
|
||||||
|
`owl.mount` if you are using the iife build, or it can be directly imported
|
||||||
|
from `owl` if you are using a module system):
|
||||||
|
|
||||||
|
```js
|
||||||
|
const mount = { owl }; // if owl is available as an object
|
||||||
|
|
||||||
|
const env = { ... };
|
||||||
|
const app = await mount(MyComponent, { target: document.body, env });
|
||||||
|
```
|
||||||
|
|
||||||
|
Another example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const config = {
|
||||||
|
env: ...,
|
||||||
|
props: ...,
|
||||||
|
target: document.body,
|
||||||
|
position: "self",
|
||||||
|
};
|
||||||
|
const app = await mount(App, config);
|
||||||
|
```
|
||||||
|
|
||||||
|
A common way to initialize an application is to first setup an environment,
|
||||||
|
then to call the `mount` method.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
Mount takes two parameters:
|
||||||
|
|
||||||
|
- `C`, which should be a component class (NOT instance),
|
||||||
|
- `params`, which is an object with the following keys:
|
||||||
|
- `target (HTMLElement | DocumentFragment)`: the target of the mount operation
|
||||||
|
- `env (optional, Env)` an environment
|
||||||
|
- `position (optional, "first-child" | "last-child" | "self")` the position
|
||||||
|
where it should be mounted (see below for more informations)
|
||||||
|
- `props (optional, any)`: some initial values that are given as props. Useful
|
||||||
|
when the root component is configurable, or when testing sub components
|
||||||
|
|
||||||
|
Here are the various positions supported by Owl:
|
||||||
|
|
||||||
|
- `first-child`: with this option, the component will be prepended inside the target,
|
||||||
|
- `last-child` (default value): with this option, the component will be
|
||||||
|
appended in the target element,
|
||||||
|
- `self`: the target will be used as the root element for the component. This
|
||||||
|
means that the target has to be an HTMLElement (and not a document fragment).
|
||||||
|
In this situation, it is possible that the component cannot be unmounted. For
|
||||||
|
example, if its target is `document.body`.
|
||||||
|
|
||||||
|
The `mount` method returns a promise that resolves to the instance of the created
|
||||||
|
component.
|
||||||
@@ -18,7 +18,7 @@ class Child extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Parent extends Component {
|
class Parent extends Component {
|
||||||
static template = xml`<div><ComponentA a="state.a" b="'string'"/></div>`;
|
static template = xml`<div><Child a="state.a" b="'string'"/></div>`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
state = useState({ a: "fromparent" });
|
state = useState({ a: "fromparent" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 });
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
- [Dynamic Attributes](#dynamic-attributes)
|
- [Dynamic Attributes](#dynamic-attributes)
|
||||||
- [Loops](#loops)
|
- [Loops](#loops)
|
||||||
- [Rendering Sub Templates](#rendering-sub-templates)
|
- [Rendering Sub Templates](#rendering-sub-templates)
|
||||||
|
- [Dynamic Sub Templates](#dynamic-sub-templates)
|
||||||
- [Translations](#translations)
|
- [Translations](#translations)
|
||||||
- [Debugging](#debugging)
|
- [Debugging](#debugging)
|
||||||
|
|
||||||
@@ -452,6 +453,17 @@ are all equivalent:
|
|||||||
|
|
||||||
If there is no `t-key` directive, Owl will use the index as a default key.
|
If there is no `t-key` directive, Owl will use the index as a default key.
|
||||||
|
|
||||||
|
Note: the `t-foreach` directive only accepts arrays (lists) or objects. It does
|
||||||
|
not work with other iterables, such as `Set`. However, it is only a matter of
|
||||||
|
using the `...` javascript operator. For example:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<t t-foreach="...items" t-as="item">...</t>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `...` operator will convert the `Set` (or any other iterables) into a list,
|
||||||
|
which will work with Owl QWeb.
|
||||||
|
|
||||||
### Rendering Sub Templates
|
### Rendering Sub Templates
|
||||||
|
|
||||||
QWeb templates can be used for top level rendering, but they can also be used
|
QWeb templates can be used for top level rendering, but they can also be used
|
||||||
@@ -509,6 +521,22 @@ This can be used to define variables scoped to a sub template:
|
|||||||
<!-- "var" does not exist here -->
|
<!-- "var" does not exist here -->
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Dynamic sub templates
|
||||||
|
|
||||||
|
The `t-call` directive can also be used to dynamically call a sub template,
|
||||||
|
using string interpolation. For example:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<div t-name="main-template">
|
||||||
|
<t t-call="{{template}}">
|
||||||
|
<em>content</em>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, the name of the template is obtained from the `template` value in the
|
||||||
|
template rendering context.
|
||||||
|
|
||||||
### Translations
|
### Translations
|
||||||
|
|
||||||
By default, QWeb specify that templates should be translated. If this behaviour
|
By default, QWeb specify that templates should be translated. If this behaviour
|
||||||
|
|||||||
+17
-2
@@ -25,6 +25,8 @@ some sub template, but still be the owner. For example, a generic dialog compone
|
|||||||
will need to render some content, some footer, but with the parent as the
|
will need to render some content, some footer, but with the parent as the
|
||||||
rendering context.
|
rendering context.
|
||||||
|
|
||||||
|
Slots are inserted with the `t-slot` directive:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-name="Dialog" class="modal">
|
<div t-name="Dialog" class="modal">
|
||||||
<div class="modal-title"><t t-esc="props.title"/></div>
|
<div class="modal-title"><t t-esc="props.title"/></div>
|
||||||
@@ -62,7 +64,9 @@ 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
|
||||||
be considered the `default` slot. For example:
|
be considered the `default` slot. For example:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
@@ -77,7 +81,9 @@ be considered the `default` slot. For example:
|
|||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
Default content: slots can define a default content, in case the parent did not define them:
|
### Default content
|
||||||
|
|
||||||
|
Slots can define a default content, in case the parent did not define them:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<div t-name="Parent">
|
<div t-name="Parent">
|
||||||
@@ -94,3 +100,12 @@ Rendering context: the content of the slots is actually rendered with the
|
|||||||
rendering context corresponding to where it was defined, not where it is
|
rendering context corresponding to where it was defined, not where it is
|
||||||
positioned. This allows the user to define event handlers that will be bound
|
positioned. This allows the user to define event handlers that will be bound
|
||||||
to the correct component (usually, the grandparent of the slot content).
|
to the correct component (usually, the grandparent of the slot content).
|
||||||
|
|
||||||
|
### Dynamic Slots
|
||||||
|
|
||||||
|
The `t-slot` directive is actually able to use any expressions, using string
|
||||||
|
interplolation:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<t t-slot="{{current}}" />
|
||||||
|
```
|
||||||
|
|||||||
+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,20 +19,20 @@ 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 env = { qweb };
|
||||||
app.mount(document.body);
|
await mount(App, { env, target: document.body });
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
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 env = { qweb };
|
||||||
app.mount(document.body);
|
await mount(App, { env, target: document.body });
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+15
-15
@@ -1,22 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "@odoo/owl",
|
"name": "@odoo/owl",
|
||||||
"version": "1.0.6",
|
"version": "1.0.13",
|
||||||
"description": "Odoo Web Library (OWL)",
|
"description": "Odoo Web Library (OWL)",
|
||||||
"main": "dist/owl.js",
|
"main": "dist/owl.cjs.js",
|
||||||
|
"browser": "dist/owl.iife.js",
|
||||||
|
"module": "dist/owl.es.js",
|
||||||
"types": "dist/types/index.d.ts",
|
"types": "dist/types/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"dist/types/",
|
"dist"
|
||||||
"dist/owl.js"
|
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.15.3"
|
"node": ">=10.15.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:js": "tsc --target esnext --module es6 --outDir dist/owl",
|
"dev": "rollup -c",
|
||||||
"build:bundle": "rollup -c",
|
"build": "NODE_ENV=production rollup -c",
|
||||||
"build": "npm run build:js && npm run build:bundle",
|
|
||||||
"buildcommonjs": "npm run build:js && npm run build:bundle -- -f cjs",
|
|
||||||
"minify": "uglifyjs dist/owl.js -o dist/owl.min.js --compress --mangle",
|
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"tools:serve": "python3 tools/server.py || python tools/server.py",
|
"tools:serve": "python3 tools/server.py || python tools/server.py",
|
||||||
@@ -24,7 +22,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 && npm publish",
|
||||||
"release": "node tools/release.js"
|
"release": "node tools/release.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -37,9 +35,9 @@
|
|||||||
"url": "https://github.com/odoo/owl/issues"
|
"url": "https://github.com/odoo/owl/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/odoo/owl#readme",
|
"homepage": "https://github.com/odoo/owl#readme",
|
||||||
"dependencies": {},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^23.3.12",
|
"@types/jest": "^23.3.14",
|
||||||
|
"@types/node": "^14.11.8",
|
||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
"cpx": "^1.5.0",
|
"cpx": "^1.5.0",
|
||||||
"git-rev-sync": "^1.12.0",
|
"git-rev-sync": "^1.12.0",
|
||||||
@@ -48,9 +46,10 @@
|
|||||||
"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-terser": "^7.0.2",
|
||||||
|
"rollup-plugin-typescript2": "^0.27.3",
|
||||||
"sass": "^1.16.1",
|
"sass": "^1.16.1",
|
||||||
"source-map-support": "^0.5.10",
|
"source-map-support": "^0.5.10",
|
||||||
"ts-jest": "^23.10.5",
|
"ts-jest": "^23.10.5",
|
||||||
@@ -77,6 +76,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"printWidth": 100
|
"printWidth": 100,
|
||||||
|
"endOfLine": "auto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# 🦉 OWL Roadmap 🦉
|
# 🦉 OWL Roadmap 🦉
|
||||||
|
|
||||||
- Current version: 1.0.6
|
- Current version: 1.0.13
|
||||||
- Status: stable
|
- Status: stable
|
||||||
|
|
||||||
This roadmap is only an attempt at predicting Owl's future. Everything may
|
This roadmap is only an attempt at predicting Owl's future. Everything may
|
||||||
|
|||||||
+66
-10
@@ -1,14 +1,70 @@
|
|||||||
import { version } from "./package.json";
|
import pkg from "./package.json";
|
||||||
import git from "git-rev-sync";
|
import git from "git-rev-sync";
|
||||||
|
import typescript from 'rollup-plugin-typescript2';
|
||||||
|
import { terser } from "rollup-plugin-terser";
|
||||||
|
|
||||||
|
const name = "owl";
|
||||||
|
const extend = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Meta data to be added on the __info__ object.
|
||||||
|
* Used to let external tools know the current owl version.
|
||||||
|
*/
|
||||||
|
const outro = `
|
||||||
|
__info__.version = '${pkg.version}';
|
||||||
|
__info__.date = '${new Date().toISOString()}';
|
||||||
|
__info__.hash = '${git.short()}';
|
||||||
|
__info__.url = 'https://github.com/odoo/owl';
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate from a string depicting a path a new path for the minified version.
|
||||||
|
* @param {string} pkgFileName file name
|
||||||
|
*/
|
||||||
|
function generateMinifiedNameFromPkgName(pkgFileName) {
|
||||||
|
const parts = pkgFileName.split('.');
|
||||||
|
parts.splice(parts.length - 1, 0, "min");
|
||||||
|
return parts.join('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the rollup config based on the arguments
|
||||||
|
* @param {string} format format of the bundle
|
||||||
|
* @param {string} generatedFileName generated file name
|
||||||
|
* @param {boolean} minified should it be minified
|
||||||
|
*/
|
||||||
|
function getConfigForFormat(format, generatedFileName, minified = false) {
|
||||||
|
return {
|
||||||
|
file: minified ? generateMinifiedNameFromPkgName(generatedFileName) : generatedFileName,
|
||||||
|
format: format,
|
||||||
|
name: name,
|
||||||
|
extend: extend,
|
||||||
|
outro: outro,
|
||||||
|
plugins: minified ? [terser()] : [],
|
||||||
|
indent: ' ', // indent with 4 spaces
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// rollup.config.js
|
|
||||||
export default {
|
export default {
|
||||||
input: "dist/owl/index.js",
|
input: "src/index.ts",
|
||||||
output: {
|
output: [
|
||||||
file: "dist/owl.js",
|
|
||||||
format: "iife",
|
/**
|
||||||
name: "owl",
|
* Read about module formats:
|
||||||
extend: true,
|
* https://auth0.com/blog/javascript-module-systems-showdown/
|
||||||
outro: `exports.__info__.version = '${version}';\nexports.__info__.date = '${new Date().toISOString()}';\nexports.__info__.hash = '${git.short()}';\nexports.__info__.url = 'https://github.com/odoo/owl';`
|
* https://medium.com/@kelin2025/so-you-wanna-use-es6-modules-714f48b3a953
|
||||||
}
|
*/
|
||||||
|
|
||||||
|
getConfigForFormat('esm', pkg.module),
|
||||||
|
getConfigForFormat('esm', pkg.module, true),
|
||||||
|
getConfigForFormat('cjs', pkg.main),
|
||||||
|
getConfigForFormat('cjs', pkg.main, true),
|
||||||
|
getConfigForFormat('iife', pkg.browser),
|
||||||
|
getConfigForFormat('iife', pkg.browser, true),
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
typescript({
|
||||||
|
useTsconfigDeclarationDir: true
|
||||||
|
}),
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -19,5 +19,5 @@ export const browser: Browser = {
|
|||||||
random: Math.random,
|
random: Math.random,
|
||||||
Date: window.Date,
|
Date: window.Date,
|
||||||
fetch: (window.fetch || (() => {})).bind(window),
|
fetch: (window.fetch || (() => {})).bind(window),
|
||||||
localStorage: window.localStorage
|
localStorage: window.localStorage,
|
||||||
};
|
};
|
||||||
|
|||||||
+105
-15
@@ -8,6 +8,7 @@ import "./props_validation";
|
|||||||
import { Scheduler, scheduler } from "./scheduler";
|
import { Scheduler, scheduler } from "./scheduler";
|
||||||
import { activateSheet } from "./styles";
|
import { activateSheet } from "./styles";
|
||||||
import { Browser, browser } from "../browser";
|
import { Browser, browser } from "../browser";
|
||||||
|
import { onMounted, onPatched } from "../hooks";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Owl Component System
|
* Owl Component System
|
||||||
@@ -91,8 +92,24 @@ interface Internal<T extends Env> {
|
|||||||
refs: { [key: string]: Component<any, T> | HTMLElement | undefined } | null;
|
refs: { [key: string]: Component<any, T> | HTMLElement | undefined } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DevToolsAccess {
|
||||||
|
props?: any;
|
||||||
|
defaultProps?: any;
|
||||||
|
template?: string | null;
|
||||||
|
state: Observer;
|
||||||
|
tag: String;
|
||||||
|
depth: number,
|
||||||
|
}
|
||||||
|
|
||||||
export const portalSymbol = Symbol("portal"); // FIXME
|
export const portalSymbol = Symbol("portal"); // FIXME
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It is required for the dev tools to have access to the __owl__ element.
|
||||||
|
*/
|
||||||
|
interface HTMLElementWithDevToolsAccess extends HTMLElement {
|
||||||
|
__owl_devtools__: DevToolsAccess
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Component
|
// Component
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -110,11 +127,13 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
|||||||
// expose scheduler s.t. it can be mocked for testing purposes
|
// expose scheduler s.t. it can be mocked for testing purposes
|
||||||
static scheduler: Scheduler = scheduler;
|
static scheduler: Scheduler = scheduler;
|
||||||
|
|
||||||
|
__devtools__: DevToolsAccess;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `el` is the root element of the component. Note that it could be null:
|
* The `el` is the root element of the component. Note that it could be null:
|
||||||
* this is the case if the component is not mounted yet, or is destroyed.
|
* this is the case if the component is not mounted yet, or is destroyed.
|
||||||
*/
|
*/
|
||||||
get el(): HTMLElement | null {
|
get el(): HTMLElementWithDevToolsAccess | null {
|
||||||
return this.__owl__.vnode ? (<any>this).__owl__.vnode.elm : null;
|
return this.__owl__.vnode ? (<any>this).__owl__.vnode.elm : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,11 +222,29 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DevTools hooks
|
||||||
|
onMounted(() => {
|
||||||
|
this.__devtools__ = {
|
||||||
|
depth: this.__owl__.depth,
|
||||||
|
state: this.__owl__.observer,
|
||||||
|
tag: this.constructor.name
|
||||||
|
};
|
||||||
|
this.__devtools__.defaultProps = defaultProps;
|
||||||
|
this.__devtools__.props = this.props;
|
||||||
|
this.__devtools__.template = template;
|
||||||
|
this.el.__owl_devtools__ = this.__devtools__;
|
||||||
|
})
|
||||||
|
onPatched(() => {
|
||||||
|
this.__devtools__.depth = this.__owl__.depth,
|
||||||
|
this.el.__owl_devtools__ = this.__devtools__;
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -316,6 +353,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) {
|
||||||
@@ -430,8 +470,8 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
|||||||
* up to the parent DOM nodes. Thus, it must be called between mounted() and
|
* up to the parent DOM nodes. Thus, it must be called between mounted() and
|
||||||
* willUnmount().
|
* willUnmount().
|
||||||
*/
|
*/
|
||||||
trigger(eventType: string, payload?: any) {
|
trigger<T = any>(eventType: string, payload?: T) {
|
||||||
this.__trigger(this, eventType, payload);
|
this.__trigger<T>(this, eventType, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
@@ -493,9 +533,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,12 +549,12 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
|||||||
* Private trigger method, allows to choose the component which triggered
|
* Private trigger method, allows to choose the component which triggered
|
||||||
* the event in the first place
|
* the event in the first place
|
||||||
*/
|
*/
|
||||||
__trigger(component: Component, eventType: string, payload?: any) {
|
__trigger<T>(component: Component, eventType: string, payload?: T) {
|
||||||
if (this.el) {
|
if (this.el) {
|
||||||
const ev = new OwlEvent(component, eventType, {
|
const ev = new OwlEvent<T>(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) {
|
||||||
@@ -549,7 +589,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;
|
||||||
@@ -646,16 +686,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) {
|
||||||
@@ -707,3 +766,34 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MountParameters {
|
||||||
|
env?: Env;
|
||||||
|
target: HTMLElement | DocumentFragment;
|
||||||
|
props?: any;
|
||||||
|
position?: MountOptions["position"];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Type<T> extends Function {
|
||||||
|
new (...args: any[]): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function mount<T extends Type<Component>>(
|
||||||
|
C: T,
|
||||||
|
params: MountParameters
|
||||||
|
): Promise<InstanceType<T>> {
|
||||||
|
const { env, props, target } = params;
|
||||||
|
let origEnv = C.hasOwnProperty("env") ? (C as any).env : null;
|
||||||
|
if (env) {
|
||||||
|
((C as any) as typeof Component).env = env;
|
||||||
|
}
|
||||||
|
const component: Component = new C(null, props);
|
||||||
|
if (origEnv) {
|
||||||
|
(C as any).env = origEnv;
|
||||||
|
} else {
|
||||||
|
delete (C as any).env;
|
||||||
|
}
|
||||||
|
const position = params.position || "last-child";
|
||||||
|
await component.mount(target, { position });
|
||||||
|
return component as any;
|
||||||
|
}
|
||||||
|
|||||||
+40
-21
@@ -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;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -227,7 +227,9 @@ QWeb.addDirective({
|
|||||||
if (name.startsWith("t-on-")) {
|
if (name.startsWith("t-on-")) {
|
||||||
events.push([name, value]);
|
events.push([name, value]);
|
||||||
} else if (name === "t-transition") {
|
} else if (name === "t-transition") {
|
||||||
|
if (QWeb.enableTransitions) {
|
||||||
transition = value;
|
transition = value;
|
||||||
|
}
|
||||||
} else if (!name.startsWith("t-")) {
|
} else if (!name.startsWith("t-")) {
|
||||||
if (name !== "class" && name !== "style") {
|
if (name !== "class" && name !== "style") {
|
||||||
// this is a prop!
|
// this is a prop!
|
||||||
@@ -238,7 +240,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 +282,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 +300,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,7 +402,6 @@ QWeb.addDirective({
|
|||||||
|
|
||||||
if (hasSlots) {
|
if (hasSlots) {
|
||||||
const clone = <Element>node.cloneNode(true);
|
const clone = <Element>node.cloneNode(true);
|
||||||
const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]"));
|
|
||||||
|
|
||||||
// The next code is a fallback for compatibility reason. It accepts 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
|
// elements that are direct children with a non empty body as nodes defining
|
||||||
@@ -410,27 +411,45 @@ QWeb.addDirective({
|
|||||||
// code using slots. This will be removed in v2.0 someday. Meanwhile,
|
// 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
|
// please use t-set-slot everywhere you need to set the content of a
|
||||||
// slot.
|
// slot.
|
||||||
for (let el of clone.children) {
|
for (let node of clone.children) {
|
||||||
if (el.getAttribute("t-set") && el.hasChildNodes()) {
|
if (node.hasAttribute("t-set") && node.hasChildNodes()) {
|
||||||
slotNodes.push(el);
|
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];
|
||||||
slotNode.parentElement!.removeChild(slotNode);
|
// check if this is defined in a sub component (in which case it should
|
||||||
let key = slotNode.getAttribute("t-set-slot")!;
|
// be ignored)
|
||||||
slotNode.removeAttribute("t-set-slot");
|
let el = slotNode.parentElement;
|
||||||
|
let isInSubComponent = false;
|
||||||
// here again, this code should be removed when we stop supporting
|
while (el !== clone) {
|
||||||
// using t-set to define the content of named slots.
|
if (
|
||||||
if (!key) {
|
el!.hasAttribute("t-component") ||
|
||||||
key = slotNode.getAttribute("t-set")!;
|
el!.tagName[0] === el!.tagName[0].toUpperCase()
|
||||||
slotNode.removeAttribute("t-set");
|
) {
|
||||||
|
isInSubComponent = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx);
|
el = el.parentElement;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
const slotFn = qweb._compile(`slot_${key}_template`, { elem: slotNode, hasParent: true });
|
||||||
QWeb.slots[`${slotId}_${key}`] = slotFn;
|
QWeb.slots[`${slotId}_${key}`] = slotFn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -439,7 +458,7 @@ QWeb.addDirective({
|
|||||||
for (let child of Object.values(clone.childNodes)) {
|
for (let child of Object.values(clone.childNodes)) {
|
||||||
t.appendChild(child);
|
t.appendChild(child);
|
||||||
}
|
}
|
||||||
const slotFn = qweb._compile(`slot_default_template`, t, ctx);
|
const slotFn = qweb._compile(`slot_default_template`, { elem: t, hasParent: true });
|
||||||
QWeb.slots[`${slotId}_default`] = slotFn;
|
QWeb.slots[`${slotId}_default`] = slotFn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -469,5 +488,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;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+10
-2
@@ -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;
|
||||||
};
|
};
|
||||||
@@ -238,13 +238,21 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (fiber === component.__owl__.currentFiber) {
|
||||||
component.__owl__.currentFiber = null;
|
component.__owl__.currentFiber = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// insert into the DOM (mount case)
|
// insert into the DOM (mount case)
|
||||||
let inDOM = false;
|
let inDOM = false;
|
||||||
@@ -280,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;
|
||||||
|
|||||||
@@ -50,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();
|
||||||
@@ -60,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();
|
||||||
@@ -76,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;
|
||||||
|
|||||||
@@ -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[] = [];
|
||||||
|
|||||||
+11
-1
@@ -8,6 +8,7 @@ import { QWeb } from "./qweb/index";
|
|||||||
|
|
||||||
interface Config {
|
interface Config {
|
||||||
mode: string;
|
mode: string;
|
||||||
|
enableTransitions: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {} as Config;
|
export const config = {} as Config;
|
||||||
@@ -26,5 +27,14 @@ Object.defineProperty(config, "mode", {
|
|||||||
} else {
|
} else {
|
||||||
console.log(`Owl is now running in 'prod' mode.`);
|
console.log(`Owl is now running in 'prod' mode.`);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(config, "enableTransitions", {
|
||||||
|
get() {
|
||||||
|
return QWeb.enableTransitions;
|
||||||
|
},
|
||||||
|
set(value: boolean) {
|
||||||
|
QWeb.enableTransitions = value;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,15 @@ export class Observer {
|
|||||||
rev: number = 1;
|
rev: number = 1;
|
||||||
allowMutations: boolean = true;
|
allowMutations: boolean = true;
|
||||||
weakMap: WeakMap<any, any> = new WeakMap();
|
weakMap: WeakMap<any, any> = new WeakMap();
|
||||||
|
|
||||||
notifyCB() {}
|
notifyCB() {}
|
||||||
|
|
||||||
observe<T>(value: T, parent?: any): T {
|
observe<T>(value: T, parent?: any): T {
|
||||||
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 +71,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);
|
||||||
|
|||||||
+12
-12
@@ -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,11 +65,11 @@ 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 {
|
||||||
@@ -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;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -19,7 +19,7 @@ import { Link } from "./router/link";
|
|||||||
import { RouteComponent } from "./router/route_component";
|
import { RouteComponent } from "./router/route_component";
|
||||||
import { Router } from "./router/router";
|
import { Router } from "./router/router";
|
||||||
|
|
||||||
export { Component } from "./component/component";
|
export { Component, mount } from "./component/component";
|
||||||
export { QWeb };
|
export { QWeb };
|
||||||
export { config };
|
export { config };
|
||||||
|
|
||||||
@@ -35,6 +35,7 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
+41
-29
@@ -1,4 +1,4 @@
|
|||||||
import { CompilationContext } from "./compilation_context";
|
import { CompilationContext, INTERP_REGEXP } from "./compilation_context";
|
||||||
import { QWeb } from "./qweb";
|
import { QWeb } from "./qweb";
|
||||||
import { htmlToVDOM } from "../vdom/html_to_vdom";
|
import { htmlToVDOM } from "../vdom/html_to_vdom";
|
||||||
import { QWebVar } from "./expression_parser";
|
import { QWebVar } from "./expression_parser";
|
||||||
@@ -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,77 +223,88 @@ 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 isDynamic = INTERP_REGEXP.test(subTemplate);
|
||||||
const nodeTemplate = qweb.templates[subTemplate];
|
const nodeTemplate = qweb.templates[subTemplate];
|
||||||
if (!nodeTemplate) {
|
if (!isDynamic && !nodeTemplate) {
|
||||||
throw new Error(`Cannot find template "${subTemplate}" (t-call)`);
|
throw new Error(`Cannot find template "${subTemplate}" (t-call)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: compile target template in sub templates
|
// Step 2: compile target template in sub templates
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
if (!qweb.subTemplates[subTemplate]) {
|
let subIdstr: string;
|
||||||
qweb.subTemplates[subTemplate] = true;
|
if (isDynamic) {
|
||||||
const subTemplateFn = qweb._compile(subTemplate, nodeTemplate.elem, ctx, true);
|
const _id = ctx.generateID();
|
||||||
qweb.subTemplates[subTemplate] = subTemplateFn;
|
ctx.addLine(`let tname${_id} = ${ctx.interpolate(subTemplate)};`);
|
||||||
|
ctx.addLine(`let tid${_id} = this.subTemplates[tname${_id}];`);
|
||||||
|
ctx.addIf(`!tid${_id}`);
|
||||||
|
ctx.addLine(`tid${_id} = this.constructor.nextId++;`);
|
||||||
|
ctx.addLine(`this.subTemplates[tname${_id}] = tid${_id};`);
|
||||||
|
ctx.addLine(
|
||||||
|
`this.constructor.subTemplates[tid${_id}] = this._compile(tname${_id}, {hasParent: true, defineKey: true});`
|
||||||
|
);
|
||||||
|
ctx.closeIf();
|
||||||
|
subIdstr = `tid${_id}`;
|
||||||
|
} else {
|
||||||
|
let subId = qweb.subTemplates[subTemplate];
|
||||||
|
if (!subId) {
|
||||||
|
subId = QWeb.nextId++;
|
||||||
|
qweb.subTemplates[subTemplate] = subId;
|
||||||
|
const subTemplateFn = qweb._compile(subTemplate, { hasParent: true, defineKey: true });
|
||||||
|
QWeb.subTemplates[subId] = subTemplateFn;
|
||||||
|
}
|
||||||
|
subIdstr = `'${subId}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
const parentNode = ctx.parentNode;
|
|
||||||
ctx.parentNode = "__0";
|
|
||||||
// this local scope is intended to trap c__0
|
// this local scope is intended to trap c__0
|
||||||
ctx.addLine(`{`);
|
ctx.addLine(`{`);
|
||||||
ctx.indent();
|
ctx.indent();
|
||||||
ctx.addLine("let c__0 = [];");
|
ctx.addLine("let c__0 = [];");
|
||||||
qweb._compileNode(nodeCopy, ctx);
|
qweb._compileNode(nodeCopy, ctx.subContext("parentNode", "__0"));
|
||||||
ctx.rootContext.shouldDefineUtils = true;
|
ctx.rootContext.shouldDefineUtils = true;
|
||||||
ctx.addLine("scope[utils.zero] = c__0;");
|
ctx.addLine("scope[utils.zero] = c__0;");
|
||||||
ctx.parentNode = parentNode;
|
|
||||||
ctx.dedent();
|
ctx.dedent();
|
||||||
ctx.addLine(`}`);
|
ctx.addLine(`}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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[${subIdstr}].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[${subIdstr}].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;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -353,7 +365,7 @@ QWeb.addDirective({
|
|||||||
ctx.addLine("}");
|
ctx.addLine("}");
|
||||||
ctx.stopProtectScope(varsID);
|
ctx.stopProtectScope(varsID);
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -364,7 +376,7 @@ QWeb.addDirective({
|
|||||||
priority: 1,
|
priority: 1,
|
||||||
atNodeEncounter({ ctx }) {
|
atNodeEncounter({ ctx }) {
|
||||||
ctx.addLine("debugger;");
|
ctx.addLine("debugger;");
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -376,5 +388,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,7 +189,7 @@ 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 {
|
||||||
|
|||||||
@@ -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("");
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-23
@@ -1,4 +1,5 @@
|
|||||||
import { VNode } from "../vdom/index";
|
import { VNode } from "../vdom/index";
|
||||||
|
import { INTERP_REGEXP } from "./compilation_context";
|
||||||
import { QWeb } from "./qweb";
|
import { QWeb } from "./qweb";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,7 +24,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 +51,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 +75,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 +91,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 +106,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 +140,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!);
|
||||||
|
|
||||||
@@ -205,16 +206,19 @@ QWeb.addDirective({
|
|||||||
name: "transition",
|
name: "transition",
|
||||||
priority: 96,
|
priority: 96,
|
||||||
atNodeCreation({ ctx, value, addNodeHook }) {
|
atNodeCreation({ ctx, value, addNodeHook }) {
|
||||||
|
if (!QWeb.enableTransitions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ctx.rootContext.shouldDefineUtils = true;
|
ctx.rootContext.shouldDefineUtils = true;
|
||||||
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]);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -225,8 +229,9 @@ QWeb.addDirective({
|
|||||||
priority: 80,
|
priority: 80,
|
||||||
atNodeEncounter({ ctx, value, node, qweb }): boolean {
|
atNodeEncounter({ ctx, value, node, qweb }): boolean {
|
||||||
const slotKey = ctx.generateID();
|
const slotKey = ctx.generateID();
|
||||||
|
const valueExpr = value.match(INTERP_REGEXP) ? ctx.interpolate(value) : `'${value}'`;
|
||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`const slot${slotKey} = this.constructor.slots[context.__owl__.slotId + '_' + '${value}'];`
|
`const slot${slotKey} = this.constructor.slots[context.__owl__.slotId + '_' + ${valueExpr}];`
|
||||||
);
|
);
|
||||||
ctx.addIf(`slot${slotKey}`);
|
ctx.addIf(`slot${slotKey}`);
|
||||||
let parentNode = `c${ctx.parentNode}`;
|
let parentNode = `c${ctx.parentNode}`;
|
||||||
@@ -251,17 +256,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 +278,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 +339,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 +363,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;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+87
-55
@@ -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__;
|
||||||
@@ -145,7 +151,7 @@ const UTILS: Utils = {
|
|||||||
obj = newObj;
|
obj = newObj;
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseXML(xml: string): Document {
|
function parseXML(xml: string): Document {
|
||||||
@@ -195,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[] = [];
|
||||||
|
|
||||||
@@ -206,16 +212,19 @@ export class QWeb extends EventBus {
|
|||||||
h = h;
|
h = h;
|
||||||
// dev mode enables better error messages or more costly validations
|
// dev mode enables better error messages or more costly validations
|
||||||
static dev: boolean = false;
|
static dev: boolean = false;
|
||||||
|
static enableTransitions: boolean = true;
|
||||||
|
|
||||||
// slots contains sub templates defined with t-set inside t-component nodes, and
|
// slots contains sub templates defined with t-set inside t-component nodes, and
|
||||||
// are meant to be used by the t-slot directive.
|
// are meant to be used by the t-slot directive.
|
||||||
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"];
|
||||||
@@ -239,7 +248,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,11 +312,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);
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -317,10 +326,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"))) {
|
||||||
@@ -330,7 +339,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
|
||||||
) {
|
) {
|
||||||
@@ -409,30 +418,33 @@ export class QWeb extends EventBus {
|
|||||||
|
|
||||||
_compile(
|
_compile(
|
||||||
name: string,
|
name: string,
|
||||||
elem: Element,
|
options: {
|
||||||
parentContext?: CompilationContext,
|
elem?: Element;
|
||||||
defineKey?: boolean
|
hasParent?: boolean;
|
||||||
|
defineKey?: boolean;
|
||||||
|
} = {}
|
||||||
): CompiledTemplate {
|
): CompiledTemplate {
|
||||||
|
const elem = options.elem || this.templates[name].elem;
|
||||||
const isDebug = elem.attributes.hasOwnProperty("t-debug");
|
const isDebug = elem.attributes.hasOwnProperty("t-debug");
|
||||||
const ctx = new CompilationContext(name);
|
const ctx = new CompilationContext(name);
|
||||||
if (elem.tagName !== "t") {
|
if (elem.tagName !== "t") {
|
||||||
ctx.shouldDefineResult = false;
|
ctx.shouldDefineResult = false;
|
||||||
}
|
}
|
||||||
if (parentContext) {
|
if (options.hasParent) {
|
||||||
ctx.variables = Object.create(parentContext.variables);
|
ctx.variables = Object.create(null);
|
||||||
ctx.parentNode = parentContext.parentNode || ctx.generateID();
|
ctx.parentNode = ctx.generateID();
|
||||||
ctx.allowMultipleRoots = true;
|
ctx.allowMultipleRoots = true;
|
||||||
ctx.hasParentWidget = true;
|
ctx.hasParentWidget = true;
|
||||||
ctx.shouldDefineResult = false;
|
ctx.shouldDefineResult = false;
|
||||||
ctx.addLine(`let c${ctx.parentNode} = extra.parentNode;`);
|
ctx.addLine(`let c${ctx.parentNode} = extra.parentNode;`);
|
||||||
if (defineKey) {
|
if (options.defineKey) {
|
||||||
ctx.addLine(`let key0 = extra.key || "";`);
|
ctx.addLine(`let key0 = extra.key || "";`);
|
||||||
ctx.hasKey0 = true;
|
ctx.hasKey0 = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._compileNode(elem, ctx);
|
this._compileNode(elem, ctx);
|
||||||
|
|
||||||
if (!parentContext) {
|
if (!options.hasParent) {
|
||||||
if (ctx.shouldDefineResult) {
|
if (ctx.shouldDefineResult) {
|
||||||
ctx.addLine(`return result;`);
|
ctx.addLine(`return result;`);
|
||||||
} else {
|
} else {
|
||||||
@@ -507,10 +519,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(
|
||||||
@@ -582,7 +601,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) {
|
||||||
@@ -597,7 +616,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);
|
||||||
};
|
};
|
||||||
@@ -611,7 +630,7 @@ export class QWeb extends EventBus {
|
|||||||
fullName,
|
fullName,
|
||||||
value,
|
value,
|
||||||
nodeID,
|
nodeID,
|
||||||
addNodeHook
|
addNodeHook,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -664,22 +683,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) {
|
||||||
|
case "input":
|
||||||
let type = (<Element>node).getAttribute("type");
|
let type = (<Element>node).getAttribute("type");
|
||||||
if (type === "checkbox" || type === "radio") {
|
if (type === "checkbox" || type === "radio") {
|
||||||
|
if (key === "checked" || key === "indeterminate") {
|
||||||
isProp = true;
|
isProp = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node.nodeName === "option" && key === "selected") {
|
if (key === "value" || key === "readonly" || key === "disabled") {
|
||||||
isProp = true;
|
isProp = true;
|
||||||
}
|
}
|
||||||
if (key === "disabled" && DISABLED_TAGS.indexOf(node.nodeName) > -1) {
|
break;
|
||||||
isProp = true;
|
case "option":
|
||||||
}
|
isProp = key === "selected" || key === "disabled";
|
||||||
if ((key === "readonly" && node.nodeName === "input") || node.nodeName === "textarea") {
|
break;
|
||||||
isProp = true;
|
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}`);
|
||||||
@@ -702,11 +730,15 @@ 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(",");
|
||||||
|
if (classObj) {
|
||||||
|
ctx.addLine(`Object.assign(${classObj}, {${classDef}})`);
|
||||||
|
} else {
|
||||||
classObj = `_${ctx.generateID()}`;
|
classObj = `_${ctx.generateID()}`;
|
||||||
ctx.addLine(`let ${classObj} = {${classDef}};`);
|
ctx.addLine(`let ${classObj} = {${classDef}};`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.addLine(`let _${attID} = '${escapeQuotes(value)}';`);
|
ctx.addLine(`let _${attID} = '${escapeQuotes(value)}';`);
|
||||||
if (!name.match(/^[a-zA-Z]+$/)) {
|
if (!name.match(/^[a-zA-Z]+$/)) {
|
||||||
@@ -714,7 +746,7 @@ export class QWeb extends EventBus {
|
|||||||
name = '"' + name + '"';
|
name = '"' + name + '"';
|
||||||
}
|
}
|
||||||
attrs.push(`${name}: _${attID}`);
|
attrs.push(`${name}: _${attID}`);
|
||||||
handleBooleanProps(name, attID);
|
handleProperties(name, attID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,12 +778,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,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);
|
||||||
@@ -198,7 +198,7 @@ export class Router {
|
|||||||
return {
|
return {
|
||||||
type: "match",
|
type: "match",
|
||||||
route: route,
|
route: route,
|
||||||
params: params
|
params: params,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,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" };
|
||||||
|
|||||||
+7
-7
@@ -53,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);
|
||||||
@@ -70,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
|
||||||
);
|
);
|
||||||
@@ -113,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;
|
||||||
@@ -126,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);
|
||||||
};
|
};
|
||||||
@@ -148,7 +148,7 @@ export function useStore(selector, options: SelectorOptions = {}): any {
|
|||||||
},
|
},
|
||||||
has(target, k) {
|
has(target, k) {
|
||||||
return k in result;
|
return k in result;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -13,13 +13,13 @@
|
|||||||
import { browser } from "./browser";
|
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> } = {};
|
||||||
@@ -28,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];
|
||||||
@@ -75,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() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { VNode, h } from "./vdom";
|
import { VNode, h, addNS } from "./vdom";
|
||||||
|
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
|
|
||||||
@@ -13,6 +13,9 @@ export function htmlToVDOM(html: string): VNode[] {
|
|||||||
|
|
||||||
function htmlToVNode(node: ChildNode): VNode {
|
function htmlToVNode(node: ChildNode): VNode {
|
||||||
if (!(node instanceof Element)) {
|
if (!(node instanceof Element)) {
|
||||||
|
if (node instanceof Comment) {
|
||||||
|
return h("!", node.textContent);
|
||||||
|
}
|
||||||
return { text: node.textContent! } as VNode;
|
return { text: node.textContent! } as VNode;
|
||||||
}
|
}
|
||||||
const attrs = {};
|
const attrs = {};
|
||||||
@@ -23,5 +26,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;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
+20
-20
@@ -1,15 +1,15 @@
|
|||||||
import { Component, Env } from "../src/component/component";
|
import { Component, Env } from "../src/component/component";
|
||||||
|
import { useRef, useState } from "../src/hooks";
|
||||||
import { QWeb } from "../src/qweb/index";
|
import { QWeb } from "../src/qweb/index";
|
||||||
import { useState, useRef } from "../src/hooks";
|
|
||||||
import { xml } from "../src/tags";
|
import { xml } from "../src/tags";
|
||||||
import {
|
import {
|
||||||
makeDeferred,
|
makeDeferred,
|
||||||
makeTestFixture,
|
|
||||||
makeTestEnv,
|
makeTestEnv,
|
||||||
|
makeTestFixture,
|
||||||
|
nextFrame,
|
||||||
patchNextFrame,
|
patchNextFrame,
|
||||||
renderToDOM,
|
renderToDOM,
|
||||||
unpatchNextFrame,
|
unpatchNextFrame,
|
||||||
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,36 +413,36 @@ 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);
|
||||||
|
|
||||||
widget.state.flag = true;
|
widget.state.flag = true;
|
||||||
|
|
||||||
await nextTick();
|
await nextFrame();
|
||||||
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend"));
|
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend"));
|
||||||
expect(fixture.innerHTML).toBe('<div><span class="">blue</span></div>');
|
expect(fixture.innerHTML).toBe('<div><span class="">blue</span></div>');
|
||||||
expect(QWeb.utils.transitionInsert).toBeCalledTimes(1);
|
expect(QWeb.utils.transitionInsert).toBeCalledTimes(1);
|
||||||
|
|
||||||
widget.state.flag = false;
|
widget.state.flag = false;
|
||||||
await nextTick();
|
await nextFrame();
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
'<div><span class="chimay-leave-active chimay-leave-to" data-owl-key="__3__">blue</span></div>'
|
'<div><span class="chimay-leave-active chimay-leave-to" data-owl-key="__3__">blue</span></div>'
|
||||||
);
|
);
|
||||||
expect(QWeb.utils.transitionInsert).toBeCalledTimes(1);
|
expect(QWeb.utils.transitionInsert).toBeCalledTimes(1);
|
||||||
|
|
||||||
widget.state.flag = true;
|
widget.state.flag = true;
|
||||||
await nextTick();
|
await nextFrame();
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
'<div><span class="chimay-enter-active chimay-enter-to" data-owl-key="__3__">blue</span></div>'
|
'<div><span class="chimay-enter-active chimay-enter-to" data-owl-key="__3__">blue</span></div>'
|
||||||
);
|
);
|
||||||
expect(QWeb.utils.transitionInsert).toBeCalledTimes(2);
|
expect(QWeb.utils.transitionInsert).toBeCalledTimes(2);
|
||||||
|
|
||||||
widget.state.flag = false;
|
widget.state.flag = false;
|
||||||
await nextTick();
|
await nextFrame();
|
||||||
widget.state.flag = true;
|
widget.state.flag = true;
|
||||||
await nextTick();
|
await nextFrame();
|
||||||
|
|
||||||
expect(QWeb.utils.transitionInsert).toBeCalledTimes(3);
|
expect(QWeb.utils.transitionInsert).toBeCalledTimes(3);
|
||||||
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend"));
|
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend"));
|
||||||
|
|||||||
@@ -534,11 +534,11 @@ exports[`other directives with t-component slot setted value (with t-set) not ac
|
|||||||
w3.__owl__.pvnode = pvnode;
|
w3.__owl__.pvnode = pvnode;
|
||||||
}
|
}
|
||||||
w3.__owl__.parentLastFiberId = extra.fiber.id;
|
w3.__owl__.parentLastFiberId = extra.fiber.id;
|
||||||
let c5 = [], p5 = {key:5};
|
let c6 = [], p6 = {key:6};
|
||||||
let vn5 = h('p', p5, c5);
|
let vn6 = h('p', p6, c6);
|
||||||
c1.push(vn5);
|
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;
|
||||||
}"
|
}"
|
||||||
@@ -1253,12 +1253,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 _origScope6 = 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: '__7__'}));
|
||||||
|
scope = _origScope6;
|
||||||
|
let c8 = [], p8 = {key:8};
|
||||||
|
let vn8 = h('p', p8, c8);
|
||||||
|
c1.push(vn8);
|
||||||
if (scope.iter != null) {
|
if (scope.iter != null) {
|
||||||
c5.push({text: scope.iter});
|
c8.push({text: scope.iter});
|
||||||
}
|
}
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -1280,23 +1284,23 @@ 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 _origScope6 = scope;
|
||||||
let _origScope4 = scope;
|
|
||||||
scope = Object.create(scope);
|
scope = Object.create(scope);
|
||||||
scope.__access_mode__ = 'ro';
|
scope.__access_mode__ = 'ro';
|
||||||
|
{
|
||||||
{
|
{
|
||||||
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: '__7__'}));
|
||||||
scope = _origScope4;
|
|
||||||
}
|
}
|
||||||
let c6 = [], p6 = {key:6};
|
scope = _origScope6;
|
||||||
let vn6 = h('p', p6, c6);
|
let c8 = [], p8 = {key:8};
|
||||||
c1.push(vn6);
|
let vn8 = h('p', p8, c8);
|
||||||
|
c1.push(vn8);
|
||||||
if (scope.iter != null) {
|
if (scope.iter != null) {
|
||||||
c6.push({text: scope.iter});
|
c8.push({text: scope.iter});
|
||||||
}
|
}
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -1401,6 +1405,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
|
||||||
) {
|
) {
|
||||||
@@ -1410,7 +1455,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 _origScope5 = 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: '__6__'}));
|
||||||
|
scope = _origScope5;
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -1525,15 +1574,15 @@ exports[`t-call handlers are properly bound through a t-call 1`] = `
|
|||||||
// Template name: \\"sub\\"
|
// Template name: \\"sub\\"
|
||||||
let utils = this.constructor.utils;
|
let utils = this.constructor.utils;
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c2 = extra.parentNode;
|
||||||
let key0 = extra.key || \\"\\";
|
let key0 = extra.key || \\"\\";
|
||||||
let c2 = [], p2 = {key:\`\${key0}_2\`,on:{}};
|
let c3 = [], p3 = {key:\`\${key0}_3\`,on:{}};
|
||||||
let vn2 = h('p', p2, c2);
|
let vn3 = h('p', p3, c3);
|
||||||
c1.push(vn2);
|
c2.push(vn3);
|
||||||
let k3 = \`click__3__\${key0}__\`;
|
let k4 = \`click__4__\${key0}__\`;
|
||||||
extra.handlers[k3] = extra.handlers[k3] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['update'](e);};
|
extra.handlers[k4] = extra.handlers[k4] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['update'](e);};
|
||||||
p2.on['click'] = extra.handlers[k3];
|
p3.on['click'] = extra.handlers[k4];
|
||||||
c2.push({text: \`lucas\`});
|
c3.push({text: \`lucas\`});
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1544,14 +1593,14 @@ exports[`t-call handlers with arguments are properly bound through a t-call 1`]
|
|||||||
let utils = this.constructor.utils;
|
let utils = this.constructor.utils;
|
||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c2 = extra.parentNode;
|
||||||
let key0 = extra.key || \\"\\";
|
let key0 = extra.key || \\"\\";
|
||||||
let c2 = [], p2 = {key:\`\${key0}_2\`,on:{}};
|
let c3 = [], p3 = {key:\`\${key0}_3\`,on:{}};
|
||||||
let vn2 = h('p', p2, c2);
|
let vn3 = h('p', p3, c3);
|
||||||
c1.push(vn2);
|
c2.push(vn3);
|
||||||
let args3 = [scope['a']];
|
let args4 = [scope['a']];
|
||||||
p2.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['update'](...args3, e);};
|
p3.on['click'] = function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['update'](...args4, e);};
|
||||||
c2.push({text: \`lucas\`});
|
c3.push({text: \`lucas\`});
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1564,32 +1613,32 @@ exports[`t-call parent is set within t-call 1`] = `
|
|||||||
let parent = extra.parent;
|
let parent = extra.parent;
|
||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c2 = extra.parentNode;
|
||||||
let key0 = extra.key || \\"\\";
|
let key0 = extra.key || \\"\\";
|
||||||
// Component 'Child'
|
// Component 'Child'
|
||||||
let k3 = \`__3__\${key0}__\`;
|
let k4 = \`__4__\${key0}__\`;
|
||||||
let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : false;
|
let w3 = k4 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k4]] : false;
|
||||||
let props2 = {};
|
let props3 = {};
|
||||||
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
|
||||||
w2.destroy();
|
w3.destroy();
|
||||||
w2 = false;
|
w3 = false;
|
||||||
}
|
}
|
||||||
if (w2) {
|
if (w3) {
|
||||||
w2.__updateProps(props2, extra.fiber, undefined);
|
w3.__updateProps(props3, extra.fiber, undefined);
|
||||||
let pvnode = w2.__owl__.pvnode;
|
let pvnode = w3.__owl__.pvnode;
|
||||||
c1.push(pvnode);
|
c2.push(pvnode);
|
||||||
} else {
|
} else {
|
||||||
let componentKey2 = \`Child\`;
|
let componentKey3 = \`Child\`;
|
||||||
let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| scope['Child'];
|
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['Child'];
|
||||||
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
|
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
|
||||||
w2 = new W2(parent, props2);
|
w3 = new W3(parent, props3);
|
||||||
parent.__owl__.cmap[k3] = w2.__owl__.id;
|
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||||
let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||||
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||||
c1.push(pvnode);
|
c2.push(pvnode);
|
||||||
w2.__owl__.pvnode = pvnode;
|
w3.__owl__.pvnode = pvnode;
|
||||||
}
|
}
|
||||||
w2.__owl__.parentLastFiberId = extra.fiber.id;
|
w3.__owl__.parentLastFiberId = extra.fiber.id;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1683,6 +1732,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
|
||||||
) {
|
) {
|
||||||
@@ -1749,6 +1825,46 @@ 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)) {
|
||||||
|
_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
|
||||||
) {
|
) {
|
||||||
@@ -1833,7 +1949,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'];
|
||||||
@@ -1843,7 +1959,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'];
|
||||||
|
|||||||
@@ -44,23 +44,23 @@ exports[`t-slot directive can define and call slots 2`] = `
|
|||||||
) {
|
) {
|
||||||
// Template name: \\"Dialog\\"
|
// Template name: \\"Dialog\\"
|
||||||
let h = this.h;
|
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 c9 = [], p9 = {key:9};
|
||||||
let vn9 = h('div', p9, c9);
|
let vn9 = h('div', p9, c9);
|
||||||
c6.push(vn9);
|
let c10 = [], p10 = {key:10};
|
||||||
const slot10 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer'];
|
let vn10 = h('div', p10, c10);
|
||||||
if (slot10) {
|
c9.push(vn10);
|
||||||
slot10.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c9, parent: extra.parent || context}));
|
const slot11 = this.constructor.slots[context.__owl__.slotId + '_' + 'header'];
|
||||||
|
if (slot11) {
|
||||||
|
slot11.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c10, parent: extra.parent || context}));
|
||||||
}
|
}
|
||||||
return vn6;
|
let c12 = [], p12 = {key:12};
|
||||||
|
let vn12 = h('div', p12, c12);
|
||||||
|
c9.push(vn12);
|
||||||
|
const slot13 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer'];
|
||||||
|
if (slot13) {
|
||||||
|
slot13.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c12, parent: extra.parent || context}));
|
||||||
|
}
|
||||||
|
return vn9;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -69,11 +69,11 @@ exports[`t-slot directive can define and call slots 3`] = `
|
|||||||
) {
|
) {
|
||||||
// Template name: \\"slot_header_template\\"
|
// Template name: \\"slot_header_template\\"
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c4 = extra.parentNode;
|
||||||
let c4 = [], p4 = {key:4};
|
let c5 = [], p5 = {key:5};
|
||||||
let vn4 = h('span', p4, c4);
|
let vn5 = h('span', p5, c5);
|
||||||
c1.push(vn4);
|
c4.push(vn5);
|
||||||
c4.push({text: \`header\`});
|
c5.push({text: \`header\`});
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -82,11 +82,11 @@ exports[`t-slot directive can define and call slots 4`] = `
|
|||||||
) {
|
) {
|
||||||
// Template name: \\"slot_footer_template\\"
|
// Template name: \\"slot_footer_template\\"
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c6 = extra.parentNode;
|
||||||
let c5 = [], p5 = {key:5};
|
let c7 = [], p7 = {key:7};
|
||||||
let vn5 = h('span', p5, c5);
|
let vn7 = h('span', p7, c7);
|
||||||
c1.push(vn5);
|
c6.push(vn7);
|
||||||
c5.push({text: \`footer\`});
|
c7.push({text: \`footer\`});
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -134,23 +134,23 @@ exports[`t-slot directive can define and call slots using old t-set keyword 2`]
|
|||||||
) {
|
) {
|
||||||
// Template name: \\"__template__1\\"
|
// Template name: \\"__template__1\\"
|
||||||
let h = this.h;
|
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 c9 = [], p9 = {key:9};
|
||||||
let vn9 = h('div', p9, c9);
|
let vn9 = h('div', p9, c9);
|
||||||
c6.push(vn9);
|
let c10 = [], p10 = {key:10};
|
||||||
const slot10 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer'];
|
let vn10 = h('div', p10, c10);
|
||||||
if (slot10) {
|
c9.push(vn10);
|
||||||
slot10.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c9, parent: extra.parent || context}));
|
const slot11 = this.constructor.slots[context.__owl__.slotId + '_' + 'header'];
|
||||||
|
if (slot11) {
|
||||||
|
slot11.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c10, parent: extra.parent || context}));
|
||||||
}
|
}
|
||||||
return vn6;
|
let c12 = [], p12 = {key:12};
|
||||||
|
let vn12 = h('div', p12, c12);
|
||||||
|
c9.push(vn12);
|
||||||
|
const slot13 = this.constructor.slots[context.__owl__.slotId + '_' + 'footer'];
|
||||||
|
if (slot13) {
|
||||||
|
slot13.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c12, parent: extra.parent || context}));
|
||||||
|
}
|
||||||
|
return vn9;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -159,11 +159,11 @@ exports[`t-slot directive can define and call slots using old t-set keyword 3`]
|
|||||||
) {
|
) {
|
||||||
// Template name: \\"slot_header_template\\"
|
// Template name: \\"slot_header_template\\"
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c4 = extra.parentNode;
|
||||||
let c4 = [], p4 = {key:4};
|
let c5 = [], p5 = {key:5};
|
||||||
let vn4 = h('span', p4, c4);
|
let vn5 = h('span', p5, c5);
|
||||||
c1.push(vn4);
|
c4.push(vn5);
|
||||||
c4.push({text: \`header\`});
|
c5.push({text: \`header\`});
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -172,11 +172,11 @@ exports[`t-slot directive can define and call slots using old t-set keyword 4`]
|
|||||||
) {
|
) {
|
||||||
// Template name: \\"slot_footer_template\\"
|
// Template name: \\"slot_footer_template\\"
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c6 = extra.parentNode;
|
||||||
let c5 = [], p5 = {key:5};
|
let c7 = [], p7 = {key:7};
|
||||||
let vn5 = h('span', p5, c5);
|
let vn7 = h('span', p7, c7);
|
||||||
c1.push(vn5);
|
c6.push(vn7);
|
||||||
c5.push({text: \`footer\`});
|
c7.push({text: \`footer\`});
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -185,11 +185,11 @@ exports[`t-slot directive content is the default slot 1`] = `
|
|||||||
) {
|
) {
|
||||||
// Template name: \\"slot_default_template\\"
|
// Template name: \\"slot_default_template\\"
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c4 = extra.parentNode;
|
||||||
let c4 = [], p4 = {key:4};
|
let c5 = [], p5 = {key:5};
|
||||||
let vn4 = h('span', p4, c4);
|
let vn5 = h('span', p5, c5);
|
||||||
c1.push(vn4);
|
c4.push(vn5);
|
||||||
c4.push({text: \`sts rocks\`});
|
c5.push({text: \`sts rocks\`});
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -215,8 +215,27 @@ exports[`t-slot directive default slot work with text nodes 1`] = `
|
|||||||
) {
|
) {
|
||||||
// Template name: \\"slot_default_template\\"
|
// Template name: \\"slot_default_template\\"
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c4 = extra.parentNode;
|
||||||
c1.push({text: \`sts rocks\`});
|
c4.push({text: \`sts rocks\`});
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`t-slot directive dynamic t-slot call 1`] = `
|
||||||
|
"function anonymous(context, extra
|
||||||
|
) {
|
||||||
|
// Template name: \\"__template__1\\"
|
||||||
|
let utils = this.constructor.utils;
|
||||||
|
let scope = Object.create(context);
|
||||||
|
let h = this.h;
|
||||||
|
let c10 = [], p10 = {key:10,on:{}};
|
||||||
|
let vn10 = h('button', p10, c10);
|
||||||
|
extra.handlers['click__11__'] = extra.handlers['click__11__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['toggle'](e);};
|
||||||
|
p10.on['click'] = extra.handlers['click__11__'];
|
||||||
|
const slot12 = this.constructor.slots[context.__owl__.slotId + '_' + (scope['current'].slot)];
|
||||||
|
if (slot12) {
|
||||||
|
slot12.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c10, parent: extra.parent || context}));
|
||||||
|
}
|
||||||
|
return vn10;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -225,15 +244,15 @@ exports[`t-slot directive multiple roots are allowed in a default slot 1`] = `
|
|||||||
) {
|
) {
|
||||||
// Template name: \\"slot_default_template\\"
|
// Template name: \\"slot_default_template\\"
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c4 = extra.parentNode;
|
||||||
let c4 = [], p4 = {key:4};
|
|
||||||
let vn4 = h('span', p4, c4);
|
|
||||||
c1.push(vn4);
|
|
||||||
c4.push({text: \`sts\`});
|
|
||||||
let c5 = [], p5 = {key:5};
|
let c5 = [], p5 = {key:5};
|
||||||
let vn5 = h('span', p5, c5);
|
let vn5 = h('span', p5, c5);
|
||||||
c1.push(vn5);
|
c4.push(vn5);
|
||||||
c5.push({text: \`rocks\`});
|
c5.push({text: \`sts\`});
|
||||||
|
let c6 = [], p6 = {key:6};
|
||||||
|
let vn6 = h('span', p6, c6);
|
||||||
|
c4.push(vn6);
|
||||||
|
c6.push({text: \`rocks\`});
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -242,15 +261,15 @@ exports[`t-slot directive multiple roots are allowed in a named slot 1`] = `
|
|||||||
) {
|
) {
|
||||||
// Template name: \\"slot_content_template\\"
|
// Template name: \\"slot_content_template\\"
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c4 = extra.parentNode;
|
||||||
let c4 = [], p4 = {key:4};
|
|
||||||
let vn4 = h('span', p4, c4);
|
|
||||||
c1.push(vn4);
|
|
||||||
c4.push({text: \`sts\`});
|
|
||||||
let c5 = [], p5 = {key:5};
|
let c5 = [], p5 = {key:5};
|
||||||
let vn5 = h('span', p5, c5);
|
let vn5 = h('span', p5, c5);
|
||||||
c1.push(vn5);
|
c4.push(vn5);
|
||||||
c5.push({text: \`rocks\`});
|
c5.push({text: \`sts\`});
|
||||||
|
let c6 = [], p6 = {key:6};
|
||||||
|
let vn6 = h('span', p6, c6);
|
||||||
|
c4.push(vn6);
|
||||||
|
c6.push({text: \`rocks\`});
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -278,22 +297,22 @@ exports[`t-slot directive refs are properly bound in slots 1`] = `
|
|||||||
let utils = this.constructor.utils;
|
let utils = this.constructor.utils;
|
||||||
context.__owl__.refs = context.__owl__.refs || {};
|
context.__owl__.refs = context.__owl__.refs || {};
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c8 = extra.parentNode;
|
||||||
let c8 = [], p8 = {key:8,on:{}};
|
let c9 = [], p9 = {key:9,on:{}};
|
||||||
let vn8 = h('button', p8, c8);
|
let vn9 = h('button', p9, c9);
|
||||||
c1.push(vn8);
|
c8.push(vn9);
|
||||||
extra.handlers['click__9__'] = extra.handlers['click__9__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);};
|
extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);};
|
||||||
p8.on['click'] = extra.handlers['click__9__'];
|
p9.on['click'] = extra.handlers['click__10__'];
|
||||||
const ref10 = \`myButton\`;
|
const ref11 = \`myButton\`;
|
||||||
p8.hook = {
|
p9.hook = {
|
||||||
create: (_, n) => {
|
create: (_, n) => {
|
||||||
context.__owl__.refs[ref10] = n.elm;
|
context.__owl__.refs[ref11] = n.elm;
|
||||||
},
|
},
|
||||||
destroy: () => {
|
destroy: () => {
|
||||||
delete context.__owl__.refs[ref10];
|
delete context.__owl__.refs[ref11];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
c8.push({text: \`do something\`});
|
c9.push({text: \`do something\`});
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -303,13 +322,13 @@ exports[`t-slot directive slots are rendered with proper context 1`] = `
|
|||||||
// Template name: \\"slot_footer_template\\"
|
// Template name: \\"slot_footer_template\\"
|
||||||
let utils = this.constructor.utils;
|
let utils = this.constructor.utils;
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c8 = extra.parentNode;
|
||||||
let c8 = [], p8 = {key:8,on:{}};
|
let c9 = [], p9 = {key:9,on:{}};
|
||||||
let vn8 = h('button', p8, c8);
|
let vn9 = h('button', p9, c9);
|
||||||
c1.push(vn8);
|
c8.push(vn9);
|
||||||
extra.handlers['click__9__'] = extra.handlers['click__9__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);};
|
extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['doSomething'](e);};
|
||||||
p8.on['click'] = extra.handlers['click__9__'];
|
p9.on['click'] = extra.handlers['click__10__'];
|
||||||
c8.push({text: \`do something\`});
|
c9.push({text: \`do something\`});
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -319,14 +338,14 @@ exports[`t-slot directive slots are rendered with proper context, part 2 1`] = `
|
|||||||
// Template name: \\"Link\\"
|
// Template name: \\"Link\\"
|
||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let _11 = scope['props'].to;
|
let _12 = scope['props'].to;
|
||||||
let c12 = [], p12 = {key:12,attrs:{href: _11}};
|
let c13 = [], p13 = {key:13,attrs:{href: _12}};
|
||||||
let vn12 = h('a', p12, c12);
|
let vn13 = h('a', p13, c13);
|
||||||
const slot13 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
const slot14 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
||||||
if (slot13) {
|
if (slot14) {
|
||||||
slot13.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c12, parent: extra.parent || context}));
|
slot14.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c13, parent: extra.parent || context}));
|
||||||
}
|
}
|
||||||
return vn12;
|
return vn13;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -401,11 +420,11 @@ exports[`t-slot directive slots are rendered with proper context, part 2 3`] = `
|
|||||||
// Template name: \\"slot_default_template\\"
|
// Template name: \\"slot_default_template\\"
|
||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c7 = extra.parentNode;
|
let c10 = extra.parentNode;
|
||||||
c7.push({text: \`User \`});
|
c10.push({text: \`User \`});
|
||||||
let _10 = scope['user'].name;
|
let _11 = scope['user'].name;
|
||||||
if (_10 != null) {
|
if (_11 != null) {
|
||||||
c7.push({text: _10});
|
c10.push({text: _11});
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -416,14 +435,14 @@ exports[`t-slot directive slots are rendered with proper context, part 3 1`] = `
|
|||||||
// Template name: \\"Link\\"
|
// Template name: \\"Link\\"
|
||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let _10 = scope['props'].to;
|
let _12 = scope['props'].to;
|
||||||
let c11 = [], p11 = {key:11,attrs:{href: _10}};
|
let c13 = [], p13 = {key:13,attrs:{href: _12}};
|
||||||
let vn11 = h('a', p11, c11);
|
let vn13 = h('a', p13, c13);
|
||||||
const slot12 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
const slot14 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
||||||
if (slot12) {
|
if (slot14) {
|
||||||
slot12.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c11, parent: extra.parent || context}));
|
slot14.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c13, parent: extra.parent || context}));
|
||||||
}
|
}
|
||||||
return vn11;
|
return vn13;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -499,9 +518,10 @@ exports[`t-slot directive slots are rendered with proper context, part 3 3`] = `
|
|||||||
// Template name: \\"slot_default_template\\"
|
// Template name: \\"slot_default_template\\"
|
||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c7 = extra.parentNode;
|
let c10 = extra.parentNode;
|
||||||
if (scope.userdescr != null) {
|
let _11 = scope['userdescr'];
|
||||||
c7.push({text: scope.userdescr});
|
if (_11 != null) {
|
||||||
|
c10.push({text: _11});
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -552,9 +572,10 @@ exports[`t-slot directive slots are rendered with proper context, part 4 2`] = `
|
|||||||
// Template name: \\"slot_default_template\\"
|
// Template name: \\"slot_default_template\\"
|
||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c1 = extra.parentNode;
|
let c4 = extra.parentNode;
|
||||||
if (scope.userdescr != null) {
|
let _5 = scope['userdescr'];
|
||||||
c1.push({text: scope.userdescr});
|
if (_5 != null) {
|
||||||
|
c4.push({text: _5});
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -564,13 +585,13 @@ exports[`t-slot directive t-set t-value in a slot 1`] = `
|
|||||||
) {
|
) {
|
||||||
// Template name: \\"__template__1\\"
|
// Template name: \\"__template__1\\"
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let c4 = [], p4 = {key:4};
|
let c5 = [], p5 = {key:5};
|
||||||
let vn4 = h('span', p4, c4);
|
let vn5 = h('span', p5, c5);
|
||||||
const slot5 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
const slot6 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
||||||
if (slot5) {
|
if (slot6) {
|
||||||
slot5.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c4, parent: extra.parent || context}));
|
slot6.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c5, parent: extra.parent || context}));
|
||||||
}
|
}
|
||||||
return vn4;
|
return vn5;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -581,12 +602,12 @@ exports[`t-slot directive template can just return a slot 1`] = `
|
|||||||
let utils = this.constructor.utils;
|
let utils = this.constructor.utils;
|
||||||
let result;
|
let result;
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
const slot6 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
const slot7 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
||||||
if (slot6) {
|
if (slot7) {
|
||||||
let children7= []
|
let children8= []
|
||||||
result = {}
|
result = {}
|
||||||
slot6.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: children7, parent: extra.parent || context}));
|
slot7.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: children8, parent: extra.parent || context}));
|
||||||
utils.defineProxy(result, children7[0]);
|
utils.defineProxy(result, children8[0]);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Component, Env } from "../../src/component/component";
|
import { Component, Env, mount } 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
|
||||||
@@ -69,18 +69,23 @@ describe("basic widget properties", () => {
|
|||||||
class SomeWidget extends Component {
|
class SomeWidget extends Component {
|
||||||
static template = xml`<div>content</div>`;
|
static template = xml`<div>content</div>`;
|
||||||
}
|
}
|
||||||
const widget = new SomeWidget();
|
await mount(SomeWidget, { target: fixture });
|
||||||
widget.mount(fixture);
|
|
||||||
await nextTick();
|
|
||||||
expect(fixture.innerHTML).toBe("<div>content</div>");
|
expect(fixture.innerHTML).toBe("<div>content</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("can be mounted with props", async () => {
|
||||||
|
class SomeWidget extends Component {
|
||||||
|
static template = xml`<div><t t-esc="props.content"/></div>`;
|
||||||
|
}
|
||||||
|
await mount(SomeWidget, { target: fixture, props: { content: "foo" } });
|
||||||
|
expect(fixture.innerHTML).toBe("<div>foo</div>");
|
||||||
|
});
|
||||||
|
|
||||||
test("can be mounted on a documentFragment", async () => {
|
test("can be mounted on a documentFragment", async () => {
|
||||||
class SomeWidget extends Component {
|
class SomeWidget extends Component {
|
||||||
static template = xml`<div>content</div>`;
|
static template = xml`<div>content</div>`;
|
||||||
}
|
}
|
||||||
const widget = new SomeWidget();
|
const widget = await mount(SomeWidget, { target: document.createDocumentFragment() });
|
||||||
await widget.mount(document.createDocumentFragment());
|
|
||||||
expect(fixture.innerHTML).toBe("");
|
expect(fixture.innerHTML).toBe("");
|
||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div>content</div>");
|
expect(fixture.innerHTML).toBe("<div>content</div>");
|
||||||
@@ -90,10 +95,9 @@ describe("basic widget properties", () => {
|
|||||||
class SomeWidget extends Component {
|
class SomeWidget extends Component {
|
||||||
static template = xml`<div>content</div>`;
|
static template = xml`<div>content</div>`;
|
||||||
}
|
}
|
||||||
const widget = new SomeWidget();
|
|
||||||
let error;
|
let error;
|
||||||
try {
|
try {
|
||||||
await widget.mount(null as any);
|
await mount(SomeWidget, { target: null as any });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
@@ -107,10 +111,9 @@ describe("basic widget properties", () => {
|
|||||||
class SomeWidget extends Component {
|
class SomeWidget extends Component {
|
||||||
static template = xml`<t/>`;
|
static template = xml`<t/>`;
|
||||||
}
|
}
|
||||||
const widget = new SomeWidget();
|
|
||||||
let error;
|
let error;
|
||||||
try {
|
try {
|
||||||
await widget.mount(fixture);
|
await mount(SomeWidget, { target: fixture });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
@@ -133,12 +136,11 @@ 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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const counter = new Counter();
|
const counter = await mount(Counter, { target: fixture });
|
||||||
counter.mount(fixture);
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div>0<button>Inc</button></div>");
|
expect(fixture.innerHTML).toBe("<div>0<button>Inc</button></div>");
|
||||||
const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0];
|
const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0];
|
||||||
@@ -156,8 +158,7 @@ describe("basic widget properties", () => {
|
|||||||
static components = { Child };
|
static components = { Child };
|
||||||
}
|
}
|
||||||
|
|
||||||
const parent = new Parent();
|
await mount(Parent, { target: fixture });
|
||||||
await parent.mount(fixture);
|
|
||||||
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
||||||
expect(fixture.innerHTML).toBe("<div><span></span></div>");
|
expect(fixture.innerHTML).toBe("<div><span></span></div>");
|
||||||
});
|
});
|
||||||
@@ -167,13 +168,12 @@ 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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const counter = new Counter();
|
|
||||||
const target = document.createElement("div");
|
const target = document.createElement("div");
|
||||||
await counter.mount(target);
|
const counter = await mount(Counter, { target: target });
|
||||||
expect(target.innerHTML).toBe("<div>0<button>Inc</button></div>");
|
expect(target.innerHTML).toBe("<div>0<button>Inc</button></div>");
|
||||||
const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0];
|
const button = (<HTMLElement>counter.el).getElementsByTagName("button")[0];
|
||||||
button.click();
|
button.click();
|
||||||
@@ -188,8 +188,7 @@ describe("basic widget properties", () => {
|
|||||||
<div style="font-weight:bold;" class="some-class">world</div>
|
<div style="font-weight:bold;" class="some-class">world</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
const widget = new StyledWidget();
|
await mount(StyledWidget, { target: fixture });
|
||||||
await widget.mount(fixture);
|
|
||||||
expect(fixture.innerHTML).toBe(`<div style="font-weight:bold;" class="some-class">world</div>`);
|
expect(fixture.innerHTML).toBe(`<div style="font-weight:bold;" class="some-class">world</div>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -212,8 +211,8 @@ describe("basic widget properties", () => {
|
|||||||
steps.push("patched");
|
steps.push("patched");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const widget = new TestW();
|
await mount(TestW, { target: fixture });
|
||||||
await widget.mount(fixture);
|
|
||||||
expect(steps).toEqual(["__render", "mounted"]);
|
expect(steps).toEqual(["__render", "mounted"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -537,7 +536,7 @@ describe("lifecycle hooks", () => {
|
|||||||
"child:mounted",
|
"child:mounted",
|
||||||
"parent:willUnmount",
|
"parent:willUnmount",
|
||||||
"child:willUnmount",
|
"child:willUnmount",
|
||||||
"childchild:willUnmount"
|
"childchild:willUnmount",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -589,7 +588,7 @@ describe("lifecycle hooks", () => {
|
|||||||
"childchild:willPatch",
|
"childchild:willPatch",
|
||||||
"childchild:patched",
|
"childchild:patched",
|
||||||
"child:patched",
|
"child:patched",
|
||||||
"parent:patched"
|
"parent:patched",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -782,7 +781,7 @@ describe("lifecycle hooks", () => {
|
|||||||
"c mounted",
|
"c mounted",
|
||||||
"p mounted",
|
"p mounted",
|
||||||
"p willunmount",
|
"p willunmount",
|
||||||
"c willunmount"
|
"c willunmount",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -960,7 +959,7 @@ describe("lifecycle hooks", () => {
|
|||||||
"parent:willPatch",
|
"parent:willPatch",
|
||||||
"child:willPatch",
|
"child:willPatch",
|
||||||
"child:patched",
|
"child:patched",
|
||||||
"parent:patched"
|
"parent:patched",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1134,7 +1133,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 +1157,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 +1227,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 +1326,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 +1349,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 +1388,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 +1428,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 +1512,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 +1620,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 +1724,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 +2043,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 +2078,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 +3016,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 +3039,7 @@ describe("random stuff/miscellaneous", () => {
|
|||||||
"E:destroy",
|
"E:destroy",
|
||||||
"F:mounted",
|
"F:mounted",
|
||||||
"D:patched",
|
"D:patched",
|
||||||
"C:patched"
|
"C:patched",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3015,6 +3055,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", () => {
|
||||||
@@ -3138,6 +3257,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>
|
||||||
@@ -3410,7 +3569,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();
|
||||||
@@ -3427,6 +3586,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`
|
||||||
@@ -3455,7 +3636,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;
|
||||||
@@ -3825,7 +4006,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 () => {
|
||||||
@@ -3847,7 +4030,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 () => {
|
||||||
@@ -3889,7 +4075,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 () => {
|
||||||
@@ -3905,7 +4093,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", () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, Env } from "../../src/component/component";
|
import { Component, Env, mount } from "../../src/component/component";
|
||||||
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 { useState, useRef } from "../../src/hooks";
|
||||||
@@ -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]);
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -224,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 };
|
||||||
}
|
}
|
||||||
@@ -268,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 };
|
||||||
}
|
}
|
||||||
@@ -929,4 +929,193 @@ describe("t-slot directive", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div><span>dash</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>dash</span></div>");
|
||||||
expect(env.qweb.templates[Dialog.template].fn.toString()).toMatchSnapshot();
|
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>"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("named slot inside slot, part 3", 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 t-set-slot="brol">
|
||||||
|
<p>B<t t-esc="value"/></p>
|
||||||
|
</t>
|
||||||
|
</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("dynamic t-slot call", async () => {
|
||||||
|
class Toggler extends Component {
|
||||||
|
static template = xml`<button t-on-click="toggle"><t t-slot="{{current.slot}}"/></button>`;
|
||||||
|
current = useState({ slot: "slot1" });
|
||||||
|
toggle() {
|
||||||
|
this.current.slot = this.current.slot === "slot1" ? "slot2" : "slot1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component {
|
||||||
|
static template = xml`
|
||||||
|
<div>
|
||||||
|
<Toggler>
|
||||||
|
<t t-set-slot="slot1"><p>slot1</p><span>content</span></t>
|
||||||
|
<t t-set-slot="slot2"><h1>slot2</h1></t>
|
||||||
|
</Toggler>
|
||||||
|
</div>`;
|
||||||
|
static components = { Toggler };
|
||||||
|
}
|
||||||
|
await mount(Parent, { target: fixture });
|
||||||
|
expect(fixture.innerHTML).toBe("<div><button><p>slot1</p><span>content</span></button></div>");
|
||||||
|
|
||||||
|
fixture.querySelector<HTMLElement>("button")!.click();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe("<div><button><h1>slot2</h1></button></div>");
|
||||||
|
|
||||||
|
fixture.querySelector<HTMLElement>("button")!.click();
|
||||||
|
await nextTick();
|
||||||
|
expect(fixture.innerHTML).toBe("<div><button><p>slot1</p><span>content</span></button></div>");
|
||||||
|
|
||||||
|
expect(env.qweb.templates[Toggler.template].fn.toString()).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Component, Env } from "../../src/component/component";
|
import { Component, Env, mount } 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
|
||||||
@@ -36,8 +37,7 @@ describe("mount targets", () => {
|
|||||||
div.innerHTML = `<p>pre-existing</p>`;
|
div.innerHTML = `<p>pre-existing</p>`;
|
||||||
fixture.appendChild(div);
|
fixture.appendChild(div);
|
||||||
|
|
||||||
const app = new App();
|
const app = await mount(App, { target: div, position: "self" });
|
||||||
await app.mount(div, { position: "self" });
|
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
`<div class="arbitrary custom"><p>pre-existing</p>app<p>another tag</p></div>`
|
`<div class="arbitrary custom"><p>pre-existing</p>app<p>another tag</p></div>`
|
||||||
@@ -65,10 +65,9 @@ describe("mount targets", () => {
|
|||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
fixture.appendChild(div);
|
fixture.appendChild(div);
|
||||||
|
|
||||||
const app = new App();
|
|
||||||
let error;
|
let error;
|
||||||
try {
|
try {
|
||||||
await app.mount(div, { position: "self" });
|
await mount(App, { target: div, position: "self" });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
@@ -83,8 +82,7 @@ describe("mount targets", () => {
|
|||||||
const span = document.createElement("span");
|
const span = document.createElement("span");
|
||||||
fixture.appendChild(span);
|
fixture.appendChild(span);
|
||||||
|
|
||||||
const app = new App();
|
await mount(App, { target: fixture, position: "first-child" });
|
||||||
await app.mount(fixture, { position: "first-child" });
|
|
||||||
expect(fixture.innerHTML).toBe("<div>app</div><span></span>");
|
expect(fixture.innerHTML).toBe("<div>app</div><span></span>");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -95,8 +93,7 @@ describe("mount targets", () => {
|
|||||||
const span = document.createElement("span");
|
const span = document.createElement("span");
|
||||||
fixture.appendChild(span);
|
fixture.appendChild(span);
|
||||||
|
|
||||||
const app = new App();
|
await mount(App, { target: fixture, position: "last-child" });
|
||||||
await app.mount(fixture, { position: "last-child" });
|
|
||||||
expect(fixture.innerHTML).toBe("<span></span><div>app</div>");
|
expect(fixture.innerHTML).toBe("<span></span><div>app</div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -107,8 +104,7 @@ describe("mount targets", () => {
|
|||||||
const span = document.createElement("span");
|
const span = document.createElement("span");
|
||||||
fixture.appendChild(span);
|
fixture.appendChild(span);
|
||||||
|
|
||||||
const app = new App();
|
await mount(App, { target: fixture });
|
||||||
await app.mount(fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("<span></span><div>app</div>");
|
expect(fixture.innerHTML).toBe("<span></span><div>app</div>");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -132,8 +128,7 @@ describe("unmounting and remounting", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const w = new MyWidget();
|
const w = await mount(MyWidget, { target: fixture });
|
||||||
await w.mount(fixture);
|
|
||||||
expect(fixture.innerHTML).toBe("<div>Hey</div>");
|
expect(fixture.innerHTML).toBe("<div>Hey</div>");
|
||||||
expect(steps).toEqual(["willstart", "mounted"]);
|
expect(steps).toEqual(["willstart", "mounted"]);
|
||||||
|
|
||||||
@@ -161,8 +156,7 @@ describe("unmounting and remounting", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const w = new MyWidget();
|
const w = await mount(MyWidget, { target: fixture });
|
||||||
await w.mount(fixture);
|
|
||||||
await w.mount(fixture);
|
await w.mount(fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div>Hey</div>");
|
expect(fixture.innerHTML).toBe("<div>Hey</div>");
|
||||||
expect(steps).toEqual(["willstart", "mounted"]);
|
expect(steps).toEqual(["willstart", "mounted"]);
|
||||||
@@ -202,8 +196,7 @@ describe("unmounting and remounting", () => {
|
|||||||
state = useState({ val: 1, flag: true });
|
state = useState({ val: 1, flag: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const widget = new Parent();
|
const widget = await mount(Parent, { target: fixture });
|
||||||
await widget.mount(fixture);
|
|
||||||
expect(steps).toEqual(["render"]);
|
expect(steps).toEqual(["render"]);
|
||||||
expect(fixture.innerHTML).toBe("<div><span>12</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>12</span></div>");
|
||||||
widget.state.flag = false;
|
widget.state.flag = false;
|
||||||
@@ -335,7 +328,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);
|
||||||
@@ -490,4 +483,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] });
|
||||||
|
|||||||
+10
-12
@@ -8,12 +8,6 @@ import "../src/qweb/extensions";
|
|||||||
import "../src/component/directive";
|
import "../src/component/directive";
|
||||||
import { browser } from "../src/browser";
|
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;
|
||||||
let slots;
|
let slots;
|
||||||
@@ -43,9 +37,13 @@ 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) => setTimeout(resolve));
|
||||||
setTimeout(() => scheduler.requestAnimationFrame(() => resolve()));
|
await new Promise((resolve) => scheduler.requestAnimationFrame(resolve));
|
||||||
});
|
}
|
||||||
|
|
||||||
|
export async function nextFrame(): Promise<void> {
|
||||||
|
await new Promise((resolve) => scheduler.requestAnimationFrame(resolve));
|
||||||
|
await new Promise((resolve) => scheduler.requestAnimationFrame(resolve));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeTestFixture() {
|
export function makeTestFixture() {
|
||||||
@@ -77,7 +75,7 @@ export function makeDeferred(): Deferred {
|
|||||||
export function makeTestEnv(): Env {
|
export function makeTestEnv(): Env {
|
||||||
return {
|
return {
|
||||||
qweb: new QWeb(),
|
qweb: new QWeb(),
|
||||||
browser: browser
|
browser: browser,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +132,7 @@ export function renderToString(
|
|||||||
// is useful for animations tests, as we hook before repaints to trigger
|
// is useful for animations tests, as we hook before repaints to trigger
|
||||||
// animations (thanks to requestAnimationFrame). Patching nextFrame allows to
|
// animations (thanks to requestAnimationFrame). Patching nextFrame allows to
|
||||||
// simulate calls to this hook. One must not forget to unpatch afterwards.
|
// simulate calls to this hook. One must not forget to unpatch afterwards.
|
||||||
let nextFrame = QWeb.utils.nextFrame;
|
let _nextFrame = QWeb.utils.nextFrame;
|
||||||
export function patchNextFrame(f: Function) {
|
export function patchNextFrame(f: Function) {
|
||||||
QWeb.utils.nextFrame = (cb: () => void) => {
|
QWeb.utils.nextFrame = (cb: () => void) => {
|
||||||
setTimeout(() => f(cb));
|
setTimeout(() => f(cb));
|
||||||
@@ -142,7 +140,7 @@ export function patchNextFrame(f: Function) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function unpatchNextFrame() {
|
export function unpatchNextFrame() {
|
||||||
QWeb.utils.nextFrame = nextFrame;
|
QWeb.utils.nextFrame = _nextFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function editInput(input: HTMLInputElement | HTMLTextAreaElement, value: string) {
|
export async function editInput(input: HTMLInputElement | HTMLTextAreaElement, value: string) {
|
||||||
|
|||||||
+7
-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();
|
||||||
@@ -560,7 +560,7 @@ 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> {
|
async function slow(): Promise<string> {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve("slow");
|
resolve("slow");
|
||||||
}, 0);
|
}, 0);
|
||||||
@@ -571,7 +571,7 @@ describe("hooks", () => {
|
|||||||
steps.push(await slow());
|
steps.push(await slow());
|
||||||
steps.push("onWillStart");
|
steps.push("onWillStart");
|
||||||
});
|
});
|
||||||
onWillUpdateProps(async nextProps => {
|
onWillUpdateProps(async (nextProps) => {
|
||||||
expect(nextProps).toEqual({ value: 2 });
|
expect(nextProps).toEqual({ value: 2 });
|
||||||
steps.push(await slow());
|
steps.push(await slow());
|
||||||
steps.push("onWillUpdateProps");
|
steps.push("onWillUpdateProps");
|
||||||
@@ -581,7 +581,7 @@ describe("hooks", () => {
|
|||||||
onWillStart(() => {
|
onWillStart(() => {
|
||||||
steps.push("on2ndStart");
|
steps.push("on2ndStart");
|
||||||
});
|
});
|
||||||
onWillUpdateProps(nextProps => {
|
onWillUpdateProps((nextProps) => {
|
||||||
expect(nextProps).toEqual({ value: 2 });
|
expect(nextProps).toEqual({ value: 2 });
|
||||||
steps.push("on2ndUpdate");
|
steps.push("on2ndUpdate");
|
||||||
});
|
});
|
||||||
@@ -623,7 +623,7 @@ describe("hooks", () => {
|
|||||||
"onWillStart",
|
"onWillStart",
|
||||||
"on2ndUpdate",
|
"on2ndUpdate",
|
||||||
"slow",
|
"slow",
|
||||||
"onWillUpdateProps"
|
"onWillUpdateProps",
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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
+210
-55
@@ -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", () => {
|
||||||
@@ -231,6 +238,13 @@ describe("t-raw", () => {
|
|||||||
"<span><span>hello</span><ok>world</ok></span>"
|
"<span><span>hello</span><ok>world</ok></span>"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("t-raw with comment", () => {
|
||||||
|
qweb.addTemplate("test", `<span><t t-raw="var"/></span>`);
|
||||||
|
expect(renderToString(qweb, "test", { var: "<p>text<!-- top secret --></p>" })).toBe(
|
||||||
|
"<span><p>text<!-- top secret --></p></span>"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("t-set", () => {
|
describe("t-set", () => {
|
||||||
@@ -486,7 +500,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 +639,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 +698,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 +717,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 +729,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 +754,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 +762,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 +770,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 +906,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 +935,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 +963,8 @@ 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();
|
expect(recursiveFn.toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -967,12 +991,13 @@ describe("t-call (template calling", () => {
|
|||||||
`);
|
`);
|
||||||
const root = {
|
const root = {
|
||||||
val: "a",
|
val: "a",
|
||||||
children: [{ val: "b", children: [{ val: "c", children: [{ val: "d" }] }] }]
|
children: [{ val: "b", children: [{ val: "c", children: [{ val: "d" }] }] }],
|
||||||
};
|
};
|
||||||
const expected =
|
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>";
|
"<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);
|
expect(renderToString(qweb, "Parent", { root })).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();
|
expect(recursiveFn.toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1044,6 +1069,52 @@ 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("t-call with body content as root of a template", () => {
|
||||||
|
qweb.addTemplate("antony", `<foo><t t-raw="0"/></foo>`);
|
||||||
|
qweb.addTemplate("main", `<t><t t-call="antony"><p>antony</p></t></t>`);
|
||||||
|
const expected = "<foo><p>antony</p></foo>";
|
||||||
|
expect(renderToString(qweb, "main")).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("dynamic t-call", () => {
|
||||||
|
qweb.addTemplate("foo", `<foo><t t-esc="val"/></foo>`);
|
||||||
|
qweb.addTemplate("bar", `<bar><t t-esc="val"/></bar>`);
|
||||||
|
qweb.addTemplate("main", `<div><t t-call="{{template}}"/></div>`);
|
||||||
|
const expected = "<div><foo>foo</foo></div>";
|
||||||
|
expect(renderToString(qweb, "main", { template: "foo", val: "foo" })).toBe(expected);
|
||||||
|
const expected2 = "<div><bar>quux</bar></div>";
|
||||||
|
expect(renderToString(qweb, "main", { template: "bar", val: "quux" })).toBe(expected2);
|
||||||
|
// duplicate call because there was a specific bug with some id that was
|
||||||
|
// incremented each rendering.
|
||||||
|
expect(renderToString(qweb, "main", { template: "bar", val: "quux" })).toBe(expected2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("foreach", () => {
|
describe("foreach", () => {
|
||||||
@@ -1134,6 +1205,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",
|
||||||
@@ -1229,7 +1357,7 @@ describe("t-on", () => {
|
|||||||
{
|
{
|
||||||
add() {
|
add() {
|
||||||
a = 3;
|
a = 3;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1252,7 +1380,7 @@ describe("t-on", () => {
|
|||||||
},
|
},
|
||||||
handleDblClick() {
|
handleDblClick() {
|
||||||
steps.push("dblclick");
|
steps.push("dblclick");
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1272,7 +1400,7 @@ describe("t-on", () => {
|
|||||||
{
|
{
|
||||||
add(n) {
|
add(n) {
|
||||||
a = a + n;
|
a = a + n;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1289,7 +1417,7 @@ describe("t-on", () => {
|
|||||||
{
|
{
|
||||||
add({ val }) {
|
add({ val }) {
|
||||||
a = a + val;
|
a = a + val;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1306,7 +1434,7 @@ describe("t-on", () => {
|
|||||||
{
|
{
|
||||||
doSomething(arg) {
|
doSomething(arg) {
|
||||||
expect(arg).toEqual({});
|
expect(arg).toEqual({});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1322,7 +1450,7 @@ describe("t-on", () => {
|
|||||||
{
|
{
|
||||||
doSomething(arg) {
|
doSomething(arg) {
|
||||||
expect(arg).toEqual({});
|
expect(arg).toEqual({});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1344,7 +1472,7 @@ describe("t-on", () => {
|
|||||||
{
|
{
|
||||||
activate(action) {
|
activate(action) {
|
||||||
expect(action).toBe("someval");
|
expect(action).toBe("someval");
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ handlers: [] }
|
{ handlers: [] }
|
||||||
);
|
);
|
||||||
@@ -1357,7 +1485,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();
|
||||||
@@ -1367,8 +1495,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);
|
||||||
@@ -1381,10 +1509,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);
|
||||||
@@ -1396,8 +1524,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);
|
||||||
@@ -1414,8 +1542,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);
|
||||||
@@ -1431,7 +1559,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: [] });
|
||||||
@@ -1448,7 +1576,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: [] });
|
||||||
@@ -1477,7 +1605,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: [] });
|
||||||
|
|
||||||
@@ -1503,7 +1631,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: [] });
|
||||||
|
|
||||||
@@ -1527,10 +1655,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);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1552,10 +1680,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);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1583,13 +1711,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: [] });
|
||||||
@@ -1614,7 +1742,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);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1630,7 +1758,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: [] });
|
||||||
@@ -1649,7 +1777,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: [] });
|
||||||
@@ -1676,7 +1804,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: [] });
|
||||||
|
|
||||||
@@ -1749,7 +1877,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 });
|
||||||
@@ -1776,6 +1904,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", () => {
|
||||||
@@ -1840,7 +1995,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();
|
||||||
});
|
});
|
||||||
@@ -1953,9 +2108,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>");
|
||||||
@@ -1963,9 +2118,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",
|
||||||
@@ -1980,9 +2135,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\\""' }]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ exports[`Link component can render simple cases 1`] = `
|
|||||||
let utils = this.constructor.utils;
|
let utils = this.constructor.utils;
|
||||||
let scope = Object.create(context);
|
let scope = Object.create(context);
|
||||||
let h = this.h;
|
let h = this.h;
|
||||||
let _4 = utils.toObj({'router-link-active':scope['isActive']});
|
let _5 = utils.toObj({'router-link-active':scope['isActive']});
|
||||||
let _5 = scope['href'];
|
let _6 = scope['href'];
|
||||||
let c6 = [], p6 = {key:6,attrs:{href: _5},class:_4,on:{}};
|
let c7 = [], p7 = {key:7,attrs:{href: _6},class:_5,on:{}};
|
||||||
let vn6 = h('a', p6, c6);
|
let vn7 = h('a', p7, c7);
|
||||||
extra.handlers['click__7__'] = extra.handlers['click__7__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['navigate'](e);};
|
extra.handlers['click__8__'] = extra.handlers['click__8__'] || function (e) {if (!context.__owl__.isMounted){return}utils.getComponent(context)['navigate'](e);};
|
||||||
p6.on['click'] = extra.handlers['click__7__'];
|
p7.on['click'] = extra.handlers['click__8__'];
|
||||||
const slot8 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
const slot9 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
||||||
if (slot8) {
|
if (slot9) {
|
||||||
slot8.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c6, parent: extra.parent || context}));
|
slot9.call(this, context.__owl__.scope, Object.assign({}, extra, {parentNode: c7, parent: extra.parent || context}));
|
||||||
}
|
}
|
||||||
return vn6;
|
return vn7;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -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++);
|
||||||
|
|||||||
+90
-90
@@ -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,15 +874,15 @@ 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<{}, EnvWithStore> {
|
class TodoItem extends Component<{}, EnvWithStore> {
|
||||||
@@ -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,15 +947,15 @@ 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<{}, EnvWithStore> {
|
class TodoItem extends Component<{}, EnvWithStore> {
|
||||||
@@ -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 -
|
||||||
|
|||||||
+199
-192
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
|||||||
import { SAMPLES } from "./samples.js";
|
import { SAMPLES } from "./samples.js";
|
||||||
const { useState, useRef, onMounted, onWillUnmount } = owl.hooks;
|
const { mount, hooks } = owl;
|
||||||
|
const { useState, useRef, onMounted, onWillUnmount } = hooks;
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Constants, helpers, utils
|
// Constants, helpers, utils
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -419,9 +420,8 @@ async function start() {
|
|||||||
owl.utils.whenReady()
|
owl.utils.whenReady()
|
||||||
]);
|
]);
|
||||||
const qweb = new owl.QWeb({ templates });
|
const qweb = new owl.QWeb({ templates });
|
||||||
owl.Component.env = { qweb };
|
const env = { qweb };
|
||||||
const app = new App();
|
await mount(App, {target: document.body, env});
|
||||||
app.mount(document.body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
start();
|
start();
|
||||||
|
|||||||
+27
-38
@@ -1,5 +1,5 @@
|
|||||||
const COMPONENTS = `// In this example, we show how components can be defined and created.
|
const COMPONENTS = `// In this example, we show how components can be defined and created.
|
||||||
const { Component, useState } = owl;
|
const { Component, useState, mount } = owl;
|
||||||
|
|
||||||
class Greeter extends Component {
|
class Greeter extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -22,8 +22,7 @@ class App extends Component {
|
|||||||
App.components = { Greeter };
|
App.components = { Greeter };
|
||||||
|
|
||||||
// Application setup
|
// Application setup
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const COMPONENTS_XML = `<templates>
|
const COMPONENTS_XML = `<templates>
|
||||||
@@ -50,7 +49,7 @@ const COMPONENTS_CSS = `.greeter {
|
|||||||
|
|
||||||
const ANIMATION = `// The goal of this component is to see how the t-transition directive can be
|
const ANIMATION = `// The goal of this component is to see how the t-transition directive can be
|
||||||
// used to generate simple transition effects.
|
// used to generate simple transition effects.
|
||||||
const { Component, useState } = owl;
|
const { Component, useState, mount } = owl;
|
||||||
|
|
||||||
class Counter extends Component {
|
class Counter extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -80,8 +79,7 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
App.components = { Counter };
|
App.components = { Counter };
|
||||||
|
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ANIMATION_XML = `<templates>
|
const ANIMATION_XML = `<templates>
|
||||||
@@ -193,7 +191,7 @@ const LIFECYCLE_DEMO = `// This example shows all the possible lifecycle hooks
|
|||||||
// methods in the console. Try modifying its state by clicking on it, or by
|
// methods in the console. Try modifying its state by clicking on it, or by
|
||||||
// clicking on the two main buttons, and look into the console to see what
|
// clicking on the two main buttons, and look into the console to see what
|
||||||
// happens.
|
// happens.
|
||||||
const { Component, useState } = owl;
|
const { Component, useState, mount } = owl;
|
||||||
|
|
||||||
class DemoComponent extends Component {
|
class DemoComponent extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -240,8 +238,7 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
App.components = { DemoComponent };
|
App.components = { DemoComponent };
|
||||||
|
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const LIFECYCLE_DEMO_XML = `<templates>
|
const LIFECYCLE_DEMO_XML = `<templates>
|
||||||
@@ -273,7 +270,8 @@ const LIFECYCLE_CSS = `button {
|
|||||||
}`;
|
}`;
|
||||||
|
|
||||||
const HOOKS_DEMO = `// In this example, we show how hooks can be used or defined.
|
const HOOKS_DEMO = `// In this example, we show how hooks can be used or defined.
|
||||||
const {useState, onMounted, onWillUnmount} = owl.hooks;
|
const { hooks, mount } = owl;
|
||||||
|
const {useState, onMounted, onWillUnmount} = hooks;
|
||||||
|
|
||||||
// We define here a custom behaviour: this hook tracks the state of the mouse
|
// We define here a custom behaviour: this hook tracks the state of the mouse
|
||||||
// position
|
// position
|
||||||
@@ -312,8 +310,7 @@ class App extends owl.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Application setup
|
// Application setup
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const HOOKS_DEMO_XML = `<templates>
|
const HOOKS_DEMO_XML = `<templates>
|
||||||
@@ -332,7 +329,7 @@ const HOOKS_CSS = `button {
|
|||||||
|
|
||||||
const CONTEXT_JS = `// In this example, we show how components can use the Context and 'useContext'
|
const CONTEXT_JS = `// In this example, we show how components can use the Context and 'useContext'
|
||||||
// hook to share information between them.
|
// hook to share information between them.
|
||||||
const { Component, Context } = owl;
|
const { Component, Context, mount } = owl;
|
||||||
const { useContext } = owl.hooks;
|
const { useContext } = owl.hooks;
|
||||||
|
|
||||||
class ToolbarButton extends Component {
|
class ToolbarButton extends Component {
|
||||||
@@ -367,8 +364,7 @@ const themeContext = new Context({
|
|||||||
});
|
});
|
||||||
// Add the themeContext the environment to make it available to all components
|
// Add the themeContext the environment to make it available to all components
|
||||||
App.env.themeContext = themeContext;
|
App.env.themeContext = themeContext;
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const CONTEXT_XML = `<templates>
|
const CONTEXT_XML = `<templates>
|
||||||
@@ -395,7 +391,7 @@ const TODO_APP_STORE = `// This example is an implementation of the TodoList app
|
|||||||
//
|
//
|
||||||
// In this implementation, we use the owl Store class to manage the state. It
|
// In this implementation, we use the owl Store class to manage the state. It
|
||||||
// is very similar to the VueX store.
|
// is very similar to the VueX store.
|
||||||
const { Component, useState } = owl;
|
const { Component, useState, mount } = owl;
|
||||||
const { useRef, useStore, useDispatch, onPatched, onMounted } = owl.hooks;
|
const { useRef, useStore, useDispatch, onPatched, onMounted } = owl.hooks;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -568,8 +564,7 @@ function makeStore() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TodoApp.env.store = makeStore();
|
TodoApp.env.store = makeStore();
|
||||||
const app = new TodoApp();
|
mount(TodoApp, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TODO_APP_STORE_XML = `<templates>
|
const TODO_APP_STORE_XML = `<templates>
|
||||||
@@ -1060,8 +1055,7 @@ function setupResponsivePlugin(env) {
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
setupResponsivePlugin(App.env);
|
setupResponsivePlugin(App.env);
|
||||||
|
|
||||||
const app = new App();
|
owl.mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const RESPONSIVE_XML = `<templates>
|
const RESPONSIVE_XML = `<templates>
|
||||||
@@ -1173,7 +1167,7 @@ const SLOTS = `// We show here how slots can be used to create generic component
|
|||||||
//
|
//
|
||||||
// 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
|
||||||
const { Component, useState } = owl;
|
const { Component, useState, mount } = owl;
|
||||||
|
|
||||||
class Card extends Component {
|
class Card extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -1211,8 +1205,8 @@ class App extends Component {
|
|||||||
App.components = {Card, Counter};
|
App.components = {Card, Counter};
|
||||||
|
|
||||||
// Application setup
|
// Application setup
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);`;
|
`;
|
||||||
|
|
||||||
const SLOTS_XML = `<templates>
|
const SLOTS_XML = `<templates>
|
||||||
<div t-name="Card" class="card" t-att-class="state.showContent ? 'full' : 'small'">
|
<div t-name="Card" class="card" t-att-class="state.showContent ? 'full' : 'small'">
|
||||||
@@ -1298,7 +1292,7 @@ const ASYNC_COMPONENTS = `// This example will not work if your browser does not
|
|||||||
// However, we don't want renderings of the other sub component to be delayed
|
// However, we don't want renderings of the other sub component to be delayed
|
||||||
// because of the slow component. We use the AsyncRoot component for this
|
// because of the slow component. We use the AsyncRoot component for this
|
||||||
// purpose. Try removing it to see the difference.
|
// purpose. Try removing it to see the difference.
|
||||||
const { Component, useState } = owl;
|
const { Component, useState, mount } = owl;
|
||||||
const { AsyncRoot } = owl.misc;
|
const { AsyncRoot } = owl.misc;
|
||||||
|
|
||||||
class SlowComponent extends Component {
|
class SlowComponent extends Component {
|
||||||
@@ -1329,8 +1323,7 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
App.components = {SlowComponent, NotificationList, AsyncRoot};
|
App.components = {SlowComponent, NotificationList, AsyncRoot};
|
||||||
|
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ASYNC_COMPONENTS_XML = `<templates>
|
const ASYNC_COMPONENTS_XML = `<templates>
|
||||||
@@ -1385,7 +1378,7 @@ const FORM = `// This example illustrate how the t-model directive can be used t
|
|||||||
// data between html inputs (and select/textareas) and the state of a component.
|
// data between html inputs (and select/textareas) and the state of a component.
|
||||||
// Note that there are two controls with t-model="color": they are totally
|
// Note that there are two controls with t-model="color": they are totally
|
||||||
// synchronized.
|
// synchronized.
|
||||||
const { Component, useState } = owl;
|
const { Component, useState, mount } = owl;
|
||||||
|
|
||||||
class Form extends Component {
|
class Form extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -1401,8 +1394,7 @@ class Form extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Application setup
|
// Application setup
|
||||||
const form = new Form();
|
mount(Form, { target: document.body });
|
||||||
form.mount(document.body);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const FORM_XML = `<templates>
|
const FORM_XML = `<templates>
|
||||||
@@ -1448,7 +1440,7 @@ const PORTAL_COMPONENTS = `
|
|||||||
// This shows the expected use case of Portal
|
// This shows the expected use case of Portal
|
||||||
// which is to implement something similar
|
// which is to implement something similar
|
||||||
// to bootstrap modal
|
// to bootstrap modal
|
||||||
const { Component, useState } = owl;
|
const { Component, useState, mount } = owl;
|
||||||
const { Portal } = owl.misc;
|
const { Portal } = owl.misc;
|
||||||
|
|
||||||
class Modal extends Component {}
|
class Modal extends Component {}
|
||||||
@@ -1470,8 +1462,7 @@ class App extends Component {
|
|||||||
App.components = { Dialog , Interstellar };
|
App.components = { Dialog , Interstellar };
|
||||||
|
|
||||||
// Application setup
|
// Application setup
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const PORTAL_XML = `
|
const PORTAL_XML = `
|
||||||
@@ -1559,7 +1550,7 @@ const WMS = `// This example is slightly more complex than usual. We demonstrate
|
|||||||
// - minimal width/height
|
// - minimal width/height
|
||||||
// - better heuristic for initial window position
|
// - better heuristic for initial window position
|
||||||
// - ...
|
// - ...
|
||||||
const { Component, useState } = owl;
|
const { Component, useState, mount } = owl;
|
||||||
const { useRef } = owl.hooks;
|
const { useRef } = owl.hooks;
|
||||||
|
|
||||||
class HelloWorld extends Component {}
|
class HelloWorld extends Component {}
|
||||||
@@ -1699,8 +1690,7 @@ const windows = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
App.env.windows = windows;
|
App.env.windows = windows;
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const WMS_XML = `<templates>
|
const WMS_XML = `<templates>
|
||||||
@@ -1818,7 +1808,7 @@ const SFC = `// This example illustrates how Owl enables single file components,
|
|||||||
// Note that this example has no external xml or css file, everything is
|
// Note that this example has no external xml or css file, everything is
|
||||||
// contained in a single js file.
|
// contained in a single js file.
|
||||||
|
|
||||||
const { Component, useState, tags } = owl;
|
const { Component, useState, tags, mount } = owl;
|
||||||
const { xml, css } = tags;
|
const { xml, css } = tags;
|
||||||
|
|
||||||
// Counter component
|
// Counter component
|
||||||
@@ -1850,8 +1840,7 @@ App.template = APP_TEMPLATE;
|
|||||||
App.components = { Counter };
|
App.components = { Counter };
|
||||||
|
|
||||||
// Application setup
|
// Application setup
|
||||||
const app = new App();
|
mount(App, { target: document.body });
|
||||||
app.mount(document.body);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SAMPLES = [
|
export const SAMPLES = [
|
||||||
|
|||||||
+23
-58
@@ -3,10 +3,9 @@ const readline = require("readline");
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const exec = require("child_process").exec;
|
const exec = require("child_process").exec;
|
||||||
const chalk = require("chalk");
|
const chalk = require("chalk");
|
||||||
const GitHub = require("github-api");
|
|
||||||
|
|
||||||
const REL_NOTES_FILE = `release-notes.md`;
|
const REL_NOTES_FILE = `release-notes.md`;
|
||||||
const STEPS = 10;
|
const STEPS = 8;
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
@@ -28,7 +27,8 @@ async function startRelease() {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
log(`Step 1/${STEPS}: collecting info...`);
|
log(`Step 1/${STEPS}: collecting info...`);
|
||||||
const current = package.version;
|
const current = package.version;
|
||||||
const next = await ask("Next version: ");
|
let next = await ask("Next version: ");
|
||||||
|
if (next[0] === 'v') next = next.substring(1);
|
||||||
let file = await ask(`Release notes (${REL_NOTES_FILE}): `);
|
let file = await ask(`Release notes (${REL_NOTES_FILE}): `);
|
||||||
file = file || REL_NOTES_FILE;
|
file = file || REL_NOTES_FILE;
|
||||||
let content;
|
let content;
|
||||||
@@ -39,11 +39,12 @@ async function startRelease() {
|
|||||||
log("Cannot find release notes... Aborting");
|
log("Cannot find release notes... Aborting");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let shouldBeDraft = await ask(`Should be a draft [y/n] ? (n)`);
|
||||||
// Todo: add playground update feature
|
let draft = ""
|
||||||
// let shouldUpdateStr = await ask("Update Playground? (y/n)");
|
if (shouldBeDraft.toLowerCase() === 'y')
|
||||||
// const shouldUpdatePlayground = shouldUpdateStr === "y";
|
{
|
||||||
const token = await ask("Github token: ");
|
draft = "--draft";
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
log(`Step 2/${STEPS}: running tests...`);
|
log(`Step 2/${STEPS}: running tests...`);
|
||||||
@@ -52,14 +53,12 @@ async function startRelease() {
|
|||||||
log("Test suite does not pass. Aborting.");
|
log("Test suite does not pass. Aborting.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await ask("Ready for next step...");
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
log(`Step 3/${STEPS}: updating package.json, readme.md and roadmap.md...`);
|
log(`Step 3/${STEPS}: updating package.json, readme.md and roadmap.md...`);
|
||||||
await replaceInFile("./package.json", current, next);
|
await replaceInFile("./package.json", current, next);
|
||||||
await replaceInFile("./README.md", current, next);
|
await replaceInFile("./README.md", current, next);
|
||||||
await replaceInFile("./roadmap.md", current, next);
|
await replaceInFile("./roadmap.md", current, next);
|
||||||
await ask("Ready for next step...");
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
log(`Step 4/${STEPS}: creating git commit...`);
|
log(`Step 4/${STEPS}: creating git commit...`);
|
||||||
@@ -68,61 +67,40 @@ async function startRelease() {
|
|||||||
log("Git commit failed. Aborting.");
|
log("Git commit failed. Aborting.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await ask("Ready for next step...");
|
|
||||||
|
|
||||||
// -------------------j--------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
log(`Step 5/${STEPS}: building owl (iife version)...`);
|
log(`Step 5/${STEPS}: building owl...`);
|
||||||
|
await execCommand("npm run prettier");
|
||||||
const buildResult = await execCommand("npm run build");
|
const buildResult = await execCommand("npm run build");
|
||||||
if (buildResult !== 0) {
|
if (buildResult !== 0) {
|
||||||
log("Build failed. Aborting.");
|
log("Build failed. Aborting.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await ask("Ready for next step...");
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
log(`Step 6/${STEPS}: minifying owl...`);
|
log(`Step 6/${STEPS}: pushing on github...`);
|
||||||
const minifyResult = await execCommand("npm run minify");
|
|
||||||
if (minifyResult !== 0) {
|
|
||||||
log("Minify failed. Aborting.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await ask("Ready for next step...");
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
log(`Step 7/${STEPS}: pushing on github...`);
|
|
||||||
const pushResult = await execCommand("git push");
|
const pushResult = await execCommand("git push");
|
||||||
if (pushResult !== 0) {
|
if (pushResult !== 0) {
|
||||||
log("git push failed. Aborting.");
|
log("git push failed. Aborting.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await ask("Ready for next step...");
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
log(`Step 8/${STEPS}: publishing release notes on github...`);
|
|
||||||
const options = {
|
|
||||||
tag_name: `v${next}`,
|
|
||||||
name: `v${next}`,
|
|
||||||
body: content,
|
|
||||||
draft: true // todo: remove this someday
|
|
||||||
};
|
|
||||||
const result = await createRelease(token, options);
|
|
||||||
await ask("Ready for next step...");
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
log(`Step 7/${STEPS}: Creating the release...`);
|
||||||
log(`Step 9/${STEPS}: adding assets to release...`);
|
const relaseResult = await execCommand(`gh release create v${next} dist/*.js ${draft} -F release-notes.md`);
|
||||||
await ask("Please add owl.js and owl.min.js to draft release, then confirm");
|
if (relaseResult !== 0) {
|
||||||
// todo: do this with curl
|
log("github release failed. Aborting.");
|
||||||
// curl \
|
return;
|
||||||
// -H "Authorization: token $GITHUB_TOKEN" \
|
}
|
||||||
// -H "Content-Type: $(file -b --mime-type $FILE)" \
|
|
||||||
// --data-binary @$FILE \
|
|
||||||
// "https://uploads.github.com/repos/hubot/singularity/releases/123/assets?name=$(basename $FILE)"
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
log(`Step 8/${STEPS}: publishing module on npm...`);
|
||||||
log(`Step 10/${STEPS}: publishing module on npm...`);
|
|
||||||
await execCommand("npm run publish");
|
await execCommand("npm run publish");
|
||||||
|
|
||||||
log("Owl Release process completed! Thank you for your patience");
|
log("Owl Release process completed! Thank you for your patience");
|
||||||
|
await execCommand(`gh release view`);
|
||||||
|
await execCommand(`gh release view -w`);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -199,16 +177,3 @@ async function replaceInFile(file, from, to) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRelease(token, options) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
var gh = new GitHub({ token });
|
|
||||||
gh.getRepo("odoo", "owl").createRelease(options, (err, result, req) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ URL = 'http://{0}:{1}/tools'.format(HOST, PORT)
|
|||||||
class OWLHandler(SimpleHTTPRequestHandler):
|
class OWLHandler(SimpleHTTPRequestHandler):
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
if self.path == '/tools/owl.js':
|
if self.path == '/tools/owl.js':
|
||||||
self.path = '/dist/owl.js'
|
self.path = '/dist/owl.iife.js'
|
||||||
return SimpleHTTPRequestHandler.do_GET(self)
|
return SimpleHTTPRequestHandler.do_GET(self)
|
||||||
|
|
||||||
def end_headers(self):
|
def end_headers(self):
|
||||||
|
|||||||
+104
-17
@@ -1,20 +1,107 @@
|
|||||||
{
|
{
|
||||||
|
/**
|
||||||
|
** Commented-out options have their default values.
|
||||||
|
**/
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/*.ts"
|
||||||
|
],
|
||||||
|
// "exclude": [],
|
||||||
|
// "files": [], // A list of relative or absolute file paths to include.
|
||||||
|
// "extends": "", // A string containing a path to another configuration file to inherit from.
|
||||||
|
// "references": [], // An array of objects `{"path": "./to/dirOrConfig"}` that specifies projects to reference.
|
||||||
|
// "compileOnSave": false, // Signals to the IDE to generate all files for a given tsconfig.json upon saving.
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
// Main options
|
||||||
"preserveConstEnums": true,
|
"target": "esnext", // Specify ECMAScript target version: 'es3' (default), 'es5', 'es2015', 'es2016', 'es2017','es2018' or 'esnext'.
|
||||||
"noImplicitThis": true,
|
"module": "esnext", // Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.
|
||||||
"removeComments": false,
|
// "lib": ["esnext", "dom"], // Specify library files to be included in the compilation.
|
||||||
"declaration": true,
|
// "allowJs": false, // Allow javascript files to be compiled.
|
||||||
"target": "esnext",
|
// "checkJs": false, // Report errors in .js files.
|
||||||
"outDir": "dist",
|
// "outFile": "./", // Concatenate and emit output to single file.
|
||||||
"alwaysStrict": true,
|
"outDir": "dist", // Redirect output structure to the directory.
|
||||||
"noUnusedLocals": true,
|
// "rootDir": "./", // Specify the root directory of input files. Use to control the output directory structure with `--outDir`.
|
||||||
"noUnusedParameters": false,
|
// "project": "", // Compile a project given a valid configuration file.
|
||||||
"noImplicitReturns": true,
|
// Compilation options
|
||||||
"noFallthroughCasesInSwitch": true,
|
// "composite": true, // Enable project compilation
|
||||||
"strictPropertyInitialization": true,
|
// "diagnostics": false, // Show diagnostic information.
|
||||||
"strictNullChecks": true,
|
// "incremental": true, // Enable incremental compilation by reading/writing information from prior compilations to a file on disk.
|
||||||
"declarationDir": "dist/types"
|
// "isolatedModules": false, // Transpile each file as a separate module (similar to 'ts.transpileModule').
|
||||||
},
|
// "listEmittedFiles": false, // Print names of generated files part of the compilation.
|
||||||
"include": ["src/**/*.ts","src/*.ts"]
|
// "listFiles": true, // Print names of files part of the compilation.
|
||||||
|
// "noErrorTruncation": false, // Do not truncate error messages.
|
||||||
|
// "preserveWatchOutput": false, // Keep outdated console output in watch mode instead of clearing the screen.
|
||||||
|
// "traceResolution": false, // Enable tracing of the name resolution process.
|
||||||
|
// "tsBuildInfoFile": ".tsbuildinfo", // Specify file to store incremental compilation information.
|
||||||
|
// Strict typechecking options
|
||||||
|
// "strict": false, // Enable all strict type-checking options.
|
||||||
|
// "noImplicitAny": true, // Raise error on expressions and declarations with an implied 'any' type.
|
||||||
|
// "noImplicitThis": true, // Raise error on 'this' expressions with an implied 'any' type.
|
||||||
|
// "strictBindCallApply": true, // Enable stricter checking of of the `bind`, `call`, and `apply` methods on functions.
|
||||||
|
// "strictFunctionTypes": true, // Disable bivariant parameter checking for function types.
|
||||||
|
// "strictNullChecks": true, // In strict null checking mode, the null and undefined values are not in the domain of every type and are only assignable to themselves and any.
|
||||||
|
// "strictPropertyInitialization": true, // Ensure non-undefined class properties are initialized in the constructor. This option requires `--strictNullChecks` be enabled in order to take effect.
|
||||||
|
// "alwaysStrict": true, // Parse in strict mode and emit "use strict" for each source file.
|
||||||
|
// Additional checks
|
||||||
|
// "allowUnreachableCode": false, // Do not report errors on unreachable code.
|
||||||
|
// "allowUnusedLabels": false, // Do not report errors on unused labels.
|
||||||
|
"forceConsistentCasingInFileNames": true, // Disallow inconsistently-cased references to the same file.
|
||||||
|
// "noStrictGenericChecks": false, // Disable strict checking of generic signatures in function types.
|
||||||
|
"noUnusedLocals": true, // Report errors on unused locals.
|
||||||
|
"noUnusedParameters": false, // Report errors on unused parameters.
|
||||||
|
"noImplicitReturns": true, // Report error when not all code paths in function return a value.
|
||||||
|
"noFallthroughCasesInSwitch": true, // Report errors for fallthrough cases in switch statement.
|
||||||
|
// "skipLibCheck": false, // Skip type checking of all declaration files (*.d.ts).
|
||||||
|
// "suppressExcessPropertyErrors": false, // Suppress excess property checks for object literals.
|
||||||
|
// "suppressImplicitAnyIndexErrors": false, // Suppress noImplicitAny errors for indexing objects lacking index signatures.
|
||||||
|
// Module resolution options
|
||||||
|
"moduleResolution": "node", // Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6).
|
||||||
|
// "baseUrl": "./", // Base directory to resolve non-absolute module names.
|
||||||
|
// "paths": {}, // A series of entries which re-map imports to lookup locations relative to the 'baseUrl'.
|
||||||
|
// "rootDirs": [], // List of root folders whose combined content represents the structure of the project at runtime.
|
||||||
|
// "typeRoots": [], // List of folders to include type definitions from.
|
||||||
|
"types": [
|
||||||
|
"jest",
|
||||||
|
"node"
|
||||||
|
], // Type declaration files to be included in compilation.
|
||||||
|
// "allowSyntheticDefaultImports": false // Allow default imports from modules with no default export. This does not affect code emit, just typechecking.
|
||||||
|
"esModuleInterop": true, // Emit '__importStar' and '__importDefault' helpers for runtime babel ecosystem compatibility and enable '--allowSyntheticDefaultImports' for typesystem compatibility.
|
||||||
|
// "maxNodeModuleJsDepth": 0, // The maximum dependency depth to search under node_modules and load JavaScript files. Only applicable with --allowJs.
|
||||||
|
// "preserveSymlinks": false, // Do not resolve the real path of symlinks.
|
||||||
|
"resolveJsonModule": true, // Include modules imported with '.json' extension.
|
||||||
|
// Emit options
|
||||||
|
"declaration": true, // Generates corresponding '.d.ts' file.
|
||||||
|
"declarationDir": "dist/types", // Output directory for generated declaration files.
|
||||||
|
// "declarationMap": false, // Generates a sourcemap for each corresponding '.d.ts' file.
|
||||||
|
// "emitBOM": false, // Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files.
|
||||||
|
// "emitDeclarationOnly": false, // Only emit ‘.d.ts’ declaration files.
|
||||||
|
// "importHelpers": false, // Import emit helpers from 'tslib'.
|
||||||
|
// "newLine": "LF", // Use the specified end of line sequence to be used when emitting files: "crlf" (windows) or "lf" (unix).”
|
||||||
|
// "noEmit": true, // Do not emit outputs.
|
||||||
|
// "noEmitHelpers": false, // Do not generate custom helper functions like __extends in compiled output.
|
||||||
|
// "noEmitOnError": false, // Do not emit outputs if any errors were reported.
|
||||||
|
// "noImplicitUseStrict": false, // Do not emit "use strict" directives in module output.
|
||||||
|
// "noResolve": false, // Do not add triple-slash references or module import targets to the list of compiled files.
|
||||||
|
"preserveConstEnums": true, // Do not erase const enum declarations in generated code.
|
||||||
|
// "removeComments": false, // Remove all comments except copy-right header comments beginning with
|
||||||
|
// "experimentalDecorators": true, // Enables experimental support for ES7 decorators.
|
||||||
|
// "emitDecoratorMetadata": true, // Enables experimental support for emitting type metadata for decorators.
|
||||||
|
// Source map options
|
||||||
|
// "sourceMap": false, // Generates corresponding '.map' file.
|
||||||
|
// "sourceRoot": "", // Specify the location where debugger should locate TypeScript files instead of source locations.
|
||||||
|
// "mapRoot": "", // Specify the location where debugger should locate map files instead of generated locations.
|
||||||
|
// "inlineSourceMap": true, // Emit a single file with source maps instead of having a separate file.
|
||||||
|
// "inlineSources": true, // Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set.
|
||||||
|
// JSX options
|
||||||
|
// "jsx": "preserve", // Specify JSX code generation: 'preserve', 'react-native', or 'react'.
|
||||||
|
// "jsxFactory": "React.createElement", // Specify the JSX factory function to use when targeting react JSX emit, e.g. 'React.createElement' or 'h'.
|
||||||
|
// Other options
|
||||||
|
// "allowUmdGlobalAccess": true, // Allow accessing UMD globals from modules.
|
||||||
|
// "charset": "utf8", // The character set of the input files.
|
||||||
|
// "downlevelIteration": false, // Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'.
|
||||||
|
// "disableSizeLimit": false, // Disable size limitation on JavaScript project.
|
||||||
|
// "keyofStringsOnly": false, // Resolve 'keyof' to string valued property names only (no numbers or symbols).
|
||||||
|
// "noLib": false, // Do not include the default library file (lib.d.ts).
|
||||||
|
// "pretty": true, // Stylize errors and messages using color and context.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user