@git.zone/tstest 3.1.3 → 3.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/tstest.classes.tap.parser.d.ts +9 -0
  3. package/dist_ts/tstest.classes.tap.parser.js +89 -32
  4. package/dist_ts/tstest.classes.tap.testresult.d.ts +6 -1
  5. package/dist_ts/tstest.classes.tap.testresult.js +10 -2
  6. package/dist_ts/tstest.logging.d.ts +2 -0
  7. package/dist_ts/tstest.logging.js +37 -2
  8. package/dist_ts_tapbundle/webhelpers.js +12 -2
  9. package/npmextra.json +11 -5
  10. package/package.json +12 -11
  11. package/readme.hints.md +46 -1
  12. package/ts/00_commitinfo_data.ts +1 -1
  13. package/ts/tstest.classes.tap.parser.ts +96 -33
  14. package/ts/tstest.classes.tap.testresult.ts +10 -1
  15. package/ts/tstest.logging.ts +39 -3
  16. package/dist_ts/tstest.classes.tap.parser.old.d.ts +0 -50
  17. package/dist_ts/tstest.classes.tap.parser.old.js +0 -332
  18. package/dist_ts_tapbundle/tapbundle.protocols.d.ts +0 -88
  19. package/dist_ts_tapbundle/tapbundle.protocols.js +0 -168
  20. package/dist_ts_tapbundle/ts_tapbundle/00_commitinfo_data.d.ts +0 -8
  21. package/dist_ts_tapbundle/ts_tapbundle/00_commitinfo_data.js +0 -9
  22. package/dist_ts_tapbundle/ts_tapbundle/index.d.ts +0 -6
  23. package/dist_ts_tapbundle/ts_tapbundle/index.js +0 -7
  24. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.pretask.d.ts +0 -10
  25. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.pretask.js +0 -13
  26. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.tap.d.ts +0 -104
  27. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.tap.js +0 -401
  28. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.taptest.d.ts +0 -38
  29. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.taptest.js +0 -110
  30. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.taptools.d.ts +0 -109
  31. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.taptools.js +0 -241
  32. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.tapwrap.d.ts +0 -8
  33. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.tapwrap.js +0 -7
  34. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.plugins.d.ts +0 -8
  35. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.plugins.js +0 -10
  36. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.tapcreator.d.ts +0 -3
  37. package/dist_ts_tapbundle/ts_tapbundle/tapbundle.tapcreator.js +0 -5
  38. package/dist_ts_tapbundle/ts_tapbundle/webhelpers.d.ts +0 -7
  39. package/dist_ts_tapbundle/ts_tapbundle/webhelpers.js +0 -35
  40. package/dist_ts_tapbundle/ts_tapbundle_node/classes.pathinject.d.ts +0 -5
  41. package/dist_ts_tapbundle/ts_tapbundle_node/classes.pathinject.js +0 -13
  42. package/dist_ts_tapbundle/ts_tapbundle_node/plugins.d.ts +0 -11
  43. package/dist_ts_tapbundle/ts_tapbundle_node/plugins.js +0 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@git.zone/tstest",
3
- "version": "3.1.3",
3
+ "version": "3.1.5",
4
4
  "private": false,
5
5
  "description": "a test utility to run tests that match test/**/*.ts",
