You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/soma/components/waveform/waveform-provider.svelte.te...

140 lines
4.2 KiB

// @vitest-environment jsdom
import { afterEach, describe, expect, it, vi } from 'vitest';
import { createActiveDom } from '$adom';
import { state } from '$libs/reactive';
import type { Morfo } from '$uix/morfo';
import { Soma } from '$soma/core/soma.svelte';
import { createSomaRuntime, type SomaRuntimeSources } from '$soma/runtime.svelte';
import { WaveformProvider, WaveformWaveProvider, buildWavePath } from './waveform-provider.svelte';
function withEffectRoot<T>(fn: () => T): { result: T; cleanup: () => void } {
let result!: T;
const cleanup = $effect.root(() => {
result = fn();
});
return { result, cleanup };
}
function installSomaHarness() {
const dom = createActiveDom();
const soma = {
dom,
// The provider resolves its default label through `soma.langs.ts(...)`;
// the keys embed their English fallback after `|`.
langs: {
ts: (key: string) => {
const i = key.lastIndexOf('|');
return i >= 0 ? key.slice(i + 1) : key;
}
},
runtime: (morfo: Morfo, sources: Omit<SomaRuntimeSources, 'dom' | 'eventEngine'>) =>
createSomaRuntime(morfo, {
dom,
translate: (key) => key,
...sources
})
} as unknown as Soma;
vi.spyOn(Soma, 'require').mockReturnValue(soma);
vi.spyOn(WaveformProvider.ctx, 'set').mockImplementation((value) => value);
return { dom };
}
function waveformOpts(root: HTMLElement) {
return {
id: state('waveform'),
ref: state<HTMLElement | null>(root),
peaks: state([0, 1]),
shape: state<'bars' | 'wave'>('bars'),
value: state([25]),
min: state(0),
max: state(100),
readonly: state(false),
ariaLabel: state<string | undefined>(undefined)
};
}
describe('WaveformProvider', () => {
afterEach(() => {
vi.restoreAllMocks();
document.body.innerHTML = '';
});
it('exposes the provider contract and the bar geometry', () => {
installSomaHarness();
const root = document.createElement('div');
const opts = waveformOpts(root);
const { result: provider, cleanup } = withEffectRoot(() => WaveformProvider.create(opts));
// Bag assert — fase C2c retired the syncAttrs effect (P0, audit 2026-08-26); this test renders nothing.
expect(provider.props).toMatchObject({
id: 'waveform',
'data-waveform': '',
'data-shape': 'bars'
});
// Two peaks → two vertical strokes. Silence paints the perceptual FLOOR
// (a thin line, the SoundCloud reading), a full peak spans the AMP.
expect(provider.wavePath).toBe('M25.00 15.5V16.5M75.00 1V31');
expect(provider.progressFraction).toBe(0.25);
// The embedded Slider's accessible name falls back to the morfo text.
expect(provider.resolvedAriaLabel).toBe('Audio waveform');
cleanup();
});
it('paints the mirrored area for shape=wave and clamps the fraction', () => {
installSomaHarness();
const root = document.createElement('div');
const opts = { ...waveformOpts(root), shape: state<'bars' | 'wave'>('wave') };
const { result: provider, cleanup } = withEffectRoot(() => WaveformProvider.create(opts));
// One closed band (area top edge + reversed baseline + Z).
expect(provider.wavePath.startsWith('M')).toBe(true);
expect(provider.wavePath.endsWith('Z')).toBe(true);
// The fraction clamps to the domain, and a degenerate domain reads 0.
opts.value.current = [150];
expect(provider.progressFraction).toBe(1);
opts.max.current = 0;
expect(provider.progressFraction).toBe(0);
cleanup();
});
it('keeps the Wave presentational: aria-hidden + data-shape from the morfo', () => {
installSomaHarness();
const root = document.createElement('div');
const waveEl = document.createElement('span');
const opts = waveformOpts(root);
const { result, cleanup } = withEffectRoot(() => {
const provider = WaveformProvider.create(opts);
vi.spyOn(WaveformProvider, 'require').mockReturnValue(provider);
const wave = WaveformWaveProvider.create({
id: state('waveform-wave'),
ref: state<HTMLElement | null>(waveEl)
});
return { wave };
});
expect(result.wave.props).toMatchObject({
'data-waveform-wave': '',
'data-shape': 'bars',
'aria-hidden': 'true'
});
cleanup();
});
it('buildWavePath degrades to an empty (valid) path with no peaks', () => {
expect(buildWavePath([], 'bars')).toBe('');
expect(buildWavePath([], 'wave')).toBe('');
});
});

Powered by TurnKey Linux.