Skip to content

Commit f52bc10

Browse files
committed
Fix Controls edit buttons to edit selected rows in place - PR_26162_047-controls-edit-row-in-place-fix
1 parent b9abd40 commit f52bc10

7 files changed

Lines changed: 438 additions & 425 deletions

File tree

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
# git status --short
22
M docs_build/dev/reports/coverage_changed_js_guardrail.txt
33
M docs_build/dev/reports/playwright_v8_coverage_report.txt
4-
M src/engine/input/NormalizedInputRegistry.js
5-
M tests/input/NormalizedInputRegistry.test.mjs
64
M tests/playwright/tools/InputMappingV2Tool.spec.mjs
75
M toolbox/controls/controls.js
8-
?? docs_build/dev/reports/controls-remove-physical-controller-actions-report.md
6+
?? docs_build/dev/reports/controls-edit-row-in-place-fix-report.md
97

108
# git ls-files --others --exclude-standard
11-
docs_build/dev/reports/controls-remove-physical-controller-actions-report.md
9+
docs_build/dev/reports/controls-edit-row-in-place-fix-report.md
1210

1311
# git diff --stat
14-
.../dev/reports/coverage_changed_js_guardrail.txt | 7 +--
15-
.../dev/reports/playwright_v8_coverage_report.txt | 18 +++---
16-
src/engine/input/NormalizedInputRegistry.js | 12 ++--
17-
tests/input/NormalizedInputRegistry.test.mjs | 8 ++-
18-
tests/playwright/tools/InputMappingV2Tool.spec.mjs | 55 ++++++++++++-----
19-
toolbox/controls/controls.js | 69 ++++++++++++++++++----
20-
6 files changed, 121 insertions(+), 48 deletions(-)
12+
.../dev/reports/coverage_changed_js_guardrail.txt | 4 +-
13+
.../dev/reports/playwright_v8_coverage_report.txt | 14 ++-
14+
tests/playwright/tools/InputMappingV2Tool.spec.mjs | 116 +++++++++++++++++++++
15+
toolbox/controls/controls.js | 14 ++-
16+
4 files changed, 130 insertions(+), 18 deletions(-)

docs_build/dev/reports/codex_review.diff

