[DOC] update quick_start and how to test pages

This commit is contained in:
Géry Debongnie
2021-12-20 17:17:32 +01:00
committed by Samuel Degueldre
parent 3f09a953b0
commit f014b1a33e
3 changed files with 50 additions and 94 deletions
+11 -46
View File
@@ -30,27 +30,21 @@ To help with this, it is useful to have a `helper.js` file that contains some
common utility functions: common utility functions:
```js ```js
let lastFixture = null;
export function makeTestFixture() { export function makeTestFixture() {
let fixture = document.createElement("div"); let fixture = document.createElement("div");
document.body.appendChild(fixture); document.body.appendChild(fixture);
if (lastFixture) {
lastFixture.remove();
}
lastFixture = fixture;
return fixture; return fixture;
} }
export function nextTick() { export async function nextTick() {
let requestAnimationFrame = owl.Component.scheduler.requestAnimationFrame; await new Promise((resolve) => setTimeout(resolve));
return new Promise(function(resolve) { await new Promise((resolve) => requestAnimationFrame(resolve));
setTimeout(() => requestAnimationFrame(() => resolve()));
});
}
export function makeTestEnv() {
// application specific. It needs a way to load actual templates
const templates = ...;
return {
qweb: new QWeb(templates),
..., // each service can be mocked here
};
} }
``` ```
@@ -59,7 +53,7 @@ With such a file, a typical test suite for Jest will look like this:
```js ```js
// in SomeComponent.test.js // in SomeComponent.test.js
import { SomeComponent } from "../../src/ui/SomeComponent"; import { SomeComponent } from "../../src/ui/SomeComponent";
import { nextTick, makeTestFixture, makeTestEnv} from '../helpers'; import { nextTick, makeTestFixture } from '../helpers';
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -70,9 +64,6 @@ let env: Env;
beforeEach(() => { beforeEach(() => {
fixture = makeTestFixture(); fixture = makeTestFixture();
env = makeTestEnv();
// we set here the default environment for each component created in the test
Component.env = env;
}); });
afterEach(() => { afterEach(() => {
@@ -85,7 +76,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 = await mount(SomeComponent, { target: fixture, props }); const comp = await mount(SomeComponent, fixture, { props });
// do some assertions // do some assertions
expect(...).toBe(...); expect(...).toBe(...);
@@ -102,29 +93,3 @@ describe("SomeComponent", () => {
Note that Owl does wait for the next animation frame to actually update the DOM. Note that Owl does wait for the next animation frame to actually update the DOM.
This is why it is necessary to wait with the `nextTick` (or other methods) to This is why it is necessary to wait with the `nextTick` (or other methods) to
make sure that the DOM is up-to-date. make sure that the DOM is up-to-date.
It is sometimes useful to wait until Owl is completely done updating components
(in particular, if we have a highly concurrent user interface). This next
helper simply polls every 20ms the internal Owl task queue and returns a promise
which resolves when it is empty:
```js
function afterUpdates() {
return new Promise((resolve, reject) => {
let timer = setTimeout(poll, 20);
let counter = 0;
function poll() {
counter++;
if (owl.Component.scheduler.tasks.length) {
if (counter > 10) {
reject(new Error("timeout"));
} else {
timer = setTimeout(poll);
}
} else {
resolve();
}
}
});
}
```
+38 -47
View File
@@ -36,6 +36,8 @@ hello_owl/
The file `owl.js` can be downloaded from the last release published at The file `owl.js` can be downloaded from the last release published at
[https://github.com/odoo/owl/releases](https://github.com/odoo/owl/releases). It [https://github.com/odoo/owl/releases](https://github.com/odoo/owl/releases). It
is a single javascript file which export all Owl into the global `owl` object. is a single javascript file which export all Owl into the global `owl` object.
Note that there are multiple files, and in this case, we need one of the two
files suffixed with `.iife`: they are built to be directly used in a browser.
Now, `index.html` should contain the following: Now, `index.html` should contain the following:
@@ -45,30 +47,24 @@ Now, `index.html` should contain the following:
<head> <head>
<title>Hello Owl</title> <title>Hello Owl</title>
<script src="owl.js"></script> <script src="owl.js"></script>
<script src="app.js"></script>
</head> </head>
<body></body> <body>
<script src="app.js"></script>
</body>
</html> </html>
``` ```
And `app.js` should look like this: And `app.js` should look like this:
```js ```js
const { Component, mount } = owl; const { Component, mount, xml } = owl;
const { xml } = owl.tags;
const { whenReady } = owl.utils;
// Owl Components // Owl Components
class App extends Component { class Root extends Component {
static template = xml`<div>Hello Owl</div>`; static template = xml`<div>Hello Owl</div>`;
} }
// Setup code mount(Root, document.body);
function setup() {
mount(App, target: { document.body })
}
whenReady(setup);
``` ```
Now, simply loading this html file in a browser should display a welcome message. Now, simply loading this html file in a browser should display a welcome message.
@@ -93,14 +89,16 @@ Let us start a new project with the following file structure:
``` ```
hello_owl/ hello_owl/
src/ src/
app.js
index.html index.html
main.js main.js
owl.js owl.js
root.js
``` ```
As previously, the file `owl.js` can be downloaded from the last release published at As previously, the file `owl.js` can be downloaded from the last release published at
[https://github.com/odoo/owl/releases](https://github.com/odoo/owl/releases). [https://github.com/odoo/owl/releases](https://github.com/odoo/owl/releases).
Note that there are multiple files, and in this case, we need one of the two
files suffixed with `.iife`: they are built to be directly used in a browser.
Now, `index.html` should contain the following: Now, `index.html` should contain the following:
@@ -110,37 +108,33 @@ Now, `index.html` should contain the following:
<head> <head>
<title>Hello Owl</title> <title>Hello Owl</title>
<script src="owl.js"></script> <script src="owl.js"></script>
<script src="main.js" type="module"></script>
</head> </head>
<body></body> <body>
<script src="main.js" type="module"></script>
</body>
</html> </html>
``` ```
Not that the `main.js` script tag has the `type="module"` attribute. This means Not that the `main.js` script tag has the `type="module"` attribute. This means
that the browser will parse the script as a module, and load all its dependencies. that the browser will parse the script as a module, and load all its dependencies.
Here is the content of `app.js` and `main.js`: Here is the content of `root.js` and `main.js`:
```js ```js
// app.js ---------------------------------------------------------------------- // root.js ----------------------------------------------------------------------
const { Component, mount } = owl; const { Component, mount, xml } = owl;
const { xml } = owl.tags;
export class App extends Component { export class Root extends Component {
static template = xml`<div>Hello Owl</div>`; static template = xml`<div>Hello Owl</div>`;
} }
// main.js --------------------------------------------------------------------- // main.js ---------------------------------------------------------------------
import { App } from "./app.js"; import { Root } from "./root.js";
function setup() { mount(Root, document.body);
mount(App, { target: document.body });
}
owl.utils.whenReady(setup);
``` ```
The `main.js` file import the `app.js` file. Note that the import statement has The `main.js` file imports the `root.js` file. Note that the import statement has
a `.js` suffix, which is important. Most text editor can understand this syntax a `.js` suffix, which is important. Most text editor can understand this syntax
and will provide autocompletion. and will provide autocompletion.
@@ -193,11 +187,11 @@ hello_owl/
index.html index.html
src/ src/
components/ components/
App.js Root.js
main.js main.js
tests/ tests/
components/ components/
App.test.js Root.test.js
helpers.js helpers.js
.gitignore .gitignore
package.json package.json
@@ -224,13 +218,15 @@ Note that there are no `<script>` tag here. They will be injected by webpack.
Now, let's have a look at the javascript files: Now, let's have a look at the javascript files:
```js ```js
// src/components/App.js ------------------------------------------------------- // src/components/Root.js -------------------------------------------------------
import { Component, tags, useState } from "@odoo/owl"; import { Component, xml, useState } from "@odoo/owl";
const { xml } = tags; export class Root extends Component {
static template = xml`
<div t-on-click="update">
Hello <t t-esc="state.text"/>
</div>`;
export class App extends Component {
static template = xml`<div t-on-click="update">Hello <t t-esc="state.text"/></div>`;
state = useState({ text: "Owl" }); state = useState({ text: "Owl" });
update() { update() {
this.state.text = this.state.text === "Owl" ? "World" : "Owl"; this.state.text = this.state.text === "Owl" ? "World" : "Owl";
@@ -239,16 +235,12 @@ export class App extends Component {
// src/main.js ----------------------------------------------------------------- // src/main.js -----------------------------------------------------------------
import { utils, mount } from "@odoo/owl"; import { utils, mount } from "@odoo/owl";
import { App } from "./components/App"; import { Root } from "./components/Root";
function setup() { mount(Root, document.body);
mount(App, { target: document.body });
}
utils.whenReady(setup); // tests/components/Root.test.js ------------------------------------------------
import { Root } from "../../src/components/Root";
// tests/components/App.test.js ------------------------------------------------
import { App } from "../../src/components/App";
import { makeTestFixture, nextTick, click } from "../helpers"; import { makeTestFixture, nextTick, click } from "../helpers";
import { mount } from "@odoo/owl"; import { mount } from "@odoo/owl";
@@ -262,9 +254,9 @@ afterEach(() => {
fixture.remove(); fixture.remove();
}); });
describe("App", () => { describe("Root", () => {
test("Works as expected...", async () => { test("Works as expected...", async () => {
await mount(App, { target: fixture }); await mount(Root, fixture);
expect(fixture.innerHTML).toBe("<div>Hello Owl</div>"); expect(fixture.innerHTML).toBe("<div>Hello Owl</div>");
click(fixture, "div"); click(fixture, "div");
@@ -278,9 +270,8 @@ 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) { await new Promise((resolve) => setTimeout(resolve));
setTimeout(() => Component.scheduler.requestAnimationFrame(() => resolve())); await new Promise((resolve) => requestAnimationFrame(resolve));
});
} }
export function makeTestFixture() { export function makeTestFixture() {
+1 -1
View File
@@ -500,7 +500,7 @@ deleteTask(task) {
``` ```
Notice that the `onDelete` prop is defined with a `.bind` suffix: this is a special Notice that the `onDelete` prop is defined with a `.bind` suffix: this is a special
suffix that make sure the function callback is bound to the component. suffix that makes sure the function callback is bound to the component.
## 10. Using a store ## 10. Using a store