Skip to content

Commit bcd4798

Browse files
authored
Merge pull request #7550 from QwikDev/v2-signal-wrapper-rerender-fix
fix: signal wrapper should not rerender
2 parents b65dcdf + eb493de commit bcd4798

File tree

7 files changed

+110
-30
lines changed

7 files changed

+110
-30
lines changed

.changeset/unlucky-olives-knock.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: signal wrapper should not rerender causing missing child error

packages/docs/src/repl/worker/repl-request-handler.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,6 @@ export const requestHandler = (ev: FetchEvent) => {
3030
return htmlRsp;
3131
}
3232

33-
// app client modules
34-
// const replEvent: ReplEventMessage = {
35-
// type: 'event',
36-
// clientId,
37-
// event: {
38-
// kind: 'client-module',
39-
// scope: 'network',
40-
// message: [reqUrl.pathname + reqUrl.search],
41-
// start: performance.now(),
42-
// },
43-
// };
44-
45-
// sendMessageToReplServer(replEvent);
46-
4733
return rsp;
4834
}
4935

@@ -169,5 +155,9 @@ const injectDevHtml = (clientId: string, html?: string) => {
169155
}, true);
170156
})();`;
171157

172-
return `<script :>${s}</script>${html || ''}`;
158+
if (!html) {
159+
return '';
160+
}
161+
162+
return html.replace(/<\/body>/i, `<script>${s}</script></body>`);
173163
};

packages/qwik/src/core/client/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ export const enum VNodeFlags {
9696
}
9797

9898
export const enum VNodeFlagsIndex {
99-
mask /* ************* */ = ~0b11_111111,
100-
negated_mask /* ****** */ = 0b11_111111,
99+
mask /* ************** */ = 0b11_111111,
101100
shift /* ************* */ = 8,
102101
}
103102

packages/qwik/src/core/client/vnode-diff.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -991,7 +991,13 @@ export const vnode_diff = (
991991
}
992992

993993
function expectVirtual(type: VirtualType, jsxKey: string | null) {
994-
if (vCurrent && vnode_isVirtualVNode(vCurrent) && getKey(vCurrent) === jsxKey && !!jsxKey) {
994+
const checkKey = type === VirtualType.Fragment;
995+
if (
996+
vCurrent &&
997+
vnode_isVirtualVNode(vCurrent) &&
998+
getKey(vCurrent) === jsxKey &&
999+
(checkKey ? !!jsxKey : true)
1000+
) {
9951001
// All is good.
9961002
return;
9971003
} else if (jsxKey !== null) {

packages/qwik/src/core/client/vnode-diff.unit.tsx

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { Fragment, _jsxSorted } from '@qwik.dev/core';
1+
import { Fragment, _fnSignal, _jsxSorted } from '@qwik.dev/core';
22
import { vnode_fromJSX } from '@qwik.dev/core/testing';
33
import { describe, expect, it } from 'vitest';
44
import { vnode_applyJournal, vnode_getFirstChild, vnode_getNode } from './vnode';
55
import { vnode_diff } from './vnode-diff';
66
import type { QElement } from '../shared/types';
77
import { createSignal } from '../reactive-primitives/signal-api';
88
import { QError, qError } from '../shared/error/error';
9+
import type { VirtualVNode } from './types';
910

1011
describe('vNode-diff', () => {
1112
it('should find no difference', () => {
@@ -430,7 +431,75 @@ describe('vNode-diff', () => {
430431
expect(b2).toBe(selectB2());
431432
});
432433
});
433-
describe.todo('fragments', () => {});
434+
describe('fragments', () => {
435+
it('should not rerender signal wrapper fragment', async () => {
436+
const { vNode, vParent, container } = vnode_fromJSX(
437+
_jsxSorted('div', {}, null, [_jsxSorted(Fragment, {}, null, ['1'], 0, null)], 0, null)
438+
);
439+
const test = _jsxSorted('div', {}, null, [_fnSignal(() => '2', [], '() => "2"')], 0, null);
440+
const expected = _jsxSorted(
441+
'div',
442+
{},
443+
null,
444+
[_jsxSorted(Fragment, {}, null, ['2'], 0, null)],
445+
0,
446+
null
447+
);
448+
449+
const signalFragment = vnode_getFirstChild(vNode!) as VirtualVNode;
450+
expect(signalFragment).toBeDefined();
451+
452+
vnode_diff(container, test, vParent, null);
453+
vnode_applyJournal(container.$journal$);
454+
expect(vNode).toMatchVDOM(expected);
455+
expect(signalFragment).toBe(vnode_getFirstChild(vNode!));
456+
});
457+
458+
it('should not rerender promise wrapper fragment', async () => {
459+
const { vNode, vParent, container } = vnode_fromJSX(
460+
_jsxSorted('div', {}, null, [_jsxSorted(Fragment, {}, null, ['1'], 0, null)], 0, null)
461+
);
462+
const test = _jsxSorted('div', {}, null, [Promise.resolve('2')], 0, null);
463+
const expected = _jsxSorted(
464+
'div',
465+
{},
466+
null,
467+
[_jsxSorted(Fragment, {}, null, ['2'], 0, null)],
468+
0,
469+
null
470+
);
471+
472+
const promiseFragment = vnode_getFirstChild(vNode!) as VirtualVNode;
473+
expect(promiseFragment).toBeDefined();
474+
475+
await vnode_diff(container, test, vParent, null);
476+
vnode_applyJournal(container.$journal$);
477+
expect(vNode).toMatchVDOM(expected);
478+
expect(promiseFragment).toBe(vnode_getFirstChild(vNode!));
479+
});
480+
481+
it('should rerender fragment if no key', async () => {
482+
const { vNode, vParent, container } = vnode_fromJSX(
483+
_jsxSorted('div', {}, null, [_jsxSorted(Fragment, {}, null, ['1'], 0, null)], 0, null)
484+
);
485+
const test = _jsxSorted(
486+
'div',
487+
{},
488+
null,
489+
[_jsxSorted(Fragment, {}, null, ['2'], 0, null)],
490+
0,
491+
null
492+
);
493+
494+
const fragment = vnode_getFirstChild(vNode!) as VirtualVNode;
495+
expect(fragment).toBeDefined();
496+
497+
await vnode_diff(container, test, vParent, null);
498+
vnode_applyJournal(container.$journal$);
499+
expect(vNode).toMatchVDOM(test);
500+
expect(fragment).not.toBe(vnode_getFirstChild(vNode!));
501+
});
502+
});
434503
describe('attributes', () => {
435504
describe('const props', () => {
436505
it('should set attributes', async () => {

packages/qwik/src/core/client/vnode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1810,7 +1810,7 @@ function materializeFromVNodeData(
18101810

18111811
const addVNode = (node: VNode) => {
18121812
node[VNodeProps.flags] =
1813-
(node[VNodeProps.flags] & VNodeFlagsIndex.negated_mask) | (idx << VNodeFlagsIndex.shift);
1813+
(node[VNodeProps.flags] & VNodeFlagsIndex.mask) | (idx << VNodeFlagsIndex.shift);
18141814
idx++;
18151815
vLast && (vLast[VNodeProps.nextSibling] = node);
18161816
node[VNodeProps.previousSibling] = vLast;

packages/qwik/src/testing/platform.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,20 @@ function createPlatform() {
1111
}
1212

1313
let render: Queue<any> | null = null;
14+
let resolveNextTickImmediate = false;
1415

1516
const moduleCache = new Map<string, { [symbol: string]: any }>();
17+
const flushNextTick = async () => {
18+
await Promise.resolve();
19+
if (render) {
20+
try {
21+
render.resolve(await render.fn());
22+
} catch (e) {
23+
render.reject(e);
24+
}
25+
render = null;
26+
}
27+
};
1628
const testPlatform: TestPlatform = {
1729
isServer: false,
1830
importSymbol(containerEl, url, symbolName) {
@@ -39,7 +51,7 @@ function createPlatform() {
3951
return mod[symbolName];
4052
});
4153
},
42-
nextTick: (renderMarked) => {
54+
nextTick: async (renderMarked) => {
4355
if (!render) {
4456
render = {
4557
fn: renderMarked,
@@ -51,6 +63,10 @@ function createPlatform() {
5163
render!.resolve = resolve;
5264
render!.reject = reject;
5365
});
66+
if (resolveNextTickImmediate) {
67+
resolveNextTickImmediate = false;
68+
await flushNextTick();
69+
}
5470
} else if (renderMarked !== render.fn) {
5571
// TODO(misko): proper error and test
5672
throw new Error(
@@ -69,14 +85,9 @@ function createPlatform() {
6985
});
7086
},
7187
flush: async () => {
72-
await Promise.resolve();
73-
if (render) {
74-
try {
75-
render.resolve(await render.fn());
76-
} catch (e) {
77-
render.reject(e);
78-
}
79-
render = null;
88+
await flushNextTick();
89+
if (!render) {
90+
resolveNextTickImmediate = true;
8091
}
8192
},
8293
chunkForSymbol() {

0 commit comments

Comments
 (0)