color-name-list 12.2.0 → 13.1.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 (52) hide show
  1. package/.github/workflows/build-and-release.yml +3 -3
  2. package/.github/workflows/build.yml +1 -1
  3. package/.husky/pre-commit +1 -1
  4. package/CONTRIBUTING.md +1 -1
  5. package/README.md +2 -2
  6. package/changes.svg +3 -3
  7. package/dist/colornames.bestof.csv +5 -4
  8. package/dist/colornames.bestof.esm.js +1 -1
  9. package/dist/colornames.bestof.esm.mjs +1 -1
  10. package/dist/colornames.bestof.html +1 -1
  11. package/dist/colornames.bestof.json +1 -1
  12. package/dist/colornames.bestof.min.json +1 -1
  13. package/dist/colornames.bestof.scss +1 -1
  14. package/dist/colornames.bestof.umd.js +1 -1
  15. package/dist/colornames.bestof.xml +17 -13
  16. package/dist/colornames.bestof.yaml +13 -10
  17. package/dist/colornames.csv +11 -38
  18. package/dist/colornames.esm.js +1 -1
  19. package/dist/colornames.esm.mjs +1 -1
  20. package/dist/colornames.html +1 -1
  21. package/dist/colornames.json +1 -1
  22. package/dist/colornames.min.json +1 -1
  23. package/dist/colornames.scss +1 -1
  24. package/dist/colornames.short.csv +3 -2
  25. package/dist/colornames.short.esm.js +1 -1
  26. package/dist/colornames.short.esm.mjs +1 -1
  27. package/dist/colornames.short.html +1 -1
  28. package/dist/colornames.short.json +1 -1
  29. package/dist/colornames.short.min.json +1 -1
  30. package/dist/colornames.short.scss +1 -1
  31. package/dist/colornames.short.umd.js +1 -1
  32. package/dist/colornames.short.xml +9 -5
  33. package/dist/colornames.short.yaml +7 -4
  34. package/dist/colornames.umd.js +1 -1
  35. package/dist/colornames.xml +17 -125
  36. package/dist/colornames.yaml +15 -96
  37. package/dist/history.json +1 -1
  38. package/package.json +2 -4
  39. package/scripts/build.js +33 -161
  40. package/scripts/lib.js +38 -6
  41. package/scripts/sortSrc.js +5 -6
  42. package/src/colornames.csv +14 -41
  43. package/tests/_utils/report.js +76 -0
  44. package/tests/csv-test-data.js +108 -0
  45. package/tests/duplicate-allowlist.json +29 -1
  46. package/tests/duplicate-plurals-allowlist.json +4 -1
  47. package/tests/duplicates.test.js +246 -40
  48. package/tests/formats.test.js +9 -8
  49. package/tests/imports.test.js +12 -14
  50. package/tests/sorting.test.js +10 -12
  51. package/tests/title-case.test.js +320 -0
  52. package/tests/validations.test.js +219 -0
