Files
SonicForgeStudio/app/static/js/services/chordTheory.test.js
T

64 lines
3.2 KiB
JavaScript

// SonicForge Studio - chordTheory.js unit test (BIG_PHASE_3.md §6.4)
// Node assert, không framework. Chạy: node app/static/js/services/chordTheory.test.js
const assert = require('assert');
const ChordTheory = require('./chordTheory.js');
let fails = 0;
function check(name, fn) {
try { fn(); console.log('ok: ' + name); }
catch (e) { fails++; console.error('FAIL: ' + name + ' — ' + e.message); }
}
function deep(arr, expected) { assert.deepStrictEqual(arr, expected); }
// 1. Parse chord symbol → pitch array (octave 4 = C4=60)
check('Cmaj7 = [60,64,67,71]', () => deep(ChordTheory.parseChordSymbol('Cmaj7'), [60, 64, 67, 71]));
check('Am9 = [69,72,76,79,83]', () => deep(ChordTheory.parseChordSymbol('Am9'), [69, 72, 76, 79, 83]));
check('Dm7 = [62,65,69,72]', () => deep(ChordTheory.parseChordSymbol('Dm7'), [62, 65, 69, 72]));
check('G13 = [67,71,74,77,81,88]', () => deep(ChordTheory.parseChordSymbol('G13'), [67, 71, 74, 77, 81, 88]));
check('Fadd9 = [65,69,72,79]', () => deep(ChordTheory.parseChordSymbol('Fadd9'), [65, 69, 72, 79]));
// 2. Roman numeral progression với key C
check('I-vi-IV-V key C → C-Am-F-G', () => {
const prog = ChordTheory.progressionToChords('I-vi-IV-V', 'C', 'root');
deep(prog.map(c => c.name), ['C', 'Am', 'F', 'G']);
deep(prog.map(c => c.pitches), [[60, 64, 67], [69, 72, 76], [65, 69, 72], [67, 71, 74]]);
});
check('I-V-vi-IV key C → C-G-Am-F (preset Pop)', () => {
const prog = ChordTheory.progressionToChords('I-V-vi-IV', 'C', 'root');
deep(prog.map(c => c.name), ['C', 'G', 'Am', 'F']);
});
check('ii7-V7-Imaj7-VI7 key C → Dm7-G7-Cmaj7-A7', () => {
const prog = ChordTheory.progressionToChords('ii7-V7-Imaj7-VI7', 'C', 'root');
deep(prog.map(c => c.name), ['Dm7', 'G7', 'Cmaj7', 'A7']);
deep(prog[0].pitches, [62, 65, 69, 72]); // Dm7
deep(prog[2].pitches, [60, 64, 67, 71]); // Cmaj7
});
check('i7-iv7-v7-i7 key C → Cm7-Fm7-Gm7-Cm7', () => {
const prog = ChordTheory.progressionToChords('i7-iv7-v7-i7', 'C', 'root');
deep(prog.map(c => c.name), ['Cm7', 'Fm7', 'Gm7', 'Cm7']);
});
check('Imaj7-iii7-IVmaj7-V13 key C → Cmaj7-Em7-Fmaj7-G13', () => {
const prog = ChordTheory.progressionToChords('Imaj7-iii7-IVmaj7-V13', 'C', 'root');
deep(prog.map(c => c.name), ['Cmaj7', 'Em7', 'Fmaj7', 'G13']);
});
// 3. Voicing
check('first inversion: root +12', () => deep(ChordTheory.applyVoicing([60, 64, 67, 71], 'first'), [64, 67, 71, 72]));
check('drop2: second-highest -12', () => deep(ChordTheory.applyVoicing([60, 64, 67, 71], 'drop2'), [55, 60, 64, 71]));
check('root position giữ nguyên', () => deep(ChordTheory.applyVoicing([60, 64, 67, 71], 'root'), [60, 64, 67, 71]));
check('jazz drop2: Dm7-G7-Cmaj7-A7 ra đủ pitch', () => {
const prog = ChordTheory.progressionToChords('ii7-V7-Imaj7-VI7', 'C', 'drop2');
deep(prog.map(c => c.name), ['Dm7', 'G7', 'Cmaj7', 'A7']);
assert(prog.every(c => c.pitches.length === 4), 'mỗi chord 4 pitch');
});
// 4. Edge cases
check('chuỗi chord name trực tiếp Cmaj7-Am9-Fadd9-G13', () => {
const prog = ChordTheory.progressionToChords('Cmaj7-Am9-Fadd9-G13', 'C', 'root');
deep(prog.map(c => c.name), ['Cmaj7', 'Am9', 'Fadd9', 'G13']);
deep(prog[0].pitches, [60, 64, 67, 71]);
});
console.log(fails ? 'FAILED: ' + fails : 'ALL PASS');
process.exit(fails ? 1 : 0);