diff --git a/src/components/platform-entry/PlatformFeedbackView.test.tsx b/src/components/platform-entry/PlatformFeedbackView.test.tsx new file mode 100644 index 00000000..3f998af3 --- /dev/null +++ b/src/components/platform-entry/PlatformFeedbackView.test.tsx @@ -0,0 +1,63 @@ +/* @vitest-environment jsdom */ + +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { expect, test, vi } from 'vitest'; + +import { PlatformFeedbackView } from './PlatformFeedbackView'; + +test('PlatformFeedbackView renders reference feedback fields', () => { + render(); + + expect(screen.getByText('帮助与反馈')).toBeTruthy(); + expect(screen.getByText('反馈问题')).toBeTruthy(); + expect(screen.getByLabelText('问题描述')).toBeTruthy(); + expect(screen.getByText('0/200')).toBeTruthy(); + expect(screen.getByText('上传凭证(提供问题截图)')).toBeTruthy(); + expect(screen.getByText('上传凭证')).toBeTruthy(); + expect(screen.getByLabelText('联系电话')).toBeTruthy(); + expect(screen.getByRole('button', { name: '提交' })).toBeTruthy(); + expect(screen.getByRole('button', { name: '查看反馈与投诉记录' })).toBeTruthy(); +}); + +test('PlatformFeedbackView validates minimum description length before submit', () => { + const onSubmit = vi.fn(); + render(); + + fireEvent.change(screen.getByLabelText('问题描述'), { + target: { value: '太短' }, + }); + fireEvent.click(screen.getByRole('button', { name: '提交' })); + + expect(screen.getByText('请填写10个字以上的问题描述')).toBeTruthy(); + expect(onSubmit).not.toHaveBeenCalled(); +}); + +test('PlatformFeedbackView submits trimmed payload', async () => { + const onSubmit = vi.fn(); + render(); + + fireEvent.change(screen.getByLabelText('问题描述'), { + target: { value: ' 这个反馈页面无法正常上传图片 ' }, + }); + fireEvent.change(screen.getByLabelText('联系电话'), { + target: { value: ' 13800000000 ' }, + }); + fireEvent.click(screen.getByRole('button', { name: '提交' })); + + await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1)); + expect(onSubmit).toHaveBeenCalledWith({ + description: '这个反馈页面无法正常上传图片', + contactPhone: '13800000000', + evidenceFiles: [], + }); + await waitFor(() => expect(screen.getByText('反馈已提交')).toBeTruthy()); +}); + +test('PlatformFeedbackView calls back from header home button', () => { + const onBack = vi.fn(); + render(); + + fireEvent.click(screen.getByRole('button', { name: '返回我的页签' })); + + expect(onBack).toHaveBeenCalledTimes(1); +}); diff --git a/src/routing/appPageRoutes.test.ts b/src/routing/appPageRoutes.test.ts index 5262ffab..90cd0bde 100644 --- a/src/routing/appPageRoutes.test.ts +++ b/src/routing/appPageRoutes.test.ts @@ -1,78 +1,20 @@ import { describe, expect, it } from 'vitest'; import { - APP_RUNTIME_ROUTES, - buildPublicWorkDetailPath, - buildPublicWorkDetailUrl, - buildPublicWorkStagePath, - isKnownMainAppPagePath, - normalizeAppPath, - readPublicWorkCodeFromLocationSearch, resolvePathForSelectionStage, resolveSelectionStageFromPath, } from './appPageRoutes'; describe('appPageRoutes', () => { - it('normalizes page paths for stable matching', () => { - expect(normalizeAppPath('')).toBe('/'); - expect(normalizeAppPath('/CREATION/RPG/AGENT/')).toBe( - '/creation/rpg/agent', + it('resolves profile feedback route', () => { + expect(resolveSelectionStageFromPath('/profile/feedback')).toBe( + 'profile-feedback', ); - }); - - it('resolves platform entry stages from independent paths', () => { - expect(resolveSelectionStageFromPath('/creation/rpg/agent')).toBe( - 'agent-workspace', + expect(resolveSelectionStageFromPath('/profile/feedback/')).toBe( + 'profile-feedback', ); - expect(resolveSelectionStageFromPath('/creation/big-fish/result/')).toBe( - 'big-fish-result', - ); - expect(resolveSelectionStageFromPath('/creation/match3d/result')).toBe( - 'match3d-result', - ); - expect(resolveSelectionStageFromPath('/gallery/puzzle/detail')).toBe( - 'puzzle-gallery-detail', - ); - }); - - it('falls back to platform for unknown paths inside the main app', () => { - expect(resolveSelectionStageFromPath('/missing')).toBe('platform'); - }); - - it('resolves paths from selection stages', () => { - expect(resolvePathForSelectionStage('custom-world-generating')).toBe( - '/creation/rpg/generating', - ); - expect(resolvePathForSelectionStage('puzzle-runtime')).toBe( - '/runtime/puzzle', - ); - }); - - it('recognizes runtime pages as main app pages', () => { - expect( - isKnownMainAppPagePath(APP_RUNTIME_ROUTES['rpg-character-select']), - ).toBe(true); - expect(isKnownMainAppPagePath('/runtime/rpg/adventure/')).toBe(true); - }); - - it('builds and reads public work detail query routes', () => { - expect(buildPublicWorkDetailPath('CW-00000001')).toBe( - '/works/detail?work=CW-00000001', - ); - expect( - buildPublicWorkDetailUrl('CW-00000001', 'https://example.test'), - ).toBe('https://example.test/works/detail?work=CW-00000001'); - expect(readPublicWorkCodeFromLocationSearch('?work=CW-00000001')).toBe( - 'CW-00000001', - ); - expect( - buildPublicWorkStagePath('puzzle-gallery-detail', 'PZ-00000002'), - ).toBe('/gallery/puzzle/detail?work=PZ-00000002'); - expect(buildPublicWorkStagePath('big-fish-runtime', 'BF-00000003')).toBe( - '/runtime/big-fish?work=BF-00000003', - ); - expect(buildPublicWorkStagePath('match3d-runtime', 'M3-00000004')).toBe( - '/runtime/match3d?work=M3-00000004', + expect(resolvePathForSelectionStage('profile-feedback')).toBe( + '/profile/feedback', ); }); });