@git.zone/tstest 1.1.0 → 1.3.0

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.
@@ -0,0 +1,288 @@
1
+ import { coloredString as cs } from '@push.rocks/consolecolor';
2
+ import * as plugins from './tstest.plugins.js';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ export class TsTestLogger {
6
+ constructor(options = {}) {
7
+ this.fileResults = [];
8
+ this.currentFileResult = null;
9
+ this.currentTestLogFile = null;
10
+ this.options = options;
11
+ this.startTime = Date.now();
12
+ }
13
+ format(text, color) {
14
+ if (this.options.noColor || !color) {
15
+ return text;
16
+ }
17
+ return cs(text, color);
18
+ }
19
+ log(message) {
20
+ if (this.options.json) {
21
+ // For JSON mode, skip console output
22
+ // JSON output is handled by logJson method
23
+ return;
24
+ }
25
+ console.log(message);
26
+ // Log to the current test file log if we're in a test and --logfile is specified
27
+ if (this.currentTestLogFile) {
28
+ this.logToTestFile(message);
29
+ }
30
+ }
31
+ logToFile(message) {
32
+ // This method is no longer used since we use logToTestFile for individual test logs
33
+ // Keeping it for potential future use with a global log file
34
+ }
35
+ logToTestFile(message) {
36
+ try {
37
+ // Remove ANSI color codes for file logging
38
+ const cleanMessage = message.replace(/\u001b\[[0-9;]*m/g, '');
39
+ // Append to test log file
40
+ fs.appendFileSync(this.currentTestLogFile, cleanMessage + '\n');
41
+ }
42
+ catch (error) {
43
+ // Silently fail to avoid disrupting the test run
44
+ }
45
+ }
46
+ logJson(data) {
47
+ const jsonString = JSON.stringify(data);
48
+ console.log(jsonString);
49
+ // Also log to test file if --logfile is specified
50
+ if (this.currentTestLogFile) {
51
+ this.logToTestFile(jsonString);
52
+ }
53
+ }
54
+ // Section separators
55
+ sectionStart(title) {
56
+ if (this.options.quiet || this.options.json)
57
+ return;
58
+ this.log(this.format(`\n━━━ ${title} ━━━`, 'cyan'));
59
+ }
60
+ sectionEnd() {
61
+ if (this.options.quiet || this.options.json)
62
+ return;
63
+ this.log(this.format('─'.repeat(50), 'dim'));
64
+ }
65
+ // Progress indication
66
+ progress(current, total, message) {
67
+ if (this.options.quiet || this.options.json)
68
+ return;
69
+ const percentage = Math.round((current / total) * 100);
70
+ const filled = Math.round((current / total) * 20);
71
+ const empty = 20 - filled;
72
+ this.log(this.format(`\n📊 Progress: ${current}/${total} (${percentage}%)`, 'cyan'));
73
+ this.log(this.format(`[${'█'.repeat(filled)}${'░'.repeat(empty)}] ${message}`, 'dim'));
74
+ }
75
+ // Test discovery
76
+ testDiscovery(count, pattern, executionMode) {
77
+ if (this.options.json) {
78
+ this.logJson({ event: 'discovery', count, pattern, executionMode });
79
+ return;
80
+ }
81
+ if (this.options.quiet) {
82
+ this.log(`Found ${count} tests`);
83
+ }
84
+ else {
85
+ this.log(this.format(`\n🔍 Test Discovery`, 'bold'));
86
+ this.log(this.format(` Mode: ${executionMode}`, 'dim'));
87
+ this.log(this.format(` Pattern: ${pattern}`, 'dim'));
88
+ this.log(this.format(` Found: ${count} test file(s)`, 'green'));
89
+ }
90
+ }
91
+ // Test execution
92
+ testFileStart(filename, runtime, index, total) {
93
+ this.currentFileResult = {
94
+ file: filename,
95
+ passed: 0,
96
+ failed: 0,
97
+ total: 0,
98
+ duration: 0,
99
+ tests: []
100
+ };
101
+ // Only set up test log file if --logfile option is specified
102
+ if (this.options.logFile) {
103
+ const baseFilename = path.basename(filename, '.ts');
104
+ this.currentTestLogFile = path.join('.nogit', 'testlogs', `${baseFilename}.log`);
105
+ // Ensure the directory exists
106
+ const logDir = path.dirname(this.currentTestLogFile);
107
+ if (!fs.existsSync(logDir)) {
108
+ fs.mkdirSync(logDir, { recursive: true });
109
+ }
110
+ // Clear the log file for this test
111
+ fs.writeFileSync(this.currentTestLogFile, '');
112
+ }
113
+ if (this.options.json) {
114
+ this.logJson({ event: 'fileStart', filename, runtime, index, total });
115
+ return;
116
+ }
117
+ if (this.options.quiet)
118
+ return;
119
+ this.log(this.format(`\n▶️ ${filename} (${index}/${total})`, 'blue'));
120
+ this.log(this.format(` Runtime: ${runtime}`, 'dim'));
121
+ }
122
+ testResult(testName, passed, duration, error) {
123
+ if (this.currentFileResult) {
124
+ this.currentFileResult.tests.push({ name: testName, passed, duration, error });
125
+ this.currentFileResult.total++;
126
+ if (passed) {
127
+ this.currentFileResult.passed++;
128
+ }
129
+ else {
130
+ this.currentFileResult.failed++;
131
+ }
132
+ this.currentFileResult.duration += duration;
133
+ }
134
+ if (this.options.json) {
135
+ this.logJson({ event: 'testResult', testName, passed, duration, error });
136
+ return;
137
+ }
138
+ const icon = passed ? '✅' : '❌';
139
+ const color = passed ? 'green' : 'red';
140
+ if (this.options.quiet) {
141
+ this.log(`${icon} ${testName}`);
142
+ }
143
+ else {
144
+ this.log(this.format(` ${icon} ${testName} (${duration}ms)`, color));
145
+ if (error && !passed) {
146
+ this.log(this.format(` ${error}`, 'red'));
147
+ }
148
+ }
149
+ }
150
+ testFileEnd(passed, failed, duration) {
151
+ if (this.currentFileResult) {
152
+ this.fileResults.push(this.currentFileResult);
153
+ this.currentFileResult = null;
154
+ }
155
+ if (this.options.json) {
156
+ this.logJson({ event: 'fileEnd', passed, failed, duration });
157
+ return;
158
+ }
159
+ if (!this.options.quiet) {
160
+ const total = passed + failed;
161
+ const status = failed === 0 ? 'PASSED' : 'FAILED';
162
+ const color = failed === 0 ? 'green' : 'red';
163
+ this.log(this.format(` Summary: ${passed}/${total} ${status}`, color));
164
+ }
165
+ // Clear the current test log file reference only if using --logfile
166
+ if (this.options.logFile) {
167
+ this.currentTestLogFile = null;
168
+ }
169
+ }
170
+ // TAP output forwarding (for TAP protocol messages)
171
+ tapOutput(message, isError = false) {
172
+ if (this.options.json)
173
+ return;
174
+ // Never show raw TAP protocol messages in console
175
+ // They are already processed by TapParser and shown in our format
176
+ // Always log to test file if --logfile is specified
177
+ if (this.currentTestLogFile) {
178
+ this.logToTestFile(` ${message}`);
179
+ }
180
+ }
181
+ // Console output from test files (non-TAP output)
182
+ testConsoleOutput(message) {
183
+ if (this.options.json)
184
+ return;
185
+ // Show console output from test files only in verbose mode
186
+ if (this.options.verbose) {
187
+ this.log(this.format(` ${message}`, 'dim'));
188
+ }
189
+ // Always log to test file if --logfile is specified
190
+ if (this.currentTestLogFile) {
191
+ this.logToTestFile(` ${message}`);
192
+ }
193
+ }
194
+ // Browser console
195
+ browserConsole(message, level = 'log') {
196
+ if (this.options.json) {
197
+ this.logJson({ event: 'browserConsole', message, level });
198
+ return;
199
+ }
200
+ if (!this.options.quiet) {
201
+ const prefix = level === 'error' ? '🌐❌' : '🌐';
202
+ const color = level === 'error' ? 'red' : 'magenta';
203
+ this.log(this.format(` ${prefix} ${message}`, color));
204
+ }
205
+ }
206
+ // Final summary
207
+ summary() {
208
+ const totalDuration = Date.now() - this.startTime;
209
+ const summary = {
210
+ totalFiles: this.fileResults.length,
211
+ totalTests: this.fileResults.reduce((sum, r) => sum + r.total, 0),
212
+ totalPassed: this.fileResults.reduce((sum, r) => sum + r.passed, 0),
213
+ totalFailed: this.fileResults.reduce((sum, r) => sum + r.failed, 0),
214
+ totalDuration,
215
+ fileResults: this.fileResults
216
+ };
217
+ if (this.options.json) {
218
+ this.logJson({ event: 'summary', summary });
219
+ return;
220
+ }
221
+ if (this.options.quiet) {
222
+ const status = summary.totalFailed === 0 ? 'PASSED' : 'FAILED';
223
+ this.log(`\nSummary: ${summary.totalPassed}/${summary.totalTests} | ${totalDuration}ms | ${status}`);
224
+ return;
225
+ }
226
+ // Detailed summary
227
+ this.log(this.format('\n📊 Test Summary', 'bold'));
228
+ this.log(this.format('┌────────────────────────────────┐', 'dim'));
229
+ this.log(this.format(`│ Total Files: ${summary.totalFiles.toString().padStart(14)} │`, 'white'));
230
+ this.log(this.format(`│ Total Tests: ${summary.totalTests.toString().padStart(14)} │`, 'white'));
231
+ this.log(this.format(`│ Passed: ${summary.totalPassed.toString().padStart(14)} │`, 'green'));
232
+ this.log(this.format(`│ Failed: ${summary.totalFailed.toString().padStart(14)} │`, summary.totalFailed > 0 ? 'red' : 'green'));
233
+ this.log(this.format(`│ Duration: ${totalDuration.toString().padStart(14)}ms │`, 'white'));
234
+ this.log(this.format('└────────────────────────────────┘', 'dim'));
235
+ // File results
236
+ if (summary.totalFailed > 0) {
237
+ this.log(this.format('\n❌ Failed Tests:', 'red'));
238
+ this.fileResults.forEach(fileResult => {
239
+ if (fileResult.failed > 0) {
240
+ this.log(this.format(`\n ${fileResult.file}`, 'yellow'));
241
+ fileResult.tests.filter(t => !t.passed).forEach(test => {
242
+ this.log(this.format(` ❌ ${test.name}`, 'red'));
243
+ if (test.error) {
244
+ this.log(this.format(` ${test.error}`, 'dim'));
245
+ }
246
+ });
247
+ }
248
+ });
249
+ }
250
+ // Performance metrics
251
+ if (this.options.verbose) {
252
+ const avgDuration = Math.round(totalDuration / summary.totalTests);
253
+ const slowestTest = this.fileResults
254
+ .flatMap(r => r.tests)
255
+ .sort((a, b) => b.duration - a.duration)[0];
256
+ this.log(this.format('\n⏱️ Performance Metrics:', 'cyan'));
257
+ this.log(this.format(` Average per test: ${avgDuration}ms`, 'white'));
258
+ if (slowestTest) {
259
+ this.log(this.format(` Slowest test: ${slowestTest.name} (${slowestTest.duration}ms)`, 'yellow'));
260
+ }
261
+ }
262
+ // Final status
263
+ const status = summary.totalFailed === 0 ? 'ALL TESTS PASSED! 🎉' : 'SOME TESTS FAILED! ❌';
264
+ const statusColor = summary.totalFailed === 0 ? 'green' : 'red';
265
+ this.log(this.format(`\n${status}`, statusColor));
266
+ }
267
+ // Error display
268
+ error(message, file, stack) {
269
+ if (this.options.json) {
270
+ this.logJson({ event: 'error', message, file, stack });
271
+ return;
272
+ }
273
+ if (this.options.quiet) {
274
+ console.error(`ERROR: ${message}`);
275
+ }
276
+ else {
277
+ this.log(this.format('\n⚠️ Error', 'red'));
278
+ if (file)
279
+ this.log(this.format(` File: ${file}`, 'yellow'));
280
+ this.log(this.format(` ${message}`, 'red'));
281
+ if (stack && this.options.verbose) {
282
+ this.log(this.format(` Stack:`, 'dim'));
283
+ this.log(this.format(stack.split('\n').map(line => ` ${line}`).join('\n'), 'dim'));
284
+ }
285
+ }
286
+ }
287
+ }
288
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHN0ZXN0LmxvZ2dpbmcuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy90c3Rlc3QubG9nZ2luZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsYUFBYSxJQUFJLEVBQUUsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQy9ELE9BQU8sS0FBSyxPQUFPLE1BQU0scUJBQXFCLENBQUM7QUFDL0MsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFDekIsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFpQzdCLE1BQU0sT0FBTyxZQUFZO0lBT3ZCLFlBQVksVUFBc0IsRUFBRTtRQUo1QixnQkFBVyxHQUFxQixFQUFFLENBQUM7UUFDbkMsc0JBQWlCLEdBQTBCLElBQUksQ0FBQztRQUNoRCx1QkFBa0IsR0FBa0IsSUFBSSxDQUFDO1FBRy9DLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO0lBQzlCLENBQUM7SUFFTyxNQUFNLENBQUMsSUFBWSxFQUFFLEtBQWM7UUFDekMsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ25DLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztRQUNELE9BQU8sRUFBRSxDQUFDLElBQUksRUFBRSxLQUFZLENBQUMsQ0FBQztJQUNoQyxDQUFDO0lBRU8sR0FBRyxDQUFDLE9BQWU7UUFDekIsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ3RCLHFDQUFxQztZQUNyQywyQ0FBMkM7WUFDM0MsT0FBTztRQUNULENBQUM7UUFFRCxPQUFPLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBRXJCLGlGQUFpRjtRQUNqRixJQUFJLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO1lBQzVCLElBQUks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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@git.zone/tstest",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "private": false,
5
5
  "description": "a test utility to run tests that match test/**/*.ts",
6
6
  "main": "dist_ts/index.js",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@git.zone/tstest',
6
- version: '1.1.0',
6
+ version: '1.3.0',
7
7
  description: 'a test utility to run tests that match test/**/*.ts'
8
8
  }
