jevstrudel.git / website / src / jev / pitches.test.mjs
1import { readdirSync, readFileSync } from 'node:fs';
2import { describe, expect, it } from 'vitest';
3import { answersOf, pitchOf } from './pitches.mjs';
4
5const ID = 'AbCdEfGhIjKlMnOpQrStUv';
6
7describe('pitchOf: a site song says which pitch it answers', () => {
8  it('takes a pitch id or nothing, and fails the build on anything else', () => {
9    expect(pitchOf(undefined)).toBeNull();
10    expect(pitchOf(null)).toBeNull();
11    expect(pitchOf(ID)).toBe(ID);
12    expect(() => pitchOf('short', 'jev/x')).toThrow(/jev\/x: pitch must be a pitch's id/);
13    expect(() => pitchOf(42)).toThrow(/pitch must be/);
14  });
15
16  it('holds for every song in the repo', () => {
17    const root = new URL('../../../songs/', import.meta.url);
18    for (const theme of readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory())) {
19      for (const song of readdirSync(new URL(`${theme.name}/`, root), { withFileTypes: true }).filter((d) => d.isDirectory())) {
20        const spec = readFileSync(new URL(`${theme.name}/${song.name}/SPEC.md`, root), 'utf8');
21        const line = spec.split(/^---\s*$/m)[1]?.match(/^pitch:\s*(.*)$/m);
22        if (line) expect(() => pitchOf(line[1].trim().replace(/^["']|["']$/g, ''), song.name)).not.toThrow();
23      }
24    }
25  });
26});
27
28describe('answersOf: every song answering a pitch', () => {
29  it("lists the site's songs from this build, then the listeners' the Worker sent", () => {
30    const pitch = { id: ID, answers: [{ id: 'L'.repeat(22), title: 'A LISTENER SONG' }] };
31    const site = [
32      { id: 'jev/hey-listen', title: 'HEY! LISTEN!', pitch: ID },
33      { id: 'jev/dial-up', title: 'DIAL-UP', pitch: null },
34    ];
35    expect(answersOf(pitch, site)).toEqual([
36      { site: 'jev/hey-listen', title: 'HEY! LISTEN!' },
37      { listener: 'L'.repeat(22), title: 'A LISTENER SONG' },
38    ]);
39    expect(answersOf({ id: 'x'.repeat(22) }, site)).toEqual([]);
40  });
41});