spec-up-t 1.5.0 → 1.6.0-beta.2

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.
@@ -14,7 +14,11 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
14
14
 
15
15
  allXTrefs.xtrefs = allXTrefs.xtrefs.filter(xtref => {
16
16
  if (!xtref.owner || !xtref.repo || !xtref.repoUrl) {
17
- Logger.error(`Removing incomplete reference: ${xtref.externalSpec}, ${xtref.term}`);
17
+ Logger.error(`Removing incomplete reference: ${xtref.externalSpec}, ${xtref.term}`, {
18
+ context: 'External reference is missing required repository information',
19
+ hint: 'Verify external_specs configuration in specs.json includes github_repo_url for each entry',
20
+ details: `Missing: ${!xtref.owner ? 'owner' : ''} ${!xtref.repo ? 'repo' : ''} ${!xtref.repoUrl ? 'repoUrl' : ''}`
21
+ });
18
22
  return false;
19
23
  }
20
24
  return true;
@@ -51,7 +55,11 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
51
55
  );
52
56
 
53
57
  if (!allTermsData) {
54
- Logger.error(`Could not fetch terms from repository ${repoKey} (${repoUrl})`);
58
+ Logger.error(`Could not fetch terms from repository ${repoKey}`, {
59
+ context: `Failed to retrieve terminology from ${repoUrl}`,
60
+ hint: 'Ensure the repository exists, is accessible, and has published its spec. If it\'s private, set GITHUB_PAT environment variable',
61
+ details: `Repository: ${repoUrl}. Check if GitHub Pages is enabled or if the repo has a valid specs.json`
62
+ });
55
63
  repoGroup.xtrefs.forEach(xtref => {
56
64
  xtref.commitHash = 'not found';
57
65
  xtref.content = 'This term was not found in the external repository.';
@@ -81,26 +89,34 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
81
89
 
82
90
  if (isExternalTref && isTref) {
83
91
  // Build a readable list of source files for the error message
84
- const sourceFilesList = xtref.sourceFile
85
- ? xtref.sourceFile
92
+ const sourceFilesList = xtref.sourceFile
93
+ ? xtref.sourceFile
86
94
  : (xtref.sourceFiles || []).map(sf => sf.file).join(', ');
87
-
95
+
88
96
  // Construct the external repository URL
89
97
  const externalRepoUrl = xtref.ghPageUrl || xtref.repoUrl || `https://github.com/${xtref.owner}/${xtref.repo}`;
90
-
91
- Logger.error(`Origin: ${sourceFilesList} 👉 NESTED TREF DETECTED: Term "${xtref.term}" in ${xtref.externalSpec} is itself a tref (transcluded from another spec). This creates a chain of external references.`);
98
+
99
+ Logger.error(`NESTED TREF DETECTED: Term "${xtref.term}" in ${xtref.externalSpec}`, {
100
+ context: `Origin: ${sourceFilesList} - This term is itself a tref transcluded from another spec`,
101
+ hint: 'Avoid chaining trefs (tref → tref). Either reference the original source spec directly, or define the term locally',
102
+ details: `Repository: ${externalRepoUrl}. Nested trefs create complex dependency chains`
103
+ });
92
104
  }
93
105
 
94
106
  if (isExternalTref && isXref) {
95
107
  // Build a readable list of source files for the warning message
96
- const sourceFilesList = xtref.sourceFile
97
- ? xtref.sourceFile
108
+ const sourceFilesList = xtref.sourceFile
109
+ ? xtref.sourceFile
98
110
  : (xtref.sourceFiles || []).map(sf => sf.file).join(', ');
99
-
111
+
100
112
  // Construct the external repository URL
101
113
  const externalRepoUrl = xtref.ghPageUrl || xtref.repoUrl || `https://github.com/${xtref.owner}/${xtref.repo}`;
102
-
103
- Logger.error(`Origin: ${sourceFilesList} 👉 NESTED XREF DETECTED: Term "${xtref.term}" in ${xtref.externalSpec} is itself a tref (transcluded from another spec). This xref points to a term that is already transcluded from elsewhere, creating a chain of external references. (${externalRepoUrl})`);
114
+
115
+ Logger.error(`NESTED XREF DETECTED: Term "${xtref.term}" in ${xtref.externalSpec}`, {
116
+ context: `Origin: ${sourceFilesList} - This xref points to a term that is already transcluded elsewhere`,
117
+ hint: 'Use [[xref]] only for terms directly defined in the external spec. For nested refs, reference the original source',
118
+ details: `Repository: ${externalRepoUrl}. This creates a chain of external references`
119
+ });
104
120
  }
105
121
 
106
122
  Logger.success(`Match found for term: ${xtref.term} in ${xtref.externalSpec}`);
@@ -108,7 +124,7 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
108
124
  xtref.commitHash = 'not found';
109
125
  xtref.content = 'This term was not found in the external repository.';
110
126
  xtref.avatarUrl = null;
111
-
127
+
112
128
  // Build a readable list of source files for the error message.
113
129
  // Two possible data structures exist:
114
130
  // 1. xtref.sourceFile is a STRING like "primitive.md"
@@ -119,15 +135,19 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
119
135
  // - Otherwise → extract file names from the sourceFiles array:
120
136
  // - .map(sf => sf.file) extracts just the filename from each object
121
137
  // - .join(', ') combines them into a comma-separated string
122
- const sourceFilesList = xtref.sourceFile
123
- ? xtref.sourceFile
138
+ const sourceFilesList = xtref.sourceFile
139
+ ? xtref.sourceFile
124
140
  : (xtref.sourceFiles || []).map(sf => sf.file).join(', ');
125
141
 
126
142
  // Prefer an explicit repo URL if provided on the xtref, otherwise
127
143
  // build a standard GitHub URL from the owner/repo.
128
144
  const githubUrl = xtref.repoUrl || `https://github.com/${repoKey}`;
129
145
 
130
- Logger.error(`Origin: ${sourceFilesList} 👉 No match found for term: ${xtref.term} in ${xtref.externalSpec} (${githubUrl})`);
146
+ Logger.error(`No match found for term: ${xtref.term} in ${xtref.externalSpec}`, {
147
+ context: `Origin: ${sourceFilesList} - Term not found in external repository`,
148
+ hint: 'Check if the term exists in the external spec. Verify spelling, ensure the external spec has published, and confirm the term is in their terminology section',
149
+ details: `Repository: ${githubUrl}. The term may have been renamed or removed`
150
+ });
131
151
  }
132
152
  }
133
153
 
@@ -138,10 +158,14 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
138
158
  const allXTrefsStr = JSON.stringify(allXTrefs, null, 2);
139
159
  fs.writeFileSync(outputPathJSON, allXTrefsStr, 'utf8');
140
160
  const jsPayload = `const allXTrefs = ${allXTrefsStr};`;
141
- fs.writeFileSync(outputPathJS, jsPayload, 'utf8');
142
- fs.writeFileSync(outputPathJSTimeStamped, jsPayload, 'utf8');
161
+ fs.writeFileSync(outputPathJS, jsPayload, 'utf8');
162
+ fs.writeFileSync(outputPathJSTimeStamped, jsPayload, 'utf8');
143
163
  } catch (error) {
144
- Logger.error('An error occurred:', error);
164
+ Logger.error('An error occurred during xtrefs processing', {
165
+ context: 'Failed while processing external references and fetching terms',
166
+ hint: 'Check your internet connection, verify GITHUB_PAT is set if needed, and ensure specs.json external_specs configuration is correct',
167
+ details: error.message
168
+ });
145
169
  }
146
170
  }
