Skip to content

Commit 9855152

Browse files
committed
Add guided defaults and UAT testability to MIDI Studio V2 - PR_26146_014-midi-studio-v2-uat-testability-and-guided-defaults
1 parent 176119e commit 9855152

11 files changed

Lines changed: 307 additions & 14 deletions
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# PR_26146_014 MIDI Studio V2 UAT Testability And Guided Defaults Validation
2+
3+
## Scope
4+
5+
- Added explicit visible choices for key, style, snap subdivision, instrument/lane type, export target type, and empty/normalized section selectors.
6+
- Added a visible Use Example Test Song action that loads clearly labeled demo toolState data and fills guided Song Sheet/grid inputs for manual UAT.
7+
- Added a compact How To Test This Tool accordion with the requested six manual test steps.
8+
- Added clearer empty states for no song selected, no section selected, no grid data, no MIDI source, and no rendered export targets.
9+
- Preserved MIDI parsing, guided Song Sheet parsing, generated lanes, snapping, playhead/loop timing preview, rendered preview/export status, and invalid payload rejection behavior.
10+
11+
## Validation
12+
13+
- PASS: `node --check tools/midi-studio-v2/js/controls/ActionNavControl.js`
14+
- PASS: `node --check tools/midi-studio-v2/js/controls/SongSheetControl.js`
15+
- PASS: `node --check tools/midi-studio-v2/js/controls/InstrumentGridControl.js`
16+
- PASS: `node --check tools/midi-studio-v2/js/controls/SongDetailsControl.js`
17+
- PASS: `node --check tools/midi-studio-v2/js/controls/RenderedExportActionsControl.js`
18+
- PASS: `node --check tools/midi-studio-v2/js/MidiStudioV2App.js`
19+
- PASS: `node --check tools/midi-studio-v2/js/bootstrap.js`
20+
- PASS: `node --check tests/playwright/tools/MidiStudioV2.spec.mjs`
21+
- PASS: `cmd /c "set PLAYWRIGHT_BROWSERS_PATH=0&& npx.cmd playwright test tests/playwright/tools/MidiStudioV2.spec.mjs"` passed 30 tests.
22+
- PASS: `git diff --check` completed with only line-ending normalization warnings.
23+
- PASS: `Select-String -Path tools/midi-studio-v2/index.html -Pattern '<script(?![^>]*src=)|<style|\son[a-z]+\s*='` returned no inline script/style/event handler matches.
24+
25+
## Playwright Coverage
26+
27+
Targeted MIDI Studio V2 Playwright coverage validates:
28+
29+
- dropdowns are populated with visible options
30+
- empty states are visible and actionable
31+
- Use Example Test Song creates explicit demo toolState data
32+
- guided test steps are visible
33+
- generated demo data can be used to test grid alignment
34+
- playhead timing preview works with demo data
35+
- export buttons report not-implemented and missing-target status accurately
36+
- invalid payload rejection still happens before render
37+
- existing MIDI inspection, Song Sheet, snapping, generated lanes, playhead, loop, and rendered preview behaviors remain covered
38+
39+
Workspace Manager V2 registration/handoff was not run because Workspace Manager files were not touched.
40+
41+
Full samples smoke test was skipped per request. Samples decision: SKIP because sample JSON alignment is out of scope.

