Compare commits

..

21 Commits

Author SHA1 Message Date
0379bd7288 v3.1.2 2025-11-21 15:45:53 +00:00
9ab4f1838d fix(docs): Update README: add issue reporting/security guidance and expanded changelog (3.1.1/3.1.0) 2025-11-21 15:45:53 +00:00
b9016206ce v3.1.1 2025-11-21 15:17:37 +00:00
8edbbd4850 fix(tapbundle): Pass TapTools to suite lifecycle hooks (beforeAll/afterAll) and update @push.rocks/smarts3 to ^3.0.0 2025-11-21 15:17:37 +00:00
97c91fc010 v3.1.0 2025-11-20 18:22:54 +00:00
ca08bb2e3c feat(tapbundle): Add global postTask (teardown) and suite lifecycle hooks (beforeAll/afterAll) to tapbundle 2025-11-20 18:22:54 +00:00
8fd114334f v3.0.1 2025-11-20 08:37:30 +00:00
c630a171b5 fix(@push.rocks/smarts3): Bump @push.rocks/smarts3 dependency to ^2.2.7 2025-11-20 08:37:30 +00:00
1a4eb5b6d9 v3.0.0 2025-11-19 21:43:15 +00:00
41d7c1ce49 BREAKING CHANGE(tapbundle_serverside): Rename Node-specific tapbundle module to tapbundle_serverside and migrate server-side utilities 2025-11-19 21:43:15 +00:00
3ab5550cb8 v2.8.3 2025-11-19 21:22:58 +00:00
ee7b387534 fix(dependencies): Update dependency versions 2025-11-19 21:22:58 +00:00
7e67b64a6e v2.8.2 2025-11-17 13:42:32 +00:00
1ce730d4f2 fix(logging): Include runtime identifier in per-test logfile name and sanitize runtime string 2025-11-17 13:42:32 +00:00
9357d6e7ef v2.8.1 2025-11-17 01:52:00 +00:00
973ce771d2 fix(config): Remove Bun config file and set deno.json useDefineForClassFields to false for compatibility 2025-11-17 01:52:00 +00:00
8441881d92 v2.8.0 2025-11-17 01:21:20 +00:00
16ca3b6374 feat(runtime-adapters): Enable TypeScript decorator support for Deno and Bun runtimes and add decorator tests 2025-11-17 01:21:20 +00:00
b94089652e 2.7.0 2025-10-26 19:47:06 +00:00
ef6f21fc9c feat(tapbundle_protocol): Add package export for tapbundle_protocol to expose protocol utilities 2025-10-26 19:47:06 +00:00
592a4f33c0 feat(docker): add Docker test file support and runtime adapter 2025-10-26 19:35:10 +00:00
30 changed files with 3833 additions and 3903 deletions

View File

@@ -22,5 +22,6 @@
} }
} }
} }
] ],
"deno.enable": false
} }

View File

@@ -1,5 +1,81 @@
# Changelog # Changelog
## 2025-11-21 - 3.1.2 - fix(docs)
Update README: add issue reporting/security guidance and expanded changelog (3.1.1/3.1.0)
- Add 'Issue Reporting and Security' section pointing to https://community.foss.global/ for bug/security reports and contributor onboarding.
- Expand Changelog with Version 3.1.1 notes: fixed TapTools parameter passing to suite lifecycle hooks (beforeAll/afterAll) and updated @push.rocks/smarts3 dependency to ^3.0.0.
- Include Changelog entries for Version 3.1.0: postTask() API, suite beforeAll/afterAll, new parallel() fluent API, and enhanced tapbundle documentation.
- Documentation-only change (no source code modifications).
## 2025-11-21 - 3.1.1 - fix(tapbundle)
Pass TapTools to suite lifecycle hooks (beforeAll/afterAll) and update @push.rocks/smarts3 to ^3.0.0
- Replace usage of a Deferred promise with a TapTools instance when invoking suite.beforeAll and suite.afterAll
- Add import for TapTools in ts_tapbundle/tapbundle.classes.tap.ts
- Bump dependency @push.rocks/smarts3 from ^2.2.7 to ^3.0.0 in package.json
## 2025-11-20 - 3.1.0 - feat(tapbundle)
Add global postTask (teardown) and suite lifecycle hooks (beforeAll/afterAll) to tapbundle
- Introduce PostTask class (ts_tapbundle/tapbundle.classes.posttask.ts) and tap.postTask() API for global teardown.
- Integrate postTask execution into Tap.start() so postTasks run after all tests and before the global afterAll hook.
- Add suite-level beforeAll and afterAll support and ensure afterAll runs after child suites and their tests (changes in ts_tapbundle/tapbundle.classes.tap.ts).
- Add lifecycle tests (test/tapbundle/test.new-lifecycle.ts) verifying execution order, including parallel tests.
- Update documentation (readme.hints.md) describing Phase 1 API improvements and usage notes.
- This is additive and backward-compatible (no breaking changes).
## 2025-11-20 - 3.0.1 - fix(@push.rocks/smarts3)
Bump @push.rocks/smarts3 dependency to ^2.2.7
- Update package.json: @push.rocks/smarts3 upgraded from ^2.2.6 to ^2.2.7
## 2025-11-19 - 3.0.0 - BREAKING CHANGE(tapbundle_serverside)
Rename Node-specific tapbundle module to tapbundle_serverside and migrate server-side utilities
- Change public export in package.json from ./tapbundle_node to ./tapbundle_serverside — consumers must update imports to @git.zone/tstest/tapbundle_serverside
- Move and re-create Node-only implementation files under ts_tapbundle_serverside (plugins, paths, classes.tapnodetools, classes.testfileprovider, index, tspublish.json) and remove legacy ts_tapbundle_node sources
- Update internal imports and tests to reference the new tapbundle_serverside path (e.g. test/tapbundle/test.node.ts updated)
- Update documentation (readme.md and readme.hints.md) to describe the new tapbundle_serverside export and its server-side utilities
- Ensure build outputs and publish metadata reflect the new module directory (tspublish.json order preserved)
## 2025-11-19 - 2.8.3 - fix(dependencies)
Update dependency versions
- Bump devDependency @git.zone/tsbuild to ^3.1.0
- Upgrade @git.zone/tsrun to ^2.0.0 (major)
- Upgrade @push.rocks/smartenv to ^6.0.0 (major)
- Upgrade @push.rocks/smartrequest to ^5.0.1 (major/feature in dependency)
- Patch updates: @api.global/typedserver → ^3.0.80, @git.zone/tsbundle → ^2.5.2, @push.rocks/smartmongo → ^2.0.14
## 2025-11-17 - 2.8.2 - fix(logging)
Include runtime identifier in per-test logfile name and sanitize runtime string
- Append a sanitized runtime identifier to the per-test log filename (format: <safeFilename>__<safeRuntime>.log) so runs for different runtimes don't clash
- Sanitize runtime names by lowercasing and removing non-alphanumeric characters to produce filesystem-safe filenames
## 2025-11-17 - 2.8.1 - fix(config)
Remove Bun config file and set deno.json useDefineForClassFields to false for compatibility
- Removed bunfig.toml (Bun-specific TypeScript decorator configuration) — stops shipping a project-local Bun transpiler config.
- Updated deno.json: set compilerOptions.useDefineForClassFields = false to keep legacy class field semantics and avoid runtime/emit incompatibilities in Deno.
## 2025-11-17 - 2.8.0 - feat(runtime-adapters)
Enable TypeScript decorator support for Deno and Bun runtimes and add decorator tests
- Add bunfig.toml to enable experimentalDecorators for Bun runtime
- Add deno.json to enable experimentalDecorators and set target/lib for Deno
- Update Bun runtime adapter to note bunfig.toml discovery so Bun runs with decorator support
- Update Deno runtime adapter to auto-detect deno.json / deno.jsonc and pass configPath in default options
- Add integration tests for decorators (test/decorator.all.ts) to verify decorator support across runtimes
## 2025-10-26 - 2.7.0 - feat(tapbundle_protocol)
Add package export for tapbundle_protocol to expose protocol utilities
- Add './tapbundle_protocol' export in package.json pointing to './dist_ts_tapbundle_protocol/index.js'.
- Allows consumers to import protocol utilities (ProtocolEmitter, ProtocolParser, types) via '@git.zone/tstest/tapbundle_protocol'.
- Non-breaking: only extends package exports surface.
## 2025-10-17 - 2.6.2 - fix(@push.rocks/smartrequest) ## 2025-10-17 - 2.6.2 - fix(@push.rocks/smartrequest)
Bump @push.rocks/smartrequest from ^4.3.1 to ^4.3.2 Bump @push.rocks/smartrequest from ^4.3.1 to ^4.3.2

13
deno.json Normal file
View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false,
"lib": [
"ES2022",
"DOM"
],
"target": "ES2022"
},
"nodeModulesDir": true,
"version": "3.1.2"
}

