spec-up-t 1.4.1 → 1.6.0-beta.1
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.
- package/assets/compiled/body.js +3 -2
- package/assets/compiled/head.css +5 -5
- package/assets/css/embedded-libraries/bootstrap.min.css +1 -1
- package/assets/css/header-navbar.css +4 -4
- package/assets/css/index.css +5 -4
- package/assets/css/refs.css +30 -0
- package/assets/css/terms-and-definitions.css +89 -1
- package/assets/js/github-issues.js +3 -3
- package/assets/js/insert-irefs.js +214 -0
- package/config/asset-map.json +2 -1
- package/examples/read-console-messages.js +102 -0
- package/gulpfile.js +42 -1
- package/index.js +49 -1
- package/package.json +2 -1
- package/src/create-docx.js +13 -6
- package/src/create-pdf.js +22 -18
- package/src/health-check.js +47 -629
- package/src/init.js +7 -3
- package/src/install-from-boilerplate/config-scripts-keys.js +1 -1
- package/src/markdown-it/README.md +2 -14
- package/src/markdown-it/index.js +1 -7
- package/src/parsers/template-tag-parser.js +42 -4
- package/src/pipeline/postprocessing/definition-list-postprocessor.js +4 -2
- package/src/pipeline/references/collect-external-references.js +101 -17
- package/src/pipeline/references/external-references-service.js +102 -21
- package/src/pipeline/references/fetch-terms-from-index.js +62 -1
- package/src/pipeline/references/process-xtrefs-data.js +67 -9
- package/src/pipeline/references/xtref-utils.js +22 -3
- package/src/pipeline/rendering/render-spec-document.js +0 -1
- package/src/run-healthcheck.js +177 -0
- package/src/utils/logger.js +129 -8
- package/src/utils/message-collector.js +144 -0
- package/src/utils/regex-patterns.js +3 -1
- package/templates/template.html +6 -6
- package/test/logger.test.js +290 -0
- package/test/message-collector.test.js +286 -0
- package/assets/css/insert-trefs.css +0 -1
- package/src/markdown-it/link-enhancement.js +0 -98
- package/src/utils/LOGGER.md +0 -81
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Unit tests for enhanced Logger.error and Logger.warn functionality
|
|
3
|
+
*
|
|
4
|
+
* Tests the new context, hint, and details options for error and warning messages.
|
|
5
|
+
* Ensures backward compatibility with existing simple message calls.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const Logger = require('../src/utils/logger');
|
|
9
|
+
const messageCollector = require('../src/utils/message-collector');
|
|
10
|
+
|
|
11
|
+
// Mock console.log to capture output
|
|
12
|
+
let consoleLogs = [];
|
|
13
|
+
const originalConsoleLog = console.log;
|
|
14
|
+
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
console.log = (...args) => {
|
|
17
|
+
consoleLogs.push(args);
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterAll(() => {
|
|
22
|
+
console.log = originalConsoleLog;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
consoleLogs = [];
|
|
27
|
+
messageCollector.clearMessages();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
messageCollector.stopCollecting();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('Enhanced Logger.error', () => {
|
|
35
|
+
test('should work with simple message (backward compatibility)', () => {
|
|
36
|
+
Logger.error('Simple error message');
|
|
37
|
+
|
|
38
|
+
expect(consoleLogs.length).toBeGreaterThan(0);
|
|
39
|
+
expect(consoleLogs[0].join(' ')).toContain('Simple error message');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('should work with message and regular arguments', () => {
|
|
43
|
+
Logger.error('Error with args', 'arg1', 123);
|
|
44
|
+
|
|
45
|
+
expect(consoleLogs.length).toBeGreaterThan(0);
|
|
46
|
+
const output = consoleLogs[0].join(' ');
|
|
47
|
+
expect(output).toContain('Error with args');
|
|
48
|
+
expect(output).toContain('arg1');
|
|
49
|
+
expect(output).toContain('123');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('should display context when provided', () => {
|
|
53
|
+
consoleLogs = [];
|
|
54
|
+
Logger.error('Test error', { context: 'specs.json' });
|
|
55
|
+
|
|
56
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
57
|
+
expect(allOutput).toContain('Context:');
|
|
58
|
+
expect(allOutput).toContain('specs.json');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('should display hint when provided', () => {
|
|
62
|
+
consoleLogs = [];
|
|
63
|
+
Logger.error('Test error', { hint: 'Run npm install' });
|
|
64
|
+
|
|
65
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
66
|
+
expect(allOutput).toContain('How to fix:');
|
|
67
|
+
expect(allOutput).toContain('Run npm install');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('should display details when provided', () => {
|
|
71
|
+
consoleLogs = [];
|
|
72
|
+
Logger.error('Test error', { details: 'Error code: 404' });
|
|
73
|
+
|
|
74
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
75
|
+
expect(allOutput).toContain('Details:');
|
|
76
|
+
expect(allOutput).toContain('Error code: 404');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('should handle all options together', () => {
|
|
80
|
+
consoleLogs = [];
|
|
81
|
+
Logger.error('Test error', {
|
|
82
|
+
context: 'Test context',
|
|
83
|
+
hint: 'Test hint',
|
|
84
|
+
details: 'Test details'
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
88
|
+
expect(allOutput).toContain('Test error');
|
|
89
|
+
expect(allOutput).toContain('Context:');
|
|
90
|
+
expect(allOutput).toContain('Test context');
|
|
91
|
+
expect(allOutput).toContain('How to fix:');
|
|
92
|
+
expect(allOutput).toContain('Test hint');
|
|
93
|
+
expect(allOutput).toContain('Details:');
|
|
94
|
+
expect(allOutput).toContain('Test details');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('should handle regular args mixed with options', () => {
|
|
98
|
+
consoleLogs = [];
|
|
99
|
+
Logger.error('Error', 'arg1', 'arg2', { hint: 'Fix it' });
|
|
100
|
+
|
|
101
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
102
|
+
expect(allOutput).toContain('Error');
|
|
103
|
+
expect(allOutput).toContain('arg1');
|
|
104
|
+
expect(allOutput).toContain('arg2');
|
|
105
|
+
expect(allOutput).toContain('Fix it');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('should handle object details', () => {
|
|
109
|
+
consoleLogs = [];
|
|
110
|
+
Logger.error('Test error', {
|
|
111
|
+
details: { code: 404, message: 'Not found' }
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
115
|
+
expect(allOutput).toContain('404');
|
|
116
|
+
expect(allOutput).toContain('Not found');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('should not treat regular object as options', () => {
|
|
120
|
+
consoleLogs = [];
|
|
121
|
+
Logger.error('Test error', { someData: 'value' });
|
|
122
|
+
|
|
123
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
124
|
+
expect(allOutput).toContain('Test error');
|
|
125
|
+
// Should treat it as regular argument, not as options
|
|
126
|
+
// The object will be stringified as [object Object]
|
|
127
|
+
expect(allOutput).toContain('[object Object]');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('should collect enhanced messages', () => {
|
|
131
|
+
messageCollector.startCollecting('test');
|
|
132
|
+
|
|
133
|
+
Logger.error('Test error', {
|
|
134
|
+
context: 'test context',
|
|
135
|
+
hint: 'test hint',
|
|
136
|
+
details: 'test details'
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const messages = messageCollector.getMessages();
|
|
140
|
+
expect(messages).toHaveLength(1);
|
|
141
|
+
expect(messages[0].type).toBe('error');
|
|
142
|
+
expect(messages[0].message).toBe('Test error');
|
|
143
|
+
|
|
144
|
+
// Should have collected the options
|
|
145
|
+
const additionalData = messages[0].additionalData;
|
|
146
|
+
expect(additionalData).toBeDefined();
|
|
147
|
+
expect(additionalData.length).toBeGreaterThan(0);
|
|
148
|
+
|
|
149
|
+
messageCollector.stopCollecting();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('Enhanced Logger.warn', () => {
|
|
154
|
+
test('should work with simple message (backward compatibility)', () => {
|
|
155
|
+
Logger.warn('Simple warning message');
|
|
156
|
+
|
|
157
|
+
expect(consoleLogs.length).toBeGreaterThan(0);
|
|
158
|
+
expect(consoleLogs[0].join(' ')).toContain('Simple warning message');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('should work with message and regular arguments', () => {
|
|
162
|
+
Logger.warn('Warning with args', 'arg1', 123);
|
|
163
|
+
|
|
164
|
+
expect(consoleLogs.length).toBeGreaterThan(0);
|
|
165
|
+
const output = consoleLogs[0].join(' ');
|
|
166
|
+
expect(output).toContain('Warning with args');
|
|
167
|
+
expect(output).toContain('arg1');
|
|
168
|
+
expect(output).toContain('123');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('should display context when provided', () => {
|
|
172
|
+
consoleLogs = [];
|
|
173
|
+
Logger.warn('Test warning', { context: 'Configuration file' });
|
|
174
|
+
|
|
175
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
176
|
+
expect(allOutput).toContain('Context:');
|
|
177
|
+
expect(allOutput).toContain('Configuration file');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('should display hint when provided', () => {
|
|
181
|
+
consoleLogs = [];
|
|
182
|
+
Logger.warn('Test warning', { hint: 'Consider updating to latest version' });
|
|
183
|
+
|
|
184
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
185
|
+
expect(allOutput).toContain('Suggestion:');
|
|
186
|
+
expect(allOutput).toContain('Consider updating to latest version');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('should display details when provided', () => {
|
|
190
|
+
consoleLogs = [];
|
|
191
|
+
Logger.warn('Test warning', { details: 'Using fallback: default' });
|
|
192
|
+
|
|
193
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
194
|
+
expect(allOutput).toContain('Details:');
|
|
195
|
+
expect(allOutput).toContain('Using fallback: default');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('should handle all options together', () => {
|
|
199
|
+
consoleLogs = [];
|
|
200
|
+
Logger.warn('Test warning', {
|
|
201
|
+
context: 'Test context',
|
|
202
|
+
hint: 'Test suggestion',
|
|
203
|
+
details: 'Test details'
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
207
|
+
expect(allOutput).toContain('Test warning');
|
|
208
|
+
expect(allOutput).toContain('Context:');
|
|
209
|
+
expect(allOutput).toContain('Test context');
|
|
210
|
+
expect(allOutput).toContain('Suggestion:');
|
|
211
|
+
expect(allOutput).toContain('Test suggestion');
|
|
212
|
+
expect(allOutput).toContain('Details:');
|
|
213
|
+
expect(allOutput).toContain('Test details');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('should handle regular args mixed with options', () => {
|
|
217
|
+
consoleLogs = [];
|
|
218
|
+
Logger.warn('Warning', 'arg1', 'arg2', { hint: 'Update config' });
|
|
219
|
+
|
|
220
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
221
|
+
expect(allOutput).toContain('Warning');
|
|
222
|
+
expect(allOutput).toContain('arg1');
|
|
223
|
+
expect(allOutput).toContain('arg2');
|
|
224
|
+
expect(allOutput).toContain('Update config');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('should collect enhanced messages', () => {
|
|
228
|
+
messageCollector.startCollecting('test');
|
|
229
|
+
|
|
230
|
+
Logger.warn('Test warning', {
|
|
231
|
+
context: 'test context',
|
|
232
|
+
hint: 'test hint',
|
|
233
|
+
details: 'test details'
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const messages = messageCollector.getMessages();
|
|
237
|
+
expect(messages).toHaveLength(1);
|
|
238
|
+
expect(messages[0].type).toBe('warn');
|
|
239
|
+
expect(messages[0].message).toBe('Test warning');
|
|
240
|
+
|
|
241
|
+
messageCollector.stopCollecting();
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('Edge Cases', () => {
|
|
246
|
+
test('should handle empty options object', () => {
|
|
247
|
+
consoleLogs = [];
|
|
248
|
+
Logger.error('Test', {});
|
|
249
|
+
|
|
250
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
251
|
+
expect(allOutput).toContain('Test');
|
|
252
|
+
// Should just show the message without extra lines
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('should handle null and undefined in options', () => {
|
|
256
|
+
consoleLogs = [];
|
|
257
|
+
Logger.error('Test', {
|
|
258
|
+
context: null,
|
|
259
|
+
hint: undefined,
|
|
260
|
+
details: 'valid'
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
264
|
+
expect(allOutput).toContain('Test');
|
|
265
|
+
expect(allOutput).toContain('Details:');
|
|
266
|
+
expect(allOutput).toContain('valid');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('should handle special characters in options', () => {
|
|
270
|
+
consoleLogs = [];
|
|
271
|
+
Logger.error('Test', {
|
|
272
|
+
hint: 'Fix with: npm run build && npm test'
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
276
|
+
expect(allOutput).toContain('npm run build && npm test');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test('should handle multiline strings in options', () => {
|
|
280
|
+
consoleLogs = [];
|
|
281
|
+
Logger.error('Test', {
|
|
282
|
+
details: 'Line 1\nLine 2\nLine 3'
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const allOutput = consoleLogs.map(log => log.join(' ')).join('\n');
|
|
286
|
+
expect(allOutput).toContain('Line 1');
|
|
287
|
+
expect(allOutput).toContain('Line 2');
|
|
288
|
+
expect(allOutput).toContain('Line 3');
|
|
289
|
+
});
|
|
290
|
+
});
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Unit tests for message-collector utility
|
|
3
|
+
*
|
|
4
|
+
* Tests the message collection functionality that captures console output
|
|
5
|
+
* from Logger calls and stores them in JSON format.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const messageCollector = require('../src/utils/message-collector');
|
|
9
|
+
const Logger = require('../src/utils/logger');
|
|
10
|
+
const fs = require('fs-extra');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
describe('Message Collector', () => {
|
|
14
|
+
// Clean up before and after each test
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
messageCollector.clearMessages();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
messageCollector.stopCollecting();
|
|
21
|
+
messageCollector.clearMessages();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('Basic Collection', () => {
|
|
25
|
+
test('should start and stop collecting', () => {
|
|
26
|
+
expect(messageCollector.getStatistics().isCollecting).toBe(false);
|
|
27
|
+
|
|
28
|
+
messageCollector.startCollecting('test');
|
|
29
|
+
expect(messageCollector.getStatistics().isCollecting).toBe(true);
|
|
30
|
+
expect(messageCollector.getStatistics().currentOperation).toBe('test');
|
|
31
|
+
|
|
32
|
+
messageCollector.stopCollecting();
|
|
33
|
+
expect(messageCollector.getStatistics().isCollecting).toBe(false);
|
|
34
|
+
expect(messageCollector.getStatistics().currentOperation).toBe(null);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('should collect messages when active', () => {
|
|
38
|
+
messageCollector.startCollecting('test');
|
|
39
|
+
|
|
40
|
+
Logger.info('Test message');
|
|
41
|
+
Logger.success('Success message');
|
|
42
|
+
Logger.warn('Warning message');
|
|
43
|
+
|
|
44
|
+
const messages = messageCollector.getMessages();
|
|
45
|
+
expect(messages).toHaveLength(3);
|
|
46
|
+
expect(messages[0].message).toBe('Test message');
|
|
47
|
+
expect(messages[0].type).toBe('info');
|
|
48
|
+
expect(messages[0].operation).toBe('test');
|
|
49
|
+
|
|
50
|
+
messageCollector.stopCollecting();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('should not collect messages when inactive', () => {
|
|
54
|
+
Logger.info('This should not be collected');
|
|
55
|
+
|
|
56
|
+
const messages = messageCollector.getMessages();
|
|
57
|
+
expect(messages).toHaveLength(0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('should clear messages', () => {
|
|
61
|
+
messageCollector.startCollecting('test');
|
|
62
|
+
Logger.info('Message 1');
|
|
63
|
+
Logger.info('Message 2');
|
|
64
|
+
|
|
65
|
+
expect(messageCollector.getMessages()).toHaveLength(2);
|
|
66
|
+
|
|
67
|
+
messageCollector.clearMessages();
|
|
68
|
+
expect(messageCollector.getMessages()).toHaveLength(0);
|
|
69
|
+
|
|
70
|
+
messageCollector.stopCollecting();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('Message Structure', () => {
|
|
75
|
+
test('should include all required fields', () => {
|
|
76
|
+
messageCollector.startCollecting('test');
|
|
77
|
+
Logger.info('Test message', 'extra', 'data');
|
|
78
|
+
|
|
79
|
+
const messages = messageCollector.getMessages();
|
|
80
|
+
expect(messages[0]).toHaveProperty('timestamp');
|
|
81
|
+
expect(messages[0]).toHaveProperty('type');
|
|
82
|
+
expect(messages[0]).toHaveProperty('message');
|
|
83
|
+
expect(messages[0]).toHaveProperty('operation');
|
|
84
|
+
|
|
85
|
+
messageCollector.stopCollecting();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('should capture additional arguments', () => {
|
|
89
|
+
messageCollector.startCollecting('test');
|
|
90
|
+
Logger.info('Test', 'arg1', 'arg2', 123);
|
|
91
|
+
|
|
92
|
+
const messages = messageCollector.getMessages();
|
|
93
|
+
expect(messages[0].additionalData).toEqual(['arg1', 'arg2', '123']);
|
|
94
|
+
|
|
95
|
+
messageCollector.stopCollecting();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('should handle different message types', () => {
|
|
99
|
+
messageCollector.startCollecting('test');
|
|
100
|
+
|
|
101
|
+
Logger.success('Success');
|
|
102
|
+
Logger.error('Error');
|
|
103
|
+
Logger.warn('Warning');
|
|
104
|
+
Logger.info('Info');
|
|
105
|
+
Logger.process('Process');
|
|
106
|
+
Logger.highlight('Highlight');
|
|
107
|
+
Logger.debug('Debug');
|
|
108
|
+
Logger.separator();
|
|
109
|
+
|
|
110
|
+
const messages = messageCollector.getMessages();
|
|
111
|
+
expect(messages).toHaveLength(8);
|
|
112
|
+
|
|
113
|
+
const types = messages.map(m => m.type);
|
|
114
|
+
expect(types).toContain('success');
|
|
115
|
+
expect(types).toContain('error');
|
|
116
|
+
expect(types).toContain('warn');
|
|
117
|
+
expect(types).toContain('info');
|
|
118
|
+
expect(types).toContain('process');
|
|
119
|
+
expect(types).toContain('highlight');
|
|
120
|
+
expect(types).toContain('debug');
|
|
121
|
+
expect(types).toContain('separator');
|
|
122
|
+
|
|
123
|
+
messageCollector.stopCollecting();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('Statistics', () => {
|
|
128
|
+
test('should calculate message counts by type', () => {
|
|
129
|
+
messageCollector.startCollecting('test');
|
|
130
|
+
|
|
131
|
+
Logger.info('Info 1');
|
|
132
|
+
Logger.info('Info 2');
|
|
133
|
+
Logger.success('Success 1');
|
|
134
|
+
Logger.error('Error 1');
|
|
135
|
+
|
|
136
|
+
const stats = messageCollector.getStatistics();
|
|
137
|
+
expect(stats.total).toBe(4);
|
|
138
|
+
expect(stats.byType.info).toBe(2);
|
|
139
|
+
expect(stats.byType.success).toBe(1);
|
|
140
|
+
expect(stats.byType.error).toBe(1);
|
|
141
|
+
|
|
142
|
+
messageCollector.stopCollecting();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('should track messages by operation', () => {
|
|
146
|
+
messageCollector.startCollecting('render');
|
|
147
|
+
Logger.info('Render 1');
|
|
148
|
+
Logger.info('Render 2');
|
|
149
|
+
messageCollector.stopCollecting();
|
|
150
|
+
|
|
151
|
+
messageCollector.startCollecting('collect');
|
|
152
|
+
Logger.info('Collect 1');
|
|
153
|
+
messageCollector.stopCollecting();
|
|
154
|
+
|
|
155
|
+
const stats = messageCollector.getStatistics();
|
|
156
|
+
expect(stats.byOperation.render).toBe(2);
|
|
157
|
+
expect(stats.byOperation.collect).toBe(1);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('File Operations', () => {
|
|
162
|
+
const testOutputPath = path.join(__dirname, '.test-cache', 'test-messages.json');
|
|
163
|
+
|
|
164
|
+
beforeEach(async () => {
|
|
165
|
+
// Clean up test output
|
|
166
|
+
await fs.remove(path.dirname(testOutputPath));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
afterEach(async () => {
|
|
170
|
+
// Clean up test output
|
|
171
|
+
await fs.remove(path.dirname(testOutputPath));
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('should save messages to file', async () => {
|
|
175
|
+
messageCollector.startCollecting('test');
|
|
176
|
+
Logger.info('Test message');
|
|
177
|
+
Logger.success('Success');
|
|
178
|
+
messageCollector.stopCollecting();
|
|
179
|
+
|
|
180
|
+
const savedPath = await messageCollector.saveMessages(testOutputPath);
|
|
181
|
+
expect(savedPath).toBe(testOutputPath);
|
|
182
|
+
|
|
183
|
+
const exists = await fs.pathExists(testOutputPath);
|
|
184
|
+
expect(exists).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('should create directory if it does not exist', async () => {
|
|
188
|
+
messageCollector.startCollecting('test');
|
|
189
|
+
Logger.info('Test');
|
|
190
|
+
messageCollector.stopCollecting();
|
|
191
|
+
|
|
192
|
+
await messageCollector.saveMessages(testOutputPath);
|
|
193
|
+
|
|
194
|
+
const dirExists = await fs.pathExists(path.dirname(testOutputPath));
|
|
195
|
+
expect(dirExists).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('should include metadata in saved file', async () => {
|
|
199
|
+
messageCollector.startCollecting('test');
|
|
200
|
+
Logger.info('Info message');
|
|
201
|
+
Logger.success('Success message');
|
|
202
|
+
Logger.warn('Warning message');
|
|
203
|
+
messageCollector.stopCollecting();
|
|
204
|
+
|
|
205
|
+
await messageCollector.saveMessages(testOutputPath);
|
|
206
|
+
|
|
207
|
+
const output = await fs.readJson(testOutputPath);
|
|
208
|
+
|
|
209
|
+
expect(output).toHaveProperty('metadata');
|
|
210
|
+
expect(output).toHaveProperty('messages');
|
|
211
|
+
|
|
212
|
+
expect(output.metadata).toHaveProperty('generatedAt');
|
|
213
|
+
expect(output.metadata).toHaveProperty('totalMessages');
|
|
214
|
+
expect(output.metadata).toHaveProperty('operations');
|
|
215
|
+
expect(output.metadata).toHaveProperty('messagesByType');
|
|
216
|
+
|
|
217
|
+
expect(output.metadata.totalMessages).toBe(3);
|
|
218
|
+
expect(output.metadata.operations).toContain('test');
|
|
219
|
+
expect(output.metadata.messagesByType.info).toBe(1);
|
|
220
|
+
expect(output.metadata.messagesByType.success).toBe(1);
|
|
221
|
+
expect(output.metadata.messagesByType.warn).toBe(1);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('should save valid JSON', async () => {
|
|
225
|
+
messageCollector.startCollecting('test');
|
|
226
|
+
Logger.info('Message 1');
|
|
227
|
+
Logger.success('Message 2');
|
|
228
|
+
messageCollector.stopCollecting();
|
|
229
|
+
|
|
230
|
+
await messageCollector.saveMessages(testOutputPath);
|
|
231
|
+
|
|
232
|
+
// Should not throw when reading
|
|
233
|
+
const output = await fs.readJson(testOutputPath);
|
|
234
|
+
expect(output.messages).toHaveLength(2);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('Edge Cases', () => {
|
|
239
|
+
test('should handle empty message collection', () => {
|
|
240
|
+
messageCollector.startCollecting('test');
|
|
241
|
+
messageCollector.stopCollecting();
|
|
242
|
+
|
|
243
|
+
const messages = messageCollector.getMessages();
|
|
244
|
+
expect(messages).toHaveLength(0);
|
|
245
|
+
|
|
246
|
+
const stats = messageCollector.getStatistics();
|
|
247
|
+
expect(stats.total).toBe(0);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('should handle multiple start calls', () => {
|
|
251
|
+
messageCollector.startCollecting('operation1');
|
|
252
|
+
messageCollector.startCollecting('operation2');
|
|
253
|
+
|
|
254
|
+
Logger.info('Test');
|
|
255
|
+
|
|
256
|
+
const messages = messageCollector.getMessages();
|
|
257
|
+
expect(messages[0].operation).toBe('operation2'); // Last operation
|
|
258
|
+
|
|
259
|
+
messageCollector.stopCollecting();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('should handle special characters in messages', () => {
|
|
263
|
+
messageCollector.startCollecting('test');
|
|
264
|
+
Logger.info('Special chars: 😀 🔥 \n\t \\');
|
|
265
|
+
|
|
266
|
+
const messages = messageCollector.getMessages();
|
|
267
|
+
expect(messages[0].message).toContain('😀');
|
|
268
|
+
|
|
269
|
+
messageCollector.stopCollecting();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('should convert non-string messages to strings', () => {
|
|
273
|
+
messageCollector.startCollecting('test');
|
|
274
|
+
Logger.info({ nested: 'object' });
|
|
275
|
+
Logger.info(123);
|
|
276
|
+
Logger.info(null);
|
|
277
|
+
|
|
278
|
+
const messages = messageCollector.getMessages();
|
|
279
|
+
expect(typeof messages[0].message).toBe('string');
|
|
280
|
+
expect(typeof messages[1].message).toBe('string');
|
|
281
|
+
expect(typeof messages[2].message).toBe('string');
|
|
282
|
+
|
|
283
|
+
messageCollector.stopCollecting();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/* Currently empty */
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Markdown-it Link Enhancement Module
|
|
5
|
-
*
|
|
6
|
-
* This module enhances link rendering by adding path-based attributes to anchor tags.
|
|
7
|
-
* These attributes can be used for CSS styling or JavaScript behavior based on the
|
|
8
|
-
* link's destination (domain, path segments, etc.).
|
|
9
|
-
*
|
|
10
|
-
* For example, a link to "https://example.com/docs/api" would get attributes like:
|
|
11
|
-
* - path-0="example.com"
|
|
12
|
-
* - path-1="docs"
|
|
13
|
-
* - path-2="api"
|
|
14
|
-
*
|
|
15
|
-
* This allows for targeted styling of links based on their destination.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Regular expression to extract domains and path segments from URLs
|
|
20
|
-
*
|
|
21
|
-
* This regex has two capture groups:
|
|
22
|
-
* - Group 1: Domain from http(s):// URLs (e.g., "example.com" from "https://example.com/path")
|
|
23
|
-
* - Group 2: Path segments from relative URLs (e.g., "docs" from "/docs/page")
|
|
24
|
-
*
|
|
25
|
-
* The 'g' flag enables global matching to find all segments in a URL.
|
|
26
|
-
*/
|
|
27
|
-
const pathSegmentRegex = /(?:http[s]*:\/\/([^\/]*)|(?:\/([^\/?]*)))/g;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Applies link enhancements to a markdown-it instance
|
|
31
|
-
*
|
|
32
|
-
* @param {Object} md - The markdown-it instance to enhance
|
|
33
|
-
*
|
|
34
|
-
* This function overrides the default link_open and link_close renderers
|
|
35
|
-
* to add path-based attributes and special handling for auto-detected links.
|
|
36
|
-
*/
|
|
37
|
-
function applyLinkEnhancements(md) {
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Custom link_open renderer that adds path attributes
|
|
41
|
-
*
|
|
42
|
-
* @param {Array} tokens - Array of all tokens being processed
|
|
43
|
-
* @param {Number} idx - Index of the current link_open token
|
|
44
|
-
* @param {Object} options - Markdown-it options
|
|
45
|
-
* @param {Object} env - Environment/context object
|
|
46
|
-
* @param {Object} renderer - The renderer instance
|
|
47
|
-
* @returns {String} HTML string for the opening anchor tag with path attributes
|
|
48
|
-
*/
|
|
49
|
-
md.renderer.rules.link_open = function (tokens, idx, options, env, renderer) {
|
|
50
|
-
let token = tokens[idx];
|
|
51
|
-
|
|
52
|
-
// Process all attributes of the link token
|
|
53
|
-
let attrs = token.attrs.reduce((str, attr) => {
|
|
54
|
-
let name = attr[0]; // Attribute name (e.g., 'href', 'title')
|
|
55
|
-
let value = attr[1]; // Attribute value (e.g., 'https://example.com')
|
|
56
|
-
|
|
57
|
-
// Special processing for href attributes to add path information
|
|
58
|
-
if (name === 'href') {
|
|
59
|
-
let index = 0;
|
|
60
|
-
|
|
61
|
-
// Extract domain and path segments using the regex
|
|
62
|
-
value.replace(pathSegmentRegex, (match, domain, pathSegment) => {
|
|
63
|
-
// Add path-N attributes for each segment found
|
|
64
|
-
// domain OR pathSegment will be defined (not both, due to regex groups)
|
|
65
|
-
str += `path-${index++}="${domain || pathSegment}" `;
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Add the original attribute to the string
|
|
70
|
-
str += `${name}="${value}" `;
|
|
71
|
-
return str;
|
|
72
|
-
}, '');
|
|
73
|
-
|
|
74
|
-
// Create the opening anchor tag with all attributes
|
|
75
|
-
let anchor = `<a ${attrs}>`;
|
|
76
|
-
|
|
77
|
-
// Special handling for auto-detected links (linkify plugin)
|
|
78
|
-
// These get an extra <span> wrapper for styling purposes
|
|
79
|
-
return token.markup === 'linkify' ? anchor + '<span>' : anchor;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Custom link_close renderer
|
|
84
|
-
*
|
|
85
|
-
* @param {Array} tokens - Array of all tokens being processed
|
|
86
|
-
* @param {Number} idx - Index of the current link_close token
|
|
87
|
-
* @param {Object} options - Markdown-it options
|
|
88
|
-
* @param {Object} env - Environment/context object
|
|
89
|
-
* @param {Object} renderer - The renderer instance
|
|
90
|
-
* @returns {String} HTML string for the closing anchor tag
|
|
91
|
-
*/
|
|
92
|
-
md.renderer.rules.link_close = function (tokens, idx, options, env, renderer) {
|
|
93
|
-
// Close the extra span for linkify links, or just close the anchor
|
|
94
|
-
return tokens[idx].markup === 'linkify' ? '</span></a>' : '</a>';
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
module.exports = applyLinkEnhancements;
|