mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[FIX] compiler, components: allow empty slot
Before this commit, a t-set-slot that has no content was not even compiled, and thus not passed to the component for which it has was defined After this commit, we allow a t-set-slot to have no content (because it can have slot props)
This commit is contained in:
committed by
Géry Debongnie
parent
55dbc01a1b
commit
de240b1ebc
@@ -1115,8 +1115,11 @@ export class CodeGenerator {
|
||||
let slotStr: string[] = [];
|
||||
for (let slotName in ast.slots) {
|
||||
const slotAst = ast.slots[slotName];
|
||||
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
|
||||
const params = [`__render: ${name}, __ctx: ${ctxStr}`];
|
||||
const params = [];
|
||||
if (slotAst.content) {
|
||||
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
|
||||
params.push(`__render: ${name}, __ctx: ${ctxStr}`);
|
||||
}
|
||||
const scope = ast.slots[slotName].scope;
|
||||
if (scope) {
|
||||
params.push(`__scope: "${scope}"`);
|
||||
|
||||
+17
-19
@@ -118,7 +118,7 @@ export interface ASTTCall {
|
||||
}
|
||||
|
||||
interface SlotDefinition {
|
||||
content: AST;
|
||||
content: AST | null;
|
||||
scope: string | null;
|
||||
on: EventHandlers | null;
|
||||
attrs: Attrs | null;
|
||||
@@ -749,26 +749,24 @@ function parseComponent(node: Element, ctx: ParsingContext): AST | null {
|
||||
slotNode.removeAttribute("t-set-slot");
|
||||
slotNode.remove();
|
||||
const slotAst = parseNode(slotNode, ctx);
|
||||
if (slotAst) {
|
||||
let on: SlotDefinition["on"] = null;
|
||||
let attrs: Attrs | null = null;
|
||||
let scope: string | null = null;
|
||||
for (let attributeName of slotNode.getAttributeNames()) {
|
||||
const value = slotNode.getAttribute(attributeName)!;
|
||||
if (attributeName === "t-slot-scope") {
|
||||
scope = value;
|
||||
continue;
|
||||
} else if (attributeName.startsWith("t-on-")) {
|
||||
on = on || {};
|
||||
on[attributeName.slice(5)] = value;
|
||||
} else {
|
||||
attrs = attrs || {};
|
||||
attrs[attributeName] = value;
|
||||
}
|
||||
let on: SlotDefinition["on"] = null;
|
||||
let attrs: Attrs | null = null;
|
||||
let scope: string | null = null;
|
||||
for (let attributeName of slotNode.getAttributeNames()) {
|
||||
const value = slotNode.getAttribute(attributeName)!;
|
||||
if (attributeName === "t-slot-scope") {
|
||||
scope = value;
|
||||
continue;
|
||||
} else if (attributeName.startsWith("t-on-")) {
|
||||
on = on || {};
|
||||
on[attributeName.slice(5)] = value;
|
||||
} else {
|
||||
attrs = attrs || {};
|
||||
attrs[attributeName] = value;
|
||||
}
|
||||
slots = slots || {};
|
||||
slots[name] = { content: slotAst, on, attrs, scope };
|
||||
}
|
||||
slots = slots || {};
|
||||
slots[name] = { content: slotAst, on, attrs, scope };
|
||||
}
|
||||
|
||||
// default slot
|
||||
|
||||
@@ -1370,6 +1370,25 @@ describe("qweb parser", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("a component with an empty named slot", async () => {
|
||||
expect(parse(`<MyComponent><t t-set-slot="mySlot"></t></MyComponent>`)).toEqual({
|
||||
dynamicProps: null,
|
||||
isDynamic: false,
|
||||
name: "MyComponent",
|
||||
on: null,
|
||||
props: null,
|
||||
slots: {
|
||||
mySlot: {
|
||||
attrs: null,
|
||||
content: null,
|
||||
on: null,
|
||||
scope: null,
|
||||
},
|
||||
},
|
||||
type: 11,
|
||||
});
|
||||
});
|
||||
|
||||
test("a component with a named slot", async () => {
|
||||
expect(parse(`<MyComponent><t t-set-slot="name">foo</t></MyComponent>`)).toEqual({
|
||||
type: ASTType.TComponent,
|
||||
|
||||
@@ -1444,6 +1444,71 @@ exports[`slots simple default slot, variation 2`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots simple named and empty slot -- 2 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { capture, markRaw } = helpers;
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx1 = capture(ctx);
|
||||
return component(\`Child\`, {slots: markRaw({'myEmptySlot': {myProp: 'myProp text'}})}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots simple named and empty slot -- 2 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { callSlot } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<span><block-child-0/></span>\`);
|
||||
|
||||
function defaultContent1(ctx, node, key = \\"\\") {
|
||||
return text(\`default empty\`);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b3 = callSlot(ctx, node, key, 'myEmptySlot', false, null, defaultContent1);
|
||||
return block1([], [b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots simple named and empty slot 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { capture, markRaw } = helpers;
|
||||
|
||||
function slot1(ctx, node, key = \\"\\") {
|
||||
return text(\`some text\`);
|
||||
}
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const ctx1 = capture(ctx);
|
||||
return component(\`Child\`, {slots: markRaw({'myEmptySlot': {}, 'default': {__render: slot1, __ctx: ctx1}})}, key + \`__1\`, node, ctx);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots simple named and empty slot 2`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
|
||||
let { callSlot } = helpers;
|
||||
|
||||
let block1 = createBlock(\`<span><block-child-0/><block-child-1/></span>\`);
|
||||
|
||||
return function template(ctx, node, key = \\"\\") {
|
||||
const b2 = callSlot(ctx, node, key, 'default', false, null);
|
||||
const b3 = callSlot(ctx, node, key, 'myEmptySlot', false, null);
|
||||
return block1([], [b2, b3]);
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`slots simple slot with slot scope 1`] = `
|
||||
"function anonymous(bdom, helpers
|
||||
) {
|
||||
|
||||
@@ -74,6 +74,43 @@ describe("slots", () => {
|
||||
expect(fixture.innerHTML).toBe("<span>other text</span>");
|
||||
});
|
||||
|
||||
test("simple named and empty slot", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<span><t t-slot="default" /><t t-slot="myEmptySlot"/></span>`;
|
||||
|
||||
setup() {
|
||||
expect(this.props.slots["myEmptySlot"]).toBeTruthy();
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`<Child>some text<t t-set-slot="myEmptySlot" /></Child>`;
|
||||
static components = { Child };
|
||||
}
|
||||
await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("<span>some text</span>");
|
||||
});
|
||||
|
||||
test("simple named and empty slot -- 2", async () => {
|
||||
class Child extends Component {
|
||||
static template = xml`<span><t t-slot="myEmptySlot">default empty</t></span>`;
|
||||
|
||||
setup() {
|
||||
expect(this.props.slots["myEmptySlot"]).toBeTruthy();
|
||||
expect(this.props.slots["myEmptySlot"].myProp).toBe("myProp text");
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component {
|
||||
static template = xml`<Child><t t-set-slot="myEmptySlot" myProp="'myProp text'" /></Child>`;
|
||||
static components = { Child };
|
||||
}
|
||||
await mount(Parent, fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("<span>default empty</span>");
|
||||
});
|
||||
|
||||
test("default slot with slot scope: shorthand syntax", async () => {
|
||||
let child: any;
|
||||
class Child extends Component {
|
||||
|
||||
Reference in New Issue
Block a user