Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 913c3cafe8 | |||
| 9ec2c8b6eb | |||
| 286030a08d | |||
| 46f0a5a8cf | |||
| ae59b7adf2 | |||
| 2b81e8e5aa | |||
| c9950d31ac | |||
| d6f657a46a | |||
| 0379bd7288 | |||
| 9ab4f1838d | |||
| b9016206ce | |||
| 8edbbd4850 | |||
| 97c91fc010 | |||
| ca08bb2e3c | |||
| 8fd114334f | |||
| c630a171b5 | |||
| 1a4eb5b6d9 | |||
| 41d7c1ce49 | |||
| 3ab5550cb8 | |||
| ee7b387534 | |||
| 7e67b64a6e | |||
| 1ce730d4f2 | |||
| 9357d6e7ef | |||
| 973ce771d2 |
1
.serena/.gitignore
vendored
1
.serena/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
/cache
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby)
|
|
||||||
# * For C, use cpp
|
|
||||||
# * For JavaScript, use typescript
|
|
||||||
# Special requirements:
|
|
||||||
# * csharp: Requires the presence of a .sln file in the project folder.
|
|
||||||
language: typescript
|
|
||||||
|
|
||||||
# whether to use the project's gitignore file to ignore files
|
|
||||||
# Added on 2025-04-07
|
|
||||||
ignore_all_files_in_gitignore: true
|
|
||||||
# list of additional paths to ignore
|
|
||||||
# same syntax as gitignore, so you can use * and **
|
|
||||||
# Was previously called `ignored_dirs`, please update your config if you are using that.
|
|
||||||
# Added (renamed) on 2025-04-07
|
|
||||||
ignored_paths: []
|
|
||||||
|
|
||||||
# whether the project is in read-only mode
|
|
||||||
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
|
||||||
# Added on 2025-04-18
|
|
||||||
read_only: false
|
|
||||||
|
|
||||||
|
|
||||||
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
|
||||||
# Below is the complete list of tools for convenience.
|
|
||||||
# To make sure you have the latest list of tools, and to view their descriptions,
|
|
||||||
# execute `uv run scripts/print_tool_overview.py`.
|
|
||||||
#
|
|
||||||
# * `activate_project`: Activates a project by name.
|
|
||||||
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
|
||||||
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
|
||||||
# * `delete_lines`: Deletes a range of lines within a file.
|
|
||||||
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
|
|
||||||
# * `execute_shell_command`: Executes a shell command.
|
|
||||||
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
|
|
||||||
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
|
||||||
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
|
||||||
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
|
||||||
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
|
||||||
# * `initial_instructions`: Gets the initial instructions for the current project.
|
|
||||||
# Should only be used in settings where the system prompt cannot be set,
|
|
||||||
# e.g. in clients you have no control over, like Claude Desktop.
|
|
||||||
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
|
||||||
# * `insert_at_line`: Inserts content at a given line in a file.
|
|
||||||
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
|
||||||
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
|
||||||
# * `list_memories`: Lists memories in Serena's project-specific memory store.
|
|
||||||
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
|
||||||
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
|
|
||||||
# * `read_file`: Reads a file within the project directory.
|
|
||||||
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
|
|
||||||
# * `remove_project`: Removes a project from the Serena configuration.
|
|
||||||
# * `replace_lines`: Replaces a range of lines within a file with new content.
|
|
||||||
# * `replace_symbol_body`: Replaces the full definition of a symbol.
|
|
||||||
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
|
|
||||||
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
|
||||||
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
|
|
||||||
# * `switch_modes`: Activates modes by providing a list of their names
|
|
||||||
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
|
|
||||||
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
|
|
||||||
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
|
|
||||||
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
|
||||||
excluded_tools: []
|
|
||||||
|
|
||||||
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
|
||||||
# (contrary to the memories, which are loaded on demand).
|
|
||||||
initial_prompt: ""
|
|
||||||
|
|
||||||
project_name: "tstest"
|
|
||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -22,5 +22,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"deno.enable": false
|
||||||
}
|
}
|
||||||
|
|||||||
13
bunfig.toml
13
bunfig.toml
@@ -1,13 +0,0 @@
|
|||||||
# Bun configuration for tstest
|
|
||||||
# This enables TypeScript decorator support in Bun runtime
|
|
||||||
|
|
||||||
[build]
|
|
||||||
target = "bun"
|
|
||||||
|
|
||||||
[test]
|
|
||||||
preload = []
|
|
||||||
|
|
||||||
# Enable decorators for Bun's TypeScript transpiler
|
|
||||||
# This ensures user code with decorators works when executed via Bun
|
|
||||||
[transpiler]
|
|
||||||
experimentalDecorators = true
|
|
||||||
93
changelog.md
93
changelog.md
@@ -1,5 +1,98 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-01-19 - 3.1.6 - fix(logging)
|
||||||
|
handle mid-line streaming output in test logger and add streaming tests
|
||||||
|
|
||||||
|
- Introduce isOutputMidLine flag to track when streaming output does not end with a newline
|
||||||
|
- Only prepend the visual prefix at the start of a line and append segments to the last buffered entry when mid-line
|
||||||
|
- Write consistent output to log files for both complete lines and raw streaming segments
|
||||||
|
- Add tests to exercise streaming behavior: test/tstest/test.gap-debug.ts and test/tstest/test.gap-debug2.ts
|
||||||
|
|
||||||
|
## 2026-01-19 - 3.1.5 - fix(tstest)
|
||||||
|
preserve streaming console output and correctly buffer incomplete TAP lines
|
||||||
|
|
||||||
|
- Reworked TapParser._processLog to buffer incomplete lines and only parse complete TAP protocol lines
|
||||||
|
- Added TapParser.lineBuffer and _looksLikeTapStart() to detect and buffer starts of TAP messages
|
||||||
|
- Added TapParser._handleConsoleOutput() to centralize console output handling and snapshot parsing; flushes buffered content on process exit
|
||||||
|
- Added TapTestResult.addLogLineRaw() to append streaming text without adding newlines
|
||||||
|
- Added TsTestLogger.testConsoleOutputStreaming() and logToTestFileRaw() to preserve streaming output formatting in both console and logfile
|
||||||
|
|
||||||
|
## 2025-12-30 - 3.1.4 - fix(webhelpers)
|
||||||
|
improve browser test fixture to append element and await custom element upgrade and Lit update completion; add generic return type; update npm packaging release config; remove pnpm onlyBuiltDependencies
|
||||||
|
|
||||||
|
- ts_tapbundle/webhelpers.ts: make fixture generic and return T; append created element to document; await customElements.whenDefined for custom elements and await updateComplete for Lit/async components to ensure stable rendering in tests
|
||||||
|
- npmextra.json: add @git.zone/cli module metadata and release.registries/accessLevel; add @ship.zone/szci entry
|
||||||
|
- pnpm-workspace.yaml: remove onlyBuiltDependencies entries
|
||||||
|
|
||||||
|
## 2025-11-21 - 3.1.3 - fix(docs)
|
||||||
|
Update package author and expand license/legal and issue-reporting information in tapbundle docs
|
||||||
|
|
||||||
|
- Update package.json author field to Task Venture Capital GmbH
|
||||||
|
- Add expanded License and Legal Information to ts_tapbundle/readme.md (clarifies MIT license scope and trademark guidance)
|
||||||
|
- Add expanded License and Legal Information to ts_tapbundle_protocol/readme.md (clarifies MIT license scope and trademark guidance)
|
||||||
|
- Add Issue Reporting and Security section to ts_tapbundle_protocol/readme.md pointing users to the community hub for bug/security reports
|
||||||
|
- Include company information and trademark usage guidance in readmes
|
||||||
|
|
||||||
|
## 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)
|
## 2025-11-17 - 2.8.0 - feat(runtime-adapters)
|
||||||
Enable TypeScript decorator support for Deno and Bun runtimes and add decorator tests
|
Enable TypeScript decorator support for Deno and Bun runtimes and add decorator tests
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
"useDefineForClassFields": false,
|
||||||
"lib": [
|
"lib": [
|
||||||
"ES2022",
|
"ES2022",
|
||||||
"DOM"
|
"DOM"
|
||||||
@@ -8,5 +9,5 @@
|
|||||||
"target": "ES2022"
|
"target": "ES2022"
|
||||||
},
|
},
|
||||||
"nodeModulesDir": true,
|
"nodeModulesDir": true,
|
||||||
"version": "2.8.0"
|
"version": "3.1.6"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"npmci": {
|
"@git.zone/cli": {
|
||||||
"npmGlobalTools": [],
|
|
||||||
"npmAccessLevel": "public"
|
|
||||||
},
|
|
||||||
"gitzone": {
|
|
||||||
"projectType": "npm",
|
"projectType": "npm",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "code.foss.global",
|
"githost": "code.foss.global",
|
||||||
@@ -12,6 +8,16 @@
|
|||||||
"description": "a test utility to run tests that match test/**/*.ts",
|
"description": "a test utility to run tests that match test/**/*.ts",
|
||||||
"npmPackagename": "@git.zone/tstest",
|
"npmPackagename": "@git.zone/tstest",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"registries": [
|
||||||
|
"https://verdaccio.lossless.digital",
|
||||||
|
"https://registry.npmjs.org"
|
||||||
|
],
|
||||||
|
"accessLevel": "public"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"@ship.zone/szci": {
|
||||||
|
"npmGlobalTools": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
22
package.json
22
package.json
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/tstest",
|
"name": "@git.zone/tstest",
|
||||||
"version": "2.8.0",
|
"version": "3.1.6",
|
||||||
"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"
|
"./tapbundle_protocol": "./dist_ts_tapbundle_protocol/index.js"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "Lossless GmbH",
|
"author": "Task Venture Capital GmbH",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tstest": "./cli.js"
|
"tstest": "./cli.js"
|
||||||
@@ -25,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
4825
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
|||||||
onlyBuiltDependencies:
|
|
||||||
- esbuild
|
|
||||||
- mongodb-memory-server
|
|
||||||
- puppeteer
|
|
||||||
178
readme.hints.md
178
readme.hints.md
@@ -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)
|
||||||
@@ -320,4 +445,49 @@ The protocol parser was fixed to correctly handle inline timing metadata:
|
|||||||
- Changed condition from `!simpleMatch[1].includes(':')` to check for simple key:value pairs
|
- Changed condition from `!simpleMatch[1].includes(':')` to check for simple key:value pairs
|
||||||
- Excludes prefixed formats (META:, SKIP:, TODO:, EVENT:) while parsing simple formats like `time:250`
|
- Excludes prefixed formats (META:, SKIP:, TODO:, EVENT:) while parsing simple formats like `time:250`
|
||||||
|
|
||||||
This ensures timing metadata is correctly extracted and displayed in test results.
|
This ensures timing metadata is correctly extracted and displayed in test results.
|
||||||
|
|
||||||
|
## Streaming Console Output (Fixed)
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
When tests use `process.stdout.write()` for streaming output (without newlines), each write was appearing on a separate line. This happened because:
|
||||||
|
1. Child process stdout data events arrive as separate chunks
|
||||||
|
2. `TapParser._processLog()` split on `\n` and processed each segment
|
||||||
|
3. `testConsoleOutput()` used `console.log()` which added a newline to each call
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
The streaming behavior is now preserved by:
|
||||||
|
1. **Line buffering for TAP parsing**: Only buffer content that looks like TAP protocol messages
|
||||||
|
2. **True streaming for console output**: Use `process.stdout.write()` instead of `console.log()` for partial lines
|
||||||
|
3. **Intelligent detection**: `_looksLikeTapStart()` checks if content could be a TAP protocol message
|
||||||
|
|
||||||
|
### Implementation Details
|
||||||
|
|
||||||
|
**TapParser changes:**
|
||||||
|
- Added `lineBuffer` property to buffer incomplete TAP protocol lines
|
||||||
|
- Rewrote `_processLog()` to handle streaming correctly:
|
||||||
|
- Complete lines (with newline) are processed through protocol parser
|
||||||
|
- Incomplete lines that look like TAP are buffered
|
||||||
|
- Incomplete lines that don't look like TAP are streamed immediately
|
||||||
|
- Added `_looksLikeTapStart()` helper to detect TAP protocol patterns
|
||||||
|
- Added `_handleConsoleOutput()` to handle console output with proper streaming
|
||||||
|
- Buffer is flushed on process exit
|
||||||
|
|
||||||
|
**TsTestLogger changes:**
|
||||||
|
- Added `testConsoleOutputStreaming()` method that uses `process.stdout.write()` in verbose mode
|
||||||
|
- Added `logToTestFileRaw()` for writing to log files without adding newlines
|
||||||
|
- In non-verbose mode, streaming content is appended to the last buffered entry
|
||||||
|
|
||||||
|
**TapTestResult changes:**
|
||||||
|
- Added `addLogLineRaw()` method that doesn't append newlines
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
Tests can now use streaming output naturally:
|
||||||
|
```typescript
|
||||||
|
process.stdout.write("Loading");
|
||||||
|
process.stdout.write(".");
|
||||||
|
process.stdout.write(".");
|
||||||
|
process.stdout.write(".\n");
|
||||||
|
```
|
||||||
|
|
||||||
|
This will correctly display as `Loading...` on a single line in verbose mode.
|
||||||
46
readme.md
46
readme.md
@@ -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,9 +322,34 @@ 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
|
- `@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
|
## tapbundle Protocol V2
|
||||||
|
|
||||||
tstest includes an enhanced TAP protocol (Protocol V2) that extends standard TAP 13 with additional metadata while maintaining backwards compatibility.
|
tstest includes an enhanced TAP protocol (Protocol V2) that extends standard TAP 13 with additional metadata while maintaining backwards compatibility.
|
||||||
@@ -1049,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
|
||||||
|
|||||||
170
test/tapbundle/test.new-lifecycle.ts
Normal file
170
test/tapbundle/test.new-lifecycle.ts
Normal 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();
|
||||||
@@ -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');
|
||||||
|
|||||||
12
test/tstest/test.gap-debug.ts
Normal file
12
test/tstest/test.gap-debug.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { tap, expect } from '../../ts_tapbundle/index.js';
|
||||||
|
|
||||||
|
tap.test('check for gaps in streaming', async () => {
|
||||||
|
// This should print "ABCD" with no gaps
|
||||||
|
process.stdout.write("A");
|
||||||
|
process.stdout.write("B");
|
||||||
|
process.stdout.write("C");
|
||||||
|
process.stdout.write("D\n");
|
||||||
|
expect(true).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
14
test/tstest/test.gap-debug2.ts
Normal file
14
test/tstest/test.gap-debug2.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { tap, expect } from '../../ts_tapbundle/index.js';
|
||||||
|
|
||||||
|
tap.test('streaming with delays', async (tools) => {
|
||||||
|
// Simulate real streaming with delays
|
||||||
|
process.stdout.write("Progress: [");
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
await tools.delayFor(50);
|
||||||
|
process.stdout.write("=");
|
||||||
|
}
|
||||||
|
process.stdout.write("]\n");
|
||||||
|
expect(true).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/tstest',
|
name: '@git.zone/tstest',
|
||||||
version: '2.8.0',
|
version: '3.1.6',
|
||||||
description: 'a test utility to run tests that match test/**/*.ts'
|
description: 'a test utility to run tests that match test/**/*.ts'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ export class TapParser {
|
|||||||
receivedTests: number = 0;
|
receivedTests: number = 0;
|
||||||
|
|
||||||
activeTapTestResult: TapTestResult;
|
activeTapTestResult: TapTestResult;
|
||||||
|
|
||||||
private logger: TsTestLogger;
|
private logger: TsTestLogger;
|
||||||
private protocolParser: ProtocolParser;
|
private protocolParser: ProtocolParser;
|
||||||
private protocolVersion: string | null = null;
|
private protocolVersion: string | null = null;
|
||||||
private startTime: number;
|
private startTime: number;
|
||||||
|
private lineBuffer: string = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the constructor for TapParser
|
* the constructor for TapParser
|
||||||
@@ -71,42 +72,99 @@ export class TapParser {
|
|||||||
if (Buffer.isBuffer(logChunk)) {
|
if (Buffer.isBuffer(logChunk)) {
|
||||||
logChunk = logChunk.toString();
|
logChunk = logChunk.toString();
|
||||||
}
|
}
|
||||||
const logLineArray = logChunk.split('\n');
|
|
||||||
if (logLineArray[logLineArray.length - 1] === '') {
|
// Prepend any buffered content from previous incomplete line
|
||||||
logLineArray.pop();
|
const fullChunk = this.lineBuffer + logChunk;
|
||||||
|
this.lineBuffer = '';
|
||||||
|
|
||||||
|
// Split into segments by newline
|
||||||
|
const segments = fullChunk.split('\n');
|
||||||
|
const lastIndex = segments.length - 1;
|
||||||
|
|
||||||
|
for (let i = 0; i < segments.length; i++) {
|
||||||
|
const segment = segments[i];
|
||||||
|
const isLastSegment = (i === lastIndex);
|
||||||
|
const hasNewline = !isLastSegment; // All segments except last had a newline after them
|
||||||
|
|
||||||
|
if (hasNewline) {
|
||||||
|
// Complete line - check if it's a TAP protocol message
|
||||||
|
const messages = this.protocolParser.parseLine(segment);
|
||||||
|
|
||||||
|
if (messages.length > 0) {
|
||||||
|
// Handle protocol messages
|
||||||
|
for (const message of messages) {
|
||||||
|
this._handleProtocolMessage(message, segment);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Non-protocol complete line - handle as console output
|
||||||
|
this._handleConsoleOutput(segment, true);
|
||||||
|
}
|
||||||
|
} else if (segment) {
|
||||||
|
// Last segment without newline - could be:
|
||||||
|
// 1. Partial console output (stream immediately)
|
||||||
|
// 2. Start of a TAP message (need to buffer for protocol parsing)
|
||||||
|
|
||||||
|
// Check if it looks like the start of a TAP protocol message
|
||||||
|
if (this._looksLikeTapStart(segment)) {
|
||||||
|
// Buffer it for complete line parsing
|
||||||
|
this.lineBuffer = segment;
|
||||||
|
} else {
|
||||||
|
// Stream immediately as console output (no newline)
|
||||||
|
this._handleConsoleOutput(segment, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if text could be the start of a TAP protocol message
|
||||||
|
*/
|
||||||
|
private _looksLikeTapStart(text: string): boolean {
|
||||||
|
return (
|
||||||
|
text.startsWith('ok ') ||
|
||||||
|
text.startsWith('not ok ') ||
|
||||||
|
text.startsWith('1..') ||
|
||||||
|
text.startsWith('# ') ||
|
||||||
|
text.startsWith('TAP version ') ||
|
||||||
|
text.startsWith('⟦TSTEST:') ||
|
||||||
|
text.startsWith('Bail out!')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle console output from test, preserving streaming behavior
|
||||||
|
*/
|
||||||
|
private _handleConsoleOutput(text: string, hasNewline: boolean) {
|
||||||
|
// Check for snapshot communication (legacy)
|
||||||
|
const snapshotMatch = text.match(/###SNAPSHOT###(.+)###SNAPSHOT###/);
|
||||||
|
if (snapshotMatch) {
|
||||||
|
const base64Data = snapshotMatch[1];
|
||||||
|
try {
|
||||||
|
const snapshotData = JSON.parse(Buffer.from(base64Data, 'base64').toString());
|
||||||
|
this.handleSnapshot(snapshotData);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (this.logger) {
|
||||||
|
this.logger.testConsoleOutput(`Error parsing snapshot data: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process each line through the protocol parser
|
// Add to test result buffer
|
||||||
for (const logLine of logLineArray) {
|
if (this.activeTapTestResult) {
|
||||||
const messages = this.protocolParser.parseLine(logLine);
|
if (hasNewline) {
|
||||||
|
this.activeTapTestResult.addLogLine(text);
|
||||||
if (messages.length > 0) {
|
|
||||||
// Handle protocol messages
|
|
||||||
for (const message of messages) {
|
|
||||||
this._handleProtocolMessage(message, logLine);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Not a protocol message, handle as console output
|
this.activeTapTestResult.addLogLineRaw(text);
|
||||||
if (this.activeTapTestResult) {
|
}
|
||||||
this.activeTapTestResult.addLogLine(logLine);
|
}
|
||||||
}
|
|
||||||
|
// Output to logger with streaming support
|
||||||
// Check for snapshot communication (legacy)
|
if (this.logger) {
|
||||||
const snapshotMatch = logLine.match(/###SNAPSHOT###(.+)###SNAPSHOT###/);
|
if (hasNewline) {
|
||||||
if (snapshotMatch) {
|
this.logger.testConsoleOutput(text);
|
||||||
const base64Data = snapshotMatch[1];
|
} else {
|
||||||
try {
|
this.logger.testConsoleOutputStreaming(text);
|
||||||
const snapshotData = JSON.parse(Buffer.from(base64Data, 'base64').toString());
|
|
||||||
this.handleSnapshot(snapshotData);
|
|
||||||
} catch (error: any) {
|
|
||||||
if (this.logger) {
|
|
||||||
this.logger.testConsoleOutput(`Error parsing snapshot data: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (this.logger) {
|
|
||||||
// This is console output from the test file
|
|
||||||
this.logger.testConsoleOutput(logLine);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,6 +475,11 @@ export class TapParser {
|
|||||||
this._processLog(data);
|
this._processLog(data);
|
||||||
});
|
});
|
||||||
childProcessArg.on('exit', async () => {
|
childProcessArg.on('exit', async () => {
|
||||||
|
// Flush any remaining buffered content
|
||||||
|
if (this.lineBuffer) {
|
||||||
|
this._handleConsoleOutput(this.lineBuffer, false);
|
||||||
|
this.lineBuffer = '';
|
||||||
|
}
|
||||||
await this.evaluateFinalResult();
|
await this.evaluateFinalResult();
|
||||||
done.resolve();
|
done.resolve();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export class TapTestResult {
|
|||||||
constructor(public id: number) {}
|
constructor(public id: number) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* adds a logLine to the log buffer of the test
|
* adds a logLine to the log buffer of the test (with newline appended)
|
||||||
* @param logLine
|
* @param logLine
|
||||||
*/
|
*/
|
||||||
addLogLine(logLine: string) {
|
addLogLine(logLine: string) {
|
||||||
@@ -19,6 +19,15 @@ export class TapTestResult {
|
|||||||
this.testLogBuffer = Buffer.concat([this.testLogBuffer, logLineBuffer]);
|
this.testLogBuffer = Buffer.concat([this.testLogBuffer, logLineBuffer]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds raw text to the log buffer without appending newline (for streaming output)
|
||||||
|
* @param text
|
||||||
|
*/
|
||||||
|
addLogLineRaw(text: string) {
|
||||||
|
const logLineBuffer = Buffer.from(text);
|
||||||
|
this.testLogBuffer = Buffer.concat([this.testLogBuffer, logLineBuffer]);
|
||||||
|
}
|
||||||
|
|
||||||
setTestResult(testOkArg: boolean) {
|
setTestResult(testOkArg: boolean) {
|
||||||
this.testOk = testOkArg;
|
this.testOk = testOkArg;
|
||||||
this.testSettled = true;
|
this.testSettled = true;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export class TsTestLogger {
|
|||||||
private currentTestLogFile: string | null = null;
|
private currentTestLogFile: string | null = null;
|
||||||
private currentTestLogs: string[] = []; // Buffer for current test logs
|
private currentTestLogs: string[] = []; // Buffer for current test logs
|
||||||
private currentTestFailed: boolean = false;
|
private currentTestFailed: boolean = false;
|
||||||
|
private isOutputMidLine: boolean = false; // Track whether we're mid-line for streaming output
|
||||||
|
|
||||||
constructor(options: LogOptions = {}) {
|
constructor(options: LogOptions = {}) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
@@ -189,6 +190,7 @@ export class TsTestLogger {
|
|||||||
// Reset test-specific state
|
// Reset test-specific state
|
||||||
this.currentTestLogs = [];
|
this.currentTestLogs = [];
|
||||||
this.currentTestFailed = false;
|
this.currentTestFailed = false;
|
||||||
|
this.isOutputMidLine = false;
|
||||||
|
|
||||||
// Only set up test log file if --logfile option is specified
|
// Only set up test log file if --logfile option is specified
|
||||||
if (this.options.logFile) {
|
if (this.options.logFile) {
|
||||||
@@ -200,15 +202,18 @@ export class TsTestLogger {
|
|||||||
.replace(/\//g, '__') // Replace path separators with double underscores
|
.replace(/\//g, '__') // Replace path separators with double underscores
|
||||||
.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);
|
||||||
if (!fs.existsSync(logDir)) {
|
if (!fs.existsSync(logDir)) {
|
||||||
fs.mkdirSync(logDir, { recursive: true });
|
fs.mkdirSync(logDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the log file for this test
|
// Clear the log file for this test
|
||||||
fs.writeFileSync(this.currentTestLogFile, '');
|
fs.writeFileSync(this.currentTestLogFile, '');
|
||||||
}
|
}
|
||||||
@@ -345,21 +350,73 @@ export class TsTestLogger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Console output from test files (non-TAP output)
|
// Console output from test files (non-TAP output) - complete lines
|
||||||
testConsoleOutput(message: string) {
|
testConsoleOutput(message: string) {
|
||||||
if (this.options.json) return;
|
if (this.options.json) return;
|
||||||
|
|
||||||
|
const prefix = ' ';
|
||||||
// In verbose mode, show console output immediately
|
// In verbose mode, show console output immediately
|
||||||
if (this.options.verbose) {
|
if (this.options.verbose) {
|
||||||
this.log(this.format(` ${message}`, 'dim'));
|
// Only add prefix if we're starting a new line
|
||||||
|
const output = this.isOutputMidLine ? message : prefix + message;
|
||||||
|
this.log(this.format(output, 'dim'));
|
||||||
} else {
|
} else {
|
||||||
// In non-verbose mode, buffer the logs
|
// In non-verbose mode, buffer the logs
|
||||||
this.currentTestLogs.push(message);
|
if (this.isOutputMidLine && this.currentTestLogs.length > 0) {
|
||||||
|
// Append to the last buffered entry since we're mid-line
|
||||||
|
this.currentTestLogs[this.currentTestLogs.length - 1] += message;
|
||||||
|
} else {
|
||||||
|
this.currentTestLogs.push(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset mid-line state since we just output a complete line
|
||||||
|
this.isOutputMidLine = false;
|
||||||
|
|
||||||
// Always log to test file if --logfile is specified
|
// Always log to test file if --logfile is specified
|
||||||
if (this.currentTestLogFile) {
|
if (this.currentTestLogFile) {
|
||||||
this.logToTestFile(` ${message}`);
|
this.logToTestFile(`${prefix}${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Streaming console output (preserves original formatting, no newline added)
|
||||||
|
testConsoleOutputStreaming(message: string) {
|
||||||
|
if (this.options.json) return;
|
||||||
|
|
||||||
|
const prefix = ' ';
|
||||||
|
// Only add prefix if we're starting a new line (not mid-line)
|
||||||
|
const output = this.isOutputMidLine ? message : prefix + message;
|
||||||
|
|
||||||
|
if (this.options.verbose) {
|
||||||
|
// Use process.stdout.write to preserve streaming without adding newlines
|
||||||
|
process.stdout.write(this.format(output, 'dim'));
|
||||||
|
} else {
|
||||||
|
// Buffer mode: append to last entry if mid-line
|
||||||
|
if (this.isOutputMidLine && this.currentTestLogs.length > 0) {
|
||||||
|
this.currentTestLogs[this.currentTestLogs.length - 1] += message;
|
||||||
|
} else {
|
||||||
|
this.currentTestLogs.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log to test file without adding newline
|
||||||
|
if (this.currentTestLogFile) {
|
||||||
|
this.logToTestFileRaw(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're now mid-line (no newline was written)
|
||||||
|
this.isOutputMidLine = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private logToTestFileRaw(message: string) {
|
||||||
|
try {
|
||||||
|
// Remove ANSI color codes for file logging
|
||||||
|
const cleanMessage = message.replace(/\u001b\[[0-9;]*m/g, '');
|
||||||
|
|
||||||
|
// Append to test log file without adding newline
|
||||||
|
fs.appendFileSync(this.currentTestLogFile, cleanMessage);
|
||||||
|
} catch (error) {
|
||||||
|
// Silently fail to avoid disrupting the test run
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 [community.foss.global/](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/](https://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:
|
||||||
@@ -382,8 +613,21 @@ tap.test('should use context', async (tapTools) => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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.
|
||||||
|
|||||||
21
ts_tapbundle/tapbundle.classes.posttask.ts
Normal file
21
ts_tapbundle/tapbundle.classes.posttask.ts
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,85 +25,89 @@ 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[]) {
|
||||||
this._tags = tags;
|
this._tags = tags;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
priority(level: 'high' | 'medium' | 'low') {
|
priority(level: 'high' | 'medium' | 'low') {
|
||||||
this._priority = level;
|
this._priority = level;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
retry(count: number) {
|
retry(count: number) {
|
||||||
this._retryCount = count;
|
this._retryCount = count;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout(ms: number) {
|
timeout(ms: number) {
|
||||||
this._timeoutMs = ms;
|
this._timeoutMs = ms;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
test.tags = this._tags;
|
test.tags = this._tags;
|
||||||
}
|
}
|
||||||
test.priority = this._priority;
|
test.priority = this._priority;
|
||||||
|
|
||||||
if (this._retryCount !== undefined) {
|
if (this._retryCount !== undefined) {
|
||||||
test.tapTools.retry(this._retryCount);
|
test.tapTools.retry(this._retryCount);
|
||||||
}
|
}
|
||||||
if (this._timeoutMs !== undefined) {
|
if (this._timeoutMs !== undefined) {
|
||||||
test.timeoutMs = this._timeoutMs;
|
test.timeoutMs = this._timeoutMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
|
|
||||||
testOnly(description: string, testFunction: ITestFunction<T>) {
|
testOnly(description: string, testFunction: ITestFunction<T>) {
|
||||||
const test = this._tap.test(description, testFunction, 'only');
|
const test = this._tap.test(description, testFunction, 'only');
|
||||||
|
|
||||||
// Apply settings to the test
|
// Apply settings to the test
|
||||||
if (this._tags.length > 0) {
|
if (this._tags.length > 0) {
|
||||||
test.tags = this._tags;
|
test.tags = this._tags;
|
||||||
}
|
}
|
||||||
test.priority = this._priority;
|
test.priority = this._priority;
|
||||||
|
|
||||||
if (this._retryCount !== undefined) {
|
if (this._retryCount !== undefined) {
|
||||||
test.tapTools.retry(this._retryCount);
|
test.tapTools.retry(this._retryCount);
|
||||||
}
|
}
|
||||||
if (this._timeoutMs !== undefined) {
|
if (this._timeoutMs !== undefined) {
|
||||||
test.timeoutMs = this._timeoutMs;
|
test.timeoutMs = this._timeoutMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
|
|
||||||
testSkip(description: string, testFunction: ITestFunction<T>) {
|
testSkip(description: string, testFunction: ITestFunction<T>) {
|
||||||
const test = this._tap.test(description, testFunction, 'skip');
|
const test = this._tap.test(description, testFunction, 'skip');
|
||||||
|
|
||||||
// Apply settings to the test
|
// Apply settings to the test
|
||||||
if (this._tags.length > 0) {
|
if (this._tags.length > 0) {
|
||||||
test.tags = this._tags;
|
test.tags = this._tags;
|
||||||
}
|
}
|
||||||
test.priority = this._priority;
|
test.priority = this._priority;
|
||||||
|
|
||||||
if (this._retryCount !== undefined) {
|
if (this._retryCount !== undefined) {
|
||||||
test.tapTools.retry(this._retryCount);
|
test.tapTools.retry(this._retryCount);
|
||||||
}
|
}
|
||||||
if (this._timeoutMs !== undefined) {
|
if (this._timeoutMs !== undefined) {
|
||||||
test.timeoutMs = this._timeoutMs;
|
test.timeoutMs = this._timeoutMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,21 +130,25 @@ export class Tap<T> {
|
|||||||
const builder = new TestBuilder<T>(this);
|
const builder = new TestBuilder<T>(this);
|
||||||
return builder.tags(...tags);
|
return builder.tags(...tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public priority(level: 'high' | 'medium' | 'low') {
|
public priority(level: 'high' | 'medium' | 'low') {
|
||||||
const builder = new TestBuilder<T>(this);
|
const builder = new TestBuilder<T>(this);
|
||||||
return builder.priority(level);
|
return builder.priority(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
public retry(count: number) {
|
public retry(count: number) {
|
||||||
const builder = new TestBuilder<T>(this);
|
const builder = new TestBuilder<T>(this);
|
||||||
return builder.retry(count);
|
return builder.retry(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public timeout(ms: number) {
|
public timeout(ms: number) {
|
||||||
const builder = new TestBuilder<T>(this);
|
const builder = new TestBuilder<T>(this);
|
||||||
return builder.timeout(ms);
|
return builder.timeout(ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public parallel() {
|
||||||
|
return new TestBuilder<T>(this, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* skips a test
|
* skips a test
|
||||||
@@ -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,18 +317,22 @@ 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,
|
||||||
parallel: true,
|
parallel: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply default settings from settings manager
|
// Apply default settings from settings manager
|
||||||
const settings = this.settingsManager.getSettings();
|
const settings = this.settingsManager.getSettings();
|
||||||
if (settings.timeout !== undefined) {
|
if (settings.timeout !== undefined) {
|
||||||
@@ -324,12 +341,14 @@ export class Tap<T> {
|
|||||||
if (settings.retries !== undefined) {
|
if (settings.retries !== undefined) {
|
||||||
localTest.tapTools.retry(settings.retries);
|
localTest.tapTools.retry(settings.retries);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._currentSuite) {
|
if (this._currentSuite) {
|
||||||
this._currentSuite.tests.push(localTest);
|
this._currentSuite.tests.push(localTest);
|
||||||
} 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
|
||||||
*/
|
*/
|
||||||
@@ -370,7 +411,7 @@ export class Tap<T> {
|
|||||||
throw new Error('beforeEach can only be used inside a describe block');
|
throw new Error('beforeEach can only be used inside a describe block');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up a function to run after each test in the current suite
|
* Set up a function to run after 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;
|
||||||
@@ -666,7 +718,12 @@ 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',
|
||||||
|
|||||||
@@ -22,10 +22,24 @@ class WebHelpers {
|
|||||||
|
|
||||||
// Initialize fixture function based on environment
|
// Initialize fixture function based on environment
|
||||||
if (smartenv.isBrowser) {
|
if (smartenv.isBrowser) {
|
||||||
this.fixture = async (htmlString: string): Promise<HTMLElement> => {
|
this.fixture = async <T extends HTMLElement>(htmlString: string): Promise<T> => {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.innerHTML = htmlString.trim();
|
container.innerHTML = htmlString.trim();
|
||||||
const element = container.firstChild as HTMLElement;
|
const element = container.firstElementChild as T;
|
||||||
|
|
||||||
|
// Append to document so custom elements upgrade and lifecycle hooks fire
|
||||||
|
document.body.appendChild(element);
|
||||||
|
|
||||||
|
// Wait for custom element definition if it's a custom element
|
||||||
|
if (element.localName.includes('-')) {
|
||||||
|
await customElements.whenDefined(element.localName).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Lit/async components to finish rendering
|
||||||
|
if ((element as any).updateComplete) {
|
||||||
|
await (element as any).updateComplete;
|
||||||
|
}
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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 [community.foss.global/](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/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
`@git.zone/tstest/tapbundle_protocol` implements Protocol V2, an enhanced version of the Test Anything Protocol (TAP) with support for structured metadata, real-time events, error diffs, and isomorphic operation. This protocol enables rich communication between test runners and test consumers while maintaining backward compatibility with standard TAP parsers.
|
`@git.zone/tstest/tapbundle_protocol` implements Protocol V2, an enhanced version of the Test Anything Protocol (TAP) with support for structured metadata, real-time events, error diffs, and isomorphic operation. This protocol enables rich communication between test runners and test consumers while maintaining backward compatibility with standard TAP parsers.
|
||||||
@@ -580,8 +584,21 @@ This module works in all JavaScript environments:
|
|||||||
|
|
||||||
No runtime-specific APIs are used, making it truly portable.
|
No runtime-specific APIs are used, making it truly portable.
|
||||||
|
|
||||||
## 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.
|
||||||
|
|||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user