mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-21 12:21:49 +00:00
fix(tests): re-green the JS suite — 18 stale source-shape tests + 1 real seek-reason violation (#740)
main's JS suite has been red since the recent v3-library and player refactors landed. 17 of 18 failures were test harnesses/regexes that went stale behind real, intentional code changes; one was a genuine contract violation in the code. Code fix: - session-resume seek passed 'resume' as its _audioSeek reason; the documented contract (enforced by song_seek.test.js) requires multi-word kebab-case. Renamed to 'session-resume' — no consumer string-matches specific reasons, so this is rename-safe. Test updates (each pins the CURRENT contract): - highway_colors_facade: inject HWC_PRESETS + applyHighwayStringPreset (new preset feature); lock presets/applyPreset into the surface test - loop_api: stub _updateEditRegionBtn (new edit-region UI hook) - song_close: sandbox gets window.feedBack.playQueue; assert a real close abandons the queue (the new queue-aware behavior) - v3_keep_practicing: the shelf moved from client-side /api/stats/recent dedupe+gating to the server-side practice-suggestions recommender — tests now pin that (fetch, arrangement-aware card click, Promise.all) - v3_songs_tuning: card row variable renamed song → shown (grouped cards) - live_guitar_tone_source: accept literal ’ where ’ drifted in copy - legacy_shim_hits: normalize CRLF before fixed-width region() slicing (Windows-only failure; char windows shrank by one char per line) Suite: 987/987 locally (Windows), previously 968/987 (and 18 red on CI). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b6442dda75
commit
d2b2a7e9f7
@ -6195,7 +6195,7 @@ window.feedBack.on('song:ready', () => {
|
||||
setSpeed(pend.speed);
|
||||
}
|
||||
} catch (_) { /* speed restore is best-effort */ }
|
||||
Promise.resolve(_audioSeek(Math.max(0, Number(pend.position) || 0), 'resume'))
|
||||
Promise.resolve(_audioSeek(Math.max(0, Number(pend.position) || 0), 'session-resume'))
|
||||
.then(() => { if (_autoplayExitEnabled() && !isPlaying) return togglePlay(); })
|
||||
.catch((err) => console.warn('[app] resume failed:', err));
|
||||
});
|
||||
|
||||
@ -35,10 +35,11 @@ function buildFacade() {
|
||||
'return _hwcInstallFacade;',
|
||||
].join('\n');
|
||||
const params = [
|
||||
'window', 'HWC_SLOTS', 'console',
|
||||
'window', 'HWC_SLOTS', 'HWC_PRESETS', 'console',
|
||||
'getHighwayStringColors', 'getHighwayDefaultSlotColors', '_hwcMergedSlotColors',
|
||||
'_hwcSlotKeysForChart', '_hwcEffectiveIndexColors', '_hwcChartShape',
|
||||
'applyHighwayStringColors', 'encodeHighwayColorShare', 'decodeHighwayColorShare',
|
||||
'applyHighwayStringColors', 'applyHighwayStringPreset',
|
||||
'encodeHighwayColorShare', 'decodeHighwayColorShare',
|
||||
];
|
||||
|
||||
const listeners = {};
|
||||
@ -64,14 +65,19 @@ function buildFacade() {
|
||||
_hwcEffectiveIndexColors: (map, sc, isBass) => ['eff', sc, isBass],
|
||||
_hwcChartShape: () => ({ sc: 6, isBass: false }),
|
||||
applyHighwayStringColors: (m) => { calls.push(['apply', m]); },
|
||||
applyHighwayStringPreset: (id) => { calls.push(['preset', id]); return true; },
|
||||
encodeHighwayColorShare: (n, m) => 'SLOPHWY2.CODE',
|
||||
decodeHighwayColorShare: (c) => ({ name: 'x', colors: {} }),
|
||||
};
|
||||
const HWC_PRESETS = [
|
||||
{ id: 'stock', label: 'Stock', colors: { lowE: '#cc0000' } },
|
||||
];
|
||||
const installer = new Function(...params, body)(
|
||||
win, HWC_SLOTS, console,
|
||||
win, HWC_SLOTS, HWC_PRESETS, console,
|
||||
stubs.getHighwayStringColors, stubs.getHighwayDefaultSlotColors, stubs._hwcMergedSlotColors,
|
||||
stubs._hwcSlotKeysForChart, stubs._hwcEffectiveIndexColors, stubs._hwcChartShape,
|
||||
stubs.applyHighwayStringColors, stubs.encodeHighwayColorShare, stubs.decodeHighwayColorShare,
|
||||
stubs.applyHighwayStringColors, stubs.applyHighwayStringPreset,
|
||||
stubs.encodeHighwayColorShare, stubs.decodeHighwayColorShare,
|
||||
);
|
||||
installer();
|
||||
return { api: win.feedBack.highwayColors, win, bus, calls, installer, stubs };
|
||||
@ -87,11 +93,13 @@ test('facade exposes the documented surface', () => {
|
||||
const { api } = buildFacade();
|
||||
assert.equal(api.version, 1);
|
||||
for (const m of ['get', 'getDefaults', 'getResolved', 'keysForChart', 'toEffective',
|
||||
'getCurrent', 'apply', 'encodeShare', 'decodeShare', 'onChange', 'offChange']) {
|
||||
'getCurrent', 'apply', 'applyPreset', 'encodeShare', 'decodeShare', 'onChange', 'offChange']) {
|
||||
assert.equal(typeof api[m], 'function', `highwayColors.${m} must be a function`);
|
||||
}
|
||||
assert.deepEqual(api.slots.map((s) => s.key),
|
||||
['highE', 'B', 'G', 'D', 'A', 'lowE', 'low7', 'low8'], 'slots in display order');
|
||||
// One-click presets: exposed as detached [{ id, label, colors }] copies.
|
||||
assert.deepEqual(api.presets, [{ id: 'stock', label: 'Stock', colors: { lowE: '#cc0000' } }]);
|
||||
});
|
||||
|
||||
test('facade read methods delegate to the manager', () => {
|
||||
|
||||
@ -74,7 +74,10 @@ const APP_JS = path.join(ROOT, 'static', 'app.js');
|
||||
const LIBRARY_JS = path.join(ROOT, 'static', 'capabilities', 'library.js');
|
||||
|
||||
function source(file) {
|
||||
return fs.readFileSync(file, 'utf8');
|
||||
// Normalize CRLF: region() slices fixed CHARACTER windows, so on a
|
||||
// Windows checkout (autocrlf) every line costs one extra char and the
|
||||
// assertion target can fall outside the window.
|
||||
return fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
|
||||
}
|
||||
|
||||
function region(src, needle, length = 1200) {
|
||||
|
||||
@ -40,7 +40,9 @@ test('settings UI exposes tone source select with all options', () => {
|
||||
assert.match(html, /value="external_hardware"/);
|
||||
assert.match(html, /value="spark_control_x"/);
|
||||
assert.match(html, /Live guitar tone source/);
|
||||
assert.match(html, /won’t warn that no internal amp tone is loaded/);
|
||||
// Apostrophe form drifted from the ’ entity to the literal ’ in a
|
||||
// copy pass — accept entity, typographic, or plain apostrophe.
|
||||
assert.match(html, /won(?:’|’|')t warn that no internal amp tone is loaded/);
|
||||
});
|
||||
|
||||
test('player audio rail exposes tone source select', () => {
|
||||
|
||||
@ -107,6 +107,7 @@ function loadFunctions(sandbox, src) {
|
||||
sectionPracticeModeCalls.push({ on, opts: opts || {} });
|
||||
}
|
||||
function _updateSectionPracticeHighlight(ct) {}
|
||||
function _updateEditRegionBtn() {}
|
||||
${extractFunction(src, 'function clearLoop(')}
|
||||
${extractFunction(src, 'function _syncSavedLoopSelection()')}
|
||||
${extractFunction(src, 'async function setLoop(')}
|
||||
|
||||
@ -42,7 +42,10 @@ function loadClose(sandbox, src) {
|
||||
globalThis.__seekCalls = 0;
|
||||
globalThis.__playSongCalls = 0;
|
||||
globalThis.__clearLoopCalls = 0;
|
||||
globalThis.__queueClearCalls = 0;
|
||||
globalThis.__audioCurrentTimeSets = [];
|
||||
// closeCurrentSong abandons any play-queue before leaving the player.
|
||||
var window = { feedBack: { playQueue: { clear() { globalThis.__queueClearCalls++; } } } };
|
||||
var audio = {
|
||||
_t: 42,
|
||||
get currentTime() { return this._t; },
|
||||
@ -75,6 +78,7 @@ test('closeCurrentSong uses _playerOriginScreen when set', async () => {
|
||||
await sandbox.__closeCurrentSong();
|
||||
assert.equal(sandbox.__showScreenCalls.length, 1);
|
||||
assert.equal(sandbox.__showScreenCalls[0], 'favorites');
|
||||
assert.equal(sandbox.__queueClearCalls, 1, 'a real close abandons the play-queue');
|
||||
assert.equal(sandbox.__restartCalls, 0);
|
||||
assert.equal(sandbox.__seekCalls, 0);
|
||||
assert.equal(sandbox.__playSongCalls, 0);
|
||||
|
||||
@ -31,21 +31,23 @@ test('the home is the unfiltered grid front door, local provider only', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('the shelf is recently-played, not-yet-mastered songs (per-song, deduped)', () => {
|
||||
assert.match(src, /\/api\/stats\/recent\?limit=/);
|
||||
// Mastery is gated on the per-SONG best (state.accuracy, what the badge
|
||||
// shows), not the per-arrangement recents row, and each filename appears
|
||||
// once — so no green-badged "keep practicing" card and no duplicates.
|
||||
test('the shelf is the server-side practice-suggestions recommender', () => {
|
||||
// The old client-side pipeline (fetch /api/stats/recent, dedupe by
|
||||
// filename, gate on state.accuracy) moved server-side: the growth-edge
|
||||
// recommender gates (not-mastered) + aggregates per song and picks the
|
||||
// arrangement closest to mastery. The client renders its rows as-is.
|
||||
assert.match(src, /\/api\/library\/practice-suggestions\?limit=/);
|
||||
// A shelf card click opens the row's recommended arrangement, not the
|
||||
// song's default.
|
||||
assert.match(
|
||||
src,
|
||||
/const\s+best\s*=\s*acc\[r\.filename\][\s\S]*?best\s*>=\s*MASTERY_ACCURACY/,
|
||||
'the shelf must gate on the per-song best (state.accuracy) at MASTERY_ACCURACY',
|
||||
/data-arr="[\s\S]*?getAttribute\('data-arr'\)[\s\S]*?playSong\(enc\(fn\), arr === '' \? undefined : Number\(arr\)\)/,
|
||||
'shelf cards must pass the recommended arrangement to playSong',
|
||||
);
|
||||
assert.match(src, /seen\.has\(r\.filename\)/, 'the shelf must dedupe recents by filename');
|
||||
});
|
||||
|
||||
test('the meter + shelf fetch together and a stale render is discarded', () => {
|
||||
assert.match(src, /Promise\.all\(\[[\s\S]*?library\/stats[\s\S]*?stats\/recent/,
|
||||
assert.match(src, /Promise\.all\(\[[\s\S]*?library\/stats[\s\S]*?practice-suggestions/,
|
||||
'the two reads must be issued together (Promise.all), not sequentially');
|
||||
assert.match(src, /_homeToken[\s\S]*?_homeToken !== myToken/,
|
||||
'a stale render must be superseded by a newer one via a token');
|
||||
|
||||
@ -64,7 +64,9 @@ const helpers = loadTuningHelpers();
|
||||
|
||||
test('v3 songs.js uses display helpers for album-art tuning badge', () => {
|
||||
const src = fs.readFileSync(SONGS_JS, 'utf8');
|
||||
assert.match(src, /displayTuningName\(song\.tuning_name \|\| song\.tuning\)/);
|
||||
// The card renderer's row variable was renamed song → shown when grouped
|
||||
// cards landed (the badge reads the representative chart); accept either.
|
||||
assert.match(src, /displayTuningName\((?:song|shown)\.tuning_name \|\| (?:song|shown)\.tuning\)/);
|
||||
assert.match(src, /displayTuningTargets/);
|
||||
assert.match(src, /parseRawTuningOffsets/);
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user