diff --git a/.husky/pre-push b/.husky/pre-push index e703b2b..ff7b2f1 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,6 +1,11 @@ # pre-push: 严格检查,阻塞有问题的代码推送到远程 -# 类型检查 +# 类型检查(全项目,因为类型错误可能跨文件传播) npx tsc --noEmit -# ESLint 严格检查(不允许 warning) -npx eslint . --max-warnings=0 +# ESLint 严格检查(仅检查本次推送的变更文件,不阻塞不相关的旧代码) +# 新分支无 upstream 时,回退到与 origin/main 对比 +BASE=$(git rev-parse --verify @{upstream} 2>/dev/null && echo "@{upstream}" || echo "origin/main") +CHANGED_FILES=$(git diff --name-only --diff-filter=d "$BASE"...HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx' '*.mjs') +if [ -n "$CHANGED_FILES" ]; then + echo "$CHANGED_FILES" | xargs npx eslint --max-warnings=0 +fi diff --git a/src/pages/QrCode/__tests__/index.test.tsx b/src/pages/QrCode/__tests__/index.test.tsx index eca7ae1..dc415bb 100644 --- a/src/pages/QrCode/__tests__/index.test.tsx +++ b/src/pages/QrCode/__tests__/index.test.tsx @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import QrCodePage from '../index'; vi.mock('lucide-react', async (importOriginal) => { @@ -102,7 +102,7 @@ describe('QrCodePage', () => { expect(generateButton).not.toBeDisabled(); }); - it('点击生成按钮应该切换到预览态', () => { + it('点击生成按钮应该切换到预览态', async () => { render(); // 输入文本 @@ -114,14 +114,16 @@ describe('QrCodePage', () => { fireEvent.click(generateButton); // 验证切换到预览态 - expect(screen.getByText('原始文本')).toBeInTheDocument(); - expect(screen.getByText('编辑')).toBeInTheDocument(); - expect(screen.queryByText('生成二维码')).not.toBeInTheDocument(); - // 预览态应该显示二维码 - expect(screen.getByTestId('qr-code-preview')).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText('原始文本')).toBeInTheDocument(); + expect(screen.getByText('编辑')).toBeInTheDocument(); + expect(screen.queryByText('生成二维码')).not.toBeInTheDocument(); + // 预览态应该显示二维码 + expect(screen.getByTestId('qr-code-preview')).toBeInTheDocument(); + }); }); - it('预览态应该显示截断的文本', () => { + it('预览态应该显示截断的文本', async () => { render(); // 输入长文本 @@ -135,10 +137,12 @@ describe('QrCodePage', () => { // 验证显示截断的文本(80字符 + "...") const truncatedText = longText.slice(0, 80) + '...'; - expect(screen.getByText(truncatedText)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(truncatedText)).toBeInTheDocument(); + }); }); - it('预览态应该显示完整的短文本', () => { + it('预览态应该显示完整的短文本', async () => { render(); // 输入短文本 @@ -150,10 +154,12 @@ describe('QrCodePage', () => { fireEvent.click(screen.getByText('生成二维码')); // 验证显示完整文本 - expect(screen.getByText(shortText)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(shortText)).toBeInTheDocument(); + }); }); - it('点击编辑按钮应该返回输入态', () => { + it('点击编辑按钮应该返回输入态', async () => { render(); // 输入文本并生成 @@ -161,6 +167,11 @@ describe('QrCodePage', () => { fireEvent.change(textarea, { target: { value: 'https://example.com' } }); fireEvent.click(screen.getByText('生成二维码')); + // 等待生成完成 + await waitFor(() => { + expect(screen.getByText('编辑')).toBeInTheDocument(); + }); + // 点击编辑按钮 fireEvent.click(screen.getByText('编辑')); @@ -172,7 +183,7 @@ describe('QrCodePage', () => { expect(screen.queryByTestId('qr-code-preview')).not.toBeInTheDocument(); }); - it('返回编辑态应该保留上次输入的内容', () => { + it('返回编辑态应该保留上次输入的内容', async () => { render(); // 输入文本并生成 @@ -180,6 +191,11 @@ describe('QrCodePage', () => { fireEvent.change(textarea, { target: { value: 'https://example.com' } }); fireEvent.click(screen.getByText('生成二维码')); + // 等待生成完成 + await waitFor(() => { + expect(screen.getByText('编辑')).toBeInTheDocument(); + }); + // 点击编辑按钮 fireEvent.click(screen.getByText('编辑')); diff --git a/src/pages/QrCode/hooks/useQrCode.ts b/src/pages/QrCode/hooks/useQrCode.ts index a88b019..8ec8eff 100644 --- a/src/pages/QrCode/hooks/useQrCode.ts +++ b/src/pages/QrCode/hooks/useQrCode.ts @@ -165,27 +165,31 @@ export function useQrCode(): QrCodeContextValue { return; } + // 先设置 loading 状态,让 React 渲染加载动画 setGeneratorState((prev) => ({ ...prev, generating: true, inputError: '' })); - const qrCodeDataUrl = generateQrCodeDataUrl(text); + // 延迟到下一帧生成,确保 loading 状态先被渲染显示 + setTimeout(() => { + const qrCodeDataUrl = generateQrCodeDataUrl(text); + + if (!qrCodeDataUrl) { + setGeneratorState((prev) => ({ + ...prev, + generating: false, + inputError: t('qrCode:generateError'), + })); + toast.error(t('qrCode:generateError')); + return; + } - if (!qrCodeDataUrl) { setGeneratorState((prev) => ({ ...prev, + step: 'preview', + savedText: text, + qrCodeDataUrl, generating: false, - inputError: t('qrCode:generateError'), })); - toast.error(t('qrCode:generateError')); - return; - } - - setGeneratorState((prev) => ({ - ...prev, - step: 'preview', - savedText: text, - qrCodeDataUrl, - generating: false, - })); + }, 0); }, [generatorState.textToEncode, t]); /** 返回编辑态,保留上次输入内容 */ diff --git a/src/utils/contextMenu.ts b/src/utils/contextMenu.ts index f394619..eac11b9 100644 --- a/src/utils/contextMenu.ts +++ b/src/utils/contextMenu.ts @@ -103,8 +103,8 @@ export function parseContextMenuClick( ): ParseResult { const featureKey = getMenuPageType(menuItemId); - // 处理图片 URL(右键点击图片时) - if (info.srcUrl) { + // 处理图片 URL(仅 qrCode-image 菜单项,右键点击图片时) + if (menuItemId === 'qrCode-image' && info.srcUrl) { return { success: true, data: { featureKey, payload: info.srcUrl },