color-name-list 13.1.0 → 13.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -3
- package/changes.svg +3 -3
- package/dist/colornames.bestof.csv +43 -17
- 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 +157 -53
- package/dist/colornames.bestof.yaml +119 -41
- package/dist/colornames.csv +96 -297
- 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 +22 -13
- 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 +79 -43
- package/dist/colornames.short.yaml +60 -33
- package/dist/colornames.umd.js +1 -1
- package/dist/colornames.xml +291 -1095
- package/dist/colornames.yaml +226 -829
- package/dist/history.json +1 -1
- package/package.json +1 -1
- package/scripts/lib.js +2 -1
- package/src/colornames.csv +105 -306
- package/tests/duplicate-allowlist.json +44 -1
- package/tests/duplicates.test.js +46 -24
- package/tests/title-case.test.js +82 -18
- package/vitest.config.js +5 -0
- /package/tests/{formats.test.js → formats.ci.test.js} +0 -0
- /package/tests/{imports.test.js → imports.ci.test.js} +0 -0
|
@@ -36,5 +36,48 @@
|
|
|
36
36
|
"Dark as Night",
|
|
37
37
|
"Green With Envy",
|
|
38
38
|
"Lost in Space",
|
|
39
|
-
"Fruit of Passion"
|
|
39
|
+
"Fruit of Passion",
|
|
40
|
+
"Aroma Garden",
|
|
41
|
+
"Back in Black",
|
|
42
|
+
"Tea Rose",
|
|
43
|
+
"Red Violet",
|
|
44
|
+
"Blue Green",
|
|
45
|
+
"Blue Grey",
|
|
46
|
+
"Pink Red",
|
|
47
|
+
"Black Green",
|
|
48
|
+
"Red Purple",
|
|
49
|
+
"Red Revival",
|
|
50
|
+
"Purple Pink",
|
|
51
|
+
"Pearl White",
|
|
52
|
+
"Milk Chocolate",
|
|
53
|
+
"Violet Blue",
|
|
54
|
+
"Black Violet",
|
|
55
|
+
"Black Velvet",
|
|
56
|
+
"Rose Pearl",
|
|
57
|
+
"Yellow Orange",
|
|
58
|
+
"Red Orange",
|
|
59
|
+
"Pink Orange",
|
|
60
|
+
"Pine Mountain",
|
|
61
|
+
"Star of the Morning",
|
|
62
|
+
"Rose Madder",
|
|
63
|
+
"Lily the Pink",
|
|
64
|
+
"Purple Grey",
|
|
65
|
+
"Yellow Green",
|
|
66
|
+
"White Green",
|
|
67
|
+
"Turquoise Green",
|
|
68
|
+
"Green Teal",
|
|
69
|
+
"Yellow Brown",
|
|
70
|
+
"Grey Green",
|
|
71
|
+
"Grape Wine",
|
|
72
|
+
"Blue Light",
|
|
73
|
+
"Indigo Blue",
|
|
74
|
+
"Blue Purple",
|
|
75
|
+
"Blue Turquoise",
|
|
76
|
+
"Brown Green",
|
|
77
|
+
"Brown Grey",
|
|
78
|
+
"Brown Orange",
|
|
79
|
+
"Brown Red",
|
|
80
|
+
"Brown Rose",
|
|
81
|
+
"Honey Butter",
|
|
82
|
+
"Custard Cream"
|
|
40
83
|
]
|
package/tests/duplicates.test.js
CHANGED
|
@@ -13,20 +13,41 @@ describe('Duplicate-like color names', () => {
|
|
|
13
13
|
|
|
14
14
|
// Shared stopwords for normalization across tests
|
|
15
15
|
const STOPWORDS = [
|
|
16
|
-
'of',
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
16
|
+
'of',
|
|
17
|
+
'the',
|
|
18
|
+
'and',
|
|
19
|
+
'a',
|
|
20
|
+
'an',
|
|
21
|
+
'in',
|
|
22
|
+
'on',
|
|
23
|
+
'at',
|
|
24
|
+
'to',
|
|
25
|
+
'for',
|
|
26
|
+
'by',
|
|
27
|
+
'with',
|
|
28
|
+
'from',
|
|
29
|
+
'as',
|
|
30
|
+
'is',
|
|
31
|
+
'it',
|
|
32
|
+
'this',
|
|
33
|
+
'that',
|
|
34
|
+
'these',
|
|
35
|
+
'those',
|
|
36
|
+
'be',
|
|
37
|
+
'are',
|
|
38
|
+
'was',
|
|
39
|
+
'were',
|
|
40
|
+
'or',
|
|
21
41
|
];
|
|
22
42
|
|
|
23
43
|
// Helper: normalize a phrase similar to scripts/lib normalization but keeping token boundaries
|
|
24
|
-
const normalize = (s) =>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
44
|
+
const normalize = (s) =>
|
|
45
|
+
String(s)
|
|
46
|
+
.toLowerCase()
|
|
47
|
+
.normalize('NFD')
|
|
48
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
49
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
50
|
+
.trim();
|
|
30
51
|
|
|
31
52
|
const tokenize = (name) => {
|
|
32
53
|
const base = normalize(name);
|
|
@@ -53,6 +74,13 @@ describe('Duplicate-like color names', () => {
|
|
|
53
74
|
groups.get(key).push({ name: item.name, lineNumber: item.lineNumber, order });
|
|
54
75
|
}
|
|
55
76
|
|
|
77
|
+
// Build allowlist set once
|
|
78
|
+
const allowSet = new Set(
|
|
79
|
+
(Array.isArray(allowlist) ? allowlist : [])
|
|
80
|
+
.filter((v) => typeof v === 'string' && v.trim().length)
|
|
81
|
+
.map((v) => normalize(v))
|
|
82
|
+
);
|
|
83
|
+
|
|
56
84
|
const conflicts = [];
|
|
57
85
|
for (const [key, entries] of groups.entries()) {
|
|
58
86
|
// We have a potential conflict if we see both orders "a b" and "b a"
|
|
@@ -78,13 +106,9 @@ describe('Duplicate-like color names', () => {
|
|
|
78
106
|
})
|
|
79
107
|
.sort((a, b) => a.lineNumber - b.lineNumber);
|
|
80
108
|
|
|
81
|
-
// Respect allowlist: if
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
.filter((v) => typeof v === 'string' && v.trim().length)
|
|
85
|
-
.map((v) => normalize(v))
|
|
86
|
-
);
|
|
87
|
-
if (allowSet.has(forward) || allowSet.has(backward)) continue;
|
|
109
|
+
// Respect allowlist: check if any of the actual names are allowlisted
|
|
110
|
+
const hasAllowlisted = unique.some((e) => allowSet.has(normalize(e.name)));
|
|
111
|
+
if (hasAllowlisted) continue;
|
|
88
112
|
|
|
89
113
|
conflicts.push({ key, tokens: [t1, t2], entries: unique });
|
|
90
114
|
}
|
|
@@ -157,7 +181,8 @@ describe('Duplicate-like color names', () => {
|
|
|
157
181
|
|
|
158
182
|
throw new Error(
|
|
159
183
|
buildFailureMessage({
|
|
160
|
-
title:
|
|
184
|
+
title:
|
|
185
|
+
'Found {n} duplicate-like {items} (case/accents/punctuation/stopwords-insensitive):',
|
|
161
186
|
offenders: [...allOffendingNames],
|
|
162
187
|
offenderLabel: 'name',
|
|
163
188
|
details: [
|
|
@@ -218,10 +243,7 @@ describe('Duplicate-like color names', () => {
|
|
|
218
243
|
});
|
|
219
244
|
|
|
220
245
|
it('should detect names that only differ by stopwords when enabled', () => {
|
|
221
|
-
const items = [
|
|
222
|
-
{ name: 'Heart Gold' },
|
|
223
|
-
{ name: 'Heart of Gold' },
|
|
224
|
-
];
|
|
246
|
+
const items = [{ name: 'Heart Gold' }, { name: 'Heart of Gold' }];
|
|
225
247
|
const conflicts = findNearDuplicateNameConflicts(items, {
|
|
226
248
|
foldStopwords: true,
|
|
227
249
|
stopwords: STOPWORDS,
|
|
@@ -231,7 +253,7 @@ describe('Duplicate-like color names', () => {
|
|
|
231
253
|
expect(conflicts[0].entries.length).toBe(2);
|
|
232
254
|
});
|
|
233
255
|
|
|
234
|
-
it
|
|
256
|
+
it('should not contain two-word names that are exact reversals of each other', () => {
|
|
235
257
|
expect(csvTestData.lineCount).toBeGreaterThan(1);
|
|
236
258
|
|
|
237
259
|
const conflicts = findTwoWordReversedPairs(csvTestData.items);
|
package/tests/title-case.test.js
CHANGED
|
@@ -8,7 +8,7 @@ describe('APA Title Case Validation', () => {
|
|
|
8
8
|
csvTestData.load();
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
12
|
* Determine if a word should be capitalized according to APA title case rules
|
|
13
13
|
* @param {string} word - The word to check
|
|
14
14
|
* @param {boolean} isFirstWordOrAfterPunctuation - True if this is the first word or after punctuation
|
|
@@ -16,7 +16,12 @@ describe('APA Title Case Validation', () => {
|
|
|
16
16
|
* @param {boolean} isLastWord - True if this is the last word in the title
|
|
17
17
|
* @returns {boolean} - True if the word should be capitalized
|
|
18
18
|
*/
|
|
19
|
-
function shouldBeCapitalized(
|
|
19
|
+
function shouldBeCapitalized(
|
|
20
|
+
word,
|
|
21
|
+
isFirstWordOrAfterPunctuation,
|
|
22
|
+
isAfterPunctuation,
|
|
23
|
+
isLastWord = false
|
|
24
|
+
) {
|
|
20
25
|
// Always capitalize first words, words after punctuation, and last words
|
|
21
26
|
if (isFirstWordOrAfterPunctuation || isAfterPunctuation || isLastWord) {
|
|
22
27
|
return true;
|
|
@@ -30,13 +35,44 @@ describe('APA Title Case Validation', () => {
|
|
|
30
35
|
// Short words (3 letters or fewer) that should be lowercase (unless first, after punctuation, or last)
|
|
31
36
|
const minorWords = new Set([
|
|
32
37
|
// Short conjunctions
|
|
33
|
-
'and',
|
|
38
|
+
'and',
|
|
39
|
+
'as',
|
|
40
|
+
'but',
|
|
41
|
+
'for',
|
|
42
|
+
'if',
|
|
43
|
+
'nor',
|
|
44
|
+
'or',
|
|
45
|
+
'so',
|
|
46
|
+
'yet',
|
|
34
47
|
// Articles
|
|
35
|
-
'a',
|
|
48
|
+
'a',
|
|
49
|
+
'an',
|
|
50
|
+
'the',
|
|
36
51
|
// Short prepositions
|
|
37
|
-
'at',
|
|
52
|
+
'at',
|
|
53
|
+
'by',
|
|
54
|
+
'in',
|
|
55
|
+
'of',
|
|
56
|
+
'off',
|
|
57
|
+
'on',
|
|
58
|
+
'per',
|
|
59
|
+
'to',
|
|
60
|
+
'up',
|
|
61
|
+
'via',
|
|
38
62
|
// for short prepositions
|
|
39
|
-
'de',
|
|
63
|
+
'de',
|
|
64
|
+
'la',
|
|
65
|
+
'le',
|
|
66
|
+
'les',
|
|
67
|
+
'un',
|
|
68
|
+
'une',
|
|
69
|
+
'du',
|
|
70
|
+
'des',
|
|
71
|
+
'et',
|
|
72
|
+
'ou',
|
|
73
|
+
'à',
|
|
74
|
+
'au',
|
|
75
|
+
'aux',
|
|
40
76
|
]);
|
|
41
77
|
|
|
42
78
|
return !minorWords.has(word.toLowerCase());
|
|
@@ -147,12 +183,22 @@ describe('APA Title Case Validation', () => {
|
|
|
147
183
|
|
|
148
184
|
// Patterns that suggest Los Angeles context
|
|
149
185
|
const losAngelesIndicators = [
|
|
150
|
-
'vibes',
|
|
151
|
-
'
|
|
186
|
+
'vibes',
|
|
187
|
+
'style',
|
|
188
|
+
'sunset',
|
|
189
|
+
'beach',
|
|
190
|
+
'hollywood',
|
|
191
|
+
'california',
|
|
192
|
+
'west coast',
|
|
193
|
+
'city',
|
|
194
|
+
'metro',
|
|
195
|
+
'downtown',
|
|
196
|
+
'freeway',
|
|
197
|
+
'boulevard',
|
|
152
198
|
];
|
|
153
199
|
|
|
154
200
|
// If the name contains any LA indicators, treat "la" as Los Angeles
|
|
155
|
-
if (losAngelesIndicators.some(indicator => lowerName.includes(indicator))) {
|
|
201
|
+
if (losAngelesIndicators.some((indicator) => lowerName.includes(indicator))) {
|
|
156
202
|
return true;
|
|
157
203
|
}
|
|
158
204
|
|
|
@@ -160,7 +206,7 @@ describe('APA Title Case Validation', () => {
|
|
|
160
206
|
const nextWord = words[wordIndex + 2]; // +2 to skip whitespace
|
|
161
207
|
if (nextWord) {
|
|
162
208
|
const nonFrenchPatterns = ['vibes', 'style', 'sunset', 'beach'];
|
|
163
|
-
if (nonFrenchPatterns.some(pattern => nextWord.toLowerCase().includes(pattern))) {
|
|
209
|
+
if (nonFrenchPatterns.some((pattern) => nextWord.toLowerCase().includes(pattern))) {
|
|
164
210
|
return true;
|
|
165
211
|
}
|
|
166
212
|
}
|
|
@@ -187,8 +233,8 @@ describe('APA Title Case Validation', () => {
|
|
|
187
233
|
|
|
188
234
|
// Check for alphanumeric patterns that should stay uppercase (like model numbers)
|
|
189
235
|
// Exclude ordinal numbers (1st, 2nd, 3rd, 18th, etc.)
|
|
190
|
-
const isAlphanumericPattern =
|
|
191
|
-
!/^\d+(st|nd|rd|th)$/i.test(segment);
|
|
236
|
+
const isAlphanumericPattern =
|
|
237
|
+
/^[0-9]+[A-Z]+$/i.test(segment) && !/^\d+(st|nd|rd|th)$/i.test(segment);
|
|
192
238
|
|
|
193
239
|
// Handle hyphenated words - both parts should follow title case rules
|
|
194
240
|
if (segment.includes('-')) {
|
|
@@ -219,11 +265,20 @@ describe('APA Title Case Validation', () => {
|
|
|
219
265
|
|
|
220
266
|
// Check if this is the last part of the last word in the title
|
|
221
267
|
const isLastPart = partIndex === parts.length - 1;
|
|
222
|
-
const isLastWordInTitle =
|
|
268
|
+
const isLastWordInTitle =
|
|
269
|
+
i === words.length - 1 ||
|
|
270
|
+
(i === words.length - 2 && /^\s+$/.test(words[words.length - 1]));
|
|
223
271
|
const isLastWord = isLastPart && isLastWordInTitle;
|
|
224
272
|
|
|
225
|
-
const shouldCap = shouldBeCapitalized(
|
|
226
|
-
|
|
273
|
+
const shouldCap = shouldBeCapitalized(
|
|
274
|
+
part,
|
|
275
|
+
isFirstWord || partIndex > 0,
|
|
276
|
+
isAfterPunctuation,
|
|
277
|
+
isLastWord
|
|
278
|
+
);
|
|
279
|
+
return shouldCap
|
|
280
|
+
? part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
|
|
281
|
+
: part.toLowerCase();
|
|
227
282
|
});
|
|
228
283
|
result += capitalizedParts.join('-');
|
|
229
284
|
} else {
|
|
@@ -242,9 +297,18 @@ describe('APA Title Case Validation', () => {
|
|
|
242
297
|
result += 'LA';
|
|
243
298
|
} else {
|
|
244
299
|
// Regular word - check if this is the last non-whitespace word
|
|
245
|
-
const isLastWordInTitle =
|
|
246
|
-
|
|
247
|
-
|
|
300
|
+
const isLastWordInTitle =
|
|
301
|
+
i === words.length - 1 ||
|
|
302
|
+
(i === words.length - 2 && /^\s+$/.test(words[words.length - 1]));
|
|
303
|
+
const shouldCap = shouldBeCapitalized(
|
|
304
|
+
segment,
|
|
305
|
+
isFirstWord,
|
|
306
|
+
isAfterPunctuation,
|
|
307
|
+
isLastWordInTitle
|
|
308
|
+
);
|
|
309
|
+
result += shouldCap
|
|
310
|
+
? segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase()
|
|
311
|
+
: segment.toLowerCase();
|
|
248
312
|
}
|
|
249
313
|
}
|
|
250
314
|
|
package/vitest.config.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { defineConfig } from 'vitest/config';
|
|
2
2
|
|
|
3
|
+
// Only run heavy/dist-dependent tests in CI
|
|
4
|
+
const isCI = !!process.env.CI;
|
|
5
|
+
|
|
3
6
|
export default defineConfig({
|
|
4
7
|
test: {
|
|
5
8
|
include: ['tests/**/*.test.js'],
|
|
9
|
+
// Exclude CI-only tests locally. GitHub Actions sets CI=true, so these will run in CI.
|
|
10
|
+
exclude: isCI ? [] : ['tests/*.ci.test.js'],
|
|
6
11
|
environment: 'node',
|
|
7
12
|
},
|
|
8
13
|
});
|
|
File without changes
|
|
File without changes
|