1import { describe, expect, it } from 'vitest'; 2import { 3 measuredFor, 4 readMeasured, 5 sectionCycles, 6 sectionSpans, 7 setMeasured, 8 songHash, 9 withoutSectionParts, 10} from './measured.mjs'; 11import { artRequest } from './critic.mjs'; 12 13const spec = '---\ntitle: X\nsong: song.js\ncritic:\n art: 0.6\n---\n\n# X\n'; 14const code = 'setcps(1)\n'; 15const m = { date: '2026-09-24', song: songHash(code), total: { loudnessDbfs: -16 }, parts: { riff: { loudnessDbfs: -20 } } }; 16 17describe('measured', () => { 18 it('writes one JSON line after song:, and reads it back', () => { 19 const written = setMeasured(spec, m); 20 expect(written).toMatch(/^song: song\.js\nmeasured: \{/m); 21 expect(readMeasured(written)).toEqual(m); 22 // replaces, never duplicates 23 const again = setMeasured(written, { ...m, date: '2026-09-25' }); 24 expect(again.match(/^measured:/gm)).toHaveLength(1); 25 expect(readMeasured(again).date).toBe('2026-09-25'); 26 expect(readMeasured(spec)).toBeNull(); 27 }); 28 29 it('holds only for the song.js it measured', () => { 30 expect(measuredFor(m, code)).toEqual(m); 31 expect(measuredFor(m, code + '// edited\n')).toBeNull(); 32 expect(measuredFor(null, code)).toBeNull(); 33 }); 34 35 it('sections a song on its longest .every cadence, keyed by cycles from 1', () => { 36 expect(sectionCycles('a.every(4, x); b.every( 8, y)')).toBe(8); 37 expect(sectionCycles('no jev here')).toBe(1); 38 expect(sectionSpans(4, 10)).toEqual([ 39 { key: '1-4', from: 0, to: 4 }, 40 { key: '5-8', from: 4, to: 8 }, 41 { key: '9-10', from: 8, to: 10 }, 42 ]); 43 }); 44 45 it('keeps sections on the one line, and gives the browser their totals only', () => { 46 const level = { loudnessDbfs: -18, peakDbfs: -3 }; 47 const withSections = { ...m, sections: { '1-4': { total: level, parts: { riff: level } }, '5-8': null } }; 48 const written = setMeasured(spec, withSections); 49 expect(written.split('\n').filter((l) => l.startsWith('measured:'))).toHaveLength(1); 50 expect(readMeasured(written)).toEqual(withSections); 51 expect(withoutSectionParts(withSections).sections).toEqual({ '1-4': { total: level }, '5-8': null }); 52 expect(withoutSectionParts(m)).toEqual(m); 53 expect(withoutSectionParts(null)).toBeNull(); 54 // the critic reads each section's total, whichever it is given 55 for (const given of [withSections, withoutSectionParts(withSections)]) { 56 const mix = artRequest({ title: 'X', spec: '', code, measured: given }).state.measuredMix; 57 expect(mix.sections).toEqual({ '1-4': level, '5-8': null }); 58 } 59 }); 60 61 it('gives the critic the mix, labelled, only when there is one', () => { 62 expect(artRequest({ title: 'X', spec: '', code }).state.measuredMix).toBeUndefined(); 63 const mix = artRequest({ title: 'X', spec: '', code, measured: m }).state.measuredMix; 64 expect(mix.parts.riff.loudnessDbfs).toBe(-20); 65 expect(mix.what).toMatch(/dBFS/); 66 }); 67});