Lines changed: 226 additions & 396 deletions
Large diffs are not rendered by default.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# PR_26162_047 Controls Edit Row In Place Fix
2+
3+
## Branch Validation
4+
- PASS: Current branch is `main`.
5+
- Expected branch: `main`.
6+
- Evidence: `git status --short --branch` reported `## main...origin/main` before edits.
7+
8+
## Requirement Checklist
9+
- PASS: Read `docs_build/dev/PROJECT_INSTRUCTIONS.md` before edits.
10+
- PASS: Verified current git branch is `main` before edits.
11+
- PASS: Continued from PR_26162_046.
12+
- PASS: Toolbox > Controls edit behavior now replaces the selected row in-place.
13+
- PASS: Clicking the first row Edit button edits the first row.
14+
- PASS: Clicking a middle/later row Edit button edits that exact row in-place.
15+
- PASS: The edit row is no longer rendered at the top of the table.
16+
- PASS: Row order is preserved before, during, and after edit.
17+
- PASS: Save updates only the selected row.
18+
- PASS: Cancel restores only the selected row.
19+
- PASS: Trash deletes only the selected row.
20+
- PASS: DB-backed persistence is preserved through the shared DB/mock adapter.
21+
- PASS: Normalized Game Controls behavior from PR_26162_046 is preserved.
22+
- PASS: Added Playwright regression coverage for non-first-row in-place editing.
23+
- PASS: No sample JSON alignment, auth behavior, production account system, or unrelated rewrites were added.
24+
- PASS: Theme V2 only; no inline CSS, inline JS, script blocks, style blocks, or inline event handlers were introduced.
25+
26+
## Changed Files
27+
- `toolbox/controls/controls.js`
28+
- `tests/playwright/tools/InputMappingV2Tool.spec.mjs`
29+
- `docs_build/dev/reports/playwright_v8_coverage_report.txt`
30+
- `docs_build/dev/reports/coverage_changed_js_guardrail.txt`
31+
- `docs_build/dev/reports/controls-edit-row-in-place-fix-report.md`
32+
33+
## Impacted Lanes
34+
- Controls tool runtime lane.
35+
- Controls Playwright behavior lane.
36+
37+
## Validation Performed
38+
- PASS: `node --check toolbox/controls/controls.js`
39+
- PASS: `node --check tests/playwright/tools/InputMappingV2Tool.spec.mjs`
40+
- PASS: `git diff --check`
41+
- PASS: `rg -n -P "<style|style\\s*=|\\son[a-z]+\\s*=" toolbox/controls/index.html toolbox/controls/controls.js` returned no matches.
42+
- PASS: `npx playwright test tests/playwright/tools/InputMappingV2Tool.spec.mjs` passed 7/7.
43+
44+
## Playwright Result
45+
- PASS: Existing Controls behavior remains green.
46+
- PASS: Regression test confirms the first row edits in-place.
47+
- PASS: Regression test confirms a middle row (`Aim Down`) edits in-place and not at the top.
48+
- PASS: Regression test confirms Cancel restores only the selected row.
49+
- PASS: Regression test confirms Save updates only the selected row.
50+
- PASS: Regression test confirms Trash deletes only the selected row.
51+
- PASS: Regression test confirms row order is preserved after Save, Cancel, Trash, and reload.
52+
- PASS: Regression test confirms DB-backed persistence after reload.
53+
54+
## V8 Coverage
55+
- PASS: `docs_build/dev/reports/playwright_v8_coverage_report.txt` refreshed from targeted Playwright.
56+
- PASS: `(95%) toolbox/controls/controls.js - current PR changed runtime JS file with browser V8 coverage`.
57+
- NOTE: The shared coverage helper also lists `src/engine/input/NormalizedInputRegistry.js` as a reporter HEAD-baseline file. That file is not part of this PR diff and is marked advisory in the coverage reports.
58+
59+
## Skipped Lanes
60+
- Full samples validation: SKIPPED per request. Safe to skip because this PR changes only Toolbox > Controls table edit rendering and targeted Playwright coverage.
61+
- Full repository test suite: SKIPPED. Safe to skip because the affected runtime behavior is covered by the targeted Controls Playwright lane.
62+
- Account/User Controls-only lane: covered as part of `InputMappingV2Tool.spec.mjs`; no Account runtime code changed.
63+
64+
## Samples Decision
65+
- Samples validation was not run, per request.
66+
67+
## Manual Validation Steps
68+
1. Open `toolbox/controls/index.html`.
69+
2. Click Edit on the first Game Controls row and confirm the first row becomes the edit row.
70+
3. Save a new label and confirm only that row changes.
71+
4. Click Edit on a middle/later row such as `Aim Down` and confirm the edit row appears in that same table position.
72+
5. Change the label, click Cancel, and confirm the original row is restored in place.
73+
6. Click Edit on a later row such as `Pause`, save a new label, and confirm only that row changes.
74+
7. Click Trash on a different row and confirm only that row is deleted.
75+
8. Reload and confirm row order and saved values persist.

docs_build/dev/reports/coverage_changed_js_guardrail.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ Missing changed runtime JS files are WARN, not FAIL.
66
Source: Playwright/Chromium built-in V8 coverage from the active Playwright run.
77

88
Changed runtime JS files considered:
9-
(89%) src/engine/input/NormalizedInputRegistry.js - executed lines 321/321; executed functions 24/27
10-
(93%) toolbox/controls/controls.js - executed lines 548/548; executed functions 56/60
9+
(95%) toolbox/controls/controls.js - current PR changed runtime JS file with browser V8 coverage; executed lines 546/546; executed functions 57/60
10+
(89%) src/engine/input/NormalizedInputRegistry.js - reporter HEAD-baseline file with browser V8 coverage; not part of the current PR diff; advisory only
1111

