@inglorious/vite-plugin-jsx 1.0.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.
@@ -0,0 +1,303 @@
1
+ import { describe, expect, it } from "vitest"
2
+
3
+ import { jsx } from "."
4
+
5
+ describe("vite-plugin-jsx", () => {
6
+ const plugin = jsx()
7
+
8
+ // Helper to run the transform
9
+ const transform = async (code, id = "test.tsx") => {
10
+ const result = await plugin.transform(code, id)
11
+ return result ? result.code : null
12
+ }
13
+
14
+ it("transforms basic JSX elements", async () => {
15
+ const code = `export const App = () => <div>Hello World</div>`
16
+ expect(await transform(code)).toMatchSnapshot()
17
+ })
18
+
19
+ it("injects the html import only when JSX is present", async () => {
20
+ const codeWithJsx = `const a = <div />`
21
+ const codeWithoutJsx = `const a = 10`
22
+
23
+ const resultWith = await transform(codeWithJsx)
24
+ const resultWithout = await transform(codeWithoutJsx)
25
+
26
+ expect(resultWith).toContain("@inglorious/web")
27
+ expect(resultWithout).not.toContain("@inglorious/web")
28
+ })
29
+
30
+ it("transforms className to class", async () => {
31
+ const code = `<div className="container">Content</div>`
32
+ expect(await transform(code)).toMatchSnapshot()
33
+ })
34
+
35
+ it("handles event listeners (@event syntax)", async () => {
36
+ const code = `<button onClick={handleClick} onMouseOver={() => {}}>Click</button>`
37
+ expect(await transform(code)).toMatchSnapshot()
38
+ })
39
+
40
+ it("handles boolean attributes", async () => {
41
+ const code = `<input disabled checked readonly />`
42
+ expect(await transform(code)).toMatchSnapshot()
43
+ })
44
+
45
+ it("distinguishes between properties (.) and attributes", async () => {
46
+ const code = `
47
+ const App = () => (
48
+ <my-element
49
+ id={id}
50
+ class={cls}
51
+ aria-label={label}
52
+ data-test={test}
53
+ someProp={value}
54
+ complexData={obj}
55
+ />
56
+ )
57
+ `
58
+ // Expect: id, class, aria-label, data-test to be attributes
59
+ // Expect: .someProp, .complexData to be properties
60
+ expect(await transform(code)).toMatchSnapshot()
61
+ })
62
+
63
+ it("handles fragments", async () => {
64
+ const code = `
65
+ const List = () => (
66
+ <>
67
+ <li>A</li>
68
+ <li>B</li>
69
+ </>
70
+ )
71
+ `
72
+ expect(await transform(code)).toMatchSnapshot()
73
+ })
74
+
75
+ it("handles nested expressions and elements", async () => {
76
+ const code = `
77
+ const App = () => <div>{show ? <span>Visible</span> : null}</div>
78
+ `
79
+ expect(await transform(code)).toMatchSnapshot()
80
+ })
81
+
82
+ it("transforms Array.map to repeat directive", async () => {
83
+ const code = `
84
+ const List = ({ items }) => (
85
+ <ul>
86
+ {items.map(item => <li>{item}</li>)}
87
+ </ul>
88
+ )
89
+ `
90
+ expect(await transform(code)).toMatchSnapshot()
91
+ })
92
+
93
+ it("transforms Array.map with key to keyed repeat directive", async () => {
94
+ const code = `
95
+ const List = ({ items }) => (
96
+ <ul>
97
+ {items.map(item => <li key={item.id}>{item.name}</li>)}
98
+ </ul>
99
+ )
100
+ `
101
+ expect(await transform(code)).toMatchSnapshot()
102
+ })
103
+
104
+ it("transforms ternary operators to when directive", async () => {
105
+ const code = `
106
+ const App = ({ isLoggedIn }) => (
107
+ <div>{isLoggedIn ? <User /> : <Login />}</div>
108
+ )
109
+ `
110
+ expect(await transform(code)).toMatchSnapshot()
111
+ })
112
+
113
+ it("transforms logical AND to when directive", async () => {
114
+ const code = `
115
+ const App = ({ show }) => (
116
+ <div>{show && <Modal />}</div>
117
+ )
118
+ `
119
+ expect(await transform(code)).toMatchSnapshot()
120
+ })
121
+
122
+ it("merges imports if @inglorious/web is already imported", async () => {
123
+ const code = `
124
+ import { html } from "@inglorious/web"
125
+ const App = ({ show }) => <div>{show ? <A /> : <B />}</div>
126
+ `
127
+ const result = await transform(code)
128
+
129
+ // Verify only one import statement exists
130
+ const importCount = (result.match(/from "@inglorious\/web"/g) || []).length
131
+ expect(importCount).toBe(1)
132
+ expect(result).toMatchSnapshot()
133
+ })
134
+
135
+ it("throws error for missing event handler expression", async () => {
136
+ const code = `<button onClick />`
137
+ await expect(transform(code)).rejects.toThrow(
138
+ "Event handler onClick must be an expression",
139
+ )
140
+ })
141
+
142
+ it("throws error for string literal event handler", async () => {
143
+ const code = `<button onClick="handleClick" />`
144
+ await expect(transform(code)).rejects.toThrow(
145
+ "Event handler onClick must be an expression",
146
+ )
147
+ })
148
+
149
+ it("does not generate closing tag for void elements", async () => {
150
+ const code = `<img src="image.png" />`
151
+ expect(await transform(code)).toMatchSnapshot()
152
+ })
153
+
154
+ it("handles SVG self-closing tags correctly", async () => {
155
+ const code = `
156
+ const Icon = () => (
157
+ <svg>
158
+ <path d="M0 0h10v10H0z" />
159
+ <circle cx="5" cy="5" r="5" />
160
+ </svg>
161
+ )
162
+ `
163
+ expect(await transform(code)).toMatchSnapshot()
164
+ })
165
+
166
+ it("expands self-closing non-void HTML tags", async () => {
167
+ const code = `<div className="empty" />`
168
+ expect(await transform(code)).toMatchSnapshot()
169
+ })
170
+
171
+ it("ignores empty expressions and comments", async () => {
172
+ const code = `
173
+ const App = () => (
174
+ <div>
175
+ {/* This is a comment */}
176
+ {}
177
+ <span>Content</span>
178
+ </div>
179
+ )
180
+ `
181
+ expect(await transform(code)).toMatchSnapshot()
182
+ })
183
+
184
+ it("transforms capitalized components to api.render calls", async () => {
185
+ const code = `
186
+ export const app = {
187
+ render(api) {
188
+ return <><Form id="f1" title="My Form" /><List items={[]} /><Footer /></>
189
+ },
190
+ }
191
+ `
192
+ expect(await transform(code)).toMatchSnapshot()
193
+ })
194
+
195
+ it("injects api argument into render function if missing when using components", async () => {
196
+ const code = `
197
+ export const app = {
198
+ render() {
199
+ return <Form />
200
+ },
201
+ }
202
+ `
203
+ expect(await transform(code)).toMatchSnapshot()
204
+ })
205
+
206
+ it("moves api parameter to the end if it is not the last one", async () => {
207
+ const code = `
208
+ export const app = {
209
+ render(api, entity) {
210
+ return <Form />
211
+ },
212
+ }
213
+ `
214
+ expect(await transform(code)).toMatchSnapshot()
215
+ })
216
+
217
+ it("does not change parameters if api is already the last argument", async () => {
218
+ const code = `
219
+ export const app = {
220
+ render(entity, api) {
221
+ return <Form />
222
+ },
223
+ }
224
+ `
225
+ expect(await transform(code)).toMatchSnapshot()
226
+ })
227
+
228
+ it("moves api parameter to the end when it is in the middle", async () => {
229
+ const code = `
230
+ export const app = {
231
+ render(entity, api, options) {
232
+ return <Form />
233
+ },
234
+ }
235
+ `
236
+ expect(await transform(code)).toMatchSnapshot()
237
+ })
238
+
239
+ it("does not inject api if no capitalized component is used", async () => {
240
+ const code = `
241
+ export const app = {
242
+ render(entity) {
243
+ return <div>{entity.name}</div>
244
+ },
245
+ }
246
+ `
247
+ expect(await transform(code)).toMatchSnapshot()
248
+ })
249
+
250
+ it("does not inject api if component is not inside a 'render' method", async () => {
251
+ const code = `
252
+ export const app = {
253
+ notRender() {
254
+ return <Form />
255
+ },
256
+ }
257
+ `
258
+ expect(await transform(code)).toMatchSnapshot()
259
+ })
260
+
261
+ it("throws an error when trying to inject api with a rest parameter present", async () => {
262
+ const code = `
263
+ export const app = {
264
+ render(entity, ...args) {
265
+ return <Form />
266
+ },
267
+ }
268
+ `
269
+ await expect(transform(code)).rejects.toThrow(
270
+ "Cannot inject 'api' parameter because of a rest element.",
271
+ )
272
+ })
273
+
274
+ it("injects api argument when component is nested inside a Fragment", async () => {
275
+ const code = `
276
+ export const app = {
277
+ render() {
278
+ return (
279
+ <>
280
+ <Form />
281
+ </>
282
+ )
283
+ },
284
+ }
285
+ `
286
+ expect(await transform(code)).toMatchSnapshot()
287
+ })
288
+
289
+ it("injects api argument when component is nested inside standard elements", async () => {
290
+ const code = `
291
+ export const app = {
292
+ render() {
293
+ return (
294
+ <div>
295
+ <Form />
296
+ </div>
297
+ )
298
+ },
299
+ }
300
+ `
301
+ expect(await transform(code)).toMatchSnapshot()
302
+ })
303
+ })