diff --git a/assets/theme-v2/css/tables.css b/assets/theme-v2/css/tables.css index 8ab6de76b..a8c75090a 100644 --- a/assets/theme-v2/css/tables.css +++ b/assets/theme-v2/css/tables.css @@ -88,6 +88,16 @@ td { mask-image: url("../images/gfs-chevron-up.svg") } +.idea-board-notes-child-surface { + margin-left: calc(var(--space-14) * 2); + max-width: calc(100% - (var(--space-14) * 2)) +} + +.idea-board-notes-child-actions { + padding-left: var(--space-14); + padding-top: var(--space-10) +} + .tool-form-table { table-layout: fixed; width: 100% diff --git a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs index b2858fe89..b115c4191 100644 --- a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +++ b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs @@ -53,6 +53,43 @@ async function expectButtonLeftAligned(page, buttonSelector, containerSelector) expect(metrics.buttonLeft).toBeLessThan(metrics.containerLeft + metrics.containerWidth / 2); } +async function expectExpandedNotesChildIndentation(page, ideaId, expectedInputRows = 0) { + const metrics = await page.locator(`[data-idea-board-expanded-row='${ideaId}']`).evaluate((row, targetIdeaId) => { + const expandedCell = row.querySelector(":scope > td"); + const childSurface = row.querySelector(".idea-board-notes-child-surface"); + const noteCell = row.querySelector(`[data-idea-board-notes-table='${targetIdeaId}'] tbody tr td:first-child`); + const noteInput = row.querySelector("[data-idea-board-note-input]"); + const addNote = row.querySelector(`[data-idea-board-add-note='${targetIdeaId}']`); + const addNoteActions = row.querySelector(".idea-board-notes-child-actions"); + const expandedStyles = getComputedStyle(expandedCell); + const noteCellStyles = getComputedStyle(noteCell); + const addNoteActionStyles = getComputedStyle(addNoteActions); + const expandedRect = expandedCell.getBoundingClientRect(); + const childRect = childSurface.getBoundingClientRect(); + const noteCellRect = noteCell.getBoundingClientRect(); + const expandedPadding = Number.parseFloat(expandedStyles.paddingLeft || "0"); + const noteCellPadding = Number.parseFloat(noteCellStyles.paddingLeft || "0"); + return { + addNoteLeft: addNote.getBoundingClientRect().left, + childSurfaceLeft: childRect.left, + expandedContentLeft: expandedRect.left + expandedPadding, + expectedContentLeft: expandedRect.left + 2 * (expandedPadding + noteCellPadding), + inputLeft: noteInput ? noteInput.getBoundingClientRect().left : null, + inputRows: row.querySelectorAll("[data-idea-board-note-input-row]").length, + noteContentLeft: noteCellRect.left + noteCellPadding, + actionContentLeft: addNoteActions.getBoundingClientRect().left + Number.parseFloat(addNoteActionStyles.paddingLeft || "0"), + }; + }, ideaId); + expect(metrics.childSurfaceLeft).toBeGreaterThan(metrics.expandedContentLeft); + expect(Math.abs(metrics.noteContentLeft - metrics.expectedContentLeft)).toBeLessThanOrEqual(2); + expect(Math.abs(metrics.addNoteLeft - metrics.expectedContentLeft)).toBeLessThanOrEqual(2); + expect(Math.abs(metrics.actionContentLeft - metrics.expectedContentLeft)).toBeLessThanOrEqual(2); + expect(metrics.inputRows).toBe(expectedInputRows); + if (expectedInputRows > 0) { + expect(Math.abs(metrics.inputLeft - metrics.expectedContentLeft)).toBeLessThanOrEqual(2); + } +} + test("Idea Board uses DB-shaped accordion table ideas and notes", async ({ page }) => { const server = await startRepoServer(); const previousApiUrl = process.env.GAMEFOUNDRY_API_URL; @@ -130,7 +167,7 @@ test("Idea Board uses DB-shaped accordion table ideas and notes", async ({ page await expect(page.locator("[data-idea-board-expanded-row='top-thoughts'] > td > .content-stack")).toHaveCount(0); await expect(page.locator("[data-idea-board-notes-table='top-thoughts'] th[scope='col']")).toHaveText(["Note", "Actions"]); await expect(page.locator("[data-idea-board-add-note='top-thoughts']")).toHaveText("Add Note"); - await expectButtonLeftAligned(page, "[data-idea-board-add-note='top-thoughts']", "[data-idea-board-expanded-row='top-thoughts'] > td"); + await expectExpandedNotesChildIndentation(page, "top-thoughts"); await expect(page.locator("[data-idea-board-notes-table] th[scope='col']", { hasText: "Type" })).toHaveCount(0); await expect(page.locator("[data-idea-board-notes-table] th[scope='col']", { hasText: "Created By" })).toHaveCount(0); await expect(page.locator("[data-idea-board-notes-table] th[scope='col']", { hasText: "Created" })).toHaveCount(0); @@ -141,6 +178,7 @@ test("Idea Board uses DB-shaped accordion table ideas and notes", async ({ page await expect(systemNote.locator("[data-idea-board-note-action='delete']")).toHaveCount(0); await systemNote.locator("[data-idea-board-note-action='edit']").click(); await expect(page.locator("[data-idea-board-note-input-row] [data-idea-board-note-action]")).toHaveText(["Save", "Cancel"]); + await expectExpandedNotesChildIndentation(page, "top-thoughts", 1); await page.locator("[data-idea-board-note-input]").fill("System note can be edited in-place."); await page.locator("[data-idea-board-note-action='save']").click(); await expect(page.locator("[data-idea-board-notes-table='top-thoughts']")).toContainText("System note can be edited in-place."); @@ -148,6 +186,7 @@ test("Idea Board uses DB-shaped accordion table ideas and notes", async ({ page await page.locator("[data-idea-board-add-note='top-thoughts']").click(); await expect(page.locator("[data-idea-board-note-input-row] [data-idea-board-note-action]")).toHaveText(["Save", "Cancel"]); + await expectExpandedNotesChildIndentation(page, "top-thoughts", 1); await page.locator("[data-idea-board-note-input]").fill("Add a fourth table-shaped note."); await page.locator("[data-idea-board-note-action='save']").click(); await expect(page.locator("[data-idea-board-notes-table='top-thoughts']")).toContainText("Add a fourth table-shaped note."); diff --git a/tests/playwright/tools/ToolboxRoutePages.spec.mjs b/tests/playwright/tools/ToolboxRoutePages.spec.mjs index c0c323185..6a1f1ebe3 100644 --- a/tests/playwright/tools/ToolboxRoutePages.spec.mjs +++ b/tests/playwright/tools/ToolboxRoutePages.spec.mjs @@ -155,6 +155,43 @@ async function expectButtonLeftAligned(page, buttonSelector, containerSelector) expect(metrics.buttonLeft).toBeLessThan(metrics.containerLeft + metrics.containerWidth / 2); } +async function expectExpandedNotesChildIndentation(page, ideaId, expectedInputRows = 0) { + const metrics = await page.locator(`[data-idea-board-expanded-row='${ideaId}']`).evaluate((row, targetIdeaId) => { + const expandedCell = row.querySelector(":scope > td"); + const childSurface = row.querySelector(".idea-board-notes-child-surface"); + const noteCell = row.querySelector(`[data-idea-board-notes-table='${targetIdeaId}'] tbody tr td:first-child`); + const noteInput = row.querySelector("[data-idea-board-note-input]"); + const addNote = row.querySelector(`[data-idea-board-add-note='${targetIdeaId}']`); + const addNoteActions = row.querySelector(".idea-board-notes-child-actions"); + const expandedStyles = getComputedStyle(expandedCell); + const noteCellStyles = getComputedStyle(noteCell); + const addNoteActionStyles = getComputedStyle(addNoteActions); + const expandedRect = expandedCell.getBoundingClientRect(); + const childRect = childSurface.getBoundingClientRect(); + const noteCellRect = noteCell.getBoundingClientRect(); + const expandedPadding = Number.parseFloat(expandedStyles.paddingLeft || "0"); + const noteCellPadding = Number.parseFloat(noteCellStyles.paddingLeft || "0"); + return { + addNoteLeft: addNote.getBoundingClientRect().left, + childSurfaceLeft: childRect.left, + expandedContentLeft: expandedRect.left + expandedPadding, + expectedContentLeft: expandedRect.left + 2 * (expandedPadding + noteCellPadding), + inputLeft: noteInput ? noteInput.getBoundingClientRect().left : null, + inputRows: row.querySelectorAll("[data-idea-board-note-input-row]").length, + noteContentLeft: noteCellRect.left + noteCellPadding, + actionContentLeft: addNoteActions.getBoundingClientRect().left + Number.parseFloat(addNoteActionStyles.paddingLeft || "0"), + }; + }, ideaId); + expect(metrics.childSurfaceLeft).toBeGreaterThan(metrics.expandedContentLeft); + expect(Math.abs(metrics.noteContentLeft - metrics.expectedContentLeft)).toBeLessThanOrEqual(2); + expect(Math.abs(metrics.addNoteLeft - metrics.expectedContentLeft)).toBeLessThanOrEqual(2); + expect(Math.abs(metrics.actionContentLeft - metrics.expectedContentLeft)).toBeLessThanOrEqual(2); + expect(metrics.inputRows).toBe(expectedInputRows); + if (expectedInputRows > 0) { + expect(Math.abs(metrics.inputLeft - metrics.expectedContentLeft)).toBeLessThanOrEqual(2); + } +} + test("tools route aliases render toolbox tool pages", async ({ page }) => { const server = await startRepoServer(); const failedRequests = []; @@ -289,7 +326,7 @@ test("Idea Board launches from Toolbox with accordion table notes model", async await expect(page.getByText("Selected")).toHaveCount(0); await expect(page.locator("[data-idea-board-add-note='top-thoughts']")).toBeVisible(); await expect(page.locator("[data-idea-board-add-note='top-thoughts']")).toHaveText("Add Note"); - await expectButtonLeftAligned(page, "[data-idea-board-add-note='top-thoughts']", "[data-idea-board-expanded-row='top-thoughts'] > td"); + await expectExpandedNotesChildIndentation(page, "top-thoughts"); await expect(page.locator("[data-idea-board-create-project]")).toBeVisible(); await expect(page.locator("[data-idea-board-create-project]")).toBeDisabled(); await expect(page.locator("style, [style], script:not([src])")).toHaveCount(0); diff --git a/toolbox/idea-board/index.js b/toolbox/idea-board/index.js index d95fcbaf4..5e0037654 100644 --- a/toolbox/idea-board/index.js +++ b/toolbox/idea-board/index.js @@ -269,6 +269,9 @@ function renderExpandedNotesRow(tbody, record) { const content = document.createElement("td"); content.colSpan = 6; + const childSurface = document.createElement("div"); + childSurface.className = "idea-board-notes-child-surface"; + const tableWrapper = document.createElement("div"); tableWrapper.className = "table-wrapper"; const notesTable = document.createElement("table"); @@ -281,7 +284,7 @@ function renderExpandedNotesRow(tbody, record) { notesBody.dataset.ideaBoardNotesBody = record.ideaId; notesTable.append(notesBody); tableWrapper.append(notesTable); - content.append(tableWrapper); + childSurface.append(tableWrapper); for (const note of notesForIdea(record.ideaId)) { if (state.editingNoteId === note.noteId) { @@ -293,11 +296,12 @@ function renderExpandedNotesRow(tbody, record) { if (state.addingNoteIdeaId === record.ideaId) renderNoteInputRow(notesBody, record.ideaId); const controls = document.createElement("div"); - controls.className = "action-group"; + controls.className = "action-group idea-board-notes-child-actions"; const addNote = actionButton("Add Note", "add", "ideaBoardNoteAction", "primary"); addNote.dataset.ideaBoardAddNote = record.ideaId; controls.append(addNote); - content.append(controls); + childSurface.append(controls); + content.append(childSurface); row.append(content); tbody.append(row);