147
171
 
@@ -15,30 +15,127 @@ class Logger {
15
15
  static success(message, ...args) {
16
16
  console.log(chalk.green('✅'), chalk.green(message), ...args);
17
17
  messageCollector.addMessage('success', message, args);
18
+
19
+ console.log(); // Extra newline for readability
18
20
  }
19
21
 
20
22
  /**
21
23
  * Error messages - red with X mark
24
+ *
25
+ * Enhanced error logging with optional context and actionable guidance.
26
+ *
27
+ * @param {string} message - The main error message
28
+ * @param {...any} args - Additional arguments. Can include:
29
+ * - Regular values (strings, numbers, objects) for message formatting
30
+ * - An options object (if last arg is object with 'hint', 'context', or 'details' keys):
31
+ * - hint: Actionable suggestion for fixing the error
32
+ * - context: Additional context about where/why the error occurred
33
+ * - details: Technical details or error objects
34
+ *
35
+ * @example
36
+ * Logger.error('File not found', {
37
+ * context: 'specs.json',
38
+ * hint: 'Create a specs.json file in your project root',
39
+ * details: error.message
40
+ * });
22
41
  */
23
42
  static error(message, ...args) {
24
- console.log(chalk.red('❌'), chalk.red(message), ...args);
25
- messageCollector.addMessage('error', message, args);
43
+ // Extract options object if present (last arg with special keys)
44
+ const lastArg = args[args.length - 1];
45
+ const isOptionsObject = lastArg && typeof lastArg === 'object' &&
46
+ (lastArg.hint || lastArg.context || lastArg.details);
47
+
48
+ const options = isOptionsObject ? args.pop() : {};
49
+ const regularArgs = args;
50
+
51
+ // Display main error message
52
+ console.log(chalk.red('❌'), chalk.red(message), ...regularArgs);
53
+
54
+ // Display context if provided - helps identify the scope of the error
55
+ if (options.context) {
56
+ console.log(chalk.red(' Context:'), chalk.gray(options.context));
57
+ }
58
+
59
+ // Display technical details if provided - useful for debugging
60
+ if (options.details) {
61
+ const detailsStr = typeof options.details === 'object'
62
+ ? JSON.stringify(options.details, null, 2)
63
+ : String(options.details);
64
+ console.log(chalk.red(' Details:'), chalk.gray(detailsStr));
65
+ }
66
+
67
+ // Display actionable hint if provided - most valuable for authors
68
+ if (options.hint) {
69
+ console.log(chalk.yellow(' 💡 How to fix:'), chalk.yellow(options.hint));
70
+ }
71
+
72
+ // Collect message with all context for healthcheck/JSON output
73
+ messageCollector.addMessage('error', message, [...regularArgs, options]);
74
+
75
+ console.log(); // Extra newline for readability
26
76
  }
27
77
 
28
78
  /**
29
79
  * Warning messages - yellow with warning symbol
80
+ *
81
+ * Enhanced warning logging with optional context and actionable guidance.
82
+ *
83
+ * @param {string} message - The main warning message
84
+ * @param {...any} args - Additional arguments. Can include:
85
+ * - Regular values (strings, numbers, objects) for message formatting
86
+ * - An options object (if last arg is object with 'hint', 'context', or 'details' keys):
87
+ * - hint: Actionable suggestion for addressing the warning
88
+ * - context: Additional context about where/why the warning occurred
89
+ * - details: Technical details or related information
90
+ *
91
+ * @example
92
+ * Logger.warn('Using fallback configuration', {
93
+ * context: 'specs.json missing optional field',
94
+ * hint: 'Add "output_path" to specs.json for better control',
95
+ * details: 'Using default: ./docs'
96
+ * });
30
97
  */
31
98
  static warn(message, ...args) {
32
- console.log(chalk.yellow('🟡'), chalk.yellow(message), ...args);
33
- messageCollector.addMessage('warn', message, args);
99
+ // Extract options object if present (last arg with special keys)
100
+ const lastArg = args[args.length - 1];
101
+ const isOptionsObject = lastArg && typeof lastArg === 'object' &&
102
+ (lastArg.hint || lastArg.context || lastArg.details);
103
+
104
+ const options = isOptionsObject ? args.pop() : {};
105
+ const regularArgs = args;
106
+
107
+ // Display main warning message
108
+ console.log(chalk.keyword('orange')('❗'), chalk.yellow(message), ...regularArgs);
109
+
110
+ // Display context if provided - helps identify the scope of the warning
111
+ if (options.context) {
112
+ console.log(chalk.yellow(' Context:'), chalk.gray(options.context));
113
+ }
114
+
115
+ // Display technical details if provided - useful for understanding the situation
116
+ if (options.details) {
117
+ const detailsStr = typeof options.details === 'object'
118
+ ? JSON.stringify(options.details, null, 2)
119
+ : String(options.details);
120
+ console.log(chalk.yellow(' Details:'), chalk.gray(detailsStr));
121
+ }
122
+
123
+ // Display actionable hint if provided - helps authors improve their spec
124
+ if (options.hint) {
125
+ console.log(chalk.cyan(' 💡 Suggestion:'), chalk.cyan(options.hint));
126
+ }
127
+
128
+ // Collect message with all context for healthcheck/JSON output
129
+ messageCollector.addMessage('warn', message, [...regularArgs, options]);
130
+
131
+ console.log(); // Extra newline for readability
34
132
  }
35
133
 
36
- /**
37
- * Info messages - blue with info symbol
38
- */
39
134
  static info(message, ...args) {
40
- console.log(chalk.blue('📒'), chalk.blue(message), ...args);
135
+ console.log(chalk.blue('📋'), chalk.blue(message), ...args);
41
136
  messageCollector.addMessage('info', message, args);
137
+
138
+ console.log(); // Extra newline for readability
42
139
  }
43
140
 
44
141
  /**
@@ -47,6 +144,8 @@ class Logger {
47
144
  static process(message, ...args) {
48
145
  console.log(chalk.cyan('🔄'), chalk.cyan(message), ...args);
49
146
  messageCollector.addMessage('process', message, args);
147
+
148
+ console.log(); // Extra newline for readability
50
149
  }
51
150
 
52
151
  /**
@@ -55,14 +154,18 @@ class Logger {
55
154
  static debug(message, ...args) {
56
155
  console.log(chalk.gray('🔍'), chalk.gray(message), ...args);
57
156
  messageCollector.addMessage('debug', message, args);
157
+
158
+ console.log(); // Extra newline for readability
58
159
  }
59
160
 
60
161
  /**
61
162
  * Highlight important data - magenta
62
163
  */
63
164
  static highlight(message, ...args) {
64
- console.log(chalk.blue(''), chalk.blue(message), ...args);
165
+ console.log(chalk.blue('📋'), chalk.blue(message), ...args);
65
166
  messageCollector.addMessage('highlight', message, args);
167
+
168
+ console.log(); // Extra newline for readability
66
169
  }
67
170
 
68
171
  /**
@@ -71,6 +174,8 @@ class Logger {
71
174
  static separator() {
72
175
  console.log(chalk.gray('═'.repeat(60)));
73
176
  messageCollector.addMessage('separator', '═'.repeat(60), []);
177
+
178
+ console.log(); // Extra newline for readability
74
179
  }
75
180
 
76
181
  /**
@@ -82,6 +187,8 @@ class Logger {
82
187
  const progressMessage = `[${bar}] ${percentage}% ${message}`;
83
188
  console.log(chalk.cyan(`📊 ${progressMessage}`));
84
189
  messageCollector.addMessage('progress', progressMessage, [current, total]);
190
+
191
+ console.log(); // Extra newline for readability
85
192
  }
86
193
  }
87
194
 
@@ -109,12 +109,14 @@ const templateTags = {
109
109
  * Pattern breakdown:
110
110
  * - ^def$ → Exact match for "def" (definition)
111
111
  * - ^ref$ → Exact match for "ref" (reference)
112
+ * - ^iref$ → Exact match for "iref" (inline reference - copies existing term)
112
113
  * - ^xref → Starts with "xref" (external reference)
113
114
  * - ^tref → Starts with "tref" (typed reference)
114
115
  *
115
116
  * Examples:
116
117
  * - "def" → matches
117
118
  * - "ref" → matches
119
+ * - "iref" → matches
118
120
  * - "xref" → matches
119
121
  * - "tref" → matches
120
122
  * - "xref:spec,term" → matches (starts with xref)
@@ -122,7 +124,7 @@ const templateTags = {
122
124
  * Flags:
123
125
  * - i: case-insensitive matching
124
126
  */
125
- terminology: /^def$|^ref$|^xref|^tref$/i
127
+ terminology: /^def$|^ref$|^iref$|^xref|^tref$/i
126
128
  };
