@wp-tester/results 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,14 @@ export declare class PHPUnitStreamingReporter extends StreamingReporter {
13
13
  /**
14
14
  * Generate PHPUnit filter command for re-running a specific test
15
15
  * PHPUnit uses --filter with ClassName::testName format
16
+ *
17
+ * The suite stack typically contains:
18
+ * - ['ActivityPub', 'Activitypub\\Tests\\Test_Class'] for regular tests
19
+ * - ['ActivityPub', 'Activitypub\\Tests\\Test_Class', 'test_method'] for data providers
20
+ *
21
+ * We need to find the class name (the suite with namespace separators '\\')
22
+ * and use it for the filter: 'Activitypub\\Tests\\Test_Class::testName'
16
23
  */
17
- protected getFilterCommand(testName: string, suiteName?: string): string | null;
24
+ protected getFilterCommand(testName: string, suiteName?: string, suiteStack?: string[]): string | null;
18
25
  }
19
26
  //# sourceMappingURL=phpunit-streaming-reporter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"phpunit-streaming-reporter.d.ts","sourceRoot":"","sources":["../src/phpunit-streaming-reporter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAElF;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,iBAAiB;gBACjD,OAAO,GAAE,wBAA6B;IAIlD;;;OAGG;IACH,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;CAMhF"}