1212
Guardrail warnings:
1313
(100%) none - no changed runtime JS coverage warnings

docs_build/dev/reports/playwright_v8_coverage_report.txt

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ Note: entry percentages use function coverage when available, otherwise line cov
1212
Note: coverage entries are aggregated across every page/tool where coverageReporter.start(page) and coverageReporter.stop(page) ran.
1313

1414
Exercised tool entry points detected:
15-
(88%) Toolbox Index - exercised 3 runtime JS files
15+
(89%) Toolbox Index - exercised 3 runtime JS files
1616
(0%) Tool Template V2 - not exercised by this Playwright run
1717
(62%) Theme V2 Shared JS - exercised 2 runtime JS files
1818

1919
Changed runtime JS files covered:
20-
(89%) src/engine/input/NormalizedInputRegistry.js - executed lines 321/321; executed functions 24/27
21-
(93%) toolbox/controls/controls.js - executed lines 548/548; executed functions 56/60
20+
(95%) toolbox/controls/controls.js - current PR changed runtime JS file with browser V8 coverage; executed lines 546/546; executed functions 57/60
21+
(89%) src/engine/input/NormalizedInputRegistry.js - reporter HEAD-baseline file with browser V8 coverage; not part of the current PR diff; advisory only
2222

2323
Files with executed line/function counts where available:
2424
(11%) src/engine/input/InputMappingManifest.js - executed lines 78/78; executed functions 1/9
@@ -37,17 +37,15 @@ Files with executed line/function counts where available:
3737
(75%) toolbox/tool-registry-api-client.js - executed lines 152/152; executed functions 21/28
3838
(80%) src/engine/input/GamepadState.js - executed lines 100/100; executed functions 16/20
3939
(89%) src/engine/input/NormalizedInputRegistry.js - executed lines 321/321; executed functions 24/27
40-
(93%) toolbox/controls/controls.js - executed lines 548/548; executed functions 56/60
4140
(94%) account/user-controls-page.js - executed lines 961/961; executed functions 111/118
41+
(95%) toolbox/controls/controls.js - executed lines 546/546; executed functions 57/60
4242
(100%) account/user-controls.js - executed lines 2/2; executed functions 1/1
4343
(100%) toolbox/controls/controls-api-client.js - executed lines 12/12; executed functions 2/2
4444

4545
Uncovered or low-coverage changed JS files:
4646
(100%) none - no low-coverage changed runtime JS files
4747

4848
Changed JS files considered:
49-
(0%) tests/input/NormalizedInputRegistry.test.mjs - changed JS file not collected as browser runtime coverage
5049
(0%) tests/playwright/tools/InputMappingV2Tool.spec.mjs - changed JS file not collected as browser runtime coverage
51-
(89%) src/engine/input/NormalizedInputRegistry.js - changed JS file with browser V8 coverage
52-
(93%) toolbox/controls/controls.js - changed JS file with browser V8 coverage
53-
(94%) account/user-controls-page.js - changed JS file with browser V8 coverage
50+
(89%) src/engine/input/NormalizedInputRegistry.js - reporter HEAD-baseline file with browser V8 coverage; not part of the current PR diff
51+
(95%) toolbox/controls/controls.js - current PR changed runtime JS file with browser V8 coverage

tests/playwright/tools/InputMappingV2Tool.spec.mjs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,26 @@ const GAME_CONTROL_NORMALIZED_INPUTS = [
2424
"action.select",
2525
];
2626

