color-name-list 12.2.0 → 13.0.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.
- package/README.md +2 -2
- package/changes.svg +3 -3
- package/dist/colornames.bestof.csv +5 -4
- package/dist/colornames.bestof.esm.js +1 -1
- package/dist/colornames.bestof.esm.mjs +1 -1
- package/dist/colornames.bestof.html +1 -1
- package/dist/colornames.bestof.json +1 -1
- package/dist/colornames.bestof.min.json +1 -1
- package/dist/colornames.bestof.scss +1 -1
- package/dist/colornames.bestof.umd.js +1 -1
- package/dist/colornames.bestof.xml +17 -13
- package/dist/colornames.bestof.yaml +13 -10
- package/dist/colornames.csv +11 -38
- package/dist/colornames.esm.js +1 -1
- package/dist/colornames.esm.mjs +1 -1
- package/dist/colornames.html +1 -1
- package/dist/colornames.json +1 -1
- package/dist/colornames.min.json +1 -1
- package/dist/colornames.scss +1 -1
- package/dist/colornames.short.csv +3 -2
- package/dist/colornames.short.esm.js +1 -1
- package/dist/colornames.short.esm.mjs +1 -1
- package/dist/colornames.short.html +1 -1
- package/dist/colornames.short.json +1 -1
- package/dist/colornames.short.min.json +1 -1
- package/dist/colornames.short.scss +1 -1
- package/dist/colornames.short.umd.js +1 -1
- package/dist/colornames.short.xml +9 -5
- package/dist/colornames.short.yaml +7 -4
- package/dist/colornames.umd.js +1 -1
- package/dist/colornames.xml +17 -125
- package/dist/colornames.yaml +15 -96
- package/dist/history.json +1 -1
- package/package.json +1 -1
- package/scripts/build.js +33 -161
- package/scripts/lib.js +38 -6
- package/scripts/sortSrc.js +4 -5
- package/src/colornames.csv +13 -40
- package/tests/_utils/report.js +76 -0
- package/tests/csv-test-data.js +108 -0
- package/tests/duplicate-allowlist.json +28 -1
- package/tests/duplicate-plurals-allowlist.json +4 -1
- package/tests/duplicates.test.js +134 -40
- package/tests/formats.test.js +9 -8
- package/tests/imports.test.js +12 -14
- package/tests/sorting.test.js +10 -12
- package/tests/validations.test.js +219 -0
package/package.json
CHANGED
package/scripts/build.js
CHANGED
|
@@ -1,22 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { parseCSVString,
|
|
3
|
+
import { parseCSVString, objArrToString } from './lib.js';
|
|
4
4
|
import { exec } from 'child_process';
|
|
5
5
|
|
|
6
|
-
const args = process.argv;
|
|
7
|
-
// treat --testonly / --testOnly the same
|
|
8
|
-
const isTestRun = args.some((arg) => arg.toLowerCase() === '--testonly');
|
|
9
|
-
|
|
10
|
-
// only hex colors with 6 values
|
|
11
|
-
const hexColorValidation = /^#[0-9a-f]{6}$/;
|
|
12
|
-
const errors = [];
|
|
13
|
-
|
|
14
|
-
// spaces regex
|
|
15
|
-
const spacesValidation = /^\s+|\s{2,}|\s$/;
|
|
16
|
-
|
|
17
|
-
// quote regex
|
|
18
|
-
const quoteValidation = /"|'|`/;
|
|
19
|
-
|
|
20
6
|
// setting
|
|
21
7
|
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
22
8
|
const baseFolder = __dirname + '/../';
|
|
@@ -29,7 +15,6 @@ const readmeFileName = 'README.md';
|
|
|
29
15
|
const fileNameShortPostfix = '.short';
|
|
30
16
|
const maxShortNameLength = 12;
|
|
31
17
|
|
|
32
|
-
const sortBy = 'name';
|
|
33
18
|
const csvKeys = ['name', 'hex'];
|
|
34
19
|
const bestOfKey = 'good name';
|
|
35
20
|
|
|
@@ -40,63 +25,6 @@ const src = fs
|
|
|
40
25
|
|
|
41
26
|
const colorsSrc = parseCSVString(src);
|
|
42
27
|
|
|
43
|
-
// sort by sorting criteria
|
|
44
|
-
colorsSrc.entries.sort((a, b) => {
|
|
45
|
-
return a[sortBy].localeCompare(b[sortBy]);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
csvKeys.forEach((key) => {
|
|
49
|
-
// find duplicates
|
|
50
|
-
const dupes = findDuplicates(colorsSrc.values[key]);
|
|
51
|
-
dupes.forEach((dupe) => {
|
|
52
|
-
log(key, dupe, `found a double ${key}`);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// loop hex values for validations
|
|
57
|
-
colorsSrc.values['hex'].forEach((hex) => {
|
|
58
|
-
// validate HEX values
|
|
59
|
-
if (!hexColorValidation.test(hex)) {
|
|
60
|
-
log(
|
|
61
|
-
'hex',
|
|
62
|
-
hex,
|
|
63
|
-
`${hex} is not a valid hex value. (Or to short, we avoid using the hex shorthands, no capital letters)`
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// loop names
|
|
69
|
-
colorsSrc.values['name'].forEach((name) => {
|
|
70
|
-
// check for spaces
|
|
71
|
-
if (spacesValidation.test(name)) {
|
|
72
|
-
log('name', name, `${name} found either a leading or trailing space (or both)`);
|
|
73
|
-
}
|
|
74
|
-
if (quoteValidation.test(name)) {
|
|
75
|
-
log('name', name, `${name} found a quote character, should be an apostrophe ’`);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// loop good name markers
|
|
80
|
-
colorsSrc.values[bestOfKey].forEach((str) => {
|
|
81
|
-
// check for spaces
|
|
82
|
-
if (spacesValidation.test(str)) {
|
|
83
|
-
// Use the actual CSV key so we can resolve the offending entries (names)
|
|
84
|
-
log(bestOfKey, str, `${str} found either a leading or trailing space (or both)`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (!(str == 'x' || str == '')) {
|
|
88
|
-
// Use the actual CSV key so we can resolve the offending entries (names)
|
|
89
|
-
log(bestOfKey, str, `${str} must be a lowercase "x" character or empty`);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
showLog();
|
|
94
|
-
// In test mode we still perform the build so tests can import dist artifacts,
|
|
95
|
-
// but we avoid mutating repository files like README.md or generating the SVG.
|
|
96
|
-
if (isTestRun) {
|
|
97
|
-
console.log('Test mode: skipping README & SVG generation.');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
28
|
// creates JS related files
|
|
101
29
|
const JSONExportString = JSON.stringify(
|
|
102
30
|
[...colorsSrc.entries].map(
|
|
@@ -339,91 +267,37 @@ for (const outputFormat in outputFormats) {
|
|
|
339
267
|
}
|
|
340
268
|
}
|
|
341
269
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
.
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
)
|
|
360
|
-
.
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
),
|
|
374
|
-
'utf8'
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* outputs the collected logs
|
|
380
|
-
*/
|
|
381
|
-
function showLog() {
|
|
382
|
-
let errorLevel = 0;
|
|
383
|
-
let totalErrors = 0;
|
|
384
|
-
errors.forEach((error, i) => {
|
|
385
|
-
totalErrors = i + 1;
|
|
386
|
-
errorLevel = error.errorLevel || errorLevel;
|
|
387
|
-
console.log(`${error.errorLevel ? '⛔' : '⚠'} ${error.message}`);
|
|
388
|
-
if (Array.isArray(error.entries) && error.entries.length) {
|
|
389
|
-
// Print a concise list of offending names for quick scanning
|
|
390
|
-
const nameList = error.entries
|
|
391
|
-
.map((e) => (e && e.name ? `${e.name}${e.hex ? ` (${e.hex})` : ''}` : ''))
|
|
392
|
-
.filter(Boolean)
|
|
393
|
-
.join(', ');
|
|
394
|
-
if (nameList) {
|
|
395
|
-
console.log(`Offending name(s): ${nameList}`);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
// Keep the JSON dump for full context
|
|
399
|
-
console.log(JSON.stringify(error.entries));
|
|
400
|
-
console.log('*-------------------------*');
|
|
401
|
-
});
|
|
402
|
-
if (errorLevel) {
|
|
403
|
-
throw new Error(`⚠ failed because of the ${totalErrors} error${totalErrors > 1 ? 's' : ''} above ⚠`);
|
|
404
|
-
}
|
|
405
|
-
return totalErrors;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* logs errors and warning
|
|
410
|
-
* @param {string} key key to look for in input
|
|
411
|
-
* @param {string} value value to look for
|
|
412
|
-
* @param {string} message error message
|
|
413
|
-
* @param {Number} errorLevel if any error is set to 1, the program will exit
|
|
414
|
-
*/
|
|
415
|
-
function log(key, value, message, errorLevel = 1) {
|
|
416
|
-
const error = {};
|
|
417
|
-
// looks for the original item that caused the error
|
|
418
|
-
error.entries = colorsSrc.entries.filter((entry) => {
|
|
419
|
-
return entry[key] === value;
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
error.message = message;
|
|
423
|
-
error.errorLevel = errorLevel;
|
|
424
|
-
|
|
425
|
-
errors.push(error);
|
|
426
|
-
}
|
|
270
|
+
// updates the color count in readme file
|
|
271
|
+
const readme = fs.readFileSync(path.normalize(`${baseFolder}${readmeFileName}`), 'utf8').toString();
|
|
272
|
+
fs.writeFileSync(
|
|
273
|
+
path.normalize(`${baseFolder}${readmeFileName}`),
|
|
274
|
+
readme
|
|
275
|
+
.replace(
|
|
276
|
+
// update color count in text
|
|
277
|
+
/__\d+__/g,
|
|
278
|
+
`__${colorsSrc.entries.length}__`
|
|
279
|
+
)
|
|
280
|
+
.replace(
|
|
281
|
+
// update color count in badge
|
|
282
|
+
/\d+-colors-orange/,
|
|
283
|
+
`${colorsSrc.entries.length}-colors-orange`
|
|
284
|
+
)
|
|
285
|
+
.replace(
|
|
286
|
+
// update color count in percentage
|
|
287
|
+
/__\d+(\.\d+)?%__/,
|
|
288
|
+
`__${((colorsSrc.entries.length / (256 * 256 * 256)) * 100).toFixed(2)}%__`
|
|
289
|
+
)
|
|
290
|
+
.replace(
|
|
291
|
+
// update file size
|
|
292
|
+
/\d+(\.\d+)? MB\)__/, // no global to only hit first occurrence
|
|
293
|
+
`${(
|
|
294
|
+
fs.statSync(path.normalize(`${baseFolder}${folderDist}${fileNameSrc}.json`)).size /
|
|
295
|
+
1024 /
|
|
296
|
+
1024
|
|
297
|
+
).toFixed(2)} MB)__`
|
|
298
|
+
),
|
|
299
|
+
'utf8'
|
|
300
|
+
);
|
|
427
301
|
|
|
428
302
|
// gets SVG template
|
|
429
303
|
const svgTpl = fs.readFileSync(path.normalize(__dirname + '/changes.svg.tpl'), 'utf8').toString();
|
|
@@ -497,6 +371,4 @@ function diffSVG() {
|
|
|
497
371
|
);
|
|
498
372
|
}
|
|
499
373
|
|
|
500
|
-
|
|
501
|
-
diffSVG();
|
|
502
|
-
}
|
|
374
|
+
diffSVG();
|
package/scripts/lib.js
CHANGED
|
@@ -131,19 +131,50 @@ export const normalizeNameForDuplicates = (name) => {
|
|
|
131
131
|
* @returns {Array<{norm:string, entries:Array<{name:string, lineNumber?:number}>}>}
|
|
132
132
|
*/
|
|
133
133
|
export const findNearDuplicateNameConflicts = (items, options = {}) => {
|
|
134
|
-
const {
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
const {
|
|
135
|
+
allowlist = [],
|
|
136
|
+
foldPlurals = false,
|
|
137
|
+
pluralAllowlist = [],
|
|
138
|
+
foldStopwords = false,
|
|
139
|
+
stopwords = [],
|
|
140
|
+
} = options;
|
|
141
|
+
|
|
142
|
+
// Precompute stopword set (normalized to lowercase ASCII) when folding is enabled
|
|
143
|
+
const stopSet = foldStopwords
|
|
144
|
+
? new Set(
|
|
145
|
+
(Array.isArray(stopwords) ? stopwords : [])
|
|
146
|
+
.filter((w) => typeof w === 'string' && w.trim().length)
|
|
147
|
+
.map((w) =>
|
|
148
|
+
String(w)
|
|
149
|
+
.toLowerCase()
|
|
150
|
+
.normalize('NFD')
|
|
151
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
: null;
|
|
155
|
+
|
|
156
|
+
// Helper: normalize name using current options (stopword folding if enabled)
|
|
157
|
+
const normalizeForOptions = (name) => {
|
|
158
|
+
const base = String(name)
|
|
159
|
+
.toLowerCase()
|
|
160
|
+
.normalize('NFD')
|
|
161
|
+
.replace(/[\u0300-\u036f]/g, '');
|
|
162
|
+
const tokens = base.match(/[a-z0-9]+/g) || [];
|
|
163
|
+
const filtered = foldStopwords && stopSet && stopSet.size ? tokens.filter((t) => !stopSet.has(t)) : tokens;
|
|
164
|
+
return filtered.length ? filtered.join('') : normalizeNameForDuplicates(name);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Normalize allowlist entries using the same normalization function.
|
|
137
168
|
const allowSet = new Set(
|
|
138
169
|
(Array.isArray(allowlist) ? allowlist : [])
|
|
139
170
|
.filter((v) => typeof v === 'string' && v.trim().length)
|
|
140
|
-
.map((v) =>
|
|
171
|
+
.map((v) => normalizeForOptions(String(v)))
|
|
141
172
|
);
|
|
142
173
|
|
|
143
174
|
const byNorm = new Map();
|
|
144
175
|
for (const item of items) {
|
|
145
176
|
if (!item || typeof item.name !== 'string') continue;
|
|
146
|
-
const norm =
|
|
177
|
+
const norm = normalizeForOptions(item.name);
|
|
147
178
|
if (!byNorm.has(norm)) byNorm.set(norm, []);
|
|
148
179
|
byNorm.get(norm).push({ name: item.name, lineNumber: item.lineNumber });
|
|
149
180
|
}
|
|
@@ -153,7 +184,8 @@ export const findNearDuplicateNameConflicts = (items, options = {}) => {
|
|
|
153
184
|
const pluralAllowSet = new Set(
|
|
154
185
|
(Array.isArray(pluralAllowlist) ? pluralAllowlist : [])
|
|
155
186
|
.filter((v) => typeof v === 'string' && v.trim().length)
|
|
156
|
-
|
|
187
|
+
// Use the same normalization as used for keys (respects stopword folding)
|
|
188
|
+
.map((v) => normalizeForOptions(String(v)))
|
|
157
189
|
);
|
|
158
190
|
// We iterate over a snapshot of keys because we'll mutate the map.
|
|
159
191
|
for (const key of Array.from(byNorm.keys())) {
|
package/scripts/sortSrc.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Script to sort the colornames.csv file alphabetically by name
|
|
5
|
-
* This helps maintain order when new colors are added to the list
|
|
6
|
-
*/
|
|
4
|
+
* Script to sort the colornames.csv file alphabetically by name
|
|
5
|
+
* This helps maintain order when new colors are added to the list
|
|
6
|
+
*/
|
|
7
7
|
|
|
8
8
|
import fs from 'fs';
|
|
9
9
|
import path from 'path';
|
|
@@ -28,7 +28,7 @@ const readAndSortCSV = () => {
|
|
|
28
28
|
while (lines.length && !lines[lines.length - 1].trim()) lines.pop();
|
|
29
29
|
|
|
30
30
|
// Trim trailing whitespace on each line
|
|
31
|
-
lines = lines.map(l => l.replace(/\s+$/,''));
|
|
31
|
+
lines = lines.map((l) => l.replace(/\s+$/, ''));
|
|
32
32
|
|
|
33
33
|
// The header should be kept as the first line
|
|
34
34
|
const header = lines[0];
|
|
@@ -52,7 +52,6 @@ const readAndSortCSV = () => {
|
|
|
52
52
|
|
|
53
53
|
console.log(`✅ Successfully sorted ${sortedColorLines.length} colors alphabetically by name`);
|
|
54
54
|
console.log(`📝 File saved: ${csvPath}`);
|
|
55
|
-
|
|
56
55
|
} catch (error) {
|
|
57
56
|
console.error('❌ Error sorting the CSV file:', error);
|
|
58
57
|
process.exit(1);
|
package/src/colornames.csv
CHANGED
|
@@ -200,7 +200,6 @@ After Dinner Mint,#e3f5e5,
|
|
|
200
200
|
After Eight,#3d2e24,
|
|
201
201
|
After Eight Filling,#d6eae8,
|
|
202
202
|
After Midnight,#38393f,x
|
|
203
|
-
After Rain,#c1dbea,
|
|
204
203
|
After Shock,#fec65f,
|
|
205
204
|
After the Rain,#8bc4d1,
|
|
206
205
|
After the Storm,#33616a,x
|
|
@@ -930,7 +929,6 @@ Apple Crisp,#e19c55,
|
|
|
930
929
|
Apple Crunch,#fee5c9,
|
|
931
930
|
Apple Cucumber,#dbdbbc,
|
|
932
931
|
Apple Custard,#fddfae,
|
|
933
|
-
Apple Day,#7e976d,
|
|
934
932
|
Apple Flower,#edf4eb,
|
|
935
933
|
Apple Fritter,#cc9350,
|
|
936
934
|
Apple Green,#76cd26,
|
|
@@ -957,7 +955,7 @@ Apple Slice,#f1f0bf,
|
|
|
957
955
|
Apple Turnover,#e8c194,
|
|
958
956
|
Apple Valley,#ea8386,
|
|
959
957
|
Apple Wine,#b59f62,
|
|
960
|
-
Apple-
|
|
958
|
+
Apple-a-Day,#903f45,
|
|
961
959
|
Applegate,#8ac479,
|
|
962
960
|
Applegate Park,#aead93,
|
|
963
961
|
Applemint,#cdeacd,
|
|
@@ -1402,7 +1400,7 @@ Asurmen Blue Wash,#273e51,
|
|
|
1402
1400
|
Aswad Black,#17181c,
|
|
1403
1401
|
At Ease,#e7eee1,
|
|
1404
1402
|
At Ease Soldier,#9e9985,
|
|
1405
|
-
At
|
|
1403
|
+
At the Beach,#e7d9b9,
|
|
1406
1404
|
Atelier,#a3abb8,
|
|
1407
1405
|
Ateneo Blue,#003a6c,
|
|
1408
1406
|
Athena,#dcd7cc,
|
|
@@ -2096,7 +2094,6 @@ Baywater Blue,#c9d8e4,
|
|
|
2096
2094
|
Bazaar,#8f7777,
|
|
2097
2095
|
Bazooka Pink,#ffa6c9,
|
|
2098
2096
|
BBQ,#a35046,
|
|
2099
|
-
Be Daring,#ffc943,
|
|
2100
2097
|
Be Mine,#f4e3e7,
|
|
2101
2098
|
Be My Valentine,#ec9dc3,
|
|
2102
2099
|
Be Spontaneous,#a5cb66,
|
|
@@ -2933,7 +2930,7 @@ Blue Brocade,#70b8d0,
|
|
|
2933
2930
|
Blue Bubble,#a6d7eb,
|
|
2934
2931
|
Blue Burst,#309cd0,x
|
|
2935
2932
|
Blue Buzz,#a1a2bd,
|
|
2936
|
-
Blue
|
|
2933
|
+
Blue by You,#a0b7ba,
|
|
2937
2934
|
Blue Calico,#a5cde1,
|
|
2938
2935
|
Blue Calypso,#55a7b6,
|
|
2939
2936
|
Blue Cardinal Flower,#2f36ba,
|
|
@@ -3199,7 +3196,7 @@ Blue Team Spirit,#5885a2,
|
|
|
3199
3196
|
Blue Thistle,#adc0d6,
|
|
3200
3197
|
Blue Tint,#9fd9d7,
|
|
3201
3198
|
Blue Titmouse,#4466ff,
|
|
3202
|
-
Blue
|
|
3199
|
+
Blue to You,#babfc5,
|
|
3203
3200
|
Blue Tone Ink,#2b4057,
|
|
3204
3201
|
Blue Topaz,#65aece,
|
|
3205
3202
|
Blue Torus,#042993,
|
|
@@ -3777,7 +3774,7 @@ Broad Bean,#94975d,
|
|
|
3777
3774
|
Broad Daylight,#bbddff,x
|
|
3778
3775
|
Broadleaf Forest,#014421,
|
|
3779
3776
|
Broadwater Blue,#034a71,
|
|
3780
|
-
Broadway,#
|
|
3777
|
+
Broadway,#145775,
|
|
3781
3778
|
Broadway Lights,#fee07c,
|
|
3782
3779
|
Brocade,#8c87c5,
|
|
3783
3780
|
Brocade Violet,#7b4d6b,
|
|
@@ -3823,7 +3820,6 @@ Broomstick,#74462d,
|
|
|
3823
3820
|
Brother Blue,#b0b7c6,
|
|
3824
3821
|
Brown,#653700,x
|
|
3825
3822
|
Brown Alpaca,#b86d29,x
|
|
3826
|
-
Brown Bag,#deac6e,
|
|
3827
3823
|
Brown Bear,#4a3f37,
|
|
3828
3824
|
Brown Beauty,#4a3832,
|
|
3829
3825
|
Brown Beige,#cc8833,
|
|
@@ -3872,7 +3868,7 @@ Brown Tumbleweed,#37290e,
|
|
|
3872
3868
|
Brown Velvet,#704e40,
|
|
3873
3869
|
Brown Wood,#b4674d,
|
|
3874
3870
|
Brown Yellow,#dd9966,
|
|
3875
|
-
Brown-Bag
|
|
3871
|
+
Brown-Bag It,#ddbda3,
|
|
3876
3872
|
Brown-Noser,#79512c,
|
|
3877
3873
|
Browned Off,#bb4433,
|
|
3878
3874
|
Brownie,#964b00,x
|
|
@@ -4195,8 +4191,7 @@ Buzz-In,#ffd756,
|
|
|
4195
4191
|
Buzzard,#5f563f,
|
|
4196
4192
|
Buzzards Bay,#017a79,
|
|
4197
4193
|
By Gum,#816a38,
|
|
4198
|
-
By the
|
|
4199
|
-
By The Sea,#8d999e,
|
|
4194
|
+
By the Sea,#8d999e,
|
|
4200
4195
|
Byakuroku Green,#a5ba93,
|
|
4201
4196
|
Bygone,#918e8a,
|
|
4202
4197
|
Bypass,#b6c4d2,
|
|
@@ -5832,10 +5827,8 @@ Cloud Abyss,#dfe7eb,
|
|
|
5832
5827
|
Cloud Blue,#a2b6b9,
|
|
5833
5828
|
Cloud Break,#f6f1fe,x
|
|
5834
5829
|
Cloud Cover,#adb5bc,
|
|
5835
|
-
Cloud Cream,#ded1b3,
|
|
5836
5830
|
Cloud Dancer,#f0eee9,x
|
|
5837
5831
|
Cloud Grey,#b8a9af,
|
|
5838
|
-
Cloud Nine,#e9e0db,
|
|
5839
5832
|
Cloud Number Nine,#f9cec6,
|
|
5840
5833
|
Cloud of Cream,#f1e2c4,x
|
|
5841
5834
|
Cloud Over London,#c2bcb1,
|
|
@@ -6942,7 +6935,6 @@ Curaçao Blue,#008894,
|
|
|
6942
6935
|
Curated Lilac,#a6a6b6,
|
|
6943
6936
|
Curated White,#eae1ce,
|
|
6944
6937
|
Curd,#f8e1ba,
|
|
6945
|
-
Curds & Whey,#b59c76,
|
|
6946
6938
|
Curds and Whey,#bca483,
|
|
6947
6939
|
Cure All,#aa6988,
|
|
6948
6940
|
Cured Eggplant,#380835,
|
|
@@ -6974,8 +6966,7 @@ Custard Powder,#f8dcaa,
|
|
|
6974
6966
|
Custard Puff,#fceeae,
|
|
6975
6967
|
Customs Green,#003839,
|
|
6976
6968
|
Cut Heather,#9e909e,
|
|
6977
|
-
Cut
|
|
6978
|
-
Cut the Mustard,#ba7f38,
|
|
6969
|
+
Cut the Mustard,#ba7f38,x
|
|
6979
6970
|
Cut Velvet,#b391c8,
|
|
6980
6971
|
Cute Crab,#dd4444,x
|
|
6981
6972
|
Cute Little Pink,#f4e2e1,
|
|
@@ -7747,7 +7738,7 @@ Devil Blue,#277594,
|
|
|
7747
7738
|
Devil’s Advocate,#ff3344,x
|
|
7748
7739
|
Devil’s Butterfly,#bb4422,
|
|
7749
7740
|
Devil’s Flower Mantis,#8f9805,
|
|
7750
|
-
Devil’s Grass,#44aa55,
|
|
7741
|
+
Devil’s Grass,#44aa55,x
|
|
7751
7742
|
Devil’s Lip,#662a2c,
|
|
7752
7743
|
Devil’s Plum,#423450,
|
|
7753
7744
|
Deviled Eggs,#fecd82,x
|
|
@@ -8036,7 +8027,6 @@ Downing Sand,#cbbca5,
|
|
|
8036
8027
|
Downing Slate,#777f86,
|
|
8037
8028
|
Downing Stone,#a6a397,
|
|
8038
8029
|
Downing Straw,#caab7d,
|
|
8039
|
-
Downing to Earth,#635a4f,
|
|
8040
8030
|
Download Progress,#58d332,
|
|
8041
8031
|
Downpour,#43718b,
|
|
8042
8032
|
Downriver,#092256,
|
|
@@ -12737,7 +12727,6 @@ Honey Bunny,#dbb881,x
|
|
|
12737
12727
|
Honey Butter,#f5d29b,
|
|
12738
12728
|
Honey Carrot Cake,#ff9955,
|
|
12739
12729
|
Honey Chili,#883344,
|
|
12740
|
-
Honey Cream,#fae8ca,
|
|
12741
12730
|
Honey Crisp,#e9c160,x
|
|
12742
12731
|
Honey Crusted Chicken,#ffbb55,
|
|
12743
12732
|
Honey Do,#ededc7,x
|
|
@@ -13161,8 +13150,6 @@ Ilvaite Black,#330011,
|
|
|
13161
13150
|
Imagery,#7a6e70,
|
|
13162
13151
|
Imaginary Mauve,#89687d,
|
|
13163
13152
|
Imagination,#dfe0ee,
|
|
13164
|
-
Imagine,#af9468,
|
|
13165
|
-
Imagine That,#947c98,
|
|
13166
13153
|
Imam Ali Gold,#fae199,
|
|
13167
13154
|
Imayou Pink,#d0576b,
|
|
13168
13155
|
Immaculate Iguana,#aacc00,
|
|
@@ -13202,7 +13189,7 @@ Impulse Blue,#006699,
|
|
|
13202
13189
|
Impulsive Purple,#624977,
|
|
13203
13190
|
Impure White,#f5e7e3,
|
|
13204
13191
|
Imrik Blue,#67aed0,
|
|
13205
|
-
In
|
|
13192
|
+
In a Pickle,#978c59,x
|
|
13206
13193
|
In Caffeine We Trust,#693c2a,
|
|
13207
13194
|
In for a Penny,#ee8877,x
|
|
13208
13195
|
In Good Taste,#b6d4a0,x
|
|
@@ -15889,7 +15876,6 @@ Mahogany Spice,#5b4646,
|
|
|
15889
15876
|
Mahonia Berry Blue,#62788e,
|
|
15890
15877
|
Mai Tai,#a56531,x
|
|
15891
15878
|
Maiden Hair,#f5e9ca,
|
|
15892
|
-
Maiden Mist,#b9c0c0,
|
|
15893
15879
|
Maiden of the Mist,#efdceb,
|
|
15894
15880
|
Maiden Pink,#ff2feb,
|
|
15895
15881
|
Maiden Voyage,#8ac7d4,
|
|
@@ -17008,7 +16994,6 @@ Mission Wildflower,#9e5566,
|
|
|
17008
16994
|
Mississippi Mud,#99886f,
|
|
17009
16995
|
Mississippi River,#3b638c,x
|
|
17010
16996
|
Missouri Mud,#a6a19b,
|
|
17011
|
-
Mist Green,#aacebc,
|
|
17012
16997
|
Mist Grey,#c4c4bc,
|
|
17013
16998
|
Mist of Green,#e3f1eb,
|
|
17014
16999
|
Mist Spirit,#e4ebe7,
|
|
@@ -17526,7 +17511,6 @@ Mousy Indigo,#5c544e,
|
|
|
17526
17511
|
Moutarde de Bénichon,#bf9005,x
|
|
17527
17512
|
Move Mint,#4effcd,
|
|
17528
17513
|
Mover & Shaker,#9cce9e,
|
|
17529
|
-
Mover and Shaker,#855d44,
|
|
17530
17514
|
Movie Magic,#b2bfd5,
|
|
17531
17515
|
Movie Star,#c52033,
|
|
17532
17516
|
Mow the Lawn,#a9b49a,
|
|
@@ -18656,7 +18640,6 @@ Olive Grey,#afa78d,
|
|
|
18656
18640
|
Olive Grove,#716a4d,
|
|
18657
18641
|
Olive Haze,#888064,
|
|
18658
18642
|
Olive Hint,#c9bd88,
|
|
18659
|
-
Olive It,#aeab9a,
|
|
18660
18643
|
Olive Leaf,#4e4b35,x
|
|
18661
18644
|
Olive Leaf Tea,#78866b,
|
|
18662
18645
|
Olive Martini,#ced2ab,
|
|
@@ -18970,7 +18953,6 @@ OU Crimson Red,#990000,
|
|
|
18970
18953
|
Oubliette,#4f4944,
|
|
18971
18954
|
Ouni Red,#ee7948,
|
|
18972
18955
|
Our Little Secret,#a84b7a,x
|
|
18973
|
-
Out of Blue,#c0f7db,
|
|
18974
18956
|
Out of Fashion,#f26d8f,
|
|
18975
18957
|
Out of Plumb,#9c909c,
|
|
18976
18958
|
Out of the Blue,#1199ee,x
|
|
@@ -19732,7 +19714,6 @@ Pearl Yellow,#f1e3bc,
|
|
|
19732
19714
|
Pearled Couscous,#f2e9d5,
|
|
19733
19715
|
Pearled Ivory,#f0dfcc,
|
|
19734
19716
|
Pearls & Lace,#dcd0cb,
|
|
19735
|
-
Pearls and Lace,#eee7dc,
|
|
19736
19717
|
Pearly,#f4e3df,x
|
|
19737
19718
|
Pearly Pink,#ee99cc,x
|
|
19738
19719
|
Pearly Purple,#b768a2,
|
|
@@ -21099,7 +21080,6 @@ Pretty Pale,#e3c6d6,
|
|
|
21099
21080
|
Pretty Parasol,#ac5d3e,
|
|
21100
21081
|
Pretty Pastry,#dfcdb2,x
|
|
21101
21082
|
Pretty Petunia,#d6b7e2,
|
|
21102
|
-
Pretty Pink,#ebb3b2,
|
|
21103
21083
|
Pretty Pink Piggy,#eeaadd,
|
|
21104
21084
|
Pretty Please,#ffccc8,
|
|
21105
21085
|
Pretty Posie,#bcbde4,
|
|
@@ -22057,6 +22037,7 @@ Red Salsa,#fd3a4a,
|
|
|
22057
22037
|
Red Sandstorm,#e5cac0,
|
|
22058
22038
|
Red Sauce Parlor,#cc3b22,
|
|
22059
22039
|
Red Savina Pepper,#ee0128,
|
|
22040
|
+
Red Seal,#c92d21,x
|
|
22060
22041
|
Red Sentinel,#b9090f,
|
|
22061
22042
|
Red Shade Wash,#862808,
|
|
22062
22043
|
Red Shimmer,#fee0da,
|
|
@@ -24494,7 +24475,6 @@ Sinatra,#4675b7,
|
|
|
24494
24475
|
Sinbad,#a6d5d0,
|
|
24495
24476
|
Sinful,#645059,
|
|
24496
24477
|
Singapore Orchid,#a020f0,
|
|
24497
|
-
Singing Blue,#0074a4,
|
|
24498
24478
|
Singing in the Rain,#8e9c98,
|
|
24499
24479
|
Singing the Blues,#2b4d68,
|
|
24500
24480
|
Single Origin,#713e39,x
|
|
@@ -25112,6 +25092,7 @@ Sour Lemon,#ffeea5,x
|
|
|
25112
25092
|
Sour Lime,#acc326,x
|
|
25113
25093
|
Sour Patch Peach,#f4d9c5,
|
|
25114
25094
|
Sour Tarts,#fee5c8,
|
|
25095
|
+
Sour Veil,#ffffbf,x
|
|
25115
25096
|
Sour Yellow,#eeff04,x
|
|
25116
25097
|
Source Blue,#cdeae5,
|
|
25117
25098
|
Source Green,#84b6a2,
|
|
@@ -26392,12 +26373,11 @@ Swedish Blue,#007cc0,
|
|
|
26392
26373
|
Swedish Clover,#7b8867,
|
|
26393
26374
|
Swedish Green,#184d43,
|
|
26394
26375
|
Swedish Yellow,#fce081,
|
|
26395
|
-
Sweet & Sour,#
|
|
26376
|
+
Sweet & Sour,#c4bf0b,
|
|
26396
26377
|
Sweet 60,#f29eab,
|
|
26397
26378
|
Sweet Almond,#cc9977,
|
|
26398
26379
|
Sweet Alyssum,#e7c2de,
|
|
26399
26380
|
Sweet and Sassy,#e1c9d1,x
|
|
26400
|
-
Sweet and Sour,#c4bf0b,
|
|
26401
26381
|
Sweet Angel,#f5c8bb,
|
|
26402
26382
|
Sweet Angelica,#e8d08e,
|
|
26403
26383
|
Sweet Annie,#9c946e,
|
|
@@ -26439,7 +26419,6 @@ Sweet Georgia Brown,#8b715a,
|
|
|
26439
26419
|
Sweet Grape,#4d3d52,
|
|
26440
26420
|
Sweet Grass,#b2b68a,
|
|
26441
26421
|
Sweet Harbor,#d9dde7,
|
|
26442
|
-
Sweet Honey,#d4a55c,
|
|
26443
26422
|
Sweet Illusion,#e0e8ec,
|
|
26444
26423
|
Sweet Jasmine,#f9f4d4,
|
|
26445
26424
|
Sweet Juliet,#b8bfd2,
|
|
@@ -27003,13 +26982,10 @@ Thatched Cottage,#d6c7a6,
|
|
|
27003
26982
|
Thatched Roof,#efe0c6,
|
|
27004
26983
|
Thawed Out,#e1eeec,
|
|
27005
26984
|
The Art of Seduction,#dd0088,
|
|
27006
|
-
The Blarney Stone,#ab9f89,
|
|
27007
26985
|
The Bluff,#ffc8c2,
|
|
27008
26986
|
The Boulevard,#d0a492,
|
|
27009
|
-
The Broadway,#145775,
|
|
27010
26987
|
The Cottage,#837663,
|
|
27011
26988
|
The Count’s Black,#102030,x
|
|
27012
|
-
The Devil’s Grass,#666420,x
|
|
27013
26989
|
The Ego Has Landed,#a75455,
|
|
27014
26990
|
The End,#2a2a2a,x
|
|
27015
26991
|
The End Is Beer,#eed508,
|
|
@@ -27024,7 +27000,6 @@ The Killing Joke,#b0bf1a,
|
|
|
27024
27000
|
The Legend of Green,#558844,x
|
|
27025
27001
|
The New Black,#ff8400,
|
|
27026
27002
|
The Rainbow Fish,#4466ee,
|
|
27027
|
-
The Real Teal,#007883,
|
|
27028
27003
|
The Sickener,#db7093,
|
|
27029
27004
|
The Speed of Light,#f6f4ef,
|
|
27030
27005
|
The Vast of Night,#110066,x
|
|
@@ -29157,7 +29132,6 @@ Whisper of Smoke,#cbcecf,x
|
|
|
29157
29132
|
Whisper of White,#eadbca,x
|
|
29158
29133
|
Whisper Pink,#d4c5b4,
|
|
29159
29134
|
Whisper Ridge,#c9c3b5,
|
|
29160
|
-
Whisper White,#eae2d3,x
|
|
29161
29135
|
Whisper Yellow,#ffe5b9,
|
|
29162
29136
|
Whispered Secret,#3f4855,
|
|
29163
29137
|
Whispering Blue,#c9dcdc,
|
|
@@ -29872,7 +29846,6 @@ Yearning,#061088,
|
|
|
29872
29846
|
Yearning Desire,#ca135e,x
|
|
29873
29847
|
Yeast,#fae1ac,
|
|
29874
29848
|
Yell for Yellow,#fffe00,x
|
|
29875
|
-
Yell Yellow,#ffffbf,x
|
|
29876
29849
|
Yellow,#ffff00,x
|
|
29877
29850
|
Yellow Acorn,#b68d4c,
|
|
29878
29851
|
Yellow Avarice,#f5f5d9,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Small utility to build consistent, helpful failure messages in tests.
|
|
2
|
+
// Keeps output informative while avoiding repeated boilerplate in each test file.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Simple pluralization helper
|
|
6
|
+
* @param {number} n
|
|
7
|
+
* @param {string} singular
|
|
8
|
+
* @param {string} [plural]
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
export function pluralize(n, singular, plural = `${singular}s`) {
|
|
12
|
+
return n === 1 ? singular : plural;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Join values as a readable, comma-separated list.
|
|
17
|
+
* Ensures uniqueness and stable order.
|
|
18
|
+
* @param {Array<string|number>} items
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
export function list(items) {
|
|
22
|
+
if (!items || !items.length) return '';
|
|
23
|
+
const uniq = [...new Set(items.map(String))];
|
|
24
|
+
return uniq.join(', ');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build a consistent failure message for test assertions.
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} opts
|
|
31
|
+
* @param {string} opts.title - Heading/title of the error (without counts)
|
|
32
|
+
* @param {Array<string>} [opts.offenders] - Values to summarize in a one-line list.
|
|
33
|
+
* @param {string} [opts.offenderLabel] - Label for offenders list (e.g. "name", "hex code").
|
|
34
|
+
* @param {Array<string>} [opts.details] - Additional bullet/section lines to include.
|
|
35
|
+
* @param {Array<string>} [opts.tips] - How-to-fix tips appended at the end.
|
|
36
|
+
* @param {number} [opts.count] - Optional explicit count (defaults to offenders.length).
|
|
37
|
+
* @param {string} [opts.icon] - Optional icon/emphasis prefix, default ⛔.
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
export function buildFailureMessage({
|
|
41
|
+
title,
|
|
42
|
+
offenders = [],
|
|
43
|
+
offenderLabel = 'item',
|
|
44
|
+
details = [],
|
|
45
|
+
tips = [],
|
|
46
|
+
count,
|
|
47
|
+
icon = '⛔',
|
|
48
|
+
} = {}) {
|
|
49
|
+
const msgLines = [];
|
|
50
|
+
const n = typeof count === 'number' ? count : offenders.length;
|
|
51
|
+
|
|
52
|
+
msgLines.push(
|
|
53
|
+
`${icon} ${title.replace('{n}', String(n)).replace('{items}', pluralize(n, offenderLabel))}`
|
|
54
|
+
);
|
|
55
|
+
msgLines.push('');
|
|
56
|
+
|
|
57
|
+
if (offenders.length) {
|
|
58
|
+
msgLines.push(`Offending ${pluralize(offenders.length, offenderLabel)}: ${list(offenders)}`);
|
|
59
|
+
msgLines.push('*-------------------------*');
|
|
60
|
+
msgLines.push('');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (details.length) {
|
|
64
|
+
for (const line of details) msgLines.push(line);
|
|
65
|
+
msgLines.push('');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (tips.length) {
|
|
69
|
+
msgLines.push('Tip:');
|
|
70
|
+
for (const t of tips) msgLines.push(` - ${t}`);
|
|
71
|
+
msgLines.push('');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
msgLines.push('*-------------------------*');
|
|
75
|
+
return msgLines.join('\n');
|
|
76
|
+
}
|