@@ -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
+ }
@@ -0,0 +1,108 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { parseCSVString } from '../scripts/lib.js';
4
+
5
+ /**
6
+ * Shared CSV test data loader
7
+ * Provides consistent CSV data across all test files
8
+ */
9
+ class CSVTestData {
10
+ constructor() {
11
+ this._data = null;
12
+ this._raw = null;
13
+ this._lines = null;
14
+ this._items = null;
15
+ }
16
+
17
+ /**
18
+ * Load and parse the CSV file once
19
+ */
20
+ load() {
21
+ if (this._data) return; // Already loaded
22
+
23
+ const csvPath = path.resolve('./src/colornames.csv');
24
+ this._raw = fs.readFileSync(csvPath, 'utf8').replace(/\r\n/g, '\n').trimEnd();
25
+
26
+ // Parse with parseCSVString for structured access
27
+ this._data = parseCSVString(this._raw);
28
+
29
+ // Parse for line-by-line access (used by duplicates/sorting tests)
30
+ this._lines = this._raw.split('\n');
31
+ const header = this._lines.shift();
32
+
33
+ if (!header.startsWith('name,hex,good name')) {
34
+ throw new Error('Invalid CSV header format');
35
+ }
36
+
37
+ // Create items array with line numbers for duplicate checking
38
+ this._items = this._lines
39
+ .map((l, idx) => {
40
+ const lineNumber = idx + 2; // +1 for header, +1 because idx is 0-based
41
+ if (!l.trim()) return null;
42
+ const firstComma = l.indexOf(',');
43
+ const name = firstComma === -1 ? l : l.slice(0, firstComma);
44
+ return { name, lineNumber };
45
+ })
46
+ .filter(Boolean);
47
+ }
48
+
49
+ /**
50
+ * Get the parsed CSV data (using parseCSVString format)
51
+ */
52
+ get data() {
53
+ this.load();
54
+ return this._data;
55
+ }
56
+
57
+ /**
58
+ * Get the raw CSV content
59
+ */
60
+ get raw() {
61
+ this.load();
62
+ return this._raw;
63
+ }
64
+
65
+ /**
66
+ * Get CSV lines (without header)
67
+ */
68
+ get lines() {
69
+ this.load();
70
+ return this._lines;
71
+ }
72
+
73
+ /**
74
+ * Get items array with line numbers for duplicate checking
75
+ */
76
+ get items() {
77
+ this.load();
78
+ return this._items;
79
+ }
80
+
81
+ /**
82
+ * Get colors in simple format (name, hex)
83
+ */
84
+ get colors() {
85
+ this.load();
86
+ return this._data.entries.map((entry) => ({
87
+ name: entry.name,
88
+ hex: entry.hex,
89
+ }));
90
+ }
91
+
92
+ /**
93
+ * Get line count including header
94
+ */
95
+ get lineCount() {
96
+ this.load();
97
+ return this._lines.length + 1; // +1 for header
98
+ }
99
+ }
100
+
101
+ // Export a singleton instance
102
+ export const csvTestData = new CSVTestData();
103
+
104
+ // Export a convenience function to ensure data is loaded
105
+ export function loadCSVTestData() {
106
+ csvTestData.load();
107
+ return csvTestData;
108
+ }
@@ -8,5 +8,33 @@
8
8
  "Sable",
9
9
  "Sablé",
10
10
  "Rosé",
11
- "Fresh Water"
11
+ "Fresh Water",
12
+ "Undersea",
13
+ "In the Twilight",
14
+ "In the Tropics",
15
+ "Teal With It",
16
+ "In the Spotlight",
17
+ "In the Shadows",
18
+ "By the Sea",
19
+ "Sail On",
20
+ "In the Red",
21
+ "In the Pink",
22
+ "In A Pickle",
23
+ "In for a Penny",
24
+ "On the Nile",
25
+ "In the Navy",
26
+ "Mint to Be",
27
+ "Mauve It",
28
+ "At the Beach",
29
+ "In the Blue",
30
+ "Blue by You",
31
+ "In the Buff",
32
+ "Buff It",
33
+ "Buzz-In",
34
+ "Coffee With Cream",
35
+ "In the Dark",
36
+ "Dark as Night",
37
+ "Green With Envy",
38
+ "Lost in Space",
39
+ "Fruit of Passion"
12
40
  ]
@@ -3,5 +3,8 @@
3
3
  "Blues",
4
4
  "Greens",
5
5
  "Nighthawks",
6
- "Spearmints"
6
+ "Spearmints",
7
+ "In the Vines",
8
+ "On the Rocks",
9
+ "Sea of Stars"
7
10
  ]
@@ -1,44 +1,147 @@
1
- import { describe, it, expect } from 'vitest';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { findNearDuplicateNameConflicts } from '../scripts/lib.js';
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import { findNearDuplicateNameConflicts, findDuplicates } from '../scripts/lib.js';
5
3
  import allowlist from './duplicate-allowlist.json';
6
4
  import pluralAllowlist from './duplicate-plurals-allowlist.json';
5
+ import { csvTestData } from './csv-test-data.js';
6
+ import { buildFailureMessage } from './_utils/report.js';
7
7
 
