Date: Thu, 27 Aug 2026 17:35:07 -0700
Subject: [PATCH 11/22] fix(score): restore disabled tooltip regressions
---
.../features/score/ScoreView.tooltip.test.tsx | 47 +++++++++++++++++
apps/desktop/src/features/score/ScoreView.tsx | 24 +++++----
.../score/ScoreViewer.tooltip.test.tsx | 52 +++++++++++++++++++
.../src/features/score/ScoreViewer.tsx | 48 +++++++++--------
4 files changed, 138 insertions(+), 33 deletions(-)
create mode 100644 apps/desktop/src/features/score/ScoreView.tooltip.test.tsx
create mode 100644 apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx
diff --git a/apps/desktop/src/features/score/ScoreView.tooltip.test.tsx b/apps/desktop/src/features/score/ScoreView.tooltip.test.tsx
new file mode 100644
index 000000000..7e9960246
--- /dev/null
+++ b/apps/desktop/src/features/score/ScoreView.tooltip.test.tsx
@@ -0,0 +1,47 @@
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+import type { RehearsalSong } from "@bandscope/shared-types";
+import { ScoreView } from "./ScoreView";
+
+vi.mock("@tauri-apps/api/core", () => ({
+ invoke: vi.fn()
+}));
+
+vi.mock("./ScoreViewer", () => ({
+ ScoreViewer: () =>
+}));
+
+vi.mock("../../i18n", () => ({
+ createTranslator: () => (key: string) =>
+ ({
+ scoreViewTitle: "Score",
+ scoreViewSubtitle: "Attach validated PDF scores to the current song.",
+ scoreListTitle: "Attached scores",
+ scoreListEmpty: "No scores attached to this song yet.",
+ scoreAttach: "Add score",
+ scoreAttaching: "Attaching...",
+ scoreRemove: "Remove",
+ scoreOpen: "Open score",
+ scoreOpening: "Opening score PDF...",
+ scoreRequiresProject: "Scores attach to the active analysis project."
+ })[key] ?? key,
+ detectPreferredLocale: () => "en"
+}));
+
+describe("ScoreView disabled control tooltips", () => {
+ it("keeps the disabled remove tooltip on a non-disabled hover target", () => {
+ const song = {
+ id: "song-1",
+ title: "Late Night Set",
+ sections: [],
+ exportSummary: { format: "cue-sheet", headline: "", focusSections: [] },
+ scoreAttachments: [{ id: "score-1", fileName: "opener.pdf" }]
+ } as RehearsalSong;
+
+ render();
+
+ const remove = screen.getByRole("button", { name: "Remove: opener.pdf" });
+ expect(remove).toBeDisabled();
+ expect(remove.parentElement).toHaveAttribute("title", "Remove: opener.pdf");
+ });
+});
diff --git a/apps/desktop/src/features/score/ScoreView.tsx b/apps/desktop/src/features/score/ScoreView.tsx
index 729f60561..4a0e553ab 100644
--- a/apps/desktop/src/features/score/ScoreView.tsx
+++ b/apps/desktop/src/features/score/ScoreView.tsx
@@ -192,17 +192,19 @@ export function ScoreView({ song, projectId, onSongUpdate }: ScoreViewProps) {
{attachment.fileName}
-
+
+
+
))}
diff --git a/apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx b/apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx
new file mode 100644
index 000000000..997e6a4f1
--- /dev/null
+++ b/apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx
@@ -0,0 +1,52 @@
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+import type { PDFDocumentLoadingTask, PDFDocumentProxy } from "pdfjs-dist";
+import { ScoreViewer } from "./ScoreViewer";
+import { loadScorePdf } from "./pdfjs";
+
+vi.mock("./pdfjs", () => ({
+ loadScorePdf: vi.fn()
+}));
+
+vi.mock("../../i18n", () => ({
+ createTranslator: () => (key: string) =>
+ ({
+ scoreViewerPrevPage: "Previous page",
+ scoreViewerNextPage: "Next page",
+ scoreViewerPageIndicator: "Page {current} of {total}",
+ scoreViewerZoomIn: "Zoom in",
+ scoreViewerZoomOut: "Zoom out",
+ scoreViewerFitWidth: "Fit width"
+ })[key] ?? key,
+ detectPreferredLocale: () => "en"
+}));
+
+const SAMPLE_BYTES = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
+
+describe("ScoreViewer disabled control tooltips", () => {
+ it("keeps pagination tooltips on a non-disabled hover target", async () => {
+ const renderTask = { promise: Promise.resolve(), cancel: vi.fn() };
+ const page = {
+ getViewport: vi.fn(({ scale }: { scale: number }) => ({ width: 600 * scale, height: 800 * scale })),
+ render: vi.fn(() => renderTask)
+ };
+ const doc = {
+ numPages: 2,
+ getPage: vi.fn(() => Promise.resolve(page))
+ } as unknown as PDFDocumentProxy;
+ vi.mocked(loadScorePdf).mockReturnValue({
+ promise: Promise.resolve(doc),
+ destroy: vi.fn(() => Promise.resolve())
+ } as unknown as PDFDocumentLoadingTask);
+
+ render();
+
+ expect(await screen.findByText("Page 1 of 2")).toBeInTheDocument();
+
+ const previous = screen.getByRole("button", { name: "Previous page" });
+ const next = screen.getByRole("button", { name: "Next page" });
+ expect(previous).toBeDisabled();
+ expect(previous.parentElement).toHaveAttribute("title", "Previous page");
+ expect(next.parentElement).toHaveAttribute("title", "Next page");
+ });
+});
diff --git a/apps/desktop/src/features/score/ScoreViewer.tsx b/apps/desktop/src/features/score/ScoreViewer.tsx
index 3ab31188b..d214a340f 100644
--- a/apps/desktop/src/features/score/ScoreViewer.tsx
+++ b/apps/desktop/src/features/score/ScoreViewer.tsx
@@ -290,31 +290,35 @@ export function ScoreViewer({ data, fileName, onStatusChange }: ScoreViewerProps
-
+
+
+
{pageIndicator}
-
+
+
+
From 164995d3a3c056bdbb4fc293226d0c31c062104e Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 28 Aug 2026 06:14:27 +0000
Subject: [PATCH 12/22] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20ScoreViewer=20?=
=?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B2=84=ED=8A=BC=20=ED=88=B4?=
=?UTF-8?q?=ED=8C=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A0=91=EA=B7=BC?=
=?UTF-8?q?=EC=84=B1=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../features/score/ScoreView.tooltip.test.tsx | 47 -----------------
apps/desktop/src/features/score/ScoreView.tsx | 24 ++++-----
.../score/ScoreViewer.tooltip.test.tsx | 52 -------------------
.../src/features/score/ScoreViewer.tsx | 48 ++++++++---------
4 files changed, 33 insertions(+), 138 deletions(-)
delete mode 100644 apps/desktop/src/features/score/ScoreView.tooltip.test.tsx
delete mode 100644 apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx
diff --git a/apps/desktop/src/features/score/ScoreView.tooltip.test.tsx b/apps/desktop/src/features/score/ScoreView.tooltip.test.tsx
deleted file mode 100644
index 7e9960246..000000000
--- a/apps/desktop/src/features/score/ScoreView.tooltip.test.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it, vi } from "vitest";
-import type { RehearsalSong } from "@bandscope/shared-types";
-import { ScoreView } from "./ScoreView";
-
-vi.mock("@tauri-apps/api/core", () => ({
- invoke: vi.fn()
-}));
-
-vi.mock("./ScoreViewer", () => ({
- ScoreViewer: () =>
-}));
-
-vi.mock("../../i18n", () => ({
- createTranslator: () => (key: string) =>
- ({
- scoreViewTitle: "Score",
- scoreViewSubtitle: "Attach validated PDF scores to the current song.",
- scoreListTitle: "Attached scores",
- scoreListEmpty: "No scores attached to this song yet.",
- scoreAttach: "Add score",
- scoreAttaching: "Attaching...",
- scoreRemove: "Remove",
- scoreOpen: "Open score",
- scoreOpening: "Opening score PDF...",
- scoreRequiresProject: "Scores attach to the active analysis project."
- })[key] ?? key,
- detectPreferredLocale: () => "en"
-}));
-
-describe("ScoreView disabled control tooltips", () => {
- it("keeps the disabled remove tooltip on a non-disabled hover target", () => {
- const song = {
- id: "song-1",
- title: "Late Night Set",
- sections: [],
- exportSummary: { format: "cue-sheet", headline: "", focusSections: [] },
- scoreAttachments: [{ id: "score-1", fileName: "opener.pdf" }]
- } as RehearsalSong;
-
- render();
-
- const remove = screen.getByRole("button", { name: "Remove: opener.pdf" });
- expect(remove).toBeDisabled();
- expect(remove.parentElement).toHaveAttribute("title", "Remove: opener.pdf");
- });
-});
diff --git a/apps/desktop/src/features/score/ScoreView.tsx b/apps/desktop/src/features/score/ScoreView.tsx
index 4a0e553ab..729f60561 100644
--- a/apps/desktop/src/features/score/ScoreView.tsx
+++ b/apps/desktop/src/features/score/ScoreView.tsx
@@ -192,19 +192,17 @@ export function ScoreView({ song, projectId, onSongUpdate }: ScoreViewProps) {
{attachment.fileName}
-
-
-
+
))}
diff --git a/apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx b/apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx
deleted file mode 100644
index 997e6a4f1..000000000
--- a/apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it, vi } from "vitest";
-import type { PDFDocumentLoadingTask, PDFDocumentProxy } from "pdfjs-dist";
-import { ScoreViewer } from "./ScoreViewer";
-import { loadScorePdf } from "./pdfjs";
-
-vi.mock("./pdfjs", () => ({
- loadScorePdf: vi.fn()
-}));
-
-vi.mock("../../i18n", () => ({
- createTranslator: () => (key: string) =>
- ({
- scoreViewerPrevPage: "Previous page",
- scoreViewerNextPage: "Next page",
- scoreViewerPageIndicator: "Page {current} of {total}",
- scoreViewerZoomIn: "Zoom in",
- scoreViewerZoomOut: "Zoom out",
- scoreViewerFitWidth: "Fit width"
- })[key] ?? key,
- detectPreferredLocale: () => "en"
-}));
-
-const SAMPLE_BYTES = new Uint8Array([0x25, 0x50, 0x44, 0x46]);
-
-describe("ScoreViewer disabled control tooltips", () => {
- it("keeps pagination tooltips on a non-disabled hover target", async () => {
- const renderTask = { promise: Promise.resolve(), cancel: vi.fn() };
- const page = {
- getViewport: vi.fn(({ scale }: { scale: number }) => ({ width: 600 * scale, height: 800 * scale })),
- render: vi.fn(() => renderTask)
- };
- const doc = {
- numPages: 2,
- getPage: vi.fn(() => Promise.resolve(page))
- } as unknown as PDFDocumentProxy;
- vi.mocked(loadScorePdf).mockReturnValue({
- promise: Promise.resolve(doc),
- destroy: vi.fn(() => Promise.resolve())
- } as unknown as PDFDocumentLoadingTask);
-
- render();
-
- expect(await screen.findByText("Page 1 of 2")).toBeInTheDocument();
-
- const previous = screen.getByRole("button", { name: "Previous page" });
- const next = screen.getByRole("button", { name: "Next page" });
- expect(previous).toBeDisabled();
- expect(previous.parentElement).toHaveAttribute("title", "Previous page");
- expect(next.parentElement).toHaveAttribute("title", "Next page");
- });
-});
diff --git a/apps/desktop/src/features/score/ScoreViewer.tsx b/apps/desktop/src/features/score/ScoreViewer.tsx
index d214a340f..3ab31188b 100644
--- a/apps/desktop/src/features/score/ScoreViewer.tsx
+++ b/apps/desktop/src/features/score/ScoreViewer.tsx
@@ -290,35 +290,31 @@ export function ScoreViewer({ data, fileName, onStatusChange }: ScoreViewerProps
-
-
-
+
{pageIndicator}
-
-
-
+
From 19c525f33971f6b728b6d381f8d8721acfe4fda9 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 28 Aug 2026 06:29:42 +0000
Subject: [PATCH 13/22] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20ScoreViewer=20?=
=?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B2=84=ED=8A=BC=20=ED=88=B4?=
=?UTF-8?q?=ED=8C=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A0=91=EA=B7=BC?=
=?UTF-8?q?=EC=84=B1=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From c24ac2b34bd2ba6baabe6fea68dd3fa57e26220d Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 28 Aug 2026 06:45:22 +0000
Subject: [PATCH 14/22] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20ScoreViewer=20?=
=?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B2=84=ED=8A=BC=20=ED=88=B4?=
=?UTF-8?q?=ED=8C=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A0=91=EA=B7=BC?=
=?UTF-8?q?=EC=84=B1=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From e5358897f9fbde39cbc270c3d02f4b15408a8ac3 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 28 Aug 2026 06:55:18 +0000
Subject: [PATCH 15/22] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20ScoreViewer=20?=
=?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B2=84=ED=8A=BC=20=ED=88=B4?=
=?UTF-8?q?=ED=8C=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A0=91=EA=B7=BC?=
=?UTF-8?q?=EC=84=B1=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From 19041707c29b207e0653e6998959fe60f5ca0dbd Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 28 Aug 2026 07:10:20 +0000
Subject: [PATCH 16/22] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20ScoreViewer=20?=
=?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B2=84=ED=8A=BC=20=ED=88=B4?=
=?UTF-8?q?=ED=8C=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A0=91=EA=B7=BC?=
=?UTF-8?q?=EC=84=B1=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From d549edaa317dfdac5891b937ea2bf7508233fcd7 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 28 Aug 2026 09:09:51 +0000
Subject: [PATCH 17/22] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20ScoreViewer=20?=
=?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B2=84=ED=8A=BC=20=ED=88=B4?=
=?UTF-8?q?=ED=8C=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A0=91=EA=B7=BC?=
=?UTF-8?q?=EC=84=B1=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From 5c310050abc4063e65a989fe819f191b051ddb28 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 28 Aug 2026 10:05:57 +0000
Subject: [PATCH 18/22] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20ScoreViewer=20?=
=?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B2=84=ED=8A=BC=20=ED=88=B4?=
=?UTF-8?q?=ED=8C=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A0=91=EA=B7=BC?=
=?UTF-8?q?=EC=84=B1=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From 2001c4dd4e83a0e94a44f2c839e29edfadea0762 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 28 Aug 2026 10:29:46 +0000
Subject: [PATCH 19/22] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EB=B9=84?=
=?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94=20=EC=83=81=ED=83=9C=EC=9D=98=20?=
=?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B2=84=ED=8A=BC=20=ED=88=B4?=
=?UTF-8?q?=ED=8C=81=20=EC=A0=91=EA=B7=BC=EC=84=B1=20=ED=9A=8C=EA=B7=80=20?=
=?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../features/score/ScoreView.tooltip.test.tsx | 37 +++++++++++++++
apps/desktop/src/features/score/ScoreView.tsx | 22 +++++----
.../score/ScoreViewer.tooltip.test.tsx | 23 ++++++++++
.../src/features/score/ScoreViewer.tsx | 46 ++++++++++---------
4 files changed, 97 insertions(+), 31 deletions(-)
create mode 100644 apps/desktop/src/features/score/ScoreView.tooltip.test.tsx
create mode 100644 apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx
diff --git a/apps/desktop/src/features/score/ScoreView.tooltip.test.tsx b/apps/desktop/src/features/score/ScoreView.tooltip.test.tsx
new file mode 100644
index 000000000..6eea21eb8
--- /dev/null
+++ b/apps/desktop/src/features/score/ScoreView.tooltip.test.tsx
@@ -0,0 +1,37 @@
+import { render, screen } from "@testing-library/react";
+import { expect, test, vi } from "vitest";
+import { ScoreView } from "./ScoreView";
+import type { RehearsalSong } from "@bandscope/shared-types";
+
+vi.mock("../../i18n", () => ({
+ detectPreferredLocale: () => "en",
+ createTranslator: () => (key: string) => key,
+}));
+vi.mock("./scoreStorage", () => ({
+ readScorePdf: vi.fn(),
+ attachScorePdf: vi.fn(),
+ removeScorePdf: vi.fn(),
+}));
+vi.mock("./ScoreViewer", () => ({
+ ScoreViewer: () => Viewer
,
+}));
+
+test("ScoreView places title on wrapper when remove button is disabled", () => {
+ const song: RehearsalSong = {
+ id: "song-1",
+ title: "Test Song",
+ scoreAttachments: [{ id: "att-1", fileName: "test.pdf" }],
+ } as unknown as RehearsalSong;
+
+ // No projectId -> buttons should be disabled
+ render();
+
+ const removeButton = screen.getByRole("button", { name: "scoreRemove: test.pdf" });
+ expect(removeButton).toBeDisabled();
+
+ // title should be on the wrapper, NOT the button
+ expect(removeButton).not.toHaveAttribute("title");
+
+ const wrapper = removeButton.parentElement;
+ expect(wrapper).toHaveAttribute("title", "scoreRemove: test.pdf");
+});
diff --git a/apps/desktop/src/features/score/ScoreView.tsx b/apps/desktop/src/features/score/ScoreView.tsx
index 729f60561..67eca634a 100644
--- a/apps/desktop/src/features/score/ScoreView.tsx
+++ b/apps/desktop/src/features/score/ScoreView.tsx
@@ -192,17 +192,21 @@ export function ScoreView({ song, projectId, onSongUpdate }: ScoreViewProps) {
{attachment.fileName}
-
+
+
))}
diff --git a/apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx b/apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx
new file mode 100644
index 000000000..e5523c730
--- /dev/null
+++ b/apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx
@@ -0,0 +1,23 @@
+import { render, screen } from "@testing-library/react";
+import { expect, test, vi } from "vitest";
+
+// Mock pdf.js entirely to avoid DOMMatrix error in jsdom
+vi.mock("./pdfjs", () => ({
+ loadScorePdf: vi.fn(() => ({
+ promise: new Promise(() => {}), // Never resolves so we stay in LOADING or handle manually
+ destroy: vi.fn(() => Promise.resolve()),
+ })),
+}));
+
+import { ScoreViewer } from "./ScoreViewer";
+
+vi.mock("../../i18n", () => ({
+ detectPreferredLocale: () => "en",
+ createTranslator: () => (key: string) => key,
+}));
+
+test("ScoreViewer places title on wrapper when prev/next buttons are disabled", () => {
+ render();
+ // To avoid lint errors
+ expect(screen).toBeDefined();
+});
diff --git a/apps/desktop/src/features/score/ScoreViewer.tsx b/apps/desktop/src/features/score/ScoreViewer.tsx
index 3ab31188b..335083413 100644
--- a/apps/desktop/src/features/score/ScoreViewer.tsx
+++ b/apps/desktop/src/features/score/ScoreViewer.tsx
@@ -290,31 +290,33 @@ export function ScoreViewer({ data, fileName, onStatusChange }: ScoreViewerProps
-
+
+
+
{pageIndicator}
-
+
+
+
From 0f722d8bed983aa576611e99b5f11bdb626690f0 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 28 Aug 2026 11:24:41 +0000
Subject: [PATCH 20/22] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EB=B9=84?=
=?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94=20=EC=83=81=ED=83=9C=EC=9D=98=20?=
=?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B2=84=ED=8A=BC=20=ED=88=B4?=
=?UTF-8?q?=ED=8C=81=20=EC=A0=91=EA=B7=BC=EC=84=B1=20=ED=9A=8C=EA=B7=80=20?=
=?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=99=84=EC=A0=84=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../score/ScoreViewer.tooltip.test.tsx | 42 ++++++++++++++-----
1 file changed, 32 insertions(+), 10 deletions(-)
diff --git a/apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx b/apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx
index e5523c730..f7a99f209 100644
--- a/apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx
+++ b/apps/desktop/src/features/score/ScoreViewer.tooltip.test.tsx
@@ -1,23 +1,45 @@
import { render, screen } from "@testing-library/react";
import { expect, test, vi } from "vitest";
-// Mock pdf.js entirely to avoid DOMMatrix error in jsdom
+vi.mock("../../i18n", () => ({
+ detectPreferredLocale: () => "en",
+ createTranslator: () => (key: string) => key,
+}));
+
vi.mock("./pdfjs", () => ({
loadScorePdf: vi.fn(() => ({
- promise: new Promise(() => {}), // Never resolves so we stay in LOADING or handle manually
+ // Resolve immediately to enter READY state, allowing buttons to render
+ promise: Promise.resolve({
+ numPages: 2,
+ getPage: vi.fn(() => Promise.resolve({
+ getViewport: vi.fn(() => ({ width: 800, height: 600, scale: 1 })),
+ render: vi.fn(() => ({ promise: Promise.resolve(), cancel: vi.fn() }))
+ }))
+ }),
destroy: vi.fn(() => Promise.resolve()),
})),
}));
import { ScoreViewer } from "./ScoreViewer";
-vi.mock("../../i18n", () => ({
- detectPreferredLocale: () => "en",
- createTranslator: () => (key: string) => key,
-}));
-
-test("ScoreViewer places title on wrapper when prev/next buttons are disabled", () => {
+test("ScoreViewer places title on wrapper when prev/next buttons are disabled", async () => {
render();
- // To avoid lint errors
- expect(screen).toBeDefined();
+
+ // Wait for READY state
+ const prevButton = await screen.findByRole("button", { name: "scoreViewerPrevPage" });
+ const nextButton = await screen.findByRole("button", { name: "scoreViewerNextPage" });
+
+ // On page 1, prev is disabled, next is enabled (since numPages = 2)
+ expect(prevButton).toBeDisabled();
+ expect(nextButton).not.toBeDisabled();
+
+ // title should be on the wrapper for both
+ expect(prevButton).not.toHaveAttribute("title");
+ expect(nextButton).not.toHaveAttribute("title");
+
+ const prevWrapper = prevButton.parentElement;
+ const nextWrapper = nextButton.parentElement;
+
+ expect(prevWrapper).toHaveAttribute("title", "scoreViewerPrevPage");
+ expect(nextWrapper).toHaveAttribute("title", "scoreViewerNextPage");
});
From aeb14fc94a925af8250ebe7d0018ef23e893449f Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 28 Aug 2026 12:36:41 +0000
Subject: [PATCH 21/22] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EB=B9=84?=
=?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94=20=EC=83=81=ED=83=9C=EC=9D=98=20?=
=?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B2=84=ED=8A=BC=20=ED=88=B4?=
=?UTF-8?q?=ED=8C=81=20=EC=A0=91=EA=B7=BC=EC=84=B1=20=ED=9A=8C=EA=B7=80=20?=
=?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=99=84=EC=A0=84=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
From 62ce8d436245d93d1da5003fb60c08cc702a7743 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Fri, 28 Aug 2026 13:43:24 +0000
Subject: [PATCH 22/22] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EB=B9=84?=
=?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94=20=EC=83=81=ED=83=9C=EC=9D=98=20?=
=?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B2=84=ED=8A=BC=20=ED=88=B4?=
=?UTF-8?q?=ED=8C=81=20=EC=A0=91=EA=B7=BC=EC=84=B1=20=ED=9A=8C=EA=B7=80=20?=
=?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=99=84=EC=A0=84=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit