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.
- package/assets/compiled/body.js +1 -0
- package/assets/compiled/head.css +3 -3
- package/assets/css/index.css +5 -4
- package/assets/css/refs.css +33 -0
- package/assets/css/terms-and-definitions.css +88 -0
- package/assets/js/insert-irefs.js +214 -0
- package/config/asset-map.json +2 -1
- package/package.json +1 -1
- package/src/create-docx.js +13 -6
- package/src/create-pdf.js +22 -18
- package/src/init.js +7 -3
- package/src/install-from-boilerplate/boilerplate/spec/terms-and-definitions-intro.md +1 -1
- package/src/parsers/template-tag-parser.js +32 -4
- package/src/pipeline/references/collect-external-references.js +41 -14
- package/src/pipeline/references/external-references-service.js +60 -24
- package/src/pipeline/references/process-xtrefs-data.js +43 -19
- package/src/utils/logger.js +116 -9
- package/src/utils/regex-patterns.js +25 -1
- package/test/logger.test.js +290 -0
- package/assets/css/insert-trefs.css +0 -1
- package/src/utils/LOGGER.md +0 -81
|
@@ -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}
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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
|
-
|
|
142
|
-
|
|
161
|
+
fs.writeFileSync(outputPathJS, jsPayload, 'utf8');
|
|
162
|
+
fs.writeFileSync(outputPathJSTimeStamped, jsPayload, 'utf8');
|
|
143
163
|
} catch (error) {
|
|
144
|
-
Logger.error('An error occurred
|
|
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
|
|
package/src/utils/logger.js
CHANGED
|
@@ -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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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('
|
|
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('
|
|
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 */
|