From 97f273eb6099887ef21f2f3b350235ee76fa86ac Mon Sep 17 00:00:00 2001 From: DavidQ Date: Tue, 23 Jun 2026 13:38:38 -0400 Subject: [PATCH] PR_26174_ALFA_022-idea-board-status-dropdown-fix --- ...d-status-dropdown-fix_branch-validation.md | 17 + ...us-dropdown-fix_manual-validation-notes.md | 6 + ...2-idea-board-status-dropdown-fix_report.md | 18 + ...tus-dropdown-fix_requirements-checklist.md | 12 + ...ard-status-dropdown-fix_validation-lane.md | 11 + .../dev/reports/codex_changed_files.txt | 4 +- docs_build/dev/reports/codex_review.diff | 364 ++++++------------ .../tools/IdeaBoardTableNotes.spec.mjs | 25 +- .../tools/ToolboxRoutePages.spec.mjs | 6 + toolbox/idea-board/index.js | 12 +- 10 files changed, 208 insertions(+), 267 deletions(-) create mode 100644 docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_branch-validation.md create mode 100644 docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_manual-validation-notes.md create mode 100644 docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_report.md create mode 100644 docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_requirements-checklist.md create mode 100644 docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_validation-lane.md diff --git a/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_branch-validation.md b/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_branch-validation.md new file mode 100644 index 000000000..1a48b0637 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_branch-validation.md @@ -0,0 +1,17 @@ +# Branch Validation + +PR: PR_26174_ALFA_022-idea-board-status-dropdown-fix + +Status: PASS with documented legacy-lane warning + +## Checks + +- PASS: Current branch is `pr/26174-ALFA-022-idea-board-status-dropdown-fix`. +- PASS: Started from the current stacked Alfa branch. +- PASS: No main merge was performed. +- PASS: Work stayed inside the requested Idea Board status dropdown/filter, targeted tests, and required report scope. +- PASS: Generated required reports and ZIP artifact. + +## Warning + +- WARN: The touched Toolbox launch-route check reached the updated Idea Board status option assertions, then failed on the existing `500 /api/game-journey/completion-metrics` request. diff --git a/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_manual-validation-notes.md b/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_manual-validation-notes.md new file mode 100644 index 000000000..c1477273b --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_manual-validation-notes.md @@ -0,0 +1,6 @@ +# Manual Validation Notes + +- Confirmed the code has separate editable and filter status option lists. +- Confirmed editable dropdown expectations include only New, Exploring, Refining, and Ready. +- Confirmed filter expectations include New, Exploring, Refining, Ready, Project, and Archived. +- Confirmed the touched launch-route test reached the new status option assertions before the existing completion-metrics warning. diff --git a/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_report.md b/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_report.md new file mode 100644 index 000000000..b2ce2c852 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_report.md @@ -0,0 +1,18 @@ +# PR_26174_ALFA_022-idea-board-status-dropdown-fix + +## Summary + +Split Idea Board status choices into explicit editable and filter option lists. + +## Changes + +- Added `editableStatusOptions` with New, Exploring, Refining, and Ready. +- Added `filterStatusOptions` with New, Exploring, Refining, Ready, Project, and Archived. +- Updated editable status dropdown rendering to use only editable statuses. +- Updated status filter rendering and Select All behavior to use filter statuses. +- Updated targeted Playwright coverage for filter options and editable dropdown options. + +## Notes + +- No unrelated cleanup was performed. +- Project and Archived remain available for filtering but do not appear in editable Status dropdowns. diff --git a/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_requirements-checklist.md b/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_requirements-checklist.md new file mode 100644 index 000000000..ec24ec9d5 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_requirements-checklist.md @@ -0,0 +1,12 @@ +# Requirement Checklist + +- PASS: Created `editableStatusOptions` with New, Exploring, Refining, and Ready. +- PASS: Created `filterStatusOptions` with New, Exploring, Refining, Ready, Project, and Archived. +- PASS: Editable Status dropdown uses `editableStatusOptions`. +- PASS: Status Filter accordion uses `filterStatusOptions`. +- PASS: Project does not appear in editable Status dropdowns. +- PASS: Archived does not appear in editable Status dropdowns. +- PASS: Project remains available for filtering. +- PASS: Archived remains available for filtering. +- PASS: Targeted Playwright tests updated. +- PASS: No unrelated cleanup performed. diff --git a/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_validation-lane.md b/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_validation-lane.md new file mode 100644 index 000000000..b76ed2d83 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_022-idea-board-status-dropdown-fix_validation-lane.md @@ -0,0 +1,11 @@ +# Validation Lane + +## PASS + +- `node --check toolbox/idea-board/index.js` +- `git diff --check` +- `npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs --workers=1` + +## WARN + +- `npx playwright test tests/playwright/tools/ToolboxRoutePages.spec.mjs --workers=1 --grep "Idea Board launches"` reached the updated Idea Board status option assertions, then failed because the page recorded `500 http://127.0.0.1:51166/api/game-journey/completion-metrics`. diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index 3c708756f..10031f5d7 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,5 +1,3 @@ -assets/theme-v2/css/tables.css -tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +tests/playwright/tools/IdeaBoardTableNotes.spec.mjs tests/playwright/tools/ToolboxRoutePages.spec.mjs -toolbox/idea-board/index.html toolbox/idea-board/index.js diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index 450a2ca28..f5b1867bc 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -1,269 +1,149 @@ -diff --git a/assets/theme-v2/css/tables.css b/assets/theme-v2/css/tables.css -index c39ef19f0..5575ff684 100644 ---- a/assets/theme-v2/css/tables.css -+++ b/assets/theme-v2/css/tables.css -@@ -116,26 +116,26 @@ td { - } - - .idea-board-idea-label { -- display: inline-flex; -- align-items: center; -- gap: .35em; -+ display: inline; - color: inherit; - font: inherit; - line-height: var(--line-height-single); - vertical-align: baseline; -- white-space: nowrap -+ white-space: normal - } - - .idea-board-idea-label__text { -- line-height: var(--line-height-single) -+ overflow-wrap: anywhere; -+ line-height: inherit - } - - .idea-board-idea-chevron { - display: inline-block; - width: 1em; - height: 1em; -- flex: 0 0 1em; -+ margin-right: .35em; - background: currentColor; -+ vertical-align: -0.125em; - -webkit-mask-position: center; - mask-position: center; - -webkit-mask-repeat: no-repeat; -diff --git a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -index 214c0081e..da7800ca0 100644 +diff --git a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +index da7800ca0..61d8fdc68 100644 --- a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +++ b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -@@ -21,8 +21,6 @@ async function expectIdeaChevron(page, ideaId, iconName) { - const cellStyles = getComputedStyle(cell); - const labelStyles = getComputedStyle(label); - const iconStyles = getComputedStyle(icon); -- const textRect = text.getBoundingClientRect(); -- const iconRect = icon.getBoundingClientRect(); - return { - iconName: icon.dataset.ideaBoardChevronIcon, - labelDisplay: labelStyles.display, -@@ -30,21 +28,19 @@ async function expectIdeaChevron(page, ideaId, iconName) { - iconHeight: Number.parseFloat(iconStyles.height), - fontSize: Number.parseFloat(cellStyles.fontSize), - iconColor: iconStyles.backgroundColor, -- iconBottom: iconRect.bottom, -- iconLeft: iconRect.left, -+ iconBeforeText: Boolean(icon.compareDocumentPosition(text) & Node.DOCUMENT_POSITION_FOLLOWING), -+ iconVerticalAlign: Number.parseFloat(iconStyles.verticalAlign), - textColor: cellStyles.color, -- textBottom: textRect.bottom, -- textLeft: textRect.left, - maskImage: iconStyles.getPropertyValue("-webkit-mask-image") || iconStyles.maskImage, - }; - }, ideaId); - expect(metrics.iconName).toBe(iconName); -- expect(metrics.labelDisplay).toBe("inline-flex"); -+ expect(metrics.labelDisplay).toBe("inline"); - expect(Math.abs(metrics.iconWidth - metrics.fontSize)).toBeLessThanOrEqual(1); - expect(Math.abs(metrics.iconHeight - metrics.fontSize)).toBeLessThanOrEqual(1); - expect(metrics.iconColor).toBe(metrics.textColor); -- expect(metrics.iconLeft).toBeLessThan(metrics.textLeft); -- expect(Math.abs(metrics.iconBottom - metrics.textBottom)).toBeLessThanOrEqual(2); -+ expect(metrics.iconBeforeText).toBe(true); -+ expect(metrics.iconVerticalAlign).toBeLessThan(0); - expect(metrics.maskImage).toContain(iconName); - } +@@ -5,6 +5,10 @@ import { isBrowserExtensionNoise } from "../../helpers/browserExtensionNoise.mjs + import { createGameJourneyCompletionMetricsPostgresClientStub } from "../../helpers/gameJourneyCompletionMetricsPostgresClientStub.mjs"; + import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; -@@ -203,7 +199,6 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { - "Idea", - "Pitch", - "Status", -- "Updated", - "Notes", - "Actions", - ]); -@@ -219,6 +214,14 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { ++const EDITABLE_STATUS_OPTIONS = ["New", "Exploring", "Refining", "Ready"]; ++const FILTER_STATUS_OPTIONS = ["New", "Exploring", "Refining", "Ready", "Project", "Archived"]; ++const DEFAULT_VISIBLE_STATUS_OPTIONS = ["New", "Exploring", "Refining", "Ready", "Project"]; ++ + function restoreEnvValue(key, value) { + if (value === undefined) { + delete process.env[key]; +@@ -213,7 +217,7 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { + await expect(statusFilterAccordion.locator("summary")).toHaveText("Status Filter"); await expect(statusFilterAccordion.locator("[data-idea-board-filter-select-all]")).toHaveText("Select All"); await expect(statusFilterAccordion.locator("[data-idea-board-filter-clear-all]")).toHaveText("Clear All"); - await expect(statusFilterAccordion.locator("[data-idea-board-status-filter-option]")).toHaveCount(6); -+ const statusFilterTheme = await statusFilterAccordion.locator("[data-idea-board-status-filter-option][value='New']").evaluate((input) => ({ -+ accentColor: getComputedStyle(input).accentColor, -+ toolGroupColor: getComputedStyle(input.closest(".control-lab")).getPropertyValue("--tool-group-color").trim(), -+ })); -+ expect(statusFilterTheme).toEqual({ -+ accentColor: "rgb(255, 45, 45)", -+ toolGroupColor: "#ff2d2d", -+ }); - await expect(statusFilterAccordion.locator(".idea-board-show-filter__option")).toHaveText([ - "New", - "Exploring", -@@ -242,10 +245,21 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { - await expectIdeaChevron(page, "top-thoughts", "gfs-chevron-down.svg"); - await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] td").nth(0)).toHaveText("Smartest person wins..."); - await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] td").nth(1)).toHaveText("Exploring"); -- await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] td").nth(2)).toHaveText("2026-06-20"); - await expect(page.locator("[data-idea-board-notes-count='top-thoughts']")).toHaveText("3 Notes"); - await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] [data-idea-board-idea-action]")).toHaveText(["Edit", "Delete"]); - await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] [data-idea-board-idea-action='create-project']")).toHaveCount(0); -+ const ideaLabelWrapping = await page.locator("[data-idea-board-idea-row='top-thoughts'] .idea-board-idea-label").evaluate((label) => { -+ const labelStyles = getComputedStyle(label); -+ const textStyles = getComputedStyle(label.querySelector(".idea-board-idea-label__text")); -+ return { -+ overflowWrap: textStyles.overflowWrap, -+ whiteSpace: labelStyles.whiteSpace, -+ }; -+ }); -+ expect(ideaLabelWrapping).toEqual({ -+ overflowWrap: "anywhere", -+ whiteSpace: "normal", -+ }); - - await expect(page.locator("[data-idea-board-idea-row='sky-orchard'] th")).toHaveText("Sky Orchard"); - await expectIdeaChevron(page, "sky-orchard", "gfs-chevron-down.svg"); -@@ -259,6 +273,7 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { - await expect(page.locator("[data-idea-board-expanded-row]")).toHaveCount(0); - await page.locator("[data-idea-board-idea-cell='top-thoughts']").click(); - await expect(page.locator("[data-idea-board-expanded-row='top-thoughts']")).toBeVisible(); -+ await expect(page.locator("[data-idea-board-expanded-row='top-thoughts'] > td")).toHaveAttribute("colspan", "5"); - await expectProductionCopy(page); - await expectIdeaChevron(page, "top-thoughts", "gfs-chevron-up.svg"); - await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] + [data-idea-board-expanded-row='top-thoughts']")).toHaveCount(1); -@@ -320,11 +335,8 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { - "Exploring", - "Refining", - "Ready", +- await expect(statusFilterAccordion.locator("[data-idea-board-status-filter-option]")).toHaveCount(6); ++ await expect(statusFilterAccordion.locator("[data-idea-board-status-filter-option]")).toHaveCount(FILTER_STATUS_OPTIONS.length); + const statusFilterTheme = await statusFilterAccordion.locator("[data-idea-board-status-filter-option][value='New']").evaluate((input) => ({ + accentColor: getComputedStyle(input).accentColor, + toolGroupColor: getComputedStyle(input.closest(".control-lab")).getPropertyValue("--tool-group-color").trim(), +@@ -222,18 +226,11 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { + accentColor: "rgb(255, 45, 45)", + toolGroupColor: "#ff2d2d", + }); +- await expect(statusFilterAccordion.locator(".idea-board-show-filter__option")).toHaveText([ +- "New", +- "Exploring", +- "Refining", +- "Ready", - "Project", - "Archived", - ]); -- await expect(ideaInputRow.locator("td").nth(2)).toHaveText(/\d{4}-\d{2}-\d{2}/); -- await expect(ideaInputRow.locator("td").nth(3)).toHaveText("0 Notes"); -+ await expect(ideaInputRow.locator("td").nth(2)).toHaveText("0 Notes"); +- ]); ++ await expect(statusFilterAccordion.locator(".idea-board-show-filter__option")).toHaveText(FILTER_STATUS_OPTIONS); + const checkedStatuses = await page.locator("[data-idea-board-status-filter-option]:checked").evaluateAll((inputs) => ( + inputs.map((input) => input.value) + )); +- expect(checkedStatuses).toEqual(["New", "Exploring", "Refining", "Ready", "Project"]); ++ expect(checkedStatuses).toEqual(DEFAULT_VISIBLE_STATUS_OPTIONS); + await expect(page.locator("[data-idea-board-status-filter-option][value='Archived']")).not.toBeChecked(); + await expect(page.getByText(/another/i)).toHaveCount(0); + await expect(page.locator("[data-idea-board-notes-chevron]")).toHaveCount(0); +@@ -330,12 +327,7 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { + const ideaInputRow = page.locator("[data-idea-board-idea-input-row]").last(); + await expect(ideaInputRow.locator("[data-idea-board-idea-action]")).toHaveText(["Save", "Cancel"]); + await expect(ideaInputRow.locator("[data-idea-board-idea-status-input]")).toHaveCount(1); +- await expect(ideaInputRow.locator("[data-idea-board-idea-status-input] option")).toHaveText([ +- "New", +- "Exploring", +- "Refining", +- "Ready", +- ]); ++ await expect(ideaInputRow.locator("[data-idea-board-idea-status-input] option")).toHaveText(EDITABLE_STATUS_OPTIONS); + await expect(ideaInputRow.locator("td").nth(2)).toHaveText("0 Notes"); await page.locator("[data-idea-board-idea-input]").fill("Lantern Reef"); await page.locator("[data-idea-board-pitch-input]").fill("Guide light through a reef that rearranges at dusk."); - await page.locator("[data-idea-board-idea-status-input]").selectOption("Refining"); +@@ -355,6 +347,7 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { + await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='edit']").click(); + await expect(page.locator("[data-idea-board-idea-input-row] [data-idea-board-idea-action]")).toHaveText(["Save", "Cancel"]); + await expect(page.locator("[data-idea-board-idea-status-input]")).toHaveCount(1); ++ await expect(page.locator("[data-idea-board-idea-status-input] option")).toHaveText(EDITABLE_STATUS_OPTIONS); + await page.locator("[data-idea-board-idea-status-input]").selectOption("Ready"); + await page.locator("[data-idea-board-idea-action='save']").click(); + await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] td").nth(1)).toHaveText("Ready"); diff --git a/tests/playwright/tools/ToolboxRoutePages.spec.mjs b/tests/playwright/tools/ToolboxRoutePages.spec.mjs -index de9995354..a2622e969 100644 +index a2622e969..1f2bf6e89 100644 --- a/tests/playwright/tools/ToolboxRoutePages.spec.mjs +++ b/tests/playwright/tools/ToolboxRoutePages.spec.mjs -@@ -120,8 +120,6 @@ async function expectIdeaChevron(page, ideaId, iconName) { - const cellStyles = getComputedStyle(cell); - const labelStyles = getComputedStyle(label); - const iconStyles = getComputedStyle(icon); -- const textRect = text.getBoundingClientRect(); -- const iconRect = icon.getBoundingClientRect(); - return { - iconName: icon.dataset.ideaBoardChevronIcon, - labelDisplay: labelStyles.display, -@@ -129,21 +127,19 @@ async function expectIdeaChevron(page, ideaId, iconName) { - iconHeight: Number.parseFloat(iconStyles.height), - fontSize: Number.parseFloat(cellStyles.fontSize), - iconColor: iconStyles.backgroundColor, -- iconBottom: iconRect.bottom, -- iconLeft: iconRect.left, -+ iconBeforeText: Boolean(icon.compareDocumentPosition(text) & Node.DOCUMENT_POSITION_FOLLOWING), -+ iconVerticalAlign: Number.parseFloat(iconStyles.verticalAlign), - textColor: cellStyles.color, -- textBottom: textRect.bottom, -- textLeft: textRect.left, - maskImage: iconStyles.getPropertyValue("-webkit-mask-image") || iconStyles.maskImage, - }; - }, ideaId); - expect(metrics.iconName).toBe(iconName); -- expect(metrics.labelDisplay).toBe("inline-flex"); -+ expect(metrics.labelDisplay).toBe("inline"); - expect(Math.abs(metrics.iconWidth - metrics.fontSize)).toBeLessThanOrEqual(1); - expect(Math.abs(metrics.iconHeight - metrics.fontSize)).toBeLessThanOrEqual(1); - expect(metrics.iconColor).toBe(metrics.textColor); -- expect(metrics.iconLeft).toBeLessThan(metrics.textLeft); -- expect(Math.abs(metrics.iconBottom - metrics.textBottom)).toBeLessThanOrEqual(2); -+ expect(metrics.iconBeforeText).toBe(true); -+ expect(metrics.iconVerticalAlign).toBeLessThan(0); - expect(metrics.maskImage).toContain(iconName); - } +@@ -4,6 +4,9 @@ import { isBrowserExtensionNoise } from "../../helpers/browserExtensionNoise.mjs + import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; + import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; -@@ -314,14 +310,15 @@ test("Idea Board launches from Toolbox with accordion table notes model", async - sections.map((section) => section.getAttribute("data-idea-board-section")) - )); - expect(ideaBoardSections).toEqual([ -- "Workflow", -+ "Status Filter", - "Status", -+ "Workflow", - "Idea Table", - "Notes Governance", - "Diagnostics", ++const IDEA_BOARD_EDITABLE_STATUS_OPTIONS = ["New", "Exploring", "Refining", "Ready"]; ++const IDEA_BOARD_FILTER_STATUS_OPTIONS = ["New", "Exploring", "Refining", "Ready", "Project", "Archived"]; ++ + const TOOL_ROUTE_SMOKE_CASES = [ + { heading: "Game Journey", route: "/tools/game-journey/index.html" }, + { heading: "Idea Board", route: "/tools/idea-board/index.html" }, +@@ -319,6 +322,8 @@ test("Idea Board launches from Toolbox with accordion table notes model", async ]); await expect(page.locator("[data-idea-board-table]")).toBeVisible(); -- await expect(page.locator("[data-idea-board-table] > thead th[scope='col']")).toHaveText(["Idea", "Pitch", "Status", "Updated", "Notes", "Actions"]); -+ await expect(page.locator("[data-idea-board-table] > thead th[scope='col']")).toHaveText(["Idea", "Pitch", "Status", "Notes", "Actions"]); + await expect(page.locator("[data-idea-board-table] > thead th[scope='col']")).toHaveText(["Idea", "Pitch", "Status", "Notes", "Actions"]); ++ await expect(page.locator("[data-idea-board-status-filter-option]")).toHaveCount(IDEA_BOARD_FILTER_STATUS_OPTIONS.length); ++ await expect(page.locator(".idea-board-show-filter__option")).toHaveText(IDEA_BOARD_FILTER_STATUS_OPTIONS); await expect(page.locator("[data-idea-board-idea-row]")).toHaveCount(3); await expect(page.locator("[data-idea-board-expanded-row]")).toHaveCount(0); await expect(page.locator("[data-idea-board-add-idea]")).toHaveText("Add Idea"); -diff --git a/toolbox/idea-board/index.html b/toolbox/idea-board/index.html -index bf4d9a010..db8b2f2ae 100644 ---- a/toolbox/idea-board/index.html -+++ b/toolbox/idea-board/index.html -@@ -36,7 +36,7 @@ - - - --
-+
- - -
-@@ -67,7 +67,6 @@ - Idea - Pitch - Status -- Updated - Notes - Actions - +@@ -360,6 +365,7 @@ test("Idea Board launches from Toolbox with accordion table notes model", async + await page.locator("[data-idea-board-add-idea]").click(); + await page.locator("[data-idea-board-idea-input]").fill("Launch Tile"); + await page.locator("[data-idea-board-pitch-input]").fill("Turn a polished board idea into a project."); ++ await expect(page.locator("[data-idea-board-idea-status-input] option")).toHaveText(IDEA_BOARD_EDITABLE_STATUS_OPTIONS); + await page.locator("[data-idea-board-idea-status-input]").selectOption("Ready"); + await page.locator("[data-idea-board-idea-action='save']").click(); + await expect(page.locator("[data-idea-board-idea-row='launch-tile'] [data-idea-board-idea-action]")).toHaveText(["Edit", "Create Project", "Delete"]); diff --git a/toolbox/idea-board/index.js b/toolbox/idea-board/index.js -index 0ae02bd7a..8fcb7ad53 100644 +index 8fcb7ad53..86d4dae4b 100644 --- a/toolbox/idea-board/index.js +++ b/toolbox/idea-board/index.js -@@ -196,6 +196,7 @@ function statusSelect(value) { +@@ -1,7 +1,8 @@ + import { createServerRepositoryClient } from "../../src/api/server-api-client.js"; + import { getSessionCurrent } from "../../src/api/session-api-client.js"; + +-const statusOptions = Object.freeze(["New", "Exploring", "Refining", "Ready", "Project", "Archived"]); ++const editableStatusOptions = Object.freeze(["New", "Exploring", "Refining", "Ready"]); ++const filterStatusOptions = Object.freeze(["New", "Exploring", "Refining", "Ready", "Project", "Archived"]); + const defaultVisibleStatuses = Object.freeze(["New", "Exploring", "Refining", "Ready", "Project"]); + const userId = "user-1"; + const gameHubRoute = "toolbox/game-hub/index.html"; +@@ -123,7 +124,7 @@ function visibleIdeas() { + } + + function previousStatusForRestore(record) { +- return statusOptions.includes(record.previousStatus) && record.previousStatus !== "Archived" ++ return filterStatusOptions.includes(record.previousStatus) && record.previousStatus !== "Archived" + ? record.previousStatus + : "Refining"; + } +@@ -168,7 +169,7 @@ function renderStatusFilter(root) { + const options = root.querySelector("[data-idea-board-status-options]"); + if (!options) return; + options.replaceChildren(); +- for (const status of statusOptions) { ++ for (const status of filterStatusOptions) { + const label = document.createElement("label"); + label.className = "idea-board-show-filter__option"; + const input = document.createElement("input"); +@@ -195,8 +196,7 @@ function statusSelect(value) { + const select = document.createElement("select"); select.setAttribute("aria-label", "Idea status"); select.dataset.ideaBoardIdeaStatusInput = "true"; - for (const optionValue of statusOptions) { -+ if (optionValue === "Project" || optionValue === "Archived") continue; +- for (const optionValue of statusOptions) { +- if (optionValue === "Project" || optionValue === "Archived") continue; ++ for (const optionValue of editableStatusOptions) { const option = document.createElement("option"); option.value = optionValue; option.textContent = optionValue; -@@ -231,7 +232,6 @@ function renderIdeaInputRow(tbody, record = null) { - const statusCell = document.createElement("td"); - statusCell.append(statusSelect(record?.status || "New")); - row.append(statusCell); -- row.append(cell(record?.updated || today())); - row.append(cell(record ? noteCountLabel(record.ideaId) : "0 Notes")); - - const actions = document.createElement("td"); -@@ -274,7 +274,6 @@ function renderIdeaRow(tbody, record) { - row.append(idea); - row.append(cell(record.pitch)); - row.append(cell(record.status)); -- row.append(cell(record.updated)); - - const notes = document.createElement("td"); - const notesCount = document.createElement("span"); -@@ -353,7 +352,7 @@ function renderExpandedNotesRow(tbody, record) { - row.dataset.ideaBoardExpandedRow = record.ideaId; - - const content = document.createElement("td"); -- content.colSpan = 6; -+ content.colSpan = 5; +@@ -756,7 +756,7 @@ function handleNoteAction(root, actionControl) { - const childSurface = document.createElement("div"); - childSurface.className = "idea-board-notes-child-surface"; -@@ -400,7 +399,7 @@ function renderAddIdeaRow(tbody) { - const row = document.createElement("tr"); - row.dataset.ideaBoardAddIdeaRow = "true"; - const actions = document.createElement("td"); -- actions.colSpan = 6; -+ actions.colSpan = 5; - const addIdea = actionButton("Add Idea", "add", "ideaBoardIdeaAction", "primary"); - addIdea.dataset.ideaBoardAddIdea = "true"; - actions.append(addIdea); + function handleFilterAction(root, actionControl) { + if (actionControl.matches("[data-idea-board-filter-select-all]")) { +- state.visibleStatuses = new Set(statusOptions); ++ state.visibleStatuses = new Set(filterStatusOptions); + updateStatus(root, "Showing all statuses."); + } else if (actionControl.matches("[data-idea-board-filter-clear-all]")) { + state.visibleStatuses = new Set(); diff --git a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs index da7800ca0..61d8fdc68 100644 --- a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +++ b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs @@ -5,6 +5,10 @@ import { isBrowserExtensionNoise } from "../../helpers/browserExtensionNoise.mjs import { createGameJourneyCompletionMetricsPostgresClientStub } from "../../helpers/gameJourneyCompletionMetricsPostgresClientStub.mjs"; import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; +const EDITABLE_STATUS_OPTIONS = ["New", "Exploring", "Refining", "Ready"]; +const FILTER_STATUS_OPTIONS = ["New", "Exploring", "Refining", "Ready", "Project", "Archived"]; +const DEFAULT_VISIBLE_STATUS_OPTIONS = ["New", "Exploring", "Refining", "Ready", "Project"]; + function restoreEnvValue(key, value) { if (value === undefined) { delete process.env[key]; @@ -213,7 +217,7 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { await expect(statusFilterAccordion.locator("summary")).toHaveText("Status Filter"); await expect(statusFilterAccordion.locator("[data-idea-board-filter-select-all]")).toHaveText("Select All"); await expect(statusFilterAccordion.locator("[data-idea-board-filter-clear-all]")).toHaveText("Clear All"); - await expect(statusFilterAccordion.locator("[data-idea-board-status-filter-option]")).toHaveCount(6); + await expect(statusFilterAccordion.locator("[data-idea-board-status-filter-option]")).toHaveCount(FILTER_STATUS_OPTIONS.length); const statusFilterTheme = await statusFilterAccordion.locator("[data-idea-board-status-filter-option][value='New']").evaluate((input) => ({ accentColor: getComputedStyle(input).accentColor, toolGroupColor: getComputedStyle(input.closest(".control-lab")).getPropertyValue("--tool-group-color").trim(), @@ -222,18 +226,11 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { accentColor: "rgb(255, 45, 45)", toolGroupColor: "#ff2d2d", }); - await expect(statusFilterAccordion.locator(".idea-board-show-filter__option")).toHaveText([ - "New", - "Exploring", - "Refining", - "Ready", - "Project", - "Archived", - ]); + await expect(statusFilterAccordion.locator(".idea-board-show-filter__option")).toHaveText(FILTER_STATUS_OPTIONS); const checkedStatuses = await page.locator("[data-idea-board-status-filter-option]:checked").evaluateAll((inputs) => ( inputs.map((input) => input.value) )); - expect(checkedStatuses).toEqual(["New", "Exploring", "Refining", "Ready", "Project"]); + expect(checkedStatuses).toEqual(DEFAULT_VISIBLE_STATUS_OPTIONS); await expect(page.locator("[data-idea-board-status-filter-option][value='Archived']")).not.toBeChecked(); await expect(page.getByText(/another/i)).toHaveCount(0); await expect(page.locator("[data-idea-board-notes-chevron]")).toHaveCount(0); @@ -330,12 +327,7 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { const ideaInputRow = page.locator("[data-idea-board-idea-input-row]").last(); await expect(ideaInputRow.locator("[data-idea-board-idea-action]")).toHaveText(["Save", "Cancel"]); await expect(ideaInputRow.locator("[data-idea-board-idea-status-input]")).toHaveCount(1); - await expect(ideaInputRow.locator("[data-idea-board-idea-status-input] option")).toHaveText([ - "New", - "Exploring", - "Refining", - "Ready", - ]); + await expect(ideaInputRow.locator("[data-idea-board-idea-status-input] option")).toHaveText(EDITABLE_STATUS_OPTIONS); await expect(ideaInputRow.locator("td").nth(2)).toHaveText("0 Notes"); await page.locator("[data-idea-board-idea-input]").fill("Lantern Reef"); await page.locator("[data-idea-board-pitch-input]").fill("Guide light through a reef that rearranges at dusk."); @@ -355,6 +347,7 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='edit']").click(); await expect(page.locator("[data-idea-board-idea-input-row] [data-idea-board-idea-action]")).toHaveText(["Save", "Cancel"]); await expect(page.locator("[data-idea-board-idea-status-input]")).toHaveCount(1); + await expect(page.locator("[data-idea-board-idea-status-input] option")).toHaveText(EDITABLE_STATUS_OPTIONS); await page.locator("[data-idea-board-idea-status-input]").selectOption("Ready"); await page.locator("[data-idea-board-idea-action='save']").click(); await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] td").nth(1)).toHaveText("Ready"); diff --git a/tests/playwright/tools/ToolboxRoutePages.spec.mjs b/tests/playwright/tools/ToolboxRoutePages.spec.mjs index a2622e969..1f2bf6e89 100644 --- a/tests/playwright/tools/ToolboxRoutePages.spec.mjs +++ b/tests/playwright/tools/ToolboxRoutePages.spec.mjs @@ -4,6 +4,9 @@ import { isBrowserExtensionNoise } from "../../helpers/browserExtensionNoise.mjs import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs"; +const IDEA_BOARD_EDITABLE_STATUS_OPTIONS = ["New", "Exploring", "Refining", "Ready"]; +const IDEA_BOARD_FILTER_STATUS_OPTIONS = ["New", "Exploring", "Refining", "Ready", "Project", "Archived"]; + const TOOL_ROUTE_SMOKE_CASES = [ { heading: "Game Journey", route: "/tools/game-journey/index.html" }, { heading: "Idea Board", route: "/tools/idea-board/index.html" }, @@ -319,6 +322,8 @@ test("Idea Board launches from Toolbox with accordion table notes model", async ]); await expect(page.locator("[data-idea-board-table]")).toBeVisible(); await expect(page.locator("[data-idea-board-table] > thead th[scope='col']")).toHaveText(["Idea", "Pitch", "Status", "Notes", "Actions"]); + await expect(page.locator("[data-idea-board-status-filter-option]")).toHaveCount(IDEA_BOARD_FILTER_STATUS_OPTIONS.length); + await expect(page.locator(".idea-board-show-filter__option")).toHaveText(IDEA_BOARD_FILTER_STATUS_OPTIONS); await expect(page.locator("[data-idea-board-idea-row]")).toHaveCount(3); await expect(page.locator("[data-idea-board-expanded-row]")).toHaveCount(0); await expect(page.locator("[data-idea-board-add-idea]")).toHaveText("Add Idea"); @@ -360,6 +365,7 @@ test("Idea Board launches from Toolbox with accordion table notes model", async await page.locator("[data-idea-board-add-idea]").click(); await page.locator("[data-idea-board-idea-input]").fill("Launch Tile"); await page.locator("[data-idea-board-pitch-input]").fill("Turn a polished board idea into a project."); + await expect(page.locator("[data-idea-board-idea-status-input] option")).toHaveText(IDEA_BOARD_EDITABLE_STATUS_OPTIONS); await page.locator("[data-idea-board-idea-status-input]").selectOption("Ready"); await page.locator("[data-idea-board-idea-action='save']").click(); await expect(page.locator("[data-idea-board-idea-row='launch-tile'] [data-idea-board-idea-action]")).toHaveText(["Edit", "Create Project", "Delete"]); diff --git a/toolbox/idea-board/index.js b/toolbox/idea-board/index.js index 8fcb7ad53..86d4dae4b 100644 --- a/toolbox/idea-board/index.js +++ b/toolbox/idea-board/index.js @@ -1,7 +1,8 @@ import { createServerRepositoryClient } from "../../src/api/server-api-client.js"; import { getSessionCurrent } from "../../src/api/session-api-client.js"; -const statusOptions = Object.freeze(["New", "Exploring", "Refining", "Ready", "Project", "Archived"]); +const editableStatusOptions = Object.freeze(["New", "Exploring", "Refining", "Ready"]); +const filterStatusOptions = Object.freeze(["New", "Exploring", "Refining", "Ready", "Project", "Archived"]); const defaultVisibleStatuses = Object.freeze(["New", "Exploring", "Refining", "Ready", "Project"]); const userId = "user-1"; const gameHubRoute = "toolbox/game-hub/index.html"; @@ -123,7 +124,7 @@ function visibleIdeas() { } function previousStatusForRestore(record) { - return statusOptions.includes(record.previousStatus) && record.previousStatus !== "Archived" + return filterStatusOptions.includes(record.previousStatus) && record.previousStatus !== "Archived" ? record.previousStatus : "Refining"; } @@ -168,7 +169,7 @@ function renderStatusFilter(root) { const options = root.querySelector("[data-idea-board-status-options]"); if (!options) return; options.replaceChildren(); - for (const status of statusOptions) { + for (const status of filterStatusOptions) { const label = document.createElement("label"); label.className = "idea-board-show-filter__option"; const input = document.createElement("input"); @@ -195,8 +196,7 @@ function statusSelect(value) { const select = document.createElement("select"); select.setAttribute("aria-label", "Idea status"); select.dataset.ideaBoardIdeaStatusInput = "true"; - for (const optionValue of statusOptions) { - if (optionValue === "Project" || optionValue === "Archived") continue; + for (const optionValue of editableStatusOptions) { const option = document.createElement("option"); option.value = optionValue; option.textContent = optionValue; @@ -756,7 +756,7 @@ function handleNoteAction(root, actionControl) { function handleFilterAction(root, actionControl) { if (actionControl.matches("[data-idea-board-filter-select-all]")) { - state.visibleStatuses = new Set(statusOptions); + state.visibleStatuses = new Set(filterStatusOptions); updateStatus(root, "Showing all statuses."); } else if (actionControl.matches("[data-idea-board-filter-clear-all]")) { state.visibleStatuses = new Set();