@sigil-dev/compiler 0.9.2 → 0.9.3
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 +162 -162
- package/index.ts +4 -4
- package/jsr.json +8 -8
- package/package.json +1 -1
- package/src/babel/handlers/dom/derived.ts +62 -62
- package/src/babel/handlers/dom/effect.ts +15 -15
- package/src/babel/handlers/dom/globals.ts +105 -105
- package/src/babel/handlers/dom/state.ts +107 -104
- package/src/babel/handlers/ssr/derived.ts +27 -27
- package/src/babel/handlers/ssr/state.ts +11 -11
- package/src/babel/index.ts +594 -589
- package/src/babel/jsx/anchor-mount.ts +389 -389
- package/src/babel/jsx/children.ts +124 -124
- package/src/babel/jsx/element.ts +831 -831
- package/src/babel/jsx/fragment.ts +282 -282
- package/src/babel/jsx/index.ts +2 -2
- package/src/babel/jsx/keyed-list.ts +278 -278
- package/src/babel/jsx/ssr.ts +319 -319
- package/src/babel/jsx/text-node.ts +192 -192
- package/src/babel/jsx/utils.ts +179 -179
- package/src/babel/util/bind.ts +139 -139
- package/src/babel/util/css.ts +151 -151
- package/src/babel/util/dead-code.ts +28 -28
- package/src/babel/util/helpers.ts +46 -46
- package/src/babel/util/magic.ts +11 -11
- package/src/bun-plugin.ts +92 -90
- package/src/vite/index.ts +58 -58
- package/test/async-components.test.ts +24 -24
- package/test/components.test.ts +129 -129
- package/test/containsSignal.test.ts +288 -288
- package/test/fragments.test.ts +90 -90
- package/test/helpers/transform.ts +45 -48
- package/test/hydration.test.ts +147 -147
- package/test/jsx.test.ts +197 -197
- package/test/keyed-lists.test.ts +88 -88
- package/test/reactivity.test.ts +225 -225
- package/test/scoped-css.test.ts +129 -129
- package/test/ssr.test.ts +130 -130
- package/test/tree-shaking.test.ts +78 -78
- package/tsconfig.json +30 -30
package/src/vite/index.ts
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
import { transformSync } from "@babel/core";
|
|
2
|
-
import type { Plugin } from "vite";
|
|
3
|
-
import sigilPlugin from "../babel/index.ts";
|
|
4
|
-
import { computeHash, scopeCSS } from "../babel/util/css.ts";
|
|
5
|
-
|
|
6
|
-
const STYLE_RE = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
7
|
-
|
|
8
|
-
export function sigil(options?: {
|
|
9
|
-
hydrate?: boolean;
|
|
10
|
-
styleNonce?: string;
|
|
11
|
-
}): Plugin {
|
|
12
|
-
// Track injected CSS per component to avoid duplicates
|
|
13
|
-
const injected = new Set<string>();
|
|
14
|
-
const styleNonce = options?.styleNonce ?? "";
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
name: "sigil",
|
|
18
|
-
transform(code, id) {
|
|
19
|
-
if (!id.match(/\.[jt]sx$/)) return;
|
|
20
|
-
|
|
21
|
-
// 1. Extract <style> from raw source BEFORE Babel
|
|
22
|
-
let scopedCSS = "";
|
|
23
|
-
const codeWithoutStyle = code.replace(STYLE_RE, (_, cssContent) => {
|
|
24
|
-
const hash = computeHash(id);
|
|
25
|
-
scopedCSS = scopeCSS(cssContent.trim(), hash);
|
|
26
|
-
return ""; // Remove <style> from source
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// 2. Transform JSX with Babel, passing hash + mode
|
|
30
|
-
const hash = computeHash(id);
|
|
31
|
-
const mode = options?.hydrate ? ("hydrate" as const) : undefined;
|
|
32
|
-
const res = transformSync(codeWithoutStyle, {
|
|
33
|
-
plugins: ["@babel/plugin-syntax-jsx", [sigilPlugin, { hash, mode }]],
|
|
34
|
-
filename: id,
|
|
35
|
-
});
|
|
36
|
-
if (!res) return;
|
|
37
|
-
|
|
38
|
-
// 3. Inject scoped CSS into <head> (once per component)
|
|
39
|
-
let cssInjection = "";
|
|
40
|
-
if (scopedCSS && !injected.has(id)) {
|
|
41
|
-
injected.add(id);
|
|
42
|
-
const nonceAttr = styleNonce ? ` nonce="${styleNonce}"` : "";
|
|
43
|
-
cssInjection = `
|
|
44
|
-
if (typeof document !== 'undefined' && !document.getElementById('sigil-${hash}')) {
|
|
45
|
-
const __style = document.createElement('style');
|
|
46
|
-
__style.id = 'sigil-${hash}';${nonceAttr ? `\n __style.setAttribute('nonce', '${styleNonce}');` : ""}
|
|
47
|
-
__style.textContent = ${JSON.stringify(scopedCSS)};
|
|
48
|
-
document.head.appendChild(__style);
|
|
49
|
-
}`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
code: cssInjection + (res.code ?? ""),
|
|
54
|
-
map: res.map as any,
|
|
55
|
-
};
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
}
|
|
1
|
+
import { transformSync } from "@babel/core";
|
|
2
|
+
import type { Plugin } from "vite";
|
|
3
|
+
import sigilPlugin from "../babel/index.ts";
|
|
4
|
+
import { computeHash, scopeCSS } from "../babel/util/css.ts";
|
|
5
|
+
|
|
6
|
+
const STYLE_RE = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
7
|
+
|
|
8
|
+
export function sigil(options?: {
|
|
9
|
+
hydrate?: boolean;
|
|
10
|
+
styleNonce?: string;
|
|
11
|
+
}): Plugin {
|
|
12
|
+
// Track injected CSS per component to avoid duplicates
|
|
13
|
+
const injected = new Set<string>();
|
|
14
|
+
const styleNonce = options?.styleNonce ?? "";
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
name: "sigil",
|
|
18
|
+
transform(code, id) {
|
|
19
|
+
if (!id.match(/\.[jt]sx$/)) return;
|
|
20
|
+
|
|
21
|
+
// 1. Extract <style> from raw source BEFORE Babel
|
|
22
|
+
let scopedCSS = "";
|
|
23
|
+
const codeWithoutStyle = code.replace(STYLE_RE, (_, cssContent) => {
|
|
24
|
+
const hash = computeHash(id);
|
|
25
|
+
scopedCSS = scopeCSS(cssContent.trim(), hash);
|
|
26
|
+
return ""; // Remove <style> from source
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 2. Transform JSX with Babel, passing hash + mode
|
|
30
|
+
const hash = computeHash(id);
|
|
31
|
+
const mode = options?.hydrate ? ("hydrate" as const) : undefined;
|
|
32
|
+
const res = transformSync(codeWithoutStyle, {
|
|
33
|
+
plugins: ["@babel/plugin-syntax-jsx", [sigilPlugin, { hash, mode }]],
|
|
34
|
+
filename: id,
|
|
35
|
+
});
|
|
36
|
+
if (!res) return;
|
|
37
|
+
|
|
38
|
+
// 3. Inject scoped CSS into <head> (once per component)
|
|
39
|
+
let cssInjection = "";
|
|
40
|
+
if (scopedCSS && !injected.has(id)) {
|
|
41
|
+
injected.add(id);
|
|
42
|
+
const nonceAttr = styleNonce ? ` nonce="${styleNonce}"` : "";
|
|
43
|
+
cssInjection = `
|
|
44
|
+
if (typeof document !== 'undefined' && !document.getElementById('sigil-${hash}')) {
|
|
45
|
+
const __style = document.createElement('style');
|
|
46
|
+
__style.id = 'sigil-${hash}';${nonceAttr ? `\n __style.setAttribute('nonce', '${styleNonce}');` : ""}
|
|
47
|
+
__style.textContent = ${JSON.stringify(scopedCSS)};
|
|
48
|
+
document.head.appendChild(__style);
|
|
49
|
+
}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
code: cssInjection + (res.code ?? ""),
|
|
54
|
+
map: res.map as any,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
import { transformAsync, transformSSR } from "./helpers/transform.ts";
|
|
3
3
|
|
|
4
4
|
describe("Async Components", () => {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
test("async default export wraps in __asyncBoundary in DOM mode", () => {
|
|
6
|
+
const out = transformAsync(`
|
|
7
7
|
export async function Page({ data }) {
|
|
8
8
|
return <div>{data}</div>;
|
|
9
9
|
}
|
|
10
10
|
export default Page;
|
|
11
11
|
`);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
expect(out).toContain("__asyncBoundary");
|
|
13
|
+
expect(out).toContain("__Page");
|
|
14
|
+
});
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
test("loading export passed to __asyncBoundary", () => {
|
|
17
|
+
const out = transformAsync(`
|
|
18
18
|
export async function Page({ data }) {
|
|
19
19
|
return <div>{data}</div>;
|
|
20
20
|
}
|
|
@@ -23,37 +23,37 @@ describe("Async Components", () => {
|
|
|
23
23
|
return <div />;
|
|
24
24
|
}
|
|
25
25
|
`);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
expect(out).toContain("loading");
|
|
27
|
+
expect(out).not.toContain("undefined, undefined");
|
|
28
|
+
});
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
test("no loading or error yields undefined args", () => {
|
|
31
|
+
const out = transformAsync(`
|
|
32
32
|
export async function Page({ data }) {
|
|
33
33
|
return <div>{data}</div>;
|
|
34
34
|
}
|
|
35
35
|
export default Page;
|
|
36
36
|
`);
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
expect(out).toContain("undefined, undefined");
|
|
38
|
+
});
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
test("SSR mode leaves async function untouched", () => {
|
|
41
|
+
const out = transformSSR(`
|
|
42
42
|
export async function Page({ data }) {
|
|
43
43
|
return <div>{data}</div>;
|
|
44
44
|
}
|
|
45
45
|
export default Page;
|
|
46
46
|
`);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
expect(out).not.toContain("__asyncBoundary");
|
|
48
|
+
expect(out).not.toContain("__Page");
|
|
49
|
+
});
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
test("non-async default is unchanged", () => {
|
|
52
|
+
const out = transformAsync(`
|
|
53
53
|
export default function Page({ data }) {
|
|
54
54
|
return <div>{data}</div>;
|
|
55
55
|
}
|
|
56
56
|
`);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
});
|
|
57
|
+
expect(out).not.toContain("__asyncBoundary");
|
|
58
|
+
});
|
|
59
|
+
});
|
package/test/components.test.ts
CHANGED
|
@@ -1,129 +1,129 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { transform } from "./helpers/transform.ts";
|
|
3
|
-
|
|
4
|
-
describe("Components", () => {
|
|
5
|
-
test("component call compiles to function invocation", () => {
|
|
6
|
-
const result = transform(`const el = <Greeting />;`);
|
|
7
|
-
expect(result).toContain("Greeting(");
|
|
8
|
-
expect(result).not.toContain('document.createElement("Greeting")');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
test("component with empty props", () => {
|
|
12
|
-
const result = transform(`const el = <Greeting />;`);
|
|
13
|
-
expect(result).toContain("Greeting({})");
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test("component with static props", () => {
|
|
17
|
-
const result = transform(`const el = <Greeting name="World" />;`);
|
|
18
|
-
expect(result).toContain('name: "World"');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("component with dynamic props", () => {
|
|
22
|
-
const result = transform(`
|
|
23
|
-
let userName = $state("World");
|
|
24
|
-
const el = <Greeting name={userName} />;
|
|
25
|
-
`);
|
|
26
|
-
expect(result).toContain("userName()");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("component with event handler prop", () => {
|
|
30
|
-
const result = transform(`const el = <Button onClick={() => {}} />;`);
|
|
31
|
-
expect(result).toContain("onClick:");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("nested component in native element", () => {
|
|
35
|
-
const result = transform(`
|
|
36
|
-
const el = <div><Greeting /></div>;
|
|
37
|
-
`);
|
|
38
|
-
expect(result).toContain('document.createElement("div")');
|
|
39
|
-
expect(result).toContain("Greeting(");
|
|
40
|
-
expect(result).toContain(".append(");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("component return value is used", () => {
|
|
44
|
-
const result = transform(`const el = <Greeting />;`);
|
|
45
|
-
// The component call should be assigned to a variable
|
|
46
|
-
expect(result).toMatch(/_el\d+ = Greeting\(/);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("native element tags are lowercase", () => {
|
|
50
|
-
const result = transform(`const el = <div />;`);
|
|
51
|
-
expect(result).toContain('document.createElement("div")');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("custom native-like tags with lowercase are native", () => {
|
|
55
|
-
const result = transform(`const el = <my-component />;`);
|
|
56
|
-
expect(result).toContain('document.createElement("my-component")');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("$state inside component function", () => {
|
|
60
|
-
const result = transform(`
|
|
61
|
-
function Counter() {
|
|
62
|
-
let count = $state(0);
|
|
63
|
-
count++;
|
|
64
|
-
return count;
|
|
65
|
-
}
|
|
66
|
-
`);
|
|
67
|
-
expect(result).toContain("createSignal(0)");
|
|
68
|
-
expect(result).toContain("count.set(count() + 1)");
|
|
69
|
-
expect(result).toContain("count()");
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe("Component Children", () => {
|
|
74
|
-
test("text children passed as string", () => {
|
|
75
|
-
const result = transform(`const el = <Button>Click me</Button>;`);
|
|
76
|
-
expect(result).toContain('children: "Click me"');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("element children passed in array", () => {
|
|
80
|
-
const result = transform(`const el = <Card><h2>Title</h2></Card>;`);
|
|
81
|
-
expect(result).toMatch(/children:\s*\[/);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("multiple children passed in array", () => {
|
|
85
|
-
const result = transform(
|
|
86
|
-
`const el = <Card><h2>Title</h2><p>Body</p></Card>;`,
|
|
87
|
-
);
|
|
88
|
-
expect(result).toMatch(/children:\s*\[/);
|
|
89
|
-
expect(result).toContain('createElement("h2")');
|
|
90
|
-
expect(result).toContain('createElement("p")');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("mixed text and element children", () => {
|
|
94
|
-
const result = transform(`const el = <Card>Hello<p>World</p></Card>;`);
|
|
95
|
-
expect(result).toContain('"Hello"');
|
|
96
|
-
expect(result).toContain('createElement("p")');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("dynamic expression children", () => {
|
|
100
|
-
const result = transform(`
|
|
101
|
-
let count = $state(0);
|
|
102
|
-
const el = <Display>{count}</Display>;
|
|
103
|
-
`);
|
|
104
|
-
expect(result).toContain("count()");
|
|
105
|
-
expect(result).toMatch(/children:\s*\[/);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("component children with reactivity", () => {
|
|
109
|
-
const result = transform(`
|
|
110
|
-
let count = $state(0);
|
|
111
|
-
const el = <Card><span>{count}</span></Card>;
|
|
112
|
-
`);
|
|
113
|
-
expect(result).toContain("createEffect(");
|
|
114
|
-
expect(result).toMatch(/children:\s*\[/);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("no children prop when empty", () => {
|
|
118
|
-
const result = transform(`const el = <Greeting />;`);
|
|
119
|
-
expect(result).not.toContain("children:");
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("component with props and children", () => {
|
|
123
|
-
const result = transform(
|
|
124
|
-
`const el = <Card title="Hello"><p>Content</p></Card>;`,
|
|
125
|
-
);
|
|
126
|
-
expect(result).toContain('title: "Hello"');
|
|
127
|
-
expect(result).toMatch(/children:\s*\[/);
|
|
128
|
-
});
|
|
129
|
-
});
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { transform } from "./helpers/transform.ts";
|
|
3
|
+
|
|
4
|
+
describe("Components", () => {
|
|
5
|
+
test("component call compiles to function invocation", () => {
|
|
6
|
+
const result = transform(`const el = <Greeting />;`);
|
|
7
|
+
expect(result).toContain("Greeting(");
|
|
8
|
+
expect(result).not.toContain('document.createElement("Greeting")');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("component with empty props", () => {
|
|
12
|
+
const result = transform(`const el = <Greeting />;`);
|
|
13
|
+
expect(result).toContain("Greeting({})");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("component with static props", () => {
|
|
17
|
+
const result = transform(`const el = <Greeting name="World" />;`);
|
|
18
|
+
expect(result).toContain('name: "World"');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("component with dynamic props", () => {
|
|
22
|
+
const result = transform(`
|
|
23
|
+
let userName = $state("World");
|
|
24
|
+
const el = <Greeting name={userName} />;
|
|
25
|
+
`);
|
|
26
|
+
expect(result).toContain("userName()");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("component with event handler prop", () => {
|
|
30
|
+
const result = transform(`const el = <Button onClick={() => {}} />;`);
|
|
31
|
+
expect(result).toContain("onClick:");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("nested component in native element", () => {
|
|
35
|
+
const result = transform(`
|
|
36
|
+
const el = <div><Greeting /></div>;
|
|
37
|
+
`);
|
|
38
|
+
expect(result).toContain('document.createElement("div")');
|
|
39
|
+
expect(result).toContain("Greeting(");
|
|
40
|
+
expect(result).toContain(".append(");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("component return value is used", () => {
|
|
44
|
+
const result = transform(`const el = <Greeting />;`);
|
|
45
|
+
// The component call should be assigned to a variable
|
|
46
|
+
expect(result).toMatch(/_el\d+ = Greeting\(/);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("native element tags are lowercase", () => {
|
|
50
|
+
const result = transform(`const el = <div />;`);
|
|
51
|
+
expect(result).toContain('document.createElement("div")');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("custom native-like tags with lowercase are native", () => {
|
|
55
|
+
const result = transform(`const el = <my-component />;`);
|
|
56
|
+
expect(result).toContain('document.createElement("my-component")');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("$state inside component function", () => {
|
|
60
|
+
const result = transform(`
|
|
61
|
+
function Counter() {
|
|
62
|
+
let count = $state(0);
|
|
63
|
+
count++;
|
|
64
|
+
return count;
|
|
65
|
+
}
|
|
66
|
+
`);
|
|
67
|
+
expect(result).toContain("createSignal(0)");
|
|
68
|
+
expect(result).toContain("count.set(count() + 1)");
|
|
69
|
+
expect(result).toContain("count()");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("Component Children", () => {
|
|
74
|
+
test("text children passed as string", () => {
|
|
75
|
+
const result = transform(`const el = <Button>Click me</Button>;`);
|
|
76
|
+
expect(result).toContain('children: "Click me"');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("element children passed in array", () => {
|
|
80
|
+
const result = transform(`const el = <Card><h2>Title</h2></Card>;`);
|
|
81
|
+
expect(result).toMatch(/children:\s*\[/);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("multiple children passed in array", () => {
|
|
85
|
+
const result = transform(
|
|
86
|
+
`const el = <Card><h2>Title</h2><p>Body</p></Card>;`,
|
|
87
|
+
);
|
|
88
|
+
expect(result).toMatch(/children:\s*\[/);
|
|
89
|
+
expect(result).toContain('createElement("h2")');
|
|
90
|
+
expect(result).toContain('createElement("p")');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("mixed text and element children", () => {
|
|
94
|
+
const result = transform(`const el = <Card>Hello<p>World</p></Card>;`);
|
|
95
|
+
expect(result).toContain('"Hello"');
|
|
96
|
+
expect(result).toContain('createElement("p")');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("dynamic expression children", () => {
|
|
100
|
+
const result = transform(`
|
|
101
|
+
let count = $state(0);
|
|
102
|
+
const el = <Display>{count}</Display>;
|
|
103
|
+
`);
|
|
104
|
+
expect(result).toContain("count()");
|
|
105
|
+
expect(result).toMatch(/children:\s*\[/);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("component children with reactivity", () => {
|
|
109
|
+
const result = transform(`
|
|
110
|
+
let count = $state(0);
|
|
111
|
+
const el = <Card><span>{count}</span></Card>;
|
|
112
|
+
`);
|
|
113
|
+
expect(result).toContain("createEffect(");
|
|
114
|
+
expect(result).toMatch(/children:\s*\[/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("no children prop when empty", () => {
|
|
118
|
+
const result = transform(`const el = <Greeting />;`);
|
|
119
|
+
expect(result).not.toContain("children:");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("component with props and children", () => {
|
|
123
|
+
const result = transform(
|
|
124
|
+
`const el = <Card title="Hello"><p>Content</p></Card>;`,
|
|
125
|
+
);
|
|
126
|
+
expect(result).toContain('title: "Hello"');
|
|
127
|
+
expect(result).toMatch(/children:\s*\[/);
|
|
128
|
+
});
|
|
129
|
+
});
|