@memberjunction/unit-testing 0.0.1 → 4.3.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/README.md +315 -28
- package/dist/custom-matchers.d.ts +6 -0
- package/dist/custom-matchers.d.ts.map +1 -0
- package/dist/custom-matchers.js +53 -0
- package/dist/custom-matchers.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/mock-entity.d.ts +36 -0
- package/dist/mock-entity.d.ts.map +1 -0
- package/dist/mock-entity.js +74 -0
- package/dist/mock-entity.js.map +1 -0
- package/dist/mock-run-view.d.ts +50 -0
- package/dist/mock-run-view.d.ts.map +1 -0
- package/dist/mock-run-view.js +57 -0
- package/dist/mock-run-view.js.map +1 -0
- package/dist/singleton-reset.d.ts +15 -0
- package/dist/singleton-reset.d.ts.map +1 -0
- package/dist/singleton-reset.js +30 -0
- package/dist/singleton-reset.js.map +1 -0
- package/package.json +27 -7
package/README.md
CHANGED
|
@@ -1,45 +1,332 @@
|
|
|
1
1
|
# @memberjunction/unit-testing
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Utilities and mocks for writing unit tests in the MemberJunction monorepo using Vitest.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install --save-dev @memberjunction/unit-testing
|
|
9
|
+
```
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
This package is typically already included as a dev dependency in MemberJunction projects.
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
1. Configure OIDC trusted publishing for the package name `@memberjunction/unit-testing`
|
|
13
|
-
2. Enable secure, token-less publishing from CI/CD workflows
|
|
14
|
-
3. Establish provenance for packages published under this name
|
|
13
|
+
## Overview
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
This package provides helper functions and mock utilities to simplify unit testing of MemberJunction components. It handles common testing challenges like:
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
- **Singleton reset** - Clean state between tests
|
|
18
|
+
- **Entity mocking** - Mock BaseEntity behavior without database
|
|
19
|
+
- **RunView mocking** - Mock data loading operations
|
|
20
|
+
- **Custom matchers** - Additional Vitest assertions
|
|
19
21
|
|
|
20
|
-
##
|
|
22
|
+
## Utilities
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
### Singleton Reset
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
2. Configure the trusted publisher (e.g., GitHub Actions)
|
|
26
|
-
3. Specify the repository and workflow that should be allowed to publish
|
|
27
|
-
4. Use the configured workflow to publish your actual package
|
|
26
|
+
MemberJunction uses singletons for engines and global state. Reset them between tests to ensure isolation.
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
#### `resetMJSingletons()`
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
- Contains no executable code
|
|
33
|
-
- Provides no functionality
|
|
34
|
-
- Should not be installed as a dependency
|
|
35
|
-
- Exists only for administrative purposes
|
|
30
|
+
Clears ALL MJ singleton instances from the global store.
|
|
36
31
|
|
|
37
|
-
|
|
32
|
+
```typescript
|
|
33
|
+
import { describe, it, beforeEach } from 'vitest';
|
|
34
|
+
import { resetMJSingletons } from '@memberjunction/unit-testing';
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
describe('MyEngine', () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
resetMJSingletons(); // Clean slate for each test
|
|
39
|
+
});
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
it('should create fresh engine instance', () => {
|
|
42
|
+
const engine = MyEngine.Instance; // Gets new instance
|
|
43
|
+
// ... test
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
```
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
#### `resetClassFactory()`
|
|
49
|
+
|
|
50
|
+
Resets only the ClassFactory registrations. Lighter weight than `resetMJSingletons`.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { resetClassFactory } from '@memberjunction/unit-testing';
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
resetClassFactory(); // Only reset class registrations
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### `resetObjectCache()`
|
|
61
|
+
|
|
62
|
+
Clears the global object cache used by MJ for caching data.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { resetObjectCache } from '@memberjunction/unit-testing';
|
|
66
|
+
|
|
67
|
+
beforeEach(() => {
|
|
68
|
+
resetObjectCache(); // Clear cached objects
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Entity Mocking
|
|
73
|
+
|
|
74
|
+
#### `createMockEntity<T>(data, options?)`
|
|
75
|
+
|
|
76
|
+
Creates a Proxy-based mock that behaves like a BaseEntity with getter/setter properties.
|
|
77
|
+
|
|
78
|
+
**Why needed:** BaseEntity uses getters/setters, so the spread operator (`...entity`) doesn't work. This mock provides the same interface without requiring a real database.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { createMockEntity } from '@memberjunction/unit-testing';
|
|
82
|
+
|
|
83
|
+
// Create a mock user entity
|
|
84
|
+
const mockUser = createMockEntity({
|
|
85
|
+
ID: 'user-123',
|
|
86
|
+
Name: 'Test User',
|
|
87
|
+
Email: 'test@example.com',
|
|
88
|
+
Status: 'Active'
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Use like a real entity
|
|
92
|
+
console.log(mockUser.Name); // 'Test User'
|
|
93
|
+
mockUser.Status = 'Inactive'; // Setter works
|
|
94
|
+
console.log(mockUser.Get('Email')); // 'test@example.com'
|
|
95
|
+
console.log(mockUser.GetAll()); // { ID: '...', Name: '...', ... }
|
|
96
|
+
|
|
97
|
+
await mockUser.Save(); // Mock save (always succeeds)
|
|
98
|
+
console.log(mockUser.Dirty); // false after save
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Options:**
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
interface MockEntityOptions {
|
|
105
|
+
isSaved?: boolean; // Default: true - Entity appears saved to DB
|
|
106
|
+
isDirty?: boolean; // Default: false - Entity appears clean
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Create unsaved entity
|
|
110
|
+
const newEntity = createMockEntity(
|
|
111
|
+
{ ID: '', Name: 'New User' },
|
|
112
|
+
{ isSaved: false, isDirty: true }
|
|
113
|
+
);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Mock Entity Methods:**
|
|
117
|
+
|
|
118
|
+
- `Get(fieldName)` - Get field value (case-insensitive)
|
|
119
|
+
- `Set(fieldName, value)` - Set field value (marks dirty)
|
|
120
|
+
- `GetAll()` - Returns all fields as plain object
|
|
121
|
+
- `Save()` - Mock save operation (always succeeds, clears dirty flag)
|
|
122
|
+
- `Delete()` - Mock delete operation (always succeeds)
|
|
123
|
+
- `Dirty` - Boolean indicating if entity has unsaved changes
|
|
124
|
+
- `IsSaved` - Boolean indicating if entity exists in DB
|
|
125
|
+
- `PrimaryKey` - Mock primary key object
|
|
126
|
+
|
|
127
|
+
### RunView Mocking
|
|
128
|
+
|
|
129
|
+
#### `mockRunView(entityName, mockResults)`
|
|
130
|
+
|
|
131
|
+
Mocks a single `RunView` operation to return specific results.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { mockRunView } from '@memberjunction/unit-testing';
|
|
135
|
+
import { vi } from 'vitest';
|
|
136
|
+
|
|
137
|
+
// Mock RunView for 'Users' entity
|
|
138
|
+
const mockUsers = [
|
|
139
|
+
createMockEntity({ ID: '1', Name: 'Alice' }),
|
|
140
|
+
createMockEntity({ ID: '2', Name: 'Bob' })
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
const runViewSpy = mockRunView('Users', mockUsers);
|
|
144
|
+
|
|
145
|
+
// Now when code calls RunView:
|
|
146
|
+
const rv = new RunView();
|
|
147
|
+
const result = await rv.RunView({ EntityName: 'Users' });
|
|
148
|
+
// result.Results === mockUsers
|
|
149
|
+
|
|
150
|
+
// Verify it was called
|
|
151
|
+
expect(runViewSpy).toHaveBeenCalledWith(
|
|
152
|
+
expect.objectContaining({ EntityName: 'Users' })
|
|
153
|
+
);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### `mockRunViews(mocks)`
|
|
157
|
+
|
|
158
|
+
Mocks multiple `RunView` operations at once.
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { mockRunViews } from '@memberjunction/unit-testing';
|
|
162
|
+
|
|
163
|
+
mockRunViews({
|
|
164
|
+
'Users': mockUsers,
|
|
165
|
+
'Actions': mockActions,
|
|
166
|
+
'AI Models': mockModels
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// All three entities will return mock data
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### `resetRunViewMocks()`
|
|
173
|
+
|
|
174
|
+
Clears all RunView mocks.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { resetRunViewMocks } from '@memberjunction/unit-testing';
|
|
178
|
+
|
|
179
|
+
afterEach(() => {
|
|
180
|
+
resetRunViewMocks(); // Clean up mocks
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Custom Matchers
|
|
185
|
+
|
|
186
|
+
#### `installCustomMatchers()`
|
|
187
|
+
|
|
188
|
+
Installs additional Vitest matchers for MemberJunction-specific assertions.
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { installCustomMatchers } from '@memberjunction/unit-testing';
|
|
192
|
+
import { beforeAll } from 'vitest';
|
|
193
|
+
|
|
194
|
+
beforeAll(() => {
|
|
195
|
+
installCustomMatchers();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Now use custom matchers in your tests
|
|
199
|
+
// (See vitest.d.ts for available custom matchers)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Common Patterns
|
|
203
|
+
|
|
204
|
+
### Test Structure with Full Setup
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
208
|
+
import {
|
|
209
|
+
resetMJSingletons,
|
|
210
|
+
createMockEntity,
|
|
211
|
+
mockRunView
|
|
212
|
+
} from '@memberjunction/unit-testing';
|
|
213
|
+
|
|
214
|
+
describe('MyService', () => {
|
|
215
|
+
beforeEach(() => {
|
|
216
|
+
// Reset singletons for clean state
|
|
217
|
+
resetMJSingletons();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should process users correctly', async () => {
|
|
221
|
+
// Setup mock data
|
|
222
|
+
const mockUsers = [
|
|
223
|
+
createMockEntity({ ID: '1', Name: 'Alice', Status: 'Active' }),
|
|
224
|
+
createMockEntity({ ID: '2', Name: 'Bob', Status: 'Inactive' })
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
// Mock RunView to return mock data
|
|
228
|
+
mockRunView('Users', mockUsers);
|
|
229
|
+
|
|
230
|
+
// Test your service
|
|
231
|
+
const service = new MyService();
|
|
232
|
+
const result = await service.getActiveUsers();
|
|
233
|
+
|
|
234
|
+
// Assertions
|
|
235
|
+
expect(result).toHaveLength(1);
|
|
236
|
+
expect(result[0].Name).toBe('Alice');
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Mocking Entity Creation
|
|
242
|
+
|
|
243
|
+
When testing code that creates entities via `Metadata.GetEntityObject()`:
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { vi } from 'vitest';
|
|
247
|
+
import { createMockEntity } from '@memberjunction/unit-testing';
|
|
248
|
+
|
|
249
|
+
// Mock the Metadata class
|
|
250
|
+
vi.mock('@memberjunction/core', () => ({
|
|
251
|
+
Metadata: vi.fn(function() {
|
|
252
|
+
return {
|
|
253
|
+
GetEntityObject: vi.fn(async (entityName) => {
|
|
254
|
+
return createMockEntity({ ID: '', Name: '' }, { isSaved: false });
|
|
255
|
+
})
|
|
256
|
+
};
|
|
257
|
+
})
|
|
258
|
+
}));
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Testing Singleton Engines
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { resetMJSingletons } from '@memberjunction/unit-testing';
|
|
265
|
+
|
|
266
|
+
describe('ActionEngine', () => {
|
|
267
|
+
beforeEach(() => {
|
|
268
|
+
resetMJSingletons(); // Ensures fresh instance
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should load actions', async () => {
|
|
272
|
+
const engine = ActionEngine.Instance;
|
|
273
|
+
await engine.Load();
|
|
274
|
+
expect(engine.Actions.length).toBeGreaterThan(0);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should get separate instance after reset', async () => {
|
|
278
|
+
const engine1 = ActionEngine.Instance;
|
|
279
|
+
resetMJSingletons();
|
|
280
|
+
const engine2 = ActionEngine.Instance;
|
|
281
|
+
|
|
282
|
+
expect(engine1).not.toBe(engine2); // Different instances
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## TypeScript Support
|
|
288
|
+
|
|
289
|
+
This package includes TypeScript definitions for all utilities. Import types as needed:
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import type { MockEntityOptions, MockEntityMethods } from '@memberjunction/unit-testing';
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Best Practices
|
|
296
|
+
|
|
297
|
+
### DO:
|
|
298
|
+
- ✅ Always reset singletons in `beforeEach()` for test isolation
|
|
299
|
+
- ✅ Use `createMockEntity()` instead of plain objects for BaseEntity mocks
|
|
300
|
+
- ✅ Use `mockRunView()` to avoid database dependencies in unit tests
|
|
301
|
+
- ✅ Keep mocks simple and focused on the test case
|
|
302
|
+
|
|
303
|
+
### DON'T:
|
|
304
|
+
- ❌ Share mock data between tests (creates hidden dependencies)
|
|
305
|
+
- ❌ Forget to reset singletons (causes test interference)
|
|
306
|
+
- ❌ Over-mock (test real logic where possible)
|
|
307
|
+
- ❌ Use real database connections in unit tests (use integration tests instead)
|
|
308
|
+
|
|
309
|
+
## Related Documentation
|
|
310
|
+
|
|
311
|
+
- **Testing Strategy**: See `/unit-testing/README.md` for comprehensive testing guidelines
|
|
312
|
+
- **Analytics**: See `/unit-testing/README.md` for test reporting and analytics
|
|
313
|
+
- **Integration Tests**: See `/packages/TestingFramework/` for full-stack testing
|
|
314
|
+
|
|
315
|
+
## Examples
|
|
316
|
+
|
|
317
|
+
Look at existing tests for usage examples:
|
|
318
|
+
- `/packages/MJCore/src/__tests__/` - Core functionality tests
|
|
319
|
+
- `/packages/Actions/Engine/src/__tests__/` - Action engine tests
|
|
320
|
+
- `/packages/AI/*/src/__tests__/` - AI provider tests
|
|
321
|
+
|
|
322
|
+
## Contributing
|
|
323
|
+
|
|
324
|
+
When adding new test utilities:
|
|
325
|
+
1. Add the utility function to appropriate file (`singleton-reset.ts`, `mock-entity.ts`, etc.)
|
|
326
|
+
2. Export from `index.ts`
|
|
327
|
+
3. Update this README with usage examples
|
|
328
|
+
4. Add TypeScript definitions to `vitest.d.ts` if adding custom matchers
|
|
329
|
+
|
|
330
|
+
## License
|
|
331
|
+
|
|
332
|
+
See repository root LICENSE file.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-matchers.d.ts","sourceRoot":"","sources":["../src/custom-matchers.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAmD5C"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { expect } from 'vitest';
|
|
2
|
+
/**
|
|
3
|
+
* Install MemberJunction-specific custom matchers for Vitest.
|
|
4
|
+
* Call this once in your setup file or at the top of your test.
|
|
5
|
+
*/
|
|
6
|
+
export function installCustomMatchers() {
|
|
7
|
+
expect.extend({
|
|
8
|
+
/**
|
|
9
|
+
* Asserts that a value looks like a valid MJ entity (has a non-empty ID)
|
|
10
|
+
*/
|
|
11
|
+
toBeValidEntity(received) {
|
|
12
|
+
const entity = received;
|
|
13
|
+
const pass = entity != null &&
|
|
14
|
+
typeof entity === 'object' &&
|
|
15
|
+
'ID' in entity &&
|
|
16
|
+
typeof entity.ID === 'string' &&
|
|
17
|
+
entity.ID.length > 0;
|
|
18
|
+
return {
|
|
19
|
+
pass,
|
|
20
|
+
message: () => pass
|
|
21
|
+
? `expected value not to be a valid entity, but it has ID="${entity.ID}"`
|
|
22
|
+
: `expected value to be a valid entity with a non-empty string ID, got ${JSON.stringify(received)}`,
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
/**
|
|
26
|
+
* Asserts that a RunView result has Success: true
|
|
27
|
+
*/
|
|
28
|
+
toHaveSucceeded(received) {
|
|
29
|
+
const result = received;
|
|
30
|
+
const pass = result != null && result.Success === true;
|
|
31
|
+
return {
|
|
32
|
+
pass,
|
|
33
|
+
message: () => pass
|
|
34
|
+
? `expected result not to have succeeded`
|
|
35
|
+
: `expected result to have succeeded but got: ${result?.ErrorMessage ?? 'Success was not true'}`,
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
/**
|
|
39
|
+
* Asserts that an entity-like object has a specific field
|
|
40
|
+
*/
|
|
41
|
+
toHaveEntityField(received, fieldName) {
|
|
42
|
+
const entity = received;
|
|
43
|
+
const pass = entity != null && typeof entity === 'object' && fieldName in entity;
|
|
44
|
+
return {
|
|
45
|
+
pass,
|
|
46
|
+
message: () => pass
|
|
47
|
+
? `expected entity not to have field "${fieldName}"`
|
|
48
|
+
: `expected entity to have field "${fieldName}", got keys: ${entity ? Object.keys(entity).join(', ') : 'null'}`,
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=custom-matchers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-matchers.js","sourceRoot":"","sources":["../src/custom-matchers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC;;;GAGG;AACH,MAAM,UAAU,qBAAqB;IACnC,MAAM,CAAC,MAAM,CAAC;QACZ;;WAEG;QACH,eAAe,CAAC,QAAiB;YAC/B,MAAM,MAAM,GAAG,QAA0C,CAAC;YAC1D,MAAM,IAAI,GAAG,MAAM,IAAI,IAAI;gBACzB,OAAO,MAAM,KAAK,QAAQ;gBAC1B,IAAI,IAAI,MAAM;gBACd,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ;gBAC7B,MAAM,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;YAEvB,OAAO;gBACL,IAAI;gBACJ,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;oBACjB,CAAC,CAAC,2DAA4D,MAAkC,CAAC,EAAE,GAAG;oBACtG,CAAC,CAAC,uEAAuE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE;aACtG,CAAC;QACJ,CAAC;QAED;;WAEG;QACH,eAAe,CAAC,QAAiB;YAC/B,MAAM,MAAM,GAAG,QAA+D,CAAC;YAC/E,MAAM,IAAI,GAAG,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC;YAEvD,OAAO;gBACL,IAAI;gBACJ,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;oBACjB,CAAC,CAAC,uCAAuC;oBACzC,CAAC,CAAC,8CAA8C,MAAM,EAAE,YAAY,IAAI,sBAAsB,EAAE;aACnG,CAAC;QACJ,CAAC;QAED;;WAEG;QACH,iBAAiB,CAAC,QAAiB,EAAE,SAAiB;YACpD,MAAM,MAAM,GAAG,QAA0C,CAAC;YAC1D,MAAM,IAAI,GAAG,MAAM,IAAI,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,SAAS,IAAI,MAAM,CAAC;YAEjF,OAAO;gBACL,IAAI;gBACJ,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;oBACjB,CAAC,CAAC,sCAAsC,SAAS,GAAG;oBACpD,CAAC,CAAC,kCAAkC,SAAS,gBAAgB,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE;aAClH,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { resetMJSingletons, resetClassFactory, resetObjectCache } from './singleton-reset.js';
|
|
2
|
+
export { createMockEntity, type MockEntityOptions } from './mock-entity.js';
|
|
3
|
+
export { mockRunView, mockRunViews, resetRunViewMocks } from './mock-run-view.js';
|
|
4
|
+
export { installCustomMatchers } from './custom-matchers.js';
|
|
5
|
+
export type {} from './vitest.d';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,YAAY,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { resetMJSingletons, resetClassFactory, resetObjectCache } from './singleton-reset.js';
|
|
2
|
+
export { createMockEntity } from './mock-entity.js';
|
|
3
|
+
export { mockRunView, mockRunViews, resetRunViewMocks } from './mock-run-view.js';
|
|
4
|
+
export { installCustomMatchers } from './custom-matchers.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAA0B,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for creating mock entity objects
|
|
3
|
+
*/
|
|
4
|
+
export interface MockEntityOptions {
|
|
5
|
+
/** Whether the entity should report as saved (existing in DB) */
|
|
6
|
+
isSaved?: boolean;
|
|
7
|
+
/** Whether the entity should report as dirty */
|
|
8
|
+
isDirty?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Creates a mock object that behaves like a BaseEntity with getter/setter properties.
|
|
12
|
+
* Since BaseEntity uses getter/setters, the spread operator doesn't work on real entities.
|
|
13
|
+
* This creates a Proxy-based mock that supports Get(), Set(), GetAll(), and property access.
|
|
14
|
+
*
|
|
15
|
+
* @param data Initial field values for the mock entity
|
|
16
|
+
* @param options Configuration options for the mock's state
|
|
17
|
+
* @returns A proxy object that behaves like a BaseEntity
|
|
18
|
+
*/
|
|
19
|
+
export declare function createMockEntity<T extends Record<string, unknown>>(data: T, options?: MockEntityOptions): T & MockEntityMethods;
|
|
20
|
+
export interface MockEntityMethods {
|
|
21
|
+
Get(fieldName: string): unknown;
|
|
22
|
+
Set(fieldName: string, value: unknown): void;
|
|
23
|
+
GetAll(): Record<string, unknown>;
|
|
24
|
+
readonly Dirty: boolean;
|
|
25
|
+
readonly IsSaved: boolean;
|
|
26
|
+
readonly PrimaryKey: {
|
|
27
|
+
KeyValuePairs: Array<{
|
|
28
|
+
FieldName: string;
|
|
29
|
+
Value: unknown;
|
|
30
|
+
}>;
|
|
31
|
+
Values: () => string;
|
|
32
|
+
};
|
|
33
|
+
Save(): Promise<boolean>;
|
|
34
|
+
Delete(): Promise<boolean>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=mock-entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-entity.d.ts","sourceRoot":"","sources":["../src/mock-entity.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gDAAgD;IAChD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChE,IAAI,EAAE,CAAC,EACP,OAAO,GAAE,iBAAsB,GAC9B,CAAC,GAAG,iBAAiB,CAiEvB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7C,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE;QAAE,aAAa,EAAE,KAAK,CAAC;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;QAAC,MAAM,EAAE,MAAM,MAAM,CAAA;KAAE,CAAC;IAC3G,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACzB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5B"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a mock object that behaves like a BaseEntity with getter/setter properties.
|
|
3
|
+
* Since BaseEntity uses getter/setters, the spread operator doesn't work on real entities.
|
|
4
|
+
* This creates a Proxy-based mock that supports Get(), Set(), GetAll(), and property access.
|
|
5
|
+
*
|
|
6
|
+
* @param data Initial field values for the mock entity
|
|
7
|
+
* @param options Configuration options for the mock's state
|
|
8
|
+
* @returns A proxy object that behaves like a BaseEntity
|
|
9
|
+
*/
|
|
10
|
+
export function createMockEntity(data, options = {}) {
|
|
11
|
+
const { isSaved = true, isDirty = false } = options;
|
|
12
|
+
const fields = new Map(Object.entries(data));
|
|
13
|
+
const oldValues = new Map(Object.entries(data));
|
|
14
|
+
let dirty = isDirty;
|
|
15
|
+
const methods = {
|
|
16
|
+
Get(fieldName) {
|
|
17
|
+
const lowerKey = [...fields.keys()].find(k => k.toLowerCase() === fieldName.toLowerCase());
|
|
18
|
+
return lowerKey ? fields.get(lowerKey) : undefined;
|
|
19
|
+
},
|
|
20
|
+
Set(fieldName, value) {
|
|
21
|
+
fields.set(fieldName, value);
|
|
22
|
+
dirty = true;
|
|
23
|
+
},
|
|
24
|
+
GetAll() {
|
|
25
|
+
const result = {};
|
|
26
|
+
for (const [key, value] of fields) {
|
|
27
|
+
result[key] = value;
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
},
|
|
31
|
+
get Dirty() {
|
|
32
|
+
return dirty;
|
|
33
|
+
},
|
|
34
|
+
get IsSaved() {
|
|
35
|
+
return isSaved;
|
|
36
|
+
},
|
|
37
|
+
get PrimaryKey() {
|
|
38
|
+
return {
|
|
39
|
+
KeyValuePairs: [{ FieldName: 'ID', Value: fields.get('ID') }],
|
|
40
|
+
Values: () => String(fields.get('ID') ?? ''),
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
async Save() {
|
|
44
|
+
dirty = false;
|
|
45
|
+
return true;
|
|
46
|
+
},
|
|
47
|
+
async Delete() {
|
|
48
|
+
return true;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
return new Proxy(methods, {
|
|
52
|
+
get(target, prop) {
|
|
53
|
+
// Check methods first
|
|
54
|
+
if (prop in target) {
|
|
55
|
+
const val = target[prop];
|
|
56
|
+
return val;
|
|
57
|
+
}
|
|
58
|
+
// Then check data fields
|
|
59
|
+
const lowerProp = prop.toLowerCase();
|
|
60
|
+
for (const [key, value] of fields) {
|
|
61
|
+
if (key.toLowerCase() === lowerProp) {
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
},
|
|
67
|
+
set(_target, prop, value) {
|
|
68
|
+
fields.set(prop, value);
|
|
69
|
+
dirty = true;
|
|
70
|
+
return true;
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=mock-entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-entity.js","sourceRoot":"","sources":["../src/mock-entity.ts"],"names":[],"mappings":"AAUA;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAO,EACP,UAA6B,EAAE;IAE/B,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IACpD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAkB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAkB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACjE,IAAI,KAAK,GAAG,OAAO,CAAC;IAEpB,MAAM,OAAO,GAAsB;QACjC,GAAG,CAAC,SAAiB;YACnB,MAAM,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;YAC3F,OAAO,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACrD,CAAC;QACD,GAAG,CAAC,SAAiB,EAAE,KAAc;YACnC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YAC7B,KAAK,GAAG,IAAI,CAAC;QACf,CAAC;QACD,MAAM;YACJ,MAAM,MAAM,GAA4B,EAAE,CAAC;YAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;gBAClC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACtB,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,IAAI,KAAK;YACP,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,OAAO;YACT,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,IAAI,UAAU;YACZ,OAAO;gBACL,aAAa,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7D,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;aAC7C,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,IAAI;YACR,KAAK,GAAG,KAAK,CAAC;YACd,OAAO,IAAI,CAAC;QACd,CAAC;QACD,KAAK,CAAC,MAAM;YACV,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC;IAEF,OAAO,IAAI,KAAK,CAAC,OAAO,EAAE;QACxB,GAAG,CAAC,MAAM,EAAE,IAAY;YACtB,sBAAsB;YACtB,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;gBACnB,MAAM,GAAG,GAAG,MAAM,CAAC,IAA+B,CAAC,CAAC;gBACpD,OAAO,GAAG,CAAC;YACb,CAAC;YACD,yBAAyB;YACzB,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YACrC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;gBAClC,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,SAAS,EAAE,CAAC;oBACpC,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,GAAG,CAAC,OAAO,EAAE,IAAY,EAAE,KAAK;YAC9B,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACxB,KAAK,GAAG,IAAI,CAAC;YACb,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAA0B,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
interface RunViewResult<T = unknown> {
|
|
2
|
+
Success: boolean;
|
|
3
|
+
Results: T[];
|
|
4
|
+
ErrorMessage?: string;
|
|
5
|
+
TotalRowCount?: number;
|
|
6
|
+
RowCount: number;
|
|
7
|
+
Metrics?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
type RunViewMockMap = Map<string, unknown[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Configure mock responses for RunView calls.
|
|
12
|
+
* Responses are keyed by entity name (case-insensitive).
|
|
13
|
+
*
|
|
14
|
+
* @param responses Map of entity name to array of result objects
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* mockRunView(new Map([
|
|
19
|
+
* ['Users', [{ ID: '1', Name: 'Test User' }]],
|
|
20
|
+
* ['AI Models', [{ ID: '2', Name: 'GPT-4' }]],
|
|
21
|
+
* ]));
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function mockRunView(responses: RunViewMockMap): void;
|
|
25
|
+
/**
|
|
26
|
+
* Create a mock RunView instance that returns configured test data.
|
|
27
|
+
* Use with vi.mock() to replace the real RunView.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createMockRunViewClass(): {
|
|
30
|
+
new (): {
|
|
31
|
+
RunView<T = unknown>(params: {
|
|
32
|
+
EntityName?: string;
|
|
33
|
+
ExtraFilter?: string;
|
|
34
|
+
}): Promise<RunViewResult<T>>;
|
|
35
|
+
RunViews<T = unknown>(paramsList: Array<{
|
|
36
|
+
EntityName?: string;
|
|
37
|
+
}>): Promise<Array<RunViewResult<T>>>;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Configure mock responses for batch RunViews calls.
|
|
42
|
+
* Same as mockRunView but semantically indicates batch usage.
|
|
43
|
+
*/
|
|
44
|
+
export declare function mockRunViews(responses: RunViewMockMap): void;
|
|
45
|
+
/**
|
|
46
|
+
* Reset all RunView mock responses.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resetRunViewMocks(): void;
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=mock-run-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-run-view.d.ts","sourceRoot":"","sources":["../src/mock-run-view.ts"],"names":[],"mappings":"AAEA,UAAU,aAAa,CAAC,CAAC,GAAG,OAAO;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,CAAC,EAAE,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,KAAK,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;AAI7C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,CAI3D;AAED;;;GAGG;AACH,wBAAgB,sBAAsB;;gBAEpB,CAAC,oBAAoB;YAAE,UAAU,CAAC,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;iBAW7F,CAAC,wBAAwB,KAAK,CAAC;YAAE,UAAU,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;;EAQ5G;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,CAE5D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
let _mockResponses = new Map();
|
|
2
|
+
/**
|
|
3
|
+
* Configure mock responses for RunView calls.
|
|
4
|
+
* Responses are keyed by entity name (case-insensitive).
|
|
5
|
+
*
|
|
6
|
+
* @param responses Map of entity name to array of result objects
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* mockRunView(new Map([
|
|
11
|
+
* ['Users', [{ ID: '1', Name: 'Test User' }]],
|
|
12
|
+
* ['AI Models', [{ ID: '2', Name: 'GPT-4' }]],
|
|
13
|
+
* ]));
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function mockRunView(responses) {
|
|
17
|
+
_mockResponses = new Map([...responses.entries()].map(([k, v]) => [k.toLowerCase(), v]));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a mock RunView instance that returns configured test data.
|
|
21
|
+
* Use with vi.mock() to replace the real RunView.
|
|
22
|
+
*/
|
|
23
|
+
export function createMockRunViewClass() {
|
|
24
|
+
return class MockRunView {
|
|
25
|
+
async RunView(params) {
|
|
26
|
+
const entityName = params.EntityName?.toLowerCase() ?? '';
|
|
27
|
+
const results = (_mockResponses.get(entityName) ?? []);
|
|
28
|
+
return {
|
|
29
|
+
Success: true,
|
|
30
|
+
Results: results,
|
|
31
|
+
RowCount: results.length,
|
|
32
|
+
TotalRowCount: results.length,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async RunViews(paramsList) {
|
|
36
|
+
const results = [];
|
|
37
|
+
for (const params of paramsList) {
|
|
38
|
+
results.push(await this.RunView(params));
|
|
39
|
+
}
|
|
40
|
+
return results;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Configure mock responses for batch RunViews calls.
|
|
46
|
+
* Same as mockRunView but semantically indicates batch usage.
|
|
47
|
+
*/
|
|
48
|
+
export function mockRunViews(responses) {
|
|
49
|
+
mockRunView(responses);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Reset all RunView mock responses.
|
|
53
|
+
*/
|
|
54
|
+
export function resetRunViewMocks() {
|
|
55
|
+
_mockResponses = new Map();
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=mock-run-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-run-view.js","sourceRoot":"","sources":["../src/mock-run-view.ts"],"names":[],"mappings":"AAaA,IAAI,cAAc,GAAmB,IAAI,GAAG,EAAE,CAAC;AAE/C;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CAAC,SAAyB;IACnD,cAAc,GAAG,IAAI,GAAG,CACtB,CAAC,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,CAC/D,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,MAAM,WAAW;QACtB,KAAK,CAAC,OAAO,CAAc,MAAqD;YAC9E,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;YAC1D,MAAM,OAAO,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAQ,CAAC;YAC9D,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,OAAO;gBAChB,QAAQ,EAAE,OAAO,CAAC,MAAM;gBACxB,aAAa,EAAE,OAAO,CAAC,MAAM;aAC9B,CAAC;QACJ,CAAC;QAED,KAAK,CAAC,QAAQ,CAAc,UAA0C;YACpE,MAAM,OAAO,GAA4B,EAAE,CAAC;YAC5C,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;gBAChC,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,CAAI,MAAM,CAAC,CAAC,CAAC;YAC9C,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,SAAyB;IACpD,WAAW,CAAC,SAAS,CAAC,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,cAAc,GAAG,IAAI,GAAG,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reset ALL MJ singletons by clearing their entries from the global object store.
|
|
3
|
+
* Call this in beforeEach() to ensure clean test isolation.
|
|
4
|
+
*/
|
|
5
|
+
export declare function resetMJSingletons(): void;
|
|
6
|
+
/**
|
|
7
|
+
* Reset just the ClassFactory registrations without destroying the MJGlobal singleton.
|
|
8
|
+
* Lighter weight than resetMJSingletons - use when you only need a clean ClassFactory.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resetClassFactory(): void;
|
|
11
|
+
/**
|
|
12
|
+
* Clear the global ObjectCache.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resetObjectCache(): void;
|
|
15
|
+
//# sourceMappingURL=singleton-reset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singleton-reset.d.ts","sourceRoot":"","sources":["../src/singleton-reset.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CASxC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
2
|
+
import { GetGlobalObjectStore } from '@memberjunction/global';
|
|
3
|
+
/**
|
|
4
|
+
* Reset ALL MJ singletons by clearing their entries from the global object store.
|
|
5
|
+
* Call this in beforeEach() to ensure clean test isolation.
|
|
6
|
+
*/
|
|
7
|
+
export function resetMJSingletons() {
|
|
8
|
+
const store = GetGlobalObjectStore();
|
|
9
|
+
if (store) {
|
|
10
|
+
const indexableStore = store;
|
|
11
|
+
const singletonKeys = Object.keys(indexableStore).filter(k => k.startsWith('___SINGLETON__'));
|
|
12
|
+
for (const key of singletonKeys) {
|
|
13
|
+
delete indexableStore[key];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Reset just the ClassFactory registrations without destroying the MJGlobal singleton.
|
|
19
|
+
* Lighter weight than resetMJSingletons - use when you only need a clean ClassFactory.
|
|
20
|
+
*/
|
|
21
|
+
export function resetClassFactory() {
|
|
22
|
+
MJGlobal.Instance.Reset();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Clear the global ObjectCache.
|
|
26
|
+
*/
|
|
27
|
+
export function resetObjectCache() {
|
|
28
|
+
MJGlobal.Instance.ObjectCache.Clear();
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=singleton-reset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singleton-reset.js","sourceRoot":"","sources":["../src/singleton-reset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,KAAK,GAAG,oBAAoB,EAAE,CAAC;IACrC,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,cAAc,GAAG,KAAgC,CAAC;QACxD,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAC9F,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;YAChC,OAAO,cAAc,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;AACxC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/unit-testing",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "4.3.0",
|
|
5
|
+
"description": "MemberJunction: Unit testing utilities and mock infrastructure for Vitest",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"/dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc && tsc-alias -f",
|
|
13
|
+
"test": "vitest run --passWithNoTests",
|
|
14
|
+
"test:watch": "vitest"
|
|
15
|
+
},
|
|
16
|
+
"author": "MemberJunction.com",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"ts-node-dev": "^2.0.0",
|
|
20
|
+
"typescript": "^5.9.3",
|
|
21
|
+
"vitest": "^4.0.18"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@memberjunction/global": "4.3.0"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/MemberJunction/MJ"
|
|
29
|
+
}
|
|
10
30
|
}
|