mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[FIX] qweb/component: handle nested t-foreach and components
The way keys were handled in QWeb was mostly ok, but a little naive. It is fine when we deal with a list of dom nodes, since the reconciliation algorithm need only to be able to differentiate/reconcile nodes in that list, but it is an issue with components, which needs to globally be able to find themselves in their parent's children map. Because of the way this was handled, there were situations were Owl internal virtual dom would crash, since components wrongly assumed that they were already rendered in a different place. To fix this, we generalize the way keys are generated, by concatenating all sub keys coming from each iteration loops. closes #584
This commit is contained in:
@@ -322,6 +322,36 @@ describe("basic widget properties", () => {
|
||||
console.warn = warn;
|
||||
});
|
||||
|
||||
test("reconciliation alg works for t-foreach in t-foreach, 2", async () => {
|
||||
class Child extends Component<any, any> {
|
||||
static template = xml`<div><t t-esc="props.row + '_' + props.col"/></div>`;
|
||||
}
|
||||
|
||||
class Parent extends Component<any, any> {
|
||||
static template = xml`
|
||||
<div>
|
||||
<p t-foreach="state.rows" t-as="row" t-key="row">
|
||||
<p t-foreach="state.cols" t-as="col" t-key="col">
|
||||
<Child row="row" col="col"/>
|
||||
</p>
|
||||
</p>
|
||||
</div>`;
|
||||
static components = { Child };
|
||||
state = useState({ rows: [1, 2], cols: ["a", "b"] });
|
||||
}
|
||||
|
||||
const widget = new Parent();
|
||||
await widget.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<div><p><p><div>1_a</div></p><p><div>1_b</div></p></p><p><p><div>2_a</div></p><p><div>2_b</div></p></p></div>"
|
||||
);
|
||||
widget.state.rows = [2, 1];
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<div><p><p><div>2_a</div></p><p><div>2_b</div></p></p><p><p><div>1_a</div></p><p><div>1_b</div></p></p></div>"
|
||||
);
|
||||
});
|
||||
|
||||
test("same t-keys in two different places", async () => {
|
||||
class Child extends Component<any, any> {
|
||||
static template = xml`<span><t t-esc="props.blip"/></span>`;
|
||||
@@ -339,6 +369,7 @@ describe("basic widget properties", () => {
|
||||
const widget = new Parent();
|
||||
await widget.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><div><span>1</span></div><div><span>2</span></div></div>");
|
||||
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("t-key on a component with t-if, and a sibling component", async () => {
|
||||
@@ -4418,6 +4449,28 @@ describe("t-model directive", () => {
|
||||
expect(comp.state[2].f).toBe(false);
|
||||
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("two inputs in a div with a t-key", async () => {
|
||||
class SomeComponent extends Component<any, any> {
|
||||
static template = xml`
|
||||
<div t-key="'key'">
|
||||
<input class="a" t-if="state.flag"/>
|
||||
<input class="b" t-if="!state.flag"/>
|
||||
</div>
|
||||
`;
|
||||
state = useState({ flag: true });
|
||||
}
|
||||
const comp = new SomeComponent();
|
||||
await comp.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe('<div><input class="a"></div>');
|
||||
expect(env.qweb.templates[SomeComponent.template].fn.toString()).toMatchSnapshot();
|
||||
fixture.querySelector("input")!.value = "asdf";
|
||||
expect(fixture.querySelector("input")!.value).toBe("asdf");
|
||||
comp.state.flag = false;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe('<div><input class="b"></div>');
|
||||
expect(fixture.querySelector("input")!.value).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("environment and plugins", () => {
|
||||
|
||||
Reference in New Issue
Block a user