Skip to content

Commit fe94f9b

Browse files
authored
Handle fragments in adapter (#545)
1 parent ca6a668 commit fe94f9b

3 files changed

Lines changed: 170 additions & 9 deletions

File tree

.changeset/few-emus-cry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@remote-dom/compat': patch
3+
---
4+
5+
Handle fragments in adapter

packages/compat/source/adapter/host.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
KIND_TEXT as LEGACY_KIND_TEXT,
3+
KIND_COMPONENT as LEGACY_KIND_COMPONENT,
34
KIND_FRAGMENT as LEGACY_KIND_FRAGMENT,
45
ACTION_MOUNT as LEGACY_ACTION_MOUNT,
56
ACTION_INSERT_CHILD as LEGACY_ACTION_INSERT_CHILD,
@@ -16,6 +17,7 @@ import {
1617
import {
1718
ROOT_ID,
1819
NODE_TYPE_TEXT,
20+
NODE_TYPE_COMMENT,
1921
NODE_TYPE_ELEMENT,
2022
MUTATION_TYPE_INSERT_CHILD,
2123
MUTATION_TYPE_REMOVE_CHILD,
@@ -26,6 +28,7 @@ import {
2628
type RemoteElementSerialization,
2729
type RemoteConnection,
2830
type RemoteNodeSerialization,
31+
type RemoteCommentSerialization,
2932
} from '@remote-dom/core';
3033

3134
export interface LegacyRemoteChannelElementMap {
@@ -233,7 +236,7 @@ export function adaptToLegacyRemoteChannel(
233236
records.push([
234237
MUTATION_TYPE_INSERT_CHILD,
235238
id,
236-
adaptLegacyFragmentSerialization(key, value, options),
239+
adaptLegacyPropFragmentSerialization(key, value, options),
237240
tree.get(id)?.length ?? 0,
238241
] satisfies RemoteMutationRecord);
239242
} else {
@@ -266,13 +269,26 @@ export function adaptToLegacyRemoteChannel(
266269
}
267270

268271
function adaptLegacyNodeSerialization(
269-
child: LegacyRemoteComponentSerialization | LegacyRemoteTextSerialization,
272+
child:
273+
| LegacyRemoteComponentSerialization
274+
| LegacyRemoteTextSerialization
275+
| LegacyRemoteFragmentSerialization,
270276
options?: LegacyRemoteChannelOptions,
271-
): RemoteElementSerialization | RemoteTextSerialization {
272-
if (child.kind === LEGACY_KIND_TEXT) {
273-
return adaptLegacyTextSerialization(child);
274-
} else {
275-
return adaptLegacyComponentSerialization(child, options);
277+
):
278+
| RemoteElementSerialization
279+
| RemoteTextSerialization
280+
| RemoteCommentSerialization {
281+
switch (child.kind) {
282+
case LEGACY_KIND_TEXT:
283+
return adaptLegacyTextSerialization(child);
284+
case LEGACY_KIND_COMPONENT:
285+
return adaptLegacyComponentSerialization(child, options);
286+
default:
287+
return {
288+
id: child.id,
289+
type: NODE_TYPE_COMMENT,
290+
data: 'added by remote-ui legacy adaptor to replace a fragment rendered as a child',
291+
};
276292
}
277293
}
278294

@@ -343,11 +359,11 @@ function adaptLegacyFragmentsSerialization(
343359
options?: LegacyRemoteChannelOptions,
344360
): RemoteElementSerialization[] {
345361
return Object.entries(fragments).map(([slot, fragment]) => {
346-
return adaptLegacyFragmentSerialization(slot, fragment, options);
362+
return adaptLegacyPropFragmentSerialization(slot, fragment, options);
347363
});
348364
}
349365

350-
function adaptLegacyFragmentSerialization(
366+
function adaptLegacyPropFragmentSerialization(
351367
slot: string,
352368
fragment: LegacyRemoteFragmentSerialization,
353369
options?: LegacyRemoteChannelOptions,

packages/compat/source/tests/adapter.test.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
MUTATION_TYPE_UPDATE_TEXT,
2323
NODE_TYPE_ELEMENT,
2424
NODE_TYPE_TEXT,
25+
NODE_TYPE_COMMENT,
2526
ROOT_ID,
2627
} from '@remote-dom/core';
2728

@@ -117,6 +118,145 @@ describe('adaptToLegacyRemoteChannel()', () => {
117118
]);
118119
});
119120

121+
it('replaces fragments with comment nodes', () => {
122+
const receiver = new TestRemoteReceiver();
123+
124+
const channel = adaptToLegacyRemoteChannel(receiver.connection);
125+
126+
channel(ACTION_MOUNT, [
127+
{
128+
id: '2',
129+
kind: KIND_FRAGMENT,
130+
children: [
131+
{
132+
id: '1',
133+
kind: KIND_TEXT,
134+
text: 'First text',
135+
},
136+
{
137+
id: '0',
138+
kind: KIND_TEXT,
139+
text: 'Second text',
140+
},
141+
],
142+
} as any,
143+
]);
144+
145+
expect(receiver.connection.mutate).toHaveBeenCalledWith([
146+
[
147+
MUTATION_TYPE_INSERT_CHILD,
148+
ROOT_ID,
149+
{
150+
id: '2',
151+
type: NODE_TYPE_COMMENT,
152+
data: 'added by remote-ui legacy adaptor to replace a fragment rendered as a child',
153+
},
154+
0,
155+
],
156+
]);
157+
158+
expect(receiver.root.children).toStrictEqual([
159+
{
160+
id: '2',
161+
type: NODE_TYPE_COMMENT,
162+
data: 'added by remote-ui legacy adaptor to replace a fragment rendered as a child',
163+
version: 0,
164+
},
165+
]);
166+
});
167+
168+
it('mounts components replacing fragment children with comment nodes', () => {
169+
const receiver = new TestRemoteReceiver();
170+
171+
const channel = adaptToLegacyRemoteChannel(receiver.connection);
172+
173+
channel(ACTION_MOUNT, [
174+
{
175+
id: '4',
176+
kind: KIND_COMPONENT,
177+
type: 'Banner',
178+
props: {title: 'Title'},
179+
children: [
180+
{
181+
id: '3',
182+
kind: KIND_TEXT,
183+
text: 'Direct text child',
184+
},
185+
{
186+
id: '2',
187+
kind: KIND_FRAGMENT,
188+
children: [
189+
{
190+
id: '1',
191+
kind: KIND_TEXT,
192+
text: 'Text in fragment',
193+
},
194+
{
195+
id: '0',
196+
kind: KIND_COMPONENT,
197+
type: 'Button',
198+
props: {},
199+
children: [],
200+
},
201+
],
202+
} as any,
203+
],
204+
},
205+
]);
206+
207+
expect(receiver.connection.mutate).toHaveBeenCalledWith([
208+
[
209+
MUTATION_TYPE_INSERT_CHILD,
210+
ROOT_ID,
211+
{
212+
id: '4',
213+
type: NODE_TYPE_ELEMENT,
214+
element: 'Banner',
215+
properties: {title: 'Title'},
216+
children: [
217+
{
218+
id: '3',
219+
type: NODE_TYPE_TEXT,
220+
data: 'Direct text child',
221+
},
222+
{
223+
id: '2',
224+
type: NODE_TYPE_COMMENT,
225+
data: 'added by remote-ui legacy adaptor to replace a fragment rendered as a child',
226+
},
227+
],
228+
},
229+
0,
230+
],
231+
]);
232+
233+
expect(receiver.root.children).toStrictEqual([
234+
{
235+
id: '4',
236+
type: NODE_TYPE_ELEMENT,
237+
element: 'Banner',
238+
children: [
239+
{
240+
id: '3',
241+
type: NODE_TYPE_TEXT,
242+
data: 'Direct text child',
243+
version: 0,
244+
},
245+
{
246+
id: '2',
247+
type: NODE_TYPE_COMMENT,
248+
data: 'added by remote-ui legacy adaptor to replace a fragment rendered as a child',
249+
version: 0,
250+
},
251+
],
252+
properties: {title: 'Title'},
253+
attributes: {},
254+
eventListeners: {},
255+
version: 0,
256+
},
257+
]);
258+
});
259+
120260
it('mounts component nodes with fragment props', () => {
121261
const receiver = new TestRemoteReceiver();
122262

0 commit comments

Comments
 (0)