From b6f318677c975220e47e00257a305bb6cf7335e9 Mon Sep 17 00:00:00 2001 From: TianKai Ma Date: Thu, 27 Aug 2026 18:26:52 +0800 Subject: [PATCH 1/5] fix(ui): harden mobile layout and accessibility targets --- .../AdminModerationCommentDialog.svelte | 2 +- .../components/CommentThreadHeader.svelte | 6 +- .../components/CommentThreadItem.svelte | 4 +- .../comments/components/CommentsPanel.svelte | 2 +- .../components/CommentsThreadList.svelte | 2 +- .../SectionDetailMainContent.svelte | 7 +- .../components/SectionExamSection.svelte | 5 +- src/lib/components/shell/AppShell.svelte | 4 +- src/lib/components/ui/button/button.svelte | 2 +- .../ui/input-group/input-group.svelte | 2 +- src/lib/components/ui/table/table.svelte | 14 ++- src/lib/components/ui/toggle/toggle.svelte | 2 +- src/lib/shell/layout-server-data.ts | 2 + tests/e2e/src/app/admin/moderation/test.ts | 1 + tests/e2e/src/app/dashboard/todos/test.ts | 101 ++++++++++++++++++ tests/e2e/src/app/sections/[jwId]/test.ts | 35 ++++++ tests/e2e/src/app/settings/passkeys/test.ts | 12 ++- .../e2e/src/app/settings/preferences/test.ts | 6 ++ tests/e2e/src/app/test.ts | 28 +++++ 19 files changed, 213 insertions(+), 24 deletions(-) diff --git a/src/features/admin/components/AdminModerationCommentDialog.svelte b/src/features/admin/components/AdminModerationCommentDialog.svelte index 16372064a..f59251d0a 100644 --- a/src/features/admin/components/AdminModerationCommentDialog.svelte +++ b/src/features/admin/components/AdminModerationCommentDialog.svelte @@ -62,7 +62,7 @@ export let targetLabel: (comment: AdminModerationComment) => string; -
+
{#if dialogMessage}{dialogMessage}{/if} void;
-
+
{#if !comment.authorHidden && comment.author?.image} @@ -35,7 +35,9 @@ export let toggleReply: (comment: CommentNode) => void; {#if comment.visibility === "logged_in_only"}{commentCopy.visibilityLoggedIn}{/if} {#if comment.status !== "active"}{statusLabel(comment.status)}{/if} {#if comment.contextLabel} - {commentCopy.postedIn} {comment.contextLabel} + + {commentCopy.postedIn} {comment.contextLabel} + {/if}
diff --git a/src/features/comments/components/CommentThreadItem.svelte b/src/features/comments/components/CommentThreadItem.svelte index 76cfcbae3..77c7c3884 100644 --- a/src/features/comments/components/CommentThreadItem.svelte +++ b/src/features/comments/components/CommentThreadItem.svelte @@ -76,11 +76,11 @@ export let viewer: ViewerContext;
-
+
{#if _message}{_message}{/if} {#if _viewer.isSuspended} +
{#each comments as comment} {@render commentItem(comment)} {/each} diff --git a/src/features/section-detail/components/SectionDetailMainContent.svelte b/src/features/section-detail/components/SectionDetailMainContent.svelte index b9836c0e4..c944bd252 100644 --- a/src/features/section-detail/components/SectionDetailMainContent.svelte +++ b/src/features/section-detail/components/SectionDetailMainContent.svelte @@ -109,7 +109,7 @@ $: sectionExamEvents = sectionCalendarEvents.filter(
@@ -124,7 +124,7 @@ $: sectionExamEvents = sectionCalendarEvents.filter( {/if}
-
+
{#key `description:section:${data.section.id}`} {#if DescriptionCard} @@ -178,6 +178,7 @@ $: sectionExamEvents = sectionCalendarEvents.filter( this={SectionExamSection} events={sectionExamEvents} {fmtDate} + heading={sectionCopy.tabs.exams} {sectionCopy} /> {/if} @@ -213,7 +214,7 @@ $: sectionExamEvents = sectionCalendarEvents.filter( {/if}
-
+
{#key `comments:section:${data.section.id}`} {#if CommentsPanel} string; +export let heading: string; export let sectionCopy: SectionExamCopy; function detailValue(event: SectionCalendarEvent, label: string) { @@ -28,8 +29,8 @@ function examTime(event: SectionCalendarEvent) { {#if events.length > 0} -
- +
+ {sectionCopy.examBatch} diff --git a/src/lib/components/shell/AppShell.svelte b/src/lib/components/shell/AppShell.svelte index fe27dacab..3d8d01734 100644 --- a/src/lib/components/shell/AppShell.svelte +++ b/src/lib/components/shell/AppShell.svelte @@ -737,7 +737,7 @@ afterNavigate(({ from, to }) => { {
[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto", + "group/input-group border-input dark:bg-input/30 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-disabled:bg-input/50 dark:has-disabled:bg-input/80 h-11 rounded-lg border transition-colors md:h-8 in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-3 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto", className )} {...props} diff --git a/src/lib/components/ui/table/table.svelte b/src/lib/components/ui/table/table.svelte index d95a02eae..13176cc40 100644 --- a/src/lib/components/ui/table/table.svelte +++ b/src/lib/components/ui/table/table.svelte @@ -5,12 +5,22 @@ let { ref = $bindable(null), class: className, + containerLabel, children, ...restProps - }: WithElementRef = $props(); + }: WithElementRef & { + containerLabel?: string; + } = $props(); -
+ +
{@render children?.()}
diff --git a/src/lib/components/ui/toggle/toggle.svelte b/src/lib/components/ui/toggle/toggle.svelte index 5d84d01de..5cd9e7d6c 100644 --- a/src/lib/components/ui/toggle/toggle.svelte +++ b/src/lib/components/ui/toggle/toggle.svelte @@ -2,7 +2,7 @@ import { type VariantProps, tv } from "tailwind-variants"; export const toggleVariants = tv({ - base: "hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[state=on]:bg-accent data-[state=on]:text-accent-foreground gap-1 rounded-lg text-sm font-medium transition-all [&_svg:not([class*='size-'])]:size-4 group/toggle hover:bg-muted inline-flex items-center justify-center whitespace-nowrap outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", + base: "hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[state=on]:bg-accent data-[state=on]:text-accent-foreground gap-1 rounded-lg text-sm font-medium transition-all [&_svg:not([class*='size-'])]:size-4 group/toggle hover:bg-muted inline-flex min-h-11 min-w-11 items-center justify-center whitespace-nowrap outline-none focus-visible:ring-[3px] md:min-h-0 md:min-w-0 disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0", variants: { variant: { default: "bg-transparent", diff --git a/src/lib/shell/layout-server-data.ts b/src/lib/shell/layout-server-data.ts index 391e217c4..2965d5bd7 100644 --- a/src/lib/shell/layout-server-data.ts +++ b/src/lib/shell/layout-server-data.ts @@ -113,6 +113,8 @@ export function buildLayoutCopy(locale: LayoutLocale) { profileMenu: messages.common.profileMenu, secondaryNavigation: locale === "zh-cn" ? "次级导航" : "Secondary navigation", + scrollRegion: + locale === "zh-cn" ? "主要内容滚动区域" : "Main content scroll region", skipToMainContent: messages.accessibility.skipToMainContent, theme: locale === "zh-cn" ? "主题" : "Theme", }, diff --git a/tests/e2e/src/app/admin/moderation/test.ts b/tests/e2e/src/app/admin/moderation/test.ts index da1ef738c..c067368fd 100644 --- a/tests/e2e/src/app/admin/moderation/test.ts +++ b/tests/e2e/src/app/admin/moderation/test.ts @@ -223,6 +223,7 @@ test("/admin/moderation 移动端弹窗滚动体不遮挡封禁控件", async ({ await expect( dialog.getByRole("button", { name: /关闭|Close/i }), ).toHaveCount(1); + await expect(dialog.locator('[data-slot="dialog-close"]')).toHaveCount(0); const suspendButton = dialog.getByRole("button", { name: /^(封禁|Suspend)$/i, diff --git a/tests/e2e/src/app/dashboard/todos/test.ts b/tests/e2e/src/app/dashboard/todos/test.ts index 563459c5b..39859dd6d 100644 --- a/tests/e2e/src/app/dashboard/todos/test.ts +++ b/tests/e2e/src/app/dashboard/todos/test.ts @@ -474,4 +474,105 @@ test.describe("仪表盘待办", () => { await cleanupTodosByTitlePrefix(page, titlePrefix); } }); + + test("短视口待办新建和编辑弹窗保持标题、滚动体与操作可达", async ({ + page, + }) => { + test.setTimeout(90_000); + const viewport = { width: 390, height: 600 } as const; + await page.setViewportSize(viewport); + await signInAsDebugUser(page, "/workspace/todos"); + + const titlePrefix = `e2e-dashboard-todo-short-${Date.now()}`; + const title = `${titlePrefix}-todo`; + + async function assertDialogBounds( + dialog: import("@playwright/test").Locator, + ) { + const dialogBox = await dialog.boundingBox(); + const footer = dialog.locator('[data-slot="dialog-footer"]'); + const closeButton = dialog.getByRole("button", { name: "Close" }); + const [footerBox, closeBox] = await Promise.all([ + footer.boundingBox(), + closeButton.boundingBox(), + ]); + expect(dialogBox).not.toBeNull(); + expect(footerBox).not.toBeNull(); + expect(closeBox).not.toBeNull(); + if (!dialogBox || !footerBox || !closeBox) { + throw new Error("Expected the short-viewport todo dialog bounds"); + } + expect(dialogBox.y).toBeGreaterThanOrEqual(16); + expect(dialogBox.y + dialogBox.height).toBeLessThanOrEqual( + viewport.height - 16, + ); + expect(footerBox.y + footerBox.height).toBeLessThanOrEqual( + viewport.height - 16, + ); + expect(closeBox.width).toBeGreaterThanOrEqual(44); + expect(closeBox.height).toBeGreaterThanOrEqual(44); + await expect(footer).toBeInViewport(); + await expect(closeButton).toBeInViewport(); + + const calendarButton = dialog.getByRole("button", { + name: /打开日历选择器|Open calendar picker/i, + }); + const calendarBox = await calendarButton.boundingBox(); + expect(calendarBox).not.toBeNull(); + expect(calendarBox?.width ?? 0).toBeGreaterThanOrEqual(44); + expect(calendarBox?.height ?? 0).toBeGreaterThanOrEqual(44); + await expect(calendarButton).toBeInViewport(); + + const scrollViewport = dialog + .locator('[data-slot="scroll-area-viewport"]') + .first(); + const scrollMetrics = await scrollViewport.evaluate((element) => ({ + clientHeight: element.clientHeight, + scrollHeight: element.scrollHeight, + })); + expect(scrollMetrics.clientHeight).toBeGreaterThan(0); + expect(scrollMetrics.scrollHeight).toBeGreaterThanOrEqual( + scrollMetrics.clientHeight, + ); + const scrollBox = await scrollViewport.boundingBox(); + expect(scrollBox).not.toBeNull(); + expect(scrollBox?.y ?? 0).toBeGreaterThanOrEqual(dialogBox.y); + expect(scrollBox?.y ?? 0).toBeLessThan(footerBox.y); + } + + try { + await page.getByTestId("dashboard-todos-add").click(); + const createDialog = page.getByRole("dialog", { + name: /新建待办|New Todo/i, + }); + await expect(createDialog).toBeVisible(); + await assertDialogBounds(createDialog); + + await createDialog.getByLabel(/^(标题|Title)$/i).fill(title); + await createDialog + .getByRole("textbox", { name: /内容描述|Description/i }) + .fill("short viewport regression content"); + await createDialog + .getByRole("button", { name: /创建待办|Create Todo/i }) + .click(); + await expect(visibleText(page, title)).toBeVisible({ timeout: 15_000 }); + + await page.getByRole("button", { name: title, exact: true }).click(); + const detailDialog = page.getByRole("dialog", { name: title }); + await expect(detailDialog).toBeVisible(); + await detailDialog + .getByRole("button", { name: /编辑待办|Edit Todo/i }) + .click(); + + const editDialog = page.getByRole("dialog", { + name: /编辑待办|Edit Todo/i, + }); + await expect(editDialog).toBeVisible(); + await assertDialogBounds(editDialog); + await editDialog.getByRole("button", { name: /取消|Cancel/i }).click(); + await expect(editDialog).toBeHidden(); + } finally { + await cleanupTodosByTitlePrefix(page, titlePrefix); + } + }); }); diff --git a/tests/e2e/src/app/sections/[jwId]/test.ts b/tests/e2e/src/app/sections/[jwId]/test.ts index 62c37b857..77a0428d2 100644 --- a/tests/e2e/src/app/sections/[jwId]/test.ts +++ b/tests/e2e/src/app/sections/[jwId]/test.ts @@ -552,6 +552,41 @@ test.describe("/catalog/sections/[jwId] 班级详情页", () => { await captureStepScreenshot(page, testInfo, "section/detail-mobile"); }); + test("移动端评论编辑器不会让详情内容列横向滚动", async ({ page }) => { + await signInAsDebugUser(page, SECTION_URL); + + for (const width of [360, 390]) { + await page.setViewportSize({ width, height: 844 }); + await gotoAndWaitForReady(page, `${SECTION_URL}#comments`); + + const detailViewport = getDetailViewport(page); + await expect(detailViewport).toBeVisible(); + await expect(page.locator("#comments")).toBeVisible(); + + const assertDetailViewportContained = async () => { + const metrics = await detailViewport.evaluate((element) => ({ + clientWidth: element.clientWidth, + overflowX: getComputedStyle(element).overflowX, + scrollLeft: element.scrollLeft, + scrollWidth: element.scrollWidth, + })); + expect(metrics.overflowX).toBe("hidden"); + expect(metrics.scrollLeft).toBe(0); + expect(metrics.scrollWidth).toBeGreaterThanOrEqual(metrics.clientWidth); + expect(metrics.scrollWidth - metrics.clientWidth).toBeLessThanOrEqual( + 1, + ); + expect( + await page.evaluate(() => document.documentElement.scrollWidth), + ).toBeLessThanOrEqual(width); + }; + + await assertDetailViewportContained(); + await openCommentComposer(page); + await assertDetailViewportContained(); + } + }); + test("桌面端保留页首主操作并隐藏移动端操作栏", async ({ page }) => { await page.setViewportSize({ width: 1440, height: 900 }); await gotoAndWaitForReady(page, `${SECTION_URL}#comments`); diff --git a/tests/e2e/src/app/settings/passkeys/test.ts b/tests/e2e/src/app/settings/passkeys/test.ts index a1339d98d..8dc967dcd 100644 --- a/tests/e2e/src/app/settings/passkeys/test.ts +++ b/tests/e2e/src/app/settings/passkeys/test.ts @@ -201,11 +201,13 @@ test.describe("/account/settings/accounts 通行密钥", () => { await expect( passkeyCard.getByLabel(/通行密钥名称|Passkey name/i), ).toBeVisible(); - await expect( - passkeyCard.getByRole("button", { - name: /添加通行密钥|Add passkey/i, - }), - ).toBeVisible(); + const addPasskeyButton = passkeyCard.getByRole("button", { + name: /添加通行密钥|Add passkey/i, + }); + await expect(addPasskeyButton).toBeVisible(); + const addPasskeyBox = await addPasskeyButton.boundingBox(); + expect(addPasskeyBox?.width ?? 0).toBeGreaterThanOrEqual(44); + expect(addPasskeyBox?.height ?? 0).toBeGreaterThanOrEqual(44); await expect .poll(() => page.evaluate( diff --git a/tests/e2e/src/app/settings/preferences/test.ts b/tests/e2e/src/app/settings/preferences/test.ts index 6b2bee221..bc327236f 100644 --- a/tests/e2e/src/app/settings/preferences/test.ts +++ b/tests/e2e/src/app/settings/preferences/test.ts @@ -44,6 +44,12 @@ test.describe("/account/settings/preferences 外观与语言偏好", () => { name: /^(深色|Dark)$/i, }); + for (const control of await preferences.getByRole("radio").all()) { + const box = await control.boundingBox(); + expect(box?.width ?? 0).toBeGreaterThanOrEqual(44); + expect(box?.height ?? 0).toBeGreaterThanOrEqual(44); + } + await dark.click(); await expect(dark).toBeChecked(); diff --git a/tests/e2e/src/app/test.ts b/tests/e2e/src/app/test.ts index 65a63a36c..bb9924e08 100644 --- a/tests/e2e/src/app/test.ts +++ b/tests/e2e/src/app/test.ts @@ -276,6 +276,34 @@ test("/ shell 提供键盘跳转到主要内容", async ({ page }) => { await expect(page.locator("#main-content")).toBeFocused(); }); +test("/ shell 只保留滚动区域的顺序焦点", async ({ page }) => { + await page.setViewportSize({ width: 1440, height: 900 }); + await signInAsDebugUser(page, "/workspace/todos"); + + const main = page.locator("#main-content"); + const scrollRegion = page.locator("[data-shell-scroll-container]"); + await expect(main).toHaveAttribute("tabindex", "-1"); + await expect(scrollRegion).toHaveAttribute("tabindex", "0"); + await expect(scrollRegion).toHaveRole("region"); + + const labels = await Promise.all([ + main.getAttribute("aria-label"), + scrollRegion.getAttribute("aria-label"), + ]); + expect(labels[1]).toBeTruthy(); + expect(labels[0]).not.toBe(labels[1]); + + const skipLink = page.getByRole("link", { + name: /跳转到主要内容|Skip to main content/i, + }); + await page.keyboard.press("Tab"); + await expect(skipLink).toBeFocused(); + await page.keyboard.press("Enter"); + await expect(main).toBeFocused(); + await page.keyboard.press("Tab"); + await expect(main).not.toBeFocused(); +}); + test("/ shell 菜单可一键切换", async ({ page }) => { await page.setViewportSize({ width: 390, height: 800 }); await signInAsDebugUser(page, "/"); From ef740850463b6f437ce4c4a3943d9faa2d91d21a Mon Sep 17 00:00:00 2001 From: TianKai Ma Date: Thu, 27 Aug 2026 19:40:12 +0800 Subject: [PATCH 2/5] fix(comments): acknowledge deletion before list reload --- .../lib/comment-panel-interactions.ts | 2 +- tests/e2e/src/app/sections/[jwId]/test.ts | 2 +- tests/unit/comment-panel-interactions.test.ts | 48 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/features/comments/lib/comment-panel-interactions.ts b/src/features/comments/lib/comment-panel-interactions.ts index 18908af4b..5c5209e69 100644 --- a/src/features/comments/lib/comment-panel-interactions.ts +++ b/src/features/comments/lib/comment-panel-interactions.ts @@ -136,8 +136,8 @@ export function createCommentPanelInteractions(input: { input.setDeleting(false); } closeDeleteDialog(); - await input.loadComments(); input.onSuccess?.("delete"); + await input.loadComments(); } return { diff --git a/tests/e2e/src/app/sections/[jwId]/test.ts b/tests/e2e/src/app/sections/[jwId]/test.ts index 77a0428d2..45ec7d749 100644 --- a/tests/e2e/src/app/sections/[jwId]/test.ts +++ b/tests/e2e/src/app/sections/[jwId]/test.ts @@ -1709,12 +1709,12 @@ test.describe("/catalog/sections/[jwId] 班级详情页", () => { releaseDeleteRequest(); await deleteResponse; await page.unroute("**/api/community/comments/**"); - await expect(editedCommentCard).toHaveCount(0); await expect( page .locator("[data-sonner-toast]") .filter({ hasText: /评论已删除|Comment deleted/i }), ).toBeVisible(); + await expect(editedCommentCard).toHaveCount(0); await captureStepScreenshot(page, testInfo, "section/comment-deleted"); } finally { await cleanupCommentsForE2e([replyId, commentId]); diff --git a/tests/unit/comment-panel-interactions.test.ts b/tests/unit/comment-panel-interactions.test.ts index bbe036c85..e8bc58ddd 100644 --- a/tests/unit/comment-panel-interactions.test.ts +++ b/tests/unit/comment-panel-interactions.test.ts @@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { createCommentPanelInteractions } from "@/features/comments/lib/comment-panel-interactions"; import type { CommentNode } from "@/features/comments/server/comment-types"; import type { ViewerContext } from "@/lib/auth/viewer-context"; +import { createDeferred } from "../shared/deferred"; function comment(overrides: Partial = {}): CommentNode { return { @@ -89,4 +90,51 @@ describe("评论面板交互", () => { expect(setPendingReactionKey).not.toHaveBeenCalled(); expect(applyReactionUpdate).not.toHaveBeenCalled(); }); + + it("删除请求成功后立即反馈,不等待评论列表重新加载", async () => { + const fetchMock = vi.fn().mockResolvedValue( + new Response(JSON.stringify({ id: "comment-1" }), { + status: 200, + headers: { "content-type": "application/json" }, + }), + ); + vi.stubGlobal("fetch", fetchMock); + const events: string[] = []; + const reload = createDeferred(); + + const interactions = createCommentPanelInteractions({ + applyReactionUpdate: vi.fn(), + getCommentCopy: () => ({ + linkCopied: "copied", + loginRequiredDescription: "login required", + pleaseRetry: "retry", + reactionFailed: "reaction failed", + submitFailed: "submit failed", + suspendedMessage: "suspended", + }), + getCurrentHref: () => "https://life.example/catalog/sections/1", + getDeleteTarget: () => comment(), + getPendingReactionKey: () => null, + getViewer: () => viewer(), + loadComments: async () => { + events.push("reload-started"); + await reload.promise; + }, + onSuccess: (action) => events.push(`success:${action}`), + setActionMenuId: vi.fn(), + setDeleteTarget: vi.fn(), + setDeleting: vi.fn(), + setMessage: vi.fn(), + setMessageVariant: vi.fn(), + setPendingReactionKey: vi.fn(), + setReactionMenuId: vi.fn(), + }); + + const deletion = interactions.deleteComment(); + await vi.waitFor(() => { + expect(events).toEqual(["success:delete", "reload-started"]); + }); + reload.resolve(); + await deletion; + }); }); From 62f7c30da7c0efd35cb071e00088b0630a1281e3 Mon Sep 17 00:00:00 2001 From: TianKai Ma Date: Thu, 27 Aug 2026 20:38:01 +0800 Subject: [PATCH 3/5] fix(admin): keep one moderation dialog close control --- .../admin/components/AdminModerationCommentDialog.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/src/features/admin/components/AdminModerationCommentDialog.svelte b/src/features/admin/components/AdminModerationCommentDialog.svelte index e42165ef9..b267d2ee5 100644 --- a/src/features/admin/components/AdminModerationCommentDialog.svelte +++ b/src/features/admin/components/AdminModerationCommentDialog.svelte @@ -47,6 +47,7 @@ export let targetLabel: (comment: AdminModerationComment) => string; {copy.manageComment} From 9ec672b559ac196e072f33a4314275dd36c4ad9f Mon Sep 17 00:00:00 2001 From: TianKai Ma Date: Thu, 27 Aug 2026 20:56:07 +0800 Subject: [PATCH 4/5] test(admin): align close assertion with dialog copy --- tests/e2e/src/app/admin/moderation/test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/src/app/admin/moderation/test.ts b/tests/e2e/src/app/admin/moderation/test.ts index c067368fd..768076815 100644 --- a/tests/e2e/src/app/admin/moderation/test.ts +++ b/tests/e2e/src/app/admin/moderation/test.ts @@ -221,7 +221,7 @@ test("/admin/moderation 移动端弹窗滚动体不遮挡封禁控件", async ({ }); await expect(dialog).toBeVisible(); await expect( - dialog.getByRole("button", { name: /关闭|Close/i }), + dialog.getByRole("button", { name: /取消|Cancel/i }), ).toHaveCount(1); await expect(dialog.locator('[data-slot="dialog-close"]')).toHaveCount(0); From 2eae03c2c808cc4bbfc87e5c74a2ad40a50cfc7f Mon Sep 17 00:00:00 2001 From: TianKai Ma Date: Thu, 27 Aug 2026 21:15:28 +0800 Subject: [PATCH 5/5] test(admin): dismiss moderation dialog by cancel action --- tests/e2e/src/app/admin/moderation/test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/src/app/admin/moderation/test.ts b/tests/e2e/src/app/admin/moderation/test.ts index 768076815..59ddbeab9 100644 --- a/tests/e2e/src/app/admin/moderation/test.ts +++ b/tests/e2e/src/app/admin/moderation/test.ts @@ -246,7 +246,7 @@ test("/admin/moderation 移动端弹窗滚动体不遮挡封禁控件", async ({ ), ); - await dialog.getByRole("button", { name: /关闭|Close/i }).click(); + await dialog.getByRole("button", { name: /取消|Cancel/i }).click(); await expect(dialog).toBeHidden(); } });