@c.a.f/testing 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.
- package/.build/__tests__/react/renderWithCAF.spec.d.ts +1 -0
- package/.build/__tests__/react/renderWithCAF.spec.js +53 -0
- package/.build/index.d.ts +1 -0
- package/.build/index.js +1 -0
- package/.build/src/core/IntegrationTestHelpers.d.ts +77 -0
- package/.build/src/core/IntegrationTestHelpers.js +78 -0
- package/.build/src/core/PlocTestHelpers.d.ts +133 -0
- package/.build/src/core/PlocTestHelpers.js +205 -0
- package/.build/src/core/PulseTestHelpers.d.ts +71 -0
- package/.build/src/core/PulseTestHelpers.js +106 -0
- package/.build/src/core/RepositoryTestHelpers.d.ts +48 -0
- package/.build/src/core/RepositoryTestHelpers.js +76 -0
- package/.build/src/core/RouteTestHelpers.d.ts +67 -0
- package/.build/src/core/RouteTestHelpers.js +94 -0
- package/.build/src/core/UseCaseTestHelpers.d.ts +100 -0
- package/.build/src/core/UseCaseTestHelpers.js +161 -0
- package/.build/src/core/index.d.ts +6 -0
- package/.build/src/core/index.js +6 -0
- package/.build/src/i18n/I18nTestHelpers.d.ts +76 -0
- package/.build/src/i18n/I18nTestHelpers.js +122 -0
- package/.build/src/i18n/index.d.ts +1 -0
- package/.build/src/i18n/index.js +1 -0
- package/.build/src/index.d.ts +5 -0
- package/.build/src/index.js +10 -0
- package/.build/src/permission/PermissionTestHelpers.d.ts +75 -0
- package/.build/src/permission/PermissionTestHelpers.js +121 -0
- package/.build/src/permission/index.d.ts +1 -0
- package/.build/src/permission/index.js +1 -0
- package/.build/src/react/createTestPloc.d.ts +19 -0
- package/.build/src/react/createTestPloc.js +21 -0
- package/.build/src/react/index.d.ts +12 -0
- package/.build/src/react/index.js +12 -0
- package/.build/src/react/mockUseCase.d.ts +36 -0
- package/.build/src/react/mockUseCase.js +44 -0
- package/.build/src/react/renderWithCAF.d.ts +31 -0
- package/.build/src/react/renderWithCAF.js +23 -0
- package/.build/src/react/waitForPlocState.d.ts +22 -0
- package/.build/src/react/waitForPlocState.js +24 -0
- package/.build/src/validation/ValidationTestHelpers.d.ts +66 -0
- package/.build/src/validation/ValidationTestHelpers.js +118 -0
- package/.build/src/validation/index.d.ts +1 -0
- package/.build/src/validation/index.js +1 -0
- package/.build/src/workflow/WorkflowTestHelpers.d.ts +75 -0
- package/.build/src/workflow/WorkflowTestHelpers.js +146 -0
- package/.build/src/workflow/index.d.ts +1 -0
- package/.build/src/workflow/index.js +1 -0
- package/.build/vitest.config.d.ts +7 -0
- package/.build/vitest.config.js +6 -0
- package/README.md +503 -0
- package/package.json +87 -0
package/README.md
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
# @c.a.f/testing
|
|
2
|
+
|
|
3
|
+
Testing utilities and helpers for CAF applications. Provides mocks, test helpers, and utilities for testing UseCase, Ploc, Pulse, Workflow, Permission, I18n, and Validation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @c.a.f/testing --save-dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Core Testing Utilities
|
|
14
|
+
|
|
15
|
+
#### Testing Ploc
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { createPlocTester, waitForStateChange } from '@c.a.f/testing/core';
|
|
19
|
+
import { Ploc } from '@c.a.f/core';
|
|
20
|
+
|
|
21
|
+
class CounterPloc extends Ploc<number> {
|
|
22
|
+
constructor() {
|
|
23
|
+
super(0);
|
|
24
|
+
}
|
|
25
|
+
increment() {
|
|
26
|
+
this.changeState(this.state + 1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('CounterPloc', () => {
|
|
31
|
+
it('tracks state changes', () => {
|
|
32
|
+
const ploc = new CounterPloc();
|
|
33
|
+
const tester = createPlocTester(ploc);
|
|
34
|
+
|
|
35
|
+
ploc.increment();
|
|
36
|
+
ploc.increment();
|
|
37
|
+
|
|
38
|
+
expect(tester.getStateChangeCount()).toBe(2);
|
|
39
|
+
expect(tester.getStateHistory()).toEqual([0, 1, 2]);
|
|
40
|
+
expect(tester.getLastStateChange()).toBe(2);
|
|
41
|
+
|
|
42
|
+
tester.cleanup();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('waits for state change', async () => {
|
|
46
|
+
const ploc = new CounterPloc();
|
|
47
|
+
|
|
48
|
+
setTimeout(() => ploc.increment(), 100);
|
|
49
|
+
const finalState = await waitForStateChange(ploc, (state) => state === 1);
|
|
50
|
+
|
|
51
|
+
expect(finalState).toBe(1);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### Testing Pulse
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { createPulseTester, waitForPulseValue } from '@c.a.f/testing/core';
|
|
60
|
+
import { pulse } from '@c.a.f/core';
|
|
61
|
+
|
|
62
|
+
describe('Pulse', () => {
|
|
63
|
+
it('tracks value changes', () => {
|
|
64
|
+
const count = pulse(0);
|
|
65
|
+
const tester = createPulseTester(count);
|
|
66
|
+
|
|
67
|
+
count.value = 5;
|
|
68
|
+
count.value = 10;
|
|
69
|
+
|
|
70
|
+
expect(tester.getValueChangeCount()).toBe(2);
|
|
71
|
+
expect(tester.getValueHistory()).toEqual([0, 5, 10]);
|
|
72
|
+
expect(tester.getLastValueChange()).toBe(10);
|
|
73
|
+
|
|
74
|
+
tester.cleanup();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('waits for pulse value', async () => {
|
|
78
|
+
const count = pulse(0);
|
|
79
|
+
|
|
80
|
+
setTimeout(() => { count.value = 5; }, 100);
|
|
81
|
+
const finalValue = await waitForPulseValue(count, (value) => value === 5);
|
|
82
|
+
|
|
83
|
+
expect(finalValue).toBe(5);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### Testing UseCase
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import {
|
|
92
|
+
createMockUseCase,
|
|
93
|
+
createMockUseCaseSuccess,
|
|
94
|
+
createMockUseCaseError,
|
|
95
|
+
createUseCaseTester,
|
|
96
|
+
createSuccessResult,
|
|
97
|
+
} from '@c.a.f/testing/core';
|
|
98
|
+
import { UseCase } from '@c.a.f/core';
|
|
99
|
+
|
|
100
|
+
describe('UseCase', () => {
|
|
101
|
+
it('creates and tests mock use case', async () => {
|
|
102
|
+
const mockUseCase = createMockUseCase<[], string>(() =>
|
|
103
|
+
createSuccessResult('result')
|
|
104
|
+
);
|
|
105
|
+
const tester = createUseCaseTester(mockUseCase);
|
|
106
|
+
|
|
107
|
+
const result = await tester.execute([]);
|
|
108
|
+
expect(result.data.value).toBe('result');
|
|
109
|
+
expect(result.error.value).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('uses success shortcut', async () => {
|
|
113
|
+
const useCase = createMockUseCaseSuccess([{ id: '1', name: 'John' }]);
|
|
114
|
+
const result = await useCase.execute();
|
|
115
|
+
expect(result.data.value).toEqual([{ id: '1', name: 'John' }]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('uses error shortcut', async () => {
|
|
119
|
+
const useCase = createMockUseCaseError(new Error('Failed'));
|
|
120
|
+
const result = await useCase.execute();
|
|
121
|
+
expect(result.error.value?.message).toBe('Failed');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('executes and gets data', async () => {
|
|
125
|
+
const mockUseCase = createMockUseCase<[number], number>((count) =>
|
|
126
|
+
createSuccessResult(count * 2)
|
|
127
|
+
);
|
|
128
|
+
const tester = createUseCaseTester(mockUseCase);
|
|
129
|
+
|
|
130
|
+
const data = await tester.executeAndGetData([5]);
|
|
131
|
+
expect(data).toBe(10);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Mock Ploc and state history (snapshot)
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import {
|
|
140
|
+
createMockPloc,
|
|
141
|
+
createPlocTester,
|
|
142
|
+
assertStateHistory,
|
|
143
|
+
getStateHistorySnapshot,
|
|
144
|
+
} from '@c.a.f/testing/core';
|
|
145
|
+
|
|
146
|
+
it('mock Ploc and state history', () => {
|
|
147
|
+
const ploc = createMockPloc({ count: 0 });
|
|
148
|
+
const tester = createPlocTester(ploc);
|
|
149
|
+
|
|
150
|
+
ploc.changeState({ count: 1 });
|
|
151
|
+
ploc.changeState({ count: 2 });
|
|
152
|
+
|
|
153
|
+
assertStateHistory(tester, [{ count: 0 }, { count: 1 }, { count: 2 }]);
|
|
154
|
+
expect(getStateHistorySnapshot(tester)).toMatchSnapshot();
|
|
155
|
+
tester.cleanup();
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Mock Repository (domain I*Repository)
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { createMockRepository, createMockRepositoryStub } from '@c.a.f/testing/core';
|
|
163
|
+
import type { IUserRepository } from '../domain';
|
|
164
|
+
|
|
165
|
+
it('mocks repository', async () => {
|
|
166
|
+
const repo = createMockRepository<IUserRepository>({
|
|
167
|
+
getUsers: async () => [{ id: '1', name: 'John' }],
|
|
168
|
+
getUserById: async (id) => ({ id, name: 'User ' + id }),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
expect(await repo.getUsers()).toHaveLength(1);
|
|
172
|
+
expect((await repo.getUserById('2'))?.name).toBe('User 2');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('stub and spy', async () => {
|
|
176
|
+
const stub = createMockRepositoryStub<IUserRepository>();
|
|
177
|
+
stub.getUsers = vi.fn().mockResolvedValue([]);
|
|
178
|
+
await stub.getUsers();
|
|
179
|
+
expect(stub.getUsers).toHaveBeenCalledTimes(1);
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### Integration test helpers
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import {
|
|
187
|
+
createPlocUseCaseContext,
|
|
188
|
+
flushPromises,
|
|
189
|
+
} from '@c.a.f/testing/core';
|
|
190
|
+
|
|
191
|
+
it('integration context', async () => {
|
|
192
|
+
const { ploc, useCase } = createPlocUseCaseContext(
|
|
193
|
+
{ items: [], loading: false },
|
|
194
|
+
[{ id: '1', name: 'Item' }]
|
|
195
|
+
);
|
|
196
|
+
// Use with CAFProvider or pass as props
|
|
197
|
+
expect(ploc.state.loading).toBe(false);
|
|
198
|
+
const result = await useCase.execute();
|
|
199
|
+
expect(result.data.value).toHaveLength(1);
|
|
200
|
+
await flushPromises();
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### React Testing Utilities (`@c.a.f/testing/react`)
|
|
205
|
+
|
|
206
|
+
Use these when testing React components that use `usePlocFromContext`, `useUseCaseFromContext`, or `usePloc` / `useUseCase` with context-provided instances.
|
|
207
|
+
|
|
208
|
+
**Requirements:** `react` and `@testing-library/react` (peer dependencies). Install in your app:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
npm install react @testing-library/react
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### renderWithCAF
|
|
215
|
+
|
|
216
|
+
Render a component wrapped in `CAFProvider` so Ploc/UseCase context is available:
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
import React from 'react';
|
|
220
|
+
import { screen } from '@testing-library/react';
|
|
221
|
+
import { renderWithCAF, createTestPloc, mockUseCase } from '@c.a.f/testing/react';
|
|
222
|
+
import { usePlocFromContext, usePloc } from '@c.a.f/infrastructure-react';
|
|
223
|
+
|
|
224
|
+
const Counter = () => {
|
|
225
|
+
const ploc = usePlocFromContext('counter');
|
|
226
|
+
if (!ploc) return null;
|
|
227
|
+
const [state] = usePloc(ploc);
|
|
228
|
+
return <span data-testid="count">{state.count}</span>;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
it('renders with CAF context', () => {
|
|
232
|
+
const ploc = createTestPloc({ count: 5 });
|
|
233
|
+
renderWithCAF(<Counter />, { plocs: { counter: ploc } });
|
|
234
|
+
expect(screen.getByTestId('count')).toHaveTextContent('5');
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### createTestPloc
|
|
239
|
+
|
|
240
|
+
Create a Ploc with controllable state (no business logic). Same idea as `createMockPloc` from core, for use in React tests:
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
const ploc = createTestPloc({ count: 0 });
|
|
244
|
+
renderWithCAF(<Counter />, { plocs: { counter: ploc } });
|
|
245
|
+
ploc.changeState({ count: 1 });
|
|
246
|
+
expect(screen.getByTestId('count')).toHaveTextContent('1');
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### waitForPlocState
|
|
250
|
+
|
|
251
|
+
Wait for a Ploc to reach a state matching a predicate (e.g. after an async update):
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
const ploc = createTestPloc({ loading: true, items: [] });
|
|
255
|
+
renderWithCAF(<List />, { plocs: { list: ploc } });
|
|
256
|
+
ploc.changeState({ loading: false, items: [{ id: '1' }] });
|
|
257
|
+
await waitForPlocState(ploc, (state) => !state.loading && state.items.length > 0);
|
|
258
|
+
expect(screen.getByText('1')).toBeInTheDocument();
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### mockUseCase
|
|
262
|
+
|
|
263
|
+
Create mock UseCases for context:
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
const submit = mockUseCase.success({ id: '1' });
|
|
267
|
+
const load = mockUseCase.error(new Error('Network error'));
|
|
268
|
+
const search = mockUseCase.async([{ id: '1' }], 50); // resolves after 50ms
|
|
269
|
+
|
|
270
|
+
renderWithCAF(<Form />, { useCases: { submit, load, search } });
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
- `mockUseCase.success(data)` — always returns success with `data`
|
|
274
|
+
- `mockUseCase.error(error)` — always returns `error`
|
|
275
|
+
- `mockUseCase.async(data, delayMs?)` — resolves with `data` after optional delay
|
|
276
|
+
- `mockUseCase.fn(implementation)` — custom implementation (same as `createMockUseCase` from core)
|
|
277
|
+
|
|
278
|
+
#### Testing RouteManager
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { createMockRouteRepository, createRouteManagerTester } from '@c.a.f/testing/core';
|
|
282
|
+
import { RouteManager } from '@c.a.f/core';
|
|
283
|
+
|
|
284
|
+
describe('RouteManager', () => {
|
|
285
|
+
it('tracks route changes', async () => {
|
|
286
|
+
const mockRepo = createMockRouteRepository();
|
|
287
|
+
const routeManager = new RouteManager(mockRepo);
|
|
288
|
+
const tester = createRouteManagerTester(routeManager);
|
|
289
|
+
|
|
290
|
+
await tester.changeRoute('/dashboard');
|
|
291
|
+
expect(tester.getCurrentRoute()).toBe('/dashboard');
|
|
292
|
+
expect(tester.getRouteHistory()).toContain('/dashboard');
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Workflow Testing Utilities
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { createWorkflowTester, waitForWorkflowState } from '@c.a.f/testing/workflow';
|
|
301
|
+
import { WorkflowManager, WorkflowDefinition } from '@c.a.f/workflow';
|
|
302
|
+
|
|
303
|
+
describe('Workflow', () => {
|
|
304
|
+
it('tracks workflow state changes', async () => {
|
|
305
|
+
const workflow = new WorkflowManager(definition);
|
|
306
|
+
const tester = createWorkflowTester(workflow);
|
|
307
|
+
|
|
308
|
+
await tester.dispatch('approve');
|
|
309
|
+
await waitForWorkflowState(workflow, 'approved');
|
|
310
|
+
|
|
311
|
+
expect(tester.getCurrentState()).toBe('approved');
|
|
312
|
+
expect(tester.getStateHistory().length).toBeGreaterThan(1);
|
|
313
|
+
|
|
314
|
+
tester.cleanup();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('waits for final state', async () => {
|
|
318
|
+
const workflow = new WorkflowManager(definition);
|
|
319
|
+
|
|
320
|
+
await workflow.dispatch('complete');
|
|
321
|
+
const finalState = await waitForFinalState(workflow);
|
|
322
|
+
|
|
323
|
+
expect(finalState.isFinal).toBe(true);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Permission Testing Utilities
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
import { createMockPermissionChecker, createPermissionTester } from '@c.a.f/testing/permission';
|
|
332
|
+
import { PermissionManager } from '@c.a.f/permission';
|
|
333
|
+
|
|
334
|
+
describe('Permission', () => {
|
|
335
|
+
it('tests permission checking', async () => {
|
|
336
|
+
const mockChecker = createMockPermissionChecker(['user.edit', 'post.create']);
|
|
337
|
+
const manager = new PermissionManager(mockChecker);
|
|
338
|
+
const tester = createPermissionTester(manager);
|
|
339
|
+
|
|
340
|
+
expect(await tester.hasPermission('user.edit')).toBe(true);
|
|
341
|
+
expect(await tester.hasPermission('user.delete')).toBe(false);
|
|
342
|
+
expect(await tester.hasAnyPermission(['user.edit', 'user.delete'])).toBe(true);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('modifies permissions dynamically', () => {
|
|
346
|
+
const mockChecker = createMockPermissionChecker(['user.view']);
|
|
347
|
+
|
|
348
|
+
mockChecker.addPermission('user.edit');
|
|
349
|
+
expect(mockChecker.check('user.edit').granted).toBe(true);
|
|
350
|
+
|
|
351
|
+
mockChecker.removePermission('user.view');
|
|
352
|
+
expect(mockChecker.check('user.view').granted).toBe(false);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### I18n Testing Utilities
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
import { createMockTranslator, createTranslationTester } from '@c.a.f/testing/i18n';
|
|
361
|
+
import { TranslationManager } from '@c.a.f/i18n';
|
|
362
|
+
|
|
363
|
+
describe('I18n', () => {
|
|
364
|
+
it('tests translation', () => {
|
|
365
|
+
const mockTranslator = createMockTranslator({
|
|
366
|
+
en: { greeting: 'Hello', welcome: 'Welcome {{name}}' },
|
|
367
|
+
fa: { greeting: 'سلام', welcome: 'خوش آمدید {{name}}' },
|
|
368
|
+
});
|
|
369
|
+
const manager = new TranslationManager(mockTranslator);
|
|
370
|
+
const tester = createTranslationTester(manager);
|
|
371
|
+
|
|
372
|
+
expect(tester.t('greeting')).toBe('Hello');
|
|
373
|
+
expect(tester.translateWithValues('welcome', { name: 'John' })).toBe('Welcome John');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('tests language switching', async () => {
|
|
377
|
+
const mockTranslator = createMockTranslator({
|
|
378
|
+
en: { greeting: 'Hello' },
|
|
379
|
+
fa: { greeting: 'سلام' },
|
|
380
|
+
});
|
|
381
|
+
const manager = new TranslationManager(mockTranslator);
|
|
382
|
+
const tester = createTranslationTester(manager);
|
|
383
|
+
|
|
384
|
+
expect(tester.getCurrentLanguage()).toBe('en');
|
|
385
|
+
await tester.changeLanguage('fa');
|
|
386
|
+
expect(tester.getCurrentLanguage()).toBe('fa');
|
|
387
|
+
expect(tester.t('greeting')).toBe('سلام');
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Validation Testing Utilities
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
import { createMockValidator, createValidationTester } from '@c.a.f/testing/validation';
|
|
396
|
+
|
|
397
|
+
describe('Validation', () => {
|
|
398
|
+
it('tests validation', async () => {
|
|
399
|
+
const mockValidator = createMockValidator((data: any) => {
|
|
400
|
+
return data.email && data.email.includes('@');
|
|
401
|
+
});
|
|
402
|
+
const tester = createValidationTester(mockValidator);
|
|
403
|
+
|
|
404
|
+
const successResult = await tester.expectSuccess({ email: 'test@example.com' });
|
|
405
|
+
expect(successResult.email).toBe('test@example.com');
|
|
406
|
+
|
|
407
|
+
const errors = await tester.expectFailure({ email: 'invalid' });
|
|
408
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Exports
|
|
414
|
+
|
|
415
|
+
### Core Testing (`@c.a.f/testing/core`)
|
|
416
|
+
|
|
417
|
+
- `PlocTester` — Tester for Ploc instances
|
|
418
|
+
- `createPlocTester` — Create a Ploc tester
|
|
419
|
+
- `createMockPloc` — Create a Ploc with controllable state (no logic)
|
|
420
|
+
- `MockPloc` — Concrete Ploc class for tests
|
|
421
|
+
- `waitForStateChange` — Wait for state change matching predicate
|
|
422
|
+
- `waitForStateChanges` — Wait for specific number of state changes
|
|
423
|
+
- `assertStateHistory` — Assert state history matches expected (snapshot-style)
|
|
424
|
+
- `getStateHistorySnapshot` / `getStateHistorySnapshotJson` — Snapshot state history
|
|
425
|
+
- `PulseTester` — Tester for Pulse instances
|
|
426
|
+
- `createPulseTester` — Create a Pulse tester
|
|
427
|
+
- `waitForPulseValue` — Wait for pulse value matching predicate
|
|
428
|
+
- `MockUseCase` — Mock UseCase implementation
|
|
429
|
+
- `createMockUseCase` — Create a mock UseCase
|
|
430
|
+
- `createMockUseCaseSuccess` — Mock UseCase that returns success with data
|
|
431
|
+
- `createMockUseCaseError` — Mock UseCase that returns an error
|
|
432
|
+
- `createMockUseCaseAsync` — Mock UseCase that resolves after optional delay
|
|
433
|
+
- `UseCaseTester` — Tester for UseCase instances
|
|
434
|
+
- `createUseCaseTester` — Create a UseCase tester
|
|
435
|
+
- `createSuccessResult` — Create successful RequestResult
|
|
436
|
+
- `createErrorResult` — Create failed RequestResult
|
|
437
|
+
- `createLoadingResult` — Create loading RequestResult
|
|
438
|
+
- `createMockRepository` — Generic stub for domain I*Repository interfaces
|
|
439
|
+
- `createMockRepositoryStub` — Empty repository stub (assign or spy methods)
|
|
440
|
+
- `flushPromises` — Resolve pending microtasks in integration tests
|
|
441
|
+
- `runWithFakeTimers` — Placeholder for running code with fake timers
|
|
442
|
+
- `createPlocUseCaseContext` — Minimal Ploc + UseCase context for integration tests
|
|
443
|
+
- `MockRouteRepository` — Mock RouteRepository implementation
|
|
444
|
+
- `createMockRouteRepository` — Create a mock RouteRepository
|
|
445
|
+
- `RouteManagerTester` — Tester for RouteManager instances
|
|
446
|
+
- `createRouteManagerTester` — Create a RouteManager tester
|
|
447
|
+
|
|
448
|
+
### Workflow Testing (`@c.a.f/testing/workflow`)
|
|
449
|
+
|
|
450
|
+
- `WorkflowTester` — Tester for WorkflowManager instances
|
|
451
|
+
- `createWorkflowTester` — Create a Workflow tester
|
|
452
|
+
- `waitForWorkflowState` — Wait for workflow to reach specific state
|
|
453
|
+
- `waitForFinalState` — Wait for workflow to reach final state
|
|
454
|
+
|
|
455
|
+
### Permission Testing (`@c.a.f/testing/permission`)
|
|
456
|
+
|
|
457
|
+
- `MockPermissionChecker` — Mock PermissionChecker implementation
|
|
458
|
+
- `createMockPermissionChecker` — Create a mock PermissionChecker
|
|
459
|
+
- `PermissionTester` — Tester for PermissionManager instances
|
|
460
|
+
- `createPermissionTester` — Create a Permission tester
|
|
461
|
+
|
|
462
|
+
### I18n Testing (`@c.a.f/testing/i18n`)
|
|
463
|
+
|
|
464
|
+
- `MockTranslator` — Mock Translator implementation
|
|
465
|
+
- `createMockTranslator` — Create a mock Translator
|
|
466
|
+
- `TranslationTester` — Tester for TranslationManager instances
|
|
467
|
+
- `createTranslationTester` — Create a Translation tester
|
|
468
|
+
|
|
469
|
+
### Validation Testing (`@c.a.f/testing/validation`)
|
|
470
|
+
|
|
471
|
+
- `MockValidator` — Mock Validator implementation
|
|
472
|
+
- `createMockValidator` — Create a mock Validator
|
|
473
|
+
- `ValidationTester` — Tester for Validator instances
|
|
474
|
+
- `createValidationTester` — Create a Validation tester
|
|
475
|
+
- `expectSuccess` — Validate and expect success
|
|
476
|
+
- `expectFailure` — Validate and expect failure
|
|
477
|
+
|
|
478
|
+
### React Testing (`@c.a.f/testing/react`)
|
|
479
|
+
|
|
480
|
+
- `renderWithCAF` — Render with CAFProvider (Ploc/UseCase context)
|
|
481
|
+
- `RenderWithCAFOptions` — Options for renderWithCAF (plocs, useCases, and RTL options)
|
|
482
|
+
- `createTestPloc` — Create a test Ploc with controllable state
|
|
483
|
+
- `waitForPlocState` — Wait for Ploc state to match a predicate
|
|
484
|
+
- `mockUseCase` — Mock UseCase helpers: `success`, `error`, `async`, `fn`
|
|
485
|
+
|
|
486
|
+
## Dependencies
|
|
487
|
+
|
|
488
|
+
- `@c.a.f/core` — Core primitives
|
|
489
|
+
- `@c.a.f/infrastructure-react` — React provider (for `@c.a.f/testing/react`)
|
|
490
|
+
- `@c.a.f/workflow` — Workflow package (for workflow testing utilities)
|
|
491
|
+
- `@c.a.f/permission` — Permission package (for permission testing utilities)
|
|
492
|
+
- `@c.a.f/i18n` — I18n package (for i18n testing utilities)
|
|
493
|
+
- `@c.a.f/validation` — Validation package (for validation testing utilities)
|
|
494
|
+
|
|
495
|
+
**React testing (`@c.a.f/testing/react`):** Peer dependencies `react` and `@testing-library/react` (optional; required only when using the react entry point).
|
|
496
|
+
|
|
497
|
+
## Dev Dependencies
|
|
498
|
+
|
|
499
|
+
- `vitest` — Testing framework (optional, works with any testing framework)
|
|
500
|
+
|
|
501
|
+
## License
|
|
502
|
+
|
|
503
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@c.a.f/testing",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Testing utilities and helpers for CAF applications. Provides mocks, test helpers, and utilities for testing UseCase, Ploc, Pulse, Workflow, Permission, I18n, and Validation.",
|
|
6
|
+
"keywords": ["clean-architecture-frontend", "clean-architecture", "caf", "testing", "test-utils", "mocks", "test-helpers"],
|
|
7
|
+
"author": "ali aslani <aliaslani.mm@gmail.com>",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": "https://github.com/ialiaslani/caf.git",
|
|
10
|
+
"main": "./.build/index.js",
|
|
11
|
+
"module": "./.build/index.js",
|
|
12
|
+
"types": "./.build/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./.build/index.d.ts",
|
|
16
|
+
"import": "./.build/index.js",
|
|
17
|
+
"node": "./.build/index.js",
|
|
18
|
+
"default": "./.build/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./core": {
|
|
21
|
+
"types": "./.build/core/index.d.ts",
|
|
22
|
+
"import": "./.build/core/index.js",
|
|
23
|
+
"node": "./.build/core/index.js",
|
|
24
|
+
"default": "./.build/core/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./workflow": {
|
|
27
|
+
"types": "./.build/workflow/index.d.ts",
|
|
28
|
+
"import": "./.build/workflow/index.js",
|
|
29
|
+
"node": "./.build/workflow/index.js",
|
|
30
|
+
"default": "./.build/workflow/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./permission": {
|
|
33
|
+
"types": "./.build/permission/index.d.ts",
|
|
34
|
+
"import": "./.build/permission/index.js",
|
|
35
|
+
"node": "./.build/permission/index.js",
|
|
36
|
+
"default": "./.build/permission/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./i18n": {
|
|
39
|
+
"types": "./.build/i18n/index.d.ts",
|
|
40
|
+
"import": "./.build/i18n/index.js",
|
|
41
|
+
"node": "./.build/i18n/index.js",
|
|
42
|
+
"default": "./.build/i18n/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./validation": {
|
|
45
|
+
"types": "./.build/validation/index.d.ts",
|
|
46
|
+
"import": "./.build/validation/index.js",
|
|
47
|
+
"node": "./.build/validation/index.js",
|
|
48
|
+
"default": "./.build/validation/index.js"
|
|
49
|
+
},
|
|
50
|
+
"./react": {
|
|
51
|
+
"types": "./.build/react/index.d.ts",
|
|
52
|
+
"import": "./.build/react/index.js",
|
|
53
|
+
"node": "./.build/react/index.js",
|
|
54
|
+
"default": "./.build/react/index.js"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"files": [".build"],
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsc --build",
|
|
60
|
+
"start": "tsc --watch",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:watch": "vitest",
|
|
63
|
+
"prepublishOnly": "npm run build"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"@c.a.f/core": "1.0.3",
|
|
67
|
+
"@c.a.f/infrastructure-react": "^1.0.4",
|
|
68
|
+
"@c.a.f/workflow": "1.0.1",
|
|
69
|
+
"@c.a.f/permission": "1.0.0",
|
|
70
|
+
"@c.a.f/i18n": "1.0.0",
|
|
71
|
+
"@c.a.f/validation": "1.0.3"
|
|
72
|
+
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"react": ">=16.8.0",
|
|
75
|
+
"@testing-library/react": ">=14.0.0"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"react": { "optional": true },
|
|
79
|
+
"@testing-library/react": { "optional": true }
|
|
80
|
+
},
|
|
81
|
+
"devDependencies": {
|
|
82
|
+
"@testing-library/react": "^16.0.0",
|
|
83
|
+
"happy-dom": "^15.11.7",
|
|
84
|
+
"react": "^18.2.0",
|
|
85
|
+
"vitest": "^2.1.0"
|
|
86
|
+
}
|
|
87
|
+
}
|