Skip to content

Commit caf51d1

Browse files
hi-ogawaclaude
andcommitted
test(rsc): add comprehensive server action transform tests
Port server action test cases from Waku's vite-plugin-rsc-transform-internals.test.ts to ensure @vitejs/plugin-rsc covers the same use cases as Waku adoption progresses. Test coverage includes: - Top-level 'use server' directives - Inline server actions (function declarations, arrow functions, anonymous functions) - Server actions in objects - Various function declaration patterns - Mixed server/client scenarios - Closure capture and variable hoisting All 8 new test cases pass alongside existing tests. Verified with both `pnpm test` and `pnpm tsc`. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 83a5741 commit caf51d1

File tree

1 file changed

+334
-0
lines changed

1 file changed

+334
-0
lines changed
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
import { parseAstAsync } from 'vite'
2+
import { describe, expect, test } from 'vitest'
3+
import { transformServerActionServer } from './server-action'
4+
import { debugSourceMap } from './test-utils'
5+
6+
describe('transformServerActionServer', () => {
7+
async function testTransform(
8+
input: string,
9+
options?: {
10+
rejectNonAsyncFunction?: boolean
11+
encode?: boolean
12+
},
13+
) {
14+
const ast = await parseAstAsync(input)
15+
const result = transformServerActionServer(input, ast, {
16+
runtime: (value, name) =>
17+
`__registerServerReference(${value}, "<id>", ${JSON.stringify(name)})`,
18+
rejectNonAsyncFunction: options?.rejectNonAsyncFunction,
19+
encode: options?.encode ? (v) => `__enc(${v})` : undefined,
20+
decode: options?.encode ? (v) => `__dec(${v})` : undefined,
21+
})
22+
23+
if (!('output' in result) || !result.output.hasChanged()) {
24+
return
25+
}
26+
27+
if (process.env['DEBUG_SOURCEMAP']) {
28+
await debugSourceMap(result.output)
29+
}
30+
31+
return result.output.toString()
32+
}
33+
34+
test('no transformation', async () => {
35+
const input = `
36+
export default function App() {
37+
return "Hello World";
38+
}
39+
`
40+
expect(await testTransform(input)).toBeUndefined()
41+
})
42+
43+
test('top-level use server', async () => {
44+
const input = `
45+
'use server';
46+
47+
const privateFunction = () => 'Secret';
48+
49+
export const log = async (mesg) => {
50+
console.log(mesg);
51+
};
52+
53+
export async function greet(name) {
54+
return 'Hello ' + name;
55+
}
56+
57+
export default async function() {
58+
return Date.now();
59+
}
60+
`
61+
expect(await testTransform(input)).toMatchInlineSnapshot(`
62+
"
63+
'use server';
64+
65+
const privateFunction = () => 'Secret';
66+
67+
let log = async (mesg) => {
68+
console.log(mesg);
69+
};
70+
71+
async function greet(name) {
72+
return 'Hello ' + name;
73+
}
74+
75+
const $$default = async function() {
76+
return Date.now();
77+
}
78+
log = /* #__PURE__ */ __registerServerReference(log, "<id>", "log");
79+
export { log };
80+
greet = /* #__PURE__ */ __registerServerReference(greet, "<id>", "greet");
81+
export { greet };
82+
;
83+
const $$wrap_$$default = /* #__PURE__ */ __registerServerReference($$default, "<id>", "default");
84+
export { $$wrap_$$default as default };
85+
"
86+
`)
87+
})
88+
89+
test('inline use server (function declaration)', async () => {
90+
const input = `
91+
export default function App() {
92+
const a = 'test';
93+
async function log(mesg) {
94+
'use server';
95+
console.log(mesg, a);
96+
}
97+
return log;
98+
}
99+
`
100+
expect(await testTransform(input)).toMatchInlineSnapshot(`
101+
"
102+
export default function App() {
103+
const a = 'test';
104+
const log = /* #__PURE__ */ __registerServerReference($$hoist_0_log, "<id>", "$$hoist_0_log").bind(null, a);
105+
return log;
106+
}
107+
108+
;export async function $$hoist_0_log(a, mesg) {
109+
'use server';
110+
console.log(mesg, a);
111+
};
112+
/* #__PURE__ */ Object.defineProperty($$hoist_0_log, "name", { value: "log" });
113+
"
114+
`)
115+
})
116+
117+
test('inline use server (const arrow function)', async () => {
118+
const input = `
119+
const now = Date.now();
120+
export default function App() {
121+
const log = async (mesg) => {
122+
'use server';
123+
console.log(mesg, now);
124+
};
125+
return log;
126+
}
127+
`
128+
expect(await testTransform(input)).toMatchInlineSnapshot(`
129+
"
130+
const now = Date.now();
131+
export default function App() {
132+
const log = /* #__PURE__ */ __registerServerReference($$hoist_0_log, "<id>", "$$hoist_0_log");
133+
return log;
134+
}
135+
136+
;export async function $$hoist_0_log(mesg) {
137+
'use server';
138+
console.log(mesg, now);
139+
};
140+
/* #__PURE__ */ Object.defineProperty($$hoist_0_log, "name", { value: "log" });
141+
"
142+
`)
143+
})
144+
145+
test('inline use server (anonymous arrow function)', async () => {
146+
const input = `
147+
const now = Date.now();
148+
export default function App() {
149+
return (mesg) => {
150+
'use server';
151+
console.log(mesg, now);
152+
};
153+
}
154+
`
155+
expect(await testTransform(input)).toMatchInlineSnapshot(`
156+
"
157+
const now = Date.now();
158+
export default function App() {
159+
return /* #__PURE__ */ __registerServerReference($$hoist_0_anonymous_server_function, "<id>", "$$hoist_0_anonymous_server_function");
160+
}
161+
162+
;export function $$hoist_0_anonymous_server_function(mesg) {
163+
'use server';
164+
console.log(mesg, now);
165+
};
166+
/* #__PURE__ */ Object.defineProperty($$hoist_0_anonymous_server_function, "name", { value: "anonymous_server_function" });
167+
"
168+
`)
169+
})
170+
171+
test('server action in object', async () => {
172+
const input = `
173+
const AI = {
174+
actions: {
175+
foo: async () => {
176+
'use server';
177+
return 0;
178+
},
179+
},
180+
};
181+
182+
export function ServerProvider() {
183+
return AI;
184+
}
185+
`
186+
expect(await testTransform(input)).toMatchInlineSnapshot(`
187+
"
188+
const AI = {
189+
actions: {
190+
foo: /* #__PURE__ */ __registerServerReference($$hoist_0_anonymous_server_function, "<id>", "$$hoist_0_anonymous_server_function"),
191+
},
192+
};
193+
194+
export function ServerProvider() {
195+
return AI;
196+
}
197+
198+
;export async function $$hoist_0_anonymous_server_function() {
199+
'use server';
200+
return 0;
201+
};
202+
/* #__PURE__ */ Object.defineProperty($$hoist_0_anonymous_server_function, "name", { value: "anonymous_server_function" });
203+
"
204+
`)
205+
})
206+
207+
test('inline use server (various patterns)', async () => {
208+
const input = `
209+
const actions = {
210+
log: async (mesg) => {
211+
'use server';
212+
console.log(mesg);
213+
},
214+
};
215+
216+
async function log2 (mesg) {
217+
'use server';
218+
console.log(mesg);
219+
}
220+
221+
const log3 = async function(mesg) {
222+
'use server';
223+
console.log(mesg);
224+
}
225+
226+
const log4 = async (mesg) => {
227+
'use server';
228+
console.log(mesg);
229+
};
230+
231+
const defaultFn = async function(mesg) {
232+
'use server';
233+
console.log(mesg);
234+
}
235+
236+
export default defaultFn;
237+
`
238+
expect(await testTransform(input)).toMatchInlineSnapshot(`
239+
"
240+
const actions = {
241+
log: /* #__PURE__ */ __registerServerReference($$hoist_0_anonymous_server_function, "<id>", "$$hoist_0_anonymous_server_function"),
242+
};
243+
244+
const log2 = /* #__PURE__ */ __registerServerReference($$hoist_1_log2, "<id>", "$$hoist_1_log2");
245+
246+
const log3 = /* #__PURE__ */ __registerServerReference($$hoist_2_log3, "<id>", "$$hoist_2_log3")
247+
248+
const log4 = /* #__PURE__ */ __registerServerReference($$hoist_3_log4, "<id>", "$$hoist_3_log4");
249+
250+
const defaultFn = /* #__PURE__ */ __registerServerReference($$hoist_4_defaultFn, "<id>", "$$hoist_4_defaultFn")
251+
252+
export default defaultFn;
253+
254+
;export async function $$hoist_0_anonymous_server_function(mesg) {
255+
'use server';
256+
console.log(mesg);
257+
};
258+
/* #__PURE__ */ Object.defineProperty($$hoist_0_anonymous_server_function, "name", { value: "anonymous_server_function" });
259+
260+
;export async function $$hoist_1_log2(mesg) {
261+
'use server';
262+
console.log(mesg);
263+
};
264+
/* #__PURE__ */ Object.defineProperty($$hoist_1_log2, "name", { value: "log2" });
265+
266+
;export async function $$hoist_2_log3(mesg) {
267+
'use server';
268+
console.log(mesg);
269+
};
270+
/* #__PURE__ */ Object.defineProperty($$hoist_2_log3, "name", { value: "log3" });
271+
272+
;export async function $$hoist_3_log4(mesg) {
273+
'use server';
274+
console.log(mesg);
275+
};
276+
/* #__PURE__ */ Object.defineProperty($$hoist_3_log4, "name", { value: "log4" });
277+
278+
;export async function $$hoist_4_defaultFn(mesg) {
279+
'use server';
280+
console.log(mesg);
281+
};
282+
/* #__PURE__ */ Object.defineProperty($$hoist_4_defaultFn, "name", { value: "defaultFn" });
283+
"
284+
`)
285+
})
286+
287+
test('top-level use server and inline use server', async () => {
288+
const input = `
289+
'use server';
290+
291+
async function innerAction(action, ...args) {
292+
'use server';
293+
return await action(...args);
294+
}
295+
296+
function wrapAction(action) {
297+
return innerAction.bind(null, action);
298+
}
299+
300+
export async function exportedAction() {
301+
'use server';
302+
return null;
303+
}
304+
305+
export default async () => null;
306+
`
307+
expect(await testTransform(input)).toMatchInlineSnapshot(`
308+
"
309+
'use server';
310+
311+
async function innerAction(action, ...args) {
312+
'use server';
313+
return await action(...args);
314+
}
315+
316+
function wrapAction(action) {
317+
return innerAction.bind(null, action);
318+
}
319+
320+
async function exportedAction() {
321+
'use server';
322+
return null;
323+
}
324+
325+
const $$default = async () => null;
326+
exportedAction = /* #__PURE__ */ __registerServerReference(exportedAction, "<id>", "exportedAction");
327+
export { exportedAction };
328+
;
329+
const $$wrap_$$default = /* #__PURE__ */ __registerServerReference($$default, "<id>", "default");
330+
export { $$wrap_$$default as default };
331+
"
332+
`)
333+
})
334+
})

0 commit comments

Comments
 (0)