From ae2c421938f1f35ecdaecb018a896b5cc2fbb16c Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Mon, 6 Apr 2026 22:48:12 +0800 Subject: [PATCH] fix(ui): restore highlight options layout and clean up color name editing (#3776) * fix(ui): restore highlight options layout and clean up color name editing Restore the pre-#3741 layout for both the AnnotationPopup highlight options row and the HighlightColorsEditor settings panel. HighlightOptions: re-add `justify-between` on the outer container and remove `flex-1` from the color strip so the gap between the style box and color strip responds to the number of colors. HighlightColorsEditor: restore `grid-cols-3 sm:grid-cols-5` grid, remove always-visible name inputs, and add a click-to-edit popover on each color circle with hover tooltip for the label. Add a browser screenshot test that renders the real AnnotationPopup component with Tailwind CSS and compares against baseline PNGs for 5, 10, and 15 colors. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/readest-app/.claude/memory/MEMORY.md | 1 + .../memory/feedback_test_file_filter.md | 11 + apps/readest-app/.gitignore | 1 + apps/readest-app/package.json | 4 +- .../annotation-popup-10-colors-chromium.png | Bin 0 -> 21829 bytes .../annotation-popup-15-colors-chromium.png | Bin 0 -> 21829 bytes .../annotation-popup-5-colors-chromium.png | Bin 0 -> 20394 bytes .../annotation-popup-layout.browser.test.tsx | 210 ++++++++++++++++++ .../components/annotator/HighlightOptions.tsx | 6 +- .../settings/color/HighlightColorsEditor.tsx | 175 +++++++++------ apps/readest-app/vitest.browser.config.mts | 26 ++- apps/readest-app/vitest.config.mts | 1 + 12 files changed, 354 insertions(+), 81 deletions(-) create mode 100644 apps/readest-app/.claude/memory/feedback_test_file_filter.md create mode 100644 apps/readest-app/src/__tests__/components/__screenshots__/annotation-popup-layout.browser.test.tsx/annotation-popup-10-colors-chromium.png create mode 100644 apps/readest-app/src/__tests__/components/__screenshots__/annotation-popup-layout.browser.test.tsx/annotation-popup-15-colors-chromium.png create mode 100644 apps/readest-app/src/__tests__/components/__screenshots__/annotation-popup-layout.browser.test.tsx/annotation-popup-5-colors-chromium.png create mode 100644 apps/readest-app/src/__tests__/components/annotation-popup-layout.browser.test.tsx diff --git a/apps/readest-app/.claude/memory/MEMORY.md b/apps/readest-app/.claude/memory/MEMORY.md index 3236ed6f..30c1379b 100644 --- a/apps/readest-app/.claude/memory/MEMORY.md +++ b/apps/readest-app/.claude/memory/MEMORY.md @@ -29,6 +29,7 @@ - Dropdown menus use `DropdownContext` (not blur-based) for screen reader compat ## Workflow +- [Test file filter](feedback_test_file_filter.md) — use `pnpm test ` without `--` to run a single file - [Always rebase before PR](feedback_pr_rebase.md) — rebase onto origin/main before creating PRs - [New branch per PR](feedback_pr_new_branch.md) — always create a fresh branch from main for each new PR/issue - [Upgrade gstack locally](feedback_gstack_upgrade.md) — always upgrade from the project's .claude/skills/gstack, not global diff --git a/apps/readest-app/.claude/memory/feedback_test_file_filter.md b/apps/readest-app/.claude/memory/feedback_test_file_filter.md new file mode 100644 index 00000000..5833b3fb --- /dev/null +++ b/apps/readest-app/.claude/memory/feedback_test_file_filter.md @@ -0,0 +1,11 @@ +--- +name: test-file-filter +description: Use pnpm test/test:browser with path directly (no --) to run a single test file +type: feedback +--- + +Run a specific test file with `pnpm test ` or `pnpm test:browser ` — no `--` separator. + +**Why:** Adding `--` before the path (e.g. `pnpm test:browser -- `) causes vitest to ignore the file filter and run all test files. Without `--`, pnpm appends the path directly to the vitest command, which correctly filters to that file only. + +**How to apply:** Always use `pnpm test src/__tests__/foo.test.ts` or `pnpm test:browser src/__tests__/foo.browser.test.tsx` when verifying a specific test file. diff --git a/apps/readest-app/.gitignore b/apps/readest-app/.gitignore index d4a8ec07..40b75e84 100644 --- a/apps/readest-app/.gitignore +++ b/apps/readest-app/.gitignore @@ -9,6 +9,7 @@ # testing /coverage .test-sandbox-node/ +.vitest-attachments/ # next.js /.next/ diff --git a/apps/readest-app/package.json b/apps/readest-app/package.json index 5d58ab0e..5af77a23 100644 --- a/apps/readest-app/package.json +++ b/apps/readest-app/package.json @@ -16,9 +16,9 @@ "build-tauri": "dotenv -e .env.tauri -- next build", "i18n:extract": "i18next-scanner --config i18next-scanner.config.cjs", "lint": "tsgo --noEmit && biome check .", - "test": "dotenv -e .env -e .env.test.local vitest", + "test": "dotenv -e .env -e .env.test.local -- vitest", "test:coverage": "dotenv -e .env -e .env.test.local -- vitest run --coverage", - "test:browser": "vitest --config vitest.browser.config.mts --watch=false", + "test:browser": "dotenv -e .env -e .env.test.local -- vitest run --config vitest.browser.config.mts", "test:tauri": "bash scripts/test-tauri.sh", "test:pr:web": "pnpm test -- --watch=false && pnpm test:browser", "test:pr:tauri": "bash scripts/test-tauri.sh", diff --git a/apps/readest-app/src/__tests__/components/__screenshots__/annotation-popup-layout.browser.test.tsx/annotation-popup-10-colors-chromium.png b/apps/readest-app/src/__tests__/components/__screenshots__/annotation-popup-layout.browser.test.tsx/annotation-popup-10-colors-chromium.png new file mode 100644 index 0000000000000000000000000000000000000000..9425895ffaea0a21ef8aa3d47f13e22ad62c29d2 GIT binary patch literal 21829 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 O3*4!6moiG=*?$1o-9A_V literal 0 HcmV?d00001 diff --git a/apps/readest-app/src/__tests__/components/annotation-popup-layout.browser.test.tsx b/apps/readest-app/src/__tests__/components/annotation-popup-layout.browser.test.tsx new file mode 100644 index 00000000..65d682a6 --- /dev/null +++ b/apps/readest-app/src/__tests__/components/annotation-popup-layout.browser.test.tsx @@ -0,0 +1,210 @@ +/** + * Visual regression test for the AnnotationPopup component. + * + * Renders the *real* AnnotationPopup + HighlightOptions with actual + * annotationToolButtons, DEFAULT_HIGHLIGHT_COLORS, and optional user + * colors. Tailwind CSS is loaded so the screenshot matches the live app. + * + * Guards against the layout regression from PR #3741 (missing + * `justify-between`, unwanted `flex-1` on the color strip). + */ + +import React from 'react'; +import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from 'vitest'; +import { render, cleanup } from '@testing-library/react'; +import { page } from 'vitest/browser'; +import type { UserHighlightColor } from '@/types/book'; + +// ── Tailwind / DaisyUI styles ─────────────────────────────────────────── +import '@/styles/globals.css'; + +// ── Per-test state read by mocks ──────────────────────────────────────── +let mockUserColors: UserHighlightColor[] = []; + +// ── Mocks (must be before component imports) ──────────────────────────── + +vi.mock('@/context/EnvContext', () => ({ + useEnv: () => ({ envConfig: {}, appService: null }), +})); + +vi.mock('@/store/themeStore', () => ({ + useThemeStore: () => ({ isDarkMode: false }), +})); + +vi.mock('@/hooks/useTranslation', () => ({ + useTranslation: () => (s: string) => s, +})); + +vi.mock('@/store/settingsStore', () => ({ + useSettingsStore: () => ({ + settings: { + globalReadSettings: { + highlightStyle: 'highlight' as const, + highlightStyles: { + highlight: 'yellow', + underline: 'red', + squiggly: 'blue', + }, + customHighlightColors: {} as Record, + get userHighlightColors() { + return mockUserColors; + }, + defaultHighlightLabels: {}, + }, + globalViewSettings: { + isEink: false, + isColorEink: false, + }, + }, + }), +})); + +vi.mock('@/hooks/useResponsiveSize', () => ({ + useResponsiveSize: (n: number) => n, + useDefaultIconSize: () => 20, +})); + +vi.mock('@/hooks/useKeyDownActions', () => ({ + useKeyDownActions: () => {}, +})); + +vi.mock('@/helpers/settings', () => ({ + saveSysSettings: vi.fn(), +})); + +vi.mock('@/app/reader/utils/annotatorUtil', () => ({ + getHighlightColorLabel: () => undefined, +})); + +// ── Real component imports ────────────────────────────────────────────── + +import AnnotationPopup from '@/app/reader/components/annotator/AnnotationPopup'; +import { annotationToolButtons } from '@/app/reader/components/annotator/AnnotationTools'; + +// ── Constants ─────────────────────────────────────────────────────────── + +const POPUP_W = 300; +const POPUP_H = 44; + +// Highlight options float above the popup by (28 + 16) = 44px +const OPTIONS_OFFSET = 28 + 16; + +// Position the popup so both it and the floating options are visible: +// y=0..OPTIONS_OFFSET: highlight-options row +// y=OPTIONS_OFFSET..OPTIONS_OFFSET+POPUP_H: toolbar +const POPUP_Y = OPTIONS_OFFSET; +const POPUP_X = 0; +const WRAPPER_H = POPUP_Y + POPUP_H + 14; // +14 for triangle below + +const toolButtons = annotationToolButtons.map(({ label, Icon }) => ({ + tooltipText: label, + Icon, + onClick: vi.fn(), +})); + +// Browser-mode matcher types are unavailable to tsgo; cast once here. +const expectElement = (locator: unknown) => + // @ts-expect-error -- expect.element() exists in vitest browser mode + expect.element(locator) as { toMatchScreenshot: (name: string) => Promise }; + +/** + * Fixed-size wrapper that contains both the popup and the absolutely + * positioned highlight-options row above it, matching the real app + * where the triangle points up and highlight options float above. + */ +const Wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
+ {children} +
+); + +const renderPopup = (userColors: UserHighlightColor[] = []) => { + mockUserColors = userColors; + return render( + + + , + ); +}; + +// ── Lifecycle ─────────────────────────────────────────────────────────── + +beforeAll(async () => { + await page.viewport(800, 600); +}); + +beforeEach(() => { + mockUserColors = []; +}); + +afterEach(() => { + cleanup(); +}); + +// ── Tests ─────────────────────────────────────────────────────────────── + +describe('AnnotationPopup layout screenshot', () => { + it('default 5 colors — compact color strip, large gap', async () => { + const { container } = renderPopup(); + const wrapper = container.firstElementChild as HTMLElement; + await expectElement(page.elementLocator(wrapper)).toMatchScreenshot( + 'annotation-popup-5-colors', + ); + }); + + it('5+5 user colors — color strip grows, gap shrinks', async () => { + const { container } = renderPopup([ + { hex: '#f97316' }, + { hex: '#06b6d4' }, + { hex: '#ec4899' }, + { hex: '#14b8a6' }, + { hex: '#f43f5e' }, + ]); + const wrapper = container.firstElementChild as HTMLElement; + await expectElement(page.elementLocator(wrapper)).toMatchScreenshot( + 'annotation-popup-10-colors', + ); + }); + + it('5+10 user colors — color strip at max, overflow scrolls', async () => { + const { container } = renderPopup([ + { hex: '#f97316' }, + { hex: '#06b6d4' }, + { hex: '#ec4899' }, + { hex: '#14b8a6' }, + { hex: '#f43f5e' }, + { hex: '#a855f7' }, + { hex: '#84cc16' }, + { hex: '#0ea5e9' }, + { hex: '#e11d48' }, + { hex: '#6366f1' }, + ]); + const wrapper = container.firstElementChild as HTMLElement; + await expectElement(page.elementLocator(wrapper)).toMatchScreenshot( + 'annotation-popup-15-colors', + ); + }); +}); diff --git a/apps/readest-app/src/app/reader/components/annotator/HighlightOptions.tsx b/apps/readest-app/src/app/reader/components/annotator/HighlightOptions.tsx index 442ed0ec..850ea93b 100644 --- a/apps/readest-app/src/app/reader/components/annotator/HighlightOptions.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/HighlightOptions.tsx @@ -173,7 +173,7 @@ const HighlightOptions: React.FC = ({ return (
= ({ {...stripPointerHandlers} className={clsx( 'not-eink:bg-gray-700 eink-bordered flex items-center gap-2 rounded-3xl', - isVertical - ? 'flex-col overflow-y-auto py-2' - : 'min-w-0 flex-1 flex-row overflow-x-auto px-2', + isVertical ? 'flex-col overflow-y-auto py-2' : 'min-w-0 flex-row overflow-x-auto px-2', !isVertical && 'cursor-grab', !isVertical && isDraggingColorStrip && 'cursor-grabbing', )} diff --git a/apps/readest-app/src/components/settings/color/HighlightColorsEditor.tsx b/apps/readest-app/src/components/settings/color/HighlightColorsEditor.tsx index 537fe487..a2bfb287 100644 --- a/apps/readest-app/src/components/settings/color/HighlightColorsEditor.tsx +++ b/apps/readest-app/src/components/settings/color/HighlightColorsEditor.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useState } from 'react'; +import clsx from 'clsx'; +import React, { useEffect, useRef, useState } from 'react'; import { MdClose } from 'react-icons/md'; import { DEFAULT_HIGHLIGHT_COLORS, @@ -25,40 +26,90 @@ interface HighlightColorsEditorProps { } /** - * Text input that commits on blur instead of on every keystroke, so we don't - * thrash the settings store while the user is typing a label. + * Popover that appears on click of a color circle, allowing the user to + * view and edit the label for that color. */ -const LabelInput: React.FC<{ +const LabelPopover: React.FC<{ label: string; onCommit: (next: string) => void; - placeholder: string; - className: string; -}> = ({ label, onCommit, placeholder, className }) => { + onClose: () => void; +}> = ({ label, onCommit, onClose }) => { + const _ = useTranslation(); const [draft, setDraft] = useState(label); + const inputRef = useRef(null); + const popoverRef = useRef(null); useEffect(() => { - setDraft(label); - }, [label]); + inputRef.current?.focus(); + inputRef.current?.select(); + }, []); + + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (popoverRef.current && !popoverRef.current.contains(e.target as Node)) { + commit(); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [draft]); const commit = () => { const trimmed = draft.trim(); if (trimmed !== label) onCommit(trimmed); + onClose(); }; return ( - setDraft(e.target.value)} - onBlur={commit} - onKeyDown={(e) => { - if (e.key === 'Enter') (e.currentTarget as HTMLInputElement).blur(); - }} - placeholder={placeholder} - maxLength={20} - className={className} - title={draft} - /> +
+ setDraft(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') commit(); + if (e.key === 'Escape') onClose(); + }} + placeholder={_('Name')} + maxLength={20} + className='bg-base-100 w-20 text-center text-xs outline-none' + /> +
+ ); +}; + +/** + * A color circle that shows label on hover and opens a label editor on click. + */ +const ColorCircle: React.FC<{ + hex: string; + label: string; + highlightPreviewStyle: React.CSSProperties; + onLabelCommit: (next: string) => void; +}> = ({ hex, label, highlightPreviewStyle, onLabelCommit }) => { + const [editing, setEditing] = useState(false); + + return ( +
+ {editing && ( + setEditing(false)} /> + )} +
setEditing(true)} + > +
+
+
); }; @@ -77,7 +128,6 @@ const HighlightColorsEditor: React.FC = ({ }) => { const _ = useTranslation(); const [newColor, setNewColor] = useState('#808080'); - const [newColorLabel, setNewColorLabel] = useState(''); const highlightPreviewStyle: React.CSSProperties = { opacity: highlightOpacity, @@ -113,9 +163,7 @@ const HighlightColorsEditor: React.FC = ({ const hex = normalizeHex(newColor); if (!hex.startsWith('#')) return; if (userHighlightColors.some((entry) => entry.hex === hex)) return; - const label = newColorLabel.trim(); - onUserHighlightColorsChange([...userHighlightColors, label ? { hex, label } : { hex }]); - setNewColorLabel(''); + onUserHighlightColorsChange([...userHighlightColors, { hex }]); }; const handleDeleteUserColor = (hex: string) => { @@ -127,7 +175,6 @@ const HighlightColorsEditor: React.FC = ({ const oldKey = normalizeHex(oldHex); const newKey = normalizeHex(newHex); if (oldKey === newKey) return; - // Drop the rename if it collides with another existing color. if (userHighlightColors.some((entry) => entry.hex === newKey)) return; onUserHighlightColorsChange( userHighlightColors.map((entry) => @@ -144,26 +191,17 @@ const HighlightColorsEditor: React.FC = ({

{_('Highlight Colors')}

-
+
{DEFAULT_HIGHLIGHT_COLORS.map((color, index, array) => { const position = index === 0 ? 'left' : index === array.length - 1 ? 'right' : 'center'; return ( -
- + handleDefaultLabelChange(color, next)} - placeholder={_('Name')} - className='input input-xs bg-base-100 border-base-200/75 h-6 w-full min-w-0 max-w-24 text-center text-xs' + highlightPreviewStyle={highlightPreviewStyle} + onLabelCommit={(next) => handleDefaultLabelChange(color, next)} /> -
-
-
= ({
-
+
0 && 'mb-4', + )} + > {_('Custom Colors')} ({userHighlightColors.length}/{MAX_USER_HIGHLIGHT_COLORS}) -
+
= ({ pickerPosition='right' onChange={setNewColor} /> - setNewColorLabel(e.target.value)} - placeholder={_('Name')} - maxLength={20} - className='input input-xs bg-base-100 border-base-200/75 h-6 w-24 text-center text-xs' - />