mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-25 14:21:21 +00:00
* Update GitHub repo references from feedback* to feedBack* * rename: slopsmith -> feedBack, byron -> got-feedBack Renames across the entire codebase: - slopsmith/Slopsmith/SLOPSMITH/SlopSmith -> feedBack/FeedBack/FEEDBACK/FeedBack - byron/Byron/Byrongamatos -> got-feedBack/got-feedBack/got-feedBack - /home/byron/ -> /opt/got-feedBack/ - byron@ougsoft.com -> hi@got-feedBack.org - github.com/byrongamatos/ -> github.com/got-feedback/ - com.byron. -> com.got-feedback. - SLOPSMITH_ env vars -> FEEDBACK_ with backward-compat fallback - Protocol/storage strings migrated with read-old/write-new pattern - window.slopsmith JS API -> window.feedBack (canonical) + backward-compat alias Refs: #rename-slopsmith * rename: complete regen against current main + fix backward-compat alias Regenerated the slopsmith->feedBack / byron->got-feedBack rename on top of current main (3 commits had landed since the branch: #572/#554/#574), resolving the four content conflicts in favour of main's newer content (autoplay/auto-exit, accuracy-badge, Virtuoso re-home, feedpak badge). Completion fixes on top of the mechanical rename: - Re-apply rename to post-branch content the original rename never saw: window.slopsmith(.Tour) consumers in lessons.js / notifications.js / onboarding-tour.js, and the matching JS + python tests (autoplay_exit, progression_*, test_feedpak_extension FEEDBACK_* env vars). The test env vars now match server.py (which reads FEEDBACK_SYNC_STARTUP / FEEDBACK_SKIP_STARTUP_TASKS), so the sync-startup test exercises the real path again. - Restore the window.slopsmith backward-compat alias dropped during conflict resolution, and move the bus aliases to AFTER the _feedBackExisting merge block so they reference the fully-assembled object (also fixes the loop_api.test.js API-surface regex, which the original PR latently broke). - Drop the stray empty data/web_library.db (runtime DB lives in CONFIG_DIR) and gitignore it. - Fix stale tone-source test: feed[dB]ack -> fee[dB]ack to match shipped source labels. Verified locally (org CI billing-blocked): JS 819/819 pass; pytest 1669 passed / 1683 collected with 0 import errors; zero residual slopsmith/byron except the two intentional window.slopsmith aliases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * rename: implement advertised backward-compat + prune dead community plugins Address gaps where PR #537's "Backward compatibility" section was advertised but not implemented, and clean up the community plugin list. Env vars (FEEDBACK_* canonical, legacy SLOPSMITH_* honoured): - New lib/env_compat.py (getenv_compat / env_flag_compat) + tests. server.py (_env_flag + all FEEDBACK_* reads), diagnostics_hardware, gp2midi and tailwind_rebuild now resolve the legacy alias, so existing SLOPSMITH_UI / SLOPSMITH_PLUGINS_DIR / etc. deployments keep working. - Fix the rename collapsing plugins/__init__.py and minigames/routes.py from `FEEDBACK_PLUGINS_DIR or SLOPSMITH_PLUGINS_DIR` into a redundant `FEEDBACK_ or FEEDBACK_` (the fallback was silently lost). Storage (app.js update-channel): - Read feedBack-update-channel, fall back to legacy slopsmith-update-channel, and clear the legacy key on write — so a user's update-channel preference survives the rename instead of resetting to "stable". Community plugin list (README): the rename rewrote third-party repo URLs we don't own. Probed every one; their owners never renamed, so: - Restore the 13 live community plugins to their real slopsmith-* names. - Prune 6 that are 404 to the public (topkoa splitscreen/stems, OmikronApex tuner, Jafz2001 nam-rig-builder, DeathlySin song-preview, Erikcb91 shuffle). - Fix a pre-existing Guitar Theory clone-command typo (nam-tone -> guitar-theory). Verified: env_compat 7/7, JS 819/819, pytest 1690 collected / 0 import errors, rename-sensitive + startup suites green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: byrongamatos <xasiklas@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
331 lines
11 KiB
JavaScript
331 lines
11 KiB
JavaScript
'use strict';
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const vm = require('node:vm');
|
|
|
|
const APP_JS = path.join(__dirname, '..', '..', 'static', 'app.js');
|
|
const TUNER_SCREEN_JS = path.join(__dirname, '..', '..', 'plugins', 'tuner', 'screen.js');
|
|
|
|
function loadTuningHelpers() {
|
|
const src = fs.readFileSync(APP_JS, 'utf8');
|
|
const start = src.indexOf('function isBassArrangement(');
|
|
const endMarker = 'window.feedBack.parseRawTuningOffsets = parseRawTuningOffsets;';
|
|
const end = src.indexOf(endMarker);
|
|
if (start === -1 || end === -1) throw new Error('tuning helper block not found in app.js');
|
|
const sandbox = { window: { feedBack: {} }, exports: {} };
|
|
vm.createContext(sandbox);
|
|
vm.runInContext(
|
|
src.slice(start, end + endMarker.length),
|
|
sandbox
|
|
);
|
|
return sandbox.window.feedBack;
|
|
}
|
|
|
|
const feedBackHelpers = loadTuningHelpers();
|
|
|
|
function createTunerSandbox() {
|
|
const enableCalls = [];
|
|
let playerActive = true;
|
|
let songInfo = null;
|
|
|
|
const sandbox = {
|
|
console,
|
|
Promise,
|
|
queueMicrotask,
|
|
setTimeout(fn) { fn(); return 0; },
|
|
clearTimeout() {},
|
|
fetch(url) {
|
|
if (String(url).includes('/config')) {
|
|
return Promise.resolve({
|
|
json: () => Promise.resolve({
|
|
showFloatingButton: true,
|
|
visualizationMode: 'default',
|
|
audioInputMode: 'auto',
|
|
lastInstrument: 'guitar-6',
|
|
lastTuning: 'Standard',
|
|
freeTune: false,
|
|
disabledTunings: [],
|
|
customTunings: {},
|
|
}),
|
|
});
|
|
}
|
|
return Promise.resolve({
|
|
json: () => Promise.resolve({
|
|
tunings: { 'guitar-6': { Standard: [82.41, 110, 146.83, 196, 246.94, 329.63] } },
|
|
referencePitch: 440,
|
|
}),
|
|
});
|
|
},
|
|
localStorage: {
|
|
getItem: () => null,
|
|
setItem() {},
|
|
},
|
|
document: {
|
|
getElementById(id) {
|
|
if (id === 'player') {
|
|
return { classList: { contains: () => playerActive } };
|
|
}
|
|
if (id === 'v3-tuner-wrap') return null;
|
|
return null;
|
|
},
|
|
querySelector() { return null; },
|
|
createElement(tag) {
|
|
const el = {
|
|
tagName: tag.toUpperCase(),
|
|
src: '',
|
|
classList: { add() {}, remove() {}, contains: () => false },
|
|
className: '',
|
|
style: {},
|
|
appendChild() {},
|
|
remove() {},
|
|
addEventListener() {},
|
|
removeEventListener() {},
|
|
querySelector: () => null,
|
|
setAttribute() {},
|
|
onload: null,
|
|
onerror: null,
|
|
};
|
|
if (tag === 'script') {
|
|
queueMicrotask(() => { if (el.onload) el.onload(); });
|
|
}
|
|
return el;
|
|
},
|
|
head: { appendChild() {} },
|
|
body: { appendChild() {} },
|
|
addEventListener() {},
|
|
removeEventListener() {},
|
|
},
|
|
__setPlayerActive(v) { playerActive = v; },
|
|
__setSongInfo(info) {
|
|
songInfo = info;
|
|
sandbox.window.feedBack.currentSong = info ? {
|
|
filename: info.filename || 'song.sloppak',
|
|
arrangementIndex: info.arrangement_index,
|
|
tuning: info.tuning,
|
|
} : null;
|
|
},
|
|
__enableCalls: enableCalls,
|
|
};
|
|
|
|
sandbox.window = sandbox;
|
|
sandbox.window.feedBack = {
|
|
...feedBackHelpers,
|
|
on() {},
|
|
off() {},
|
|
currentSong: null,
|
|
};
|
|
sandbox.window.highway = {
|
|
getSongInfo: () => songInfo,
|
|
};
|
|
sandbox.window._tunerUtils = {
|
|
preferFlats: () => false,
|
|
offsetsToFreqs: (offsets) => offsets.map((o, i) => 80 + i * 10),
|
|
freqToMidi: () => 40,
|
|
midiToNote: () => 'E',
|
|
};
|
|
sandbox.window._tunerUI = () => ({
|
|
addButton() {},
|
|
initUI() {},
|
|
renderInstrumentOptions() {},
|
|
renderTuningOptions() {},
|
|
renderStringNotes() {},
|
|
updateSaveAsCustomVisibility() {},
|
|
updateFreeTuneUI() {},
|
|
updateFloatingButton() {},
|
|
updatePlayerButton() {},
|
|
updateFloatingButtonVisibility() {},
|
|
updateInstrumentDisplay() {},
|
|
positionPanel() {},
|
|
updateUI() {},
|
|
});
|
|
sandbox.window._tunerAudio = {
|
|
start: async () => {},
|
|
stop() {},
|
|
restart: async () => {},
|
|
};
|
|
sandbox.window._tunerViz_default = () => ({
|
|
update() {},
|
|
destroy() {},
|
|
});
|
|
|
|
vm.createContext(sandbox);
|
|
vm.runInContext(fs.readFileSync(TUNER_SCREEN_JS, 'utf8'), sandbox);
|
|
|
|
const realEnable = sandbox.window.tuner.enable.bind(sandbox.window.tuner);
|
|
sandbox.window.tuner.enable = async () => {
|
|
enableCalls.push(1);
|
|
return realEnable();
|
|
};
|
|
|
|
return sandbox;
|
|
}
|
|
|
|
const CUSTOM_GUITAR = {
|
|
filename: 'amnesia.sloppak',
|
|
arrangement: 'Lead',
|
|
arrangement_index: 0,
|
|
stringCount: 6,
|
|
tuning: [-2, 0, 0, 0, -2, -2],
|
|
};
|
|
|
|
const E_STANDARD = {
|
|
filename: 'standard.sloppak',
|
|
arrangement: 'Lead',
|
|
arrangement_index: 0,
|
|
stringCount: 6,
|
|
tuning: [0, 0, 0, 0, 0, 0],
|
|
};
|
|
|
|
const DROP_D = {
|
|
filename: 'dropd.sloppak',
|
|
arrangement: 'Lead',
|
|
arrangement_index: 0,
|
|
stringCount: 6,
|
|
tuning: [-2, 0, 0, 0, 0, 0],
|
|
};
|
|
|
|
const BASS_EADG = {
|
|
filename: 'bass.sloppak',
|
|
arrangement: 'Bass',
|
|
arrangement_index: 0,
|
|
stringCount: 4,
|
|
tuning: [0, 0, 0, 0],
|
|
};
|
|
|
|
async function ready(sandbox, song) {
|
|
sandbox.__setSongInfo(song);
|
|
await sandbox.window._tunerAutoOpen.maybeAutoOpenOnTuningChange();
|
|
}
|
|
|
|
test('tuning identity: same effective tuning returns same key', () => {
|
|
const sandbox = createTunerSandbox();
|
|
const key = sandbox.window._tunerAutoOpen.tuningIdentityKey(CUSTOM_GUITAR);
|
|
assert.equal(key, sandbox.window._tunerAutoOpen.tuningIdentityKey({ ...CUSTOM_GUITAR }));
|
|
assert.match(key, /^g:6:-2,0,0,0,-2,-2$/);
|
|
});
|
|
|
|
test('tuning identity: DADGAD custom vs E Standard differ', () => {
|
|
const sandbox = createTunerSandbox();
|
|
const custom = sandbox.window._tunerAutoOpen.tuningIdentityKey(CUSTOM_GUITAR);
|
|
const standard = sandbox.window._tunerAutoOpen.tuningIdentityKey(E_STANDARD);
|
|
assert.notEqual(custom, standard);
|
|
});
|
|
|
|
test('tuning identity: E Standard vs Drop D differ', () => {
|
|
const sandbox = createTunerSandbox();
|
|
const standard = sandbox.window._tunerAutoOpen.tuningIdentityKey(E_STANDARD);
|
|
const dropD = sandbox.window._tunerAutoOpen.tuningIdentityKey(DROP_D);
|
|
assert.notEqual(standard, dropD);
|
|
});
|
|
|
|
test('tuning identity: bass 4-string vs guitar 6-string differ', () => {
|
|
const sandbox = createTunerSandbox();
|
|
const bass = sandbox.window._tunerAutoOpen.tuningIdentityKey(BASS_EADG);
|
|
const guitar = sandbox.window._tunerAutoOpen.tuningIdentityKey({
|
|
...BASS_EADG,
|
|
arrangement: 'Lead',
|
|
stringCount: 6,
|
|
tuning: [0, 0, 0, 0, 0, 0],
|
|
});
|
|
assert.notEqual(bass, guitar);
|
|
assert.match(bass, /^b:4:/);
|
|
assert.match(guitar, /^g:6:/);
|
|
});
|
|
|
|
test('tuning identity: missing tuning returns null', () => {
|
|
const sandbox = createTunerSandbox();
|
|
assert.equal(sandbox.window._tunerAutoOpen.tuningIdentityKey(null), null);
|
|
assert.equal(sandbox.window._tunerAutoOpen.tuningIdentityKey({ tuning: [] }), null);
|
|
});
|
|
|
|
test('first song load sets lastTuningKey but does not auto-open', async () => {
|
|
const sandbox = createTunerSandbox();
|
|
sandbox.window._tunerAutoOpen.resetState();
|
|
await ready(sandbox, E_STANDARD);
|
|
assert.equal(sandbox.__enableCalls.length, 0);
|
|
assert.equal(sandbox.window._tunerAutoOpen.getState().lastTuningKey, 'g:6:0,0,0,0,0,0');
|
|
});
|
|
|
|
test('custom tuning then E Standard triggers one auto-open', async () => {
|
|
const sandbox = createTunerSandbox();
|
|
sandbox.window._tunerAutoOpen.resetState();
|
|
await ready(sandbox, CUSTOM_GUITAR);
|
|
await ready(sandbox, E_STANDARD);
|
|
assert.equal(sandbox.__enableCalls.length, 1);
|
|
});
|
|
|
|
test('E Standard then Drop D triggers one auto-open', async () => {
|
|
const sandbox = createTunerSandbox();
|
|
sandbox.window._tunerAutoOpen.resetState();
|
|
await ready(sandbox, E_STANDARD);
|
|
await ready(sandbox, DROP_D);
|
|
assert.equal(sandbox.__enableCalls.length, 1);
|
|
});
|
|
|
|
test('same tuning twice does not auto-open', async () => {
|
|
const sandbox = createTunerSandbox();
|
|
sandbox.window._tunerAutoOpen.resetState();
|
|
await ready(sandbox, E_STANDARD);
|
|
await ready(sandbox, { ...E_STANDARD, filename: 'other.sloppak' });
|
|
assert.equal(sandbox.__enableCalls.length, 0);
|
|
});
|
|
|
|
test('duplicate song:ready for same tuning does not auto-open repeatedly', async () => {
|
|
const sandbox = createTunerSandbox();
|
|
sandbox.window._tunerAutoOpen.resetState();
|
|
await ready(sandbox, CUSTOM_GUITAR);
|
|
await ready(sandbox, E_STANDARD);
|
|
await ready(sandbox, E_STANDARD);
|
|
assert.equal(sandbox.__enableCalls.length, 1);
|
|
});
|
|
|
|
test('if tuner already enabled, no duplicate enable call', async () => {
|
|
const sandbox = createTunerSandbox();
|
|
sandbox.window._tunerAutoOpen.resetState();
|
|
await ready(sandbox, CUSTOM_GUITAR);
|
|
sandbox.window._tunerAutoOpen.setEnabledForTests(true);
|
|
const before = sandbox.__enableCalls.length;
|
|
await ready(sandbox, E_STANDARD);
|
|
assert.equal(sandbox.__enableCalls.length, before);
|
|
});
|
|
|
|
test('user dismiss prevents reopen for same session', async () => {
|
|
const sandbox = createTunerSandbox();
|
|
sandbox.window._tunerAutoOpen.resetState();
|
|
await ready(sandbox, CUSTOM_GUITAR);
|
|
await ready(sandbox, { ...E_STANDARD, filename: 'amnesia.sloppak', arrangement_index: 0 });
|
|
assert.equal(sandbox.__enableCalls.length, 1);
|
|
sandbox.window._tunerAutoOpen.setEnabledForTests(true);
|
|
sandbox.window.tuner.disable();
|
|
await ready(sandbox, { ...DROP_D, filename: 'amnesia.sloppak', arrangement_index: 0 });
|
|
assert.equal(sandbox.__enableCalls.length, 1);
|
|
});
|
|
|
|
test('song:loading clears dismiss state for next load', async () => {
|
|
const sandbox = createTunerSandbox();
|
|
sandbox.window._tunerAutoOpen.resetState();
|
|
await ready(sandbox, CUSTOM_GUITAR);
|
|
await ready(sandbox, E_STANDARD);
|
|
sandbox.window.tuner.disable();
|
|
sandbox.window._tunerAutoOpen.onSongLoading();
|
|
await ready(sandbox, DROP_D);
|
|
assert.equal(sandbox.__enableCalls.length, 2);
|
|
});
|
|
|
|
test('screen.js registers song:loading and song:ready auto-open listeners at boot', () => {
|
|
const src = fs.readFileSync(TUNER_SCREEN_JS, 'utf8');
|
|
assert.match(src, /function _installAutoOpenListeners/);
|
|
assert.match(src, /window\.feedBack\.on\('song:loading', _onAutoOpenSongLoading\)/);
|
|
assert.match(src, /window\.feedBack\.on\('song:ready', _onAutoOpenSongReady\)/);
|
|
assert.match(src, /function _tuningIdentityKey/);
|
|
assert.doesNotMatch(src, /restartCurrentSong/);
|
|
});
|
|
|
|
test('auto-open does not require app.js changes', () => {
|
|
const appSrc = fs.readFileSync(APP_JS, 'utf8');
|
|
assert.doesNotMatch(appSrc, /_tunerAutoOpen|maybeAutoOpenOnTuningChange/);
|
|
});
|