mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] *: re-add a bunch of tests
This commit is contained in:
committed by
Aaron Bohy
parent
b6eb4d009e
commit
ced5d0f69f
@@ -16,12 +16,12 @@ export function onWillUpdateProps(fn: (nextProps: any) => Promise<void> | void |
|
||||
|
||||
export function onMounted(fn: () => void | any) {
|
||||
const node = getCurrent()!;
|
||||
node.mounted.push(fn);
|
||||
node.mounted.unshift(fn);
|
||||
}
|
||||
|
||||
export function onWillPatch(fn: () => Promise<void> | any | void) {
|
||||
const node = getCurrent()!;
|
||||
node.willPatch.push(fn);
|
||||
node.willPatch.unshift(fn);
|
||||
}
|
||||
|
||||
export function onPatched(fn: () => void | any) {
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ export function useRef<C extends Component = Component>(name: string): Ref<C> {
|
||||
return {
|
||||
get el(): HTMLElement | null {
|
||||
const val = node.refs[name];
|
||||
return val!;
|
||||
return val || null;
|
||||
// if (val instanceof HTMLElement) {
|
||||
// return val;
|
||||
// } else if (val instanceof Component) {
|
||||
|
||||
@@ -134,6 +134,34 @@ exports[`basics can handle empty props 2`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics can inject values in tagged templates 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<span><block-text-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['state'].n;
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics can inject values in tagged templates 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
const callTemplate_2 = getTemplate(\`__template__9\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return callTemplate_2(ctx, node, key + \`__1\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics can mount a component with just some text 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
@@ -1006,6 +1034,36 @@ exports[`basics updating a component with t-foreach as root 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics updating widget immediately 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<span>abc<block-child-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2;
|
||||
if (ctx['props'].flag) {
|
||||
b2 = text(\`def\`);
|
||||
}
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics updating widget immediately 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return component(\`Child\`, {flag: ctx['state'].flag}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`basics widget after a t-foreach 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
@@ -1074,3 +1132,71 @@ exports[`basics zero or one child components 2`] = `
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`support svg components add proper namespace to svg 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<g><circle cx=\\"50\\" cy=\\"50\\" r=\\"4\\" stroke=\\"green\\" stroke-width=\\"1\\" fill=\\"yellow\\"/></g>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`support svg components add proper namespace to svg 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<svg><block-child-0/></svg>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`GComp\`, {}, key + \`__1\`, node, ctx);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-raw in components can render list of t-raw 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['state'].items);
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
let key1 = ctx['item'];
|
||||
let b4 = text(ctx['item']);
|
||||
let b5 = html(ctx['item']);
|
||||
c_block2[i1] = withKey(multi([b4, b5]), key1);
|
||||
}
|
||||
let b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-raw in components update properly on state changes 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = html(ctx['state'].value);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -76,3 +76,28 @@ exports[`event handling support for callable expression in event handler 1`] = `
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`event handling t-on with handler bound to dynamic argument on a t-foreach 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
let block3 = createBlock(\`<div class=\\"item\\" block-handler-0=\\"click\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
const [k_block2, v_block2, l_block2, c_block2] = prepareList(ctx['items']);
|
||||
for (let i1 = 0; i1 < l_block2; i1++) {
|
||||
ctx[\`item\`] = v_block2[i1];
|
||||
let key1 = ctx['item'];
|
||||
const arg1 = [ctx['item']];
|
||||
let d1 = [ctx, 'onClick', arg1];
|
||||
c_block2[i1] = withKey(block3([d1]), key1);
|
||||
}
|
||||
let b2 = list(c_block2);
|
||||
return block1([], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`hooks autofocus hook input in a t-if 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><input block-ref=\\"0\\"/><block-child-0/></div>\`);
|
||||
let block2 = createBlock(\`<input block-ref=\\"0\\"/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const refs = ctx.__owl__.refs;
|
||||
let b2;
|
||||
let d1 = (el) => refs[\`input1\`] = el;
|
||||
if (ctx['state'].flag) {
|
||||
let d2 = (el) => refs[\`input2\`] = el;
|
||||
b2 = block2([d2]);
|
||||
}
|
||||
return block1([d1], [b2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`hooks autofocus hook simple input 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><input block-ref=\\"0\\"/><input block-ref=\\"1\\"/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const refs = ctx.__owl__.refs;
|
||||
let d1 = (el) => refs[\`input1\`] = el;
|
||||
let d2 = (el) => refs[\`input2\`] = el;
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`hooks can use onWillStart, onWillUpdateProps 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<span><block-text-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['props'].value;
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`hooks can use onWillStart, onWillUpdateProps 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return component(\`MyComponent\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`hooks can use useComponent 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`hooks mounted callbacks should be called in reverse order from willUnmount callbacks 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div>hey<block-text-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['state'].value;
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`hooks two different call to willPatch/patched should work 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div>hey<block-text-0/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['state'].value;
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`hooks useRef hook: basic use 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div><button block-ref=\\"0\\"><block-text-1/></button></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const refs = ctx.__owl__.refs;
|
||||
let d1 = (el) => refs[\`button\`] = el;
|
||||
let d2 = ctx['value'];
|
||||
return block1([d1, d2]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -14,6 +14,99 @@ exports[`lifecycle hooks basic checks for a component 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks component semantics 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div>B</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks component semantics 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div>D</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks component semantics 3`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div>E</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks component semantics 4`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div>F</div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks component semantics 5`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div>C<block-child-0/><block-child-1/><block-child-2/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2,b3,b4;
|
||||
b2 = component(\`D\`, {}, key + \`__1\`, node, ctx);
|
||||
if (ctx['state'].flag) {
|
||||
b3 = component(\`E\`, {}, key + \`__2\`, node, ctx);
|
||||
} else {
|
||||
b4 = component(\`F\`, {}, key + \`__3\`, node, ctx);
|
||||
}
|
||||
return block1([], [b2, b3, b4]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks component semantics 6`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div>A<block-child-0/><block-child-1/></div>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b2 = component(\`B\`, {}, key + \`__1\`, node, ctx);
|
||||
let b3 = component(\`C\`, {}, key + \`__2\`, node, ctx);
|
||||
return block1([], [b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`lifecycle hooks components are unmounted and destroyed if no longer in DOM 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -741,6 +741,165 @@ describe("basics", () => {
|
||||
await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><div><span>asdf</span><span>asdf</span></div></div>");
|
||||
});
|
||||
// TODO: rename
|
||||
test("updating widget immediately", async () => {
|
||||
// in this situation, we protect against a bug that occurred: because of the
|
||||
// interplay between components and vnodes, a sub widget vnode was patched
|
||||
// twice.
|
||||
class Child extends Component {
|
||||
static template = xml`<span>abc<t t-if="props.flag">def</t></span>`;
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`<Child flag="state.flag"/>`
|
||||
static components = { Child };
|
||||
state = useState({ flag: false });
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("<span>abc</span>");
|
||||
parent.state.flag = true;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<span>abcdef</span>");
|
||||
});
|
||||
|
||||
test("can inject values in tagged templates", async () => {
|
||||
const SUBTEMPLATE = xml`<span><t t-esc="state.n"/></span>`;
|
||||
class Parent extends Component {
|
||||
static template = xml`<t t-call="${SUBTEMPLATE}"/>`;
|
||||
state = useState({ n: 42 });
|
||||
}
|
||||
|
||||
await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("<span>42</span>");
|
||||
});
|
||||
// Depends on t-props
|
||||
test.skip("update props of component without concrete own node", async () => {
|
||||
class Custom extends Component {
|
||||
static template = xml`
|
||||
<div class="widget-subkey">
|
||||
<t t-esc="props.key"/>__<t t-esc="props.subKey"/>
|
||||
</div>`;
|
||||
}
|
||||
class Child extends Component {
|
||||
static components = { Custom };
|
||||
static template = xml`
|
||||
<Custom
|
||||
t-key="props.subKey"
|
||||
key="props.key"
|
||||
subKey="props.subKey"/>`;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static components = { Child };
|
||||
static template = xml`
|
||||
<div>
|
||||
<Child t-key="childProps.key" t-props="childProps"/>
|
||||
</div>`;
|
||||
childProps = {
|
||||
key: 1,
|
||||
subKey: 1,
|
||||
};
|
||||
}
|
||||
const parent = await mount(Parent, fixture);
|
||||
expect(fixture.textContent!.trim()).toBe("1__1");
|
||||
|
||||
// First step: change the Custom's instance
|
||||
Object.assign(parent.childProps, {
|
||||
subKey: 2,
|
||||
});
|
||||
parent.render();
|
||||
await nextTick();
|
||||
expect(fixture.textContent!.trim()).toBe("1__2");
|
||||
|
||||
// Second step, change both Child's and Custom's instance
|
||||
Object.assign(parent.childProps, {
|
||||
key: 2,
|
||||
subKey: 3,
|
||||
});
|
||||
parent.render();
|
||||
await nextTick();
|
||||
expect(fixture.textContent!.trim()).toBe("2__3");
|
||||
});
|
||||
|
||||
test.skip("subcomponents cannot change observable state received from parent", async () => {
|
||||
const consoleError = console.error;
|
||||
console.error = jest.fn();
|
||||
class Child extends Component {
|
||||
static template = xml`<div/>`;
|
||||
setup() {
|
||||
this.props.obj.coffee = 2;
|
||||
}
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`<div><Child obj="state.obj"/></div>`;
|
||||
static components = { Child };
|
||||
state = useState({ obj: { coffee: 1 } });
|
||||
}
|
||||
let error;
|
||||
try {
|
||||
await mount(Parent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe('Observed state cannot be changed here! (key: "coffee", val: "2")');
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
console.error = consoleError;
|
||||
});
|
||||
});
|
||||
|
||||
describe("dynamic t-props", () => {
|
||||
test.skip("basic use", async () => {
|
||||
expect.assertions(4);
|
||||
|
||||
class Child extends Component {
|
||||
static template = xml`
|
||||
<span>
|
||||
<t t-esc="props.a + props.b"/>
|
||||
</span>
|
||||
`;
|
||||
setup() {
|
||||
expect(this.props).toEqual({ a: 1, b: 2 });
|
||||
expect(this.props).not.toBe(parent.some.obj);
|
||||
}
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<Child t-props="some.obj"/>
|
||||
</div>
|
||||
`;
|
||||
static components = { Child };
|
||||
|
||||
some = { obj: { a: 1, b: 2 } };
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><span>3</span></div>");
|
||||
});
|
||||
|
||||
test.skip("t-props with props", async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
class Child extends Component {
|
||||
static template = xml`<div />`;
|
||||
setup() {
|
||||
expect(this.props).toEqual({ a: 1, b: 2, c: "c" });
|
||||
}
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<Child t-props="props" a="1" b="2" />
|
||||
</div>
|
||||
`;
|
||||
static components = { Child };
|
||||
|
||||
props = { a: "a", c: "c" };
|
||||
}
|
||||
|
||||
await mount(Parent, fixture);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip("mount targets", () => {
|
||||
@@ -1012,3 +1171,61 @@ describe.skip("mount special cases", () => {
|
||||
// expect(fixture.textContent).toBe("fixedsome text");
|
||||
});
|
||||
});
|
||||
|
||||
describe("support svg components", () => {
|
||||
test("add proper namespace to svg", async () => {
|
||||
class GComp extends Component {
|
||||
static template = xml`
|
||||
<g>
|
||||
<circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"/>
|
||||
</g>`;
|
||||
}
|
||||
|
||||
class Svg extends Component {
|
||||
static template = xml`
|
||||
<svg>
|
||||
<GComp/>
|
||||
</svg>`;
|
||||
static components = { GComp };
|
||||
}
|
||||
await mount(Svg, fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe(
|
||||
'<svg><g><circle cx="50" cy="50" r="4" stroke="green" stroke-width="1" fill="yellow"></circle></g></svg>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("t-raw in components", () => {
|
||||
test("update properly on state changes", async () => {
|
||||
class Test extends Component {
|
||||
static template = xml`<div><t t-raw="state.value"/></div>`;
|
||||
state = useState({ value: "<b>content</b>" });
|
||||
}
|
||||
const component = await mount(Test, fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div><b>content</b></div>");
|
||||
|
||||
component.state.value = "<span>other content</span>";
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><span>other content</span></div>");
|
||||
});
|
||||
|
||||
test("can render list of t-raw ", async () => {
|
||||
class Test extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-foreach="state.items" t-as="item" t-key="item">
|
||||
<t t-esc="item"/>
|
||||
<t t-raw="item"/>
|
||||
</t>
|
||||
</div>`;
|
||||
state = useState({ items: ["<b>one</b>", "<b>two</b>", "<b>tree</b>"] });
|
||||
}
|
||||
await mount(Test, fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe(
|
||||
"<div><b>one</b><b>one</b><b>two</b><b>two</b><b>tree</b><b>tree</b></div>"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -302,4 +302,24 @@ describe("event handling", () => {
|
||||
button.click();
|
||||
expect(steps).toEqual(["captured", "normal"]);*/
|
||||
});
|
||||
|
||||
test.only("t-on with handler bound to dynamic argument on a t-foreach", async () => {
|
||||
expect.assertions(3);
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-foreach="items" t-as="item" t-key="item">
|
||||
<div class="item" t-on-click="onClick(item)"/>
|
||||
</t>
|
||||
</div>`;
|
||||
items = [1, 2, 3, 4];
|
||||
onClick(n: number, ev: MouseEvent) {
|
||||
expect(n).toBe(1);
|
||||
expect(ev).toBeInstanceOf(MouseEvent);
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Parent, fixture);
|
||||
(<HTMLElement>fixture.querySelector('.item')).click();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
import { App, Component, mount, useRef, useState, useComponent, onMounted, onPatched, onWillStart, onWillUpdateProps, onWillPatch, xml, onWillUnmount } from "../../src";
|
||||
import {
|
||||
makeTestFixture,
|
||||
nextTick,
|
||||
snapshotEverything,
|
||||
} from "../helpers";
|
||||
|
||||
let fixture: HTMLElement;
|
||||
|
||||
snapshotEverything();
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
});
|
||||
|
||||
describe("hooks", () => {
|
||||
test("useRef hook: basic use", async () => {
|
||||
let counter: Counter;
|
||||
class Counter extends Component {
|
||||
static template = xml`<div><button t-ref="button"><t t-esc="value"/></button></div>`;
|
||||
button = useRef("button");
|
||||
value = 0;
|
||||
setup() {
|
||||
counter = this;
|
||||
}
|
||||
increment() {
|
||||
this.value++;
|
||||
(this.button.el as HTMLButtonElement).innerHTML = String(this.value);
|
||||
}
|
||||
}
|
||||
const mounted = mount(Counter, fixture);
|
||||
expect(counter!.button.el).toBe(null);
|
||||
await mounted;
|
||||
expect(fixture.innerHTML).toBe("<div><button>0</button></div>");
|
||||
expect(counter!.button.el).not.toBe(null);
|
||||
expect(counter!.button.el).toBe(fixture.querySelector("button"));
|
||||
counter!.increment();
|
||||
expect(fixture.innerHTML).toBe("<div><button>1</button></div>");
|
||||
});
|
||||
// TODO: rename like next test (ensures willPatch/patched calls are symmetrical)
|
||||
test("two different call to willPatch/patched should work", async () => {
|
||||
const steps: string[] = [];
|
||||
function useMyHook(i: number) {
|
||||
onPatched(() => {
|
||||
steps.push("hook:patched" + i);
|
||||
});
|
||||
onWillPatch(() => {
|
||||
steps.push("hook:willPatch" + i);
|
||||
});
|
||||
}
|
||||
class Test extends Component {
|
||||
static template = xml`<div>hey<t t-esc="state.value"/></div>`;
|
||||
state = useState({ value: 1 });
|
||||
setup() {
|
||||
useMyHook(1);
|
||||
useMyHook(2);
|
||||
}
|
||||
}
|
||||
const component = await mount(Test, fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>hey1</div>");
|
||||
component.state.value++;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div>hey2</div>");
|
||||
|
||||
expect(steps).toEqual(["hook:willPatch2", "hook:willPatch1", "hook:patched1", "hook:patched2"]);
|
||||
});
|
||||
|
||||
test("mounted callbacks should be called in reverse order from willUnmount callbacks", async () => {
|
||||
const steps: string[] = [];
|
||||
function useMyHook(i: number) {
|
||||
onMounted(() => {
|
||||
steps.push("hook:mounted" + i);
|
||||
});
|
||||
onWillUnmount(() => {
|
||||
steps.push("hook:willUnmount" + i);
|
||||
});
|
||||
}
|
||||
class Test extends Component {
|
||||
static template = xml`<div>hey<t t-esc="state.value"/></div>`;
|
||||
state = useState({ value: 1 });
|
||||
setup() {
|
||||
useMyHook(1);
|
||||
useMyHook(2);
|
||||
}
|
||||
}
|
||||
const app = new App(Test);
|
||||
await app.mount(fixture);
|
||||
app.destroy();
|
||||
expect(steps).toEqual(["hook:mounted2", "hook:mounted1", "hook:willUnmount1", "hook:willUnmount2"]);
|
||||
});
|
||||
|
||||
describe("autofocus hook", () => {
|
||||
function useAutofocus(name: string) {
|
||||
let ref = useRef(name);
|
||||
let isInDom = false;
|
||||
function updateFocus() {
|
||||
if (!isInDom && ref.el) {
|
||||
isInDom = true;
|
||||
ref.el.focus();
|
||||
} else if (isInDom && !ref.el) {
|
||||
isInDom = false;
|
||||
}
|
||||
}
|
||||
onPatched(updateFocus);
|
||||
onMounted(updateFocus);
|
||||
}
|
||||
|
||||
test("simple input", async () => {
|
||||
class Test extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<input t-ref="input1"/>
|
||||
<input t-ref="input2"/>
|
||||
</div>`;
|
||||
|
||||
setup() {
|
||||
useAutofocus("input2");
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Test, fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><input><input></div>");
|
||||
const input2 = fixture.querySelectorAll("input")[1];
|
||||
expect(input2).toBe(document.activeElement);
|
||||
});
|
||||
|
||||
test("input in a t-if", async () => {
|
||||
class Test extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<input t-ref="input1"/>
|
||||
<t t-if="state.flag"><input t-ref="input2"/></t>
|
||||
</div>`;
|
||||
|
||||
state = useState({ flag: false });
|
||||
setup() {
|
||||
useAutofocus("input2");
|
||||
}
|
||||
}
|
||||
|
||||
const component = await mount(Test, fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><input></div>");
|
||||
expect(document.activeElement).toBe(document.body);
|
||||
|
||||
component.state.flag = true;
|
||||
await nextTick();
|
||||
const input2 = fixture.querySelectorAll("input")[1];
|
||||
expect(input2).toBe(document.activeElement);
|
||||
});
|
||||
});
|
||||
|
||||
test.skip("can use useEnv", async () => {
|
||||
expect.assertions(2);
|
||||
class Test extends Component {
|
||||
static template = xml`<div><t t-esc="env.val"/></div>`;
|
||||
setup() {
|
||||
//expect(useEnv()).toBe(this.env);
|
||||
}
|
||||
}
|
||||
await mount(Test, fixture);
|
||||
});
|
||||
|
||||
test.skip("can use sub env", async () => {
|
||||
class Test extends Component {
|
||||
static template = xml`<div><t t-esc="env.val"/></div>`;
|
||||
setup() {
|
||||
//useSubEnv({ val: 3 });
|
||||
}
|
||||
}
|
||||
const component = await mount(Test, fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>3</div>");
|
||||
expect(component.env).not.toHaveProperty("val");
|
||||
expect(component.env).toHaveProperty("val");
|
||||
});
|
||||
|
||||
test("can use useComponent", async () => {
|
||||
expect.assertions(2);
|
||||
class Test extends Component {
|
||||
static template = xml`<div></div>`;
|
||||
setup() {
|
||||
expect(useComponent()).toBe(this);
|
||||
}
|
||||
}
|
||||
await mount(Test, fixture);
|
||||
});
|
||||
|
||||
test.skip("parent and child env", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<div><t t-esc="env.val"/></div>`;
|
||||
super() {
|
||||
//useSubEnv({ val: 5 });
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`<t t-esc="env.val"/><Child/>`;
|
||||
static components = { Child };
|
||||
setup() {
|
||||
//useSubEnv({ val: 3 });
|
||||
}
|
||||
}
|
||||
mount(Parent, fixture);
|
||||
expect(fixture.innerHTML).toBe("3<div>5</div>");
|
||||
});
|
||||
|
||||
test("can use onWillStart, onWillUpdateProps", async () => {
|
||||
const steps: string[] = [];
|
||||
async function slow(): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve("slow");
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
function useMyHook() {
|
||||
onWillStart(async () => {
|
||||
steps.push(await slow());
|
||||
steps.push("onWillStart");
|
||||
});
|
||||
onWillUpdateProps(async (nextProps) => {
|
||||
expect(nextProps).toEqual({ value: 2 });
|
||||
steps.push(await slow());
|
||||
steps.push("onWillUpdateProps");
|
||||
});
|
||||
}
|
||||
function use2ndHook() {
|
||||
onWillStart(() => {
|
||||
steps.push("on2ndStart");
|
||||
});
|
||||
onWillUpdateProps((nextProps) => {
|
||||
expect(nextProps).toEqual({ value: 2 });
|
||||
steps.push("on2ndUpdate");
|
||||
});
|
||||
}
|
||||
class MyComponent extends Component {
|
||||
static template = xml`<span><t t-esc="props.value"/></span>`;
|
||||
setup() {
|
||||
useMyHook();
|
||||
use2ndHook();
|
||||
}
|
||||
}
|
||||
class App extends Component {
|
||||
static template = xml`<MyComponent value="state.value"/>`;
|
||||
static components = { MyComponent };
|
||||
state = useState({ value: 1 });
|
||||
}
|
||||
|
||||
const app = await mount(App, fixture);
|
||||
expect(fixture.innerHTML).toBe("<span>1</span>");
|
||||
|
||||
// NOTE: 'on2ndStart' appears first in the list even though
|
||||
// the 'use2ndHook' is declared after 'useMyHook'. This is
|
||||
// because Promise.all is used to call the callbacks specified
|
||||
// in the hooks, which runs them simultaneously.
|
||||
// Additionally, 'slow' should be listed before 'onWillStart'
|
||||
// because call to `slow` is awaited.
|
||||
expect(steps).toEqual(["on2ndStart", "slow", "onWillStart"]);
|
||||
|
||||
app.state.value = 2;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<span>2</span>");
|
||||
expect(steps).toEqual([
|
||||
"on2ndStart",
|
||||
"slow",
|
||||
"onWillStart",
|
||||
"on2ndUpdate",
|
||||
"slow",
|
||||
"onWillUpdateProps",
|
||||
]);
|
||||
});
|
||||
|
||||
test.skip("useExternalListener", async () => {
|
||||
let n = 0;
|
||||
|
||||
class MyComponent extends Component {
|
||||
static template = xml`<span><t t-esc="props.value"/></span>`;
|
||||
setup() {
|
||||
//useExternalListener(window as any, "click", this.increment);
|
||||
}
|
||||
increment() {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
class App extends Component {
|
||||
static template = xml`<MyComponent t-if="state.flag"/>`;
|
||||
static components = { MyComponent };
|
||||
state = useState({ flag: false });
|
||||
}
|
||||
|
||||
const app = await mount(App, 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);
|
||||
});
|
||||
});
|
||||
@@ -969,4 +969,99 @@ describe("lifecycle hooks", () => {
|
||||
"hook:willunmount1",
|
||||
]);*/
|
||||
});
|
||||
|
||||
// TODO: rename (corresponds to https://github.com/odoo/owl/blob/master/doc/reference/concurrency_model.md#semantics)
|
||||
test("component semantics", async () => {
|
||||
let steps: string[] = [];
|
||||
|
||||
class TestWidget extends Component {
|
||||
name: string = "test";
|
||||
setup() {
|
||||
useLogLifecycle(steps);
|
||||
}
|
||||
}
|
||||
class B extends TestWidget {
|
||||
static template = xml`<div>B</div>`;
|
||||
name = "B";
|
||||
}
|
||||
class D extends TestWidget {
|
||||
static template = xml`<div>D</div>`;
|
||||
name = "D";
|
||||
}
|
||||
class E extends TestWidget {
|
||||
static template = xml`<div>E</div>`;
|
||||
name = "E";
|
||||
}
|
||||
|
||||
class F extends TestWidget {
|
||||
static template = xml`<div>F</div>`;
|
||||
name = "F";
|
||||
}
|
||||
let c: C;
|
||||
class C extends TestWidget {
|
||||
static template = xml`
|
||||
<div>C<D />
|
||||
<E t-if="state.flag" />
|
||||
<F t-else="!state.flag" />
|
||||
</div>`;
|
||||
static components = { D, E, F };
|
||||
name = "C";
|
||||
state = useState({ flag: true });
|
||||
|
||||
setup() {
|
||||
c = this;
|
||||
super.setup();
|
||||
}
|
||||
}
|
||||
class A extends TestWidget {
|
||||
static template = xml`<div>A<B /><C /></div>`;
|
||||
static components = { B, C };
|
||||
name = "A";
|
||||
}
|
||||
|
||||
await mount(A, fixture);
|
||||
expect(fixture.innerHTML).toBe(`<div>A<div>B</div><div>C<div>D</div><div>E</div></div></div>`);
|
||||
expect(steps).toEqual([
|
||||
"A:setup",
|
||||
"A:willStart",
|
||||
"A:render",
|
||||
"B:setup",
|
||||
"B:willStart",
|
||||
"C:setup",
|
||||
"C:willStart",
|
||||
"B:render",
|
||||
"C:render",
|
||||
"D:setup",
|
||||
"D:willStart",
|
||||
"E:setup",
|
||||
"E:willStart",
|
||||
"D:render",
|
||||
"E:render",
|
||||
"E:mounted",
|
||||
"D:mounted",
|
||||
"C:mounted",
|
||||
"B:mounted",
|
||||
"A:mounted",
|
||||
]);
|
||||
|
||||
// update
|
||||
steps.splice(0);
|
||||
c!.state.flag = false;
|
||||
await nextTick();
|
||||
expect(steps).toEqual([
|
||||
"C:render",
|
||||
"D:willUpdateProps",
|
||||
"F:setup",
|
||||
"F:willStart",
|
||||
"D:render",
|
||||
"F:render",
|
||||
"C:willPatch",
|
||||
"D:willPatch",
|
||||
"E:willUnmount",
|
||||
"E:destroyed",
|
||||
"F:mounted",
|
||||
"D:patched",
|
||||
"C:patched",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,4 +44,104 @@ describe("refs", () => {
|
||||
'<div><span class="counter">1</span><span><button>do something</button></span></div>'
|
||||
);
|
||||
});
|
||||
// TODO: rename
|
||||
test.skip("t-refs on widget are components", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<div>b</div>`;
|
||||
}
|
||||
let parent: Parent;
|
||||
class Parent extends Component {
|
||||
static template = xml`<div class="outer-div">Hello<Child t-ref="mywidgetb" /></div>`;
|
||||
static components = { Child };
|
||||
ref = useRef<Child>("mywidgetb");
|
||||
setup() {
|
||||
parent = this;
|
||||
}
|
||||
}
|
||||
|
||||
const mounted = mount(Parent, fixture);
|
||||
expect(parent!.ref.comp).toBe(null);
|
||||
expect(parent!.ref.el).toBe(null);
|
||||
await mounted;
|
||||
expect(parent!.ref.comp).toBeInstanceOf(Child);
|
||||
expect(parent!.ref.el).toEqual(fixture.querySelector(".outer-div > div"));
|
||||
});
|
||||
|
||||
test.skip("t-refs are bound at proper timing", async () => {
|
||||
expect.assertions(4);
|
||||
class Child extends Component {
|
||||
static template = xml`<div>widget</div>`;
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<Child t-foreach="state.list" t-as="elem" t-ref="child" t-key="elem"/>
|
||||
</div>
|
||||
`;
|
||||
static components = { Child };
|
||||
state = useState({ list: <any>[] });
|
||||
child = useRef("child");
|
||||
willPatch() {
|
||||
expect(this.child.comp).toBeNull();
|
||||
}
|
||||
patched() {
|
||||
expect(this.child.comp).not.toBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
parent.state.list.push(1);
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
test.skip("t-refs are bound at proper timing (2)", async () => {
|
||||
expect.assertions(10);
|
||||
class Child extends Component {
|
||||
static template = xml`<div>widget</div>`;
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<Child t-if="state.child1" t-ref="child1"/>
|
||||
<Child t-if="state.child2" t-ref="child2"/>
|
||||
</div>`;
|
||||
static components = { Child };
|
||||
state = useState({ child1: true, child2: false });
|
||||
child1 = useRef("child1");
|
||||
child2 = useRef("child2");
|
||||
count = 0;
|
||||
mounted() {
|
||||
expect(this.child1.comp).toBeDefined();
|
||||
expect(this.child2.comp).toBeNull();
|
||||
}
|
||||
willPatch() {
|
||||
if (this.count === 0) {
|
||||
expect(this.child1.comp).toBeDefined();
|
||||
expect(this.child2.comp).toBeNull();
|
||||
}
|
||||
if (this.count === 1) {
|
||||
expect(this.child1.comp).toBeDefined();
|
||||
expect(this.child2.comp).toBeDefined();
|
||||
}
|
||||
}
|
||||
patched() {
|
||||
if (this.count === 0) {
|
||||
expect(this.child1.comp).toBeDefined();
|
||||
expect(this.child2.comp).toBeDefined();
|
||||
}
|
||||
if (this.count === 1) {
|
||||
expect(this.child1.comp).toBeNull();
|
||||
expect(this.child2.comp).toBeDefined();
|
||||
}
|
||||
this.count++;
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
parent.state.child2 = true;
|
||||
await nextTick();
|
||||
parent.state.child1 = false;
|
||||
await nextTick();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -201,6 +201,41 @@ exports[`Portal portal could have dynamically no content 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Portal portal destroys on crash 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<span><block-text-0/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let d1 = ctx['props'].error&&this.will.crash;
|
||||
return block1([d1]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Portal portal destroys on crash 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
let assign = Object.assign;
|
||||
|
||||
let block1 = createBlock(\`<div><block-child-0/></div>\`);
|
||||
|
||||
const slot2 = ctx => (node, key) => {
|
||||
return component(\`Child\`, {error: ctx['state'].error}, key + \`__3\`, node, ctx);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
let b3 = assign(component(\`Portal\`, {target: '#outside'}, key + \`__1\`, node, ctx), {slots: {'default': slot2(ctx)}});
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`Portal portal with child and props 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
//import { AsyncRoot } from "../../src/misc/async_root";
|
||||
import { useState, mount } from "../../src";
|
||||
import { xml } from "../../src/tags";
|
||||
import { makeDeferred, makeTestFixture, snapshotEverything, nextTick } from "../helpers";
|
||||
import { Component } from "../../src/component/component";
|
||||
|
||||
let fixture: HTMLElement;
|
||||
|
||||
snapshotEverything();
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
});
|
||||
|
||||
describe("Asyncroot", () => {
|
||||
test.skip("delayed component with AsyncRoot component", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<span><t t-esc="props.val"/></span>`;
|
||||
}
|
||||
class AsyncChild extends Child {
|
||||
willUpdateProps() {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<button t-on-click="updateApp">Update App State</button>
|
||||
<div class="children">
|
||||
<Child val="state.val"/>
|
||||
<AsyncRoot>
|
||||
<AsyncChild val="state.val"/>
|
||||
</AsyncRoot>
|
||||
</div>
|
||||
</div>`;
|
||||
static components = { Child, AsyncChild, /*AsyncRoot*/ };
|
||||
state = useState({ val: 0 });
|
||||
|
||||
updateApp() {
|
||||
this.state.val++;
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>0</span><span>0</span>");
|
||||
|
||||
// click on button to increment Parent counter
|
||||
const def = makeDeferred();
|
||||
fixture.querySelector("button")!.click();
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1</span><span>0</span>");
|
||||
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1</span><span>1</span>");
|
||||
});
|
||||
|
||||
test.skip("fast component with AsyncRoot", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<span><t t-esc="props.val"/></span>`;
|
||||
}
|
||||
class AsyncChild extends Child {
|
||||
willUpdateProps() {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<button t-on-click="updateApp">Update App State</button>
|
||||
<div class="children">
|
||||
<AsyncRoot>
|
||||
<Child val="state.val"/>
|
||||
</AsyncRoot>
|
||||
<AsyncChild val="state.val"/>
|
||||
</div>
|
||||
</div>`;
|
||||
static components = { Child, AsyncChild, /*AsyncRoot*/ };
|
||||
state = useState({ val: 0 });
|
||||
|
||||
updateApp() {
|
||||
this.state.val++;
|
||||
}
|
||||
}
|
||||
|
||||
await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>0</span><span>0</span>");
|
||||
|
||||
// click on button to increment Parent counter
|
||||
const def = makeDeferred();
|
||||
fixture.querySelector("button")!.click();
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1</span><span>0</span>");
|
||||
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1</span><span>1</span>");
|
||||
});
|
||||
|
||||
test.skip("asyncroot component: mixed re-renderings", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`
|
||||
<span t-on-click="increment">
|
||||
<t t-esc="state.val"/>/<t t-esc="props.val"/>
|
||||
</span>`;
|
||||
state = useState({ val: 0 });
|
||||
|
||||
increment() {
|
||||
this.state.val++;
|
||||
}
|
||||
}
|
||||
class AsyncChild extends Child {
|
||||
willUpdateProps() {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
class Parent extends Component {
|
||||
static template = xml`
|
||||
<div>
|
||||
<button t-on-click="updateApp">Update App State</button>
|
||||
<div class="children">
|
||||
<Child val="state.val"/>
|
||||
<AsyncRoot>
|
||||
<AsyncChild val="state.val"/>
|
||||
</AsyncRoot>
|
||||
</div>
|
||||
</div>`;
|
||||
static components = { Child, AsyncChild, /*AsyncRoot*/ };
|
||||
state = useState({ val: 0 });
|
||||
|
||||
updateApp() {
|
||||
this.state.val++;
|
||||
}
|
||||
}
|
||||
|
||||
const parent = await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>0/0</span><span>0/0</span>");
|
||||
|
||||
// click on button to increment Parent counter
|
||||
const def = makeDeferred();
|
||||
fixture.querySelector("button")!.click();
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>0/1</span><span>0/0</span>");
|
||||
|
||||
// click on each Child to increment their local counter
|
||||
const children = (<HTMLElement>parent.el).querySelectorAll("span");
|
||||
children[0]!.click();
|
||||
await nextTick();
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1/1</span><span>0/0</span>");
|
||||
|
||||
children[1]!.click();
|
||||
await nextTick();
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1/1</span><span>0/0</span>");
|
||||
|
||||
// finalize first re-rendering (coming from the props update)
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
|
||||
expect(fixture.querySelector(".children")!.innerHTML).toBe("<span>1/1</span><span>1/1</span>");
|
||||
});
|
||||
});
|
||||
+70
-276
@@ -379,283 +379,35 @@ describe("Portal", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
// test.skip("portal destroys on crash", async () => {
|
||||
// class Child extends Component {
|
||||
// static template = xml`<span t-esc="props.error and this.will.crash" />`;
|
||||
// state = {};
|
||||
// }
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal, Child };
|
||||
// static template = xml`
|
||||
// <div>
|
||||
// <Portal target="'#outside'" >
|
||||
// <Child error="state.error"/>
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
// state = { error: false };
|
||||
// }
|
||||
test("portal destroys on crash", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<span t-esc="props.error and this.will.crash" />`;
|
||||
state = {};
|
||||
}
|
||||
class Parent extends Component {
|
||||
static components = { Portal, Child };
|
||||
static template = xml`
|
||||
<div>
|
||||
<Portal target="'#outside'" >
|
||||
<Child error="state.error"/>
|
||||
</Portal>
|
||||
</div>`;
|
||||
state = { error: false };
|
||||
}
|
||||
addOutsideDiv(fixture);
|
||||
const parent = await mount(Parent, fixture);
|
||||
parent.state.error = true;
|
||||
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
// parent.state.error = true;
|
||||
|
||||
// let error;
|
||||
// try {
|
||||
// await parent.render();
|
||||
// } catch (e) {
|
||||
// error = e;
|
||||
// }
|
||||
// expect(error).toBeDefined();
|
||||
// const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g
|
||||
// expect(error.message).toMatch(regexp);
|
||||
// });
|
||||
|
||||
// test.skip("portal manual unmount", async () => {
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal };
|
||||
// static template = xml`
|
||||
// <div>
|
||||
// <Portal target="'#outside'">
|
||||
// <span>gloria</span>
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
// }
|
||||
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
|
||||
// expect(outside.innerHTML).toBe("<span>gloria</span>");
|
||||
// expect(parent.el!.innerHTML).toBe("<portal></portal>");
|
||||
|
||||
// parent.unmount();
|
||||
// expect(outside.innerHTML).toBe("");
|
||||
// expect(parent.el!.innerHTML).toBe("<portal><span>gloria</span></portal>");
|
||||
|
||||
// await parent.mount(fixture);
|
||||
// expect(outside.innerHTML).toBe("<span>gloria</span>");
|
||||
// expect(parent.el!.innerHTML).toBe("<portal></portal>");
|
||||
// });
|
||||
|
||||
// test.skip("portal manual unmount with subcomponent", async () => {
|
||||
// expect.assertions(9);
|
||||
// class Child extends Component {
|
||||
// static template = xml`<span>gloria</span>`;
|
||||
// mounted() {
|
||||
// expect(outside.contains(this.el)).toBeTruthy();
|
||||
// }
|
||||
// willUnmount() {
|
||||
// expect(outside.contains(this.el)).toBeTruthy();
|
||||
// }
|
||||
// }
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal, Child };
|
||||
// static template = xml`
|
||||
// <div>
|
||||
// <Portal target="'#outside'">
|
||||
// <Child />
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
// }
|
||||
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
|
||||
// expect(outside.innerHTML).toBe("<span>gloria</span>");
|
||||
// expect(parent.el!.innerHTML).toBe("<portal></portal>");
|
||||
|
||||
// parent.unmount();
|
||||
// expect(outside.innerHTML).toBe("");
|
||||
// expect(parent.el!.innerHTML).toBe("<portal><span>gloria</span></portal>");
|
||||
|
||||
// await parent.mount(fixture);
|
||||
// expect(outside.innerHTML).toBe("<span>gloria</span>");
|
||||
// expect(parent.el!.innerHTML).toBe("<portal></portal>");
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe("Portal: Events handling", () => {
|
||||
// test.skip("events triggered on movable pure node are handled", async () => {
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal };
|
||||
// static template = xml`
|
||||
// <div>
|
||||
// <Portal target="'#outside'">
|
||||
// <span id="trigger-me" t-on-custom="_onCustom" t-esc="state.val"/>
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
// state = useState({ val: "ab" });
|
||||
|
||||
// _onCustom() {
|
||||
// this.state.val = "triggered";
|
||||
// }
|
||||
// }
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
|
||||
// expect(outside.innerHTML).toBe(`<span id="trigger-me">ab</span>`);
|
||||
// outside.querySelector("#trigger-me")!.dispatchEvent(new Event("custom"));
|
||||
// await nextTick();
|
||||
// expect(outside.innerHTML).toBe(`<span id="trigger-me">triggered</span>`);
|
||||
// });
|
||||
|
||||
// test.skip("events triggered on movable owl components are redirected", async () => {
|
||||
// let childInst: Component | null = null;
|
||||
// class Child extends Component {
|
||||
// static template = xml`
|
||||
// <span t-on-custom="_onCustom" t-esc="props.val"/>`;
|
||||
|
||||
// constructor(parent, props) {
|
||||
// super(parent, props);
|
||||
// childInst = this;
|
||||
// }
|
||||
|
||||
// _onCustom() {
|
||||
// this.trigger("custom-portal");
|
||||
// }
|
||||
// }
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal, Child };
|
||||
// static template = xml`
|
||||
// <div t-on-custom-portal="_onCustomPortal">
|
||||
// <Portal target="'#outside'">
|
||||
// <Child val="state.val"/>
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
// state = useState({ val: "ab" });
|
||||
|
||||
// _onCustomPortal() {
|
||||
// this.state.val = "triggered";
|
||||
// }
|
||||
// }
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
|
||||
// expect(outside.innerHTML).toBe(`<span>ab</span>`);
|
||||
// childInst!.trigger("custom");
|
||||
// await nextTick();
|
||||
// expect(outside.innerHTML).toBe(`<span>triggered</span>`);
|
||||
// });
|
||||
|
||||
// test.skip("events triggered on contained movable owl components are redirected", async () => {
|
||||
// const steps: string[] = [];
|
||||
// let childInst: Component | null = null;
|
||||
// class Child extends Component {
|
||||
// static template = xml`
|
||||
// <span t-on-custom="_onCustom"/>`;
|
||||
|
||||
// constructor(parent, props) {
|
||||
// super(parent, props);
|
||||
// childInst = this;
|
||||
// }
|
||||
|
||||
// _onCustom() {
|
||||
// this.trigger("custom-portal");
|
||||
// }
|
||||
// }
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal, Child };
|
||||
// static template = xml`
|
||||
// <div t-on-custom="_handled" t-on-custom-portal="_handled">
|
||||
// <Portal target="'#outside'">
|
||||
// <div>
|
||||
// <Child/>
|
||||
// </div>
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
|
||||
// _handled(ev) {
|
||||
// steps.push(ev.type);
|
||||
// }
|
||||
// }
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
|
||||
// childInst!.trigger("custom");
|
||||
// await nextTick();
|
||||
|
||||
// // This is expected because trigger is synchronous
|
||||
// expect(steps).toMatchObject(["custom-portal", "custom"]);
|
||||
// });
|
||||
|
||||
// test.skip("Dom events are not mapped", async () => {
|
||||
// let childInst: Component | null = null;
|
||||
// const steps: string[] = [];
|
||||
// class Child extends Component {
|
||||
// static template = xml`
|
||||
// <button>child</button>`;
|
||||
|
||||
// constructor(parent, props) {
|
||||
// super(parent, props);
|
||||
// childInst = this;
|
||||
// }
|
||||
// }
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal, Child };
|
||||
// static template = xml`
|
||||
// <div t-on-click="_handled">
|
||||
// <Portal target="'#outside'">
|
||||
// <Child />
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
|
||||
// _handled(ev) {
|
||||
// steps.push(ev.type as string);
|
||||
// }
|
||||
// }
|
||||
// const bodyListener = (ev) => {
|
||||
// steps.push(`body: ${ev.type}`);
|
||||
// };
|
||||
// document.body.addEventListener("click", bodyListener);
|
||||
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
// childInst!.el!.click();
|
||||
|
||||
// expect(steps).toEqual(["body: click"]);
|
||||
// document.body.removeEventListener("click", bodyListener);
|
||||
// });
|
||||
|
||||
// test.skip("Nested portals event propagation", async () => {
|
||||
// const outside2 = document.createElement("div");
|
||||
// outside2.setAttribute("id", "outside2");
|
||||
// fixture.appendChild(outside2);
|
||||
|
||||
// const steps: Array<string> = [];
|
||||
// let childInst: Component | null = null;
|
||||
// class Child2 extends Component {
|
||||
// static template = xml`<div>child2</div>`;
|
||||
// constructor(parent, props) {
|
||||
// super(parent, props);
|
||||
// childInst = this;
|
||||
// }
|
||||
// }
|
||||
// class Child extends Component {
|
||||
// static components = { Portal, Child2 };
|
||||
// static template = xml`
|
||||
// <Portal target="'#outside2'">
|
||||
// <Child2 />
|
||||
// </Portal>`;
|
||||
// }
|
||||
// class Parent extends Component {
|
||||
// static components = { Portal, Child };
|
||||
// static template = xml`
|
||||
// <div t-on-custom='_handled'>
|
||||
// <Portal target="'#outside'">
|
||||
// <Child/>
|
||||
// </Portal>
|
||||
// </div>`;
|
||||
|
||||
// _handled(ev) {
|
||||
// steps.push(`${ev.type} from ${ev.originalComponent.constructor.name}`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// const parent = new Parent();
|
||||
// await parent.mount(fixture);
|
||||
|
||||
// childInst!.trigger("custom");
|
||||
// expect(steps).toEqual(["custom from Child2"]);
|
||||
// });
|
||||
let error;
|
||||
try {
|
||||
await parent.render();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
const regexp = /Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g
|
||||
expect(error.message).toMatch(regexp);
|
||||
});
|
||||
|
||||
test("portal's parent's env is not polluted", async () => {
|
||||
class Child extends Component {
|
||||
@@ -750,3 +502,45 @@ describe("Portal: UI/UX", () => {
|
||||
expect(document.activeElement === inputReRendered).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Portal: Props validation", () => {
|
||||
test.skip("target is mandatory", async () => {
|
||||
class Parent extends Component {
|
||||
static components = { Portal };
|
||||
static template = xml`
|
||||
<div>
|
||||
<Portal>
|
||||
<div>2</div>
|
||||
</Portal>
|
||||
</div>`;
|
||||
}
|
||||
let error;
|
||||
try {
|
||||
await mount(Parent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Missing props 'target' (component 'Portal')`);
|
||||
});
|
||||
|
||||
test.skip("target is not list", async () => {
|
||||
class Parent extends Component {
|
||||
static components = { Portal };
|
||||
static template = xml`
|
||||
<div>
|
||||
<Portal target="['body']">
|
||||
<div>2</div>
|
||||
</Portal>
|
||||
</div>`;
|
||||
}
|
||||
let error;
|
||||
try {
|
||||
await mount(Parent, fixture);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toBe(`Invalid Prop 'target' in component 'Portal'`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`error handling cannot add twice the same template 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<div/>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return block1();
|
||||
}
|
||||
}"
|
||||
`;
|
||||
@@ -178,6 +178,45 @@ exports[`simple templates, mostly static empty string in a template set 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`simple templates, mostly static inline template string in t-esc 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`text\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`simple templates, mostly static inline template string with content in t-esc 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
ctx = Object.create(ctx);
|
||||
ctx[isBoundary] = 1
|
||||
setContextValue(ctx, \\"v\\", 1);
|
||||
return text(\`text\${ctx['v']}\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`simple templates, mostly static inline template string with variable in context 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component } = bdom;
|
||||
let { withDefault, getTemplate, prepareList, withKey, zero, call, callSlot, capture, isBoundary, shallowEqual, setContextValue } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
return text(\`text \${ctx['v']}\`);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`simple templates, mostly static multiple root nodes 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { renderToString, snapshotEverything, TestContext } from "../helpers";
|
||||
|
||||
snapshotEverything();
|
||||
|
||||
describe("error handling", () => {
|
||||
test("invalid xml", () => {
|
||||
expect(() => renderToString("<div>")).toThrow("Invalid XML in template");
|
||||
});
|
||||
|
||||
test("nice warning if no template with given name", () => {
|
||||
const context = new TestContext();
|
||||
expect(() => context.renderToString("invalidname")).toThrow("Missing template");
|
||||
});
|
||||
|
||||
test("cannot add twice the same template", () => {
|
||||
const context = new TestContext();
|
||||
context.addTemplate("test", `<t></t>`);
|
||||
expect(() => context.addTemplate("test", "<div/>", { allowDuplicate: true })).not.toThrow("already defined");
|
||||
expect(() => context.addTemplate("test", "<div/>")).toThrow("already defined");
|
||||
});
|
||||
|
||||
// test("addTemplates throw if parser error", () => {
|
||||
// const context = new TestContext();
|
||||
// expect(() => {
|
||||
// context.addTemplates("<templates><abc>></templates>");
|
||||
// }).toThrow("Invalid XML in template");
|
||||
// });
|
||||
|
||||
test("nice error when t-on is evaluated with a missing event", () => {
|
||||
expect(() => renderToString(`<div t-on="somemethod"></div>`)).toThrow(
|
||||
"Missing event name with t-on directive"
|
||||
);
|
||||
});
|
||||
|
||||
test("error when unknown directive", () => {
|
||||
expect(() => renderToString(`<div t-best-beer="rochefort 10">test</div>`)).toThrow(
|
||||
"Unknown QWeb directive: 't-best-beer'"
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -122,6 +122,21 @@ describe("simple templates, mostly static", () => {
|
||||
const template = `<tr><td>cell</td></tr>`;
|
||||
expect(renderToString(template)).toBe(template);
|
||||
});
|
||||
|
||||
test("inline template string in t-esc", () => {
|
||||
const template = '<t><t t-esc="`text`"/></t>';
|
||||
expect(renderToString(template)).toBe("text");
|
||||
});
|
||||
|
||||
test("inline template string with content in t-esc", () => {
|
||||
const template = '<t><t t-set="v" t-value="1"/><t t-esc="`text${v}`"/></t>';
|
||||
expect(renderToString(template)).toBe("text1");
|
||||
});
|
||||
|
||||
test("inline template string with variable in context", () => {
|
||||
const template = '<t><t t-esc="`text ${v}`"/></t>';
|
||||
expect(renderToString(template, { v: "from context" })).toBe("text from context");
|
||||
});
|
||||
});
|
||||
|
||||
describe("loading templates", () => {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
renderToString,
|
||||
snapshotEverything,
|
||||
} from "../helpers";
|
||||
|
||||
snapshotEverything();
|
||||
|
||||
describe("qweb t-tag", () => {
|
||||
test.skip("simple usecases", () => {
|
||||
expect(renderToString(`<t t-tag="'div'"></t>`)).toBe("<div></div>");
|
||||
expect(renderToString(`<t t-tag="tag">text</t>`, { tag: "span" })).toBe("<span>text</span>");
|
||||
});
|
||||
|
||||
test.skip("with multiple child nodes", () => {
|
||||
const template = `
|
||||
<t t-tag="tag">
|
||||
pear
|
||||
<span>apple</span>
|
||||
strawberry
|
||||
</t>`;
|
||||
expect(renderToString(template, { tag: "div" })).toBe(
|
||||
"<div> pear <span>apple</span> strawberry </div>"
|
||||
);
|
||||
});
|
||||
|
||||
test.skip("with multiple attributes", () => {
|
||||
const template = `<t t-tag="tag" class="blueberry" taste="raspberry">gooseberry</t>`;
|
||||
const expected = `<div taste=\"raspberry\" class=\"blueberry\">gooseberry</div>`;
|
||||
expect(renderToString(template, { tag: "div" })).toBe(expected);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user