diff --git a/src/compiler/code_generator.ts b/src/compiler/code_generator.ts
index 2b9c0e7a..b3aa587d 100644
--- a/src/compiler/code_generator.ts
+++ b/src/compiler/code_generator.ts
@@ -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}"`);
diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts
index 16547c01..2e315835 100644
--- a/src/compiler/parser.ts
+++ b/src/compiler/parser.ts
@@ -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
diff --git a/tests/compiler/parser.test.ts b/tests/compiler/parser.test.ts
index c8959148..3402f89f 100644
--- a/tests/compiler/parser.test.ts
+++ b/tests/compiler/parser.test.ts
@@ -1370,6 +1370,25 @@ describe("qweb parser", () => {
});
});
+ test("a component with an empty named slot", async () => {
+ expect(parse(``)).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(`foo`)).toEqual({
type: ASTType.TComponent,
diff --git a/tests/components/__snapshots__/slots.test.ts.snap b/tests/components/__snapshots__/slots.test.ts.snap
index e98f2726..c58a6727 100644
--- a/tests/components/__snapshots__/slots.test.ts.snap
+++ b/tests/components/__snapshots__/slots.test.ts.snap
@@ -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(\`\`);
+
+ 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(\`\`);
+
+ 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
) {
diff --git a/tests/components/slots.test.ts b/tests/components/slots.test.ts
index 729b2fda..9cbf325b 100644
--- a/tests/components/slots.test.ts
+++ b/tests/components/slots.test.ts
@@ -74,6 +74,43 @@ describe("slots", () => {
expect(fixture.innerHTML).toBe("other text");
});
+ test("simple named and empty slot", async () => {
+ class Child extends Component {
+ static template = xml``;
+
+ setup() {
+ expect(this.props.slots["myEmptySlot"]).toBeTruthy();
+ }
+ }
+
+ class Parent extends Component {
+ static template = xml`some text`;
+ static components = { Child };
+ }
+ await mount(Parent, fixture);
+
+ expect(fixture.innerHTML).toBe("some text");
+ });
+
+ test("simple named and empty slot -- 2", async () => {
+ class Child extends Component {
+ static template = xml`default empty`;
+
+ setup() {
+ expect(this.props.slots["myEmptySlot"]).toBeTruthy();
+ expect(this.props.slots["myEmptySlot"].myProp).toBe("myProp text");
+ }
+ }
+
+ class Parent extends Component {
+ static template = xml``;
+ static components = { Child };
+ }
+ await mount(Parent, fixture);
+
+ expect(fixture.innerHTML).toBe("default empty");
+ });
+
test("default slot with slot scope: shorthand syntax", async () => {
let child: any;
class Child extends Component {