Skip to content

Commit 8624beb

Browse files
Varixowmertens
authored andcommitted
test: add loader handler unit tests
1 parent 34d222a commit 8624beb

File tree

6 files changed

+1022
-101
lines changed

6 files changed

+1022
-101
lines changed

packages/qwik-router/src/middleware/request-handler/handlers/action-handler.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1+
import { _serialize, type ValueOrPromise } from '@qwik.dev/core/internal';
12
import type {
23
ActionInternal,
34
JSONObject,
45
RequestEvent,
56
RequestHandler,
67
} from '../../../runtime/src/types';
7-
import { runValidators } from './loader-handler';
88
import { getRequestActions, getRequestMode, type RequestEventInternal } from '../request-event';
99
import { measure, verifySerializable } from '../resolve-request-handlers';
1010
import { IsQAction, QActionId } from '../user-response';
11-
import { _serialize, _UNINITIALIZED, type ValueOrPromise } from '@qwik.dev/core/internal';
11+
import { runValidators } from './validator-utils';
1212

1313
export function actionHandler(routeActions: ActionInternal[]): RequestHandler {
1414
return async (requestEvent: RequestEvent) => {
@@ -58,7 +58,7 @@ export function actionHandler(routeActions: ActionInternal[]): RequestHandler {
5858
await executeAction(action, actions, requestEv, isDev);
5959

6060
if (requestEv.request.headers.get('accept')?.includes('application/json')) {
61-
// only return the action data if the client accepts json, otherwise return the html page
61+
// only return the action data if the client accepts json, otherwise it will return the html page (for forms)
6262
const data = await _serialize([actions[actionId]]);
6363
requestEv.headers.set('Content-Type', 'application/json; charset=utf-8');
6464
requestEv.send(200, data);

packages/qwik-router/src/middleware/request-handler/handlers/action-handler.unit.ts

Lines changed: 46 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { describe, it, expect, vi, beforeEach, afterEach, Mock, type Mocked } from 'vitest';
2-
import { actionHandler } from './action-handler';
1+
import type { QRL } from 'packages/qwik/public';
2+
import { afterEach, beforeEach, describe, expect, it, Mock, vi, type Mocked } from 'vitest';
33
import type { ActionInternal, ActionStore } from '../../../runtime/src/types';
44
import type { RequestEventInternal } from '../request-event';
5-
import type { QwikSerializer } from '../types';
5+
import { getRequestActions, getRequestMode } from '../request-event';
6+
import { measure, verifySerializable } from '../resolve-request-handlers';
67
import { IsQAction, QActionId } from '../user-response';
7-
import { RequestEvQwikSerializer } from '../request-event';
8-
import type { QRL } from 'packages/qwik/public';
8+
import { actionHandler } from './action-handler';
9+
import { runValidators } from './validator-utils';
10+
import { _serialize } from '@qwik.dev/core/internal';
911

1012
// Mock dependencies
11-
vi.mock('./loader-handler', () => ({
13+
vi.mock('./validator-utils', () => ({
1214
runValidators: vi.fn(),
1315
}));
1416

@@ -23,14 +25,32 @@ vi.mock('../request-event', () => ({
2325
RequestEvQwikSerializer: Symbol('RequestEvQwikSerializer'),
2426
}));
2527

26-
const { runValidators } = await import('./loader-handler');
27-
const { measure, verifySerializable } = await import('../resolve-request-handlers');
28-
const { getRequestActions, getRequestMode } = await import('../request-event');
28+
function createMockAction(id: string, hash: string): Mocked<ActionInternal> {
29+
const mockActionFunction = (): Mocked<ActionStore<unknown, unknown>> => ({
30+
actionPath: `?action=${id}`,
31+
isRunning: false,
32+
status: undefined,
33+
value: undefined,
34+
formData: undefined,
35+
submit: vi.fn() as any,
36+
submitted: false,
37+
});
38+
39+
return {
40+
__brand: 'server_action' as const,
41+
__id: id,
42+
__qrl: {
43+
call: vi.fn(),
44+
getHash: vi.fn().mockReturnValue(hash),
45+
} as unknown as Mocked<QRL<(form: any, event: any) => any>>,
46+
__validators: [],
47+
...mockActionFunction,
48+
} as unknown as Mocked<ActionInternal>;
49+
}
2950

3051
describe('actionHandler', () => {
3152
let mockRequestEvent: Mocked<RequestEventInternal>;
3253
let mockAction: Mocked<ActionInternal>;
33-
let mockQwikSerializer: Mocked<QwikSerializer>;
3454
let mockActions: Record<string, any>;
3555
let consoleSpy: any;
3656

@@ -41,42 +61,11 @@ describe('actionHandler', () => {
4161
// Reset all mocks
4262
vi.clearAllMocks();
4363

44-
// Mock console.warn
4564
consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
4665

47-
// Create mock action
48-
const mockActionFunction = (): Mocked<ActionStore<unknown, unknown>> => ({
49-
actionPath: `?action=${mockActionId}`,
50-
isRunning: false,
51-
status: undefined,
52-
value: undefined,
53-
formData: undefined,
54-
submit: vi.fn() as any,
55-
submitted: false,
56-
});
66+
mockAction = createMockAction(mockActionId, mockActionHash);
5767

58-
mockAction = {
59-
__brand: 'server_action' as const,
60-
__id: mockActionId,
61-
__qrl: {
62-
call: vi.fn(),
63-
getHash: vi.fn().mockReturnValue(mockActionHash),
64-
} as unknown as Mocked<QRL<(form: any, event: any) => any>>,
65-
__validators: [],
66-
...mockActionFunction,
67-
} as unknown as Mocked<ActionInternal>;
68-
69-
// Create mock serializer
70-
mockQwikSerializer = {
71-
_serialize: vi.fn(),
72-
_deserialize: vi.fn(),
73-
_verifySerializable: vi.fn(),
74-
} as Mocked<QwikSerializer>;
75-
76-
// Create mock actions record
7768
mockActions = {};
78-
79-
// Create mock request event
8069
mockRequestEvent = {
8170
sharedMap: new Map(),
8271
headersSent: false,
@@ -123,9 +112,8 @@ describe('actionHandler', () => {
123112
} as unknown as Mocked<RequestEventInternal>;
124113

125114
// Set up default mocks
126-
(getRequestActions as Mock).mockReturnValue(mockActions);
127-
(getRequestMode as Mock).mockReturnValue('dev');
128-
mockRequestEvent[RequestEvQwikSerializer] = mockQwikSerializer;
115+
vi.mocked(getRequestActions).mockReturnValue(mockActions);
116+
vi.mocked(getRequestMode).mockReturnValue('dev');
129117
});
130118

131119
afterEach(() => {
@@ -241,33 +229,30 @@ describe('actionHandler', () => {
241229

242230
const data = { test: 'data' };
243231

244-
(mockRequestEvent.parseBody as Mock).mockResolvedValue(data);
232+
vi.mocked(mockRequestEvent.parseBody).mockResolvedValue(data);
245233
(runValidators as Mock).mockResolvedValue({
246234
success: true,
247235
data,
248236
});
249237

250-
(mockQwikSerializer._serialize as Mock).mockResolvedValue(data);
251-
252238
const handler = actionHandler([mockAction]);
253239

254240
await handler(mockRequestEvent);
255241

256-
expect(mockRequestEvent.send).toBeCalledWith(200, data);
242+
expect(mockRequestEvent.send).toBeCalledWith(200, await _serialize([undefined]));
257243
});
258244
});
259245

260246
describe('when action is found and executed successfully', () => {
261247
beforeEach(() => {
262248
mockRequestEvent.sharedMap.set(IsQAction, true);
263249
mockRequestEvent.sharedMap.set(QActionId, mockActionId);
264-
(mockRequestEvent.parseBody as Mock).mockResolvedValue({ test: 'data' });
265-
(runValidators as Mock).mockResolvedValue({
250+
vi.mocked(mockRequestEvent.parseBody).mockResolvedValue({ test: 'data' });
251+
vi.mocked(runValidators).mockResolvedValue({
266252
success: true,
267253
data: { test: 'data' },
268254
});
269-
(mockAction.__qrl.call as Mock).mockResolvedValue({ result: 'success' });
270-
(mockQwikSerializer._serialize as Mock).mockResolvedValue('serialized-data');
255+
vi.mocked(mockAction.__qrl.call).mockResolvedValue({ result: 'success' });
271256
});
272257

273258
it('should execute action and return serialized data', async () => {
@@ -286,29 +271,29 @@ describe('actionHandler', () => {
286271
{ test: 'data' },
287272
mockRequestEvent
288273
);
289-
expect(mockQwikSerializer._serialize).toHaveBeenCalledWith([{ result: 'success' }]);
290274
expect(mockRequestEvent.headers.set).toHaveBeenCalledWith(
291275
'Content-Type',
292276
'application/json; charset=utf-8'
293277
);
294-
expect(mockRequestEvent.send).toHaveBeenCalledWith(200, 'serialized-data');
278+
expect(mockRequestEvent.send).toHaveBeenCalledWith(
279+
200,
280+
await _serialize([{ result: 'success' }])
281+
);
295282
});
296283

297284
it('should measure execution time in dev mode', async () => {
285+
vi.mocked(getRequestMode).mockReturnValue('dev');
286+
298287
const handler = actionHandler([mockAction]);
299288

300289
await handler(mockRequestEvent);
301290

302291
expect(measure).toHaveBeenCalledWith(mockRequestEvent, mockActionHash, expect.any(Function));
303-
expect(verifySerializable).toHaveBeenCalledWith(
304-
mockQwikSerializer,
305-
{ result: 'success' },
306-
mockAction.__qrl
307-
);
292+
expect(verifySerializable).toHaveBeenCalledWith({ result: 'success' }, mockAction.__qrl);
308293
});
309294

310295
it('should not measure execution time in production mode', async () => {
311-
(getRequestMode as Mock).mockReturnValue('prod');
296+
vi.mocked(getRequestMode).mockReturnValue('server');
312297

313298
const handler = actionHandler([mockAction]);
314299

@@ -325,7 +310,6 @@ describe('actionHandler', () => {
325310

326311
await handler(mockRequestEvent);
327312

328-
expect(mockQwikSerializer._serialize).not.toHaveBeenCalled();
329313
expect(mockRequestEvent.send).not.toHaveBeenCalled();
330314
});
331315
});
@@ -473,7 +457,6 @@ describe('actionHandler', () => {
473457
data: { test: 'data' },
474458
});
475459
(mockAction.__qrl.call as Mock).mockResolvedValue({ result: 'success' });
476-
(mockQwikSerializer._serialize as Mock).mockResolvedValue('serialized-data');
477460

478461
const handler = actionHandler([mockAction]);
479462

packages/qwik-router/src/middleware/request-handler/handlers/loader-handler.ts

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import qwikRouterConfig from '@qwik-router-config';
22
import { _serialize, _UNINITIALIZED } from '@qwik.dev/core/internal';
3-
import type {
4-
DataValidator,
5-
LoaderInternal,
6-
RequestHandler,
7-
ValidatorReturn,
8-
} from '../../../runtime/src/types';
3+
import type { LoaderInternal, RequestHandler } from '../../../runtime/src/types';
94
import { getPathnameForDynamicRoute } from '../../../utils/pathname';
105
import {
116
getRequestLoaders,
@@ -16,6 +11,7 @@ import {
1611
import { measure, verifySerializable } from '../resolve-request-handlers';
1712
import type { RequestEvent } from '../types';
1813
import { IsQLoader, IsQLoaderData, QLoaderId } from '../user-response';
14+
import { runValidators } from './validator-utils';
1915

2016
export function loadersMiddleware(routeLoaders: LoaderInternal[]): RequestHandler {
2117
return async (requestEvent: RequestEvent) => {
@@ -132,6 +128,7 @@ export async function executeLoader(
132128
isDev: boolean
133129
) {
134130
const loaderId = loader.__id;
131+
135132
loaders[loaderId] = runValidators(
136133
requestEv,
137134
loader.__validators,
@@ -166,32 +163,3 @@ export async function executeLoader(
166163
loadersSerializationStrategy.set(loaderId, loader.__serializationStrategy);
167164
return loaders[loaderId];
168165
}
169-
170-
export async function runValidators(
171-
requestEv: RequestEvent,
172-
validators: DataValidator[] | undefined,
173-
data: unknown,
174-
isDev: boolean
175-
) {
176-
let lastResult: ValidatorReturn = {
177-
success: true,
178-
data,
179-
};
180-
if (validators) {
181-
for (const validator of validators) {
182-
if (isDev) {
183-
lastResult = await measure(requestEv, `validator$`, () =>
184-
validator.validate(requestEv, data)
185-
);
186-
} else {
187-
lastResult = await validator.validate(requestEv, data);
188-
}
189-
if (!lastResult.success) {
190-
return lastResult;
191-
} else {
192-
data = lastResult.data;
193-
}
194-
}
195-
}
196-
return lastResult;
197-
}

0 commit comments

Comments
 (0)