Previously tags where very much focused on color coding email and less about adding additional information. They were also visualized in different ways in different locations. This commit gets rid of all "Color-coding" references, aligns visualization of the tags across the whole project and tries to improve user experience of using tags in general. A search box is shown in the tagging control so the user can quickly search for a tag if they have a huge (more than 10) amount of tags.
211 lines
8.5 KiB
TypeScript
211 lines
8.5 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { useSettingsStore, DEFAULT_KEYWORDS, KEYWORD_PALETTE, KEYWORD_PALETTE_ROWS, getKeywordVisibility } from '../settings-store';
|
|
import type { KeywordDefinition } from '../settings-store';
|
|
|
|
describe('settings-store keywords', () => {
|
|
beforeEach(() => {
|
|
useSettingsStore.setState({ emailKeywords: [...DEFAULT_KEYWORDS] });
|
|
});
|
|
|
|
describe('DEFAULT_KEYWORDS', () => {
|
|
it('has 7 default keywords', () => {
|
|
expect(DEFAULT_KEYWORDS).toHaveLength(7);
|
|
});
|
|
|
|
it('each default keyword has a valid palette color', () => {
|
|
DEFAULT_KEYWORDS.forEach((kw) => {
|
|
expect(KEYWORD_PALETTE[kw.color]).toBeDefined();
|
|
expect(KEYWORD_PALETTE[kw.color].dot).toBeTruthy();
|
|
expect(KEYWORD_PALETTE[kw.color].fill).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
it('all default keyword ids are unique', () => {
|
|
const ids = DEFAULT_KEYWORDS.map((k) => k.id);
|
|
expect(new Set(ids).size).toBe(ids.length);
|
|
});
|
|
});
|
|
|
|
describe('KEYWORD_PALETTE', () => {
|
|
it('has a lighter, base and darker shade of every hue', () => {
|
|
expect(KEYWORD_PALETTE_ROWS).toHaveLength(3);
|
|
KEYWORD_PALETTE_ROWS.forEach((row) => expect(row).toHaveLength(13));
|
|
expect(Object.keys(KEYWORD_PALETTE)).toHaveLength(39);
|
|
});
|
|
|
|
it('lays every row out in the same hue order', () => {
|
|
const [light, base, dark] = KEYWORD_PALETTE_ROWS;
|
|
expect(light).toEqual(base.map((key) => `${key}-light`));
|
|
expect(dark).toEqual(base.map((key) => `${key}-dark`));
|
|
});
|
|
|
|
it('keeps the bare hue name on the base row, so saved tags still resolve', () => {
|
|
// A tag stored as `red` predates the lighter and darker rows.
|
|
expect(KEYWORD_PALETTE_ROWS[1]).toContain('red');
|
|
expect(KEYWORD_PALETTE.red).toBeDefined();
|
|
});
|
|
|
|
it('spells every class out so Tailwind can find it', () => {
|
|
// A composed class name would compile to nothing, so none may be built
|
|
// at runtime and each has to carry its own utility prefix.
|
|
Object.values(KEYWORD_PALETTE).forEach((entry) => {
|
|
expect(entry.dot).toMatch(/^bg-/);
|
|
expect(entry.fill).toMatch(/^bg-/);
|
|
expect(entry.border).toMatch(/^border-/);
|
|
expect(entry.text).toMatch(/^text-.* dark:text-/);
|
|
expect(entry.rowTint).toMatch(/^bg-.* dark:bg-/);
|
|
});
|
|
});
|
|
|
|
it('resolves every row key', () => {
|
|
KEYWORD_PALETTE_ROWS.flat().forEach((key) => {
|
|
expect(KEYWORD_PALETTE[key]).toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('addKeyword', () => {
|
|
it('adds a new keyword to the list', () => {
|
|
const newKw: KeywordDefinition = { id: 'custom', label: 'Custom', color: 'teal' };
|
|
useSettingsStore.getState().addKeyword(newKw);
|
|
const keywords = useSettingsStore.getState().emailKeywords;
|
|
expect(keywords).toHaveLength(DEFAULT_KEYWORDS.length + 1);
|
|
expect(keywords[keywords.length - 1]).toEqual(newKw);
|
|
});
|
|
|
|
it('does not add duplicate keyword id', () => {
|
|
const duplicate: KeywordDefinition = { id: 'red', label: 'Another Red', color: 'red' };
|
|
useSettingsStore.getState().addKeyword(duplicate);
|
|
expect(useSettingsStore.getState().emailKeywords).toHaveLength(DEFAULT_KEYWORDS.length);
|
|
});
|
|
|
|
it('allows adding keyword after removing one with same id', () => {
|
|
useSettingsStore.getState().removeKeyword('red');
|
|
const newRed: KeywordDefinition = { id: 'red', label: 'New Red', color: 'red' };
|
|
useSettingsStore.getState().addKeyword(newRed);
|
|
const kw = useSettingsStore.getState().emailKeywords.find((k) => k.id === 'red');
|
|
expect(kw?.label).toBe('New Red');
|
|
});
|
|
});
|
|
|
|
describe('updateKeyword', () => {
|
|
it('updates label of existing keyword', () => {
|
|
useSettingsStore.getState().updateKeyword('red', { label: 'Crimson' });
|
|
const kw = useSettingsStore.getState().emailKeywords.find((k) => k.id === 'red');
|
|
expect(kw?.label).toBe('Crimson');
|
|
expect(kw?.color).toBe('red'); // color unchanged
|
|
});
|
|
|
|
it('updates color of existing keyword', () => {
|
|
useSettingsStore.getState().updateKeyword('blue', { color: 'cyan' });
|
|
const kw = useSettingsStore.getState().emailKeywords.find((k) => k.id === 'blue');
|
|
expect(kw?.color).toBe('cyan');
|
|
expect(kw?.label).toBe('Blue'); // label unchanged
|
|
});
|
|
|
|
it('updates both label and color', () => {
|
|
useSettingsStore.getState().updateKeyword('green', { label: 'Emerald', color: 'teal' });
|
|
const kw = useSettingsStore.getState().emailKeywords.find((k) => k.id === 'green');
|
|
expect(kw?.label).toBe('Emerald');
|
|
expect(kw?.color).toBe('teal');
|
|
});
|
|
|
|
it('does not affect other keywords', () => {
|
|
useSettingsStore.getState().updateKeyword('red', { label: 'Crimson' });
|
|
const blue = useSettingsStore.getState().emailKeywords.find((k) => k.id === 'blue');
|
|
expect(blue?.label).toBe('Blue');
|
|
});
|
|
|
|
it('is a no-op for non-existent id', () => {
|
|
const before = useSettingsStore.getState().emailKeywords;
|
|
useSettingsStore.getState().updateKeyword('nonexistent', { label: 'Test' });
|
|
const after = useSettingsStore.getState().emailKeywords;
|
|
expect(after).toHaveLength(before.length);
|
|
});
|
|
});
|
|
|
|
describe('removeKeyword', () => {
|
|
it('removes a keyword by id', () => {
|
|
useSettingsStore.getState().removeKeyword('red');
|
|
const keywords = useSettingsStore.getState().emailKeywords;
|
|
expect(keywords).toHaveLength(DEFAULT_KEYWORDS.length - 1);
|
|
expect(keywords.find((k) => k.id === 'red')).toBeUndefined();
|
|
});
|
|
|
|
it('is a no-op for non-existent id', () => {
|
|
useSettingsStore.getState().removeKeyword('nonexistent');
|
|
expect(useSettingsStore.getState().emailKeywords).toHaveLength(DEFAULT_KEYWORDS.length);
|
|
});
|
|
|
|
it('preserves order of remaining keywords', () => {
|
|
useSettingsStore.getState().removeKeyword('green');
|
|
const ids = useSettingsStore.getState().emailKeywords.map((k) => k.id);
|
|
expect(ids).toEqual(['red', 'orange', 'yellow', 'blue', 'purple', 'pink']);
|
|
});
|
|
});
|
|
|
|
describe('reorderKeywords', () => {
|
|
it('replaces keyword list with new ordering', () => {
|
|
const reversed = [...DEFAULT_KEYWORDS].reverse();
|
|
useSettingsStore.getState().reorderKeywords(reversed);
|
|
const ids = useSettingsStore.getState().emailKeywords.map((k) => k.id);
|
|
expect(ids).toEqual(reversed.map((k) => k.id));
|
|
});
|
|
|
|
it('can set to empty list', () => {
|
|
useSettingsStore.getState().reorderKeywords([]);
|
|
expect(useSettingsStore.getState().emailKeywords).toHaveLength(0);
|
|
});
|
|
|
|
it('can reset to defaults', () => {
|
|
useSettingsStore.getState().removeKeyword('red');
|
|
useSettingsStore.getState().removeKeyword('blue');
|
|
useSettingsStore.getState().reorderKeywords(DEFAULT_KEYWORDS);
|
|
expect(useSettingsStore.getState().emailKeywords).toEqual(DEFAULT_KEYWORDS);
|
|
});
|
|
});
|
|
|
|
describe('getKeywordById', () => {
|
|
it('finds keyword by id', () => {
|
|
const kw = useSettingsStore.getState().getKeywordById('blue');
|
|
expect(kw).toEqual({ id: 'blue', label: 'Blue', color: 'blue' });
|
|
});
|
|
|
|
it('returns undefined for non-existent id', () => {
|
|
expect(useSettingsStore.getState().getKeywordById('nonexistent')).toBeUndefined();
|
|
});
|
|
|
|
it('returns updated keyword data after updateKeyword', () => {
|
|
useSettingsStore.getState().updateKeyword('red', { label: 'Scarlet' });
|
|
const kw = useSettingsStore.getState().getKeywordById('red');
|
|
expect(kw?.label).toBe('Scarlet');
|
|
});
|
|
});
|
|
|
|
describe('getKeywordVisibility', () => {
|
|
it('treats a tag stored before visibility was configurable as always shown', () => {
|
|
expect(getKeywordVisibility({ id: 'red', label: 'Red', color: 'red' })).toBe('show');
|
|
});
|
|
|
|
it('returns the stored choice when there is one', () => {
|
|
expect(getKeywordVisibility({ id: 'red', label: 'Red', color: 'red', visibility: 'unread' })).toBe('unread');
|
|
expect(getKeywordVisibility({ id: 'red', label: 'Red', color: 'red', visibility: 'hide' })).toBe('hide');
|
|
});
|
|
});
|
|
|
|
describe('nestedTags', () => {
|
|
it('is off by default', () => {
|
|
useSettingsStore.getState().resetToDefaults();
|
|
expect(useSettingsStore.getState().nestedTags).toBe(false);
|
|
});
|
|
|
|
it('is included in exported settings', () => {
|
|
useSettingsStore.getState().updateSetting('nestedTags', true);
|
|
const exported = JSON.parse(useSettingsStore.getState().exportSettings()) as {
|
|
nestedTags?: boolean;
|
|
};
|
|
expect(exported.nestedTags).toBe(true);
|
|
});
|
|
});
|
|
});
|