6
6
  "exports": {
@@ -15,6 +15,15 @@
15
15
  "bin": {
16
16
  "tstest": "./cli.js"
17
17
  },
18
+ "scripts": {
19
+ "test": "pnpm run build && pnpm run test:tapbundle:verbose && pnpm run test:tstest:verbose",
20
+ "test:tapbundle": "tsx ./cli.child.ts \"test/tapbundle/**/*.ts\"",
21
+ "test:tapbundle:verbose": "tsx ./cli.child.ts \"test/tapbundle/**/*.ts\" --verbose",
22
+ "test:tstest": "tsx ./cli.child.ts \"test/tstest/**/*.ts\"",
23
+ "test:tstest:verbose": "tsx ./cli.child.ts \"test/tstest/**/*.ts\" --verbose",
24
+ "build": "(tsbuild tsfolders)",
25
+ "buildDocs": "tsdoc"
26
+ },
18
27
  "devDependencies": {
19
28
  "@git.zone/tsbuild": "^3.1.0",
20
29
  "@types/node": "^22.15.21"
@@ -61,13 +70,5 @@
61
70
  "browserslist": [
62
71
  "last 1 chrome versions"
63
72
  ],
64
- "scripts": {
65
- "test": "pnpm run build && pnpm run test:tapbundle:verbose && pnpm run test:tstest:verbose",
66
- "test:tapbundle": "tsx ./cli.child.ts \"test/tapbundle/**/*.ts\"",
67
- "test:tapbundle:verbose": "tsx ./cli.child.ts \"test/tapbundle/**/*.ts\" --verbose",
68
- "test:tstest": "tsx ./cli.child.ts \"test/tstest/**/*.ts\"",
69
- "test:tstest:verbose": "tsx ./cli.child.ts \"test/tstest/**/*.ts\" --verbose",
70
- "build": "(tsbuild tsfolders)",
71
- "buildDocs": "tsdoc"
72
- }
73
- }
73
+ "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
74
+ }
package/readme.hints.md CHANGED
@@ -445,4 +445,49 @@ The protocol parser was fixed to correctly handle inline timing metadata:
445
445
  - Changed condition from `!simpleMatch[1].includes(':')` to check for simple key:value pairs
446
446
  - Excludes prefixed formats (META:, SKIP:, TODO:, EVENT:) while parsing simple formats like `time:250`
447
447
 
448
- This ensures timing metadata is correctly extracted and displayed in test results.
448
+ This ensures timing metadata is correctly extracted and displayed in test results.
449
+
450
+ ## Streaming Console Output (Fixed)
451
+
452
+ ### Problem
453
+ When tests use `process.stdout.write()` for streaming output (without newlines), each write was appearing on a separate line. This happened because:
454
+ 1. Child process stdout data events arrive as separate chunks
455
+ 2. `TapParser._processLog()` split on `\n` and processed each segment
456
+ 3. `testConsoleOutput()` used `console.log()` which added a newline to each call
457
+
458
+ ### Solution
459
+ The streaming behavior is now preserved by:
460
+ 1. **Line buffering for TAP parsing**: Only buffer content that looks like TAP protocol messages
461
+ 2. **True streaming for console output**: Use `process.stdout.write()` instead of `console.log()` for partial lines
462
+ 3. **Intelligent detection**: `_looksLikeTapStart()` checks if content could be a TAP protocol message
463
+
464
+ ### Implementation Details
465
+
466
+ **TapParser changes:**
467
+ - Added `lineBuffer` property to buffer incomplete TAP protocol lines
468
+ - Rewrote `_processLog()` to handle streaming correctly:
469
+ - Complete lines (with newline) are processed through protocol parser
470
+ - Incomplete lines that look like TAP are buffered
471
+ - Incomplete lines that don't look like TAP are streamed immediately
472
+ - Added `_looksLikeTapStart()` helper to detect TAP protocol patterns
473
+ - Added `_handleConsoleOutput()` to handle console output with proper streaming
474
+ - Buffer is flushed on process exit
475
+
476
+ **TsTestLogger changes:**
477
+ - Added `testConsoleOutputStreaming()` method that uses `process.stdout.write()` in verbose mode
478
+ - Added `logToTestFileRaw()` for writing to log files without adding newlines
479
+ - In non-verbose mode, streaming content is appended to the last buffered entry
480
+
481
+ **TapTestResult changes:**
482
+ - Added `addLogLineRaw()` method that doesn't append newlines
483
+
484
+ ### Usage
485
+ Tests can now use streaming output naturally:
486
+ ```typescript
487
+ process.stdout.write("Loading");
488
+ process.stdout.write(".");
489
+ process.stdout.write(".");
490
+ process.stdout.write(".\n");
491
+ ```
492
+
493
+ This will correctly display as `Loading...` on a single line in verbose mode.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@git.zone/tstest',
6
- version: '3.1.3',
6
+ version: '3.1.5',
7
7
  description: 'a test utility to run tests that match test/**/*.ts'