8
8
  describe('Duplicate-like color names', () => {
9
- it('should not contain names that only differ by spacing/punctuation/case/accents', () => {
10
- const csvPath = path.resolve('./src/colornames.csv');
11
- const raw = fs.readFileSync(csvPath, 'utf8').replace(/\r\n/g, '\n').trimEnd();
12
- const lines = raw.split('\n');
13
- expect(lines.length).toBeGreaterThan(1);
9
+ beforeAll(() => {
10
+ // Load CSV data once for all tests
11
+ csvTestData.load();
12
+ });
13
+
14
+ // Shared stopwords for normalization across tests
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',
21
+ ];
22
+
23
+ // 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();
30
+
31
+ const tokenize = (name) => {
32
+ const base = normalize(name);
33
+ const tokens = base.match(/[a-z0-9]+/g) || [];
34
+ const stopSet = new Set(STOPWORDS.map((w) => normalize(w)));
35
+ return tokens.filter((t) => t && !stopSet.has(t));
36
+ };
14
37
 
15
- const header = lines.shift();
16
- expect(header.startsWith('name,hex')).toBe(true);
38
+ // Detect two-word names that are exact reversals of each other (after stopword filtering)
39
+ function findTwoWordReversedPairs(items) {
40
+ const groups = new Map(); // key: sorted pair "a|b" -> list of { name, lineNumber, order: "a b" }
41
+
42
+ for (const item of items) {
43
+ if (!item || typeof item.name !== 'string') continue;
44
+ const tokens = tokenize(item.name);
45
+ if (tokens.length !== 2) continue; // only 2-token (after stopword removal)
46
+ const [a, b] = tokens;
47
+ if (!a || !b) continue;
48
+ if (a === b) continue; // "blue blue" reversed is the same – ignore
49
+
50
+ const key = [a, b].sort().join('|');
51
+ const order = `${a} ${b}`;
52
+ if (!groups.has(key)) groups.set(key, []);
53
+ groups.get(key).push({ name: item.name, lineNumber: item.lineNumber, order });
54
+ }
17
55
 
18
- const items = lines.map((l, idx) => {
19
- const lineNumber = idx + 2; // +1 for header, +1 because idx is 0-based
20
- if (!l.trim()) return null;
21
- const firstComma = l.indexOf(',');
22
- const name = firstComma === -1 ? l : l.slice(0, firstComma);
23
- return { name, lineNumber };
24
- }).filter(Boolean);
56
+ const conflicts = [];
57
+ for (const [key, entries] of groups.entries()) {
58
+ // We have a potential conflict if we see both orders "a b" and "b a"
59
+ const uniqOrders = [...new Set(entries.map((e) => e.order))];
60
+ if (uniqOrders.length < 2) continue;
61
+
62
+ const [t1, t2] = key.split('|');
63
+ const forward = `${t1} ${t2}`;
64
+ const backward = `${t2} ${t1}`;
65
+
66
+ const hasForward = uniqOrders.includes(forward);
67
+ const hasBackward = uniqOrders.includes(backward);
68
+
69
+ if (hasForward && hasBackward) {
70
+ // Keep entries unique by name@line and sorted by line number for stable output
71
+ const seen = new Set();
72
+ const unique = entries
73
+ .filter((e) => {
74
+ const k = `${e.name}@${e.lineNumber}`;
75
+ if (seen.has(k)) return false;
76
+ seen.add(k);
77
+ return true;
78
+ })
79
+ .sort((a, b) => a.lineNumber - b.lineNumber);
80
+
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;
88
+
89
+ conflicts.push({ key, tokens: [t1, t2], entries: unique });
90
+ }
91
+ }
25
92
 
26
- const conflicts = findNearDuplicateNameConflicts(items, { allowlist, foldPlurals: true, pluralAllowlist });
93
+ return conflicts;
94
+ }
95
+
96
+ it('should not contain the same name twice', () => {
97
+ expect(csvTestData.lineCount).toBeGreaterThan(1);
98
+
99
+ const duplicates = findDuplicates(csvTestData.data.values['name']);
100
+
101
+ if (duplicates.length) {
102
+ throw new Error(
103
+ buildFailureMessage({
104
+ title: 'Found {n} duplicate {items}:',
105
+ offenders: duplicates,
106
+ offenderLabel: 'name',
107
+ details: [
108
+ 'Exact duplicate names are not allowed.',
109
+ 'Please remove duplicates or consolidate to a single preferred name.',
110
+ ],
111
+ tips: [
112
+ 'Edit src/colornames.csv and keep only one entry per name',
113
+ 'When in doubt, prefer the most common or descriptive name',
114
+ ],
115
+ })
116
+ );
117
+ }
118
+
119
+ expect(duplicates.length).toBe(0);
120
+ });
121
+
122
+ it('should not contain names that only differ by spacing/punctuation/case/accents/stopwords', () => {
123
+ expect(csvTestData.lineCount).toBeGreaterThan(1);
124
+
125
+ const conflicts = findNearDuplicateNameConflicts(csvTestData.items, {
126
+ allowlist,
127
+ foldPlurals: true,
128
+ pluralAllowlist,
129
+ foldStopwords: true,
130
+ stopwords: STOPWORDS,
131
+ });
27
132
 
28
133
  if (conflicts.length) {
29
- // Create a helpful error message with examples and hints.
30
- const groupCount = conflicts.length;
31
- const msgLines = [
32
- `Found ${groupCount} duplicate-like group${groupCount === 1 ? '' : 's'} (case/accents/punctuation-insensitive):`,
33
- '',
34
- ];
134
+ // Create a summary of all names across conflict groups
135
+ const allOffendingNames = new Set();
136
+ conflicts.forEach(({ entries }) => entries.forEach((e) => allOffendingNames.add(e.name)));
137
+
138
+ // Build detailed section: groups with line numbers (stable + unique)
139
+ const details = [];
35
140
  conflicts
36
- // make message deterministic by sorting
37
141
  .sort((a, b) => a.norm.localeCompare(b.norm))
38
142
  .forEach(({ norm, entries }) => {
39
143
  const unique = [];
40
144
  const seen = new Set();
41
- // De-duplicate exact same name+line pairs in output
42
145
  for (const e of entries) {
43
146
  const key = `${e.name}@${e.lineNumber}`;
44
147
  if (!seen.has(key)) {
@@ -46,26 +149,129 @@ describe('Duplicate-like color names', () => {
46
149
  unique.push(e);
47
150
  }
48
151
  }
49
- // sort entries by line number for readability
50
152
  unique.sort((a, b) => a.lineNumber - b.lineNumber);
51
- msgLines.push(` • ${norm}:`);
52
- unique.forEach((e) => msgLines.push(` - line ${e.lineNumber}: "${e.name}"`));
153
+ details.push(` • ${norm}:`);
154
+ unique.forEach((e) => details.push(` - line ${e.lineNumber}: "${e.name}"`));
155
+ details.push('');
53
156
  });
54
157
 
55
- msgLines.push(
56
- '',
57
- 'This typically indicates near-duplicates that only differ by spacing/punctuation, like "Snow Pink" vs "Snowpink".',
58
- 'Please unify or remove duplicates to keep the dataset clean.',
59
- '',
60
- 'Tip:',
61
- ' - Edit src/colornames.csv and keep a single preferred spelling. When in doubt, prefer the most common or simplest form or the British spelling.',
62
- ' - After changes, run: npm run sort-colors',
158
+ throw new Error(
159
+ buildFailureMessage({
160
+ title: 'Found {n} duplicate-like {items} (case/accents/punctuation/stopwords-insensitive):',
161
+ offenders: [...allOffendingNames],
162
+ offenderLabel: 'name',
163
+ details: [
164
+ ...details,
165
+ 'This typically indicates near-duplicates that only differ by spacing/punctuation, like "Snow Pink" vs "Snowpink".',
166
+ 'Please unify or remove duplicates to keep the dataset clean.',
167
+ ],
168
+ tips: [
169
+ 'Edit src/colornames.csv and keep a single preferred spelling. When in doubt, prefer the most common or simplest form or the British spelling.',
170
+ 'After changes, run: npm run sort-colors',
171
+ ],
172
+ count: conflicts.length,
173
+ })
63
174
  );
64
-
65
- throw new Error(msgLines.join('\n'));
66
175
  }
67
176
 
68
177
  // If we reach here, no conflicts were found.
69
178
  expect(conflicts.length).toBe(0);
70
179
  });
180
+
181
+ it('should not contain duplicate hex codes', () => {
182
+ // Find duplicates in hex values
183
+ const hexDuplicates = findDuplicates(csvTestData.data.values['hex']);
184
+
185
+ if (hexDuplicates.length) {
186
+ const details = [];
187
+ hexDuplicates.forEach((duplicateHex) => {
188
+ const entriesWithHex = csvTestData.data.entries
189
+ .map((entry, index) => ({ ...entry, lineNumber: index + 2 }))
190
+ .filter((entry) => entry.hex === duplicateHex);
191
+
192
+ details.push(` • ${duplicateHex}:`);
193
+ entriesWithHex.forEach((entry) => {
194
+ details.push(` - line ${entry.lineNumber}: "${entry.name}" (${entry.hex})`);
195
+ });
196
+ details.push('');
197
+ });
198
+
199
+ throw new Error(
200
+ buildFailureMessage({
201
+ title: 'Found {n} duplicate {items}:',
202
+ offenders: hexDuplicates,
203
+ offenderLabel: 'hex code',
204
+ details: [
205
+ ...details,
206
+ 'Duplicate hex codes indicate multiple color names pointing to the same exact color.',
207
+ 'Please remove duplicates or consolidate to a single preferred name.',
208
+ ],
209
+ tips: [
210
+ 'Edit src/colornames.csv and keep only one entry per hex code',
211
+ 'When in doubt, prefer the most common or descriptive name',
212
+ ],
213
+ })
214
+ );
215
+ }
216
+
217
+ expect(hexDuplicates.length).toBe(0);
218
+ });
219
+
220
+ it('should detect names that only differ by stopwords when enabled', () => {
221
+ const items = [
222
+ { name: 'Heart Gold' },
223
+ { name: 'Heart of Gold' },
224
+ ];
225
+ const conflicts = findNearDuplicateNameConflicts(items, {
226
+ foldStopwords: true,
227
+ stopwords: STOPWORDS,
228
+ });
229
+
230
+ expect(conflicts.length).toBe(1);
231
+ expect(conflicts[0].entries.length).toBe(2);
232
+ });
233
+
234
+ it.skip('should not contain two-word names that are exact reversals of each other', () => {
235
+ expect(csvTestData.lineCount).toBeGreaterThan(1);
236
+
237
+ const conflicts = findTwoWordReversedPairs(csvTestData.items);
238
+
239
+ if (conflicts.length) {
240
+ // Build helpful details with line numbers
241
+ const details = [];
242
+ const offenderNames = new Set();
243
+
244
+ conflicts
245
+ .sort((a, b) => a.tokens.join(' ').localeCompare(b.tokens.join(' ')))
246
+ .forEach(({ tokens, entries }) => {
247
+ const [a, b] = tokens;
248
+ details.push(` • ${a} / ${b}:`);
249
+ entries.forEach((e) => {
250
+ offenderNames.add(e.name);
251
+ details.push(` - line ${e.lineNumber}: "${e.name}"`);
252
+ });
253
+ details.push('');
254
+ });
255
+
256
+ throw new Error(
257
+ buildFailureMessage({
258
+ title: 'Found {n} word-order reversed {items}:',
259
+ offenders: [...offenderNames],
260
+ offenderLabel: 'name',
261
+ details: [
262
+ ...details,
263
+ 'Names that only differ by word order (e.g., "Beach Sand" vs "Sand Beach") should be unified.',
264
+ 'Please keep a single preferred order and remove the other.',
265
+ ],
266
+ tips: [
267
+ 'Edit src/colornames.csv and keep only one order for each two-word pair.',
268
+ 'After changes, run: npm run sort-colors',
269
+ ],
270
+ count: conflicts.length,
271
+ })
272
+ );
273
+ }
274
+
275
+ expect(conflicts.length).toBe(0);
276
+ });
71
277
  });
@@ -1,16 +1,17 @@
1
- import { describe, it, expect } from 'vitest';
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  import { parseCSVString } from '../scripts/lib.js';
5
+ import { csvTestData } from './csv-test-data.js';
5
6
 
6
7
  describe('Other Format Tests', () => {
7
- // Load CSV data for comparison
8
- const csvSource = fs.readFileSync(path.resolve('./src/colornames.csv'), 'utf8').toString();
9
- const csvData = parseCSVString(csvSource);
10
- const csvColors = csvData.entries.map(entry => ({
11
- name: entry.name,
12
- hex: entry.hex
13
- }));
8
+ let csvColors;
9
+
10
+ beforeAll(() => {
11
+ // Load CSV data once for all tests
12
+ csvTestData.load();
13
+ csvColors = csvTestData.colors;
14
+ });
14
15
 
15
16
  describe('CSV Output', () => {
16
17
  it('should correctly generate CSV files', () => {
@@ -1,4 +1,4 @@
1
- import { describe, it, expect } from 'vitest';
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
2
 
3
3
  import * as esmColors from '../dist/colornames.esm.js';
4
4
  import * as esmBestOfColors from '../dist/colornames.bestof.esm.js';
@@ -15,18 +15,16 @@ import jsonMinBestOfColors from '../dist/colornames.bestof.min.json' assert { ty
15
15
  import jsonMinShortColors from '../dist/colornames.short.min.json' assert { type: 'json' };
16
16
 
17
17
  // Also import the source CSV file for verification
18
- import fs from 'fs';
19
- import path from 'path';
20
- import { parseCSVString } from '../scripts/lib.js';
18
+ import { csvTestData } from './csv-test-data.js';
21
19
 
22
20
  describe('Color Names Import Tests', () => {
23
- // Load CSV data for comparison
24
- const csvSource = fs.readFileSync(path.resolve('./src/colornames.csv'), 'utf8').toString();
25
- const csvData = parseCSVString(csvSource);
26
- const csvColors = csvData.entries.map(entry => ({
27
- name: entry.name,
28
- hex: entry.hex
29
- }));
21
+ let csvColors;
22
+
23
+ beforeAll(() => {
24
+ // Load CSV data for comparison
25
+ csvTestData.load();
26
+ csvColors = csvTestData.colors;
27
+ });
30
28
 
31
29
  describe('JSON Files', () => {
32
30
  it('should import main JSON file correctly', () => {
@@ -108,11 +106,11 @@ describe('Color Names Import Tests', () => {
108
106
  const commonColors = ['black', 'white', 'red', 'blue', 'green', 'yellow', 'purple', 'pink'];
109
107
 
110
108
  // Convert to lowercase for easier comparison
111
- const allNames = jsonColors.map(color => color.name.toLowerCase());
109
+ const allNames = jsonColors.map((color) => color.name.toLowerCase());
112
110
 
113
- commonColors.forEach(color => {
111
+ commonColors.forEach((color) => {
114
112
  // Check if at least one entry contains this common color name
115
- expect(allNames.some(name => name.includes(color))).toBe(true);
113
+ expect(allNames.some((name) => name.includes(color))).toBe(true);
116
114
  });
117
115
  });
118
116
  });
@@ -1,22 +1,20 @@
1
- import { describe, it, expect } from 'vitest';
2
- import fs from 'fs';
3
- import path from 'path';
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import { csvTestData } from './csv-test-data.js';
4
3
 
5
4
  /**
6
5
  * Ensures that the source CSV file is already sorted alphabetically (case-insensitive)
7
6
  * by the color name. If not, it throws with a helpful message telling how to fix it.
8
7
  */
9
8
  describe('Source CSV sorting', () => {
10
- it('colornames.csv should be sorted by name (case-insensitive)', () => {
11
- const csvPath = path.resolve('./src/colornames.csv');
12
- const raw = fs.readFileSync(csvPath, 'utf8').replace(/\r\n/g, '\n').trimEnd();
13
- const lines = raw.split('\n');
14
- expect(lines.length).toBeGreaterThan(1);
9
+ beforeAll(() => {
10
+ // Load CSV data once
11
+ csvTestData.load();
12
+ });
15
13
 
16
- const header = lines.shift();
17
- expect(header.startsWith('name,hex')).toBe(true);
14
+ it('colornames.csv should be sorted by name (case-insensitive)', () => {
15
+ expect(csvTestData.lineCount).toBeGreaterThan(1);
18
16
 
19
- const entries = lines
17
+ const entries = csvTestData.lines
20
18
  .filter((l) => l.trim().length)
21
19
  .map((l, idx) => {
22
20
  const [name, hex] = l.split(',');
@@ -36,7 +34,7 @@ describe('Source CSV sorting', () => {
36
34
  'To fix automatically run:',
37
35
  ' npm run sort-colors',
38
36
  '',
39
- 'Commit the updated src/colornames.csv after sorting.'
37
+ 'Commit the updated src/colornames.csv after sorting.',
40
38
  ].join('\n')
41
39
  );
42
40
  }