@git.zone/tstest 1.9.1 → 1.9.3
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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/tstest.classes.tap.parser.js +3 -3
- package/dist_ts/tstest.logging.js +10 -3
- package/dist_ts_tapbundle/index.d.ts +1 -0
- package/dist_ts_tapbundle/index.js +3 -1
- package/dist_ts_tapbundle/tapbundle.protocols.d.ts +88 -0
- package/dist_ts_tapbundle/tapbundle.protocols.js +168 -0
- package/package.json +1 -1
- package/readme.hints.md +47 -2
- package/readme.md +357 -51
- package/readme.plan.md +101 -133
- package/readme.protocol.md +287 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/tstest.classes.tap.parser.ts +2 -2
- package/ts/tstest.logging.ts +10 -2
package/readme.md
CHANGED
|
@@ -141,9 +141,9 @@ tstest supports different test environments through file naming:
|
|
|
141
141
|
| `*.browser.ts` | Browser environment | `test.ui.browser.ts` |
|
|
142
142
|
| `*.both.ts` | Both Node.js and browser | `test.isomorphic.both.ts` |
|
|
143
143
|
|
|
144
|
-
### Writing Tests
|
|
144
|
+
### Writing Tests with tapbundle
|
|
145
145
|
|
|
146
|
-
tstest includes a
|
|
146
|
+
tstest includes tapbundle, a powerful TAP-based test framework. Import it from the embedded tapbundle:
|
|
147
147
|
|
|
148
148
|
```typescript
|
|
149
149
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
@@ -164,100 +164,392 @@ tstest provides multiple exports for different use cases:
|
|
|
164
164
|
- `@git.zone/tstest/tapbundle` - Browser-compatible test framework
|
|
165
165
|
- `@git.zone/tstest/tapbundle_node` - Node.js-specific test utilities
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
## tapbundle Test Framework
|
|
168
|
+
|
|
169
|
+
### Basic Test Syntax
|
|
168
170
|
|
|
169
|
-
**Tag-based Test Filtering**
|
|
170
171
|
```typescript
|
|
171
|
-
tap
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
173
|
+
|
|
174
|
+
// Basic test
|
|
175
|
+
tap.test('should perform basic arithmetic', async () => {
|
|
176
|
+
expect(2 + 2).toEqual(4);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Async test with tools
|
|
180
|
+
tap.test('async operations', async (tools) => {
|
|
181
|
+
await tools.delayFor(100); // delay for 100ms
|
|
182
|
+
const result = await fetchData();
|
|
183
|
+
expect(result).toBeDefined();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Start test execution
|
|
187
|
+
tap.start();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Test Modifiers and Chaining
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// Skip a test
|
|
194
|
+
tap.skip.test('not ready yet', async () => {
|
|
195
|
+
// This test will be skipped
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Run only this test (exclusive)
|
|
199
|
+
tap.only.test('focus on this', async () => {
|
|
200
|
+
// Only this test will run
|
|
201
|
+
});
|
|
175
202
|
|
|
176
|
-
//
|
|
203
|
+
// Todo test
|
|
204
|
+
tap.todo('implement later', async () => {
|
|
205
|
+
// Marked as todo
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Chaining modifiers
|
|
209
|
+
tap.timeout(5000)
|
|
210
|
+
.retry(3)
|
|
211
|
+
.tags('api', 'integration')
|
|
212
|
+
.test('complex test', async (tools) => {
|
|
213
|
+
// Test with 5s timeout, 3 retries, and tags
|
|
214
|
+
});
|
|
177
215
|
```
|
|
178
216
|
|
|
179
|
-
|
|
217
|
+
### Test Organization with describe()
|
|
218
|
+
|
|
180
219
|
```typescript
|
|
181
|
-
tap.describe('User
|
|
182
|
-
let
|
|
220
|
+
tap.describe('User Management', () => {
|
|
221
|
+
let testDatabase;
|
|
183
222
|
|
|
184
223
|
tap.beforeEach(async () => {
|
|
185
|
-
|
|
224
|
+
testDatabase = await createTestDB();
|
|
186
225
|
});
|
|
187
226
|
|
|
188
227
|
tap.afterEach(async () => {
|
|
189
|
-
await
|
|
228
|
+
await testDatabase.cleanup();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
tap.test('should create user', async () => {
|
|
232
|
+
const user = await testDatabase.createUser({ name: 'John' });
|
|
233
|
+
expect(user.id).toBeDefined();
|
|
190
234
|
});
|
|
191
235
|
|
|
192
|
-
tap.
|
|
193
|
-
|
|
236
|
+
tap.describe('User Permissions', () => {
|
|
237
|
+
tap.test('should set admin role', async () => {
|
|
238
|
+
// Nested describe blocks
|
|
239
|
+
});
|
|
194
240
|
});
|
|
195
241
|
});
|
|
196
242
|
```
|
|
197
243
|
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
// Files with matching parallel group names run concurrently
|
|
201
|
-
// test.auth.para__1.ts
|
|
202
|
-
tap.test('authentication test', async () => { /* ... */ });
|
|
244
|
+
### Test Tools (Available in Test Function)
|
|
203
245
|
|
|
204
|
-
|
|
205
|
-
tap.test('user operations test', async () => { /* ... */ });
|
|
206
|
-
```
|
|
246
|
+
Every test function receives a `tools` parameter with utilities:
|
|
207
247
|
|
|
208
|
-
**Test Timeouts and Retries**
|
|
209
248
|
```typescript
|
|
210
|
-
tap.
|
|
211
|
-
|
|
212
|
-
.
|
|
213
|
-
|
|
249
|
+
tap.test('using test tools', async (tools) => {
|
|
250
|
+
// Delay utilities
|
|
251
|
+
await tools.delayFor(1000); // delay for 1000ms
|
|
252
|
+
await tools.delayForRandom(100, 500); // random delay between 100-500ms
|
|
253
|
+
|
|
254
|
+
// Skip test conditionally
|
|
255
|
+
tools.skipIf(process.env.CI === 'true', 'Skipping in CI');
|
|
256
|
+
|
|
257
|
+
// Skip test unconditionally
|
|
258
|
+
if (!apiKeyAvailable) {
|
|
259
|
+
tools.skip('API key not available');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Mark as todo
|
|
263
|
+
tools.todo('Needs implementation');
|
|
264
|
+
|
|
265
|
+
// Retry configuration
|
|
266
|
+
tools.retry(3); // Set retry count
|
|
267
|
+
|
|
268
|
+
// Timeout configuration
|
|
269
|
+
tools.timeout(10000); // Set timeout to 10s
|
|
270
|
+
|
|
271
|
+
// Context sharing between tests
|
|
272
|
+
tools.context.set('userId', 12345);
|
|
273
|
+
const userId = tools.context.get('userId');
|
|
274
|
+
|
|
275
|
+
// Deferred promises
|
|
276
|
+
const deferred = tools.defer();
|
|
277
|
+
setTimeout(() => deferred.resolve('done'), 100);
|
|
278
|
+
await deferred.promise;
|
|
279
|
+
|
|
280
|
+
// Colored console output
|
|
281
|
+
const coloredString = await tools.coloredString('Success!', 'green');
|
|
282
|
+
console.log(coloredString);
|
|
283
|
+
|
|
284
|
+
// Error handling helper
|
|
285
|
+
const error = await tools.returnError(async () => {
|
|
286
|
+
throw new Error('Expected error');
|
|
214
287
|
});
|
|
288
|
+
expect(error).toBeInstanceOf(Error);
|
|
289
|
+
});
|
|
215
290
|
```
|
|
216
291
|
|
|
217
|
-
|
|
292
|
+
### Snapshot Testing
|
|
293
|
+
|
|
218
294
|
```typescript
|
|
219
|
-
tap.test('
|
|
220
|
-
const
|
|
221
|
-
|
|
295
|
+
tap.test('snapshot test', async (tools) => {
|
|
296
|
+
const output = generateComplexOutput();
|
|
297
|
+
|
|
298
|
+
// Compare with saved snapshot
|
|
299
|
+
await tools.matchSnapshot(output);
|
|
300
|
+
|
|
301
|
+
// Named snapshots for multiple checks in one test
|
|
302
|
+
await tools.matchSnapshot(output.header, 'header');
|
|
303
|
+
await tools.matchSnapshot(output.body, 'body');
|
|
222
304
|
});
|
|
305
|
+
|
|
306
|
+
// Update snapshots with: UPDATE_SNAPSHOTS=true tstest test/
|
|
223
307
|
```
|
|
224
308
|
|
|
225
|
-
|
|
309
|
+
### Test Fixtures
|
|
310
|
+
|
|
226
311
|
```typescript
|
|
227
|
-
// Define
|
|
228
|
-
tap.defineFixture('testUser', async () => ({
|
|
229
|
-
id:
|
|
230
|
-
name: 'Test User',
|
|
231
|
-
email: 'test@example.com'
|
|
312
|
+
// Define reusable fixtures
|
|
313
|
+
tap.defineFixture('testUser', async (data) => ({
|
|
314
|
+
id: Date.now(),
|
|
315
|
+
name: data?.name || 'Test User',
|
|
316
|
+
email: data?.email || 'test@example.com',
|
|
317
|
+
created: new Date()
|
|
318
|
+
}));
|
|
319
|
+
|
|
320
|
+
tap.defineFixture('testPost', async (data) => ({
|
|
321
|
+
id: Date.now(),
|
|
322
|
+
title: data?.title || 'Test Post',
|
|
323
|
+
authorId: data?.authorId || 1
|
|
232
324
|
}));
|
|
233
325
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
326
|
+
// Use fixtures in tests
|
|
327
|
+
tap.test('fixture test', async (tools) => {
|
|
328
|
+
const user = await tools.fixture('testUser', { name: 'John' });
|
|
329
|
+
const post = await tools.fixture('testPost', { authorId: user.id });
|
|
330
|
+
|
|
331
|
+
expect(post.authorId).toEqual(user.id);
|
|
332
|
+
|
|
333
|
+
// Factory pattern for multiple instances
|
|
334
|
+
const users = await tools.factory('testUser').createMany(5);
|
|
335
|
+
expect(users).toHaveLength(5);
|
|
237
336
|
});
|
|
238
337
|
```
|
|
239
338
|
|
|
240
|
-
|
|
339
|
+
### Parallel Test Execution
|
|
340
|
+
|
|
241
341
|
```typescript
|
|
242
|
-
|
|
243
|
-
|
|
342
|
+
// Parallel tests within a file
|
|
343
|
+
tap.testParallel('parallel test 1', async () => {
|
|
344
|
+
await heavyOperation();
|
|
244
345
|
});
|
|
245
346
|
|
|
246
|
-
tap.
|
|
247
|
-
|
|
347
|
+
tap.testParallel('parallel test 2', async () => {
|
|
348
|
+
await anotherHeavyOperation();
|
|
248
349
|
});
|
|
350
|
+
|
|
351
|
+
// File naming for parallel groups
|
|
352
|
+
// test.api.para__1.ts - runs in parallel with other para__1 files
|
|
353
|
+
// test.db.para__1.ts - runs in parallel with other para__1 files
|
|
354
|
+
// test.auth.para__2.ts - runs after para__1 group completes
|
|
249
355
|
```
|
|
250
356
|
|
|
251
|
-
|
|
357
|
+
### Assertions with expect()
|
|
358
|
+
|
|
359
|
+
tapbundle uses @push.rocks/smartexpect for assertions:
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
// Basic assertions
|
|
363
|
+
expect(value).toEqual(5);
|
|
364
|
+
expect(value).not.toEqual(10);
|
|
365
|
+
expect(obj).toDeepEqual({ a: 1, b: 2 });
|
|
366
|
+
|
|
367
|
+
// Type assertions
|
|
368
|
+
expect('hello').toBeTypeofString();
|
|
369
|
+
expect(42).toBeTypeofNumber();
|
|
370
|
+
expect(true).toBeTypeofBoolean();
|
|
371
|
+
expect([]).toBeArray();
|
|
372
|
+
expect({}).toBeTypeOf('object');
|
|
373
|
+
|
|
374
|
+
// Comparison assertions
|
|
375
|
+
expect(5).toBeGreaterThan(3);
|
|
376
|
+
expect(3).toBeLessThan(5);
|
|
377
|
+
expect(5).toBeGreaterThanOrEqual(5);
|
|
378
|
+
expect(5).toBeLessThanOrEqual(5);
|
|
379
|
+
expect(0.1 + 0.2).toBeCloseTo(0.3, 10);
|
|
380
|
+
|
|
381
|
+
// Truthiness
|
|
382
|
+
expect(true).toBeTrue();
|
|
383
|
+
expect(false).toBeFalse();
|
|
384
|
+
expect('text').toBeTruthy();
|
|
385
|
+
expect(0).toBeFalsy();
|
|
386
|
+
expect(null).toBeNull();
|
|
387
|
+
expect(undefined).toBeUndefined();
|
|
388
|
+
expect(null).toBeNullOrUndefined();
|
|
389
|
+
|
|
390
|
+
// String assertions
|
|
391
|
+
expect('hello world').toStartWith('hello');
|
|
392
|
+
expect('hello world').toEndWith('world');
|
|
393
|
+
expect('hello world').toInclude('lo wo');
|
|
394
|
+
expect('hello world').toMatch(/^hello/);
|
|
395
|
+
expect('option').toBeOneOf(['choice', 'option', 'alternative']);
|
|
396
|
+
|
|
397
|
+
// Array assertions
|
|
398
|
+
expect([1, 2, 3]).toContain(2);
|
|
399
|
+
expect([1, 2, 3]).toContainAll([1, 3]);
|
|
400
|
+
expect([1, 2, 3]).toExclude(4);
|
|
401
|
+
expect([1, 2, 3]).toHaveLength(3);
|
|
402
|
+
expect([]).toBeEmptyArray();
|
|
403
|
+
expect([{ id: 1 }]).toContainEqual({ id: 1 });
|
|
404
|
+
|
|
405
|
+
// Object assertions
|
|
406
|
+
expect(obj).toHaveProperty('name');
|
|
407
|
+
expect(obj).toHaveProperty('user.email', 'test@example.com');
|
|
408
|
+
expect(obj).toHaveDeepProperty(['level1', 'level2']);
|
|
409
|
+
expect(obj).toMatchObject({ name: 'John' });
|
|
410
|
+
|
|
411
|
+
// Function assertions
|
|
412
|
+
expect(() => { throw new Error('test'); }).toThrow();
|
|
413
|
+
expect(() => { throw new Error('test'); }).toThrow(Error);
|
|
414
|
+
expect(() => { throw new Error('test error'); }).toThrowErrorMatching(/test/);
|
|
415
|
+
expect(myFunction).not.toThrow();
|
|
416
|
+
|
|
417
|
+
// Promise assertions
|
|
418
|
+
await expect(Promise.resolve('value')).resolves.toEqual('value');
|
|
419
|
+
await expect(Promise.reject(new Error('fail'))).rejects.toThrow();
|
|
420
|
+
|
|
421
|
+
// Custom assertions
|
|
422
|
+
expect(7).customAssertion(
|
|
423
|
+
value => value % 2 === 1,
|
|
424
|
+
'Value is not odd'
|
|
425
|
+
);
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Pre-tasks
|
|
429
|
+
|
|
430
|
+
Run setup tasks before tests start:
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
tap.preTask('setup database', async () => {
|
|
434
|
+
await initializeTestDatabase();
|
|
435
|
+
console.log('Database initialized');
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
tap.preTask('load environment', async () => {
|
|
439
|
+
await loadTestEnvironment();
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Pre-tasks run in order before any tests
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Tag-based Test Filtering
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
// Tag individual tests
|
|
449
|
+
tap.tags('unit', 'api')
|
|
450
|
+
.test('api unit test', async () => {
|
|
451
|
+
// Test code
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
tap.tags('integration', 'slow')
|
|
455
|
+
.test('database integration', async () => {
|
|
456
|
+
// Test code
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// Run only tests with specific tags
|
|
460
|
+
// tstest test/ --tags unit,api
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### Context Sharing
|
|
464
|
+
|
|
465
|
+
Share data between tests:
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
tap.test('first test', async (tools) => {
|
|
469
|
+
const sessionId = await createSession();
|
|
470
|
+
tools.context.set('sessionId', sessionId);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
tap.test('second test', async (tools) => {
|
|
474
|
+
const sessionId = tools.context.get('sessionId');
|
|
475
|
+
expect(sessionId).toBeDefined();
|
|
476
|
+
|
|
477
|
+
// Cleanup
|
|
478
|
+
tools.context.delete('sessionId');
|
|
479
|
+
});
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Browser Testing with webhelpers
|
|
483
|
+
|
|
484
|
+
For browser-specific tests:
|
|
485
|
+
|
|
252
486
|
```typescript
|
|
253
|
-
// test.browser.ts
|
|
254
487
|
import { tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
|
255
488
|
|
|
256
489
|
tap.test('DOM manipulation', async () => {
|
|
490
|
+
// Create DOM elements from HTML strings
|
|
257
491
|
const element = await webhelpers.fixture(webhelpers.html`
|
|
258
|
-
<div
|
|
492
|
+
<div class="test-container">
|
|
493
|
+
<h1>Test Title</h1>
|
|
494
|
+
<button id="test-btn">Click Me</button>
|
|
495
|
+
</div>
|
|
259
496
|
`);
|
|
260
|
-
|
|
497
|
+
|
|
498
|
+
expect(element.querySelector('h1').textContent).toEqual('Test Title');
|
|
499
|
+
|
|
500
|
+
// Simulate interactions
|
|
501
|
+
const button = element.querySelector('#test-btn');
|
|
502
|
+
button.click();
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
tap.test('CSS testing', async () => {
|
|
506
|
+
const styles = webhelpers.css`
|
|
507
|
+
.test-class {
|
|
508
|
+
color: red;
|
|
509
|
+
font-size: 16px;
|
|
510
|
+
}
|
|
511
|
+
`;
|
|
512
|
+
|
|
513
|
+
// styles is a string that can be injected into the page
|
|
514
|
+
expect(styles).toInclude('color: red');
|
|
515
|
+
});
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Advanced Error Handling
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
tap.test('error handling', async (tools) => {
|
|
522
|
+
// Capture errors without failing the test
|
|
523
|
+
const error = await tools.returnError(async () => {
|
|
524
|
+
await functionThatThrows();
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
expect(error).toBeInstanceOf(Error);
|
|
528
|
+
expect(error.message).toEqual('Expected error message');
|
|
529
|
+
});
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### Test Wrap
|
|
533
|
+
|
|
534
|
+
Create wrapped test environments:
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
import { TapWrap } from '@git.zone/tstest/tapbundle';
|
|
538
|
+
|
|
539
|
+
const tapWrap = new TapWrap({
|
|
540
|
+
before: async () => {
|
|
541
|
+
console.log('Before all tests');
|
|
542
|
+
await globalSetup();
|
|
543
|
+
},
|
|
544
|
+
after: async () => {
|
|
545
|
+
console.log('After all tests');
|
|
546
|
+
await globalCleanup();
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// Tests registered here will have the wrap lifecycle
|
|
551
|
+
tapWrap.tap.test('wrapped test', async () => {
|
|
552
|
+
// This test runs with the wrap setup/teardown
|
|
261
553
|
});
|
|
262
554
|
```
|
|
263
555
|
|
|
@@ -330,6 +622,20 @@ tstest test/ --quiet
|
|
|
330
622
|
|
|
331
623
|
## Changelog
|
|
332
624
|
|
|
625
|
+
### Version 1.9.2
|
|
626
|
+
- 🐛 Fixed test timing display issue (removed duplicate timing in output)
|
|
627
|
+
- 📝 Improved internal protocol design documentation
|
|
628
|
+
- 🔧 Added protocol v2 utilities for future improvements
|
|
629
|
+
|
|
630
|
+
### Version 1.9.1
|
|
631
|
+
- 🐛 Fixed log file naming to preserve directory structure
|
|
632
|
+
- 📁 Log files now prevent collisions: `test__dir__file.log`
|
|
633
|
+
|
|
634
|
+
### Version 1.9.0
|
|
635
|
+
- 📚 Comprehensive documentation update
|
|
636
|
+
- 🏗️ Embedded tapbundle for better integration
|
|
637
|
+
- 🌐 Full browser compatibility
|
|
638
|
+
|
|
333
639
|
### Version 1.8.0
|
|
334
640
|
- 📦 Embedded tapbundle directly into tstest project
|
|
335
641
|
- 🌐 Made tapbundle fully browser-compatible
|