1371
deno.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,13 @@
{ {
"name": "@git.zone/tstest", "name": "@git.zone/tstest",
"version": "2.6.2", "version": "3.1.2",
"private": false, "private": false,
"description": "a test utility to run tests that match test/**/*.ts", "description": "a test utility to run tests that match test/**/*.ts",
"exports": { "exports": {
".": "./dist_ts/index.js", ".": "./dist_ts/index.js",
"./tapbundle": "./dist_ts_tapbundle/index.js", "./tapbundle": "./dist_ts_tapbundle/index.js",
"./tapbundle_node": "./dist_ts_tapbundle_node/index.js" "./tapbundle_serverside": "./dist_ts_tapbundle_serverside/index.js",
"./tapbundle_protocol": "./dist_ts_tapbundle_protocol/index.js"
}, },
"type": "module", "type": "module",
"author": "Lossless GmbH", "author": "Lossless GmbH",
@@ -24,30 +25,30 @@
"buildDocs": "tsdoc" "buildDocs": "tsdoc"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.6.8", "@git.zone/tsbuild": "^3.1.0",
"@types/node": "^22.15.21" "@types/node": "^22.15.21"
}, },
"dependencies": { "dependencies": {
"@api.global/typedserver": "^3.0.79", "@api.global/typedserver": "^3.0.80",
"@git.zone/tsbundle": "^2.5.1", "@git.zone/tsbundle": "^2.5.2",
"@git.zone/tsrun": "^1.6.2", "@git.zone/tsrun": "^2.0.0",
"@push.rocks/consolecolor": "^2.0.3", "@push.rocks/consolecolor": "^2.0.3",
"@push.rocks/qenv": "^6.1.3", "@push.rocks/qenv": "^6.1.3",
"@push.rocks/smartbrowser": "^2.0.8", "@push.rocks/smartbrowser": "^2.0.8",
"@push.rocks/smartchok": "^1.1.1", "@push.rocks/smartchok": "^1.1.1",
"@push.rocks/smartcrypto": "^2.0.4", "@push.rocks/smartcrypto": "^2.0.4",
"@push.rocks/smartdelay": "^3.0.5", "@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartenv": "^5.0.13", "@push.rocks/smartenv": "^6.0.0",
"@push.rocks/smartexpect": "^2.5.0", "@push.rocks/smartexpect": "^2.5.0",
"@push.rocks/smartfile": "^11.2.7", "@push.rocks/smartfile": "^11.2.7",
"@push.rocks/smartjson": "^5.2.0", "@push.rocks/smartjson": "^5.2.0",
"@push.rocks/smartlog": "^3.1.10", "@push.rocks/smartlog": "^3.1.10",
"@push.rocks/smartmongo": "^2.0.12", "@push.rocks/smartmongo": "^2.0.14",
"@push.rocks/smartnetwork": "^4.4.0", "@push.rocks/smartnetwork": "^4.4.0",
"@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartpromise": "^4.2.3", "@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrequest": "^4.3.2", "@push.rocks/smartrequest": "^5.0.1",
"@push.rocks/smarts3": "^2.2.6", "@push.rocks/smarts3": "^3.0.0",
"@push.rocks/smartshell": "^3.3.0", "@push.rocks/smartshell": "^3.3.0",
"@push.rocks/smarttime": "^4.1.1", "@push.rocks/smarttime": "^4.1.1",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",

4825
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ This project integrates tstest with tapbundle through a modular architecture:
1. **tstest** (`/ts/`) - The test runner that discovers and executes test files 1. **tstest** (`/ts/`) - The test runner that discovers and executes test files
2. **tapbundle** (`/ts_tapbundle/`) - The TAP testing framework for writing tests 2. **tapbundle** (`/ts_tapbundle/`) - The TAP testing framework for writing tests
3. **tapbundle_node** (`/ts_tapbundle_node/`) - Node.js-specific testing utilities 3. **tapbundle_serverside** (`/ts_tapbundle_serverside/`) - Server-side testing utilities (runCommand, env vars, HTTPS certs, MongoDB, S3, test assets)
## How Components Work Together ## How Components Work Together
@@ -31,7 +31,7 @@ This project integrates tstest with tapbundle through a modular architecture:
1. **Import Structure** 1. **Import Structure**
- Test files import from local tapbundle: `import { tap, expect } from '../../ts_tapbundle/index.js'` - Test files import from local tapbundle: `import { tap, expect } from '../../ts_tapbundle/index.js'`
- Node-specific tests also import from tapbundle_node: `import { tapNodeTools } from '../../ts_tapbundle_node/index.js'` - Server-side tests also import from tapbundle_serverside for Node.js-only utilities: `import { tapNodeTools } from '../../ts_tapbundle_serverside/index.js'`
2. **WebHelpers** 2. **WebHelpers**
- Browser tests can use webhelpers for DOM manipulation - Browser tests can use webhelpers for DOM manipulation
@@ -41,7 +41,7 @@ This project integrates tstest with tapbundle through a modular architecture:
3. **Build System** 3. **Build System**
- Uses `tsbuild tsfolders` to compile TypeScript (invoked by `pnpm build`) - Uses `tsbuild tsfolders` to compile TypeScript (invoked by `pnpm build`)
- Maintains separate output directories: `/dist_ts/`, `/dist_ts_tapbundle/`, `/dist_ts_tapbundle_node/`, `/dist_ts_tapbundle_protocol/` - Maintains separate output directories: `/dist_ts/`, `/dist_ts_tapbundle/`, `/dist_ts_tapbundle_serverside/`, `/dist_ts_tapbundle_protocol/`
- Compilation order is resolved automatically based on dependencies in tspublish.json files - Compilation order is resolved automatically based on dependencies in tspublish.json files
- Protocol imports use compiled dist directories: - Protocol imports use compiled dist directories:
```typescript ```typescript
@@ -244,6 +244,131 @@ tstest test/specific.ts -w
- Ignores changes matching the ignore patterns - Ignores changes matching the ignore patterns
- Shows "Waiting for file changes..." between runs - Shows "Waiting for file changes..." between runs
## Phase 1 API Improvements (v3.1.0)
### New Features Implemented
#### 1. tap.postTask() - Global Teardown (COMPLETED)
Added symmetric teardown method to complement `tap.preTask()`:
**Implementation:**
- Created `PostTask` class in `ts_tapbundle/tapbundle.classes.posttask.ts`
- Mirrors PreTask structure with description and function
- Integrated into Tap class execution flow
- Runs after all tests complete but before global `afterAll` hook
**Usage:**
```typescript
tap.postTask('cleanup database', async () => {
await cleanupDatabase();
});
```
**Execution Order:**
1. preTask hooks
2. Global beforeAll
3. Tests (with suite hooks)
4. **postTask hooks** ← NEW
5. Global afterAll
#### 2. Suite-Level beforeAll/afterAll (COMPLETED)
Added once-per-suite lifecycle hooks:
**Implementation:**
- Extended `ITestSuite` interface with `beforeAll` and `afterAll` properties
- Added `tap.beforeAll()` and `tap.afterAll()` methods
- Integrated into `_runSuite()` execution flow
- Properly handles nested suites
**Usage:**
```typescript
tap.describe('Database Tests', () => {
tap.beforeAll(async () => {
await initializeDatabaseConnection(); // Runs once
});
tap.test('test 1', async () => {});
tap.test('test 2', async () => {});
tap.afterAll(async () => {
await closeDatabaseConnection(); // Runs once
});
});
```
**Execution Order per Suite:**
1. Suite beforeAll ← NEW
2. Suite beforeEach
3. Test
4. Suite afterEach
5. (Repeat 2-4 for each test)
6. Child suites (recursive)
7. Suite afterAll ← NEW
#### 3. tap.parallel() Fluent Entry Point (COMPLETED)
Added fluent API for parallel test creation:
**Implementation:**
- Updated `TestBuilder` class with `_parallel` flag
- Builder constructor accepts optional parallel parameter
- Added `tap.parallel()` method returning configured builder
- Fixed `testParallel()` to return TapTest<T> (was void)
**Usage:**
```typescript
// Simple parallel test
tap.parallel().test('fetch data', async () => {});
// With full configuration
tap
.parallel()
.tags('api', 'integration')
.retry(2)
.timeout(5000)
.test('configured parallel test', async () => {});
```
**Benefits:**
- Consistent with other fluent builders (tags, priority, etc.)
- More discoverable than separate `testParallel()` method
- Allows chaining parallel with other configurations
- `testParallel()` kept for backward compatibility
### Documentation Updates
**tapbundle/readme.md:**
- Added suite-level beforeAll/afterAll documentation
- Documented postTask with execution order notes
- Added parallel() fluent API examples
- Expanded TapTools documentation with all methods
- Added "Additional Tap Methods" section for fail(), getSettings(), etc.
- Documented all previously undocumented methods
### Tests
**test/tapbundle/test.new-lifecycle.ts:**
- Tests postTask execution order
- Verifies suite-level beforeAll/afterAll
- Tests nested suite lifecycle
- Validates parallel() fluent API
- Confirms all execution order requirements
**Test Results:** All 9 tests passing ✅
### Breaking Changes
None - all changes are additive and backward compatible.
### Migration Guide
No migration needed. New features are opt-in:
- Continue using existing patterns
- Adopt new features incrementally
- `testParallel()` still works (recommended: switch to `parallel().test()`)
## Fixed Issues ## Fixed Issues
### tap.skip.test(), tap.todo(), and tap.only.test() (Fixed) ### tap.skip.test(), tap.todo(), and tap.only.test() (Fixed)

169
readme.md
View File

@@ -5,6 +5,10 @@
* [npmjs.org (npm package)](https://www.npmjs.com/package/@git.zone/tstest) * [npmjs.org (npm package)](https://www.npmjs.com/package/@git.zone/tstest)
* [code.foss.global (source)](https://code.foss.global/git.zone/tstest) * [code.foss.global (source)](https://code.foss.global/git.zone/tstest)
## Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit https://community.foss.global/. This is the central community hub for all issue reporting. Developers who want to sign a contribution agreement and go through identification can also get a code.foss.global account to submit Pull Requests directly.
## Why tstest? ## Why tstest?
**tstest** is a TypeScript test runner that makes testing delightful. It's designed for modern development workflows with beautiful output, flexible test execution, and powerful features that make debugging a breeze. **tstest** is a TypeScript test runner that makes testing delightful. It's designed for modern development workflows with beautiful output, flexible test execution, and powerful features that make debugging a breeze.
@@ -318,7 +322,155 @@ tstest provides multiple exports for different use cases:
- `@git.zone/tstest` - Main CLI and test runner functionality - `@git.zone/tstest` - Main CLI and test runner functionality
- `@git.zone/tstest/tapbundle` - Browser-compatible test framework - `@git.zone/tstest/tapbundle` - Browser-compatible test framework
- `@git.zone/tstest/tapbundle_node` - Node.js-specific test utilities - `@git.zone/tstest/tapbundle_serverside` - Server-side testing utilities for Node.js-only tests (*.node.ts files)
- Execute shell commands during tests
- Manage environment variables on-demand with secure storage
- Generate self-signed HTTPS certificates for testing secure connections
- Create ephemeral MongoDB instances for database testing
- Create local S3-compatible storage for object storage testing
- Download and manage test assets (e.g., Docker images)
- `@git.zone/tstest/tapbundle_protocol` - Protocol V2 emitter and parser for TAP extensions
### When to Use tapbundle_serverside
Use `@git.zone/tstest/tapbundle_serverside` when your tests:
- Run exclusively on Node.js server-side (*.node.ts test files)
- Need to execute shell commands or interact with the file system
- Require environment variable management with secure on-demand prompts
- Test HTTPS servers and need self-signed certificates
- Interact with databases (MongoDB) and need ephemeral test instances
- Work with object storage (S3-compatible) and need local testing
- Require test assets like Docker images or other downloadable files
**Important:** tapbundle_serverside utilities are NOT available in:
- Browser environments
- Deno runtime
- Bun runtime
For cross-runtime tests, only import tapbundle_serverside in `.node.ts` files where you need server-side specific functionality.
## tapbundle Protocol V2
tstest includes an enhanced TAP protocol (Protocol V2) that extends standard TAP 13 with additional metadata while maintaining backwards compatibility.
### Overview
Protocol V2 adds structured metadata to TAP output using Unicode markers (`⟦TSTEST:...⟧`) that standard TAP parsers safely ignore. This allows for:
- **Timing information** - Test execution duration in milliseconds
- **Structured errors** - Stack traces, diffs, and detailed error data
- **Test events** - Real-time progress and lifecycle events
- **Snapshots** - Snapshot testing data exchange
- **Custom metadata** - Tags, retry counts, file locations
### Using the Protocol
```typescript
import {
ProtocolEmitter,
ProtocolParser,
PROTOCOL_MARKERS,
PROTOCOL_VERSION
} from '@git.zone/tstest/tapbundle_protocol';
// Create an emitter
const emitter = new ProtocolEmitter();
// Emit protocol header
console.log(emitter.emitProtocolHeader());
// Output: ⟦TSTEST:PROTOCOL:2.0.0⟧
// Emit TAP version
console.log(emitter.emitTapVersion(13));
// Output: TAP version 13
// Emit a test result with metadata
const testResult = {
ok: true,
testNumber: 1,
description: 'user authentication works',
metadata: {
time: 123,
tags: ['auth', 'unit']
}
};
console.log(emitter.emitTest(testResult).join('\n'));
// Output: ok 1 - user authentication works ⟦TSTEST:time:123⟧
// ⟦TSTEST:META:{"tags":["auth","unit"]}⟧
```
### Protocol Markers
```typescript
PROTOCOL_MARKERS = {
START: '⟦TSTEST:',
END: '⟧',
META_PREFIX: 'META:',
ERROR_PREFIX: 'ERROR',
SNAPSHOT_PREFIX: 'SNAPSHOT:',
SKIP_PREFIX: 'SKIP:',
TODO_PREFIX: 'TODO:',
EVENT_PREFIX: 'EVENT:'
}
```
### Use Cases
#### Creating Custom Test Runners
```typescript
import { ProtocolEmitter } from '@git.zone/tstest/tapbundle_protocol';
const emitter = new ProtocolEmitter();
// Emit header and version
console.log(emitter.emitProtocolHeader());
console.log(emitter.emitTapVersion(13));
console.log(emitter.emitPlan({ start: 1, end: 2 }));
// Run your tests and emit results
const startTime = Date.now();
// ... run test ...
const duration = Date.now() - startTime;
console.log(emitter.emitTest({
ok: true,
testNumber: 1,
description: 'my custom test',
metadata: { time: duration }
}).join('\n'));
```
#### Parsing tapbundle Output
```typescript
import { ProtocolParser } from '@git.zone/tstest/tapbundle_protocol';
const parser = new ProtocolParser();
// Parse TAP output line by line
parser.parseLine('⟦TSTEST:PROTOCOL:2.0.0⟧');
parser.parseLine('TAP version 13');
parser.parseLine('1..1');
parser.parseLine('ok 1 - test name ⟦TSTEST:time:123⟧');
// Get parsed results
const results = parser.getResults();
console.log(results);
```
### Backwards Compatibility
Protocol V2 is fully backwards compatible with standard TAP 13. The Unicode markers are treated as comments by standard TAP parsers, so Protocol V2 output can be consumed by any TAP-compliant tool:
```
⟦TSTEST:PROTOCOL:2.0.0⟧ ← Ignored by standard TAP parsers
TAP version 13 ← Standard TAP
1..2 ← Standard TAP
ok 1 - test ⟦TSTEST:time:45⟧ ← TAP parsers see: "ok 1 - test"
ok 2 - another test ← Standard TAP
```
## tapbundle Test Framework ## tapbundle Test Framework
@@ -926,6 +1078,21 @@ tstest test/api/endpoints.test.ts --verbose --timeout 60
## Changelog ## Changelog
### Version 3.1.1
- 🐛 Fixed TapTools parameter passing to suite lifecycle hooks (beforeAll/afterAll)
- 📦 Updated @push.rocks/smarts3 dependency to ^3.0.0
### Version 3.1.0
- 🎯 **postTask() API** - Global teardown method for cleanup after all tests
- 🏗️ **Suite beforeAll/afterAll** - Lifecycle hooks that run once per describe block
-**parallel() Fluent API** - New fluent entry point for parallel tests
- 📚 Enhanced tapbundle documentation with complete API reference
### Version 3.0.0
- 🔥 **BREAKING:** Renamed tapbundle_node to tapbundle_serverside for clarity
- 🔧 Migrated all server-side utilities to tapbundle_serverside
- 📦 Improved module separation and organization
### Version 2.4.0 ### Version 2.4.0
- 🚀 **Multi-Runtime Architecture** - Support for Deno, Bun, Node.js, and Chromium - 🚀 **Multi-Runtime Architecture** - Support for Deno, Bun, Node.js, and Chromium
- 🔀 **New Naming Convention** - Flexible `.runtime1+runtime2.ts` pattern - 🔀 **New Naming Convention** - Flexible `.runtime1+runtime2.ts` pattern

91
test/decorator.all.ts Normal file
View File

@@ -0,0 +1,91 @@
import { tap, expect } from '../ts_tapbundle/index.js';
/**
* Simple class decorator for testing decorator support across runtimes
*/
function testDecorator(target: any) {
target.decoratorApplied = true;
target.decoratorData = 'Decorator was applied successfully';
return target;
}
/**
* Method decorator for testing
*/
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const result = originalMethod.apply(this, args);
return `[logged] ${result}`;
};
return descriptor;
}
/**
* Parameter decorator for testing
*/
function validateParam(target: any, propertyKey: string, parameterIndex: number) {
// Mark that parameter validation decorator was applied
if (!target.decoratedParams) {
target.decoratedParams = {};
}
if (!target.decoratedParams[propertyKey]) {
target.decoratedParams[propertyKey] = [];
}
target.decoratedParams[propertyKey].push(parameterIndex);
}
/**
* Test class with decorators
*/
@testDecorator
class TestClass {
public name: string = 'test';
@logMethod
public greet(message: string): string {
return `Hello, ${message}!`;
}
public getValue(): number {
return 42;
}
public testParams(@validateParam value: string): string {
return value;
}
}
// Tests
tap.test('Class decorator should be applied', async () => {
expect((TestClass as any).decoratorApplied).toEqual(true);
expect((TestClass as any).decoratorData).toEqual('Decorator was applied successfully');
});
tap.test('Method decorator should modify method behavior', async () => {
const instance = new TestClass();
const result = instance.greet('World');
expect(result).toEqual('[logged] Hello, World!');
});
tap.test('Regular methods should work normally', async () => {
const instance = new TestClass();
expect(instance.getValue()).toEqual(42);
expect(instance.name).toEqual('test');
});
tap.test('Parameter decorator should be applied', async () => {
const decoratedParams = (TestClass.prototype as any).decoratedParams;
expect(decoratedParams).toBeDefined();
expect(decoratedParams.testParams).toBeDefined();
expect(decoratedParams.testParams).toContain(0);
});
tap.test('Decorator metadata preservation', async () => {
const instance = new TestClass();
expect(instance instanceof TestClass).toEqual(true);
expect(instance.constructor.name).toEqual('TestClass');
expect(instance.testParams('hello')).toEqual('hello');
});
export default tap.start();

View File

@@ -0,0 +1,170 @@
import { tap, expect } from '../../ts_tapbundle/index.js';
// Global state for testing new lifecycle features
const executionOrder: string[] = [];
let postTaskRan = false;
// Test preTask and postTask
tap.preTask('setup environment', async () => {
executionOrder.push('preTask');
console.log('🔧 PreTask: Setting up environment');
});
tap.postTask('cleanup environment', async () => {
postTaskRan = true;
executionOrder.push('postTask');
console.log('🧹 PostTask: Cleaning up environment');
});
// Test suite-level beforeAll and afterAll
tap.describe('Suite with beforeAll/afterAll', () => {
tap.beforeAll(async () => {
executionOrder.push('suite-beforeAll');
console.log('🔰 Suite beforeAll executed');
});
tap.afterAll(async () => {
executionOrder.push('suite-afterAll');
console.log('🏁 Suite afterAll executed');
});
tap.beforeEach(async () => {
executionOrder.push('suite-beforeEach');
});
tap.afterEach(async () => {
executionOrder.push('suite-afterEach');
});
tap.test('first test in suite', async () => {
executionOrder.push('test-1');
expect(executionOrder).toContain('preTask');
expect(executionOrder).toContain('suite-beforeAll');
console.log('✓ Test 1 executed');
});
tap.test('second test in suite', async () => {
executionOrder.push('test-2');
expect(executionOrder).toContain('suite-beforeAll');
console.log('✓ Test 2 executed');
});
});
// Test nested suites with beforeAll/afterAll
tap.describe('Parent Suite', () => {
tap.beforeAll(async () => {
executionOrder.push('parent-beforeAll');
console.log('🔰 Parent beforeAll executed');
});
tap.afterAll(async () => {
executionOrder.push('parent-afterAll');
console.log('🏁 Parent afterAll executed');
});
tap.test('test in parent', async () => {
executionOrder.push('parent-test');
expect(executionOrder).toContain('parent-beforeAll');
});
tap.describe('Child Suite', () => {
tap.beforeAll(async () => {
executionOrder.push('child-beforeAll');
console.log('🔰 Child beforeAll executed');
});
tap.afterAll(async () => {
executionOrder.push('child-afterAll');
console.log('🏁 Child afterAll executed');
});
tap.test('test in child', async () => {
executionOrder.push('child-test');
expect(executionOrder).toContain('parent-beforeAll');
expect(executionOrder).toContain('child-beforeAll');
});
});
});
// Test parallel() fluent API
tap.parallel().test('parallel test 1', async () => {
await new Promise(resolve => setTimeout(resolve, 10));
executionOrder.push('parallel-1');
console.log('⚡ Parallel test 1 executed');
expect(true).toBeTrue();
});
tap.parallel().test('parallel test 2', async () => {
await new Promise(resolve => setTimeout(resolve, 5));
executionOrder.push('parallel-2');
console.log('⚡ Parallel test 2 executed');
expect(true).toBeTrue();
});
// Test parallel() with configuration
tap
.parallel()
.tags('integration', 'parallel')
.timeout(1000)
.test('configured parallel test', async () => {
executionOrder.push('parallel-configured');
console.log('⚡ Configured parallel test executed');
expect(true).toBeTrue();
});
// Verify execution order
tap.test('verify lifecycle execution order', async () => {
// Give a moment for any async operations to complete
await new Promise(resolve => setTimeout(resolve, 100));
console.log('📊 Execution order:', executionOrder);
// Verify preTask ran first
expect(executionOrder[0]).toEqual('preTask');
// Verify suite beforeAll ran before tests
const suiteBeforeAllIndex = executionOrder.indexOf('suite-beforeAll');
const test1Index = executionOrder.indexOf('test-1');
expect(suiteBeforeAllIndex).toBeLessThan(test1Index);
// Verify beforeEach ran before each test
const beforeEachIndices = executionOrder
.map((item, index) => item === 'suite-beforeEach' ? index : -1)
.filter(index => index !== -1);
expect(beforeEachIndices.length).toBeGreaterThanOrEqual(2);
// Verify afterEach ran after each test
const afterEachIndices = executionOrder
.map((item, index) => item === 'suite-afterEach' ? index : -1)
.filter(index => index !== -1);
expect(afterEachIndices.length).toBeGreaterThanOrEqual(2);
// Verify afterAll ran after all tests
const suiteAfterAllIndex = executionOrder.indexOf('suite-afterAll');
const test2Index = executionOrder.indexOf('test-2');
expect(suiteAfterAllIndex).toBeGreaterThan(test2Index);
// Verify nested suite lifecycle
expect(executionOrder).toContain('parent-beforeAll');
expect(executionOrder).toContain('parent-test');
expect(executionOrder).toContain('child-beforeAll');
expect(executionOrder).toContain('child-test');
expect(executionOrder).toContain('child-afterAll');
expect(executionOrder).toContain('parent-afterAll');
// Verify parallel tests ran
expect(executionOrder).toContain('parallel-1');
expect(executionOrder).toContain('parallel-2');
expect(executionOrder).toContain('parallel-configured');
console.log('✅ All lifecycle hooks executed in correct order');
});
// This test will verify postTask ran (after tap.start() completes)
tap.test('verify postTask execution', async () => {
// PostTask hasn't run yet because tests are still running
expect(postTaskRan).toBeFalse();
console.log('✓ Verified postTask will run after all tests');
});
export default tap.start();

View File

@@ -1,6 +1,6 @@
import { tap, expect } from '../../ts_tapbundle/index.js'; import { tap, expect } from '../../ts_tapbundle/index.js';
import { tapNodeTools } from '../../ts_tapbundle_node/index.js'; import { tapNodeTools } from '../../ts_tapbundle_serverside/index.js';
tap.test('should execure a command', async () => { tap.test('should execure a command', async () => {
const result = await tapNodeTools.runCommand('ls -la'); const result = await tapNodeTools.runCommand('ls -la');

View File

@@ -0,0 +1,9 @@
#!/bin/bash
# Sample Docker test file
# This file demonstrates the naming pattern: test.{baseName}.{variant}.docker.sh
# The variant "latest" maps to the Dockerfile in the project root
echo "TAP version 13"
echo "1..2"
echo "ok 1 - Sample Docker test passes"
echo "ok 2 - Docker environment is working"

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/tstest', name: '@git.zone/tstest',
version: '2.6.2', version: '3.1.2',
description: 'a test utility to run tests that match test/**/*.ts' description: 'a test utility to run tests that match test/**/*.ts'
} }

View File

@@ -69,6 +69,9 @@ export class BunRuntimeAdapter extends RuntimeAdapter {
const args: string[] = ['run']; const args: string[] = ['run'];
// Note: Bun automatically discovers bunfig.toml in the current directory
// This ensures TypeScript decorator support is enabled if bunfig.toml is present
// Add extra args // Add extra args
if (mergedOptions.extraArgs && mergedOptions.extraArgs.length > 0) { if (mergedOptions.extraArgs && mergedOptions.extraArgs.length > 0) {
args.push(...mergedOptions.extraArgs); args.push(...mergedOptions.extraArgs);

View File

@@ -31,8 +31,20 @@ export class DenoRuntimeAdapter extends RuntimeAdapter {
* Get default Deno options * Get default Deno options
*/ */
protected getDefaultOptions(): DenoOptions { protected getDefaultOptions(): DenoOptions {
// Auto-detect deno.json or deno.jsonc config file for TypeScript decorator support
let configPath: string | undefined;
const denoJsonPath = plugins.path.join(process.cwd(), 'deno.json');
const denoJsoncPath = plugins.path.join(process.cwd(), 'deno.jsonc');
if (plugins.smartfile.fs.fileExistsSync(denoJsonPath)) {
configPath = denoJsonPath;
} else if (plugins.smartfile.fs.fileExistsSync(denoJsoncPath)) {
configPath = denoJsoncPath;
}
return { return {
...super.getDefaultOptions(), ...super.getDefaultOptions(),
configPath,
permissions: [ permissions: [
'--allow-read', '--allow-read',
'--allow-env', '--allow-env',

View File

@@ -0,0 +1,251 @@
import * as plugins from './tstest.plugins.js';
import { coloredString as cs } from '@push.rocks/consolecolor';
import {
RuntimeAdapter,
type RuntimeOptions,
type RuntimeCommand,
type RuntimeAvailability,
} from './tstest.classes.runtime.adapter.js';
import { TapParser } from './tstest.classes.tap.parser.js';
import { TsTestLogger } from './tstest.logging.js';
import type { Runtime } from './tstest.classes.runtime.parser.js';
import {
parseDockerTestFilename,
mapVariantToDockerfile,
isDockerTestFile
} from './tstest.classes.runtime.parser.js';
/**
* Docker runtime adapter
* Executes shell test files inside Docker containers
* Pattern: test.{variant}.docker.sh
* Variants map to Dockerfiles: latest -> Dockerfile, others -> Dockerfile_{variant}
*/
export class DockerRuntimeAdapter extends RuntimeAdapter {
readonly id: Runtime = 'node'; // Using 'node' temporarily as Runtime type doesn't include 'docker'
readonly displayName: string = 'Docker';
private builtImages: Set<string> = new Set(); // Track built images to avoid rebuilding
constructor(
private logger: TsTestLogger,
private smartshellInstance: any, // SmartShell instance from @push.rocks/smartshell
private timeoutSeconds: number | null,
private cwd: string
) {
super();
}
/**
* Check if Docker CLI is available
*/
async checkAvailable(): Promise<RuntimeAvailability> {
try {
const result = await this.smartshellInstance.exec('docker --version');
if (result.exitCode !== 0) {
return {
available: false,
error: 'Docker command failed',
};
}
// Extract version from output like "Docker version 24.0.5, build ced0996"
const versionMatch = result.stdout.match(/Docker version ([^,]+)/);
const version = versionMatch ? versionMatch[1] : 'unknown';
return {
available: true,
version,
};
} catch (error) {
return {
available: false,
error: `Docker not found: ${error.message}`,
};
}
}
/**
* Create command configuration for Docker test execution
* This is used for informational purposes
*/
createCommand(testFile: string, options?: RuntimeOptions): RuntimeCommand {
const parsed = parseDockerTestFilename(testFile);
const dockerfilePath = mapVariantToDockerfile(parsed.variant, this.cwd);
const imageName = `tstest-${parsed.variant}`;
return {
command: 'docker',
args: [
'run',
'--rm',
'-v',
`${this.cwd}/test:/test`,
imageName,
'taprun',
`/test/${plugins.path.basename(testFile)}`
],
env: {},
cwd: this.cwd,
};
}
/**
* Build a Docker image from the specified Dockerfile
*/
private async buildDockerImage(dockerfilePath: string, imageName: string): Promise<void> {
// Check if image is already built
if (this.builtImages.has(imageName)) {
this.logger.tapOutput(`Using cached Docker image: ${imageName}`);
return;
}
// Check if Dockerfile exists
if (!await plugins.smartfile.fs.fileExists(dockerfilePath)) {
throw new Error(
`Dockerfile not found: ${dockerfilePath}\n` +
`Expected Dockerfile for Docker test variant.`
);
}
this.logger.tapOutput(`Building Docker image: ${imageName} from ${dockerfilePath}`);
try {
const buildResult = await this.smartshellInstance.exec(
`docker build -f ${dockerfilePath} -t ${imageName} ${this.cwd}`,
{
cwd: this.cwd,
}
);
if (buildResult.exitCode !== 0) {
throw new Error(`Docker build failed:\n${buildResult.stderr}`);
}
this.builtImages.add(imageName);
this.logger.tapOutput(`✅ Docker image built successfully: ${imageName}`);
} catch (error) {
throw new Error(`Failed to build Docker image: ${error.message}`);
}
}
/**
* Execute a Docker test file
*/
async run(
testFile: string,
index: number,
total: number,
options?: RuntimeOptions
): Promise<TapParser> {
this.logger.testFileStart(testFile, this.displayName, index, total);
// Parse the Docker test filename
const parsed = parseDockerTestFilename(testFile);
const dockerfilePath = mapVariantToDockerfile(parsed.variant, this.cwd);
const imageName = `tstest-${parsed.variant}`;
// Build the Docker image
await this.buildDockerImage(dockerfilePath, imageName);
// Prepare the test file path relative to the mounted directory
// We need to get the path relative to cwd
const absoluteTestPath = plugins.path.isAbsolute(testFile)
? testFile
: plugins.path.join(this.cwd, testFile);
const relativeTestPath = plugins.path.relative(this.cwd, absoluteTestPath);
// Create TAP parser
const tapParser = new TapParser(testFile + ':docker', this.logger);
try {
// Build docker run command
const dockerArgs = [
'run',
'--rm',
'-v',
`${this.cwd}/test:/test`,
imageName,
'taprun',
`/test/${plugins.path.basename(testFile)}`
];
this.logger.tapOutput(`Executing: docker ${dockerArgs.join(' ')}`);
// Execute the Docker container
const execPromise = this.smartshellInstance.execStreaming(
`docker ${dockerArgs.join(' ')}`,
{
cwd: this.cwd,
}
);
// Set up timeout if configured
let timeoutHandle: NodeJS.Timeout | null = null;
if (this.timeoutSeconds) {
timeoutHandle = setTimeout(() => {
this.logger.tapOutput(`⏱️ Test timeout (${this.timeoutSeconds}s) - killing container`);
// Try to kill any running containers with this image
this.smartshellInstance.exec(`docker ps -q --filter ancestor=${imageName} | xargs -r docker kill`);
}, this.timeoutSeconds * 1000);
}
// Stream output to TAP parser line by line
execPromise.childProcess.stdout.on('data', (data: Buffer) => {
const output = data.toString();
const lines = output.split('\n');
for (const line of lines) {
if (line.trim()) {
tapParser.handleTapLog(line);
}
}
});
execPromise.childProcess.stderr.on('data', (data: Buffer) => {
const output = data.toString();
this.logger.tapOutput(cs(`[stderr] ${output}`, 'orange'));
});
// Wait for completion
const result = await execPromise;
// Clear timeout
if (timeoutHandle) {
clearTimeout(timeoutHandle);
}
if (result.exitCode !== 0) {
this.logger.tapOutput(cs(`❌ Docker test failed with exit code ${result.exitCode}`, 'red'));
}
// Evaluate final result
await tapParser.evaluateFinalResult();
} catch (error) {
this.logger.tapOutput(cs(`❌ Error running Docker test: ${error.message}`, 'red'));
// Add a failing test result to the parser
tapParser.handleTapLog('not ok 1 - Docker test execution failed');
await tapParser.evaluateFinalResult();
}
return tapParser;
}
/**
* Clean up built Docker images (optional, can be called at end of test suite)
*/
async cleanup(): Promise<void> {
for (const imageName of this.builtImages) {
try {
this.logger.tapOutput(`Removing Docker image: ${imageName}`);
await this.smartshellInstance.exec(`docker rmi ${imageName}`);
} catch (error) {
// Ignore cleanup errors
this.logger.tapOutput(cs(`Warning: Failed to remove image ${imageName}: ${error.message}`, 'orange'));
}
}
this.builtImages.clear();
}
}

View File

@@ -29,7 +29,7 @@ export interface ParserConfig {
const KNOWN_RUNTIMES: Set<string> = new Set(['node', 'chromium', 'deno', 'bun']); const KNOWN_RUNTIMES: Set<string> = new Set(['node', 'chromium', 'deno', 'bun']);
const KNOWN_MODIFIERS: Set<string> = new Set(['nonci']); const KNOWN_MODIFIERS: Set<string> = new Set(['nonci']);
const VALID_EXTENSIONS: Set<string> = new Set(['ts', 'tsx', 'mts', 'cts']); const VALID_EXTENSIONS: Set<string> = new Set(['ts', 'tsx', 'mts', 'cts', 'sh']);
const ALL_RUNTIMES: Runtime[] = ['node', 'chromium', 'deno', 'bun']; const ALL_RUNTIMES: Runtime[] = ['node', 'chromium', 'deno', 'bun'];
// Legacy mappings for backwards compatibility // Legacy mappings for backwards compatibility
@@ -228,3 +228,81 @@ export function getLegacyMigrationTarget(fileName: string): string | null {
return parts.join('.'); return parts.join('.');
} }
/**
* Docker test file information
*/
export interface DockerTestFileInfo {
baseName: string;
variant: string;
isDockerTest: true;
original: string;
}
/**
* Check if a filename matches the Docker test pattern: *.{variant}.docker.sh
* Examples: test.latest.docker.sh, test.integration.npmci.docker.sh
*/
export function isDockerTestFile(fileName: string): boolean {
// Must end with .docker.sh
if (!fileName.endsWith('.docker.sh')) {
return false;
}
// Extract filename from path if needed
const name = fileName.split('/').pop() || fileName;
// Must have at least 3 parts: [baseName, variant, docker, sh]
const parts = name.split('.');
return parts.length >= 4 && parts[parts.length - 2] === 'docker' && parts[parts.length - 1] === 'sh';
}
/**
* Parse a Docker test filename to extract variant and base name
* Pattern: test.{baseName}.{variant}.docker.sh
* Examples:
* - test.latest.docker.sh -> { baseName: 'test', variant: 'latest' }
* - test.integration.npmci.docker.sh -> { baseName: 'test.integration', variant: 'npmci' }
*/
export function parseDockerTestFilename(filePath: string): DockerTestFileInfo {
// Extract just the filename from the path
const fileName = filePath.split('/').pop() || filePath;
const original = fileName;
if (!isDockerTestFile(fileName)) {
throw new Error(`Not a valid Docker test file: "${fileName}". Expected pattern: *.{variant}.docker.sh`);
}
// Remove .docker.sh suffix
const withoutSuffix = fileName.slice(0, -10); // Remove '.docker.sh'
const tokens = withoutSuffix.split('.');
if (tokens.length === 0) {
throw new Error(`Invalid Docker test file: empty basename in "${fileName}"`);
}
// Last token before .docker.sh is the variant
const variant = tokens[tokens.length - 1];
// Everything else is the base name
const baseName = tokens.slice(0, -1).join('.');
return {
baseName: baseName || 'test',
variant,
isDockerTest: true,
original,
};
}
/**
* Map a Docker variant to its corresponding Dockerfile path
* "latest" -> "Dockerfile"
* Other variants -> "Dockerfile_{variant}"
*/
export function mapVariantToDockerfile(variant: string, baseDir: string): string {
if (variant === 'latest') {
return `${baseDir}/Dockerfile`;
}
return `${baseDir}/Dockerfile_${variant}`;
}

View File

@@ -74,12 +74,20 @@ export class TestDirectory {
case TestExecutionMode.DIRECTORY: case TestExecutionMode.DIRECTORY:
// Directory mode - now recursive with ** pattern // Directory mode - now recursive with ** pattern
const dirPath = plugins.path.join(this.cwd, this.testPath); const dirPath = plugins.path.join(this.cwd, this.testPath);
const testPattern = '**/test*.ts';
const testFiles = await plugins.smartfile.fs.listFileTree(dirPath, testPattern); // Search for both TypeScript test files and Docker shell test files
const tsPattern = '**/test*.ts';
const dockerPattern = '**/*.docker.sh';
const [tsFiles, dockerFiles] = await Promise.all([
plugins.smartfile.fs.listFileTree(dirPath, tsPattern),
plugins.smartfile.fs.listFileTree(dirPath, dockerPattern),
]);
const allTestFiles = [...tsFiles, ...dockerFiles];
this.testfileArray = await Promise.all( this.testfileArray = await Promise.all(
testFiles.map(async (filePath) => { allTestFiles.map(async (filePath) => {
const absolutePath = plugins.path.isAbsolute(filePath) const absolutePath = plugins.path.isAbsolute(filePath)
? filePath ? filePath
: plugins.path.join(dirPath, filePath); : plugins.path.join(dirPath, filePath);

View File

@@ -11,12 +11,13 @@ import { TsTestLogger } from './tstest.logging.js';
import type { LogOptions } from './tstest.logging.js'; import type { LogOptions } from './tstest.logging.js';
// Runtime adapters // Runtime adapters
import { parseTestFilename } from './tstest.classes.runtime.parser.js'; import { parseTestFilename, isDockerTestFile, parseDockerTestFilename } from './tstest.classes.runtime.parser.js';
import { RuntimeAdapterRegistry } from './tstest.classes.runtime.adapter.js'; import { RuntimeAdapterRegistry } from './tstest.classes.runtime.adapter.js';
import { NodeRuntimeAdapter } from './tstest.classes.runtime.node.js'; import { NodeRuntimeAdapter } from './tstest.classes.runtime.node.js';
import { ChromiumRuntimeAdapter } from './tstest.classes.runtime.chromium.js'; import { ChromiumRuntimeAdapter } from './tstest.classes.runtime.chromium.js';
import { DenoRuntimeAdapter } from './tstest.classes.runtime.deno.js'; import { DenoRuntimeAdapter } from './tstest.classes.runtime.deno.js';
import { BunRuntimeAdapter } from './tstest.classes.runtime.bun.js'; import { BunRuntimeAdapter } from './tstest.classes.runtime.bun.js';
import { DockerRuntimeAdapter } from './tstest.classes.runtime.docker.js';
export class TsTest { export class TsTest {
public testDir: TestDirectory; public testDir: TestDirectory;
@@ -37,6 +38,7 @@ export class TsTest {
public tsbundleInstance = new plugins.tsbundle.TsBundle(); public tsbundleInstance = new plugins.tsbundle.TsBundle();
public runtimeRegistry = new RuntimeAdapterRegistry(); public runtimeRegistry = new RuntimeAdapterRegistry();
public dockerAdapter: DockerRuntimeAdapter | null = null;
constructor(cwdArg: string, testPathArg: string, executionModeArg: TestExecutionMode, logOptions: LogOptions = {}, tags: string[] = [], startFromFile: number | null = null, stopAtFile: number | null = null, timeoutSeconds: number | null = null) { constructor(cwdArg: string, testPathArg: string, executionModeArg: TestExecutionMode, logOptions: LogOptions = {}, tags: string[] = [], startFromFile: number | null = null, stopAtFile: number | null = null, timeoutSeconds: number | null = null) {
this.executionMode = executionModeArg; this.executionMode = executionModeArg;
@@ -60,6 +62,14 @@ export class TsTest {
this.runtimeRegistry.register( this.runtimeRegistry.register(
new BunRuntimeAdapter(this.logger, this.smartshellInstance, this.timeoutSeconds, this.filterTags) new BunRuntimeAdapter(this.logger, this.smartshellInstance, this.timeoutSeconds, this.filterTags)
); );
// Initialize Docker adapter
this.dockerAdapter = new DockerRuntimeAdapter(
this.logger,
this.smartshellInstance,
this.timeoutSeconds,
cwdArg
);
} }
/** /**
@@ -211,8 +221,14 @@ export class TsTest {
} }
private async runSingleTest(fileNameArg: string, fileIndex: number, totalFiles: number, tapCombinator: TapCombinator) { private async runSingleTest(fileNameArg: string, fileIndex: number, totalFiles: number, tapCombinator: TapCombinator) {
// Parse the filename to determine runtimes and modifiers
const fileName = plugins.path.basename(fileNameArg); const fileName = plugins.path.basename(fileNameArg);
// Check if this is a Docker test file
if (isDockerTestFile(fileName)) {
return await this.runDockerTest(fileNameArg, fileIndex, totalFiles, tapCombinator);
}
// Parse the filename to determine runtimes and modifiers (for TypeScript tests)
const parsed = parseTestFilename(fileName, { strictUnknownRuntime: false }); const parsed = parseTestFilename(fileName, { strictUnknownRuntime: false });
// Check for nonci modifier in CI environment // Check for nonci modifier in CI environment
@@ -258,6 +274,28 @@ export class TsTest {
} }
} }
/**
* Execute a Docker test file
*/
private async runDockerTest(
fileNameArg: string,
fileIndex: number,
totalFiles: number,
tapCombinator: TapCombinator
): Promise<void> {
if (!this.dockerAdapter) {
this.logger.tapOutput(cs('❌ Docker adapter not initialized', 'red'));
return;
}
try {
const tapParser = await this.dockerAdapter.run(fileNameArg, fileIndex, totalFiles);
tapCombinator.addTapParser(tapParser);
} catch (error) {
this.logger.tapOutput(cs(`❌ Docker test failed: ${error.message}`, 'red'));
}
}
public async runInNode(fileNameArg: string, index: number, total: number): Promise<TapParser> { public async runInNode(fileNameArg: string, index: number, total: number): Promise<TapParser> {
this.logger.testFileStart(fileNameArg, 'node.js', index, total); this.logger.testFileStart(fileNameArg, 'node.js', index, total);
const tapParser = new TapParser(fileNameArg + ':node', this.logger); const tapParser = new TapParser(fileNameArg + ':node', this.logger);

View File

@@ -201,7 +201,10 @@ export class TsTestLogger {
.replace(/\.ts$/, '') // Remove .ts extension .replace(/\.ts$/, '') // Remove .ts extension
.replace(/^\.\.__|^\.__|^__/, ''); // Clean up leading separators from relative paths .replace(/^\.\.__|^\.__|^__/, ''); // Clean up leading separators from relative paths
this.currentTestLogFile = path.join('.nogit', 'testlogs', `${safeFilename}.log`); // Sanitize runtime name for use in filename (lowercase, no spaces/dots/special chars)
const safeRuntime = runtime.toLowerCase().replace(/[^a-z0-9]/g, '');
this.currentTestLogFile = path.join('.nogit', 'testlogs', `${safeFilename}__${safeRuntime}.log`);
// Ensure the directory exists // Ensure the directory exists
const logDir = path.dirname(this.currentTestLogFile); const logDir = path.dirname(this.currentTestLogFile);

View File

@@ -9,6 +9,10 @@
pnpm install --save-dev @git.zone/tstest pnpm install --save-dev @git.zone/tstest
``` ```
## Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit https://community.foss.global/. This is the central community hub for all issue reporting. Developers who want to sign a contribution agreement and go through identification can also get a code.foss.global account to submit Pull Requests directly.
## Overview ## Overview
`@git.zone/tstest/tapbundle` is the core testing framework module that provides the TAP (Test Anything Protocol) implementation for tstest. It offers a comprehensive API for writing and organizing tests with support for lifecycle hooks, test suites, enhanced assertions with diff generation, and flexible test configuration. `@git.zone/tstest/tapbundle` is the core testing framework module that provides the TAP (Test Anything Protocol) implementation for tstest. It offers a comprehensive API for writing and organizing tests with support for lifecycle hooks, test suites, enhanced assertions with diff generation, and flexible test configuration.
@@ -91,6 +95,24 @@ tap.testParallel('should fetch user data', async () => {
}); });
``` ```
**Note:** The `tap.parallel().test()` fluent API is now the recommended way to define parallel tests (see Fluent API section below).
#### `tap.parallel()`
Returns a fluent test builder configured for parallel execution.
```typescript
tap.parallel().test('should fetch data', async () => {
// Parallel test
});
// With full configuration
tap.parallel()
.tags('api')
.retry(2)
.test('configured parallel test', async () => {});
```
#### `tap.describe(description, suiteFunction)` #### `tap.describe(description, suiteFunction)`
Create a test suite to group related tests. Create a test suite to group related tests.
@@ -141,22 +163,56 @@ tap
}); });
``` ```
#### Parallel Tests with Fluent API
Use `tap.parallel()` to create parallel tests with fluent configuration:
```typescript
// Simple parallel test
tap.parallel().test('fetches user data', async () => {
// Runs in parallel with other parallel tests
});
// Parallel test with full configuration
tap
.parallel()
.tags('api', 'integration')
.retry(2)
.timeout(5000)
.test('should fetch data concurrently', async () => {
// Configured parallel test
});
```
**Note:** `tap.parallel().test()` is the recommended way to define parallel tests. The older `tap.testParallel()` method is still supported for backward compatibility.
### Lifecycle Hooks ### Lifecycle Hooks
#### Suite-Level Hooks #### Suite-Level Hooks
```typescript ```typescript
tap.describe('Database Tests', () => { tap.describe('Database Tests', () => {
tap.beforeAll(async (tapTools) => {
// Runs once before all tests in this suite
await initializeDatabaseConnection();
});
tap.beforeEach(async (tapTools) => { tap.beforeEach(async (tapTools) => {
// Runs before each test in this suite // Runs before each test in this suite
await clearTestData();
}); });
tap.test('test 1', async () => { });
tap.test('test 2', async () => { });
tap.afterEach(async (tapTools) => { tap.afterEach(async (tapTools) => {
// Runs after each test in this suite // Runs after each test in this suite
}); });
tap.test('test 1', async () => { }); tap.afterAll(async (tapTools) => {
tap.test('test 2', async () => { }); // Runs once after all tests in this suite
await closeDatabaseConnection();
});
}); });
``` ```
@@ -267,38 +323,169 @@ TSTEST_FILTER_TAGS=unit tstest test/mytest.node.ts
Each test receives a `tapTools` instance with utilities: Each test receives a `tapTools` instance with utilities:
#### Test Control Methods
```typescript ```typescript
tap.test('should have utilities', async (tapTools) => { tap.test('test control examples', async (tapTools) => {
// Mark test as skipped // Skip this test
tapTools.skip('reason');
// Conditionally skip
tapTools.skipIf(condition, 'reason');
// Mark test as skipped before execution
tapTools.markAsSkipped('reason'); tapTools.markAsSkipped('reason');
// Mark as todo // Mark as todo
tapTools.todo('not implemented'); tapTools.todo('not implemented');
// Allow test to fail without marking suite as failed
tapTools.allowFailure();
// Configure retries // Configure retries
tapTools.retry(3); tapTools.retry(3);
// Log test output // Set timeout
tapTools.log('debug message'); tapTools.timeout(5000);
}); });
``` ```
#### Utility Methods
```typescript
tap.test('utility examples', async (tapTools) => {
// Delay execution
await tapTools.delayFor(1000); // Wait 1 second
await tapTools.delayForRandom(500, 1500); // Random delay
// Colored console output
tapTools.coloredString('✓ Success', 'green');
tapTools.coloredString('✗ Error', 'red');
});
```
#### Context and Data Sharing
```typescript
tap.test('first test', async (tapTools) => {
// Store data in context
tapTools.context.set('userId', '12345');
// Store in testData property
tapTools.testData = { username: 'alice' };
});
tap.test('second test', async (tapTools) => {
// Retrieve from context
const userId = tapTools.context.get('userId');
// Check existence
if (tapTools.context.has('userId')) {
// Use data
}
// Clear context
tapTools.context.clear();
});
```
#### Fixtures
```typescript
// Define a fixture globally (outside tests)
import { TapTools } from '@git.zone/tstest/tapbundle';
TapTools.defineFixture('database', async () => {
const db = await createTestDatabase();
return {
value: db,
cleanup: async () => await db.close()
};
});
// Use fixtures in tests
tap.test('database test', async (tapTools) => {
const db = await tapTools.fixture('database');
// Use db...
// Cleanup happens automatically
});
```
#### Factory Pattern
```typescript
// Define a factory
TapTools.defineFixture('user', async () => {
return {
value: null, // Not used for factories
factory: async (data) => {
return await createUser(data);
},
cleanup: async (user) => await user.delete()
};
});
// Use factory in tests
tap.test('user test', async (tapTools) => {
const user = await tapTools.factory('user').create({ name: 'Alice' });
// Create multiple
const users = await tapTools.factory('user').createMany([
{ name: 'Alice' },
{ name: 'Bob' }
]);
// Cleanup happens automatically
});
```
#### Snapshot Testing
```typescript
tap.test('snapshot test', async (tapTools) => {
const result = { name: 'Alice', age: 30 };
// Compare with stored snapshot
await tapTools.matchSnapshot(result);
// Named snapshots
await tapTools.matchSnapshot(result, 'user-data');
});
```
To update snapshots, run with:
```bash
UPDATE_SNAPSHOTS=true tstest test/mytest.ts
```
## Advanced Features ## Advanced Features
### Pre-Tasks ### Pre-Tasks and Post-Tasks
Run setup tasks before any tests execute: Run setup and teardown tasks before/after all tests:
```typescript ```typescript
tap.preTask('setup database', async () => { tap.preTask('setup database', async () => {
// Runs before any tests // Runs before any tests
await initializeDatabase();
}); });
tap.test('first test', async () => { tap.test('first test', async () => {
// Database is ready // Database is ready
}); });
tap.test('second test', async () => {
// Tests run...
});
tap.postTask('cleanup database', async () => {
// Runs after all tests complete
await cleanupDatabase();
});
``` ```
**Note:** Post tasks run after all tests but before the global `afterAll` hook.
### Test Priority ### Test Priority
Organize tests by priority level: Organize tests by priority level:
@@ -334,6 +521,50 @@ import { setProtocolEmitter } from '@git.zone/tstest/tapbundle';
// Events: test:started, test:completed, assertion:failed, suite:started, suite:completed // Events: test:started, test:completed, assertion:failed, suite:started, suite:completed
``` ```
### Additional Tap Methods
#### Configuration and Inspection
```typescript
// Get current test settings
const settings = tap.getSettings();
console.log(settings.timeout, settings.retries);
// Explicitly fail a test
tap.test('validation test', async () => {
if (invalidCondition) {
tap.fail('Custom failure message');
}
});
```
#### Advanced Control
```typescript
// Force stop test execution
tap.stopForcefully(exitCode, immediate);
// Handle thrown errors (internal use)
tap.threw(error);
```
#### Parallel Test Variants
In addition to `tap.parallel().test()`, skip/only/todo modes also support parallel execution:
```typescript
// Skip parallel test
tap.skip.testParallel('not ready', async () => {});
// Only run this parallel test
tap.only.testParallel('focus here', async () => {});
// Todo parallel test
tap.todo.testParallel('implement later');
```
**Note:** Using `tap.parallel()` fluent API is recommended over these direct methods.
## Best Practices ## Best Practices
1. **Always export `tap.start()`** at the end of test files: 1. **Always export `tap.start()`** at the end of test files:

View File

@@ -0,0 +1,21 @@
import * as plugins from './tapbundle.plugins.js';
import { TapTools } from './tapbundle.classes.taptools.js';
export interface IPostTaskFunction {
(tapTools?: TapTools): Promise<any>;
}
export class PostTask {
public description: string;
public postTaskFunction: IPostTaskFunction;
constructor(descriptionArg: string, postTaskFunctionArg: IPostTaskFunction) {
this.description = descriptionArg;
this.postTaskFunction = postTaskFunctionArg;
}
public async run() {
console.log(`::__POSTTASK: ${this.description}`);
await this.postTaskFunction(new TapTools(null));
}
}

View File

@@ -1,7 +1,9 @@
import * as plugins from './tapbundle.plugins.js'; import * as plugins from './tapbundle.plugins.js';
import { type IPreTaskFunction, PreTask } from './tapbundle.classes.pretask.js'; import { type IPreTaskFunction, PreTask } from './tapbundle.classes.pretask.js';
import { type IPostTaskFunction, PostTask } from './tapbundle.classes.posttask.js';
import { TapTest, type ITestFunction } from './tapbundle.classes.taptest.js'; import { TapTest, type ITestFunction } from './tapbundle.classes.taptest.js';
import { TapTools } from './tapbundle.classes.taptools.js';
import { ProtocolEmitter, type ITestEvent } from '../dist_ts_tapbundle_protocol/index.js'; import { ProtocolEmitter, type ITestEvent } from '../dist_ts_tapbundle_protocol/index.js';
import type { ITapSettings } from './tapbundle.interfaces.js'; import type { ITapSettings } from './tapbundle.interfaces.js';
import { SettingsManager } from './tapbundle.classes.settingsmanager.js'; import { SettingsManager } from './tapbundle.classes.settingsmanager.js';
@@ -9,6 +11,8 @@ import { SettingsManager } from './tapbundle.classes.settingsmanager.js';
export interface ITestSuite { export interface ITestSuite {
description: string; description: string;
tests: TapTest<any>[]; tests: TapTest<any>[];
beforeAll?: ITestFunction<any>;
afterAll?: ITestFunction<any>;
beforeEach?: ITestFunction<any>; beforeEach?: ITestFunction<any>;
afterEach?: ITestFunction<any>; afterEach?: ITestFunction<any>;
parent?: ITestSuite; parent?: ITestSuite;
@@ -21,9 +25,11 @@ class TestBuilder<T> {
private _priority: 'high' | 'medium' | 'low' = 'medium'; private _priority: 'high' | 'medium' | 'low' = 'medium';
private _retryCount?: number; private _retryCount?: number;
private _timeoutMs?: number; private _timeoutMs?: number;
private _parallel: boolean = false;
constructor(tap: Tap<T>) { constructor(tap: Tap<T>, parallel: boolean = false) {
this._tap = tap; this._tap = tap;
this._parallel = parallel;
} }
tags(...tags: string[]) { tags(...tags: string[]) {
@@ -47,7 +53,9 @@ class TestBuilder<T> {
} }
test(description: string, testFunction: ITestFunction<T>) { test(description: string, testFunction: ITestFunction<T>) {
const test = this._tap.test(description, testFunction, 'normal'); const test = this._parallel
? this._tap.testParallel(description, testFunction)
: this._tap.test(description, testFunction, 'normal');
// Apply settings to the test // Apply settings to the test
if (this._tags.length > 0) { if (this._tags.length > 0) {
@@ -138,6 +146,10 @@ export class Tap<T> {
return builder.timeout(ms); return builder.timeout(ms);
} }
public parallel() {
return new TestBuilder<T>(this, true);
}
/** /**
* skips a test * skips a test
* tests marked with tap.skip.test() are never executed * tests marked with tap.skip.test() are never executed
@@ -236,6 +248,7 @@ export class Tap<T> {
}; };
private _tapPreTasks: PreTask[] = []; private _tapPreTasks: PreTask[] = [];
private _tapPostTasks: PostTask[] = [];
private _tapTests: TapTest<any>[] = []; private _tapTests: TapTest<any>[] = [];
private _tapTestsOnly: TapTest<any>[] = []; private _tapTestsOnly: TapTest<any>[] = [];
private _currentSuite: ITestSuite | null = null; private _currentSuite: ITestSuite | null = null;
@@ -304,12 +317,16 @@ export class Tap<T> {
this._tapPreTasks.push(new PreTask(descriptionArg, functionArg)); this._tapPreTasks.push(new PreTask(descriptionArg, functionArg));
} }
public postTask(descriptionArg: string, functionArg: IPostTaskFunction) {
this._tapPostTasks.push(new PostTask(descriptionArg, functionArg));
}
/** /**
* A parallel test that will not be waited for before the next starts. * A parallel test that will not be waited for before the next starts.
* @param testDescription - A description of what the test does * @param testDescription - A description of what the test does
* @param testFunction - A Function that returns a Promise and resolves or rejects * @param testFunction - A Function that returns a Promise and resolves or rejects
*/ */
public testParallel(testDescription: string, testFunction: ITestFunction<T>) { public testParallel(testDescription: string, testFunction: ITestFunction<T>): TapTest<T> {
const localTest = new TapTest({ const localTest = new TapTest({
description: testDescription, description: testDescription,
testFunction, testFunction,
@@ -330,6 +347,8 @@ export class Tap<T> {
} else { } else {
this._tapTests.push(localTest); this._tapTests.push(localTest);
} }
return localTest;
} }
/** /**
@@ -360,6 +379,28 @@ export class Tap<T> {
} }
} }
/**
* Set up a function to run once before all tests in the current suite
*/
public beforeAll(setupFunction: ITestFunction<any>) {
if (this._currentSuite) {
this._currentSuite.beforeAll = setupFunction;
} else {
throw new Error('beforeAll can only be used inside a describe block');
}
}
/**
* Set up a function to run once after all tests in the current suite
*/
public afterAll(teardownFunction: ITestFunction<any>) {
if (this._currentSuite) {
this._currentSuite.afterAll = teardownFunction;
} else {
throw new Error('afterAll can only be used inside a describe block');
}
}
/** /**
* Set up a function to run before each test in the current suite * Set up a function to run before each test in the current suite
*/ */
@@ -554,6 +595,11 @@ export class Tap<T> {
console.log(failReason); console.log(failReason);
} }
// Run post tasks
for (const postTask of this._tapPostTasks) {
await postTask.run();
}
// Run global afterAll hook if configured // Run global afterAll hook if configured
if (settings.afterAll) { if (settings.afterAll) {
try { try {
@@ -597,6 +643,12 @@ export class Tap<T> {
suiteName: suite.description suiteName: suite.description
} }
}); });
// Run beforeAll hook for this suite
if (suite.beforeAll) {
await suite.beforeAll(new TapTools(null as any));
}
// Run beforeEach from parent suites // Run beforeEach from parent suites
const beforeEachFunctions: ITestFunction<any>[] = []; const beforeEachFunctions: ITestFunction<any>[] = [];
let currentSuite: ITestSuite | null = suite; let currentSuite: ITestSuite | null = suite;
@@ -667,6 +719,11 @@ export class Tap<T> {
// Recursively run child suites // Recursively run child suites
await this._runSuite(suite, suite.children, promiseArray, context); await this._runSuite(suite, suite.children, promiseArray, context);
// Run afterAll hook for this suite
if (suite.afterAll) {
await suite.afterAll(new TapTools(null as any));
}
// Emit suite:completed event // Emit suite:completed event
this.emitEvent({ this.emitEvent({
eventType: 'suite:completed', eventType: 'suite:completed',

View File

@@ -1,17 +1,21 @@
# @git.zone/tstest/tapbundle_node # @git.zone/tstest/tapbundle_serverside
> 🔧 Node.js-specific testing utilities for enhanced test capabilities > 🔧 Server-side testing utilities for Node.js runtime tests
## Installation ## Installation
```bash ```bash
# tapbundle_node is included as part of @git.zone/tstest # tapbundle_serverside is included as part of @git.zone/tstest
pnpm install --save-dev @git.zone/tstest pnpm install --save-dev @git.zone/tstest
``` ```
## Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit https://community.foss.global/. This is the central community hub for all issue reporting. Developers who want to sign a contribution agreement and go through identification can also get a code.foss.global account to submit Pull Requests directly.
## Overview ## Overview
`@git.zone/tstest/tapbundle_node` provides Node.js-specific utilities for testing. These tools are only available when running tests in Node.js runtime and provide functionality for working with environment variables, shell commands, test databases, storage systems, and HTTPS certificates. `@git.zone/tstest/tapbundle_serverside` provides server-side testing utilities exclusively for Node.js runtime. These tools enable shell command execution, environment variable management, HTTPS certificate generation, database testing, object storage testing, and test asset management - all functionality that only makes sense on the server-side.
## Key Features ## Key Features
@@ -25,11 +29,11 @@ pnpm install --save-dev @git.zone/tstest
## Basic Usage ## Basic Usage
```typescript ```typescript
import { tapNodeTools } from '@git.zone/tstest/tapbundle_node'; import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
import { tap } from '@git.zone/tstest/tapbundle'; import { tap } from '@git.zone/tstest/tapbundle';
tap.test('should use node-specific tools', async () => { tap.test('should use server-side tools', async () => {
// Use Node.js-specific utilities // Execute shell commands on the server-side
const result = await tapNodeTools.runCommand('echo "hello"'); const result = await tapNodeTools.runCommand('echo "hello"');
console.log(result); console.log(result);
}); });
@@ -131,7 +135,7 @@ tap.test('should create HTTPS server', async () => {
Create an ephemeral MongoDB instance for testing. Automatically started and ready to use. Create an ephemeral MongoDB instance for testing. Automatically started and ready to use.
```typescript ```typescript
import { tapNodeTools } from '@git.zone/tstest/tapbundle_node'; import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
tap.test('should use MongoDB', async () => { tap.test('should use MongoDB', async () => {
const mongoInstance = await tapNodeTools.createSmartmongo(); const mongoInstance = await tapNodeTools.createSmartmongo();
@@ -170,7 +174,7 @@ export default tap.start();
Create a local S3-compatible storage instance for testing object storage operations. Create a local S3-compatible storage instance for testing object storage operations.
```typescript ```typescript
import { tapNodeTools } from '@git.zone/tstest/tapbundle_node'; import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
tap.test('should use S3 storage', async () => { tap.test('should use S3 storage', async () => {
const s3Instance = await tapNodeTools.createSmarts3(); const s3Instance = await tapNodeTools.createSmarts3();
@@ -209,7 +213,7 @@ Utility for downloading and managing test assets.
Download the Alpine Linux Docker image as a tarball for testing. Download the Alpine Linux Docker image as a tarball for testing.
```typescript ```typescript
import { tapNodeTools } from '@git.zone/tstest/tapbundle_node'; import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
tap.test('should provide docker image', async () => { tap.test('should provide docker image', async () => {
const tarballPath = await tapNodeTools.testFileProvider.getDockerAlpineImageAsLocalTarball(); const tarballPath = await tapNodeTools.testFileProvider.getDockerAlpineImageAsLocalTarball();
@@ -238,7 +242,7 @@ export default tap.start();
The module exports useful path constants: The module exports useful path constants:
```typescript ```typescript
import * as paths from '@git.zone/tstest/tapbundle_node/paths'; import * as paths from '@git.zone/tstest/tapbundle_serverside/paths';
console.log(paths.cwd); // Current working directory console.log(paths.cwd); // Current working directory
console.log(paths.testFilesDir); // ./.nogit/testfiles/ console.log(paths.testFilesDir); // ./.nogit/testfiles/
@@ -249,7 +253,7 @@ console.log(paths.testFilesDir); // ./.nogit/testfiles/
### Testing with External Services ### Testing with External Services
```typescript ```typescript
import { tapNodeTools } from '@git.zone/tstest/tapbundle_node'; import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
tap.describe('User Service Integration', () => { tap.describe('User Service Integration', () => {
@@ -280,7 +284,7 @@ export default tap.start();
### Testing HTTPS Servers ### Testing HTTPS Servers
```typescript ```typescript
import { tapNodeTools } from '@git.zone/tstest/tapbundle_node'; import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as https from 'https'; import * as https from 'https';
@@ -311,7 +315,7 @@ export default tap.start();
### Environment-Dependent Tests ### Environment-Dependent Tests
```typescript ```typescript
import { tapNodeTools } from '@git.zone/tstest/tapbundle_node'; import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
import { tap, expect } from '@git.zone/tstest/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
tap.test('should authenticate with GitHub', async () => { tap.test('should authenticate with GitHub', async () => {
@@ -332,12 +336,14 @@ export default tap.start();
## Runtime Requirements ## Runtime Requirements
⚠️ **Node.js Only**: All utilities in this module require Node.js runtime. They will not work in: ⚠️ **Server-Side Only (Node.js)**: All utilities in this module are designed exclusively for server-side testing in Node.js runtime. They provide functionality like shell command execution, file system operations, and process management that only make sense on the server.
**NOT available in:**
- Browser environments - Browser environments
- Deno runtime - Deno runtime
- Bun runtime - Bun runtime
For multi-runtime tests, use these utilities only in `.node.ts` test files. **Important:** Import tapbundle_serverside only in tests that run exclusively on the server-side (`.node.ts` test files). For cross-runtime tests, these utilities will fail in non-Node environments.
## File Naming ## File Naming
@@ -360,8 +366,21 @@ This module uses the following packages:
- [@push.rocks/smartfile](https://code.foss.global/push.rocks/smartfile) - File operations - [@push.rocks/smartfile](https://code.foss.global/push.rocks/smartfile) - File operations
- [@push.rocks/smartrequest](https://code.foss.global/push.rocks/smartrequest) - HTTP requests - [@push.rocks/smartrequest](https://code.foss.global/push.rocks/smartrequest) - HTTP requests
## Legal ## License and Legal Information
This project is licensed under MIT. This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](../license) file within this repository.
© 2025 Task Venture Capital GmbH. All rights reserved. **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
### Company Information
Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.