@git.zone/tstest 1.9.2 → 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/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 built-in TAP (Test Anything Protocol) test framework. Import it from the embedded tapbundle:
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
- #### Test Features
167
+ ## tapbundle Test Framework
168
+
169
+ ### Basic Test Syntax
168
170
 
169
- **Tag-based Test Filtering**
170
171
  ```typescript
171
- tap.tags('unit', 'api')
172
- .test('should handle API requests', async () => {
173
- // Test code
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
- // Run with: tstest test/ --tags unit,api
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
- **Test Lifecycle Hooks**
217
+ ### Test Organization with describe()
218
+
180
219
  ```typescript
181
- tap.describe('User API Tests', () => {
182
- let testUser;
220
+ tap.describe('User Management', () => {
221
+ let testDatabase;
183
222
 
184
223
  tap.beforeEach(async () => {
185
- testUser = await createTestUser();
224
+ testDatabase = await createTestDB();
186
225
  });
187
226
 
188
227
  tap.afterEach(async () => {
189
- await deleteTestUser(testUser.id);
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.test('should update user profile', async () => {
193
- // Test code using testUser
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
- **Parallel Test Execution**
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
- // test.user.para__1.ts
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.timeout(5000)
211
- .retry(3)
212
- .test('flaky network test', async (tools) => {
213
- // This test has 5 seconds to complete and will retry up to 3 times
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
- **Snapshot Testing**
292
+ ### Snapshot Testing
293
+
218
294
  ```typescript
219
- tap.test('should match snapshot', async (tools) => {
220
- const result = await generateReport();
221
- await tools.matchSnapshot(result);
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
- **Test Fixtures**
309
+ ### Test Fixtures
310
+
226
311
  ```typescript
227
- // Define a reusable fixture
228
- tap.defineFixture('testUser', async () => ({
229
- id: 1,
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
- tap.test('user test', async (tools) => {
235
- const user = tools.fixture('testUser');
236
- expect(user.name).toEqual('Test User');
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
- **Skipping and Todo Tests**
339
+ ### Parallel Test Execution
340
+
241
341
  ```typescript
242
- tap.skip.test('work in progress', async () => {
243
- // This test will be skipped
342
+ // Parallel tests within a file
343
+ tap.testParallel('parallel test 1', async () => {
344
+ await heavyOperation();
244
345
  });
245
346
 
246
- tap.todo('implement user deletion', async () => {
247
- // This marks a test as todo
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
- **Browser Testing**
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>Hello World</div>
492
+ <div class="test-container">
493
+ <h1>Test Title</h1>
494
+ <button id="test-btn">Click Me</button>
495
+ </div>
259
496
  `);
260
- expect(element).toBeInstanceOf(HTMLElement);
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