@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
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test helpers for I18n.
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for testing translation managers and translators.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { createMockTranslator, createTranslationTester } from '@c.a.f/testing/i18n';
|
|
9
|
+
* import { TranslationManager } from '@c.a.f/i18n';
|
|
10
|
+
*
|
|
11
|
+
* const mockTranslator = createMockTranslator({
|
|
12
|
+
* en: { 'greeting': 'Hello', 'welcome': 'Welcome {{name}}' },
|
|
13
|
+
* fa: { 'greeting': 'سلام', 'welcome': 'خوش آمدید {{name}}' },
|
|
14
|
+
* });
|
|
15
|
+
* const manager = new TranslationManager(mockTranslator);
|
|
16
|
+
* const tester = createTranslationTester(manager);
|
|
17
|
+
*
|
|
18
|
+
* expect(tester.t('greeting')).toBe('Hello');
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import type { ITranslator, TranslationManager } from '@c.a.f/i18n';
|
|
22
|
+
/**
|
|
23
|
+
* Mock Translator implementation for testing.
|
|
24
|
+
*/
|
|
25
|
+
export declare class MockTranslator implements ITranslator {
|
|
26
|
+
private translations;
|
|
27
|
+
private currentLanguage;
|
|
28
|
+
constructor(translations?: Record<string, Record<string, string>>, currentLanguage?: string);
|
|
29
|
+
translate(key: string, options?: Record<string, unknown>): string;
|
|
30
|
+
getCurrentLanguage(): string;
|
|
31
|
+
changeLanguage(language: string): Promise<void>;
|
|
32
|
+
exists(key: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Add translations (for testing).
|
|
35
|
+
*/
|
|
36
|
+
addTranslations(language: string, translations: Record<string, string>): void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a mock Translator.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createMockTranslator(translations?: Record<string, Record<string, string>>, initialLanguage?: string): MockTranslator;
|
|
42
|
+
/**
|
|
43
|
+
* Translation tester utility.
|
|
44
|
+
*/
|
|
45
|
+
export declare class TranslationTester {
|
|
46
|
+
readonly manager: TranslationManager;
|
|
47
|
+
constructor(manager: TranslationManager);
|
|
48
|
+
/**
|
|
49
|
+
* Translate a key.
|
|
50
|
+
*/
|
|
51
|
+
t(key: string, options?: Record<string, unknown>): string;
|
|
52
|
+
/**
|
|
53
|
+
* Translate with values.
|
|
54
|
+
*/
|
|
55
|
+
translateWithValues(key: string, values: Record<string, unknown>): string;
|
|
56
|
+
/**
|
|
57
|
+
* Translate plural.
|
|
58
|
+
*/
|
|
59
|
+
translatePlural(key: string, count: number, options?: Record<string, unknown>): string;
|
|
60
|
+
/**
|
|
61
|
+
* Get current language.
|
|
62
|
+
*/
|
|
63
|
+
getCurrentLanguage(): string;
|
|
64
|
+
/**
|
|
65
|
+
* Change language.
|
|
66
|
+
*/
|
|
67
|
+
changeLanguage(language: string): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Check if key exists.
|
|
70
|
+
*/
|
|
71
|
+
hasKey(key: string): boolean;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create a Translation tester instance.
|
|
75
|
+
*/
|
|
76
|
+
export declare function createTranslationTester(manager: TranslationManager): TranslationTester;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test helpers for I18n.
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for testing translation managers and translators.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { createMockTranslator, createTranslationTester } from '@c.a.f/testing/i18n';
|
|
9
|
+
* import { TranslationManager } from '@c.a.f/i18n';
|
|
10
|
+
*
|
|
11
|
+
* const mockTranslator = createMockTranslator({
|
|
12
|
+
* en: { 'greeting': 'Hello', 'welcome': 'Welcome {{name}}' },
|
|
13
|
+
* fa: { 'greeting': 'سلام', 'welcome': 'خوش آمدید {{name}}' },
|
|
14
|
+
* });
|
|
15
|
+
* const manager = new TranslationManager(mockTranslator);
|
|
16
|
+
* const tester = createTranslationTester(manager);
|
|
17
|
+
*
|
|
18
|
+
* expect(tester.t('greeting')).toBe('Hello');
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Mock Translator implementation for testing.
|
|
23
|
+
*/
|
|
24
|
+
export class MockTranslator {
|
|
25
|
+
translations;
|
|
26
|
+
currentLanguage;
|
|
27
|
+
constructor(translations = {}, currentLanguage = 'en') {
|
|
28
|
+
this.translations = translations;
|
|
29
|
+
this.currentLanguage = currentLanguage;
|
|
30
|
+
}
|
|
31
|
+
translate(key, options) {
|
|
32
|
+
const langTranslations = this.translations[this.currentLanguage] || {};
|
|
33
|
+
let text = langTranslations[key] || key;
|
|
34
|
+
// Simple interpolation
|
|
35
|
+
if (options) {
|
|
36
|
+
Object.keys(options).forEach((k) => {
|
|
37
|
+
if (k !== 'ns' && k !== 'defaultValue' && k !== 'count' && k !== 'returnObjects') {
|
|
38
|
+
const value = String(options[k]);
|
|
39
|
+
text = text.replace(new RegExp(`{{${k}}}`, 'g'), value);
|
|
40
|
+
text = text.replace(new RegExp(`\\{${k}\\}`, 'g'), value);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return text;
|
|
45
|
+
}
|
|
46
|
+
getCurrentLanguage() {
|
|
47
|
+
return this.currentLanguage;
|
|
48
|
+
}
|
|
49
|
+
async changeLanguage(language) {
|
|
50
|
+
this.currentLanguage = language;
|
|
51
|
+
}
|
|
52
|
+
exists(key) {
|
|
53
|
+
const langTranslations = this.translations[this.currentLanguage] || {};
|
|
54
|
+
return key in langTranslations;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Add translations (for testing).
|
|
58
|
+
*/
|
|
59
|
+
addTranslations(language, translations) {
|
|
60
|
+
if (!this.translations[language]) {
|
|
61
|
+
this.translations[language] = {};
|
|
62
|
+
}
|
|
63
|
+
this.translations[language] = { ...this.translations[language], ...translations };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a mock Translator.
|
|
68
|
+
*/
|
|
69
|
+
export function createMockTranslator(translations = {}, initialLanguage = 'en') {
|
|
70
|
+
return new MockTranslator(translations, initialLanguage);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Translation tester utility.
|
|
74
|
+
*/
|
|
75
|
+
export class TranslationTester {
|
|
76
|
+
manager;
|
|
77
|
+
constructor(manager) {
|
|
78
|
+
this.manager = manager;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Translate a key.
|
|
82
|
+
*/
|
|
83
|
+
t(key, options) {
|
|
84
|
+
return this.manager.t(key, options);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Translate with values.
|
|
88
|
+
*/
|
|
89
|
+
translateWithValues(key, values) {
|
|
90
|
+
return this.manager.translateWithValues(key, values);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Translate plural.
|
|
94
|
+
*/
|
|
95
|
+
translatePlural(key, count, options) {
|
|
96
|
+
return this.manager.translatePlural(key, count, options);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get current language.
|
|
100
|
+
*/
|
|
101
|
+
getCurrentLanguage() {
|
|
102
|
+
return this.manager.getCurrentLanguage();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Change language.
|
|
106
|
+
*/
|
|
107
|
+
async changeLanguage(language) {
|
|
108
|
+
await this.manager.changeLanguage(language);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Check if key exists.
|
|
112
|
+
*/
|
|
113
|
+
hasKey(key) {
|
|
114
|
+
return this.manager.hasKey(key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Create a Translation tester instance.
|
|
119
|
+
*/
|
|
120
|
+
export function createTranslationTester(manager) {
|
|
121
|
+
return new TranslationTester(manager);
|
|
122
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './I18nTestHelpers';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './I18nTestHelpers';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Core testing utilities
|
|
2
|
+
export * from './core';
|
|
3
|
+
// Workflow testing utilities
|
|
4
|
+
export * from './workflow';
|
|
5
|
+
// Permission testing utilities
|
|
6
|
+
export * from './permission';
|
|
7
|
+
// I18n testing utilities
|
|
8
|
+
export * from './i18n';
|
|
9
|
+
// Validation testing utilities
|
|
10
|
+
export * from './validation';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test helpers for Permission.
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for testing permission checkers and managers.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { createMockPermissionChecker, createPermissionTester } from '@c.a.f/testing/permission';
|
|
9
|
+
* import { PermissionManager } from '@c.a.f/permission';
|
|
10
|
+
*
|
|
11
|
+
* const mockChecker = createMockPermissionChecker(['user.edit', 'post.create']);
|
|
12
|
+
* const manager = new PermissionManager(mockChecker);
|
|
13
|
+
* const tester = createPermissionTester(manager);
|
|
14
|
+
*
|
|
15
|
+
* expect(await tester.hasPermission('user.edit')).toBe(true);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import type { IPermissionChecker, PermissionResult, PermissionManager } from '@c.a.f/permission';
|
|
19
|
+
/**
|
|
20
|
+
* Mock PermissionChecker implementation for testing.
|
|
21
|
+
*/
|
|
22
|
+
export declare class MockPermissionChecker implements IPermissionChecker {
|
|
23
|
+
private allowedPermissions;
|
|
24
|
+
constructor(allowedPermissions?: string[]);
|
|
25
|
+
check(permission: string): PermissionResult;
|
|
26
|
+
checkAny(permissions: string[]): PermissionResult;
|
|
27
|
+
checkAll(permissions: string[]): PermissionResult;
|
|
28
|
+
/**
|
|
29
|
+
* Add a permission (for testing).
|
|
30
|
+
*/
|
|
31
|
+
addPermission(permission: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* Remove a permission (for testing).
|
|
34
|
+
*/
|
|
35
|
+
removePermission(permission: string): void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create a mock PermissionChecker.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createMockPermissionChecker(allowedPermissions?: string[]): MockPermissionChecker;
|
|
41
|
+
/**
|
|
42
|
+
* Permission tester utility.
|
|
43
|
+
*/
|
|
44
|
+
export declare class PermissionTester {
|
|
45
|
+
readonly manager: PermissionManager;
|
|
46
|
+
constructor(manager: PermissionManager);
|
|
47
|
+
/**
|
|
48
|
+
* Check if permission is granted.
|
|
49
|
+
*/
|
|
50
|
+
hasPermission(permission: string): Promise<boolean>;
|
|
51
|
+
/**
|
|
52
|
+
* Check if any permission is granted.
|
|
53
|
+
*/
|
|
54
|
+
hasAnyPermission(permissions: string[]): Promise<boolean>;
|
|
55
|
+
/**
|
|
56
|
+
* Check if all permissions are granted.
|
|
57
|
+
*/
|
|
58
|
+
hasAllPermissions(permissions: string[]): Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Require permission (throws if denied).
|
|
61
|
+
*/
|
|
62
|
+
requirePermission(permission: string): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Require any permission (throws if all denied).
|
|
65
|
+
*/
|
|
66
|
+
requireAnyPermission(permissions: string[]): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Require all permissions (throws if any denied).
|
|
69
|
+
*/
|
|
70
|
+
requireAllPermissions(permissions: string[]): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Create a Permission tester instance.
|
|
74
|
+
*/
|
|
75
|
+
export declare function createPermissionTester(manager: PermissionManager): PermissionTester;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test helpers for Permission.
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for testing permission checkers and managers.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { createMockPermissionChecker, createPermissionTester } from '@c.a.f/testing/permission';
|
|
9
|
+
* import { PermissionManager } from '@c.a.f/permission';
|
|
10
|
+
*
|
|
11
|
+
* const mockChecker = createMockPermissionChecker(['user.edit', 'post.create']);
|
|
12
|
+
* const manager = new PermissionManager(mockChecker);
|
|
13
|
+
* const tester = createPermissionTester(manager);
|
|
14
|
+
*
|
|
15
|
+
* expect(await tester.hasPermission('user.edit')).toBe(true);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Mock PermissionChecker implementation for testing.
|
|
20
|
+
*/
|
|
21
|
+
export class MockPermissionChecker {
|
|
22
|
+
allowedPermissions;
|
|
23
|
+
constructor(allowedPermissions = []) {
|
|
24
|
+
this.allowedPermissions = allowedPermissions;
|
|
25
|
+
}
|
|
26
|
+
check(permission) {
|
|
27
|
+
const granted = this.allowedPermissions.includes(permission);
|
|
28
|
+
return {
|
|
29
|
+
granted,
|
|
30
|
+
reason: granted ? undefined : `Permission '${permission}' not granted`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
checkAny(permissions) {
|
|
34
|
+
const granted = permissions.some(p => this.allowedPermissions.includes(p));
|
|
35
|
+
return {
|
|
36
|
+
granted,
|
|
37
|
+
reason: granted ? undefined : 'None of the permissions are granted',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
checkAll(permissions) {
|
|
41
|
+
const missing = permissions.filter(p => !this.allowedPermissions.includes(p));
|
|
42
|
+
return {
|
|
43
|
+
granted: missing.length === 0,
|
|
44
|
+
reason: missing.length > 0 ? `Missing permissions: ${missing.join(', ')}` : undefined,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Add a permission (for testing).
|
|
49
|
+
*/
|
|
50
|
+
addPermission(permission) {
|
|
51
|
+
if (!this.allowedPermissions.includes(permission)) {
|
|
52
|
+
this.allowedPermissions.push(permission);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Remove a permission (for testing).
|
|
57
|
+
*/
|
|
58
|
+
removePermission(permission) {
|
|
59
|
+
const index = this.allowedPermissions.indexOf(permission);
|
|
60
|
+
if (index > -1) {
|
|
61
|
+
this.allowedPermissions.splice(index, 1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Create a mock PermissionChecker.
|
|
67
|
+
*/
|
|
68
|
+
export function createMockPermissionChecker(allowedPermissions = []) {
|
|
69
|
+
return new MockPermissionChecker(allowedPermissions);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Permission tester utility.
|
|
73
|
+
*/
|
|
74
|
+
export class PermissionTester {
|
|
75
|
+
manager;
|
|
76
|
+
constructor(manager) {
|
|
77
|
+
this.manager = manager;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if permission is granted.
|
|
81
|
+
*/
|
|
82
|
+
async hasPermission(permission) {
|
|
83
|
+
return await this.manager.hasPermission(permission);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if any permission is granted.
|
|
87
|
+
*/
|
|
88
|
+
async hasAnyPermission(permissions) {
|
|
89
|
+
return await this.manager.hasAnyPermission(permissions);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if all permissions are granted.
|
|
93
|
+
*/
|
|
94
|
+
async hasAllPermissions(permissions) {
|
|
95
|
+
return await this.manager.hasAllPermissions(permissions);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Require permission (throws if denied).
|
|
99
|
+
*/
|
|
100
|
+
async requirePermission(permission) {
|
|
101
|
+
await this.manager.requirePermission(permission);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Require any permission (throws if all denied).
|
|
105
|
+
*/
|
|
106
|
+
async requireAnyPermission(permissions) {
|
|
107
|
+
await this.manager.requireAnyPermission(permissions);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Require all permissions (throws if any denied).
|
|
111
|
+
*/
|
|
112
|
+
async requireAllPermissions(permissions) {
|
|
113
|
+
await this.manager.requireAllPermissions(permissions);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Create a Permission tester instance.
|
|
118
|
+
*/
|
|
119
|
+
export function createPermissionTester(manager) {
|
|
120
|
+
return new PermissionTester(manager);
|
|
121
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PermissionTestHelpers';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PermissionTestHelpers';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a Ploc instance for React component tests. Same as createMockPloc from core:
|
|
3
|
+
* a Ploc with controllable state and no business logic. Use with renderWithCAF.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```tsx
|
|
7
|
+
* import { createTestPloc, renderWithCAF } from '@c.a.f/testing/react';
|
|
8
|
+
*
|
|
9
|
+
* const ploc = createTestPloc({ count: 0 });
|
|
10
|
+
* const { getByRole } = renderWithCAF(<Counter />, { plocs: { counter: ploc } });
|
|
11
|
+
* ploc.changeState({ count: 1 });
|
|
12
|
+
* expect(screen.getByText('1')).toBeInTheDocument();
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import type { Ploc } from '@c.a.f/core';
|
|
16
|
+
/**
|
|
17
|
+
* Create a test Ploc with initial state. Use changeState() to drive state in tests.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createTestPloc<S>(initialState: S): Ploc<S>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a Ploc instance for React component tests. Same as createMockPloc from core:
|
|
3
|
+
* a Ploc with controllable state and no business logic. Use with renderWithCAF.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```tsx
|
|
7
|
+
* import { createTestPloc, renderWithCAF } from '@c.a.f/testing/react';
|
|
8
|
+
*
|
|
9
|
+
* const ploc = createTestPloc({ count: 0 });
|
|
10
|
+
* const { getByRole } = renderWithCAF(<Counter />, { plocs: { counter: ploc } });
|
|
11
|
+
* ploc.changeState({ count: 1 });
|
|
12
|
+
* expect(screen.getByText('1')).toBeInTheDocument();
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import { createMockPloc } from '../core/PlocTestHelpers';
|
|
16
|
+
/**
|
|
17
|
+
* Create a test Ploc with initial state. Use changeState() to drive state in tests.
|
|
18
|
+
*/
|
|
19
|
+
export function createTestPloc(initialState) {
|
|
20
|
+
return createMockPloc(initialState);
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React testing utilities for CAF.
|
|
3
|
+
*
|
|
4
|
+
* - renderWithCAF: render with CAFProvider (Ploc/UseCase context)
|
|
5
|
+
* - createTestPloc: create a test Ploc with controllable state
|
|
6
|
+
* - waitForPlocState: wait for Ploc state to match a predicate
|
|
7
|
+
* - mockUseCase: mock UseCase (success, error, async, fn)
|
|
8
|
+
*/
|
|
9
|
+
export { renderWithCAF, type RenderWithCAFOptions } from './renderWithCAF';
|
|
10
|
+
export { createTestPloc } from './createTestPloc';
|
|
11
|
+
export { waitForPlocState } from './waitForPlocState';
|
|
12
|
+
export { mockUseCase } from './mockUseCase';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React testing utilities for CAF.
|
|
3
|
+
*
|
|
4
|
+
* - renderWithCAF: render with CAFProvider (Ploc/UseCase context)
|
|
5
|
+
* - createTestPloc: create a test Ploc with controllable state
|
|
6
|
+
* - waitForPlocState: wait for Ploc state to match a predicate
|
|
7
|
+
* - mockUseCase: mock UseCase (success, error, async, fn)
|
|
8
|
+
*/
|
|
9
|
+
export { renderWithCAF } from './renderWithCAF';
|
|
10
|
+
export { createTestPloc } from './createTestPloc';
|
|
11
|
+
export { waitForPlocState } from './waitForPlocState';
|
|
12
|
+
export { mockUseCase } from './mockUseCase';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers to create mock UseCases for React component tests. Use with renderWithCAF.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```tsx
|
|
6
|
+
* import { renderWithCAF, mockUseCase } from '@c.a.f/testing/react';
|
|
7
|
+
*
|
|
8
|
+
* const submit = mockUseCase.success({ id: '1' });
|
|
9
|
+
* renderWithCAF(<Form />, { useCases: { submit } });
|
|
10
|
+
*
|
|
11
|
+
* // Or error
|
|
12
|
+
* const load = mockUseCase.error(new Error('Network error'));
|
|
13
|
+
*
|
|
14
|
+
* // Or custom implementation
|
|
15
|
+
* const search = mockUseCase.fn((query: string) => createSuccessResult([{ id: '1', title: query }]));
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import type { UseCase, RequestResult } from '@c.a.f/core';
|
|
19
|
+
export declare const mockUseCase: {
|
|
20
|
+
/**
|
|
21
|
+
* UseCase that always returns success with the given data.
|
|
22
|
+
*/
|
|
23
|
+
success<T>(data: T): UseCase<[], T>;
|
|
24
|
+
/**
|
|
25
|
+
* UseCase that always returns the given error.
|
|
26
|
+
*/
|
|
27
|
+
error<T_1 = unknown>(error: Error): UseCase<[], T_1>;
|
|
28
|
+
/**
|
|
29
|
+
* UseCase that resolves with data after an optional delay (for loading-state tests).
|
|
30
|
+
*/
|
|
31
|
+
async<T_2>(data: T_2, delayMs?: number): UseCase<[], T_2>;
|
|
32
|
+
/**
|
|
33
|
+
* UseCase with a custom implementation (same as createMockUseCase).
|
|
34
|
+
*/
|
|
35
|
+
fn<A extends any[], T_3>(implementation: (...args: A) => RequestResult<T_3> | Promise<RequestResult<T_3>>): UseCase<A, T_3>;
|
|
36
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers to create mock UseCases for React component tests. Use with renderWithCAF.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```tsx
|
|
6
|
+
* import { renderWithCAF, mockUseCase } from '@c.a.f/testing/react';
|
|
7
|
+
*
|
|
8
|
+
* const submit = mockUseCase.success({ id: '1' });
|
|
9
|
+
* renderWithCAF(<Form />, { useCases: { submit } });
|
|
10
|
+
*
|
|
11
|
+
* // Or error
|
|
12
|
+
* const load = mockUseCase.error(new Error('Network error'));
|
|
13
|
+
*
|
|
14
|
+
* // Or custom implementation
|
|
15
|
+
* const search = mockUseCase.fn((query: string) => createSuccessResult([{ id: '1', title: query }]));
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { createMockUseCase, createMockUseCaseSuccess, createMockUseCaseError, createMockUseCaseAsync, } from '../core/UseCaseTestHelpers';
|
|
19
|
+
export const mockUseCase = {
|
|
20
|
+
/**
|
|
21
|
+
* UseCase that always returns success with the given data.
|
|
22
|
+
*/
|
|
23
|
+
success(data) {
|
|
24
|
+
return createMockUseCaseSuccess(data);
|
|
25
|
+
},
|
|
26
|
+
/**
|
|
27
|
+
* UseCase that always returns the given error.
|
|
28
|
+
*/
|
|
29
|
+
error(error) {
|
|
30
|
+
return createMockUseCaseError(error);
|
|
31
|
+
},
|
|
32
|
+
/**
|
|
33
|
+
* UseCase that resolves with data after an optional delay (for loading-state tests).
|
|
34
|
+
*/
|
|
35
|
+
async(data, delayMs = 0) {
|
|
36
|
+
return createMockUseCaseAsync(data, delayMs);
|
|
37
|
+
},
|
|
38
|
+
/**
|
|
39
|
+
* UseCase with a custom implementation (same as createMockUseCase).
|
|
40
|
+
*/
|
|
41
|
+
fn(implementation) {
|
|
42
|
+
return createMockUseCase(implementation);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render a React component with CAFProvider so that usePlocFromContext and
|
|
3
|
+
* useUseCaseFromContext work. Use this instead of raw render() when testing
|
|
4
|
+
* components that depend on CAF context.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import { renderWithCAF, createTestPloc, mockUseCase } from '@c.a.f/testing/react';
|
|
9
|
+
*
|
|
10
|
+
* const ploc = createTestPloc({ count: 0 });
|
|
11
|
+
* const { getByRole } = renderWithCAF(<Counter />, {
|
|
12
|
+
* plocs: { counter: ploc },
|
|
13
|
+
* useCases: { submit: mockUseCase.success({ id: '1' }) },
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import { type ReactElement } from 'react';
|
|
18
|
+
import { type RenderOptions, type RenderResult } from '@testing-library/react';
|
|
19
|
+
import type { Ploc } from '@c.a.f/core';
|
|
20
|
+
import type { UseCase } from '@c.a.f/core';
|
|
21
|
+
export interface RenderWithCAFOptions extends Omit<RenderOptions, 'wrapper'> {
|
|
22
|
+
/** Plocs to provide (keyed by string). */
|
|
23
|
+
plocs?: Record<string, Ploc<unknown>>;
|
|
24
|
+
/** UseCases to provide (keyed by string). */
|
|
25
|
+
useCases?: Record<string, UseCase<any[], any>>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Render a component with CAFProvider so Ploc/UseCase context is available.
|
|
29
|
+
* Returns the same result as React Testing Library's render().
|
|
30
|
+
*/
|
|
31
|
+
export declare function renderWithCAF(ui: ReactElement, options?: RenderWithCAFOptions): RenderResult;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import { CAFProvider } from '@c.a.f/infrastructure-react';
|
|
4
|
+
/**
|
|
5
|
+
* Wrapper that provides CAFProvider around the component tree.
|
|
6
|
+
*/
|
|
7
|
+
function createCAFWrapper(plocs, useCases) {
|
|
8
|
+
return function Wrapper({ children }) {
|
|
9
|
+
return (_jsx(CAFProvider, { plocs: plocs ?? {}, useCases: useCases ?? {}, children: children }));
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Render a component with CAFProvider so Ploc/UseCase context is available.
|
|
14
|
+
* Returns the same result as React Testing Library's render().
|
|
15
|
+
*/
|
|
16
|
+
export function renderWithCAF(ui, options = {}) {
|
|
17
|
+
const { plocs, useCases, ...renderOptions } = options;
|
|
18
|
+
const Wrapper = createCAFWrapper(plocs, useCases);
|
|
19
|
+
return render(ui, {
|
|
20
|
+
...renderOptions,
|
|
21
|
+
wrapper: Wrapper,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wait for a Ploc to reach a state that matches a predicate. Useful in React tests
|
|
3
|
+
* after triggering an action that updates the Ploc asynchronously.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```tsx
|
|
7
|
+
* import { renderWithCAF, createTestPloc, waitForPlocState } from '@c.a.f/testing/react';
|
|
8
|
+
*
|
|
9
|
+
* const ploc = createTestPloc({ loading: true, items: [] });
|
|
10
|
+
* renderWithCAF(<List />, { plocs: { list: ploc } });
|
|
11
|
+
*
|
|
12
|
+
* // Simulate load complete
|
|
13
|
+
* ploc.changeState({ loading: false, items: [{ id: '1' }] });
|
|
14
|
+
* await waitForPlocState(ploc, (state) => !state.loading && state.items.length > 0);
|
|
15
|
+
* expect(screen.getByText('1')).toBeInTheDocument();
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import type { Ploc } from '@c.a.f/core';
|
|
19
|
+
/**
|
|
20
|
+
* Wait until the Ploc's state satisfies the predicate (or timeout).
|
|
21
|
+
*/
|
|
22
|
+
export declare function waitForPlocState<S>(ploc: Ploc<S>, predicate: (state: S) => boolean, timeoutMs?: number): Promise<S>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wait for a Ploc to reach a state that matches a predicate. Useful in React tests
|
|
3
|
+
* after triggering an action that updates the Ploc asynchronously.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```tsx
|
|
7
|
+
* import { renderWithCAF, createTestPloc, waitForPlocState } from '@c.a.f/testing/react';
|
|
8
|
+
*
|
|
9
|
+
* const ploc = createTestPloc({ loading: true, items: [] });
|
|
10
|
+
* renderWithCAF(<List />, { plocs: { list: ploc } });
|
|
11
|
+
*
|
|
12
|
+
* // Simulate load complete
|
|
13
|
+
* ploc.changeState({ loading: false, items: [{ id: '1' }] });
|
|
14
|
+
* await waitForPlocState(ploc, (state) => !state.loading && state.items.length > 0);
|
|
15
|
+
* expect(screen.getByText('1')).toBeInTheDocument();
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { waitForStateChange } from '../core/PlocTestHelpers';
|
|
19
|
+
/**
|
|
20
|
+
* Wait until the Ploc's state satisfies the predicate (or timeout).
|
|
21
|
+
*/
|
|
22
|
+
export function waitForPlocState(ploc, predicate, timeoutMs = 5000) {
|
|
23
|
+
return waitForStateChange(ploc, predicate, timeoutMs);
|
|
24
|
+
}
|