27+
const GAME_CONTROL_USAGE_LABELS = [
28+
"Move Left",
29+
"Move Right",
30+
"Move Up",
31+
"Move Down",
32+
"Aim Left",
33+
"Aim Right",
34+
"Aim Up",
35+
"Aim Down",
36+
"Primary Action",
37+
"Secondary Action",
38+
"Third Action",
39+
"Fourth Action",
40+
"Confirm",
41+
"Cancel",
42+
"Pause",
43+
"Start",
44+
"Select",
45+
];
46+
2747
test.beforeEach(async ({ page }) => {
2848
await installPlaywrightStorageIsolation(page, {
2949
lane: "controls",
@@ -169,6 +189,16 @@ async function editGameControl(page, rowText, {
169189
await page.locator("[data-input-save-mapping]").click();
170190
}
171191

192+
async function gameControlRowLabels(page) {
193+
return page.locator("[data-input-mapping-table] tbody tr").evaluateAll((rows) => rows.map((row) => {
194+
const usageInput = row.querySelector("[data-input-row-usage-label]");
195+
if (usageInput) {
196+
return usageInput.value;
197+
}
198+
return row.children[2]?.textContent?.trim() || "";
199+
}));
200+
}
201+
172202
test("Toolbox Controls shows game controls only and keeps presets wireframe safe", async ({ page }) => {
173203
const failures = await openRepoPage(page, "/toolbox/controls/index.html");
174204

@@ -258,6 +288,92 @@ test("Toolbox Controls shows game controls only and keeps presets wireframe safe
258288
}
259289
});
260290

291+
test("Game Controls edits the selected row in place and preserves row order", async ({ page }) => {
292+
const failures = await openRepoPage(page, "/toolbox/controls/index.html");
293+
294+
try {
295+
await expect(page.locator("[data-input-mapping-row]")).toHaveCount(GAME_CONTROL_NORMALIZED_INPUTS.length);
296+
expect(await gameControlRowLabels(page)).toEqual(GAME_CONTROL_USAGE_LABELS);
297+
298+
await page.locator("[data-input-mapping-table] tbody tr").first().getByRole("button", { name: "Edit" }).click();
299+
await expect(page.locator("[data-input-mapping-table] tbody tr").first()).toHaveAttribute("data-input-editing-row", "true");
300+
await expect(page.locator("[data-input-row-usage-label]")).toHaveValue("Move Left");
301+
await page.locator("[data-input-row-usage-label]").fill("Move Left Prime");
302+
await page.locator("[data-input-save-mapping]").click();
303+
await expect(page.locator("[data-input-mapping-table] tbody tr").first()).toContainText("Move Left Prime");
304+
expect(await gameControlRowLabels(page)).toEqual([
305+
"Move Left Prime",
306+
...GAME_CONTROL_USAGE_LABELS.slice(1),
307+
]);
308+
309+
const aimDownIndex = GAME_CONTROL_USAGE_LABELS.indexOf("Aim Down");
310+
await page.locator("[data-input-mapping-row]").filter({ hasText: "Aim Down" }).first().getByRole("button", { name: "Edit" }).click();
311+
const rowsDuringMiddleEdit = page.locator("[data-input-mapping-table] tbody tr");
312+
await expect(rowsDuringMiddleEdit.nth(aimDownIndex)).toHaveAttribute("data-input-editing-row", "true");
313+
await expect(rowsDuringMiddleEdit.first()).not.toHaveAttribute("data-input-editing-row", "true");
314+
await expect(page.locator("[data-input-row-usage-label]")).toHaveValue("Aim Down");
315+
expect(await gameControlRowLabels(page)).toEqual([
316+
"Move Left Prime",
317+
...GAME_CONTROL_USAGE_LABELS.slice(1, aimDownIndex),
318+
"Aim Down",
319+
...GAME_CONTROL_USAGE_LABELS.slice(aimDownIndex + 1),
320+
]);
321+
await page.locator("[data-input-row-usage-label]").fill("Aim Down Draft");
322+
await page.locator("[data-input-cancel-mapping]").click();
323+
expect(await gameControlRowLabels(page)).toEqual([
324+
"Move Left Prime",
325+
...GAME_CONTROL_USAGE_LABELS.slice(1),
326+
]);
327+
328+
const pauseIndex = GAME_CONTROL_USAGE_LABELS.indexOf("Pause");
329+
await page.locator("[data-input-mapping-row]").filter({ hasText: "Pause" }).first().getByRole("button", { name: "Edit" }).click();
330+
await expect(page.locator("[data-input-mapping-table] tbody tr").nth(pauseIndex)).toHaveAttribute("data-input-editing-row", "true");
331+
await page.locator("[data-input-row-usage-label]").fill("Pause Menu");
332+
await page.locator("[data-input-save-mapping]").click();
333+
expect(await gameControlRowLabels(page)).toEqual([
334+
"Move Left Prime",
335+
...GAME_CONTROL_USAGE_LABELS.slice(1, pauseIndex),
336+
"Pause Menu",
337+
...GAME_CONTROL_USAGE_LABELS.slice(pauseIndex + 1),
338+
]);
339+
340+
await page.locator("[data-input-mapping-row]").filter({ hasText: "Secondary Action" }).first().getByRole("button", { name: "Trash" }).click();
341+
const labelsAfterTrash = await gameControlRowLabels(page);
342+
expect(labelsAfterTrash).not.toContain("Secondary Action");
343+
expect(labelsAfterTrash).toEqual([
344+
"Move Left Prime",
345+
"Move Right",
346+
"Move Up",
347+
"Move Down",
348+
"Aim Left",
349+
"Aim Right",
350+
"Aim Up",
351+
"Aim Down",
352+
"Primary Action",
353+
"Third Action",
354+
"Fourth Action",
355+
"Confirm",
356+
"Cancel",
357+
"Pause Menu",
358+
"Start",
359+
"Select",
360+
]);
361+
362+
let records = await inputMappingRecords(page);
363+
expect(records).toHaveLength(GAME_CONTROL_NORMALIZED_INPUTS.length - 1);
364+
expect(records.map((record) => record.usageLabel)).toEqual(labelsAfterTrash);
365+
366+
await page.reload({ waitUntil: "networkidle" });
367+
expect(await gameControlRowLabels(page)).toEqual(labelsAfterTrash);
368+
records = await inputMappingRecords(page);
369+
expect(records.map((record) => record.usageLabel)).toEqual(labelsAfterTrash);
370+
371+
await expectNoPageFailures(failures);
372+
} finally {
373+
await closeWithCoverage(page, failures);
374+
}
375+
});
376+
261377
test("Game Controls validates rows and persists device-agnostic event fields", async ({ page }) => {
262378
const failures = await openRepoPage(page, "/toolbox/controls/index.html");
263379

toolbox/controls/controls.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ function renderMappingRow(mapping) {
391391
function renderEditingRow(values = {}) {
392392
const row = document.createElement("tr");
393393
row.dataset.inputEditingRow = "true";
394+
row.dataset.inputEditingRowId = values.id || "";
394395

395396
const enabledInput = document.createElement("input");
396397
enabledInput.type = "checkbox";
@@ -440,14 +441,11 @@ function renderMappings() {
440441
if (!elements.list) {
441442
return;
442443
}
443-
const rows = [];
444-
if (editingRow) {
445-
rows.push(renderEditingRow(editingRow.values));
446-
}
447-
const visibleMappings = editingRow?.id
448-
? mappings.filter((mapping) => mapping.id !== editingRow.id)
449-
: mappings;
450-
rows.push(...visibleMappings.map((mapping) => renderMappingRow(mapping)));
444+
const rows = mappings.map((mapping) => (
445+
editingRow?.id === mapping.id
446+
? renderEditingRow(editingRow.values)
447+
: renderMappingRow(mapping)
448+
));
451449
if (!rows.length) {
452450
const row = document.createElement("tr");
453451
const cell = document.createElement("td");

0 commit comments

Comments
 (0)