Skip to content

Commit 976e636

Browse files
committed
fix: undefined or null as projection child
1 parent da11736 commit 976e636

File tree

5 files changed

+91
-33
lines changed

5 files changed

+91
-33
lines changed

.changeset/rotten-penguins-cough.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/core': patch
3+
---
4+
5+
fix: undefined or null as projection child
Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
import { assertTrue } from '../shared/error/assert';
22

3-
export const mapApp_findIndx = <T>(
4-
elementVNode: (T | null)[],
5-
key: string,
6-
start: number
7-
): number => {
3+
export const mapApp_findIndx = <T>(array: (T | null)[], key: string, start: number): number => {
84
assertTrue(start % 2 === 0, 'Expecting even number.');
95
let bottom = (start as number) >> 1;
10-
let top = (elementVNode.length - 2) >> 1;
6+
let top = (array.length - 2) >> 1;
117
while (bottom <= top) {
128
const mid = bottom + ((top - bottom) >> 1);
13-
const midKey = elementVNode[mid << 1] as string;
9+
const midKey = array[mid << 1] as string;
1410
if (midKey === key) {
1511
return mid << 1;
1612
}
@@ -24,47 +20,43 @@ export const mapApp_findIndx = <T>(
2420
};
2521

2622
export const mapArray_set = <T>(
27-
elementVNode: (T | null)[],
23+
array: (T | null)[],
2824
key: string,
2925
value: T | null,
3026
start: number
3127
) => {
32-
const indx = mapApp_findIndx(elementVNode, key, start);
28+
const indx = mapApp_findIndx(array, key, start);
3329
if (indx >= 0) {
3430
if (value == null) {
35-
elementVNode.splice(indx, 2);
31+
array.splice(indx, 2);
3632
} else {
37-
elementVNode[indx + 1] = value;
33+
array[indx + 1] = value;
3834
}
3935
} else if (value != null) {
40-
elementVNode.splice(indx ^ -1, 0, key as any, value);
36+
array.splice(indx ^ -1, 0, key as any, value);
4137
}
4238
};
4339

44-
export const mapApp_remove = <T>(
45-
elementVNode: (T | null)[],
46-
key: string,
47-
start: number
48-
): T | null => {
49-
const indx = mapApp_findIndx(elementVNode, key, start);
40+
export const mapApp_remove = <T>(array: (T | null)[], key: string, start: number): T | null => {
41+
const indx = mapApp_findIndx(array, key, start);
5042
let value: T | null = null;
5143
if (indx >= 0) {
52-
value = elementVNode[indx + 1];
53-
elementVNode.splice(indx, 2);
44+
value = array[indx + 1];
45+
array.splice(indx, 2);
5446
return value;
5547
}
5648
return value;
5749
};
5850

59-
export const mapArray_get = <T>(
60-
elementVNode: (T | null)[],
61-
key: string,
62-
start: number
63-
): T | null => {
64-
const indx = mapApp_findIndx(elementVNode, key, start);
51+
export const mapArray_get = <T>(array: (T | null)[], key: string, start: number): T | null => {
52+
const indx = mapApp_findIndx(array, key, start);
6553
if (indx >= 0) {
66-
return elementVNode[indx + 1] as T | null;
54+
return array[indx + 1] as T | null;
6755
} else {
6856
return null;
6957
}
7058
};
59+
60+
export const mapArray_has = <T>(array: (T | null)[], key: string, start: number): boolean => {
61+
return mapApp_findIndx(array, key, start) >= 0;
62+
};

packages/qwik/src/core/tests/projection.spec.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2195,6 +2195,63 @@ describe.each([
21952195
}
21962196
});
21972197

2198+
it('should toggle content projection from undefined or null', async () => {
2199+
const Wrapper = component$(() => {
2200+
return <Slot />;
2201+
});
2202+
2203+
const Cmp = component$(() => {
2204+
const show = useSignal(false);
2205+
2206+
return (
2207+
<>
2208+
<button
2209+
onClick$={() => {
2210+
show.value = !show.value;
2211+
}}
2212+
>
2213+
Click
2214+
</button>
2215+
<Wrapper>{show.value ? <div>Test</div> : undefined}</Wrapper>
2216+
<Wrapper>{show.value ? <div>Test</div> : null}</Wrapper>
2217+
</>
2218+
);
2219+
});
2220+
2221+
const { vNode, document } = await render(<Cmp />, { debug: DEBUG });
2222+
expect(vNode).toMatchVDOM(
2223+
<Component ssr-required>
2224+
<Fragment ssr-required>
2225+
<button>Click</button>
2226+
<Component ssr-required>
2227+
<Projection ssr-required></Projection>
2228+
</Component>
2229+
<Component ssr-required>
2230+
<Projection ssr-required></Projection>
2231+
</Component>
2232+
</Fragment>
2233+
</Component>
2234+
);
2235+
await trigger(document.body, 'button', 'click');
2236+
expect(vNode).toMatchVDOM(
2237+
<Component ssr-required>
2238+
<Fragment ssr-required>
2239+
<button>Click</button>
2240+
<Component ssr-required>
2241+
<Projection ssr-required>
2242+
<div>Test</div>
2243+
</Projection>
2244+
</Component>
2245+
<Component ssr-required>
2246+
<Projection ssr-required>
2247+
<div>Test</div>
2248+
</Projection>
2249+
</Component>
2250+
</Fragment>
2251+
</Component>
2252+
);
2253+
});
2254+
21982255
describe('regression', () => {
21992256
it('#1630', async () => {
22002257
const Child = component$(() => <b>CHILD</b>);

packages/qwik/src/server/qwik-copy.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313
* lists code which we are OK to have duplicated.
1414
*/
1515

16-
export { mapApp_remove, mapArray_get, mapArray_set } from '../core/client/util-mapArray';
16+
export {
17+
mapApp_remove,
18+
mapArray_get,
19+
mapArray_set,
20+
mapArray_has,
21+
} from '../core/client/util-mapArray';
1722
export { QError, qError } from '../core/shared/error/error';
1823
export { SYNC_QRL } from '../core/shared/qrl/qrl-utils';
1924
export { ChoreType } from '../core/shared/util-chore-type';

packages/qwik/src/server/ssr-node.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
mapApp_remove,
1111
mapArray_get,
1212
mapArray_set,
13+
mapArray_has,
1314
ELEMENT_SEQ,
1415
QSlot,
1516
QDefaultSlot,
@@ -196,15 +197,13 @@ export class SsrComponentFrame implements ISsrComponentFrame {
196197
}
197198

198199
hasSlot(slotName: string): boolean {
199-
return mapArray_get(this.slots, slotName, 0) !== null;
200+
return mapArray_has(this.slots, slotName, 0);
200201
}
201202

202203
consumeChildrenForSlot(projectionNode: ISsrNode, slotName: string): JSXChildren | null {
203204
const children = mapApp_remove(this.slots, slotName, 0);
204-
if (children !== null) {
205-
this.componentNode.setProp(slotName, projectionNode.id);
206-
projectionNode.setProp(QSlotParent, this.componentNode.id);
207-
}
205+
this.componentNode.setProp(slotName, projectionNode.id);
206+
projectionNode.setProp(QSlotParent, this.componentNode.id);
208207
return children;
209208
}
210209

0 commit comments

Comments
 (0)