8
8
  }
@@ -18,11 +18,12 @@ export class TapParser {
18
18
  receivedTests: number = 0;
19
19
 
20
20
  activeTapTestResult: TapTestResult;
21
-
21
+
22
22
  private logger: TsTestLogger;
23
23
  private protocolParser: ProtocolParser;
24
24
  private protocolVersion: string | null = null;
25
25
  private startTime: number;
26
+ private lineBuffer: string = '';
26
27
 
27
28
  /**
28
29
  * the constructor for TapParser
@@ -71,43 +72,100 @@ export class TapParser {
71
72
  if (Buffer.isBuffer(logChunk)) {
72
73
  logChunk = logChunk.toString();
73
74
  }
74
- const logLineArray = logChunk.split('\n');
75
- if (logLineArray[logLineArray.length - 1] === '') {
76
- logLineArray.pop();
77
- }
78
75
 
79
- // Process each line through the protocol parser
80
- for (const logLine of logLineArray) {
81
- const messages = this.protocolParser.parseLine(logLine);
82
-
83
- if (messages.length > 0) {
84
- // Handle protocol messages
85
- for (const message of messages) {
86
- this._handleProtocolMessage(message, logLine);
76
+ // Prepend any buffered content from previous incomplete line
77
+ const fullChunk = this.lineBuffer + logChunk;
78
+ this.lineBuffer = '';
79
+
80
+ // Split into segments by newline
81
+ const segments = fullChunk.split('\n');
82
+ const lastIndex = segments.length - 1;
83
+
84
+ for (let i = 0; i < segments.length; i++) {
85
+ const segment = segments[i];
86
+ const isLastSegment = (i === lastIndex);
87
+ const hasNewline = !isLastSegment; // All segments except last had a newline after them
88
+
89
+ if (hasNewline) {
90
+ // Complete line - check if it's a TAP protocol message
91
+ const messages = this.protocolParser.parseLine(segment);
92
+
93
+ if (messages.length > 0) {
94
+ // Handle protocol messages
95
+ for (const message of messages) {
96
+ this._handleProtocolMessage(message, segment);
97
+ }
98
+ } else {
99
+ // Non-protocol complete line - handle as console output
100
+ this._handleConsoleOutput(segment, true);
87
101
  }
88
- } else {
89
- // Not a protocol message, handle as console output
90
- if (this.activeTapTestResult) {
91
- this.activeTapTestResult.addLogLine(logLine);
102
+ } else if (segment) {
103
+ // Last segment without newline - could be:
104
+ // 1. Partial console output (stream immediately)
105
+ // 2. Start of a TAP message (need to buffer for protocol parsing)
106
+
107
+ // Check if it looks like the start of a TAP protocol message
108
+ if (this._looksLikeTapStart(segment)) {
109
+ // Buffer it for complete line parsing
110
+ this.lineBuffer = segment;
111
+ } else {
112
+ // Stream immediately as console output (no newline)
113
+ this._handleConsoleOutput(segment, false);
92
114
  }
93
-
94
- // Check for snapshot communication (legacy)
95
- const snapshotMatch = logLine.match(/###SNAPSHOT###(.+)###SNAPSHOT###/);
96
- if (snapshotMatch) {
97
- const base64Data = snapshotMatch[1];
98
- try {
99
- const snapshotData = JSON.parse(Buffer.from(base64Data, 'base64').toString());
100
- this.handleSnapshot(snapshotData);
101
- } catch (error: any) {
102
- if (this.logger) {
103
- this.logger.testConsoleOutput(`Error parsing snapshot data: ${error.message}`);
104
- }
105
- }
106
- } else if (this.logger) {
107
- // This is console output from the test file
108
- this.logger.testConsoleOutput(logLine);
115
+ }
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Check if text could be the start of a TAP protocol message
121
+ */
122
+ private _looksLikeTapStart(text: string): boolean {
123
+ return (
124
+ text.startsWith('ok ') ||
125
+ text.startsWith('not ok ') ||
126
+ text.startsWith('1..') ||
127
+ text.startsWith('# ') ||
128
+ text.startsWith('TAP version ') ||
129
+ text.startsWith('⟦TSTEST:') ||
130
+ text.startsWith('Bail out!')
131
+ );
132
+ }
133
+
134
+ /**
135
+ * Handle console output from test, preserving streaming behavior
136
+ */
137
+ private _handleConsoleOutput(text: string, hasNewline: boolean) {
138
+ // Check for snapshot communication (legacy)
139
+ const snapshotMatch = text.match(/###SNAPSHOT###(.+)###SNAPSHOT###/);
140
+ if (snapshotMatch) {
141
+ const base64Data = snapshotMatch[1];
142
+ try {
143
+ const snapshotData = JSON.parse(Buffer.from(base64Data, 'base64').toString());
144
+ this.handleSnapshot(snapshotData);
145
+ } catch (error: any) {
146
+ if (this.logger) {
147
+ this.logger.testConsoleOutput(`Error parsing snapshot data: ${error.message}`);
109
148
  }
110
149
  }
150
+ return;
151
+ }
152
+
153
+ // Add to test result buffer
154
+ if (this.activeTapTestResult) {
155
+ if (hasNewline) {
156
+ this.activeTapTestResult.addLogLine(text);
157
+ } else {
158
+ this.activeTapTestResult.addLogLineRaw(text);
159
+ }
160
+ }
161
+
162
+ // Output to logger with streaming support
163
+ if (this.logger) {
164
+ if (hasNewline) {
165
+ this.logger.testConsoleOutput(text);
166
+ } else {
167
+ this.logger.testConsoleOutputStreaming(text);
168
+ }
111
169
  }
112
170
  }
113
171
 
@@ -417,6 +475,11 @@ export class TapParser {
417
475
  this._processLog(data);
418
476
  });
419
477
  childProcessArg.on('exit', async () => {
478
+ // Flush any remaining buffered content
479
+ if (this.lineBuffer) {
480
+ this._handleConsoleOutput(this.lineBuffer, false);
481
+ this.lineBuffer = '';
482
+ }
420
483
  await this.evaluateFinalResult();
421
484
  done.resolve();
422
485
  });
@@ -10,7 +10,7 @@ export class TapTestResult {
10
10
  constructor(public id: number) {}
11
11
 
12
12
  /**
13
- * adds a logLine to the log buffer of the test
13
+ * adds a logLine to the log buffer of the test (with newline appended)
14
14
  * @param logLine
15
15
  */
16
16
  addLogLine(logLine: string) {
@@ -19,6 +19,15 @@ export class TapTestResult {
19
19
  this.testLogBuffer = Buffer.concat([this.testLogBuffer, logLineBuffer]);
20
20
  }
21
21
 
22
+ /**
23
+ * adds raw text to the log buffer without appending newline (for streaming output)
24
+ * @param text
25
+ */
26
+ addLogLineRaw(text: string) {
27
+ const logLineBuffer = Buffer.from(text);
28
+ this.testLogBuffer = Buffer.concat([this.testLogBuffer, logLineBuffer]);
29
+ }
30
+
22
31
  setTestResult(testOkArg: boolean) {
23
32
  this.testOk = testOkArg;
24
33
  this.testSettled = true;
@@ -348,10 +348,10 @@ export class TsTestLogger {
348
348
  }
349
349
  }
350
350
 
351
- // Console output from test files (non-TAP output)
351
+ // Console output from test files (non-TAP output) - complete lines
352
352
  testConsoleOutput(message: string) {
353
353
  if (this.options.json) return;
354
-
354
+
355
355
  // In verbose mode, show console output immediately
356
356
  if (this.options.verbose) {
357
357
  this.log(this.format(` ${message}`, 'dim'));
@@ -359,12 +359,48 @@ export class TsTestLogger {
359
359
  // In non-verbose mode, buffer the logs
360
360
  this.currentTestLogs.push(message);
361
361
  }
362
-
362
+
363
363
  // Always log to test file if --logfile is specified
364
364
  if (this.currentTestLogFile) {
365
365
  this.logToTestFile(` ${message}`);
366
366
  }
367
367
  }
368
+
369
+ // Streaming console output (preserves original formatting, no newline added)
370
+ testConsoleOutputStreaming(message: string) {
371
+ if (this.options.json) return;
372
+
373
+ const prefix = ' ';
374
+ if (this.options.verbose) {
375
+ // Use process.stdout.write to preserve streaming without adding newlines
376
+ process.stdout.write(this.format(prefix + message, 'dim'));
377
+ } else {
378
+ // Buffer without trailing newline - append to last entry if exists and incomplete
379
+ if (this.currentTestLogs.length > 0) {
380
+ // Append to the last buffered entry (for streaming segments)
381
+ this.currentTestLogs[this.currentTestLogs.length - 1] += message;
382
+ } else {
383
+ this.currentTestLogs.push(message);
384
+ }
385
+ }
386
+
387
+ // Log to test file without adding newline
388
+ if (this.currentTestLogFile) {
389
+ this.logToTestFileRaw(prefix + message);
390
+ }
391
+ }
392
+
393
+ private logToTestFileRaw(message: string) {
394
+ try {
395
+ // Remove ANSI color codes for file logging
396
+ const cleanMessage = message.replace(/\u001b\[[0-9;]*m/g, '');
397
+
398
+ // Append to test log file without adding newline
399
+ fs.appendFileSync(this.currentTestLogFile, cleanMessage);
400
+ } catch (error) {
401
+ // Silently fail to avoid disrupting the test run
402
+ }
403
+ }
368
404
 
369
405
  // Skipped test file
370
406
  testFileSkipped(filename: string, index: number, total: number, reason: string) {
@@ -1,50 +0,0 @@
1
- import { ChildProcess } from 'child_process';
2
- import { TapTestResult } from './tstest.classes.tap.testresult.js';
3
- import { TsTestLogger } from './tstest.logging.js';
4
- export declare class TapParser {
5
- fileName: string;
6
- testStore: TapTestResult[];
7
- expectedTestsRegex: RegExp;
8
- expectedTests: number;
9
- receivedTests: number;
10
- testStatusRegex: RegExp;
11
- activeTapTestResult: TapTestResult;
12
- collectingErrorDetails: boolean;
13
- currentTestError: string[];
14
- pretaskRegex: RegExp;
15
- private logger;
16
- private protocolParser;
17
- /**
18
- * the constructor for TapParser
19
- */
20
- constructor(fileName: string, logger?: TsTestLogger);
21
- /**
22
- * Handle test file timeout
23
- */
24
- handleTimeout(timeoutSeconds: number): void;
25
- private _getNewTapTestResult;
26
- private _processLog;
27
- /**
28
- * returns all tests that are not completed
29
- */
30
- getUncompletedTests(): void;
31
- /**
32
- * returns all tests that threw an error
33
- */
34
- getErrorTests(): TapTestResult[];
35
- /**
36
- * returns a test overview as string
37
- */
38
- getTestOverviewAsString(): string;
39
- /**
40
- * handles a tap process
41
- * @param childProcessArg
42
- */
43
- handleTapProcess(childProcessArg: ChildProcess): Promise<void>;
44
- handleTapLog(tapLog: string): Promise<void>;
45
- /**
46
- * Handle snapshot data from the test
47
- */
48
- private handleSnapshot;
49
- evaluateFinalResult(): Promise<void>;
50
- }