This project is a palette creator tool that allows users to generate and customize color palettes for their design projects.
1// oxlint-disable max-lines
2
3import { mount } from '@vue/test-utils';
4
5import { DEFAULT_HEX_COLORS } from '../lib/colors';
6import stateFactory from '../store/state';
7import store from '../store';
8
9import UtilityButtonsPanel from './UtilityButtonsPanel.vue';
10
11// oxlint-disable-next-line max-lines-per-function
12describe('component UtilityButtonsPanel', () => {
13 /** @type {import('@vue/test-utils').VueWrapper} */
14 let wrapper;
15
16 beforeEach(() => {
17 Object.assign(store.state, stateFactory());
18 store.state.mainHSL = 'hsl(20, 20%, 20%)';
19 store.state.allColors = [
20 {
21 hex: '#40BFBF',
22 hsl: 'hsl(180, 50%, 50%)',
23 rgb: 'rgb(64,191,191)',
24 type: 'analogous',
25 },
26 {
27 hex: '#4080BF',
28 hsl: 'hsl(200, 50%, 50%)',
29 rgb: 'rgb(64,128,191)',
30 type: 'complement',
31 },
32 {
33 hex: '#B3C4D6',
34 hsl: 'hsl(220, 50%, 85%)',
35 rgb: 'rgb(179,196,214)',
36 type: 'triad',
37 },
38 {
39 hex: '#13133D',
40 hsl: 'hsl(240, 50%, 15%)',
41 rgb: 'rgb(19,19,61)',
42 type: 'mono',
43 },
44 {
45 hex: '#8040BF',
46 hsl: 'hsl(260, 50%, 50%)',
47 rgb: 'rgb(128,64,191)',
48 type: 'saturation',
49 },
50 ];
51
52 wrapper = mount(UtilityButtonsPanel, {
53 global: { plugins: [store] },
54 });
55 });
56
57 afterEach(() => {
58 wrapper.unmount();
59 vi.restoreAllMocks();
60 });
61
62 it('renders the component', () => {
63 expect(
64 wrapper.find('[data-testid="utility-buttons"]').exists(),
65 ).toBeTruthy();
66 expect(
67 wrapper.find('[data-testid="random-scheme-button"]').exists(),
68 ).toBeTruthy();
69 expect(
70 wrapper.find('[data-testid="one-shot-button"]').exists(),
71 ).toBeTruthy();
72 });
73
74 it('calls SET_RANDOM_SCHEME when random button is clicked', async () => {
75 const spy = vi.spyOn(store, 'dispatch').mockResolvedValue(null);
76
77 await wrapper
78 .find('[data-testid="random-scheme-button"]')
79 .trigger('click');
80
81 expect(spy).toHaveBeenCalledWith('SET_RANDOM_SCHEME');
82 });
83
84 it('fills the slots with random colors', async () => {
85 await wrapper
86 .find('[data-testid="random-scheme-button"]')
87 .trigger('click');
88 await wrapper.vm.$nextTick();
89
90 const { slotColors, mainHSL } = store.state;
91 const slots = ['slot2', 'slot3', 'slot4', 'slot5'];
92
93 for (const slotId of slots) {
94 const color = slotColors[slotId];
95
96 expect(color.hsl).toContain('hsl');
97 expect(color.rgb).toContain('rgb');
98 expect(color.hex).toContain('#');
99
100 // Check uniqueness against main color
101 expect(color.hsl).not.toBe(mainHSL);
102
103 // Check uniqueness against other slots
104 const otherSlots = slots.filter((s) => s !== slotId);
105 for (const otherId of otherSlots) {
106 expect(color.hsl).not.toBe(slotColors[otherId].hsl);
107 }
108 }
109 });
110
111 it('show the one-shot button always, even when the full scheme is not set', () => {
112 store.state.allColors = [];
113 expect(
114 wrapper.find('[data-testid="one-shot-button"]').exists(),
115 ).toBeTruthy();
116 });
117
118 it('shows all buttons when the full scheme is set', async () => {
119 const buttons = [
120 'test-palette-button',
121 'reset-site-colors-button',
122 'set-light-text-button',
123 'set-dark-text-button',
124 'export-css-button',
125 'save-palette-button',
126 ];
127
128 for (const button of buttons) {
129 expect(
130 wrapper.find(`[data-testid="${button}"]`).exists(),
131 ).toBeFalsy();
132 }
133
134 await wrapper
135 .find('[data-testid="random-scheme-button"]')
136 .trigger('click');
137 await wrapper.vm.$nextTick();
138
139 for (const button of buttons) {
140 expect(
141 wrapper.find(`[data-testid="${button}"]`).exists(),
142 ).toBeTruthy();
143 }
144 });
145
146 it('sets the theme colors when the test palette button is clicked', async () => {
147 await wrapper
148 .find('[data-testid="random-scheme-button"]')
149 .trigger('click');
150
151 await wrapper.vm.$nextTick();
152
153 const { mainSlotColor, slotColors } = store.state;
154
155 await wrapper
156 .find('[data-testid="test-palette-button"]')
157 .trigger('click');
158
159 const expectedColors = {
160 '--clr-accent': slotColors.slot3.hex,
161 '--clr-dark': slotColors.slot5.hex,
162 '--clr-light': slotColors.slot4.hex,
163 '--clr-main': mainSlotColor.hex,
164 '--clr-secondary': slotColors.slot2.hex,
165 };
166
167 for (const [variable, expectedHex] of Object.entries(expectedColors)) {
168 const actualHex = getComputedStyle(
169 document.documentElement,
170 ).getPropertyValue(variable);
171 expect(actualHex).toBe(expectedHex);
172 }
173 });
174
175 it('resets the site colors when the reset button is clicked', async () => {
176 await wrapper
177 .find('[data-testid="random-scheme-button"]')
178 .trigger('click');
179
180 await wrapper.vm.$nextTick();
181
182 await wrapper
183 .find('[data-testid="test-palette-button"]')
184 .trigger('click');
185
186 await wrapper.vm.$nextTick();
187
188 const mainColor = getComputedStyle(
189 document.documentElement,
190 ).getPropertyValue('--clr-main');
191
192 expect(mainColor).not.toBe(DEFAULT_HEX_COLORS.MAIN);
193
194 await wrapper
195 .find('[data-testid="reset-site-colors-button"]')
196 .trigger('click');
197
198 const variables = [
199 '--clr-main',
200 '--clr-secondary',
201 '--clr-accent',
202 '--clr-light',
203 '--clr-dark',
204 ];
205
206 for (const variable of variables) {
207 expect(
208 document.documentElement.style.getPropertyValue(variable),
209 ).toBe('');
210 }
211 });
212
213 it('sets the text color to the default light color when the light button is clicked', async () => {
214 await wrapper
215 .find('[data-testid="random-scheme-button"]')
216 .trigger('click');
217
218 await wrapper.vm.$nextTick();
219
220 await wrapper
221 .find('[data-testid="set-light-text-button"]')
222 .trigger('click');
223
224 await wrapper.vm.$nextTick();
225
226 const textColor = getComputedStyle(
227 document.documentElement,
228 ).getPropertyValue('--text-color');
229
230 expect(textColor).toBe(DEFAULT_HEX_COLORS.LIGHT);
231 expect(store.state.textColor.hex).toBe(DEFAULT_HEX_COLORS.LIGHT);
232 });
233
234 it('sets the text color to the default dark color when the dark button is clicked', async () => {
235 await wrapper
236 .find('[data-testid="random-scheme-button"]')
237 .trigger('click');
238
239 await wrapper.vm.$nextTick();
240
241 await wrapper
242 .find('[data-testid="set-dark-text-button"]')
243 .trigger('click');
244
245 await wrapper.vm.$nextTick();
246
247 const textColor = getComputedStyle(
248 document.documentElement,
249 ).getPropertyValue('--text-color');
250
251 expect(textColor).toBe(DEFAULT_HEX_COLORS.DARK);
252 expect(store.state.textColor.hex).toBe(DEFAULT_HEX_COLORS.DARK);
253 });
254
255 it('sends copyPalette event when the export css button is clicked', async () => {
256 await wrapper
257 .find('[data-testid="random-scheme-button"]')
258 .trigger('click');
259
260 await wrapper.vm.$nextTick();
261
262 await wrapper
263 .find('[data-testid="export-css-button"]')
264 .trigger('click');
265
266 expect(wrapper.emitted('copyPalette')).toBeTruthy();
267 });
268
269 it('sends savePalette event when the save palette button is clicked', async () => {
270 await wrapper
271 .find('[data-testid="random-scheme-button"]')
272 .trigger('click');
273
274 await wrapper.vm.$nextTick();
275
276 await wrapper
277 .find('[data-testid="save-palette-button"]')
278 .trigger('click');
279
280 expect(wrapper.emitted('savePalette')).toBeTruthy();
281 });
282
283 it('one-shot button dispatches SET_MAIN_COLOR and SET_RANDOM_SCHEME', async () => {
284 const spy = vi.spyOn(store, 'dispatch').mockResolvedValue(null);
285
286 await wrapper.find('[data-testid="one-shot-button"]').trigger('click');
287
288 expect(spy).toHaveBeenCalledWith('SET_MAIN_COLOR');
289 expect(spy).toHaveBeenCalledWith('SET_RANDOM_SCHEME');
290 });
291
292 it('one-shot button sets a full random palette and applies CSS vars', async () => {
293 await wrapper.find('[data-testid="one-shot-button"]').trigger('click');
294
295 await wrapper.vm.$nextTick();
296
297 const { slotColors, mainSlotColor } = store.state;
298
299 expect(mainSlotColor.hex).not.toBe('');
300
301 const slots = ['slot2', 'slot3', 'slot4', 'slot5'];
302 for (const slotId of slots) {
303 expect(slotColors[slotId].hsl).not.toBe('');
304 }
305
306 const expectedColors = {
307 '--clr-accent': slotColors.slot3.hex,
308 '--clr-dark': slotColors.slot5.hex,
309 '--clr-light': slotColors.slot4.hex,
310 '--clr-main': mainSlotColor.hex,
311 '--clr-secondary': slotColors.slot2.hex,
312 };
313
314 for (const [variable, expectedHex] of Object.entries(expectedColors)) {
315 expect(
316 document.documentElement.style.getPropertyValue(variable),
317 ).toBe(expectedHex);
318 }
319 });
320
321 it('one-shot button sets isTestingColorScheme to true', async () => {
322 await wrapper.find('[data-testid="one-shot-button"]').trigger('click');
323
324 await wrapper.vm.$nextTick();
325
326 expect(store.state.isTestingColorScheme).toBeTruthy();
327 });
328
329 it('Test this palette applies text color from palette (dark theme uses slot4)', async () => {
330 await wrapper
331 .find('[data-testid="random-scheme-button"]')
332 .trigger('click');
333 await wrapper.vm.$nextTick();
334
335 // default theme is dark → text should use slot4
336 const { slot4 } = store.state.slotColors;
337
338 await wrapper
339 .find('[data-testid="test-palette-button"]')
340 .trigger('click');
341 await wrapper.vm.$nextTick();
342
343 expect(store.state.textColor.hex).toBe(slot4.hex);
344 expect(
345 document.documentElement.style.getPropertyValue('--text-color'),
346 ).toBe(slot4.hex);
347 });
348
349 it('Light Text uses slot4 color when testing', async () => {
350 await wrapper
351 .find('[data-testid="random-scheme-button"]')
352 .trigger('click');
353 await wrapper.vm.$nextTick();
354
355 store.commit('SET_IS_TESTING', true);
356
357 const { slot4 } = store.state.slotColors;
358
359 await wrapper
360 .find('[data-testid="set-light-text-button"]')
361 .trigger('click');
362 await wrapper.vm.$nextTick();
363
364 expect(store.state.textColor.hex).toBe(slot4.hex);
365 });
366
367 it('Dark Text uses slot5 color when testing', async () => {
368 await wrapper
369 .find('[data-testid="random-scheme-button"]')
370 .trigger('click');
371 await wrapper.vm.$nextTick();
372
373 store.commit('SET_IS_TESTING', true);
374
375 const { slot5 } = store.state.slotColors;
376
377 await wrapper
378 .find('[data-testid="set-dark-text-button"]')
379 .trigger('click');
380 await wrapper.vm.$nextTick();
381
382 expect(store.state.textColor.hex).toBe(slot5.hex);
383 });
384
385 it('Light Text uses theme default when not testing', async () => {
386 await wrapper
387 .find('[data-testid="random-scheme-button"]')
388 .trigger('click');
389 await wrapper.vm.$nextTick();
390
391 store.commit('SET_IS_TESTING', false);
392
393 await wrapper
394 .find('[data-testid="set-light-text-button"]')
395 .trigger('click');
396 await wrapper.vm.$nextTick();
397
398 expect(store.state.textColor.hex).toBe(DEFAULT_HEX_COLORS.LIGHT);
399 });
400
401 // oxlint-disable-next-line max-statements
402 it('Reset restores text color to theme default', async () => {
403 await wrapper
404 .find('[data-testid="random-scheme-button"]')
405 .trigger('click');
406 await wrapper.vm.$nextTick();
407
408 store.commit('SET_IS_TESTING', true);
409 await store.dispatch('SET_TEXT_COLOR', 'dark');
410
411 await wrapper
412 .find('[data-testid="reset-site-colors-button"]')
413 .trigger('click');
414 await wrapper.vm.$nextTick();
415
416 expect(store.state.isTestingColorScheme).toBeFalsy();
417 // default theme is dark → textType becomes 'light'
418 expect(store.state.textColor.hex).toBe(DEFAULT_HEX_COLORS.LIGHT);
419 });
420});