mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[ADD] hooks: add useExternalListener hook
It is very useful. Also, this commit prettifies the code. closes #608
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
- [`useContext`](#usecontext)
|
- [`useContext`](#usecontext)
|
||||||
- [`useRef`](#useref)
|
- [`useRef`](#useref)
|
||||||
- [`useSubEnv`](#usesubenv)
|
- [`useSubEnv`](#usesubenv)
|
||||||
|
- [`useExternalListener`](#useexternallistener)
|
||||||
- [`useStore`](#usestore)
|
- [`useStore`](#usestore)
|
||||||
- [`useDispatch`](#usedispatch)
|
- [`useDispatch`](#usedispatch)
|
||||||
- [`useGetters`](#usegetters)
|
- [`useGetters`](#usegetters)
|
||||||
@@ -354,6 +355,17 @@ will be added to the parent environment. Note that it will extend, not replace
|
|||||||
the parent environment. And of course, the parent environment will not be
|
the parent environment. And of course, the parent environment will not be
|
||||||
affected.
|
affected.
|
||||||
|
|
||||||
|
### `useExternalListener`
|
||||||
|
|
||||||
|
The `useExternalListener` hook helps solve a very common problem: adding and removing
|
||||||
|
a listener on some target whenever a component is mounted/unmounted. For example,
|
||||||
|
a dropdown menu (or its parent) may need to listen to a `click` event on `window`
|
||||||
|
to be closed:
|
||||||
|
|
||||||
|
```js
|
||||||
|
useExternalListener(window, "click", this.closeMenu);
|
||||||
|
```
|
||||||
|
|
||||||
### `useStore`
|
### `useStore`
|
||||||
|
|
||||||
The `useStore` hook is the entry point for a component to connect to the store.
|
The `useStore` hook is the entry point for a component to connect to the store.
|
||||||
|
|||||||
@@ -131,3 +131,27 @@ export function useSubEnv(nextEnv) {
|
|||||||
const component = Component.current!;
|
const component = Component.current!;
|
||||||
component.env = Object.assign(Object.create(component.env), nextEnv);
|
component.env = Object.assign(Object.create(component.env), nextEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// useExternalListener
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a component needs to listen to DOM Events on element(s) that are not
|
||||||
|
* part of his hierarchy, we can use the `useExternalListener` hook.
|
||||||
|
* It will correctly add and remove the event listener, whenever the
|
||||||
|
* component is mounted and unmounted.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* a menu needs to listen to the click on window to be closed automatically
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* in the constructor of the OWL component that needs to be notified,
|
||||||
|
* `useExternalListener(window, 'click', this._doSomething);`
|
||||||
|
* */
|
||||||
|
export function useExternalListener(target: HTMLElement, eventName: string, handler, eventParams?) {
|
||||||
|
const boundHandler = handler.bind(Component.current);
|
||||||
|
|
||||||
|
onMounted(() => target.addEventListener(eventName, boundHandler, eventParams));
|
||||||
|
onWillUnmount(() => target.removeEventListener(eventName, boundHandler, eventParams));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { compileExpr , compileExprToArray , QWebVar } from "./expression_parser";
|
import { compileExpr, compileExprToArray, QWebVar } from "./expression_parser";
|
||||||
|
|
||||||
export const INTERP_REGEXP = /\{\{.*?\}\}/g;
|
export const INTERP_REGEXP = /\{\{.*?\}\}/g;
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
@@ -161,16 +161,18 @@ export class CompilationContext {
|
|||||||
const argId = this.generateID();
|
const argId = this.generateID();
|
||||||
const tokens = compileExprToArray(expr, this.variables);
|
const tokens = compileExprToArray(expr, this.variables);
|
||||||
const done = new Set();
|
const done = new Set();
|
||||||
return tokens.map((tok) => {
|
return tokens
|
||||||
if (tok.varName) {
|
.map(tok => {
|
||||||
if (!done.has(tok.varName)) {
|
if (tok.varName) {
|
||||||
done.add(tok.varName);
|
if (!done.has(tok.varName)) {
|
||||||
this.addLine(`const ${tok.varName}_${argId} = ${tok.value};`);
|
done.add(tok.varName);
|
||||||
|
this.addLine(`const ${tok.varName}_${argId} = ${tok.value};`);
|
||||||
|
}
|
||||||
|
tok.value = `${tok.varName}_${argId}`;
|
||||||
}
|
}
|
||||||
tok.value = `${tok.varName}_${argId}`;
|
return tok.value;
|
||||||
}
|
})
|
||||||
return tok.value;
|
.join("");
|
||||||
}).join("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,7 +196,9 @@ export class CompilationContext {
|
|||||||
const protectID = this.generateID();
|
const protectID = this.generateID();
|
||||||
this.rootContext.protectedScopeNumber++;
|
this.rootContext.protectedScopeNumber++;
|
||||||
this.rootContext.shouldDefineScope = true;
|
this.rootContext.shouldDefineScope = true;
|
||||||
const scopeExpr = codeBlock ? `Object.create(scope);` : `Object.assign(Object.create(context), scope);`;
|
const scopeExpr = codeBlock
|
||||||
|
? `Object.create(scope);`
|
||||||
|
: `Object.assign(Object.create(context), scope);`;
|
||||||
this.addLine(`let _origScope${protectID} = scope;`);
|
this.addLine(`let _origScope${protectID} = scope;`);
|
||||||
this.addLine(`scope = ${scopeExpr}`);
|
this.addLine(`scope = ${scopeExpr}`);
|
||||||
return protectID;
|
return protectID;
|
||||||
|
|||||||
@@ -283,5 +283,7 @@ 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).map(t => t.value).join("");
|
return compileExprToArray(expr, scope)
|
||||||
|
.map(t => t.value)
|
||||||
|
.join("");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2683,7 +2683,7 @@ describe("other directives with t-component", () => {
|
|||||||
<p>EndLoop: <t t-esc="iter"/></p>
|
<p>EndLoop: <t t-esc="iter"/></p>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
state = useState({values: ['a', 'b']});
|
state = useState({ values: ["a", "b"] });
|
||||||
}
|
}
|
||||||
const widget = new SomeWidget();
|
const widget = new SomeWidget();
|
||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
@@ -2710,23 +2710,23 @@ describe("other directives with t-component", () => {
|
|||||||
<p><t t-esc="iter"/></p>
|
<p><t t-esc="iter"/></p>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
state = {flag: 'if'};
|
state = { flag: "if" };
|
||||||
}
|
}
|
||||||
const widget = new SomeWidget();
|
const widget = new SomeWidget();
|
||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div><p>2</p></div>");
|
expect(fixture.innerHTML).toBe("<div><p>2</p></div>");
|
||||||
widget.state.flag = 'elif';
|
widget.state.flag = "elif";
|
||||||
await widget.render();
|
await widget.render();
|
||||||
expect(fixture.innerHTML).toBe("<div><p>3</p></div>");
|
expect(fixture.innerHTML).toBe("<div><p>3</p></div>");
|
||||||
widget.state.flag = 'false';
|
widget.state.flag = "false";
|
||||||
await widget.render();
|
await widget.render();
|
||||||
expect(fixture.innerHTML).toBe("<div><p>4</p></div>");
|
expect(fixture.innerHTML).toBe("<div><p>4</p></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("t-set in t-if", async () => {
|
test("t-set in t-if", async () => {
|
||||||
// Weird that code block within 'if' leaks outside of it
|
// Weird that code block within 'if' leaks outside of it
|
||||||
// Python does the same
|
// Python does the same
|
||||||
class SomeWidget extends Component<any, any> {
|
class SomeWidget extends Component<any, any> {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
@@ -2743,16 +2743,16 @@ describe("other directives with t-component", () => {
|
|||||||
<p><t t-esc="iter"/></p>
|
<p><t t-esc="iter"/></p>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
state = {flag: 'if'};
|
state = { flag: "if" };
|
||||||
}
|
}
|
||||||
const widget = new SomeWidget();
|
const widget = new SomeWidget();
|
||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div><p>2</p></div>");
|
expect(fixture.innerHTML).toBe("<div><p>2</p></div>");
|
||||||
widget.state.flag = 'elif';
|
widget.state.flag = "elif";
|
||||||
await widget.render();
|
await widget.render();
|
||||||
expect(fixture.innerHTML).toBe("<div><p>3</p></div>");
|
expect(fixture.innerHTML).toBe("<div><p>3</p></div>");
|
||||||
widget.state.flag = 'false';
|
widget.state.flag = "false";
|
||||||
await widget.render();
|
await widget.render();
|
||||||
expect(fixture.innerHTML).toBe("<div><p>4</p></div>");
|
expect(fixture.innerHTML).toBe("<div><p>4</p></div>");
|
||||||
});
|
});
|
||||||
@@ -2777,7 +2777,10 @@ describe("other directives with t-component", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("t-set can't alter from within callee", async () => {
|
test("t-set can't alter from within callee", async () => {
|
||||||
env.qweb.addTemplate("ChildWidget", `<div><t t-esc="iter"/><t t-set="iter" t-value="'called'"/><t t-esc="iter"/></div>`);
|
env.qweb.addTemplate(
|
||||||
|
"ChildWidget",
|
||||||
|
`<div><t t-esc="iter"/><t t-set="iter" t-value="'called'"/><t t-esc="iter"/></div>`
|
||||||
|
);
|
||||||
class SomeWidget extends Component<any, any> {
|
class SomeWidget extends Component<any, any> {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
@@ -2795,7 +2798,10 @@ describe("other directives with t-component", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("t-set can't alter in t-call body", async () => {
|
test("t-set can't alter in t-call body", async () => {
|
||||||
env.qweb.addTemplate("ChildWidget", `<div><t t-esc="iter"/><t t-set="iter" t-value="'called'"/><t t-esc="iter"/></div>`);
|
env.qweb.addTemplate(
|
||||||
|
"ChildWidget",
|
||||||
|
`<div><t t-esc="iter"/><t t-set="iter" t-value="'called'"/><t t-esc="iter"/></div>`
|
||||||
|
);
|
||||||
class SomeWidget extends Component<any, any> {
|
class SomeWidget extends Component<any, any> {
|
||||||
static template = xml`
|
static template = xml`
|
||||||
<div>
|
<div>
|
||||||
@@ -2841,7 +2847,7 @@ describe("other directives with t-component", () => {
|
|||||||
let child;
|
let child;
|
||||||
class ChildWidget extends Component<any, any> {
|
class ChildWidget extends Component<any, any> {
|
||||||
static template = xml`<div><t t-esc="iter"/><t t-set="iter" t-value="'called'"/><t t-esc="iter"/></div>`;
|
static template = xml`<div><t t-esc="iter"/><t t-set="iter" t-value="'called'"/><t t-esc="iter"/></div>`;
|
||||||
iter = 'child';
|
iter = "child";
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
child = this;
|
child = this;
|
||||||
@@ -2861,7 +2867,7 @@ describe("other directives with t-component", () => {
|
|||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div><p>source</p><div>childcalled</div><p>source</p></div>");
|
expect(fixture.innerHTML).toBe("<div><p>source</p><div>childcalled</div><p>source</p></div>");
|
||||||
expect(child.iter).toBe('child');
|
expect(child.iter).toBe("child");
|
||||||
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
|
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2877,7 +2883,7 @@ describe("other directives with t-component", () => {
|
|||||||
<p>EndLoop: <t t-esc="iter"/></p>
|
<p>EndLoop: <t t-esc="iter"/></p>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
state = useState({values: ['a', 'b']});
|
state = useState({ values: ["a", "b"] });
|
||||||
}
|
}
|
||||||
const widget = new SomeWidget();
|
const widget = new SomeWidget();
|
||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
@@ -2896,7 +2902,7 @@ describe("other directives with t-component", () => {
|
|||||||
<p>EndLoop: <t t-esc="iter"/></p>
|
<p>EndLoop: <t t-esc="iter"/></p>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
state = useState({values: ['a', 'b']});
|
state = useState({ values: ["a", "b"] });
|
||||||
}
|
}
|
||||||
const widget = new SomeWidget();
|
const widget = new SomeWidget();
|
||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
@@ -2913,18 +2919,20 @@ describe("other directives with t-component", () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
state = useState({values: ['a', 'b']});
|
state = useState({ values: ["a", "b"] });
|
||||||
otherState = {vals: []};
|
otherState = { vals: [] };
|
||||||
}
|
}
|
||||||
const widget = new SomeWidget();
|
const widget = new SomeWidget();
|
||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div><div>0: a<button>Expr</button></div><div>1: b<button>Expr</button></div></div>");
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div>0: a<button>Expr</button></div><div>1: b<button>Expr</button></div></div>"
|
||||||
|
);
|
||||||
expect(widget.otherState.vals).toStrictEqual([]);
|
expect(widget.otherState.vals).toStrictEqual([]);
|
||||||
const buttons = fixture.querySelectorAll('button');
|
const buttons = fixture.querySelectorAll("button");
|
||||||
buttons[0].click();
|
buttons[0].click();
|
||||||
buttons[1].click();
|
buttons[1].click();
|
||||||
expect(widget.otherState.vals).toStrictEqual(['a', 'b']);
|
expect(widget.otherState.vals).toStrictEqual(["a", "b"]);
|
||||||
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
|
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2940,18 +2948,20 @@ describe("other directives with t-component", () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
state = useState({values: ['a', 'b']});
|
state = useState({ values: ["a", "b"] });
|
||||||
otherState = {vals: []};
|
otherState = { vals: [] };
|
||||||
}
|
}
|
||||||
const widget = new SomeWidget();
|
const widget = new SomeWidget();
|
||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
|
|
||||||
expect(fixture.innerHTML).toBe("<div><div>0: a<button>Expr</button></div><div>1: b<button>Expr</button></div></div>");
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div>0: a<button>Expr</button></div><div>1: b<button>Expr</button></div></div>"
|
||||||
|
);
|
||||||
expect(widget.otherState.vals).toStrictEqual([]);
|
expect(widget.otherState.vals).toStrictEqual([]);
|
||||||
const buttons = fixture.querySelectorAll('button');
|
const buttons = fixture.querySelectorAll("button");
|
||||||
buttons[0].click();
|
buttons[0].click();
|
||||||
buttons[1].click();
|
buttons[1].click();
|
||||||
expect(widget.otherState.vals).toStrictEqual(['a_nova_0', 'b_nova_0_1']);
|
expect(widget.otherState.vals).toStrictEqual(["a_nova_0", "b_nova_0_1"]);
|
||||||
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
|
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2965,8 +2975,8 @@ describe("other directives with t-component", () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
state = useState({values: ['a', 'b']});
|
state = useState({ values: ["a", "b"] });
|
||||||
otherState = {vals: new Array<string>()};
|
otherState = { vals: new Array<string>() };
|
||||||
|
|
||||||
addVal(val: string) {
|
addVal(val: string) {
|
||||||
this.otherState.vals.push(val);
|
this.otherState.vals.push(val);
|
||||||
@@ -2975,13 +2985,14 @@ describe("other directives with t-component", () => {
|
|||||||
const widget = new SomeWidget();
|
const widget = new SomeWidget();
|
||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
expect(fixture.innerHTML).toBe("<div><div>0: a<button>meth call</button></div><div>1: b<button>meth call</button></div></div>");
|
"<div><div>0: a<button>meth call</button></div><div>1: b<button>meth call</button></div></div>"
|
||||||
|
);
|
||||||
expect(widget.otherState.vals).toStrictEqual([]);
|
expect(widget.otherState.vals).toStrictEqual([]);
|
||||||
const buttons = fixture.querySelectorAll('button');
|
const buttons = fixture.querySelectorAll("button");
|
||||||
buttons[0].click();
|
buttons[0].click();
|
||||||
buttons[1].click();
|
buttons[1].click();
|
||||||
expect(widget.otherState.vals).toStrictEqual(['a', 'b']);
|
expect(widget.otherState.vals).toStrictEqual(["a", "b"]);
|
||||||
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
|
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2996,19 +3007,20 @@ describe("other directives with t-component", () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
arr = ['a', 'b']
|
arr = ["a", "b"];
|
||||||
otherState = {vals: new Array<string>()};
|
otherState = { vals: new Array<string>() };
|
||||||
}
|
}
|
||||||
const widget = new SomeWidget();
|
const widget = new SomeWidget();
|
||||||
await widget.mount(fixture);
|
await widget.mount(fixture);
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe(
|
||||||
expect(fixture.innerHTML).toBe("<div><div><button>expr</button></div><div><button>expr</button></div></div>");
|
"<div><div><button>expr</button></div><div><button>expr</button></div></div>"
|
||||||
|
);
|
||||||
expect(widget.otherState.vals).toStrictEqual([]);
|
expect(widget.otherState.vals).toStrictEqual([]);
|
||||||
const buttons = fixture.querySelectorAll('button');
|
const buttons = fixture.querySelectorAll("button");
|
||||||
buttons[0].click();
|
buttons[0].click();
|
||||||
buttons[1].click();
|
buttons[1].click();
|
||||||
expect(widget.otherState.vals).toStrictEqual(['0_0', '1_1']);
|
expect(widget.otherState.vals).toStrictEqual(["0_0", "1_1"]);
|
||||||
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
|
expect(QWeb.TEMPLATES[SomeWidget.template].fn.toString()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -6069,7 +6081,7 @@ describe("t-call", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("sub components in two t-calls", async () => {
|
test("sub components in two t-calls", async () => {
|
||||||
class Child extends Component<any,any> {
|
class Child extends Component<any, any> {
|
||||||
static template = xml`<span><t t-esc="props.val"/></span>`;
|
static template = xml`<span><t t-esc="props.val"/></span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6083,7 +6095,7 @@ describe("t-call", () => {
|
|||||||
<div t-else=""><t t-call="sub"/></div>
|
<div t-else=""><t t-call="sub"/></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
state = useState({val: 1});
|
state = useState({ val: 1 });
|
||||||
}
|
}
|
||||||
const parent = new Parent();
|
const parent = new Parent();
|
||||||
await parent.mount(fixture);
|
await parent.mount(fixture);
|
||||||
@@ -6092,5 +6104,4 @@ describe("t-call", () => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div><div><span>2</span></div></div>");
|
expect(fixture.innerHTML).toBe("<div><div><span>2</span></div></div>");
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -137,8 +137,9 @@ describe("styles and component", () => {
|
|||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
}`);
|
}`);
|
||||||
|
|
||||||
expect(sheet).toBe(`.parent-a .child-a, .parent-a .child-b, .parent-b .child-a, .parent-b .child-b {
|
expect(sheet)
|
||||||
|
.toBe(`.parent-a .child-a, .parent-a .child-b, .parent-b .child-a, .parent-b .child-b {
|
||||||
color: red;
|
color: red;
|
||||||
}`);
|
}`);
|
||||||
});
|
});
|
||||||
@@ -166,5 +167,4 @@ describe("styles and component", () => {
|
|||||||
color: red;
|
color: red;
|
||||||
}`);
|
}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
+37
-1
@@ -9,7 +9,8 @@ import {
|
|||||||
onWillPatch,
|
onWillPatch,
|
||||||
onWillStart,
|
onWillStart,
|
||||||
onWillUpdateProps,
|
onWillUpdateProps,
|
||||||
useSubEnv
|
useSubEnv,
|
||||||
|
useExternalListener
|
||||||
} from "../src/hooks";
|
} from "../src/hooks";
|
||||||
import { xml } from "../src/tags";
|
import { xml } from "../src/tags";
|
||||||
|
|
||||||
@@ -592,4 +593,39 @@ describe("hooks", () => {
|
|||||||
expect(fixture.innerHTML).toBe("<div><span>2</span></div>");
|
expect(fixture.innerHTML).toBe("<div><span>2</span></div>");
|
||||||
expect(steps).toEqual(["onWillStart", "onWillUpdateProps"]);
|
expect(steps).toEqual(["onWillStart", "onWillUpdateProps"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("useExternalListener", async () => {
|
||||||
|
let n = 0;
|
||||||
|
|
||||||
|
class MyComponent extends Component<any, any> {
|
||||||
|
static template = xml`<span><t t-esc="props.value"/></span>`;
|
||||||
|
constructor(parent, props) {
|
||||||
|
super(parent, props);
|
||||||
|
useExternalListener(window as any, "click", this.increment);
|
||||||
|
}
|
||||||
|
increment() {
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class App extends Component<any, any> {
|
||||||
|
static template = xml`<div><MyComponent t-if="state.flag"/></div>`;
|
||||||
|
static components = { MyComponent };
|
||||||
|
state = useState({ flag: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = new App();
|
||||||
|
await app.mount(fixture);
|
||||||
|
|
||||||
|
expect(n).toBe(0);
|
||||||
|
window.dispatchEvent(new Event("click"));
|
||||||
|
expect(n).toBe(0);
|
||||||
|
app.state.flag = true;
|
||||||
|
await nextTick();
|
||||||
|
window.dispatchEvent(new Event("click"));
|
||||||
|
expect(n).toBe(1);
|
||||||
|
app.state.flag = false;
|
||||||
|
await nextTick();
|
||||||
|
window.dispatchEvent(new Event("click"));
|
||||||
|
expect(n).toBe(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,18 +21,17 @@ test("log a sub component with non stringifiable props", async () => {
|
|||||||
const log = console.log;
|
const log = console.log;
|
||||||
console.log = arg => steps.push(arg);
|
console.log = arg => steps.push(arg);
|
||||||
|
|
||||||
class Child extends Component<any,any> {
|
class Child extends Component<any, any> {
|
||||||
static template = xml`<span><t t-esc="props.obj.val"/></span>`;
|
static template = xml`<span><t t-esc="props.obj.val"/></span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const circularObject: any = {val: 1};
|
const circularObject: any = { val: 1 };
|
||||||
circularObject.circularObject = circularObject;
|
circularObject.circularObject = circularObject;
|
||||||
|
|
||||||
class Parent extends Component<any, any> {
|
class Parent extends Component<any, any> {
|
||||||
static template = xml`<div><Child obj="obj"/></div>`;
|
static template = xml`<div><Child obj="obj"/></div>`;
|
||||||
static components = { Child };
|
static components = { Child };
|
||||||
obj = circularObject;
|
obj = circularObject;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const parent = new Parent(null, {});
|
const parent = new Parent(null, {});
|
||||||
|
|||||||
Reference in New Issue
Block a user