127
129
 
128
130
  /**
@@ -464,6 +466,28 @@ const utils = {
464
466
  createGitignoreRegex: function(globPattern) {
465
467
  const pattern = '^' + globPattern.replace(/\*/g, '.*').replace(/\//g, '\\/') + '$';
466
468
  return new RegExp(pattern);
469
+ },
470
+
471
+ /**
472
+ * Sanitizes a string for use as a valid CSS selector ID
473
+ * Removes special characters that would break querySelector while preserving readability
474
+ *
475
+ * Keeps: letters, numbers, hyphens, underscores, colons
476
+ * Removes: parentheses, brackets, slashes, and other special characters
477
+ *
478
+ * @param {string} str - String to sanitize
479
+ * @returns {string} Sanitized string safe for use in CSS selectors
480
+ *
481
+ * Example:
482
+ * sanitizeTermId("authentic chained data container (ACDC)")
483
+ * → "authentic-chained-data-container-acdc"
484
+ * sanitizeTermId("term/with/slashes") → "term-with-slashes"
485
+ */
486
+ sanitizeTermId: function(str) {
487
+ return str
488
+ .replace(/[()[\]{}\/\\]/g, '-') // Replace special chars with hyphens
489
+ .replace(/-+/g, '-') // Collapse multiple hyphens into one
490
+ .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
467
491
  }
468
492
  };
469
493
 
@@ -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
+ });
@@ -1 +0,0 @@
1
- /* Currently empty */