LGTM
This commit is contained in:
parent
b1acdff594
commit
4e92913925
8 changed files with 743 additions and 124 deletions
115
src/vibes.test.ts
Normal file
115
src/vibes.test.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { gardenAudioConfig } from './audio/garden-audio-config';
|
||||
import { getInitialVibe, hexToRgb, VIBE_PRESETS } from './vibes';
|
||||
|
||||
const originalLocalStorage = globalThis.localStorage;
|
||||
const originalWindow = globalThis.window;
|
||||
|
||||
const setBrowserVibeState = ({
|
||||
search = '',
|
||||
storedVibeId = null,
|
||||
}: {
|
||||
search?: string;
|
||||
storedVibeId?: string | null;
|
||||
}) => {
|
||||
Object.defineProperty(globalThis, 'window', {
|
||||
configurable: true,
|
||||
value: {
|
||||
location: new URL(`https://garden.test/${search}`),
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(globalThis, 'localStorage', {
|
||||
configurable: true,
|
||||
value: {
|
||||
getItem: vi.fn((key: string) =>
|
||||
key === 'fleeting-garden:vibe' ? storedVibeId : null
|
||||
),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('vibe URL selection', () => {
|
||||
afterEach(() => {
|
||||
Object.defineProperty(globalThis, 'window', {
|
||||
configurable: true,
|
||||
value: originalWindow,
|
||||
});
|
||||
Object.defineProperty(globalThis, 'localStorage', {
|
||||
configurable: true,
|
||||
value: originalLocalStorage,
|
||||
});
|
||||
});
|
||||
|
||||
it('uses a valid vibe id from the URL before local storage', () => {
|
||||
setBrowserVibeState({
|
||||
search: '?vibe=moon-orchid',
|
||||
storedVibeId: 'candy-rain',
|
||||
});
|
||||
|
||||
expect(getInitialVibe().id).toBe('moon-orchid');
|
||||
});
|
||||
|
||||
it('uses a valid stored vibe id when the URL does not provide one', () => {
|
||||
setBrowserVibeState({ storedVibeId: 'sunlit-moss' });
|
||||
|
||||
expect(getInitialVibe().id).toBe('sunlit-moss');
|
||||
});
|
||||
|
||||
it('falls back to the default preset for an unknown URL vibe id', () => {
|
||||
setBrowserVibeState({
|
||||
search: '?vibe=unknown',
|
||||
storedVibeId: 'sunlit-moss',
|
||||
});
|
||||
|
||||
expect(getInitialVibe()).toBe(VIBE_PRESETS[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vibe and audio config contract', () => {
|
||||
it('keeps preset ids unique, URL-safe, and covered by audio profiles', () => {
|
||||
const vibeIds = VIBE_PRESETS.map((vibe) => vibe.id);
|
||||
const audioIds = Object.keys(gardenAudioConfig.vibes);
|
||||
|
||||
expect(new Set(vibeIds).size).toBe(vibeIds.length);
|
||||
expect(vibeIds.every((id) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(id))).toBe(true);
|
||||
expect(audioIds.slice().sort()).toEqual(vibeIds.slice().sort());
|
||||
expect(vibeIds).toContain(gardenAudioConfig.fallbackVibeId);
|
||||
});
|
||||
|
||||
it('keeps each vibe palette and audio profile complete', () => {
|
||||
VIBE_PRESETS.forEach((vibe) => {
|
||||
expect(vibe.colors).toHaveLength(3);
|
||||
vibe.colors.forEach((color) => {
|
||||
expect(color).toMatch(/^#[0-9a-f]{6}$/i);
|
||||
hexToRgb(color).forEach((channel) => {
|
||||
expect(channel).toBeGreaterThanOrEqual(0);
|
||||
expect(channel).toBeLessThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
const profile = gardenAudioConfig.vibes[vibe.id];
|
||||
expect(Number.isFinite(profile.rootMidi)).toBe(true);
|
||||
expect(profile.scale.length).toBeGreaterThan(0);
|
||||
expect(profile.scale.every((degree) => Number.isFinite(degree))).toBe(true);
|
||||
expect(profile.brightness).toBeGreaterThan(0);
|
||||
expect(profile.delayTimeMultiplier).toBeGreaterThan(0);
|
||||
expect(profile.progression.length).toBeGreaterThan(0);
|
||||
profile.progression.forEach((chord) => {
|
||||
expect(Number.isFinite(chord.rootOffset)).toBe(true);
|
||||
expect(['major', 'minor']).toContain(chord.quality);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps audio color voices aligned with the three vibe palette slots', () => {
|
||||
expect(gardenAudioConfig.colorVoices).toHaveLength(3);
|
||||
gardenAudioConfig.colorVoices.forEach((voice) => {
|
||||
expect(Number.isFinite(voice.scaleDegreeOffset)).toBe(true);
|
||||
expect(Number.isFinite(voice.octaveOffset)).toBe(true);
|
||||
expect(voice.velocityMultiplier).toBeGreaterThan(0);
|
||||
expect(Math.abs(voice.panOffset)).toBeLessThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue