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.
Files changed (42) hide show
  1. package/README.md +49 -3
  2. package/changes.svg +3 -3
  3. package/dist/colornames.bestof.csv +43 -17
  4. package/dist/colornames.bestof.esm.js +1 -1
  5. package/dist/colornames.bestof.esm.mjs +1 -1
  6. package/dist/colornames.bestof.html +1 -1
  7. package/dist/colornames.bestof.json +1 -1
  8. package/dist/colornames.bestof.min.json +1 -1
  9. package/dist/colornames.bestof.scss +1 -1
  10. package/dist/colornames.bestof.umd.js +1 -1
  11. package/dist/colornames.bestof.xml +157 -53
  12. package/dist/colornames.bestof.yaml +119 -41
  13. package/dist/colornames.csv +96 -297
  14. package/dist/colornames.esm.js +1 -1
  15. package/dist/colornames.esm.mjs +1 -1
  16. package/dist/colornames.html +1 -1
  17. package/dist/colornames.json +1 -1
  18. package/dist/colornames.min.json +1 -1
  19. package/dist/colornames.scss +1 -1
  20. package/dist/colornames.short.csv +22 -13
  21. package/dist/colornames.short.esm.js +1 -1
  22. package/dist/colornames.short.esm.mjs +1 -1
  23. package/dist/colornames.short.html +1 -1
  24. package/dist/colornames.short.json +1 -1
  25. package/dist/colornames.short.min.json +1 -1
  26. package/dist/colornames.short.scss +1 -1
  27. package/dist/colornames.short.umd.js +1 -1
  28. package/dist/colornames.short.xml +79 -43
  29. package/dist/colornames.short.yaml +60 -33
  30. package/dist/colornames.umd.js +1 -1
  31. package/dist/colornames.xml +291 -1095
  32. package/dist/colornames.yaml +226 -829
  33. package/dist/history.json +1 -1
  34. package/package.json +1 -1
  35. package/scripts/lib.js +2 -1
  36. package/src/colornames.csv +105 -306
  37. package/tests/duplicate-allowlist.json +44 -1
  38. package/tests/duplicates.test.js +46 -24
  39. package/tests/title-case.test.js +82 -18
  40. package/vitest.config.js +5 -0
  41. /package/tests/{formats.test.js → formats.ci.test.js} +0 -0
  42. /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
  ]
@@ -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', 'the', 'and', 'a', 'an',
17
- 'in', 'on', 'at', 'to', 'for',
18
- 'by', 'with', 'from', 'as', 'is',
19
- 'it', 'this', 'that', 'these', 'those',
20
- 'be', 'are', 'was', 'were', 'or',
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) => String(s)
25
- .toLowerCase()
26
- .normalize('NFD')
27
- .replace(/[\u0300-\u036f]/g, '')
28
- .replace(/[^a-z0-9]+/g, ' ')
29
- .trim();
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 either direction string is allowlisted, skip
82
- const allowSet = new Set(
83
- (Array.isArray(allowlist) ? allowlist : [])
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: 'Found {n} duplicate-like {items} (case/accents/punctuation/stopwords-insensitive):',
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.skip('should not contain two-word names that are exact reversals of each other', () => {
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);
@@ -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(word, isFirstWordOrAfterPunctuation, isAfterPunctuation, isLastWord = false) {
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', 'as', 'but', 'for', 'if', 'nor', 'or', 'so', 'yet',
38
+ 'and',
39
+ 'as',
40
+ 'but',
41
+ 'for',
42
+ 'if',
43
+ 'nor',
44
+ 'or',
45
+ 'so',
46
+ 'yet',
34
47
  // Articles
35
- 'a', 'an', 'the',
48
+ 'a',
49
+ 'an',
50
+ 'the',
36
51
  // Short prepositions
37
- 'at', 'by', 'in', 'of', 'off', 'on', 'per', 'to', 'up', 'via',
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', 'la', 'le', 'les', 'un', 'une', 'du', 'des', 'et', 'ou', 'à', 'au', 'aux'
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', 'style', 'sunset', 'beach', 'hollywood', 'california', 'west coast',
151
- 'city', 'metro', 'downtown', 'freeway', 'boulevard'
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 = /^[0-9]+[A-Z]+$/i.test(segment) &&
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 = i === words.length - 1 || (i === words.length - 2 && /^\s+$/.test(words[words.length - 1]));
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(part, isFirstWord || partIndex > 0, isAfterPunctuation, isLastWord);
226
- return shouldCap ? part.charAt(0).toUpperCase() + part.slice(1).toLowerCase() : part.toLowerCase();
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 = i === words.length - 1 || (i === words.length - 2 && /^\s+$/.test(words[words.length - 1]));
246
- const shouldCap = shouldBeCapitalized(segment, isFirstWord, isAfterPunctuation, isLastWordInTitle);
247
- result += shouldCap ? segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase() : segment.toLowerCase();
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