Skip to content

Commit

Permalink
fix(v2): slot toggle fixes (#6545)
Browse files Browse the repository at this point in the history
* fix(v2): slot toggle fixes

* add more comment
  • Loading branch information
Varixo authored Jun 15, 2024
1 parent 44034f7 commit 6d5c545
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 19 deletions.
32 changes: 23 additions & 9 deletions packages/qwik/src/core/v2/client/vnode-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,12 +382,28 @@ export const vnode_diff = (
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

function descendContentToProject(children: JSXChildren) {
function descendContentToProject(children: JSXChildren, host: VirtualVNode | null) {
if (!Array.isArray(children)) {
children = [children];
}
if (children.length) {
const createProjectionJSXNode = (slotName: string) => {
return new JSXNodeImpl(Projection, EMPTY_OBJ, null, [], 0, slotName);
};

const projections: Array<string | JSXNode> = [];
if (host) {
// we need to create empty projections for all the slots to remove unused slots content
for (let i = vnode_getPropStartIndex(host); i < host.length; i = i + 2) {
const prop = host[i] as string;
if (!prop.startsWith('q:')) {
const slotName = prop;
projections.push(slotName);
projections.push(createProjectionJSXNode(slotName));
}
}
}

/// STEP 1: Bucketize the children based on the projection name.
for (let i = 0; i < children.length; i++) {
const child = children[i];
Expand All @@ -397,14 +413,12 @@ export const vnode_diff = (
if (idx >= 0) {
jsxBucket = projections[idx + 1] as any;
} else {
projections.splice(
~idx,
0,
slotName,
(jsxBucket = new JSXNodeImpl(Projection, EMPTY_OBJ, null, [], 0, slotName))
);
projections.splice(~idx, 0, slotName, (jsxBucket = createProjectionJSXNode(slotName)));
}
const removeProjection = child === false;
if (!removeProjection) {
(jsxBucket.children as JSXChildren[]).push(child);
}
(jsxBucket.children as JSXChildren[]).push(child);
}
/// STEP 2: remove the names
for (let i = projections.length - 2; i >= 0; i = i - 2) {
Expand Down Expand Up @@ -951,7 +965,7 @@ export const vnode_diff = (
container.$scheduler$(ChoreType.COMPONENT, host, componentQRL, jsxProps);
}
}
jsxValue.children != null && descendContentToProject(jsxValue.children);
jsxValue.children != null && descendContentToProject(jsxValue.children, host);
} else {
// Inline Component
vnode_insertBefore(
Expand Down
146 changes: 140 additions & 6 deletions packages/qwik/src/core/v2/tests/projection.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ describe.each([
<div id="btn1">
<Component>
<div>
<Projection>{''}</Projection>
<Projection></Projection>
</div>
</Component>
</div>
Expand Down Expand Up @@ -566,15 +566,13 @@ describe.each([
<Component>
<Projection>
<Projection></Projection>
{''}
</Projection>
</Component>
</Component>
<Component>
<Component>
<Projection>
<Projection></Projection>
{''}
</Projection>
</Component>
</Component>
Expand All @@ -584,6 +582,143 @@ describe.each([
);
});

it('should toggle named slot to nothing', async () => {
const Projector = component$((props: { state: any; id: string }) => {
return (
<div id={props.id}>
<Slot name="start"></Slot>
<Slot />
<Slot name="end"></Slot>
</div>
);
});

const Parent = component$(() => {
const state = useStore({
toggle: true,
count: 0,
});
return (
<>
<Projector state={state} id="btn1">
{state.toggle && <>DEFAULT {state.count}</>}
</Projector>

<Projector state={state} id="btn2">
{state.toggle && <span q:slot="start">START {state.count}</span>}
{state.toggle && <span q:slot="end">END {state.count}</span>}
</Projector>
<button id="toggle" onClick$={() => (state.toggle = !state.toggle)}></button>
<button id="count" onClick$={() => state.count++}></button>
</>
);
});

const { vNode, document } = await render(<Parent />, { debug });
expect(vNode).toMatchVDOM(
<Component>
<Fragment>
<Component>
<div id="btn1">
<Projection>{render === ssrRenderToDom ? '' : null}</Projection>
<Projection>
<Fragment>
{'DEFAULT '}
<DerivedSignal>{'0'}</DerivedSignal>
</Fragment>
</Projection>
<Projection>{render === ssrRenderToDom ? '' : null}</Projection>
</div>
</Component>
<Component>
<div id="btn2">
<Projection>
<span q:slot="start">
{'START '}
<DerivedSignal>{'0'}</DerivedSignal>
</span>
</Projection>
<Projection>{render === ssrRenderToDom ? '' : null}</Projection>
<Projection>
<span q:slot="end">
{'END '}
<DerivedSignal>{'0'}</DerivedSignal>
</span>
</Projection>
</div>
</Component>
<button id="toggle"></button>
<button id="count"></button>
</Fragment>
</Component>
);

await trigger(document.body, '#toggle', 'click');
expect(vNode).toMatchVDOM(
<Component>
<Fragment>
<Component>
<div id="btn1">
<Projection>{render === ssrRenderToDom ? '' : null}</Projection>
<Projection></Projection>
<Projection>{render === ssrRenderToDom ? '' : null}</Projection>
</div>
</Component>
<Component>
<div id="btn2">
<Projection></Projection>
<Projection>{render === ssrRenderToDom ? '' : null}</Projection>
<Projection></Projection>
</div>
</Component>
<button id="toggle"></button>
<button id="count"></button>
</Fragment>
</Component>
);

await trigger(document.body, '#count', 'click');
await trigger(document.body, '#toggle', 'click');

expect(vNode).toMatchVDOM(
<Component>
<Fragment>
<Component>
<div id="btn1">
<Projection>{render === ssrRenderToDom ? '' : null}</Projection>
<Projection>
<Fragment>
{'DEFAULT '}
<DerivedSignal>{'1'}</DerivedSignal>
</Fragment>
</Projection>
<Projection>{render === ssrRenderToDom ? '' : null}</Projection>
</div>
</Component>
<Component>
<div id="btn2">
<Projection>
<span q:slot="start">
{'START '}
<DerivedSignal>{'1'}</DerivedSignal>
</span>
</Projection>
<Projection>{render === ssrRenderToDom ? '' : null}</Projection>
<Projection>
<span q:slot="end">
{'END '}
<DerivedSignal>{'1'}</DerivedSignal>
</span>
</Projection>
</div>
</Component>
<button id="toggle"></button>
<button id="count"></button>
</Fragment>
</Component>
);
});

it('should render to named slot in nested named slots', async () => {
const NestedSlotCmp = component$(() => {
return (
Expand Down Expand Up @@ -1107,8 +1242,7 @@ describe.each([
await trigger(document.body, '#reload', 'click');
});

// TODO(slot): fix this test
it.skip('should not go into an infinity loop because of removing nodes from q:template', async () => {
it('should not go into an infinity loop because of removing nodes from q:template', async () => {
const Projector = component$(() => {
return (
<div>
Expand Down Expand Up @@ -1238,7 +1372,7 @@ describe.each([
style="width:24px;height:24px"
xmlns="http://www.w3.org/2000/svg"
>
<Projection>{''}</Projection>
<Projection></Projection>
</svg>
</Component>
</Fragment>
Expand Down
6 changes: 2 additions & 4 deletions starters/e2e/e2e.slot.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ test.describe("slot", () => {
});
});

// TODO(v2): fix this
test.skip("should toggle content", async ({ page }) => {
test("should toggle content", async ({ page }) => {
const content1 = page.locator("#btn1");
const content2 = page.locator("#btn2");
const content3 = page.locator("#btn3");
Expand All @@ -115,8 +114,7 @@ test.describe("slot", () => {
});
});

// TODO(v2): fix this
test.skip("should toggle content and buttons", async ({ page }) => {
test("should toggle content and buttons", async ({ page }) => {
const content1 = page.locator("#btn1");
const content2 = page.locator("#btn2");
const content3 = page.locator("#btn3");
Expand Down

0 comments on commit 6d5c545

Please sign in to comment.