mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 71f545058b | |||
| 1a20cc57de |
@@ -120,7 +120,7 @@ npm install @odoo/owl
|
||||
|
||||
If you want to use a simple `<script>` tag, the last release can be downloaded here:
|
||||
|
||||
- [owl-1.2.1](https://github.com/odoo/owl/releases/tag/v1.2.1)
|
||||
- [owl-1.2.2](https://github.com/odoo/owl/releases/tag/v1.2.2)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odoo/owl",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "Odoo Web Library (OWL)",
|
||||
"main": "dist/owl.cjs.js",
|
||||
"browser": "dist/owl.iife.js",
|
||||
@@ -44,7 +44,7 @@
|
||||
"github-api": "^3.3.0",
|
||||
"jest": "^23.6.0",
|
||||
"jest-environment-jsdom": "^24.7.1",
|
||||
"live-server": "^1.2.1",
|
||||
"live-server": "^1.2.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.0.4",
|
||||
"rollup": "^1.6.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# 🦉 OWL Roadmap 🦉
|
||||
|
||||
- Current version: 1.2.1
|
||||
- Current version: 1.2.2
|
||||
- Status: stable
|
||||
|
||||
This roadmap is only an attempt at predicting Owl's future. Everything may
|
||||
|
||||
@@ -322,7 +322,14 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
||||
}
|
||||
if (__owl__.currentFiber) {
|
||||
const currentFiber = __owl__.currentFiber;
|
||||
if (currentFiber.target === target && currentFiber.position === position) {
|
||||
if (!currentFiber.target && !currentFiber.position) {
|
||||
// this means we have a pending rendering, but it was a render operation,
|
||||
// not a mount operation. We can simply update the fiber with the target
|
||||
// and the position
|
||||
currentFiber.target = target;
|
||||
currentFiber.position = position;
|
||||
return scheduler.addFiber(currentFiber);
|
||||
} else if (currentFiber.target === target && currentFiber.position === position) {
|
||||
return scheduler.addFiber(currentFiber);
|
||||
} else {
|
||||
scheduler.rejectFiber(currentFiber, "Mounting operation cancelled");
|
||||
@@ -366,12 +373,6 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
||||
async render(force: boolean = false): Promise<void> {
|
||||
const __owl__ = this.__owl__;
|
||||
const currentFiber = __owl__.currentFiber;
|
||||
if (!__owl__.isMounted && !currentFiber) {
|
||||
// if we get here, this means that the component was either never mounted,
|
||||
// or was unmounted and some state change triggered a render. Either way,
|
||||
// we do not want to actually render anything in this case.
|
||||
return;
|
||||
}
|
||||
if (currentFiber && !currentFiber.isRendered && !currentFiber.isCompleted) {
|
||||
return scheduler.addFiber(currentFiber.root);
|
||||
}
|
||||
@@ -385,8 +386,6 @@ export class Component<Props extends {} = any, T extends Env = Env> {
|
||||
if (fiber.isCompleted) {
|
||||
return;
|
||||
}
|
||||
// we are mounted (__owl__.isMounted), or if we are currently being
|
||||
// mounted (!isMounted), so we call __render
|
||||
this.__render(fiber);
|
||||
} else {
|
||||
// we were mounted when render was called, but we aren't anymore, so we
|
||||
|
||||
+22
-17
@@ -188,7 +188,8 @@ export class Fiber {
|
||||
complete() {
|
||||
let component = this.component;
|
||||
this.isCompleted = true;
|
||||
if (!this.target && !component.__owl__.isMounted) {
|
||||
const { isMounted, isDestroyed } = component.__owl__;
|
||||
if (isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,14 +203,16 @@ export class Fiber {
|
||||
const patchLen = patchQueue.length;
|
||||
|
||||
// call willPatch hook on each fiber of patchQueue
|
||||
for (let i = 0; i < patchLen; i++) {
|
||||
const fiber = patchQueue[i];
|
||||
if (fiber.shouldPatch) {
|
||||
component = fiber.component;
|
||||
if (component.__owl__.willPatchCB) {
|
||||
component.__owl__.willPatchCB();
|
||||
if (isMounted) {
|
||||
for (let i = 0; i < patchLen; i++) {
|
||||
const fiber = patchQueue[i];
|
||||
if (fiber.shouldPatch) {
|
||||
component = fiber.component;
|
||||
if (component.__owl__.willPatchCB) {
|
||||
component.__owl__.willPatchCB();
|
||||
}
|
||||
component.willPatch();
|
||||
}
|
||||
component.willPatch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,16 +274,18 @@ export class Fiber {
|
||||
}
|
||||
|
||||
// call patched/mounted hook on each fiber of (reversed) patchQueue
|
||||
for (let i = patchLen - 1; i >= 0; i--) {
|
||||
const fiber = patchQueue[i];
|
||||
component = fiber.component;
|
||||
if (fiber.shouldPatch && !this.target) {
|
||||
component.patched();
|
||||
if (component.__owl__.patchedCB) {
|
||||
component.__owl__.patchedCB();
|
||||
if (isMounted || inDOM) {
|
||||
for (let i = patchLen - 1; i >= 0; i--) {
|
||||
const fiber = patchQueue[i];
|
||||
component = fiber.component;
|
||||
if (fiber.shouldPatch && !this.target) {
|
||||
component.patched();
|
||||
if (component.__owl__.patchedCB) {
|
||||
component.__owl__.patchedCB();
|
||||
}
|
||||
} else {
|
||||
component.__callMounted();
|
||||
}
|
||||
} else if (this.target ? inDOM : true) {
|
||||
component.__callMounted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -962,6 +962,69 @@ describe("lifecycle hooks", () => {
|
||||
"parent:patched",
|
||||
]);
|
||||
});
|
||||
|
||||
test("willPatch/patched hook is not called if not mounted in DOM", async () => {
|
||||
const steps: string[] = [];
|
||||
|
||||
class ChildWidget extends Component {
|
||||
static template = xml`<div/>`;
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
steps.push("child:constructor");
|
||||
}
|
||||
mounted() {
|
||||
steps.push("child:mounted");
|
||||
}
|
||||
willPatch() {
|
||||
steps.push("child:willPatch");
|
||||
}
|
||||
patched() {
|
||||
steps.push("child:patched");
|
||||
}
|
||||
}
|
||||
class ParentWidget extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-component="child" v="state.n"/>
|
||||
</div>
|
||||
`;
|
||||
static components = { child: ChildWidget };
|
||||
state = useState({ n: 1 });
|
||||
constructor() {
|
||||
super();
|
||||
steps.push("parent:constructor");
|
||||
}
|
||||
mounted() {
|
||||
steps.push("parent:mounted");
|
||||
}
|
||||
willPatch() {
|
||||
steps.push("parent:willPatch");
|
||||
}
|
||||
patched() {
|
||||
steps.push("parent:patched");
|
||||
}
|
||||
}
|
||||
|
||||
const div = document.createElement("div");
|
||||
const widget = new ParentWidget();
|
||||
await widget.mount(div);
|
||||
expect(steps).toEqual(["parent:constructor", "child:constructor"]);
|
||||
|
||||
widget.state.n = 2;
|
||||
await nextTick();
|
||||
|
||||
expect(steps).toEqual(["parent:constructor", "child:constructor"]);
|
||||
|
||||
// then we remount the component in the dom
|
||||
await widget.mount(fixture);
|
||||
|
||||
expect(steps).toEqual([
|
||||
"parent:constructor",
|
||||
"child:constructor",
|
||||
"child:mounted",
|
||||
"parent:mounted",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("destroy method", () => {
|
||||
|
||||
@@ -323,6 +323,38 @@ describe("unmounting and remounting", () => {
|
||||
expect(steps).toEqual([2, 2, 3]);
|
||||
});
|
||||
|
||||
test("change state and render while mounted in detached dom", async () => {
|
||||
class App extends Component {
|
||||
static template = xml`<div><t t-esc="state.val"/></div>`;
|
||||
state = useState({ val: 1 });
|
||||
}
|
||||
|
||||
const detachedDiv = document.createElement("div");
|
||||
const app = await mount(App, { target: detachedDiv });
|
||||
|
||||
expect(detachedDiv.innerHTML).toBe("<div>1</div>");
|
||||
app.state.val = 2;
|
||||
await nextTick();
|
||||
expect(detachedDiv.innerHTML).toBe("<div>2</div>");
|
||||
});
|
||||
|
||||
test("destroy and change state after mounted in detached dom", async () => {
|
||||
class App extends Component {
|
||||
static template = xml`<div><t t-esc="state.val"/></div>`;
|
||||
state = useState({ val: 1 });
|
||||
}
|
||||
|
||||
const detachedDiv = document.createElement("div");
|
||||
const app = await mount(App, { target: detachedDiv });
|
||||
|
||||
expect(detachedDiv.innerHTML).toBe("<div>1</div>");
|
||||
|
||||
app.destroy();
|
||||
app.state.val = 2;
|
||||
await nextTick();
|
||||
expect(detachedDiv.innerHTML).toBe("");
|
||||
});
|
||||
|
||||
test("change state while component is unmounted", async () => {
|
||||
let child;
|
||||
class Child extends Component {
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as owl from "../../src/index";
|
||||
|
||||
import { Component, Env } from "../../src/component/component";
|
||||
import { xml } from "../../src/tags";
|
||||
import { makeTestFixture, makeTestEnv } from "../helpers";
|
||||
import { makeTestFixture, makeTestEnv, nextTick } from "../helpers";
|
||||
|
||||
let fixture: HTMLElement = makeTestFixture();
|
||||
let env: Env = makeTestEnv();
|
||||
@@ -31,6 +31,7 @@ test("log a specific message for render method calls if component is not mounted
|
||||
parent.unmount();
|
||||
parent.state.value = 2;
|
||||
|
||||
await nextTick();
|
||||
expect(steps).toEqual([
|
||||
"[OWL_DEBUG] Parent<id=1> constructor, props={}",
|
||||
"[OWL_DEBUG] Parent<id=1> mount",
|
||||
@@ -40,7 +41,10 @@ test("log a specific message for render method calls if component is not mounted
|
||||
"[OWL_DEBUG] Parent<id=1> mounted",
|
||||
"[OWL_DEBUG] scheduler: stop running tasks queue",
|
||||
"[OWL_DEBUG] Parent<id=1> willUnmount",
|
||||
"[OWL_DEBUG] Parent<id=1> render (warning: component is not mounted, this render has no effect)",
|
||||
"[OWL_DEBUG] Parent<id=1> render (warning: component is not mounted)",
|
||||
"[OWL_DEBUG] scheduler: start running tasks queue",
|
||||
"[OWL_DEBUG] Parent<id=1> rendering template",
|
||||
"[OWL_DEBUG] scheduler: stop running tasks queue",
|
||||
]);
|
||||
console.log = log;
|
||||
});
|
||||
|
||||
+1
-1
@@ -102,7 +102,7 @@
|
||||
const __owl__ = component.__owl__;
|
||||
let msg = `render`;
|
||||
if (!__owl__.isMounted && !__owl__.currentFiber) {
|
||||
msg += ` (warning: component is not mounted, this render has no effect)`;
|
||||
msg += ` (warning: component is not mounted)`;
|
||||
}
|
||||
log(msg);
|
||||
return render(...args);
|
||||
|
||||
Reference in New Issue
Block a user