Skip to content

Commit 5a3e1e2

Browse files
authored
Add support for metadata (#570)
1 parent a4dfa83 commit 5a3e1e2

File tree

9 files changed

+1740
-1759
lines changed

9 files changed

+1740
-1759
lines changed

package-lock.json

Lines changed: 1491 additions & 1637 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@
4242
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
4343
},
4444
"dependencies": {
45-
"@croct/plug": "^0.20.0",
46-
"@croct/sdk": "^0.19.0"
45+
"@croct/plug": "^0.21.0",
46+
"@croct/sdk": "^0.20.0"
4747
},
4848
"devDependencies": {
4949
"@babel/core": "^7.25.2",

src/components/Slot/index.d.test.tsx

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,29 @@ describe('<Slot /> typing', () => {
7272
return info.name;
7373
}
7474

75+
it('should infer whether the schema is requested', () => {
76+
const code: CodeOptions = {
77+
code: `
78+
<Slot id={'home-banner'} initial={true} includeSchema>
79+
{(params) => typeof params.metadata}
80+
</Slot>;
81+
`,
82+
mapping: true,
83+
};
84+
85+
expect(() => compileCode(code)).not.toThrow();
86+
87+
expect(getParameterType(code)).toBe(
88+
'FetchResponse<boolean | HomeBannerV1, '
89+
+ '{children: {};id: "home-banner";initial: boolean;includeSchema: true;}>',
90+
);
91+
});
92+
7593
it('should allow a renderer that accepts JSON objects or covariants for unmapped slots', () => {
7694
const code: CodeOptions = {
7795
code: `
7896
<Slot id={'home-banner'}>
79-
{(params: {foo: string}) => typeof params}
97+
{(params: {content: {foo: string}}) => typeof params.content}
8098
</Slot>;
8199
`,
82100
mapping: false,
@@ -89,7 +107,7 @@ describe('<Slot /> typing', () => {
89107
const code: CodeOptions = {
90108
code: `
91109
<Slot id={'home-banner'}>
92-
{(params: true) => typeof params}
110+
{(params: {content: true}) => typeof params.content}
93111
</Slot>;
94112
`,
95113
mapping: false,
@@ -102,7 +120,7 @@ describe('<Slot /> typing', () => {
102120
const code: CodeOptions = {
103121
code: `
104122
<Slot id={'home-banner'} initial={true}>
105-
{(params: {foo: string}|boolean) => typeof params}
123+
{(params: {content: {foo: string}|boolean}) => typeof params.content}
106124
</Slot>;
107125
`,
108126
mapping: false,
@@ -115,7 +133,7 @@ describe('<Slot /> typing', () => {
115133
const code: CodeOptions = {
116134
code: `
117135
<Slot id={'home-banner'} initial={true}>
118-
{(params: {foo: string}) => typeof params}
136+
{(params: {content: {foo: string}}) => typeof params.content}
119137
</Slot>;
120138
`,
121139
mapping: false,
@@ -128,7 +146,7 @@ describe('<Slot /> typing', () => {
128146
const code: CodeOptions = {
129147
code: `
130148
<Slot id={'home-banner'} fallback={true}>
131-
{(params: {foo: string}|boolean) => typeof params}
149+
{(params: {content: {foo: string}|boolean}) => typeof params.content}
132150
</Slot>;
133151
`,
134152
mapping: false,
@@ -141,7 +159,7 @@ describe('<Slot /> typing', () => {
141159
const code: CodeOptions = {
142160
code: `
143161
<Slot id={'home-banner'} fallback={true}>
144-
{(params: {foo: string}) => typeof params}
162+
{(params: {content: {foo: string}}) => typeof params.content}
145163
</Slot>;
146164
`,
147165
mapping: false,
@@ -154,7 +172,7 @@ describe('<Slot /> typing', () => {
154172
const code: CodeOptions = {
155173
code: `
156174
<Slot id={'home-banner'} initial={true} fallback={1}>
157-
{(params: {foo: string}|boolean|number) => typeof params}
175+
{(params: {content: {foo: string}|boolean|number}) => typeof params.content}
158176
</Slot>;
159177
`,
160178
mapping: false,
@@ -167,7 +185,7 @@ describe('<Slot /> typing', () => {
167185
const code: CodeOptions = {
168186
code: `
169187
<Slot id={'home-banner'} initial={true} fallback={1}>
170-
{(params: {foo: string}|boolean) => typeof params}
188+
{(params: {content: {foo: string}|boolean}) => typeof params.content}
171189
</Slot>;
172190
`,
173191
mapping: false,
@@ -180,7 +198,7 @@ describe('<Slot /> typing', () => {
180198
const code: CodeOptions = {
181199
code: `
182200
<Slot id={'home-banner'} initial={true} fallback={1}>
183-
{(params: {foo: string}|number) => typeof params}
201+
{(params: {content: {foo: string}|number}) => typeof params.content}
184202
</Slot>;
185203
`,
186204
mapping: false,
@@ -201,14 +219,14 @@ describe('<Slot /> typing', () => {
201219

202220
expect(() => compileCode(code)).not.toThrow();
203221

204-
expect(getParameterType(code)).toBe('HomeBannerV1');
222+
expect(getParameterType(code)).toBe('FetchResponse<HomeBannerV1, FetchResponseOptions>');
205223
});
206224

207225
it('should allow a covariant renderer parameter type for mapped slots', () => {
208226
const code: CodeOptions = {
209227
code: `
210228
<Slot id={'home-banner'}>
211-
{(params: {title: string}) => typeof params}
229+
{(params: {content: {title: string}}) => typeof params}
212230
</Slot>;
213231
`,
214232
mapping: true,
@@ -221,7 +239,7 @@ describe('<Slot /> typing', () => {
221239
const code: CodeOptions = {
222240
code: `
223241
<Slot id={'home-banner'}>
224-
{(params: {foo: string}) => typeof params}
242+
{(params: {content: {foo: string}}) => typeof params}
225243
</Slot>;
226244
`,
227245
mapping: true,
@@ -242,14 +260,14 @@ describe('<Slot /> typing', () => {
242260

243261
expect(() => compileCode(code)).not.toThrow();
244262

245-
expect(getParameterType(code)).toBe('boolean | HomeBannerV1');
263+
expect(getParameterType(code)).toBe('FetchResponse<boolean | HomeBannerV1, FetchResponseOptions>');
246264
});
247265

248266
it('should allow a renderer that accepts the initial value for mapped slots', () => {
249267
const code: CodeOptions = {
250268
code: `
251269
<Slot id={'home-banner'} initial={true}>
252-
{(params: {title: string}|boolean) => typeof params}
270+
{(params: {content: {title: string}|boolean}) => typeof params.content}
253271
</Slot>;
254272
`,
255273
mapping: true,
@@ -262,7 +280,7 @@ describe('<Slot /> typing', () => {
262280
const code: CodeOptions = {
263281
code: `
264282
<Slot id={'home-banner'} initial={true}>
265-
{(params: {title: string}) => typeof params}
283+
{(params: {content: {title: string}}) => typeof params.content}
266284
</Slot>;
267285
`,
268286
mapping: true,
@@ -283,14 +301,14 @@ describe('<Slot /> typing', () => {
283301

284302
expect(() => compileCode(code)).not.toThrow();
285303

286-
expect(getParameterType(code)).toBe('boolean | HomeBannerV1');
304+
expect(getParameterType(code)).toBe('FetchResponse<boolean | HomeBannerV1, FetchResponseOptions>');
287305
});
288306

289307
it('should allow a renderer that accepts the fallback value for mapped slots', () => {
290308
const code: CodeOptions = {
291309
code: `
292310
<Slot id={'home-banner'} fallback={true}>
293-
{(params: {title: string}|boolean) => typeof params}
311+
{(params: {content: {title: string}|boolean}) => typeof params.content}
294312
</Slot>;
295313
`,
296314
mapping: true,
@@ -303,7 +321,7 @@ describe('<Slot /> typing', () => {
303321
const code: CodeOptions = {
304322
code: `
305323
<Slot id={'home-banner'} fallback={true}>
306-
{(params: {title: string}) => typeof params}
324+
{(params: {content: {title: string}}) => typeof params.content}
307325
</Slot>;
308326
`,
309327
mapping: true,
@@ -324,14 +342,14 @@ describe('<Slot /> typing', () => {
324342

325343
expect(() => compileCode(code)).not.toThrow();
326344

327-
expect(getParameterType(code)).toBe('number | boolean | HomeBannerV1');
345+
expect(getParameterType(code)).toBe('FetchResponse<number | boolean | HomeBannerV1, FetchResponseOptions>');
328346
});
329347

330348
it('should allow a renderer that accepts both the initial and fallback values for mapped slots', () => {
331349
const code: CodeOptions = {
332350
code: `
333351
<Slot id={'home-banner'} initial={true} fallback={1}>
334-
{(params: {title: string}|boolean|number) => typeof params}
352+
{(params: {content: {title: string}|boolean|number}) => typeof params.content}
335353
</Slot>;
336354
`,
337355
mapping: true,
@@ -344,7 +362,7 @@ describe('<Slot /> typing', () => {
344362
const code: CodeOptions = {
345363
code: `
346364
<Slot id={'home-banner'} initial={true} fallback={1}>
347-
{(params: {title: string}|boolean) => typeof params}
365+
{(params: {content: {title: string}|boolean}) => typeof params.content}
348366
</Slot>;
349367
`,
350368
mapping: true,
@@ -357,7 +375,7 @@ describe('<Slot /> typing', () => {
357375
const code: CodeOptions = {
358376
code: `
359377
<Slot id={'home-banner'} initial={true} fallback={1}>
360-
{(params: {title: string}|number) => typeof params}
378+
{(params: {content: {title: string}|number}) => typeof params.content}
361379
</Slot>;
362380
`,
363381
mapping: true,

src/components/Slot/index.test.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {render, screen} from '@testing-library/react';
22
import {Slot, SlotProps} from './index';
3-
import {useContent} from '../../hooks';
3+
import {FetchResponse, useContent} from '../../hooks';
44
import '@testing-library/jest-dom';
55

66
jest.mock(
@@ -14,11 +14,18 @@ describe('<Slot />', () => {
1414
it('should fetch and render a slot', () => {
1515
const {id, children, ...options}: SlotProps<{title: string}> = {
1616
id: 'home-banner',
17-
children: jest.fn(({title}) => title),
17+
children: jest.fn(({content: {title}}) => title),
1818
fallback: {title: 'fallback'},
1919
};
2020

21-
const result = {title: 'result'};
21+
const result = {
22+
metadata: {
23+
version: '1.0',
24+
},
25+
content: {
26+
title: 'result',
27+
},
28+
} satisfies FetchResponse;
2229

2330
jest.mocked(useContent).mockReturnValue(result);
2431

@@ -30,6 +37,6 @@ describe('<Slot />', () => {
3037

3138
expect(useContent).toHaveBeenCalledWith(id, options);
3239
expect(children).toHaveBeenCalledWith(result);
33-
expect(screen.getByText(result.title)).toBeInTheDocument();
40+
expect(screen.getByText(result.content.title)).toBeInTheDocument();
3441
});
3542
});

src/components/Slot/index.tsx

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,54 @@
11
'use client';
22

33
import {Fragment, ReactElement, ReactNode} from 'react';
4-
import {SlotContent, VersionedSlotId, VersionedSlotMap} from '@croct/plug/slot';
4+
import {DynamicSlotId, SlotContent, VersionedSlotId, VersionedSlotMap} from '@croct/plug/slot';
55
import {JsonObject} from '@croct/plug/sdk/json';
6-
import {useContent, UseContentOptions} from '../../hooks';
6+
import {FetchResponseOptions} from '@croct/sdk/contentFetcher';
7+
import {FetchResponse, useContent, UseContentOptions} from '../../hooks';
78

89
type Renderer<P> = (props: P) => ReactNode;
910

10-
export type SlotProps<P, I = P, F = P, S extends VersionedSlotId = VersionedSlotId> = UseContentOptions<I, F> & {
11+
export type SlotProps<
12+
P,
13+
I = P,
14+
F = P,
15+
S extends VersionedSlotId = VersionedSlotId,
16+
O extends FetchResponseOptions = FetchResponseOptions
17+
> = O & UseContentOptions<I, F> & {
1118
id: S,
12-
children: Renderer<P | I | F>,
19+
children: Renderer<FetchResponse<P | I | F, O>>,
1320
};
1421

1522
type SlotComponent = {
16-
<P, I, F>(
23+
<P, I, F, O extends FetchResponseOptions>(
1724
props:
1825
Extract<P | I | F, JsonObject> extends never
19-
? SlotProps<JsonObject, never, never, keyof VersionedSlotMap extends never ? string : never>
20-
: SlotProps<P, I, F, keyof VersionedSlotMap extends never ? string : never>
26+
? SlotProps<JsonObject, never, never, keyof VersionedSlotMap extends never ? string : never, O>
27+
: SlotProps<P, I, F, keyof VersionedSlotMap extends never ? string : never, O>
2128
): ReactElement,
2229

23-
<S extends VersionedSlotId>(props: SlotProps<SlotContent<S>, never, never, S>): ReactElement,
30+
<S extends VersionedSlotId, O extends FetchResponseOptions>(
31+
props: SlotProps<SlotContent<S>, never, never, S, O>
32+
): ReactElement,
2433

25-
<I, S extends VersionedSlotId>(props: SlotProps<SlotContent<S>, I, never, S>): ReactElement,
34+
<I, S extends VersionedSlotId, O extends FetchResponseOptions>(
35+
props: SlotProps<SlotContent<S>, I, never, S, O>
36+
): ReactElement,
2637

27-
<F, S extends VersionedSlotId>(props: SlotProps<SlotContent<S>, never, F, S>): ReactElement,
38+
<F, S extends VersionedSlotId, O extends FetchResponseOptions>(
39+
props: SlotProps<SlotContent<S>, never, F, S, O>
40+
): ReactElement,
2841

29-
<I, F, S extends VersionedSlotId>(props: SlotProps<SlotContent<S>, I, F, S>): ReactElement,
42+
<I, F, S extends VersionedSlotId, O extends FetchResponseOptions>(
43+
props: SlotProps<SlotContent<S>, I, F, S, O>
44+
): ReactElement,
3045

3146
(props: SlotProps<void, void, void>): ReactElement,
3247
};
3348

3449
export const Slot: SlotComponent = <I, F>(props: SlotProps<JsonObject, I, F>): ReactElement => {
3550
const {id, children, ...options} = props;
36-
const data = useContent(id, options);
51+
const data = useContent<DynamicSlotId>(id, options);
3752

3853
return <Fragment>{children(data)}</Fragment>;
3954
};

0 commit comments

Comments
 (0)