package/ts/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { TsTest } from './tstest.classes.tstest.js';
2
+ import type { LogOptions } from './tstest.logging.js';
2
3
 
3
4
  export enum TestExecutionMode {
4
5
  DIRECTORY = 'directory',
@@ -7,12 +8,53 @@ export enum TestExecutionMode {
7
8
  }
8
9
 
9
10
  export const runCli = async () => {
10
- if (!process.argv[2]) {
11
+ // Parse command line arguments
12
+ const args = process.argv.slice(2);
13
+ const logOptions: LogOptions = {};
14
+ let testPath: string | null = null;
15
+
16
+ // Parse options
17
+ for (let i = 0; i < args.length; i++) {
18
+ const arg = args[i];
19
+
20
+ switch (arg) {
21
+ case '--quiet':
22
+ case '-q':
23
+ logOptions.quiet = true;
24
+ break;
25
+ case '--verbose':
26
+ case '-v':
27
+ logOptions.verbose = true;
28
+ break;
29
+ case '--no-color':
30
+ logOptions.noColor = true;
31
+ break;
32
+ case '--json':
33
+ logOptions.json = true;
34
+ break;
35
+ case '--log-file':
36
+ case '--logfile':
37
+ logOptions.logFile = true; // Set this as a flag, not a value
38
+ break;
39
+ default:
40
+ if (!arg.startsWith('-')) {
41
+ testPath = arg;
42
+ }
43
+ }
44
+ }
45
+
46
+ if (!testPath) {
11
47
  console.error('You must specify a test directory/file/pattern as argument. Please try again.');
48
+ console.error('\nUsage: tstest <path> [options]');
49
+ console.error('\nOptions:');
50
+ console.error(' --quiet, -q Minimal output');
51
+ console.error(' --verbose, -v Verbose output');
52
+ console.error(' --no-color Disable colored output');
53
+ console.error(' --json Output results as JSON');
54
+ console.error(' --logfile Write logs to .nogit/testlogs/[testfile].log');
12
55
  process.exit(1);
13
56
  }
14
57
 
15
- const testPath = process.argv[2];
16
58
  let executionMode: TestExecutionMode;
17
59
 
18
60
  // Detect execution mode based on the argument
@@ -24,6 +66,6 @@ export const runCli = async () => {
24
66
  executionMode = TestExecutionMode.DIRECTORY;
25
67
  }
26
68
 
27
- const tsTestInstance = new TsTest(process.cwd(), testPath, executionMode);
69
+ const tsTestInstance = new TsTest(process.cwd(), testPath, executionMode, logOptions);
28
70
  await tsTestInstance.run();
29
71
  };
@@ -6,59 +6,37 @@ import { coloredString as cs } from '@push.rocks/consolecolor';
6
6
 
7
7
  import { TapParser } from './tstest.classes.tap.parser.js';
8
8
  import * as logPrefixes from './tstest.logprefixes.js';
9
+ import { TsTestLogger } from './tstest.logging.js';
9
10
 
10
11
  export class TapCombinator {
11
12
  tapParserStore: TapParser[] = [];
13
+ private logger: TsTestLogger;
14
+
15
+ constructor(logger: TsTestLogger) {
16
+ this.logger = logger;
17
+ }
18
+
12
19
  addTapParser(tapParserArg: TapParser) {
13
20
  this.tapParserStore.push(tapParserArg);
14
21
  }
15
22
 
16
23
  evaluate() {
17
- console.log(
18
- `${logPrefixes.TsTestPrefix} RESULTS FOR ${this.tapParserStore.length} TESTFILE(S):`
19
- );
20
-
21
- let failGlobal = false; // determine wether tstest should fail
24
+ // Call the logger's summary method
25
+ this.logger.summary();
26
+
27
+ // Check for failures
28
+ let failGlobal = false;
22
29
  for (const tapParser of this.tapParserStore) {
23
- if (!tapParser.expectedTests) {
24
- failGlobal = true;
25
- let overviewString =
26
- logPrefixes.TsTestPrefix +
27
- cs(` ${tapParser.fileName} ${plugins.figures.cross}`, 'red') +
28
- ` ${plugins.figures.pointer} ` +
29
- `does not specify tests!`;
30
- console.log(overviewString);
31
- } else if (tapParser.expectedTests !== tapParser.receivedTests) {
32
- failGlobal = true;
33
- let overviewString =
34
- logPrefixes.TsTestPrefix +
35
- cs(` ${tapParser.fileName} ${plugins.figures.cross}`, 'red') +
36
- ` ${plugins.figures.pointer} ` +
37
- tapParser.getTestOverviewAsString() +
38
- `did not execute all specified tests!`;
39
- console.log(overviewString);
40
- } else if (tapParser.getErrorTests().length === 0) {
41
- let overviewString =
42
- logPrefixes.TsTestPrefix +
43
- cs(` ${tapParser.fileName} ${plugins.figures.tick}`, 'green') +
44
- ` ${plugins.figures.pointer} ` +
45
- tapParser.getTestOverviewAsString();
46
- console.log(overviewString);
47
- } else {
30
+ if (!tapParser.expectedTests ||
31
+ tapParser.expectedTests !== tapParser.receivedTests ||
32
+ tapParser.getErrorTests().length > 0) {
48
33
  failGlobal = true;
49
- let overviewString =
50
- logPrefixes.TsTestPrefix +
51
- cs(` ${tapParser.fileName} ${plugins.figures.cross}`, 'red') +
52
- ` ${plugins.figures.pointer} ` +
53
- tapParser.getTestOverviewAsString();
54
- console.log(overviewString);
34
+ break;
55
35
  }
56
36
  }
57
- console.log(cs(plugins.figures.hamburger.repeat(48), 'cyan'));
58
- if (!failGlobal) {
59
- console.log(cs('FINAL RESULT: SUCCESS!', 'green'));
60
- } else {
61
- console.log(cs('FINAL RESULT: FAIL!', 'red'));
37
+
38
+ // Exit with error code if tests failed
39
+ if (failGlobal) {
62
40
  process.exit(1);
63
41
  }
64
42
  }
@@ -7,6 +7,7 @@ import { coloredString as cs } from '@push.rocks/consolecolor';
7
7
  import * as plugins from './tstest.plugins.js';
8
8
  import { TapTestResult } from './tstest.classes.tap.testresult.js';
9
9
  import * as logPrefixes from './tstest.logprefixes.js';
10
+ import { TsTestLogger } from './tstest.logging.js';
10
11
 
11
12
  export class TapParser {
12
13
  testStore: TapTestResult[] = [];
@@ -19,11 +20,15 @@ export class TapParser {
19
20
  activeTapTestResult: TapTestResult;
20
21
 
21
22
  pretaskRegex = /^::__PRETASK:(.*)$/;
23
+
24
+ private logger: TsTestLogger;
22
25
 
23
26
  /**
24
27
  * the constructor for TapParser
25
28
  */
26
- constructor(public fileName: string) {}
29
+ constructor(public fileName: string, logger?: TsTestLogger) {
30
+ this.logger = logger;
31
+ }
27
32
 
28
33
  private _getNewTapTestResult() {
29
34
  this.activeTapTestResult = new TapTestResult(this.testStore.length + 1);
@@ -45,9 +50,9 @@ export class TapParser {
45
50
  logLineIsTapProtocol = true;
46
51
  const regexResult = this.expectedTestsRegex.exec(logLine);
47
52
  this.expectedTests = parseInt(regexResult[2]);
48
- console.log(
49
- `${logPrefixes.TapPrefix} ${cs(`Expecting ${this.expectedTests} tests!`, 'blue')}`
50
- );
53
+ if (this.logger) {
54
+ this.logger.tapOutput(`Expecting ${this.expectedTests} tests!`);
55
+ }
51
56
 
52
57
  // initiating first TapResult
53
58
  this._getNewTapTestResult();
@@ -55,7 +60,9 @@ export class TapParser {
55
60
  logLineIsTapProtocol = true;
56
61
  const pretaskContentMatch = this.pretaskRegex.exec(logLine);
57
62
  if (pretaskContentMatch && pretaskContentMatch[1]) {
58
- console.log(`${logPrefixes.TapPretaskPrefix} Pretask ->${pretaskContentMatch[1]}: Success.`);
63
+ if (this.logger) {
64
+ this.logger.tapOutput(`Pretask -> ${pretaskContentMatch[1]}: Success.`);
65
+ }
59
66
  }
60
67
  } else if (this.testStatusRegex.test(logLine)) {
61
68
  logLineIsTapProtocol = true;
@@ -73,26 +80,20 @@ export class TapParser {
73
80
 
74
81
  // test for protocol error
75
82
  if (testId !== this.activeTapTestResult.id) {
76
- console.log(
77
- `${logPrefixes.TapErrorPrefix} Something is strange! Test Ids are not equal!`
78
- );
83
+ if (this.logger) {
84
+ this.logger.error('Something is strange! Test Ids are not equal!');
85
+ }
79
86
  }
80
87
  this.activeTapTestResult.setTestResult(testOk);
81
88
 
82
89
  if (testOk) {
83
- console.log(
84
- logPrefixes.TapPrefix,
85
- `${cs(`T${testId} ${plugins.figures.tick}`, 'green')} ${plugins.figures.arrowRight} ` +
86
- cs(testSubject, 'blue') +
87
- ` | ${cs(`${testDuration} ms`, 'orange')}`
88
- );
90
+ if (this.logger) {
91
+ this.logger.testResult(testSubject, true, testDuration);
92
+ }
89
93
  } else {
90
- console.log(
91
- logPrefixes.TapPrefix,
92
- `${cs(`T${testId} ${plugins.figures.cross}`, 'red')} ${plugins.figures.arrowRight} ` +
93
- cs(testSubject, 'blue') +
94
- ` | ${cs(`${testDuration} ms`, 'orange')}`
95
- );
94
+ if (this.logger) {
95
+ this.logger.testResult(testSubject, false, testDuration);
96
+ }
96
97
  }
97
98
  }
98
99
 
@@ -100,7 +101,10 @@ export class TapParser {
100
101
  if (this.activeTapTestResult) {
101
102
  this.activeTapTestResult.addLogLine(logLine);
102
103
  }
103
- console.log(logLine);
104
+ if (this.logger) {
105
+ // This is console output from the test file, not TAP protocol
106
+ this.logger.testConsoleOutput(logLine);
107
+ }
104
108
  }
105
109
 
106
110
  if (this.activeTapTestResult && this.activeTapTestResult.testSettled) {
@@ -172,38 +176,32 @@ export class TapParser {
172
176
 
173
177
  // check wether all tests ran
174
178
  if (this.expectedTests === this.receivedTests) {
175
- console.log(
176
- `${logPrefixes.TapPrefix} ${cs(
177
- `${this.receivedTests} out of ${this.expectedTests} Tests completed!`,
178
- 'green'
179
- )}`
180
- );
179
+ if (this.logger) {
180
+ this.logger.tapOutput(`${this.receivedTests} out of ${this.expectedTests} Tests completed!`);
181
+ }
181
182
  } else {
182
- console.log(
183
- `${logPrefixes.TapErrorPrefix} ${cs(
184
- `Only ${this.receivedTests} out of ${this.expectedTests} completed!`,
185
- 'red'
186
- )}`
187
- );
183
+ if (this.logger) {
184
+ this.logger.error(`Only ${this.receivedTests} out of ${this.expectedTests} completed!`);
185
+ }
188
186
  }
189
187
  if (!this.expectedTests) {
190
- console.log(cs('Error: No tests were defined. Therefore the testfile failed!', 'red'));
188
+ if (this.logger) {
189
+ this.logger.error('No tests were defined. Therefore the testfile failed!');
190
+ }
191
191
  } else if (this.expectedTests !== this.receivedTests) {
192
- console.log(
193
- cs(
194
- 'Error: The amount of received tests and expectedTests is unequal! Therefore the testfile failed',
195
- 'red'
196
- )
197
- );
192
+ if (this.logger) {
193
+ this.logger.error('The amount of received tests and expectedTests is unequal! Therefore the testfile failed');
194
+ }
198
195
  } else if (this.getErrorTests().length === 0) {
199
- console.log(`${logPrefixes.TapPrefix} ${cs(`All tests are successfull!!!`, 'green')}`);
196
+ if (this.logger) {
197
+ this.logger.tapOutput('All tests are successfull!!!');
198
+ this.logger.testFileEnd(this.receivedTests, 0, 0);
199
+ }
200
200
  } else {
201
- console.log(
202
- `${logPrefixes.TapPrefix} ${cs(
203
- `${this.getErrorTests().length} tests threw an error!!!`,
204
- 'red'
205
- )}`
206
- );
201
+ if (this.logger) {
202
+ this.logger.tapOutput(`${this.getErrorTests().length} tests threw an error!!!`, true);
203
+ this.logger.testFileEnd(this.receivedTests - this.getErrorTests().length, this.getErrorTests().length, 0);
204
+ }
207
205
  }
208
206
  }
209
207
  }