1
+ {"version":3,"file":"phpunit-streaming-reporter.d.ts","sourceRoot":"","sources":["../src/phpunit-streaming-reporter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAElF;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,iBAAiB;gBACjD,OAAO,GAAE,wBAA6B;IAIlD;;;;;;;;;;OAUG;IACH,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI;CAgCvG"}
@@ -15,12 +15,42 @@ export class PHPUnitStreamingReporter extends StreamingReporter {
15
15
  /**
16
16
  * Generate PHPUnit filter command for re-running a specific test
17
17
  * PHPUnit uses --filter with ClassName::testName format
18
+ *
19
+ * The suite stack typically contains:
20
+ * - ['ActivityPub', 'Activitypub\\Tests\\Test_Class'] for regular tests
21
+ * - ['ActivityPub', 'Activitypub\\Tests\\Test_Class', 'test_method'] for data providers
22
+ *
23
+ * We need to find the class name (the suite with namespace separators '\\')
24
+ * and use it for the filter: 'Activitypub\\Tests\\Test_Class::testName'
18
25
  */
19
- getFilterCommand(testName, suiteName) {
26
+ getFilterCommand(testName, suiteName, suiteStack) {
27
+ // If we have a suite stack, find the class name (contains namespace separator)
28
+ if (suiteStack && suiteStack.length > 0) {
29
+ // Find the last suite that contains a namespace separator (backslash)
30
+ // This is typically the class name
31
+ let className = null;
32
+ for (let i = suiteStack.length - 1; i >= 0; i--) {
33
+ if (suiteStack[i].includes('\\')) {
34
+ className = suiteStack[i];
35
+ break;
36
+ }
37
+ }
38
+ // If we found a class name, use it
39
+ if (className) {
40
+ // Escape backslashes for shell (single \ becomes \\)
41
+ // This allows users to copy-paste the filter directly
42
+ const filterArg = `${className}::${testName}`.replace(/\\/g, '\\\\');
43
+ return `-- --filter '${filterArg}'`;
44
+ }
45
+ // Otherwise, fall back to using the first suite
46
+ const filterArg = `${suiteStack[0]}::${testName}`.replace(/\\/g, '\\\\');
47
+ return `-- --filter '${filterArg}'`;
48
+ }
49
+ // Fallback to old behavior if no suite stack is available
20
50
  const filterArg = suiteName
21
51
  ? `${suiteName}::${testName}`
22
52
  : testName;
23
- return `-- --filter '${filterArg}'`;
53
+ return `-- --filter '${filterArg.replace(/\\/g, '\\\\')}'`;
24
54
  }
25
55
  }
26
56
  //# sourceMappingURL=phpunit-streaming-reporter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"phpunit-streaming-reporter.js","sourceRoot":"","sources":["../src/phpunit-streaming-reporter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAiC,MAAM,gBAAgB,CAAC;AAElF;;GAEG;AACH,MAAM,OAAO,wBAAyB,SAAQ,iBAAiB;IAC7D,YAAY,UAAoC,EAAE;QAChD,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;IAED;;;OAGG;IACO,gBAAgB,CAAC,QAAgB,EAAE,SAAkB;QAC7D,MAAM,SAAS,GAAG,SAAS;YACzB,CAAC,CAAC,GAAG,SAAS,KAAK,QAAQ,EAAE;YAC7B,CAAC,CAAC,QAAQ,CAAC;QACb,OAAO,gBAAgB,SAAS,GAAG,CAAC;IACtC,CAAC;CACF"}
1
+ {"version":3,"file":"phpunit-streaming-reporter.js","sourceRoot":"","sources":["../src/phpunit-streaming-reporter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAiC,MAAM,gBAAgB,CAAC;AAElF;;GAEG;AACH,MAAM,OAAO,wBAAyB,SAAQ,iBAAiB;IAC7D,YAAY,UAAoC,EAAE;QAChD,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;IAED;;;;;;;;;;OAUG;IACO,gBAAgB,CAAC,QAAgB,EAAE,SAAkB,EAAE,UAAqB;QACpF,+EAA+E;QAC/E,IAAI,UAAU,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxC,sEAAsE;YACtE,mCAAmC;YACnC,IAAI,SAAS,GAAkB,IAAI,CAAC;YACpC,KAAK,IAAI,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAChD,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBACjC,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;oBAC1B,MAAM;gBACR,CAAC;YACH,CAAC;YAED,mCAAmC;YACnC,IAAI,SAAS,EAAE,CAAC;gBACd,qDAAqD;gBACrD,sDAAsD;gBACtD,MAAM,SAAS,GAAG,GAAG,SAAS,KAAK,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;gBACrE,OAAO,gBAAgB,SAAS,GAAG,CAAC;YACtC,CAAC;YAED,gDAAgD;YAChD,MAAM,SAAS,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YACzE,OAAO,gBAAgB,SAAS,GAAG,CAAC;QACtC,CAAC;QAED,0DAA0D;QAC1D,MAAM,SAAS,GAAG,SAAS;YACzB,CAAC,CAAC,GAAG,SAAS,KAAK,QAAQ,EAAE;YAC7B,CAAC,CAAC,QAAQ,CAAC;QACb,OAAO,gBAAgB,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC;IAC7D,CAAC;CACF"}
@@ -15,6 +15,7 @@ export interface TestEvent {
15
15
  type: "test:start" | "test:pass" | "test:fail" | "test:skip" | "test:pending";
16
16
  name: string;
17
17
  suiteName?: string;
18
+ suiteStack?: string[];
18
19
  duration?: number;
19
20
  message?: string;
20
21
  trace?: string;
@@ -57,18 +58,26 @@ export interface StreamWriter {
57
58
  */
58
59
  export declare const stdoutWriter: StreamWriter;
59
60
  /**
60
- * Filter options for controlling which test statuses are displayed
61
+ * Filter options for controlling which test statuses are shown.
62
+ *
63
+ * Filters affect:
64
+ * - Console display: Only matching tests are shown
65
+ * - Report tests array: Only matching tests are included in getReport().results.tests
66
+ * - Summary counts: Always show accurate totals for ALL tests (not filtered)
67
+ *
68
+ * This allows --failed-only to produce a JSON report with only failed tests in the
69
+ * array while still showing accurate total counts in the summary.
61
70
  */
62
71
  export interface ReporterFilterOptions {
63
- /** Show passed tests (default: false) */
72
+ /** Show/include passed tests (default: false) */
64
73
  passed?: boolean;
65
- /** Show failed tests (default: false) */
74
+ /** Show/include failed tests (default: false) */
66
75
  failed?: boolean;
67
- /** Show skipped tests (default: false) */
76
+ /** Show/include skipped tests (default: false) */
68
77
  skipped?: boolean;
69
- /** Show pending tests (default: false) */
78
+ /** Show/include pending tests (default: false) */
70
79
  pending?: boolean;
71
- /** Show other test statuses (default: false) */
80
+ /** Show/include other test statuses (default: false) */
72
81
  other?: boolean;
73
82
  }
74
83
  /**
@@ -79,7 +88,7 @@ export interface StreamingReporterOptions {
79
88
  showRunBoundaries?: boolean;
80
89
  showSummary?: boolean;
81
90
  enabled?: boolean;
82
- /** Filter options for which test statuses to display */
91
+ /** Filter options for which test statuses to display and include in reports */
83
92
  filter?: ReporterFilterOptions;
84
93
  }
85
94
  /**
@@ -98,16 +107,19 @@ export declare class StreamingReporter {
98
107
  private showRunBoundaries;
99
108
  private showSummary;
100
109
  private filter;
110
+ private useLogUpdate;
101
111
  private state;
102
- private lastOutputLineCount;
103
112
  private spinnerFrame;
104
113
  private spinnerInterval;
114
+ private renderThrottleTimeout;
115
+ private pendingRender;
116
+ private readonly renderInterval;
105
117
  constructor(options?: StreamingReporterOptions);
106
118
  /**
107
119
  * Generate filter command for re-running a specific test
108
120
  * Override in subclasses for framework-specific behavior
109
121
  */
110
- protected getFilterCommand(_testName: string, _suiteName?: string): string | null;
122
+ protected getFilterCommand(_testName: string, _suiteName?: string, _suiteStack?: string[]): string | null;
111
123
  /**
112
124
  * Enable or disable run boundaries (header and summary)
113
125
  */
@@ -125,13 +137,21 @@ export declare class StreamingReporter {
125
137
  */
126
138
  private stopSpinner;
127
139
  /**
128
- * Clear the previous output
140
+ * Clear throttle timeout and execute any pending render
129
141
  */
130
- private clearPreviousOutput;
142
+ private clearThrottle;
131
143
  /**
132
- * Render the current state to output
144
+ * Render the current state to output (throttled)
133
145
  */
134
146
  private render;
147
+ /**
148
+ * Execute render immediately without throttling
149
+ */
150
+ private renderImmediate;
151
+ /**
152
+ * Render final output (used in non-interactive mode)
153
+ */
154
+ private renderFinal;
135
155
  /**
136
156
  * Check if a suite or any of its descendants has visible content
137
157
  */
@@ -191,29 +211,34 @@ export declare class StreamingReporter {
191
211
  /**
192
212
  * Called when a test starts
193
213
  */
194
- onTestStart(name: string, suiteName?: string, fileId?: string): void;
214
+ onTestStart(name: string, suiteName?: string, suiteStack?: string[], fileId?: string): void;
195
215
  /**
196
216
  * Called when a test passes
197
217
  */
198
- onTestPass(name: string, duration: number, suiteName?: string, fileId?: string): void;
218
+ onTestPass(name: string, duration: number, suiteName?: string, suiteStack?: string[], fileId?: string): void;
199
219
  /**
200
220
  * Called when a test fails
201
221
  */
202
- onTestFail(name: string, duration: number, message?: string, trace?: string, suiteName?: string, fileId?: string): void;
222
+ onTestFail(name: string, duration: number, message?: string, trace?: string, suiteName?: string, suiteStack?: string[], fileId?: string): void;
203
223
  /**
204
224
  * Called when a test is skipped
205
225
  */
206
- onTestSkip(name: string, reason?: string, suiteName?: string, fileId?: string): void;
226
+ onTestSkip(name: string, reason?: string, suiteName?: string, suiteStack?: string[], fileId?: string): void;
207
227
  /**
208
228
  * Called when a test is pending
209
229
  */
210
- onTestPending(name: string, suiteName?: string, fileId?: string): void;
230
+ onTestPending(name: string, suiteName?: string, suiteStack?: string[], fileId?: string): void;
211
231
  /**
212
232
  * Print final summary
213
233
  */
214
234
  private printSummary;
215
235
  /**
216
236
  * Get the current report in CTRF format
237
+ *
238
+ * The tests array is filtered based on the reporter's filter options,
239
+ * but the summary counts reflect ALL tests for accurate totals.
240
+ * This allows the JSON report to contain only relevant tests while
241
+ * showing complete statistics.
217
242
  */
218
243
  getReport(): Report;
219
244
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,EAAE,MAAM,MAAM,CAAC;AAMrD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,YAAY,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,aAAa,GAAG,WAAW,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,YAAY,GAAG,UAAU,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,WAAW,GAAG,SAAS,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAExE;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,YAO1B,CAAC;AAYF;;;;;;;;;;GAUG;AACH,MAAM,WAAW,qBAAqB;IACpC,iDAAiD;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iDAAiD;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,wDAAwD;IACxD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AAoDD;;;;;;;;;GASG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,iBAAiB,CAAQ;IACjC,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,YAAY,CAAU;IAG9B,OAAO,CAAC,KAAK,CAAgB;IAG7B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,eAAe,CAA+B;IAGtD,OAAO,CAAC,qBAAqB,CAA+B;IAC5D,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAM;gBAEzB,OAAO,GAAE,wBAA6B;IAkClD;;;OAGG;IACH,SAAS,CAAC,gBAAgB,CACxB,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,EAAE,GACrB,MAAM,GAAG,IAAI;IAIhB;;OAEG;IACH,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAIzC;;OAEG;IACH,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IA0DjC;;OAEG;IACH,OAAO,CAAC,YAAY;IASpB;;OAEG;IACH,OAAO,CAAC,WAAW;IAOnB;;OAEG;IACH,OAAO,CAAC,aAAa;IAWrB;;OAEG;IACH,OAAO,CAAC,MAAM;IAyBd;;OAEG;IACH,OAAO,CAAC,eAAe;IA4CvB;;OAEG;IACH,OAAO,CAAC,WAAW;IAcnB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAqCzB;;OAEG;IACH,OAAO,CAAC,UAAU;IAMlB;;OAEG;IACH,OAAO,CAAC,WAAW;IAsCnB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAqBxB;;OAEG;IACH,OAAO,CAAC,UAAU;IA0HlB;;OAEG;IACH,OAAO,CAAC,eAAe;IAcvB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA2CxB;;OAEG;IACH,OAAO,CAAC,cAAc;IAStB;;OAEG;IACH,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IA4BnC;;OAEG;IACH,QAAQ,IAAI,IAAI;IAuChB;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAIpD;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKhC;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAkBjD;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IA+B/C;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IA8B3F;;OAEG;IACH,UAAU,CACR,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI;IA8CP;;OAEG;IACH,UAAU,CACR,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI;IAkDP;;OAEG;IACH,UAAU,CACR,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI;IA8CP;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IA6B7F;;OAEG;IACH,OAAO,CAAC,YAAY;IAiCpB;;;;;;;OAOG;IACH,SAAS,IAAI,MAAM;IA8DnB;;OAEG;IACH,SAAS,IAAI;QACX,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;KACjB;CASF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAmB1D"}
package/dist/streaming.js CHANGED
@@ -10,6 +10,7 @@
10
10
  import pc from "picocolors";
11
11
  import { applyDiffHighlighting } from "./diff-utils.js";
12
12
  import { SPINNER_FRAMES } from "./spinner.js";
13
+ import logUpdate from "log-update";
13
14
  /**
14
15
  * Default stdout writer
15
16
  */
@@ -46,13 +47,21 @@ export class StreamingReporter {
46
47
  this.showRunBoundaries = true;
47
48
  this.showSummary = true;
48
49
  // Rendering state
49
- this.lastOutputLineCount = 0;
50
50
  this.spinnerFrame = 0;
51
51
  this.spinnerInterval = null;
52
+ // Throttling state
53
+ this.renderThrottleTimeout = null;
54
+ this.pendingRender = false;
55
+ this.renderInterval = 50; // Render at most every 50ms (~20fps)
52
56
  this.writer = options.writer ?? stdoutWriter;
53
57
  this.showRunBoundaries = options.showRunBoundaries ?? true;
54
58
  this.showSummary = options.showSummary ?? true;
55
59
  this.enabled = options.enabled ?? true;
60
+ // Use log-update only if:
61
+ // - Reporter is enabled
62
+ // - We're using the default stdout writer (not a custom writer)
63
+ // - stdout is a TTY (log-update handles this check internally)
64
+ this.useLogUpdate = this.enabled && this.writer === stdoutWriter && (process.stdout.isTTY ?? false);
56
65
  // Default filter: show all statuses (for backwards compatibility)
57
66
  this.filter = options.filter ?? {
58
67
  passed: true,
@@ -77,7 +86,7 @@ export class StreamingReporter {
77
86
  * Generate filter command for re-running a specific test
78
87
  * Override in subclasses for framework-specific behavior
79
88
  */
80
- getFilterCommand(_testName, _suiteName) {
89
+ getFilterCommand(_testName, _suiteName, _suiteStack) {
81
90
  return null;
82
91
  }
83
92
  /**
@@ -110,19 +119,19 @@ export class StreamingReporter {
110
119
  this.onSuiteEnd(event.name, event.fileId);
111
120
  break;
112
121
  case "test:start":
113
- this.onTestStart(event.name, event.suiteName, event.fileId);
122
+ this.onTestStart(event.name, event.suiteName, event.suiteStack, event.fileId);
114
123
  break;
115
124
  case "test:pass":
116
- this.onTestPass(event.name, event.duration || 0, event.suiteName, event.fileId);
125
+ this.onTestPass(event.name, event.duration || 0, event.suiteName, event.suiteStack, event.fileId);
117
126
  break;
118
127
  case "test:fail":
119
- this.onTestFail(event.name, event.duration || 0, event.message, event.trace, event.suiteName, event.fileId);
128
+ this.onTestFail(event.name, event.duration || 0, event.message, event.trace, event.suiteName, event.suiteStack, event.fileId);
120
129
  break;
121
130
  case "test:skip":
122
- this.onTestSkip(event.name, event.message, event.suiteName, event.fileId);
131
+ this.onTestSkip(event.name, event.message, event.suiteName, event.suiteStack, event.fileId);
123
132
  break;
124
133
  case "test:pending":
125
- this.onTestPending(event.name, event.suiteName, event.fileId);
134
+ this.onTestPending(event.name, event.suiteName, event.suiteStack, event.fileId);
126
135
  break;
127
136
  }
128
137
  }
@@ -147,22 +156,46 @@ export class StreamingReporter {
147
156
  }
148
157
  }
149
158
  /**
150
- * Clear the previous output
159
+ * Clear throttle timeout and execute any pending render
151
160
  */
152
- clearPreviousOutput() {
153
- if (!this.enabled || this.lastOutputLineCount === 0)
154
- return;
155
- // Move cursor up and clear each line
156
- for (let i = 0; i < this.lastOutputLineCount; i++) {
157
- this.writer.write("\x1b[1A\x1b[2K");
161
+ clearThrottle() {
162
+ if (this.renderThrottleTimeout) {
163
+ clearTimeout(this.renderThrottleTimeout);
164
+ this.renderThrottleTimeout = null;
165
+ }
166
+ // Execute any pending render
167
+ if (this.pendingRender) {
168
+ this.renderImmediate();
158
169
  }
159
170
  }
160
171
  /**
161
- * Render the current state to output
172
+ * Render the current state to output (throttled)
162
173
  */
163
174
  render() {
164
175
  if (!this.enabled)
165
176
  return;
177
+ // Mark that a render is pending
178
+ this.pendingRender = true;
179
+ // If we're already waiting for a throttled render, just update the flag
180
+ if (this.renderThrottleTimeout) {
181
+ return;
182
+ }
183
+ // Execute render immediately and schedule the next allowed render time
184
+ this.renderThrottleTimeout = setTimeout(() => {
185
+ this.renderThrottleTimeout = null;
186
+ // If another render was requested while throttled, execute it now
187
+ if (this.pendingRender) {
188
+ this.renderImmediate();
189
+ }
190
+ }, this.renderInterval);
191
+ // Execute the first render immediately
192
+ this.renderImmediate();
193
+ }
194
+ /**
195
+ * Execute render immediately without throttling
196
+ */
197
+ renderImmediate() {
198
+ this.pendingRender = false;
166
199
  // Build output lines
167
200
  const lines = [];
168
201
  // Check if there are any running tests or loading suites
@@ -182,12 +215,16 @@ export class StreamingReporter {
182
215
  for (const file of this.state.files.values()) {
183
216
  this.renderFile(file, lines);
184
217
  }
185
- // Clear previous output and render new output
186
- this.clearPreviousOutput();
187
- for (const line of lines) {
188
- this.writer.writeLine(line);
218
+ // Render output based on environment
219
+ if (this.useLogUpdate) {
220
+ // TTY mode: use log-update for live updates with clearing
221
+ logUpdate(lines.join('\n'));
222
+ }
223
+ else {
224
+ // Non-TTY mode or custom writer: render once only when tests complete
225
+ // Skip intermediate renders to avoid duplicate output
226
+ // Tests and non-interactive environments see final state only
189
227
  }
190
- this.lastOutputLineCount = lines.length;
191
228
  // Start/stop spinner based on running tests
192
229
  if (hasRunningTests && !this.spinnerInterval) {
193
230
  this.startSpinner();
@@ -196,6 +233,20 @@ export class StreamingReporter {
196
233
  this.stopSpinner();
197
234
  }
198
235
  }
236
+ /**
237
+ * Render final output (used in non-interactive mode)
238
+ */
239
+ renderFinal() {
240
+ const lines = [];
241
+ // Render each file
242
+ for (const file of this.state.files.values()) {
243
+ this.renderFile(file, lines);
244
+ }
245
+ // Write final output
246
+ for (const line of lines) {
247
+ this.writer.writeLine(line);
248
+ }
249
+ }
199
250
  /**
200
251
  * Check if a suite or any of its descendants has visible content
201
252
  */
@@ -206,7 +257,7 @@ export class StreamingReporter {
206
257
  return true;
207
258
  }
208
259
  // Check if suite has any visible tests
209
- const hasVisibleTests = suite.tests.some(test => this.shouldShowStatus(test.status));
260
+ const hasVisibleTests = suite.tests.some((test) => this.shouldShowStatus(test.status));
210
261
  if (hasVisibleTests) {
211
262
  return true;
212
263
  }
@@ -368,7 +419,7 @@ export class StreamingReporter {
368
419
  }
369
420
  // Add filter to re-run this specific test
370
421
  if (test.name) {
371
- const filterCmd = this.getFilterCommand(test.name, test.suiteName);
422
+ const filterCmd = this.getFilterCommand(test.name, test.suiteName, test.suiteStack);
372
423
  if (filterCmd) {
373
424
  lines.push(`${traceIndent}${pc.dim("Re-run only this test by appending:")}`);
374
425
  lines.push(`${traceIndent}${pc.dim(filterCmd)}`);
@@ -411,14 +462,36 @@ export class StreamingReporter {
411
462
  * Find or create a suite in a file
412
463
  */
413
464
  getOrCreateSuite(file, suiteName) {
414
- // Find existing suite
415
- let suite = file.suites.find((s) => s.name === suiteName);
465
+ // Find existing suite with matching name, depth, AND parent context
466
+ // This prevents suites with the same name from being confused when they're
467
+ // in different branches of the suite tree (e.g., Test_Accept::test_method
468
+ // vs Test_Reject::test_method both creating a "test_method" sub-suite)
469
+ const currentDepth = file.currentSuiteStack.length;
470
+ // Create a snapshot of the current parent stack (excluding the current suite name)
471
+ const parentStack = [...file.currentSuiteStack];
472
+ // Find a suite with matching name, depth, and parent stack
473
+ let suite = file.suites.find((s) => {
474
+ if (s.name !== suiteName || s.depth !== currentDepth) {
475
+ return false;
476
+ }
477
+ // Check if parent stacks match
478
+ if (s.parentStack.length !== parentStack.length) {
479
+ return false;
480
+ }
481
+ for (let i = 0; i < parentStack.length; i++) {
482
+ if (s.parentStack[i] !== parentStack[i]) {
483
+ return false;
484
+ }
485
+ }
486
+ return true;
487
+ });
416
488
  if (!suite) {
417
489
  suite = {
418
490
  name: suiteName,
419
- depth: file.currentSuiteStack.length,
491
+ depth: currentDepth,
420
492
  tests: [],
421
493
  isLoading: true, // Start in loading state
494
+ parentStack: parentStack,
422
495
  };
423
496
  file.suites.push(suite);
424
497
  }
@@ -452,13 +525,23 @@ export class StreamingReporter {
452
525
  pendingTests: 0,
453
526
  isRunning: true,
454
527
  };
455
- this.lastOutputLineCount = 0;
528
+ // Clear any previous log-update state
529
+ if (this.useLogUpdate) {
530
+ logUpdate.clear();
531
+ }
532
+ // Clear any previous throttle state
533
+ if (this.renderThrottleTimeout) {
534
+ clearTimeout(this.renderThrottleTimeout);
535
+ this.renderThrottleTimeout = null;
536
+ }
537
+ this.pendingRender = false;
456
538
  }
457
539
  /**
458
540
  * Called when test run ends
459
541
  */
460
542
  onRunEnd() {
461
543
  this.stopSpinner();
544
+ this.clearThrottle(); // Clear any pending throttled renders
462
545
  this.state.isRunning = false;
463
546
  // Clean up any tests still in "running" state across all files
464
547
  // This ensures we never show spinners in the final output
@@ -476,7 +559,14 @@ export class StreamingReporter {
476
559
  }
477
560
  }
478
561
  // Final render
479
- this.render();
562
+ if (this.useLogUpdate) {
563
+ // Clear the live updating display
564
+ logUpdate.clear();
565
+ logUpdate.done();
566
+ }
567
+ // Always use renderFinal for the final output to avoid truncation
568
+ // log-update is only for live updates, not for final large output
569
+ this.renderFinal();
480
570
  if (!this.enabled || !this.showSummary)
481
571
  return;
482
572
  const duration = Date.now() - this.state.startTime;
@@ -548,7 +638,7 @@ export class StreamingReporter {
548
638
  /**
549
639
  * Called when a test starts
550
640
  */
551
- onTestStart(name, suiteName, fileId) {
641
+ onTestStart(name, suiteName, suiteStack, fileId) {
552
642
  const file = fileId
553
643
  ? this.getOrCreateFile(fileId)
554
644
  : this.getOrCreateFile("__global__");
@@ -565,6 +655,7 @@ export class StreamingReporter {
565
655
  suite.tests.push({
566
656
  name,
567
657
  suiteName,
658
+ suiteStack,
568
659
  status: "running",
569
660
  });
570
661
  }
@@ -574,7 +665,7 @@ export class StreamingReporter {
574
665
  /**
575
666
  * Called when a test passes
576
667
  */
577
- onTestPass(name, duration, suiteName, fileId) {
668
+ onTestPass(name, duration, suiteName, suiteStack, fileId) {
578
669
  const file = fileId
579
670
  ? this.getOrCreateFile(fileId)
580
671
  : this.getOrCreateFile("__global__");
@@ -595,12 +686,14 @@ export class StreamingReporter {
595
686
  test.status = "passed";
596
687
  test.duration = duration;
597
688
  test.suiteName = suiteName; // Ensure suiteName is set
689
+ test.suiteStack = suiteStack;
598
690
  }
599
691
  else {
600
692
  // Test start event hasn't arrived yet - create the test with completed state
601
693
  suite.tests.push({
602
694
  name,
603
695
  suiteName,
696
+ suiteStack,
604
697
  status: "passed",
605
698
  duration,
606
699
  });
@@ -612,7 +705,7 @@ export class StreamingReporter {
612
705
  /**
613
706
  * Called when a test fails
614
707
  */
615
- onTestFail(name, duration, message, trace, suiteName, fileId) {
708
+ onTestFail(name, duration, message, trace, suiteName, suiteStack, fileId) {
616
709
  const file = fileId
617
710
  ? this.getOrCreateFile(fileId)
618
711
  : this.getOrCreateFile("__global__");
@@ -635,12 +728,14 @@ export class StreamingReporter {
635
728
  test.message = message;
636
729
  test.trace = trace;
637
730
  test.suiteName = suiteName; // Ensure suiteName is set
731
+ test.suiteStack = suiteStack;
638
732
  }
639
733
  else {
640
734
  // Test start event hasn't arrived yet - create the test with completed state
641
735
  suite.tests.push({
642
736
  name,
643
737
  suiteName,
738
+ suiteStack,
644
739
  status: "failed",
645
740
  duration,
646
741
  message,
@@ -654,7 +749,7 @@ export class StreamingReporter {
654
749
  /**
655
750
  * Called when a test is skipped
656
751
  */
657
- onTestSkip(name, reason, suiteName, fileId) {
752
+ onTestSkip(name, reason, suiteName, suiteStack, fileId) {
658
753
  const file = fileId
659
754
  ? this.getOrCreateFile(fileId)
660
755
  : this.getOrCreateFile("__global__");
@@ -675,12 +770,14 @@ export class StreamingReporter {
675
770
  test.status = "skipped";
676
771
  test.message = reason;
677
772
  test.suiteName = suiteName; // Ensure suiteName is set
773
+ test.suiteStack = suiteStack;
678
774
  }
679
775
  else {
680
776
  // Test start event hasn't arrived yet - create the test with completed state
681
777
  suite.tests.push({
682
778
  name,
683
779
  suiteName,
780
+ suiteStack,
684
781
  status: "skipped",
685
782
  message: reason,
686
783
  });
@@ -692,7 +789,7 @@ export class StreamingReporter {
692
789
  /**
693
790
  * Called when a test is pending
694
791
  */
695
- onTestPending(name, suiteName, fileId) {
792
+ onTestPending(name, suiteName, suiteStack, fileId) {
696
793
  const file = fileId
697
794
  ? this.getOrCreateFile(fileId)
698
795
  : this.getOrCreateFile("__global__");
@@ -703,11 +800,13 @@ export class StreamingReporter {
703
800
  const test = suite.tests.find((t) => t.name === name && t.suiteName === suiteName);
704
801
  if (test) {
705
802
  test.status = "pending";
803
+ test.suiteStack = suiteStack;
706
804
  }
707
805
  else {
708
806
  suite.tests.push({
709
807
  name,
710
808
  suiteName,
809
+ suiteStack,
711
810
  status: "pending",
712
811
  });
713
812
  }
@@ -740,18 +839,28 @@ export class StreamingReporter {
740
839
  }
741
840
  /**
742
841
  * Get the current report in CTRF format
842
+ *
843
+ * The tests array is filtered based on the reporter's filter options,
844
+ * but the summary counts reflect ALL tests for accurate totals.
845
+ * This allows the JSON report to contain only relevant tests while
846
+ * showing complete statistics.
743
847
  */
744
848
  getReport() {
745
849
  const tests = [];
746
- // Collect all tests from all files
850
+ // Collect tests from all files, applying filter to tests array
747
851
  for (const file of this.state.files.values()) {
748
852
  for (const suite of file.suites) {
749
853
  for (const test of suite.tests) {
854
+ const status = test.status === "running" ? "other" : test.status;
855
+ // Apply filter - only include tests that match filter criteria
856
+ if (!this.shouldShowStatus(test.status)) {
857
+ continue;
858
+ }
750
859
  const ctrf = {
751
860
  name: test.suiteName
752
861
  ? `${test.suiteName}::${test.name}`
753
862
  : test.name,
754
- status: test.status === "running" ? "other" : test.status,
863
+ status,
755
864
  duration: test.duration || 0,
756
865
  };
757
866
  if (test.message) {
@@ -764,6 +873,18 @@ export class StreamingReporter {
764
873
  }
765
874
  }
766
875
  }
876
+ // Use actual state counts for accurate totals (includes all tests, even filtered ones)
877
+ // The tests array is filtered, but the summary reflects the true test counts
878
+ const summary = {
879
+ tests: this.state.totalTests,
880
+ passed: this.state.passedTests,
881
+ failed: this.state.failedTests,
882
+ skipped: this.state.skippedTests,
883
+ pending: this.state.pendingTests,
884
+ other: 0, // We don't track "other" status in our state
885
+ start: this.state.startTime,
886
+ stop: Date.now(),
887
+ };
767
888
  return {
768
889
  reportFormat: "CTRF",
769
890
  specVersion: "1.0.0",
@@ -771,16 +892,7 @@ export class StreamingReporter {
771
892
  tool: {
772
893
  name: this.state.toolName,
773
894
  },
774
- summary: {
775
- tests: this.state.totalTests,
776
- passed: this.state.passedTests,
777
- failed: this.state.failedTests,
778
- skipped: this.state.skippedTests,
779
- pending: this.state.pendingTests,
780
- other: 0,
781
- start: this.state.startTime,
782
- stop: Date.now(),
783
- },
895
+ summary,
784
896
  tests,
785
897
  },
786
898
  };