tests/playwright/tools/MidiStudioV2.spec.mjs

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,8 @@ async function openMidiStudio(page, routePayload = validManifest, midiRoutes = {
186186

187187
async function fillGuidedSongSheet(page, { intro = "Am F", key = "A minor", loop = "Am F C G", style = "retro-arcade", tempo = "132" } = {}) {
188188
await page.locator("#songSheetTempoInput").fill(tempo);
189-
await page.locator("#songSheetKeyInput").fill(key);
190-
await page.locator("#songSheetStyleInput").fill(style);
189+
await page.locator("#songSheetKeyInput").selectOption(key);
190+
await page.locator("#songSheetStyleInput").selectOption(style);
191191
await page.locator("#songSheetIntroInput").fill(intro);
192192
await page.locator("#songSheetLoopInput").fill(loop);
193193
}
@@ -245,12 +245,71 @@ test.describe("MIDI Studio V2", () => {
245245
}
246246
});
247247

248+
test("shows guided visible options and how-to-test steps", async ({ page }) => {
249+
const server = await openMidiStudio(page);
250+
try {
251+
await expect(page.locator("#songSheetKeyInput option")).toContainText(["Choose key", "A minor", "C major", "D minor", "E minor", "G major"]);
252+
await expect(page.locator("#songSheetStyleInput option")).toContainText(["Choose style", "retro-arcade", "chip", "orchestral-boss", "ambient-loop", "victory-fanfare"]);
253+
await expect(page.locator("#instrumentGridSubdivisionInput option")).toContainText(["1/1", "1/2", "1/4", "1/8", "1/16"]);
254+
await expect(page.locator("#instrumentGridLaneTypeSelect option")).toContainText(["Chords", "Bass", "Pad", "Lead", "Drums"]);
255+
await expect(page.locator("#renderedExportTargetTypeSelect option")).toContainText(["WAV", "MP3", "OGG"]);
256+
await expect(page.locator("#instrumentGridSectionSelect")).toContainText("No section selected");
257+
await expect(page.locator("#instrumentGridTransportState")).toContainText("No section selected. Normalize grid data before testing section timing.");
258+
await expect(page.locator("#howToTestContent")).toContainText("Step 1: choose style/key/tempo");
259+
await expect(page.locator("#howToTestContent")).toContainText("Step 6: test export action status");
260+
} finally {
261+
await workspaceV2CoverageReporter.stop(page);
262+
await server.close();
263+
}
264+
});
265+
266+
test("loads explicit demo test song data for UAT grid and timing preview", async ({ page }) => {
267+
const server = await openMidiStudio(page);
268+
try {
269+
await page.locator("#useExampleButton").click();
270+
await expect(page.locator("#songList")).toContainText("Demo Test Song");
271+
await expect(page.locator("#songList")).toContainText("Demo Missing Target");
272+
await expect(page.locator("#songSheetKeyInput")).toHaveValue("A minor");
273+
await expect(page.locator("#songSheetStyleInput")).toHaveValue("retro-arcade");
274+
await expect(page.locator("#instrumentGridSectionsInput")).toHaveValue("intro:1, loop:1, victory:1");
275+
await expect(page.locator("#instrumentGridChordsInput")).toHaveValue("Am F C G | Am F C G | C G F Am");
276+
await expect(page.locator("#statusLog")).toHaveValue(/OK Loaded explicit demo test song data\. Demo paths are declared for UAT only; they are not hidden fallback assets\./);
277+
278+
await page.locator("#generateBassFromChordsButton").click();
279+
await page.locator("#generatePadFromChordsButton").click();
280+
await page.locator("#generateArpeggioFromChordsButton").click();
281+
await page.locator("#generateBasicDrumsButton").click();
282+
await page.locator("#normalizeInstrumentGridButton").click();
283+
await expect(page.locator("#instrumentGridOutput")).toContainText("victory");
284+
await expect(page.locator(".midi-studio-v2__grid-cell--bar")).toHaveCount(3);
285+
await expect(page.locator("#instrumentGridSectionSelect")).toContainText("victory");
286+
const demoModel = await page.evaluate(() => window.__midiStudioV2App.lastInstrumentGridResult);
287+
expect(demoModel).toMatchObject({ barCount: 3, ok: true });
288+
expect(demoModel.timeline.some((event) => event.source === "generated" && event.lane === "bass")).toBe(true);
289+
290+
await page.locator("#instrumentGridLoopStartSelect").selectOption("loop");
291+
await page.locator("#instrumentGridLoopEndSelect").selectOption("victory");
292+
expect(await page.locator(".midi-studio-v2__grid-cell--loop-region").count()).toBeGreaterThan(0);
293+
await page.locator("#playLoopButton").click();
294+
await expect(page.locator("#statusLog")).toHaveValue(/WARN Live playback synthesis not implemented\. Playing timing-preview playhead only; no audio playback was started\./);
295+
await expect(page.locator("#instrumentGridTransportState")).toContainText("Playing loop timing preview: loop to victory");
296+
await page.locator("#exportOggButton").click();
297+
await expect(page.locator("#statusLog")).toHaveValue(/WARN Export rendering not implemented for OGG\. Planned target: assets\/music\/demo\/demo-test-song\.ogg\./);
298+
await page.locator('[data-song-id="demo-missing-target"]').click();
299+
await page.locator("#exportWavButton").click();
300+
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Missing rendered WAV export target for Demo Missing Target\. Add music\.songs\[\]\.rendered\.wav before exporting\./);
301+
} finally {
302+
await workspaceV2CoverageReporter.stop(page);
303+
await server.close();
304+
}
305+
});
306+
248307
test("selects multiple songs and updates source, director, and rendered targets", async ({ page }) => {
249308
const server = await openMidiStudio(page);
250309
try {
251310
await page.locator('[data-song-id="combat-light"]').click();
252311
await expect(page.locator('[data-song-id="combat-light"]')).toHaveAttribute("aria-pressed", "true");
253-
await expect(page.locator("#songSourceField")).toHaveValue("missing sourceMidi");
312+
await expect(page.locator("#songSourceField")).toHaveValue("No MIDI source declared.");
254313
await expect(page.locator("#instrumentSetField")).toHaveValue("Combat GM");
255314
await expect(page.locator("#renderedTargets")).toContainText("combat-light.mp3");
256315
await expect(page.locator("#directorPanel")).toContainText("Short encounter loop.");
@@ -448,7 +507,7 @@ test.describe("MIDI Studio V2", () => {
448507
await expect(page.locator("#songSheetSummary")).not.toContainText("intro:");
449508
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Song Sheet rejected: Invalid tempo\/BPM\. Enter a positive number before parsing the guided Song Sheet\./);
450509
await page.locator("#songSheetTempoInput").fill("132");
451-
await page.locator("#songSheetKeyInput").fill("");
510+
await page.locator("#songSheetKeyInput").selectOption("");
452511
await page.locator("#parseSongSheetButton").click();
453512
await expect(page.locator("#songSheetSummary")).toContainText("Missing key. Enter a key before parsing the guided Song Sheet.");
454513
await expect(page.locator("#songSheetSummary")).not.toContainText("loop:");
@@ -906,6 +965,11 @@ Am F`);
906965
try {
907966
await expect(page.locator("#songList")).toContainText("No MIDI songs loaded.");
908967
await expect(page.locator("#playButton")).toBeDisabled();
968+
await expect(page.locator("#songDetails")).toContainText("No song selected");
969+
await expect(page.locator("#instrumentGridOutput")).toContainText("No grid data normalized. Enter sections/chords or use the example test song, then choose Normalize Grid.");
970+
await expect(page.locator("#instrumentGridSectionSelect")).toContainText("No section selected");
971+
await expect(page.locator("#songSourceField")).toHaveValue("No song selected");
972+
await expect(page.locator("#renderedTargets")).toContainText("No rendered WAV target selected.");
909973
await expect(page.locator("#statusLog")).toHaveValue(/FAIL MIDI Studio V2 payload rejected before render .* music\.songs\[0\]\.id is required\./);
910974
await page.locator("#exportWavButton").click();
911975
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Missing MIDI song for WAV export\. Load or select a song before exporting\./);

tools/midi-studio-v2/index.html

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
3939

4040
<nav class="tool-starter__menu tool-starter__tool__menu midi-studio-v2__tool-menu" aria-label="Tool actions" data-launch-mode-nav="tool">
4141
<button id="toolImportManifestButton" type="button">Import Manifest</button>
42+
<button id="useExampleButton" type="button">Use Example Test Song</button>
4243
<button id="toolCopyJsonButton" type="button" disabled>Copy JSON</button>
4344
<button id="toolExportToolStateButton" type="button" disabled>Export JSON</button>
4445
<input id="toolImportManifestInput" type="file" accept="application/json,.json" hidden>
@@ -93,11 +94,25 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
9394
</label>
9495
<label class="tool-starter__field" for="songSheetKeyInput">
9596
<span>Key</span>
96-
<input id="songSheetKeyInput" type="text" placeholder="A minor">
97+
<select id="songSheetKeyInput">
98+
<option value="">Choose key</option>
99+
<option value="A minor">A minor</option>
100+
<option value="C major">C major</option>
101+
<option value="D minor">D minor</option>
102+
<option value="E minor">E minor</option>
103+
<option value="G major">G major</option>
104+
</select>
97105
</label>
98106
<label class="tool-starter__field" for="songSheetStyleInput">
99107
<span>Style</span>
100-
<input id="songSheetStyleInput" type="text" placeholder="retro-arcade">
108+
<select id="songSheetStyleInput">
109+
<option value="">Choose style</option>
110+
<option value="retro-arcade">retro-arcade</option>
111+
<option value="chip">chip</option>
112+
<option value="orchestral-boss">orchestral-boss</option>
113+
<option value="ambient-loop">ambient-loop</option>
114+
<option value="victory-fanfare">victory-fanfare</option>
115+
</select>
101116
</label>
102117
<label class="tool-starter__field" for="songSheetIntroInput">
103118
<span>Intro chord progression</span>
@@ -195,6 +210,16 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
195210
<option value="16">1/16</option>
196211
</select>
197212
</label>
213+
<label class="tool-starter__field" for="instrumentGridLaneTypeSelect">
214+
<span>Instrument/lane type</span>
215+
<select id="instrumentGridLaneTypeSelect">
216+
<option value="chords">Chords</option>
217+
<option value="bass">Bass</option>
218+
<option value="pad">Pad</option>
219+
<option value="lead">Lead</option>
220+
<option value="drums">Drums</option>
221+
</select>
222+
</label>
198223
</div>
199224
<div class="midi-studio-v2__grid-lane-inputs">
200225
<label class="tool-starter__field" for="instrumentGridChordsInput">
@@ -262,6 +287,23 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
262287
</section>
263288

264289
<aside class="tool-starter__panel tool-starter__panel--right" aria-label="MIDI output">
290+
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true">
291+
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="howToTestContent">
292+
<span>How To Test This Tool</span>
293+
<span class="accordion-v2__icon" aria-hidden="true">+</span>
294+
</button>
295+
<div id="howToTestContent" class="accordion-v2__content">
296+
<ol class="midi-studio-v2__test-steps">
297+
<li>Step 1: choose style/key/tempo</li>
298+
<li>Step 2: enter intro/loop chords</li>
299+
<li>Step 3: generate lanes</li>
300+
<li>Step 4: inspect grid alignment</li>
301+
<li>Step 5: test playhead/loop timing preview</li>
302+
<li>Step 6: test export action status</li>
303+
</ol>
304+
</div>
305+
</section>
306+
265307
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true">
266308
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="directorContent">
267309
<span>Game Music Director</span>
@@ -276,6 +318,14 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
276318
<div class="accordion-v2__header midi-studio-v2__rendered-targets-header" aria-expanded="true" aria-controls="renderedTargetsContent">
277319
<span>Rendered Export Targets</span>
278320
<div class="midi-studio-v2__header-actions" aria-label="Rendered export actions">
321+
<label class="midi-studio-v2__compact-field" for="renderedExportTargetTypeSelect">
322+
<span>Export target type</span>
323+
<select id="renderedExportTargetTypeSelect">
324+
<option value="wav">WAV</option>
325+
<option value="mp3">MP3</option>
326+
<option value="ogg">OGG</option>
327+
</select>
328+
</label>
279329
<button id="exportWavButton" type="button">Export WAV</button>
280330
<button id="exportMp3Button" type="button">Export MP3</button>
281331
<button id="exportOggButton" type="button">Export OGG</button>

0 commit comments

Comments
 (0)