mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-11 05:04:30 +00:00
fix(h3d): cut-16 follow-up — tests, plugin.json bump, visibility/string-colors retargets
- Add tests/js/highway_3d_scene_init.test.js (18 tests): wiring guard, export surface, class-killer, DI anti-vacuity, import correctness, function presence, kill tests, plugin.json version gate. - Bump plugins/highway_3d/plugin.json 3.51.0 → 3.52.0. - Retarget highway_string_colors.test.js tests 3–4 to scene-init.js (functions moved in cut-16). - Update highway_visibility.test.js test 22 regexes to accept DI-rewritten forms: getHighwayCanvas() / getWrap().style.display / getHighwayCanvas().offsetParent. Suite: 1426/1427 pass (1 pre-existing audio test; all cut-16 regressions cleared). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014uZ169yfoFYArXz962g7KW
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
4bb5d21a40
commit
273412744a
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "highway_3d",
|
||||
"name": "3D Highway",
|
||||
"version": "3.51.0",
|
||||
"version": "3.52.0",
|
||||
"type": "visualization",
|
||||
"scriptType": "module",
|
||||
"bundled": true,
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
// Source-level guards for src/scene-init.js (h3d-carve-16).
|
||||
// Validates wiring, DI contract, class-killer, export surface, and kill tests
|
||||
// for initScene / buildBoard / _bgUnmountStyle / _bcSyncMode.
|
||||
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const sceneInitJs = path.join(__dirname, '..', '..', 'plugins', 'highway_3d', 'src', 'scene-init.js');
|
||||
const screen3dJs = path.join(__dirname, '..', '..', 'plugins', 'highway_3d', 'screen.js');
|
||||
const pluginJson = path.join(__dirname, '..', '..', 'plugins', 'highway_3d', 'plugin.json');
|
||||
|
||||
// ── §1 Wiring guard ───────────────────────────────────────────────────────────
|
||||
|
||||
test('scene-init exports createSceneInit as a named ES export', () => {
|
||||
const src = fs.readFileSync(sceneInitJs, 'utf8');
|
||||
assert.match(src, /^export\s+function\s+createSceneInit\s*\(/m,
|
||||
'must have: export function createSceneInit(');
|
||||
});
|
||||
|
||||
test('screen.js imports createSceneInit from ./src/scene-init.js', () => {
|
||||
const src = fs.readFileSync(screen3dJs, 'utf8');
|
||||
assert.match(src, /import\s*\{[^}]*createSceneInit[^}]*\}\s*from\s*['"]\.\/src\/scene-init\.js['"]/,
|
||||
'screen.js must import createSceneInit from ./src/scene-init.js');
|
||||
});
|
||||
|
||||
test('screen.js wires the four exports from createSceneInit', () => {
|
||||
const src = fs.readFileSync(screen3dJs, 'utf8');
|
||||
assert.match(src, /const\s*\{[^}]*initScene[^}]*\}\s*=\s*createSceneInit\s*\(/,
|
||||
'screen.js must destructure initScene from createSceneInit(...)');
|
||||
assert.match(src, /const\s*\{[^}]*buildBoard[^}]*\}\s*=\s*createSceneInit\s*\(/,
|
||||
'screen.js must destructure buildBoard from createSceneInit(...)');
|
||||
assert.match(src, /const\s*\{[^}]*_bgUnmountStyle[^}]*\}\s*=\s*createSceneInit\s*\(/,
|
||||
'screen.js must destructure _bgUnmountStyle from createSceneInit(...)');
|
||||
assert.match(src, /const\s*\{[^}]*_bcSyncMode[^}]*\}\s*=\s*createSceneInit\s*\(/,
|
||||
'screen.js must destructure _bcSyncMode from createSceneInit(...)');
|
||||
});
|
||||
|
||||
// ── §2 Export surface ─────────────────────────────────────────────────────────
|
||||
|
||||
test('createSceneInit returns exactly { initScene, buildBoard, _bgUnmountStyle, _bcSyncMode }', () => {
|
||||
const src = fs.readFileSync(sceneInitJs, 'utf8');
|
||||
// The return statement at the bottom of createSceneInit must name exactly these four
|
||||
assert.match(src,
|
||||
/return\s*\{\s*initScene\s*,\s*buildBoard\s*,\s*_bgUnmountStyle\s*,\s*_bcSyncMode\s*\}/,
|
||||
'return surface must be exactly { initScene, buildBoard, _bgUnmountStyle, _bcSyncMode }');
|
||||
});
|
||||
|
||||
test('createSceneInit does NOT export _bgLoadSettings (internal function)', () => {
|
||||
const src = fs.readFileSync(sceneInitJs, 'utf8');
|
||||
// _bgLoadSettings is internal; must not appear in the return object
|
||||
assert.doesNotMatch(src,
|
||||
/return\s*\{[^}]*_bgLoadSettings[^}]*\}/,
|
||||
'_bgLoadSettings must NOT be in the return surface');
|
||||
});
|
||||
|
||||
// ── §3 Class-killer guard ─────────────────────────────────────────────────────
|
||||
|
||||
test('createSceneInit body never assigns to a DI parameter name (class-killer)', () => {
|
||||
const src = fs.readFileSync(sceneInitJs, 'utf8');
|
||||
// Extract the DI parameter block (between first { and the closing }) of the factory signature
|
||||
const sigStart = src.indexOf('export function createSceneInit({');
|
||||
assert.ok(sigStart !== -1, 'createSceneInit signature not found');
|
||||
const bodyOpen = src.indexOf(') {', sigStart);
|
||||
assert.ok(bodyOpen !== -1, 'factory body open not found');
|
||||
const paramBlock = src.slice(sigStart, bodyOpen);
|
||||
// Collect setter names (setX) from the DI block
|
||||
const setterNames = [...paramBlock.matchAll(/\bset([A-Z][A-Za-z0-9]*)\b/g)].map(m => m[0]);
|
||||
assert.ok(setterNames.length > 10, `expected many setter params, got ${setterNames.length}`);
|
||||
const body = src.slice(bodyOpen);
|
||||
for (const name of setterNames) {
|
||||
// Assignment to the bare DI name (not a call) would be: `name = ` or `name=`
|
||||
const assignPat = new RegExp(`\\b${name}\\s*=(?!=)`, 'g');
|
||||
const hits = body.match(assignPat);
|
||||
assert.ok(!hits, `class-killer: body assigns to DI param '${name}' (${hits && hits.length} hit(s))`);
|
||||
}
|
||||
});
|
||||
|
||||
// ── §4 DI anti-vacuity ────────────────────────────────────────────────────────
|
||||
|
||||
test('createSceneInit receives ≥150 DI parameters (anti-vacuity)', () => {
|
||||
const src = fs.readFileSync(sceneInitJs, 'utf8');
|
||||
const sigStart = src.indexOf('export function createSceneInit({');
|
||||
const bodyOpen = src.indexOf(') {', sigStart);
|
||||
const paramBlock = src.slice(sigStart, bodyOpen);
|
||||
// Count unique identifiers that look like DI params
|
||||
const names = new Set([...paramBlock.matchAll(/\b([A-Za-z_][A-Za-z0-9_]*)\b/g)].map(m => m[1]));
|
||||
assert.ok(names.size >= 150, `expected ≥150 DI params, got ${names.size}`);
|
||||
});
|
||||
|
||||
// ── §5 Import correctness ─────────────────────────────────────────────────────
|
||||
|
||||
test('scene-init imports geoFretX and geoFretMid from geometry.js (not bare fretX/fretMid)', () => {
|
||||
const src = fs.readFileSync(sceneInitJs, 'utf8');
|
||||
assert.match(src, /import\s*\{[^}]*geoFretX[^}]*\}\s*from\s*['"]\.\/geometry\.js['"]/,
|
||||
'must import geoFretX from ./geometry.js');
|
||||
assert.match(src, /import\s*\{[^}]*geoFretMid[^}]*\}\s*from\s*['"]\.\/geometry\.js['"]/,
|
||||
'must import geoFretMid from ./geometry.js');
|
||||
// Should NOT import the bare names that don't exist in geometry.js
|
||||
assert.doesNotMatch(src,
|
||||
/import\s*\{[^}]*(?<!\w)fretX(?!\w)[^}]*\}\s*from\s*['"]\.\/geometry\.js['"]/,
|
||||
'must NOT import bare fretX from geometry.js (it exports geoFretX)');
|
||||
});
|
||||
|
||||
test('scene-init rebuilds fretX/fretMid as closures over getH3dFretUniform()', () => {
|
||||
const src = fs.readFileSync(sceneInitJs, 'utf8');
|
||||
assert.match(src, /const\s+fretX\s*=\s*f\s*=>\s*geoFretX\s*\(\s*f\s*,\s*getH3dFretUniform\s*\(\s*\)\s*\)/,
|
||||
'fretX must be rebuilt as: f => geoFretX(f, getH3dFretUniform())');
|
||||
assert.match(src, /const\s+fretMid\s*=\s*f\s*=>\s*geoFretMid\s*\(\s*f\s*,\s*getH3dFretUniform\s*\(\s*\)\s*\)/,
|
||||
'fretMid must be rebuilt as: f => geoFretMid(f, getH3dFretUniform())');
|
||||
});
|
||||
|
||||
// ── §6 Key functions present ───────────────────────────────────────────────────
|
||||
|
||||
test('initScene is defined as a function inside createSceneInit body', () => {
|
||||
const src = fs.readFileSync(sceneInitJs, 'utf8');
|
||||
assert.match(src, /function\s+initScene\s*\(\s*\)/,
|
||||
'initScene() must be defined inside scene-init.js');
|
||||
});
|
||||
|
||||
test('buildBoard is defined as a function inside createSceneInit body', () => {
|
||||
const src = fs.readFileSync(sceneInitJs, 'utf8');
|
||||
assert.match(src, /function\s+buildBoard\s*\(\s*\)/,
|
||||
'buildBoard() must be defined inside scene-init.js');
|
||||
});
|
||||
|
||||
test('_bgUnmountStyle is defined as a function inside createSceneInit body', () => {
|
||||
const src = fs.readFileSync(sceneInitJs, 'utf8');
|
||||
assert.match(src, /function\s+_bgUnmountStyle\s*\(\s*\)/,
|
||||
'_bgUnmountStyle() must be defined inside scene-init.js');
|
||||
});
|
||||
|
||||
test('_bcSyncMode is defined as a function inside createSceneInit body', () => {
|
||||
const src = fs.readFileSync(sceneInitJs, 'utf8');
|
||||
assert.match(src, /function\s+_bcSyncMode\s*\(\s*\)/,
|
||||
'_bcSyncMode() must be defined inside scene-init.js');
|
||||
});
|
||||
|
||||
// ── §7 Kill tests — functions that must NOT survive in screen.js ───────────────
|
||||
|
||||
test('initScene no longer defined in screen.js (moved to scene-init.js)', () => {
|
||||
const src = fs.readFileSync(screen3dJs, 'utf8');
|
||||
assert.doesNotMatch(src, /^\s*function\s+initScene\s*\(\s*\)/m,
|
||||
'initScene() must not be defined in screen.js — it moved to scene-init.js');
|
||||
});
|
||||
|
||||
test('buildBoard no longer defined in screen.js (moved to scene-init.js)', () => {
|
||||
const src = fs.readFileSync(screen3dJs, 'utf8');
|
||||
assert.doesNotMatch(src, /^\s*function\s+buildBoard\s*\(\s*\)/m,
|
||||
'buildBoard() must not be defined in screen.js — it moved to scene-init.js');
|
||||
});
|
||||
|
||||
test('_bgUnmountStyle no longer defined in screen.js (moved to scene-init.js)', () => {
|
||||
const src = fs.readFileSync(screen3dJs, 'utf8');
|
||||
assert.doesNotMatch(src, /^\s*function\s+_bgUnmountStyle\s*\(\s*\)/m,
|
||||
'_bgUnmountStyle() must not be defined in screen.js — it moved to scene-init.js');
|
||||
});
|
||||
|
||||
test('_bcSyncMode no longer defined in screen.js (moved to scene-init.js)', () => {
|
||||
const src = fs.readFileSync(screen3dJs, 'utf8');
|
||||
assert.doesNotMatch(src, /^\s*function\s+_bcSyncMode\s*\(\s*\)/m,
|
||||
'_bcSyncMode() must not be defined in screen.js — it moved to scene-init.js');
|
||||
});
|
||||
|
||||
// ── §8 plugin.json version bump ───────────────────────────────────────────────
|
||||
|
||||
test('plugin.json version is 3.52.0 (bumped for cut-16)', () => {
|
||||
const pkg = JSON.parse(fs.readFileSync(pluginJson, 'utf8'));
|
||||
assert.equal(pkg.version, '3.52.0',
|
||||
'plugin.json must be bumped to 3.52.0 for cut-16');
|
||||
});
|
||||
@@ -13,6 +13,7 @@ const path = require('node:path');
|
||||
|
||||
const highwayJs = path.join(__dirname, '..', '..', 'static', 'highway.js');
|
||||
const highway3dJs = path.join(__dirname, '..', '..', 'plugins', 'highway_3d', 'screen.js');
|
||||
const sceneInitJs = path.join(__dirname, '..', '..', 'plugins', 'highway_3d', 'src', 'scene-init.js'); // h3d-carve-16
|
||||
// The highway string-colour manager was carved out of app.js into its own
|
||||
// module (R3a).
|
||||
const appJs = path.join(__dirname, '..', '..', 'static', 'js', 'highway-colors.js');
|
||||
@@ -78,7 +79,8 @@ test('2D public API exposes getStringColors / setStringColors', () => {
|
||||
// ── 3D highway (plugins/highway_3d/screen.js) ─────────────────────────────
|
||||
|
||||
test('3D adds a custom palette path + h3dBgSetStringColors setter', () => {
|
||||
const src = fs.readFileSync(highway3dJs, 'utf8');
|
||||
// _bgLoadSettings (lines 89-90) moved to scene-init.js in h3d-carve-16 — combine both
|
||||
const src = fs.readFileSync(highway3dJs, 'utf8') + '\n' + fs.readFileSync(sceneInitJs, 'utf8');
|
||||
assert.match(src, /window\.h3dBgSetStringColors\s*=/, 'window.h3dBgSetStringColors must be defined');
|
||||
assert.match(src, /_bgWriteGlobal\('customColors'/, 'setter must persist customColors');
|
||||
assert.match(src, /_bgWriteGlobal\('palette',\s*'custom'\)/, "setter must flip palette to 'custom'");
|
||||
@@ -91,7 +93,8 @@ test('3D adds a custom palette path + h3dBgSetStringColors setter', () => {
|
||||
});
|
||||
|
||||
test('3D gem-body gradients follow the active palette (not hardcoded)', () => {
|
||||
const src = fs.readFileSync(highway3dJs, 'utf8');
|
||||
// _recolorGemGradients + _applyPaletteToMaterials moved to scene-init.js in h3d-carve-16
|
||||
const src = fs.readFileSync(sceneInitJs, 'utf8');
|
||||
// The gem bodies (strings 0..5) are a baked per-vertex gradient; a custom
|
||||
// palette must recolor them, else gems/sustain/vibrato heads stay stock.
|
||||
assert.match(src, /function _recolorGemGradients\(\)/, '_recolorGemGradients must exist');
|
||||
|
||||
@@ -10,6 +10,7 @@ const path = require('node:path');
|
||||
|
||||
const highwayJs = path.join(__dirname, '..', '..', 'static', 'highway.js');
|
||||
const highway3dJs = path.join(__dirname, '..', '..', 'plugins', 'highway_3d', 'screen.js');
|
||||
const sceneInitJs = path.join(__dirname, '..', '..', 'plugins', 'highway_3d', 'src', 'scene-init.js'); // h3d-carve-16
|
||||
|
||||
// Brace-balanced extraction so a future method that grows guards or
|
||||
// nested blocks doesn't get truncated by a naive `[^}]*\}` regex.
|
||||
@@ -131,10 +132,10 @@ test('api.setVisible accepts bool / null and re-emits inline', () => {
|
||||
});
|
||||
|
||||
test('3D Highway subscribes to highway:visibility and toggles wrap on hide', () => {
|
||||
// initScene moved to scene-init.js in h3d-carve-16; teardown stays in screen.js
|
||||
const sceneInitSrc = fs.readFileSync(sceneInitJs, 'utf8');
|
||||
const src = fs.readFileSync(highway3dJs, 'utf8');
|
||||
// Scope to lifecycle blocks so unrelated / commented mentions
|
||||
// elsewhere in screen.js can't cause false positives.
|
||||
const initSceneBlock = extractBlock(src, 'function initScene()');
|
||||
const initSceneBlock = extractBlock(sceneInitSrc, 'function initScene()');
|
||||
const teardownBlock = extractBlock(src, 'function teardown()');
|
||||
|
||||
// Listener registration with the documented event name (in init).
|
||||
@@ -146,23 +147,25 @@ test('3D Highway subscribes to highway:visibility and toggles wrap on hide', ()
|
||||
// Handler filters by canvas identity so splitscreen panels don't
|
||||
// hide each other's overlays — every instance receives every event
|
||||
// on the shared feedBack bus, so this gate is essential.
|
||||
assert.match(
|
||||
initSceneBlock,
|
||||
/e\.detail\.canvas\s*!==\s*highwayCanvas/,
|
||||
// Accept DI-rewritten form (getHighwayCanvas()) as well as original (highwayCanvas) — h3d-carve-16
|
||||
assert.ok(
|
||||
/e\.detail\.canvas\s*!==\s*highwayCanvas/.test(initSceneBlock) ||
|
||||
/e\.detail\.canvas\s*!==\s*getHighwayCanvas\(\)/.test(initSceneBlock),
|
||||
'handler must filter on event.detail.canvas !== highwayCanvas (splitscreen-safe)',
|
||||
);
|
||||
// Handler toggles wrap.style.display based on visible === false.
|
||||
assert.match(
|
||||
initSceneBlock,
|
||||
/wrap\.style\.display\s*=\s*v\s*===\s*false\s*\?\s*['"]none['"]\s*:\s*['"]['"]/,
|
||||
// Handler toggles wrap/getWrap() display based on visible === false.
|
||||
assert.ok(
|
||||
/wrap\.style\.display\s*=\s*v\s*===\s*false\s*\?\s*['"]none['"]\s*:\s*['"]/.test(initSceneBlock) ||
|
||||
/getWrap\(\)\.style\.display\s*=\s*v\s*===\s*false\s*\?\s*['"]none['"]\s*:\s*['"]/.test(initSceneBlock),
|
||||
'handler must hide the wrap when visible === false',
|
||||
);
|
||||
// Initial-sync on bind so renderers that mount while the canvas
|
||||
// is already hidden (e.g. plugin loaded mid-splitscreen) don't
|
||||
// leave the wrap stuck in the wrong state.
|
||||
assert.match(
|
||||
initSceneBlock,
|
||||
/highwayCanvas\.offsetParent\s*!==\s*null/,
|
||||
// Accept DI-rewritten form (getHighwayCanvas()) as well as direct ref — h3d-carve-16
|
||||
assert.ok(
|
||||
/highwayCanvas\.offsetParent\s*!==\s*null/.test(initSceneBlock) ||
|
||||
/getHighwayCanvas\(\)\.offsetParent\s*!==\s*null/.test(initSceneBlock),
|
||||
'initScene must compute initial visibility from local highwayCanvas (splitscreen-safe)',
|
||||
);
|
||||
// Subscribes to highway:canvas-replaced so the identity gate
|
||||
@@ -173,9 +176,10 @@ test('3D Highway subscribes to highway:visibility and toggles wrap on hide', ()
|
||||
/window\.feedBack\.on\(\s*['"]highway:canvas-replaced['"]/,
|
||||
'initScene must track canvas swaps so the visibility gate keeps matching',
|
||||
);
|
||||
assert.match(
|
||||
initSceneBlock,
|
||||
/highwayCanvas\s*=\s*e\.detail\.newCanvas/,
|
||||
// Accept DI-rewritten form for canvas-replaced handler — h3d-carve-16
|
||||
assert.ok(
|
||||
/highwayCanvas\s*=\s*e\.detail\.newCanvas/.test(initSceneBlock) ||
|
||||
/setHighwayCanvas\(\s*e\.detail\.newCanvas\s*\)/.test(initSceneBlock),
|
||||
'canvas-replaced handler must update the local highwayCanvas reference',
|
||||
);
|
||||
// Teardown unbinds both listeners.
|
||||
|
||||
Reference in New Issue
Block a user