mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-11 06:24:31 +00:00
Carve cut 14 of the h3d-carve epic. Moves the full V-section (note renderer):
drawNote, drawArpBrackets, drawNotedetectLabels, chordHarmonyLabels
and 14 private helpers (~1,433 lines)
from screen.js into plugins/highway_3d/src/note-renderer.js
as a factory-DI ES module: createNoteRenderer({137 DI params}).
Beyond-subst (3 sites):
_ndVerdictSawAlpha = true → setNdVerdictSawAlpha(true)
_ndVerdictMaxAlpha = v → setNdVerdictMaxAlpha(v)
_streakHits = 0/++ → setStreakHits(0/getStreakHits()+1)
Tests:
- New: tests/js/highway_3d_note_renderer.test.js (22 assertions)
wiring guard (137 params), export contract, chordHarmonyLabels
behavioral kills, beyond-subst sentinel, tombstone checks
- Updated 8 existing test files to search note-renderer.js
alongside screen.js for moved patterns (h3d-carve-14 retarget)
Bumps plugin.json 3.49.0 → 3.50.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014uZ169yfoFYArXz962g7KW
96 lines
4.4 KiB
JavaScript
96 lines
4.4 KiB
JavaScript
// Behavioural tests for the teaching-marks (§6.2.2) render helpers:
|
|
// teachingFingerLabel / teachingDegreeLabel (both highways) and
|
|
// strumGroupBuckets (2D, drives the strum bracket). All pure, so we extract
|
|
// the function source by brace-matching and eval it in isolation — same
|
|
// pattern as highway_bend_curve.test.js.
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
function extractFn(src, name) {
|
|
const start = src.indexOf('function ' + name);
|
|
assert.ok(start >= 0, `function ${name} must exist`);
|
|
const open = src.indexOf('{', start);
|
|
let depth = 0;
|
|
for (let i = open; i < src.length; i++) {
|
|
if (src[i] === '{') depth++;
|
|
else if (src[i] === '}' && --depth === 0) return src.slice(start, i + 1);
|
|
}
|
|
throw new Error(`unbalanced braces extracting ${name}`);
|
|
}
|
|
|
|
function loadFn(file, name) {
|
|
const src = fs.readFileSync(path.join(__dirname, '..', '..', file), 'utf8');
|
|
return new Function('"use strict";' + extractFn(src, name) + `\nreturn ${name};`)();
|
|
}
|
|
|
|
// R3c: the PURE geometry/label primitives were carved out of highway.js into
|
|
// static/js/highway-geometry.js. Same bodies, byte-for-byte — only the file moved.
|
|
const fingerLabel2D = loadFn('static/js/highway-geometry.js', 'teachingFingerLabel');
|
|
const degreeLabel2D = loadFn('static/js/highway-geometry.js', 'teachingDegreeLabel');
|
|
// h3d-carve-14: teachingFingerLabel/Degree moved to note-renderer.js
|
|
const fingerLabel3D = loadFn('plugins/highway_3d/src/note-renderer.js', 'teachingFingerLabel');
|
|
const degreeLabel3D = loadFn('plugins/highway_3d/src/note-renderer.js', 'teachingDegreeLabel');
|
|
const strumGroupBuckets = loadFn('static/js/highway-draw.js', 'strumGroupBuckets');
|
|
|
|
// ── teachingFingerLabel (fg) ─────────────────────────────────────────────────
|
|
|
|
for (const [name, fn] of [['2D', fingerLabel2D], ['3D', fingerLabel3D]]) {
|
|
test(`teachingFingerLabel (${name}) maps 0->T, 1..4->digit, else ''`, () => {
|
|
assert.equal(fn(0), 'T'); // thumb
|
|
assert.equal(fn(1), '1');
|
|
assert.equal(fn(4), '4'); // pinky
|
|
assert.equal(fn(-1), ''); // unset
|
|
assert.equal(fn(5), ''); // out of range
|
|
assert.equal(fn(1.5), ''); // non-integer
|
|
assert.equal(fn(undefined), '');
|
|
assert.equal(fn(null), '');
|
|
});
|
|
}
|
|
|
|
// ── teachingDegreeLabel (sd) ─────────────────────────────────────────────────
|
|
|
|
for (const [name, fn] of [['2D', degreeLabel2D], ['3D', degreeLabel3D]]) {
|
|
test(`teachingDegreeLabel (${name}) shows 0..11, else ''`, () => {
|
|
assert.equal(fn(0), '0'); // tonic
|
|
assert.equal(fn(7), '7'); // fifth
|
|
assert.equal(fn(11), '11');
|
|
assert.equal(fn(-1), ''); // unset
|
|
assert.equal(fn(12), ''); // out of range
|
|
assert.equal(fn(3.2), ''); // non-integer
|
|
assert.equal(fn(undefined), '');
|
|
});
|
|
}
|
|
|
|
// ── strumGroupBuckets (ch) ───────────────────────────────────────────────────
|
|
|
|
test('strumGroupBuckets groups notes sharing a ch >= 0, dropping lone notes', () => {
|
|
const items = [
|
|
{ id: 'a', ch: 5 },
|
|
{ id: 'b', ch: -1 }, // ungrouped
|
|
{ id: 'c', ch: 5 },
|
|
{ id: 'd', ch: 7 }, // lone group (only one member) -> dropped
|
|
{ id: 'e', ch: 5 },
|
|
];
|
|
const groups = strumGroupBuckets(items);
|
|
assert.equal(groups.length, 1);
|
|
assert.deepEqual(groups[0].map(n => n.id), ['a', 'c', 'e']);
|
|
});
|
|
|
|
test('strumGroupBuckets preserves first-seen group order and handles multiple groups', () => {
|
|
const items = [
|
|
{ id: 'a', ch: 2 }, { id: 'b', ch: 9 },
|
|
{ id: 'c', ch: 2 }, { id: 'd', ch: 9 },
|
|
];
|
|
const groups = strumGroupBuckets(items);
|
|
assert.deepEqual(groups.map(g => g.map(n => n.id)), [['a', 'c'], ['b', 'd']]);
|
|
});
|
|
|
|
test('strumGroupBuckets ignores non-integer / negative ch and bad input', () => {
|
|
assert.deepEqual(strumGroupBuckets([{ ch: -1 }, { ch: 1.5 }, { ch: null }, {}]), []);
|
|
assert.deepEqual(strumGroupBuckets([]), []);
|
|
assert.deepEqual(strumGroupBuckets(null), []);
|
|
});
|