mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
9b1ec5dc0e
This commit moves OwlError to its own file in the new common folder such that it is no longer associated with the runtime.
364 lines
12 KiB
TypeScript
364 lines
12 KiB
TypeScript
import { OwlError } from "../../src/common/owl_error";
|
|
import { App, Component, mount, onMounted, useState, xml } from "../../src";
|
|
import { makeTestFixture, nextAppError, nextTick, snapshotEverything } from "../helpers";
|
|
|
|
snapshotEverything();
|
|
let fixture: HTMLElement;
|
|
let originalconsoleWarn = console.warn;
|
|
let mockConsoleWarn: any;
|
|
|
|
beforeEach(() => {
|
|
fixture = makeTestFixture();
|
|
mockConsoleWarn = jest.fn(() => {});
|
|
console.warn = mockConsoleWarn;
|
|
});
|
|
|
|
afterEach(() => {
|
|
console.warn = originalconsoleWarn;
|
|
});
|
|
|
|
describe("style and class handling", () => {
|
|
test("can set style and class inside component", async () => {
|
|
class Test extends Component {
|
|
static template = xml`
|
|
<div style="font-weight:bold;" class="some-class">world</div>
|
|
`;
|
|
}
|
|
await mount(Test, fixture);
|
|
expect(fixture.innerHTML).toBe(`<div style="font-weight:bold;" class="some-class">world</div>`);
|
|
});
|
|
|
|
test("can set class on sub component, as prop", async () => {
|
|
class Child extends Component {
|
|
static template = xml`<div t-att-class="props.class">child</div>`;
|
|
}
|
|
|
|
class Parent extends Component {
|
|
static template = xml`<Child class="'some-class'" />`;
|
|
static components = { Child };
|
|
}
|
|
await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe(`<div class="some-class">child</div>`);
|
|
});
|
|
|
|
test("no class is set is parent does not give it as prop", async () => {
|
|
class Child extends Component {
|
|
static template = xml`<div t-att-class="props.class">child</div>`;
|
|
}
|
|
|
|
class Parent extends Component {
|
|
static template = xml`<Child />`;
|
|
static components = { Child };
|
|
}
|
|
await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe(`<div>child</div>`);
|
|
});
|
|
|
|
test("no class is set is child ignores it", async () => {
|
|
class Child extends Component {
|
|
static template = xml`<div>child</div>`;
|
|
}
|
|
|
|
class Parent extends Component {
|
|
static template = xml`<Child class="'hey'"/>`;
|
|
static components = { Child };
|
|
}
|
|
await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe(`<div>child</div>`);
|
|
});
|
|
|
|
test("empty class attribute is not added on widget root el", async () => {
|
|
class Child extends Component {
|
|
static template = xml`<span t-att-class="props.class"/>`;
|
|
}
|
|
class Parent extends Component {
|
|
static template = xml`<div><Child class=""/></div>`;
|
|
static components = { Child };
|
|
}
|
|
await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe(`<div><span></span></div>`);
|
|
});
|
|
|
|
test("can set more than one class on sub component", async () => {
|
|
class Child extends Component {
|
|
static template = xml`<div t-att-class="props.class">child</div>`;
|
|
}
|
|
|
|
class Parent extends Component {
|
|
static template = xml`<Child class="'a b'" />`;
|
|
static components = { Child };
|
|
}
|
|
await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe(`<div class="a b">child</div>`);
|
|
});
|
|
|
|
test("component class and parent class combine together", async () => {
|
|
class Child extends Component {
|
|
static template = xml`<div class="child" t-att-class="props.class">child</div>`;
|
|
}
|
|
|
|
class Parent extends Component {
|
|
static template = xml`<Child class="'from parent'" />`;
|
|
static components = { Child };
|
|
}
|
|
await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe(`<div class="child from parent">child</div>`);
|
|
});
|
|
|
|
test("can set class on sub sub component", async () => {
|
|
class ChildChild extends Component {
|
|
static template = xml`<div t-att-class="props.class">childchild</div>`;
|
|
}
|
|
|
|
class Child extends Component {
|
|
static template = xml`<ChildChild class="(props.class || '') + ' fromchild'" />`;
|
|
static components = { ChildChild };
|
|
}
|
|
|
|
class Parent extends Component {
|
|
static template = xml`<Child class="'fromparent'" />`;
|
|
static components = { Child };
|
|
}
|
|
await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe(`<div class="fromparent fromchild">childchild</div>`);
|
|
});
|
|
|
|
test("can set class on multi root component", async () => {
|
|
class Child extends Component {
|
|
static template = xml`<div>a</div><span t-att-class="props.class">b</span>`;
|
|
}
|
|
|
|
class Parent extends Component {
|
|
static template = xml`<Child class="'fromparent'" />`;
|
|
static components = { Child };
|
|
}
|
|
await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe(`<div>a</div><span class="fromparent">b</span>`);
|
|
});
|
|
|
|
test("class on sub component, which is switched to another", async () => {
|
|
class ChildA extends Component {
|
|
static template = xml`<div t-att-class="props.class">a</div>`;
|
|
}
|
|
class ChildB extends Component {
|
|
static template = xml`<span t-att-class="props.class">b</span>`;
|
|
}
|
|
class Child extends Component {
|
|
static template = xml`<ChildA class="props.class" t-if="props.child==='a'"/><ChildB class="props.class" t-else=""/>`;
|
|
static components = { ChildA, ChildB };
|
|
}
|
|
|
|
class Parent extends Component {
|
|
static template = xml`<Child class="'someclass'" child="state.child" />`;
|
|
static components = { Child };
|
|
|
|
state = useState({ child: "a" });
|
|
}
|
|
|
|
const parent = await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe(`<div class="someclass">a</div>`);
|
|
parent.state.child = "b";
|
|
await nextTick();
|
|
expect(fixture.innerHTML).toBe(`<span class="someclass">b</span>`);
|
|
});
|
|
|
|
// test("t-att-class is properly added/removed on widget root el", async () => {
|
|
// class Child extends Component {
|
|
// static template = xml`<div class="c"/>`;
|
|
// }
|
|
// class Parent extends Component {
|
|
// static template = xml`<div><Child t-att-class="{a:state.a, b:state.b}"/></div>`;
|
|
// static components = { Child };
|
|
// state = useState({ a: true, b: false });
|
|
// }
|
|
// const widget = await mount(Parent, fixture);
|
|
// expect(fixture.innerHTML).toBe(`<div><div class="c a"></div></div>`);
|
|
|
|
// widget.state.a = false;
|
|
// widget.state.b = true;
|
|
// await nextTick();
|
|
// expect(fixture.innerHTML).toBe(`<div><div class="c b"></div></div>`);
|
|
// });
|
|
|
|
test("class with extra whitespaces", async () => {
|
|
class Child extends Component {
|
|
static template = xml`<div t-att-class="props.class"/>`;
|
|
}
|
|
class Parent extends Component {
|
|
static template = xml`<Child class="'a b c d'"/>`;
|
|
static components = { Child };
|
|
}
|
|
await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe(`<div class="a b c d"></div>`);
|
|
});
|
|
|
|
test("class with extra whitespaces (variation)", async () => {
|
|
class Child extends Component {
|
|
static template = xml`<div t-att-class="props.class"/>`;
|
|
}
|
|
class Parent extends Component {
|
|
static template = xml`<p><Child class="'a b c d'"/></p>`;
|
|
static components = { Child };
|
|
}
|
|
await mount(Parent, fixture);
|
|
expect(fixture.innerHTML).toBe(`<p><div class="a b c d"></div></p>`);
|
|
});
|
|
|
|
// TODO: adapt name (t-att-class no longer makes sense on Components)
|
|
test("t-att-class is properly added/removed on widget root el (v2)", async () => {
|
|
let child: Child;
|
|
class Child extends Component {
|
|
static template = xml`<span class="c" t-att-class="{ d: state.d, ...props.class }"/>`;
|
|
state = useState({ d: true });
|
|
setup() {
|
|
child = this;
|
|
}
|
|
}
|
|
|
|
class Parent extends Component {
|
|
static template = xml`
|
|
<div>
|
|
<Child class="{ b: state.b }" />
|
|
</div>`;
|
|
static components = { Child };
|
|
state = useState({ b: true });
|
|
}
|
|
const widget = await mount(Parent, fixture);
|
|
|
|
const span = fixture.querySelector("span")!;
|
|
expect(span.className).toBe("c d b");
|
|
|
|
widget.state.b = false;
|
|
await nextTick();
|
|
expect(span.className).toBe("c d");
|
|
|
|
child!.state.d = false;
|
|
await nextTick();
|
|
expect(span.className).toBe("c");
|
|
|
|
widget.state.b = true;
|
|
await nextTick();
|
|
expect(span.className).toBe("c b");
|
|
|
|
child!.state.d = true;
|
|
await nextTick();
|
|
expect(span.className).toBe("c b d");
|
|
});
|
|
|
|
// TODO: adapt name (t-att-class no longer makes sense on Components)
|
|
test("t-att-class is properly added/removed on widget root el (v3)", async () => {
|
|
let child: Child;
|
|
class Child extends Component {
|
|
static template = xml`<span class="c" t-att-class="{ d: state.d, ...props.class }"/>`;
|
|
state = useState({ d: true });
|
|
setup() {
|
|
child = this;
|
|
}
|
|
}
|
|
class Parent extends Component {
|
|
static template = xml`<Child class="{ a: true, b: state.b }"/>`;
|
|
static components = { Child };
|
|
state = useState({ b: true });
|
|
}
|
|
const widget = await mount(Parent, fixture);
|
|
|
|
const span = fixture.querySelector("span")!;
|
|
expect(span.className).toBe("c d a b");
|
|
|
|
widget.state.b = false;
|
|
await nextTick();
|
|
expect(span.className).toBe("c d a");
|
|
|
|
child!.state.d = false;
|
|
await nextTick();
|
|
expect(span.className).toBe("c a");
|
|
|
|
widget.state.b = true;
|
|
await nextTick();
|
|
expect(span.className).toBe("c a b");
|
|
|
|
child!.state.d = true;
|
|
await nextTick();
|
|
expect(span.className).toBe("c a b d");
|
|
});
|
|
|
|
test("class on components do not interfere with user defined classes", async () => {
|
|
class App extends Component {
|
|
static template = xml`<div t-att-class="{ c: state.c }" />`;
|
|
state = useState({ c: true });
|
|
setup() {
|
|
onMounted(() => {
|
|
fixture.querySelector("div")!.classList.add("user");
|
|
});
|
|
}
|
|
}
|
|
const widget = await mount(App, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe('<div class="c user"></div>');
|
|
|
|
widget.state.c = false;
|
|
await nextTick();
|
|
|
|
expect(fixture.innerHTML).toBe('<div class="user"></div>');
|
|
});
|
|
|
|
// TODO: adapt name
|
|
test("style is properly added on widget root el", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`<div t-att-style="props.style"/>`;
|
|
}
|
|
|
|
class ParentWidget extends Component {
|
|
static components = { Child: SomeComponent };
|
|
static template = xml`<Child style="'font-weight: bold;'"/>`;
|
|
}
|
|
await mount(ParentWidget, fixture);
|
|
expect(fixture.innerHTML).toBe(`<div style="font-weight: bold;"></div>`);
|
|
});
|
|
|
|
// TODO: adapt name
|
|
// TODO: t-att-style as object (like class)
|
|
test.skip("dynamic t-att-style is properly added and updated on widget root el", async () => {
|
|
class SomeComponent extends Component {
|
|
static template = xml`<div t-att-style="{ 'font-size': '20px', ...props.style }"/>`;
|
|
}
|
|
|
|
class ParentWidget extends Component {
|
|
static template = xml`<Child style="state.style"/>`;
|
|
static components = { Child: SomeComponent };
|
|
state = useState({ style: { "font-size": "20px" } });
|
|
}
|
|
const widget = await mount(ParentWidget, fixture);
|
|
|
|
expect(fixture.innerHTML).toBe(`<div t-att-style="font-size: 20px;"></div>`);
|
|
|
|
widget.state.style["font-size"] = "30px";
|
|
await nextTick();
|
|
|
|
expect(fixture.innerHTML).toBe(`<div style="font-size: 30px;"></div>`);
|
|
});
|
|
|
|
// TODO: does this test need to be moved? (class now a standard prop)
|
|
test("error in subcomponent with class", async () => {
|
|
class Child extends Component {
|
|
static template = xml`<div t-att-class="props.class" t-esc="this.will.crash"/>`;
|
|
}
|
|
class Parent extends Component {
|
|
static template = xml`<Child class="'a'"/>`;
|
|
static components = { Child };
|
|
}
|
|
let error: OwlError;
|
|
const app = new App(Parent);
|
|
const mountProm = app.mount(fixture).catch((e: Error) => (error = e));
|
|
await expect(nextAppError(app)).resolves.toThrow("error occured in the owl lifecycle");
|
|
await mountProm;
|
|
expect(error!).toBeDefined();
|
|
expect(error!.cause).toBeDefined();
|
|
const regexp =
|
|
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
|
|
expect(error!.cause.message).toMatch(regexp);
|
|
expect(fixture.innerHTML).toBe("");
|
|
expect(mockConsoleWarn).toBeCalledTimes(1);
|
|
});
|
|
});
|