safe-mdx 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/safe-mdx.bench.d.ts +2 -0
- package/dist/safe-mdx.bench.d.ts.map +1 -0
- package/dist/safe-mdx.bench.js +35 -0
- package/dist/safe-mdx.bench.js.map +1 -0
- package/dist/safe-mdx.d.ts +21 -10
- package/dist/safe-mdx.d.ts.map +1 -1
- package/dist/safe-mdx.js +71 -29
- package/dist/safe-mdx.js.map +1 -1
- package/dist/safe-mdx.test.js +194 -2
- package/dist/safe-mdx.test.js.map +1 -1
- package/package.json +4 -2
- package/src/safe-mdx.bench.tsx +46 -0
- package/src/safe-mdx.test.tsx +212 -3
- package/src/safe-mdx.tsx +137 -125
package/dist/safe-mdx.test.js
CHANGED
|
@@ -3,6 +3,7 @@ import dedent from 'dedent';
|
|
|
3
3
|
import { htmlToJsx } from 'html-to-jsx-transform';
|
|
4
4
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
5
5
|
import { expect, test } from 'vitest';
|
|
6
|
+
import { z } from 'zod';
|
|
6
7
|
import { mdxParse } from './parse.js';
|
|
7
8
|
import { MdastToJsx, mdastBfs } from './safe-mdx.js';
|
|
8
9
|
import { completeJsxTags } from './streaming.js';
|
|
@@ -14,9 +15,9 @@ const components = {
|
|
|
14
15
|
return _jsx("div", { children: children });
|
|
15
16
|
},
|
|
16
17
|
};
|
|
17
|
-
function render(code) {
|
|
18
|
+
function render(code, componentPropsSchema) {
|
|
18
19
|
const mdast = mdxParse(code);
|
|
19
|
-
const visitor = new MdastToJsx({ markdown: code, mdast, components });
|
|
20
|
+
const visitor = new MdastToJsx({ markdown: code, mdast, components, componentPropsSchema });
|
|
20
21
|
const result = visitor.run();
|
|
21
22
|
const html = renderToStaticMarkup(result);
|
|
22
23
|
// console.log(JSON.stringify(result, null, 2))
|
|
@@ -441,6 +442,7 @@ test('missing components are ignored', () => {
|
|
|
441
442
|
{
|
|
442
443
|
"errors": [
|
|
443
444
|
{
|
|
445
|
+
"line": 1,
|
|
444
446
|
"message": "Unsupported jsx component MissingComponent",
|
|
445
447
|
},
|
|
446
448
|
],
|
|
@@ -474,15 +476,19 @@ test('props parsing', () => {
|
|
|
474
476
|
{
|
|
475
477
|
"errors": [
|
|
476
478
|
{
|
|
479
|
+
"line": 7,
|
|
477
480
|
"message": "Expressions in jsx props are not supported (expression1={1 + 3})",
|
|
478
481
|
},
|
|
479
482
|
{
|
|
483
|
+
"line": 8,
|
|
480
484
|
"message": "Expressions in jsx props are not supported (expression2={Boolean(1)})",
|
|
481
485
|
},
|
|
482
486
|
{
|
|
487
|
+
"line": 9,
|
|
483
488
|
"message": "Expressions in jsx props are not supported (jsx={<SomeComponent />})",
|
|
484
489
|
},
|
|
485
490
|
{
|
|
491
|
+
"line": 13,
|
|
486
492
|
"message": "Expressions in jsx props are not supported (...{ spread: true })",
|
|
487
493
|
},
|
|
488
494
|
],
|
|
@@ -2243,4 +2249,190 @@ _No documentation_
|
|
|
2243
2249
|
</React.Fragment>
|
|
2244
2250
|
`);
|
|
2245
2251
|
});
|
|
2252
|
+
test('component props schema validation with zod', () => {
|
|
2253
|
+
const HeadingSchema = z.object({
|
|
2254
|
+
level: z.number().min(1).max(6),
|
|
2255
|
+
title: z.string().optional(),
|
|
2256
|
+
});
|
|
2257
|
+
const CardsSchema = z.object({
|
|
2258
|
+
count: z.number().positive(),
|
|
2259
|
+
variant: z.enum(['default', 'outline']).optional(),
|
|
2260
|
+
});
|
|
2261
|
+
const componentPropsSchema = {
|
|
2262
|
+
Heading: HeadingSchema,
|
|
2263
|
+
Cards: CardsSchema,
|
|
2264
|
+
};
|
|
2265
|
+
const code = dedent `
|
|
2266
|
+
<Heading level={2} title="test">Valid heading</Heading>
|
|
2267
|
+
|
|
2268
|
+
<Cards count={3} variant="outline">Valid cards</Cards>
|
|
2269
|
+
|
|
2270
|
+
<Heading level={10} title="test">Invalid heading - level too high</Heading>
|
|
2271
|
+
|
|
2272
|
+
<Cards count={-1}>Invalid cards - negative count</Cards>
|
|
2273
|
+
|
|
2274
|
+
<Cards count="not a number">Invalid cards - wrong type</Cards>
|
|
2275
|
+
`;
|
|
2276
|
+
expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
|
|
2277
|
+
{
|
|
2278
|
+
"errors": [
|
|
2279
|
+
{
|
|
2280
|
+
"line": 5,
|
|
2281
|
+
"message": "Invalid props for component "Heading" at "level": Number must be less than or equal to 6",
|
|
2282
|
+
"schemaPath": "level",
|
|
2283
|
+
},
|
|
2284
|
+
{
|
|
2285
|
+
"line": 7,
|
|
2286
|
+
"message": "Invalid props for component "Cards" at "count": Number must be greater than 0",
|
|
2287
|
+
"schemaPath": "count",
|
|
2288
|
+
},
|
|
2289
|
+
{
|
|
2290
|
+
"line": 9,
|
|
2291
|
+
"message": "Invalid props for component "Cards" at "count": Expected number, received string",
|
|
2292
|
+
"schemaPath": "count",
|
|
2293
|
+
},
|
|
2294
|
+
],
|
|
2295
|
+
"html": "<h1>Valid heading</h1><div>Valid cards</div><h1>Invalid heading - level too high</h1><div>Invalid cards - negative count</div><div>Invalid cards - wrong type</div>",
|
|
2296
|
+
"result": <React.Fragment>
|
|
2297
|
+
<Heading
|
|
2298
|
+
level={2}
|
|
2299
|
+
title="test"
|
|
2300
|
+
>
|
|
2301
|
+
Valid heading
|
|
2302
|
+
</Heading>
|
|
2303
|
+
<Cards
|
|
2304
|
+
count={3}
|
|
2305
|
+
variant="outline"
|
|
2306
|
+
>
|
|
2307
|
+
Valid cards
|
|
2308
|
+
</Cards>
|
|
2309
|
+
<Heading
|
|
2310
|
+
level={10}
|
|
2311
|
+
title="test"
|
|
2312
|
+
>
|
|
2313
|
+
Invalid heading - level too high
|
|
2314
|
+
</Heading>
|
|
2315
|
+
<Cards
|
|
2316
|
+
count={-1}
|
|
2317
|
+
>
|
|
2318
|
+
Invalid cards - negative count
|
|
2319
|
+
</Cards>
|
|
2320
|
+
<Cards
|
|
2321
|
+
count="not a number"
|
|
2322
|
+
>
|
|
2323
|
+
Invalid cards - wrong type
|
|
2324
|
+
</Cards>
|
|
2325
|
+
</React.Fragment>,
|
|
2326
|
+
}
|
|
2327
|
+
`);
|
|
2328
|
+
});
|
|
2329
|
+
test('schema validation without errors', () => {
|
|
2330
|
+
const HeadingSchema = z.object({
|
|
2331
|
+
level: z.number().min(1).max(6),
|
|
2332
|
+
title: z.string().optional(),
|
|
2333
|
+
});
|
|
2334
|
+
const componentPropsSchema = {
|
|
2335
|
+
Heading: HeadingSchema,
|
|
2336
|
+
};
|
|
2337
|
+
const code = dedent `
|
|
2338
|
+
<Heading level={2} title="test">Valid heading</Heading>
|
|
2339
|
+
<Heading level={1}>Another valid heading</Heading>
|
|
2340
|
+
`;
|
|
2341
|
+
expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
|
|
2342
|
+
{
|
|
2343
|
+
"errors": [],
|
|
2344
|
+
"html": "<h1>Valid heading</h1><h1>Another valid heading</h1>",
|
|
2345
|
+
"result": <React.Fragment>
|
|
2346
|
+
<Heading
|
|
2347
|
+
level={2}
|
|
2348
|
+
title="test"
|
|
2349
|
+
>
|
|
2350
|
+
Valid heading
|
|
2351
|
+
</Heading>
|
|
2352
|
+
<Heading
|
|
2353
|
+
level={1}
|
|
2354
|
+
>
|
|
2355
|
+
Another valid heading
|
|
2356
|
+
</Heading>
|
|
2357
|
+
</React.Fragment>,
|
|
2358
|
+
}
|
|
2359
|
+
`);
|
|
2360
|
+
});
|
|
2361
|
+
test('component without schema should not be validated', () => {
|
|
2362
|
+
const HeadingSchema = z.object({
|
|
2363
|
+
level: z.number().min(1).max(6),
|
|
2364
|
+
});
|
|
2365
|
+
const componentPropsSchema = {
|
|
2366
|
+
Heading: HeadingSchema,
|
|
2367
|
+
};
|
|
2368
|
+
const code = dedent `
|
|
2369
|
+
<Heading level={2}>Valid heading with schema</Heading>
|
|
2370
|
+
<Cards invalidProp="anything">Cards without schema - should not be validated</Cards>
|
|
2371
|
+
`;
|
|
2372
|
+
expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
|
|
2373
|
+
{
|
|
2374
|
+
"errors": [],
|
|
2375
|
+
"html": "<h1>Valid heading with schema</h1><div>Cards without schema - should not be validated</div>",
|
|
2376
|
+
"result": <React.Fragment>
|
|
2377
|
+
<Heading
|
|
2378
|
+
level={2}
|
|
2379
|
+
>
|
|
2380
|
+
Valid heading with schema
|
|
2381
|
+
</Heading>
|
|
2382
|
+
<Cards
|
|
2383
|
+
invalidProp="anything"
|
|
2384
|
+
>
|
|
2385
|
+
Cards without schema - should not be validated
|
|
2386
|
+
</Cards>
|
|
2387
|
+
</React.Fragment>,
|
|
2388
|
+
}
|
|
2389
|
+
`);
|
|
2390
|
+
});
|
|
2391
|
+
test('validation error includes schema path', () => {
|
|
2392
|
+
const ComplexSchema = z.object({
|
|
2393
|
+
user: z.object({
|
|
2394
|
+
name: z.string(),
|
|
2395
|
+
age: z.number().min(0),
|
|
2396
|
+
}),
|
|
2397
|
+
settings: z.object({
|
|
2398
|
+
theme: z.enum(['light', 'dark']),
|
|
2399
|
+
}),
|
|
2400
|
+
});
|
|
2401
|
+
const componentPropsSchema = {
|
|
2402
|
+
Heading: ComplexSchema,
|
|
2403
|
+
};
|
|
2404
|
+
const code = dedent `
|
|
2405
|
+
<Heading user={{ name: "test", age: -1 }} settings={{ theme: "invalid" }}>Complex validation</Heading>
|
|
2406
|
+
`;
|
|
2407
|
+
expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
|
|
2408
|
+
{
|
|
2409
|
+
"errors": [
|
|
2410
|
+
{
|
|
2411
|
+
"line": 1,
|
|
2412
|
+
"message": "Expressions in jsx props are not supported (user={{ name: "test", age: -1 }})",
|
|
2413
|
+
},
|
|
2414
|
+
{
|
|
2415
|
+
"line": 1,
|
|
2416
|
+
"message": "Expressions in jsx props are not supported (settings={{ theme: "invalid" }})",
|
|
2417
|
+
},
|
|
2418
|
+
{
|
|
2419
|
+
"line": 1,
|
|
2420
|
+
"message": "Invalid props for component "Heading" at "user": Required",
|
|
2421
|
+
"schemaPath": "user",
|
|
2422
|
+
},
|
|
2423
|
+
{
|
|
2424
|
+
"line": 1,
|
|
2425
|
+
"message": "Invalid props for component "Heading" at "settings": Required",
|
|
2426
|
+
"schemaPath": "settings",
|
|
2427
|
+
},
|
|
2428
|
+
],
|
|
2429
|
+
"html": "<h1>Complex validation</h1>",
|
|
2430
|
+
"result": <React.Fragment>
|
|
2431
|
+
<Heading>
|
|
2432
|
+
Complex validation
|
|
2433
|
+
</Heading>
|
|
2434
|
+
</React.Fragment>,
|
|
2435
|
+
}
|
|
2436
|
+
`);
|
|
2437
|
+
});
|
|
2246
2438
|
//# sourceMappingURL=safe-mdx.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safe-mdx.test.js","sourceRoot":"","sources":["../src/safe-mdx.test.tsx"],"names":[],"mappings":";AAAA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAE,UAAU,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"safe-mdx.test.js","sourceRoot":"","sources":["../src/safe-mdx.test.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safe-mdx",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Render MDX in React without eval",
|
|
6
6
|
"repository": "https://github.com/holocron-hq/safe-mdx",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"react": "*"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
+
"@standard-schema/spec": "^1.0.0",
|
|
34
35
|
"collapse-white-space": "^2.1.0",
|
|
35
36
|
"html-to-jsx-transform": "^1.0.0",
|
|
36
37
|
"remark": "^15.0.1",
|
|
@@ -51,7 +52,8 @@
|
|
|
51
52
|
"react": "^19.1.0",
|
|
52
53
|
"react-dom": "^19.1.0",
|
|
53
54
|
"typescript": "5.8.3",
|
|
54
|
-
"vitest": "^3.1.3"
|
|
55
|
+
"vitest": "^3.1.3",
|
|
56
|
+
"zod": "^3.25.67"
|
|
55
57
|
},
|
|
56
58
|
"scripts": {
|
|
57
59
|
"build": "tsc",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { bench, describe } from 'vitest'
|
|
2
|
+
import { mdxParse } from './parse.js'
|
|
3
|
+
import { MdastToJsx } from './safe-mdx.js'
|
|
4
|
+
|
|
5
|
+
let longMdxContent = await fetch(
|
|
6
|
+
'https://raw.githubusercontent.com/colinhacks/zod/0a49fa39348b7c72b19ddedc3b0f879bd395304b/packages/docs/content/packages/v3.mdx',
|
|
7
|
+
).then((x) => x.text())
|
|
8
|
+
|
|
9
|
+
function Callout({ children }: { children: any }) {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
style={{
|
|
13
|
+
borderLeft: '4px solid #0070f3',
|
|
14
|
+
background: '#f0f8ff',
|
|
15
|
+
padding: '8px 16px',
|
|
16
|
+
margin: '16px 0',
|
|
17
|
+
}}
|
|
18
|
+
>
|
|
19
|
+
{children}
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const mdast = mdxParse(longMdxContent)
|
|
25
|
+
|
|
26
|
+
describe('safe-mdx performance benchmarks', () => {
|
|
27
|
+
bench('MdastToJsx class processing (long MDX)', () => {
|
|
28
|
+
const visitor = new MdastToJsx({
|
|
29
|
+
markdown: longMdxContent,
|
|
30
|
+
mdast,
|
|
31
|
+
components: { Callout },
|
|
32
|
+
})
|
|
33
|
+
visitor.run()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
bench('MdastToJsx with noop createElement (long MDX)', () => {
|
|
37
|
+
const noopCreateElement = () => null
|
|
38
|
+
const visitor = new MdastToJsx({
|
|
39
|
+
markdown: longMdxContent,
|
|
40
|
+
mdast,
|
|
41
|
+
components: { Callout },
|
|
42
|
+
createElement: noopCreateElement,
|
|
43
|
+
})
|
|
44
|
+
visitor.run()
|
|
45
|
+
})
|
|
46
|
+
})
|
package/src/safe-mdx.test.tsx
CHANGED
|
@@ -3,8 +3,9 @@ import { htmlToJsx } from 'html-to-jsx-transform'
|
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
5
5
|
import { expect, test } from 'vitest'
|
|
6
|
+
import { z } from 'zod'
|
|
6
7
|
import { mdxParse } from './parse.js'
|
|
7
|
-
import { MdastToJsx, mdastBfs } from './safe-mdx.js'
|
|
8
|
+
import { MdastToJsx, mdastBfs, type ComponentPropsSchema } from './safe-mdx.js'
|
|
8
9
|
import { completeJsxTags } from './streaming.js'
|
|
9
10
|
|
|
10
11
|
const components = {
|
|
@@ -16,9 +17,9 @@ const components = {
|
|
|
16
17
|
},
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
function render(code) {
|
|
20
|
+
function render(code, componentPropsSchema?: ComponentPropsSchema) {
|
|
20
21
|
const mdast = mdxParse(code)
|
|
21
|
-
const visitor = new MdastToJsx({ markdown: code, mdast, components })
|
|
22
|
+
const visitor = new MdastToJsx({ markdown: code, mdast, components, componentPropsSchema })
|
|
22
23
|
const result = visitor.run()
|
|
23
24
|
const html = renderToStaticMarkup(result)
|
|
24
25
|
// console.log(JSON.stringify(result, null, 2))
|
|
@@ -474,6 +475,7 @@ test('missing components are ignored', () => {
|
|
|
474
475
|
{
|
|
475
476
|
"errors": [
|
|
476
477
|
{
|
|
478
|
+
"line": 1,
|
|
477
479
|
"message": "Unsupported jsx component MissingComponent",
|
|
478
480
|
},
|
|
479
481
|
],
|
|
@@ -510,15 +512,19 @@ test('props parsing', () => {
|
|
|
510
512
|
{
|
|
511
513
|
"errors": [
|
|
512
514
|
{
|
|
515
|
+
"line": 7,
|
|
513
516
|
"message": "Expressions in jsx props are not supported (expression1={1 + 3})",
|
|
514
517
|
},
|
|
515
518
|
{
|
|
519
|
+
"line": 8,
|
|
516
520
|
"message": "Expressions in jsx props are not supported (expression2={Boolean(1)})",
|
|
517
521
|
},
|
|
518
522
|
{
|
|
523
|
+
"line": 9,
|
|
519
524
|
"message": "Expressions in jsx props are not supported (jsx={<SomeComponent />})",
|
|
520
525
|
},
|
|
521
526
|
{
|
|
527
|
+
"line": 13,
|
|
522
528
|
"message": "Expressions in jsx props are not supported (...{ spread: true })",
|
|
523
529
|
},
|
|
524
530
|
],
|
|
@@ -2288,3 +2294,206 @@ _No documentation_
|
|
|
2288
2294
|
</React.Fragment>
|
|
2289
2295
|
`)
|
|
2290
2296
|
})
|
|
2297
|
+
|
|
2298
|
+
test('component props schema validation with zod', () => {
|
|
2299
|
+
const HeadingSchema = z.object({
|
|
2300
|
+
level: z.number().min(1).max(6),
|
|
2301
|
+
title: z.string().optional(),
|
|
2302
|
+
})
|
|
2303
|
+
|
|
2304
|
+
const CardsSchema = z.object({
|
|
2305
|
+
count: z.number().positive(),
|
|
2306
|
+
variant: z.enum(['default', 'outline']).optional(),
|
|
2307
|
+
})
|
|
2308
|
+
|
|
2309
|
+
const componentPropsSchema: ComponentPropsSchema = {
|
|
2310
|
+
Heading: HeadingSchema,
|
|
2311
|
+
Cards: CardsSchema,
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
const code = dedent`
|
|
2315
|
+
<Heading level={2} title="test">Valid heading</Heading>
|
|
2316
|
+
|
|
2317
|
+
<Cards count={3} variant="outline">Valid cards</Cards>
|
|
2318
|
+
|
|
2319
|
+
<Heading level={10} title="test">Invalid heading - level too high</Heading>
|
|
2320
|
+
|
|
2321
|
+
<Cards count={-1}>Invalid cards - negative count</Cards>
|
|
2322
|
+
|
|
2323
|
+
<Cards count="not a number">Invalid cards - wrong type</Cards>
|
|
2324
|
+
`
|
|
2325
|
+
|
|
2326
|
+
expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
|
|
2327
|
+
{
|
|
2328
|
+
"errors": [
|
|
2329
|
+
{
|
|
2330
|
+
"line": 5,
|
|
2331
|
+
"message": "Invalid props for component "Heading" at "level": Number must be less than or equal to 6",
|
|
2332
|
+
"schemaPath": "level",
|
|
2333
|
+
},
|
|
2334
|
+
{
|
|
2335
|
+
"line": 7,
|
|
2336
|
+
"message": "Invalid props for component "Cards" at "count": Number must be greater than 0",
|
|
2337
|
+
"schemaPath": "count",
|
|
2338
|
+
},
|
|
2339
|
+
{
|
|
2340
|
+
"line": 9,
|
|
2341
|
+
"message": "Invalid props for component "Cards" at "count": Expected number, received string",
|
|
2342
|
+
"schemaPath": "count",
|
|
2343
|
+
},
|
|
2344
|
+
],
|
|
2345
|
+
"html": "<h1>Valid heading</h1><div>Valid cards</div><h1>Invalid heading - level too high</h1><div>Invalid cards - negative count</div><div>Invalid cards - wrong type</div>",
|
|
2346
|
+
"result": <React.Fragment>
|
|
2347
|
+
<Heading
|
|
2348
|
+
level={2}
|
|
2349
|
+
title="test"
|
|
2350
|
+
>
|
|
2351
|
+
Valid heading
|
|
2352
|
+
</Heading>
|
|
2353
|
+
<Cards
|
|
2354
|
+
count={3}
|
|
2355
|
+
variant="outline"
|
|
2356
|
+
>
|
|
2357
|
+
Valid cards
|
|
2358
|
+
</Cards>
|
|
2359
|
+
<Heading
|
|
2360
|
+
level={10}
|
|
2361
|
+
title="test"
|
|
2362
|
+
>
|
|
2363
|
+
Invalid heading - level too high
|
|
2364
|
+
</Heading>
|
|
2365
|
+
<Cards
|
|
2366
|
+
count={-1}
|
|
2367
|
+
>
|
|
2368
|
+
Invalid cards - negative count
|
|
2369
|
+
</Cards>
|
|
2370
|
+
<Cards
|
|
2371
|
+
count="not a number"
|
|
2372
|
+
>
|
|
2373
|
+
Invalid cards - wrong type
|
|
2374
|
+
</Cards>
|
|
2375
|
+
</React.Fragment>,
|
|
2376
|
+
}
|
|
2377
|
+
`)
|
|
2378
|
+
})
|
|
2379
|
+
|
|
2380
|
+
test('schema validation without errors', () => {
|
|
2381
|
+
const HeadingSchema = z.object({
|
|
2382
|
+
level: z.number().min(1).max(6),
|
|
2383
|
+
title: z.string().optional(),
|
|
2384
|
+
})
|
|
2385
|
+
|
|
2386
|
+
const componentPropsSchema: ComponentPropsSchema = {
|
|
2387
|
+
Heading: HeadingSchema,
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
const code = dedent`
|
|
2391
|
+
<Heading level={2} title="test">Valid heading</Heading>
|
|
2392
|
+
<Heading level={1}>Another valid heading</Heading>
|
|
2393
|
+
`
|
|
2394
|
+
|
|
2395
|
+
expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
|
|
2396
|
+
{
|
|
2397
|
+
"errors": [],
|
|
2398
|
+
"html": "<h1>Valid heading</h1><h1>Another valid heading</h1>",
|
|
2399
|
+
"result": <React.Fragment>
|
|
2400
|
+
<Heading
|
|
2401
|
+
level={2}
|
|
2402
|
+
title="test"
|
|
2403
|
+
>
|
|
2404
|
+
Valid heading
|
|
2405
|
+
</Heading>
|
|
2406
|
+
<Heading
|
|
2407
|
+
level={1}
|
|
2408
|
+
>
|
|
2409
|
+
Another valid heading
|
|
2410
|
+
</Heading>
|
|
2411
|
+
</React.Fragment>,
|
|
2412
|
+
}
|
|
2413
|
+
`)
|
|
2414
|
+
})
|
|
2415
|
+
|
|
2416
|
+
test('component without schema should not be validated', () => {
|
|
2417
|
+
const HeadingSchema = z.object({
|
|
2418
|
+
level: z.number().min(1).max(6),
|
|
2419
|
+
})
|
|
2420
|
+
|
|
2421
|
+
const componentPropsSchema: ComponentPropsSchema = {
|
|
2422
|
+
Heading: HeadingSchema,
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
const code = dedent`
|
|
2426
|
+
<Heading level={2}>Valid heading with schema</Heading>
|
|
2427
|
+
<Cards invalidProp="anything">Cards without schema - should not be validated</Cards>
|
|
2428
|
+
`
|
|
2429
|
+
|
|
2430
|
+
expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
|
|
2431
|
+
{
|
|
2432
|
+
"errors": [],
|
|
2433
|
+
"html": "<h1>Valid heading with schema</h1><div>Cards without schema - should not be validated</div>",
|
|
2434
|
+
"result": <React.Fragment>
|
|
2435
|
+
<Heading
|
|
2436
|
+
level={2}
|
|
2437
|
+
>
|
|
2438
|
+
Valid heading with schema
|
|
2439
|
+
</Heading>
|
|
2440
|
+
<Cards
|
|
2441
|
+
invalidProp="anything"
|
|
2442
|
+
>
|
|
2443
|
+
Cards without schema - should not be validated
|
|
2444
|
+
</Cards>
|
|
2445
|
+
</React.Fragment>,
|
|
2446
|
+
}
|
|
2447
|
+
`)
|
|
2448
|
+
})
|
|
2449
|
+
|
|
2450
|
+
test('validation error includes schema path', () => {
|
|
2451
|
+
const ComplexSchema = z.object({
|
|
2452
|
+
user: z.object({
|
|
2453
|
+
name: z.string(),
|
|
2454
|
+
age: z.number().min(0),
|
|
2455
|
+
}),
|
|
2456
|
+
settings: z.object({
|
|
2457
|
+
theme: z.enum(['light', 'dark']),
|
|
2458
|
+
}),
|
|
2459
|
+
})
|
|
2460
|
+
|
|
2461
|
+
const componentPropsSchema: ComponentPropsSchema = {
|
|
2462
|
+
Heading: ComplexSchema,
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
const code = dedent`
|
|
2466
|
+
<Heading user={{ name: "test", age: -1 }} settings={{ theme: "invalid" }}>Complex validation</Heading>
|
|
2467
|
+
`
|
|
2468
|
+
|
|
2469
|
+
expect(render(code, componentPropsSchema)).toMatchInlineSnapshot(`
|
|
2470
|
+
{
|
|
2471
|
+
"errors": [
|
|
2472
|
+
{
|
|
2473
|
+
"line": 1,
|
|
2474
|
+
"message": "Expressions in jsx props are not supported (user={{ name: "test", age: -1 }})",
|
|
2475
|
+
},
|
|
2476
|
+
{
|
|
2477
|
+
"line": 1,
|
|
2478
|
+
"message": "Expressions in jsx props are not supported (settings={{ theme: "invalid" }})",
|
|
2479
|
+
},
|
|
2480
|
+
{
|
|
2481
|
+
"line": 1,
|
|
2482
|
+
"message": "Invalid props for component "Heading" at "user": Required",
|
|
2483
|
+
"schemaPath": "user",
|
|
2484
|
+
},
|
|
2485
|
+
{
|
|
2486
|
+
"line": 1,
|
|
2487
|
+
"message": "Invalid props for component "Heading" at "settings": Required",
|
|
2488
|
+
"schemaPath": "settings",
|
|
2489
|
+
},
|
|
2490
|
+
],
|
|
2491
|
+
"html": "<h1>Complex validation</h1>",
|
|
2492
|
+
"result": <React.Fragment>
|
|
2493
|
+
<Heading>
|
|
2494
|
+
Complex validation
|
|
2495
|
+
</Heading>
|
|
2496
|
+
</React.Fragment>,
|
|
2497
|
+
}
|
|
2498
|
+
`)
|
|
2499
|
+
})
|