1import { describe, expect, it } from 'vitest'; 2import { criticArt, format, readSongs, report } from './agreement.mjs'; 3 4const spec = (critic) => `--- 5title: X 6theme: jev 7measured: {"total":{"art":0.1}} 8${critic} 9revisions: 10 - rev: 1 11 art: 0.11 12--- 13 14# X 15 16 art: 0.99 17`; 18 19describe('criticArt: the song as it stands, from its SPEC.md', () => { 20 it('reads critic.art, not a revision art or the body', () => { 21 expect(criticArt(spec('critic:\n date: 2026-09-25\n art: 0.68\n criteria:\n hook: 3.1'))).toBe(0.68); 22 expect(criticArt(spec('critic:\n art: 0.5 # a comment'))).toBe(0.5); 23 expect(criticArt(spec(''))).toBeNull(); 24 expect(criticArt(spec('critic:\n art: null'))).toBeNull(); 25 }); 26 27 it('reads every song in the repo, scored or not', () => { 28 const songs = readSongs(new URL('../../songs/', import.meta.url)); 29 expect(songs.length).toBeGreaterThan(5); 30 for (const s of songs) { 31 expect(s.id).toMatch(/^[a-z0-9-]+\/[a-z0-9-]+$/); 32 if (s.art) expect(s.art.p).toBeGreaterThanOrEqual(0); 33 } 34 expect(songs.some((s) => s.art)).toBe(true); 35 }); 36}); 37 38describe('report: agreement with the critic’s current scores', () => { 39 const songs = [ 40 { id: 'jev/high', art: { p: 0.8 } }, 41 { id: 'jev/mid', art: { p: 0.6 } }, 42 { id: 'jev/twin', art: { p: 0.6 } }, 43 { id: 'jev/unscored', art: null }, 44 ]; 45 46 it('counts each (a, b, pick) n times, either way round, and leaves out ties and unknowns', () => { 47 const r = report(songs, [ 48 { a: 'jev/high', b: 'jev/mid', pick: 'jev/high', n: 3 }, // agree 49 { a: 'jev/mid', b: 'jev/high', pick: 'jev/mid', n: 1 }, // disagree, shown the other way round 50 { a: 'jev/twin', b: 'jev/high', pick: 'jev/high', n: 2 }, // agree 51 { a: 'jev/mid', b: 'jev/twin', pick: 'jev/mid', n: 5 }, // a tie: no preference 52 { a: 'jev/gone', b: 'jev/high', pick: 'jev/high', n: 4 }, // no longer here 53 { a: 'jev/unscored', b: 'jev/high', pick: 'jev/high', n: 1 }, // unscored 54 ]); 55 expect(r.overall).toEqual({ agreed: 5, counted: 6, share: 5 / 6, ties: 5, unknown: 5, votes: 16 }); 56 expect(r.songs).toEqual([ 57 { id: 'jev/high', art: 0.8, agreed: 5, counted: 6, share: 5 / 6 }, 58 { id: 'jev/mid', art: 0.6, agreed: 3, counted: 4, share: 3 / 4 }, 59 { id: 'jev/twin', art: 0.6, agreed: 2, counted: 2, share: 1 }, 60 ]); 61 }); 62 63 it('has no share with nothing counted', () => { 64 const r = report(songs, []); 65 expect(r.overall.share).toBeNull(); 66 expect(r.songs).toEqual([]); 67 expect(format(r)).toMatch(/— of the time \(0 of 0 votes\)/); 68 }); 69 70 it('formats a line per song', () => { 71 const text = format(report(songs, [{ a: 'jev/high', b: 'jev/mid', pick: 'jev/mid', n: 2 }])); 72 expect(text).toMatch(/agree with the critic 0% of the time \(0 of 2 votes\)/); 73 expect(text).toMatch(/jev\/high\s+80%\s+0%\s+2/); 74 }); 75});