From 27e64cf7708fe7e8db1a7238cd4920c164c6344e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=90=E5=83=A7=E6=B4=97=E5=8F=91=E7=94=A8=E9=A3=98?= =?UTF-8?q?=E6=9F=94?= <2906266135@qq.com> Date: Fri, 14 Aug 2026 10:26:04 +0800 Subject: [PATCH 1/3] fix(schedule): restore calendar refresh and refine account header --- .../pr-calendar-account-header-mobile.png | Bin 0 -> 33754 bytes frontend/src/app/AppRoot.tsx | 90 ++++-------- .../presentation/ScheduleCalendarScreen.tsx | 133 ++++++++++++++++-- frontend/src/screens/HomeScreen.tsx | 9 ++ frontend/tests/unit/app/AppRoot.test.tsx | 33 ++++- .../ScheduleCalendarScreen.test.tsx | 83 ++++++++++- .../tests/unit/screens/HomeScreen.test.tsx | 90 ++++++++++++ 7 files changed, 357 insertions(+), 81 deletions(-) create mode 100644 docs/pr-assets/pr-calendar-account-header-mobile.png create mode 100644 frontend/tests/unit/screens/HomeScreen.test.tsx diff --git a/docs/pr-assets/pr-calendar-account-header-mobile.png b/docs/pr-assets/pr-calendar-account-header-mobile.png new file mode 100644 index 0000000000000000000000000000000000000000..08637f896e3454c229c2724affa6d2b691067e0e GIT binary patch literal 33754 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 h)~)-0zdCnsNs~<@dp>i|g1@ - - - 账号:{username} - - void handleSignOut()} - style={({ pressed }) => [ - styles.signOutButton, - pressed && !isSigningOut && styles.signOutButtonPressed, - ]} - > - {isSigningOut ? '正在退出…' : '退出登录'} - - - - {currentLoadState?.status === 'ready' && - scheduleService && - pushToTalkApplication && - continuousApplication ? ( - - ) : ( - - - {currentLoadState?.status === 'error' ? '本地日程存储初始化失败' : '正在准备日程'} - - {currentLoadState?.status === 'error' ? ( - - 重试 - - ) : null} - - )} - + {currentLoadState?.status === 'ready' && + scheduleService && + pushToTalkApplication && + continuousApplication ? ( + + ) : ( + + + {currentLoadState?.status === 'error' ? '本地日程存储初始化失败' : '正在准备日程'} + + {currentLoadState?.status === 'error' ? ( + + 重试 + + ) : null} + + )} ); } const styles = StyleSheet.create({ account: { color: colors.mutedText, fontSize: 16, marginTop: spacing.sm }, - accountBar: { - alignItems: 'center', - backgroundColor: colors.surface, - borderBottomColor: colors.border, - borderBottomWidth: 1, - flexDirection: 'row', - gap: spacing.md, - justifyContent: 'space-between', - paddingHorizontal: spacing.lg, - paddingVertical: spacing.sm, - }, - accountIdentity: { color: colors.mutedText, flex: 1, fontSize: 14 }, - authenticatedContent: { flex: 1 }, authenticatedRoute: { backgroundColor: colors.background, flex: 1 }, authenticatedScreen: { alignItems: 'center', @@ -287,14 +258,5 @@ const styles = StyleSheet.create({ paddingVertical: spacing.sm, }, retryText: { color: colors.onPrimary, fontWeight: '700' }, - signOutButton: { - borderColor: colors.border, - borderRadius: 8, - borderWidth: 1, - paddingHorizontal: spacing.md, - paddingVertical: spacing.sm, - }, - signOutButtonPressed: { opacity: 0.7 }, - signOutText: { color: colors.text, fontSize: 14, fontWeight: '600' }, title: { color: colors.text, fontSize: 22, fontWeight: '700' }, }); diff --git a/frontend/src/features/schedule/presentation/ScheduleCalendarScreen.tsx b/frontend/src/features/schedule/presentation/ScheduleCalendarScreen.tsx index ef3059b..30ad8ef 100644 --- a/frontend/src/features/schedule/presentation/ScheduleCalendarScreen.tsx +++ b/frontend/src/features/schedule/presentation/ScheduleCalendarScreen.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; +import Svg, { Path } from 'react-native-svg'; import { colors, spacing } from '../../../shared/ui/theme'; import type { @@ -20,22 +21,32 @@ const SELECTED_DATE_FORMATTER = new Intl.DateTimeFormat('zh-CN', { weekday: 'long', }); -export function ScheduleCalendarScreen({ - service, - accountId, - timezone, - refreshSignal, -}: { +interface ScheduleCalendarScreenProps { service: ScheduleCalendarReadService; accountId: string; timezone: string; + username: string; + onSignOut: () => void | Promise; + isSigningOut?: boolean; /** 外部触发刷新用(比如语音写完一条日程);变化即重取,不用管具体数值。 */ refreshSignal?: number; -}) { +} + +export function ScheduleCalendarScreen({ + service, + accountId, + timezone, + username, + onSignOut, + isSigningOut = false, + refreshSignal, +}: ScheduleCalendarScreenProps) { const calendar = useScheduleCalendar(service, accountId, timezone, undefined, refreshSignal); const [selectedOccurrence, setSelectedOccurrence] = useState(null); const [selectedLocation, setSelectedLocation] = useState(null); const selectedLabel = SELECTED_DATE_FORMATTER.format(calendar.selectedDate); + const displayUsername = username.trim() || '用户'; + const avatarInitial = Array.from(displayUsername)[0]?.toLocaleUpperCase() ?? '用'; return ( @@ -46,8 +57,45 @@ export function ScheduleCalendarScreen({ > - 我的日程 - {selectedLabel} + + 我的日程 + + + + {avatarInitial} + + + {displayUsername} + + + void onSignOut()} + style={({ pressed }) => [ + styles.signOutButton, + pressed && !isSigningOut && styles.signOutButtonPressed, + ]} + > + {isSigningOut ? ( + + ) : ( + + )} + + + + + {selectedLabel} + + + + ); +} + const styles = StyleSheet.create({ + accountActions: { + alignItems: 'center', + flex: 1, + flexDirection: 'row', + gap: spacing.sm, + justifyContent: 'flex-end', + marginLeft: spacing.sm, + maxWidth: 240, + minWidth: 0, + }, agenda: { paddingHorizontal: spacing.md, paddingTop: spacing.xl }, + avatar: { + alignItems: 'center', + backgroundColor: colors.accent, + borderRadius: 999, + height: 28, + justifyContent: 'center', + width: 28, + }, + avatarText: { color: colors.text, fontSize: 12, fontWeight: '800' }, center: { alignItems: 'center', backgroundColor: colors.surface, @@ -175,6 +256,12 @@ const styles = StyleSheet.create({ paddingHorizontal: spacing.lg, paddingTop: spacing.xl, }, + headerTop: { + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + minWidth: 0, + }, locationSection: { borderTopColor: colors.border, borderTopWidth: StyleSheet.hairlineWidth, @@ -199,6 +286,34 @@ const styles = StyleSheet.create({ paddingBottom: 14, }, sectionTitle: { color: colors.text, fontSize: 20, fontWeight: '800' }, + signOutButton: { + alignItems: 'center', + backgroundColor: colors.surface, + borderColor: colors.border, + borderRadius: 18, + borderWidth: 1, + flexShrink: 0, + height: 36, + justifyContent: 'center', + width: 36, + }, + signOutButtonPressed: { opacity: 0.62 }, stateText: { color: colors.mutedText }, title: { color: colors.text, fontSize: 28, fontWeight: '800', lineHeight: 34, marginTop: 4 }, + userPill: { + alignItems: 'center', + backgroundColor: colors.surface, + borderColor: colors.border, + borderRadius: 18, + borderWidth: 1, + flexDirection: 'row', + flexShrink: 1, + gap: spacing.sm, + maxWidth: 196, + minWidth: 0, + paddingHorizontal: 4, + paddingRight: 10, + paddingVertical: 3, + }, + username: { color: colors.text, flexShrink: 1, fontSize: 13, fontWeight: '700', minWidth: 0 }, }); diff --git a/frontend/src/screens/HomeScreen.tsx b/frontend/src/screens/HomeScreen.tsx index cf3725d..018344b 100644 --- a/frontend/src/screens/HomeScreen.tsx +++ b/frontend/src/screens/HomeScreen.tsx @@ -12,7 +12,10 @@ interface HomeScreenProps { continuousApplication: AssistantApplicationPort; scheduleService: ScheduleCalendarReadService; accountId: string; + isSigningOut: boolean; + onSignOut: () => Promise; timezone: string; + username: string; } /** @@ -25,7 +28,10 @@ export function HomeScreen({ continuousApplication, scheduleService, accountId, + isSigningOut, + onSignOut, timezone, + username, }: HomeScreenProps) { const { lastAppliedCommand: pttCommand } = useAssistantConversation(pushToTalkApplication); const { lastAppliedCommand: callCommand } = useAssistantConversation(continuousApplication); @@ -54,9 +60,12 @@ export function HomeScreen({ ({ SqliteScheduleClientService: jest.fn(), })); jest.mock('../../../src/features/schedule/presentation/ScheduleCalendarScreen', () => ({ - ScheduleCalendarScreen: () => { - const { Text } = jest.requireActual('react-native') as typeof import('react-native'); - return 日程日历; + ScheduleCalendarScreen: ({ + isSigningOut, + onSignOut, + username, + }: { + isSigningOut: boolean; + onSignOut: () => Promise; + username: string; + }) => { + const { Pressable, Text, View } = jest.requireActual( + 'react-native', + ) as typeof import('react-native'); + return ( + + 日程日历 + {username} + void onSignOut()} + /> + + ); }, })); // 语音助手这几个真实实现都要接原生模块(麦克风/播放/定位),测试环境没有对应的 @@ -95,7 +116,8 @@ describe('AppRoot', () => { }); await waitFor(() => expect(screen.getByText('日程日历')).toBeTruthy()); - expect(screen.getByText('账号:timeflow_user')).toBeTruthy(); + expect(screen.getByText('timeflow_user')).toBeTruthy(); + expect(screen.queryByText(/账号:/)).toBeNull(); expect(screen.queryByText(/acc_001/)).toBeNull(); expect(screen.queryByText('登录或注册')).toBeNull(); expect(screen.queryByText('opaque-token')).toBeNull(); @@ -110,7 +132,8 @@ describe('AppRoot', () => { }); render(); - await screen.findByText('账号:restored_user'); + await screen.findByText('restored_user'); + expect(screen.queryByText(/账号:/)).toBeNull(); expect(screen.queryByText(/acc_internal_001/)).toBeNull(); expect(screen.queryByText('opaque-token')).toBeNull(); }); diff --git a/frontend/tests/unit/features/schedule/presentation/ScheduleCalendarScreen.test.tsx b/frontend/tests/unit/features/schedule/presentation/ScheduleCalendarScreen.test.tsx index 685214a..7c2f698 100644 --- a/frontend/tests/unit/features/schedule/presentation/ScheduleCalendarScreen.test.tsx +++ b/frontend/tests/unit/features/schedule/presentation/ScheduleCalendarScreen.test.tsx @@ -1,5 +1,6 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react-native'; import { describe, expect, it, jest } from '@jest/globals'; +import { StyleSheet } from 'react-native'; import type { ScheduleCalendarReadService } from '../../../../../src/features/schedule/application'; import { ScheduleCalendarScreen } from '../../../../../src/features/schedule/presentation/ScheduleCalendarScreen'; @@ -33,7 +34,13 @@ describe('ScheduleCalendarScreen location schedules', () => { const service = createService(); const accountId = 'internal-account-id-not-for-display'; render( - , + {}} + service={service} + timezone="Asia/Shanghai" + username="Sarah" + />, ); await waitFor(() => expect(service.getSchedulesByRange).toHaveBeenCalled()); @@ -44,10 +51,74 @@ describe('ScheduleCalendarScreen location schedules', () => { expect(screen.queryByText(accountId)).toBeNull(); }); + it('reloads calendar queries when refreshSignal changes', async () => { + const service = createService(); + const props = { + accountId: 'account-a', + onSignOut: () => {}, + service, + timezone: 'Asia/Shanghai', + username: 'Sarah', + }; + const view = render(); + + await waitFor(() => expect(service.getSchedulesByRange).toHaveBeenCalledTimes(1)); + await waitFor(() => expect(service.getLocationSchedules).toHaveBeenCalledTimes(1)); + + view.rerender(); + + await waitFor(() => expect(service.getSchedulesByRange).toHaveBeenCalledTimes(2)); + await waitFor(() => expect(service.getLocationSchedules).toHaveBeenCalledTimes(2)); + expect(service.getSchedulesByRange).toHaveBeenLastCalledWith( + expect.objectContaining({ accountId: 'account-a' }), + ); + expect(service.getLocationSchedules).toHaveBeenLastCalledWith({ accountId: 'account-a' }); + }); + + it('renders compact account controls, truncates a long username, and signs out', async () => { + const service = createService(); + const onSignOut = jest.fn<() => void>(); + const username = 'zhangsan-with-an-extremely-long-account-name'; + render( + , + ); + + await waitFor(() => expect(service.getSchedulesByRange).toHaveBeenCalled()); + expect(screen.getByText('我的日程')).toBeTruthy(); + expect(screen.getByText('Z')).toBeTruthy(); + expect(screen.getByText(username)).toBeTruthy(); + expect(screen.queryByText(/账号:/)).toBeNull(); + expect(screen.getByTestId('schedule-account-username').props).toMatchObject({ + ellipsizeMode: 'tail', + numberOfLines: 1, + }); + expect( + StyleSheet.flatten(screen.getByTestId('schedule-account-username').props.style), + ).toMatchObject({ flexShrink: 1, minWidth: 0 }); + expect( + StyleSheet.flatten(screen.getByTestId('schedule-account-actions').props.style), + ).toMatchObject({ maxWidth: 240, minWidth: 0 }); + + fireEvent.press(screen.getByRole('button', { name: '退出登录' })); + expect(onSignOut).toHaveBeenCalledTimes(1); + }); + it('shows a location section that stays visible after selecting a day and changing month', async () => { const service = createService(); render( - , + {}} + service={service} + timezone="Asia/Shanghai" + username="Sarah" + />, ); await waitFor(() => expect(screen.getByText('地点提醒')).toBeTruthy()); @@ -70,7 +141,13 @@ describe('ScheduleCalendarScreen location schedules', () => { it('opens a read-only location detail with the configured fields', async () => { const service = createService(); render( - , + {}} + service={service} + timezone="Asia/Shanghai" + username="Sarah" + />, ); await waitFor(() => expect(screen.getByLabelText('公司 到公司提醒我打卡')).toBeTruthy()); diff --git a/frontend/tests/unit/screens/HomeScreen.test.tsx b/frontend/tests/unit/screens/HomeScreen.test.tsx new file mode 100644 index 0000000..7883e75 --- /dev/null +++ b/frontend/tests/unit/screens/HomeScreen.test.tsx @@ -0,0 +1,90 @@ +import { act, render, waitFor } from '@testing-library/react-native'; +import { describe, expect, it, jest } from '@jest/globals'; + +import type { AssistantApplicationPort } from '../../../src/features/assistant/application/AssistantApplication'; +import type { + AppliedCommand, + ConversationTurnState, +} from '../../../src/features/assistant/domain/ConversationTurn'; +import type { ScheduleCalendarReadService } from '../../../src/features/schedule/application'; +import { HomeScreen } from '../../../src/screens/HomeScreen'; + +jest.mock('../../../src/features/assistant/presentation/AssistantVoiceOverlay', () => ({ + AssistantVoiceOverlay: () => null, +})); + +class FakeAssistantApplication implements AssistantApplicationPort { + private command: AppliedCommand | null = null; + private readonly listeners = new Set<(state: ConversationTurnState) => void>(); + + apply(command: AppliedCommand) { + this.command = command; + for (const listener of this.listeners) listener({ phase: 'idle' }); + } + + dismissReply = async () => {}; + dispose = () => {}; + endTurn = async () => {}; + getLastAppliedCommand = () => this.command; + getReplyText = () => null; + getSoundLevel = () => null; + getState = (): ConversationTurnState => ({ phase: 'idle' }); + startTurn = async () => {}; + subscribe = (listener: (state: ConversationTurnState) => void) => { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + }; +} + +describe('HomeScreen calendar refresh', () => { + it('reloads account-scoped calendar data after a voice command is applied', async () => { + const pushToTalkApplication = new FakeAssistantApplication(); + const continuousApplication = new FakeAssistantApplication(); + const scheduleService: ScheduleCalendarReadService = { + getLocationSchedules: jest + .fn() + .mockResolvedValue([]), + getSchedulesByDay: jest + .fn() + .mockResolvedValue([]), + getSchedulesByRange: jest + .fn() + .mockResolvedValue([]), + }; + render( + {}} + pushToTalkApplication={pushToTalkApplication} + scheduleService={scheduleService} + timezone="Asia/Shanghai" + username="Sarah" + />, + ); + + await waitFor(() => expect(scheduleService.getSchedulesByRange).toHaveBeenCalledTimes(1)); + await waitFor(() => expect(scheduleService.getLocationSchedules).toHaveBeenCalledTimes(1)); + + act(() => { + pushToTalkApplication.apply({ operation: 'update_schedule', status: 'applied' }); + }); + + await waitFor(() => expect(scheduleService.getSchedulesByRange).toHaveBeenCalledTimes(2)); + await waitFor(() => expect(scheduleService.getLocationSchedules).toHaveBeenCalledTimes(2)); + + act(() => { + continuousApplication.apply({ operation: 'create_schedule', status: 'applied' }); + }); + + await waitFor(() => expect(scheduleService.getSchedulesByRange).toHaveBeenCalledTimes(3)); + await waitFor(() => expect(scheduleService.getLocationSchedules).toHaveBeenCalledTimes(3)); + expect(scheduleService.getSchedulesByRange).toHaveBeenLastCalledWith( + expect.objectContaining({ accountId: 'account-a' }), + ); + expect(scheduleService.getLocationSchedules).toHaveBeenLastCalledWith({ + accountId: 'account-a', + }); + }); +}); From 773999f7e40505f18770a2661c328883492adf1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=90=E5=83=A7=E6=B4=97=E5=8F=91=E7=94=A8=E9=A3=98?= =?UTF-8?q?=E6=9F=94?= <2906266135@qq.com> Date: Fri, 14 Aug 2026 10:58:21 +0800 Subject: [PATCH 2/3] fix(auth): keep sign-out available during database setup --- frontend/src/app/AppRoot.tsx | 22 +++++++++++++++ frontend/tests/unit/app/AppRoot.test.tsx | 36 ++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/frontend/src/app/AppRoot.tsx b/frontend/src/app/AppRoot.tsx index 9a06d9b..dcde474 100644 --- a/frontend/src/app/AppRoot.tsx +++ b/frontend/src/app/AppRoot.tsx @@ -234,6 +234,18 @@ function AuthenticatedScheduleRoute({ 重试 ) : null} + void handleSignOut()} + style={styles.loadStateSignOut} + > + + {isSigningOut ? '正在退出…' : '退出登录'} + + )} @@ -250,6 +262,16 @@ const styles = StyleSheet.create({ justifyContent: 'center', padding: spacing.xl, }, + loadStateSignOut: { + backgroundColor: colors.surface, + borderColor: colors.border, + borderRadius: 8, + borderWidth: 1, + marginTop: spacing.md, + paddingHorizontal: spacing.lg, + paddingVertical: spacing.sm, + }, + loadStateSignOutText: { color: colors.mutedText, fontWeight: '600' }, retry: { backgroundColor: colors.text, borderRadius: 8, diff --git a/frontend/tests/unit/app/AppRoot.test.tsx b/frontend/tests/unit/app/AppRoot.test.tsx index 1b1576f..2368b47 100644 --- a/frontend/tests/unit/app/AppRoot.test.tsx +++ b/frontend/tests/unit/app/AppRoot.test.tsx @@ -155,6 +155,42 @@ describe('AppRoot', () => { await waitFor(() => expect(mockedOpenTimeflowDatabase).toHaveBeenCalledTimes(2)); await waitFor(() => expect(screen.getByText('日程日历')).toBeTruthy()); + expect(screen.getByText('timeflow_user')).toBeTruthy(); + expect(screen.queryByText(/账号:/)).toBeNull(); + }); + + it('can sign out when SQLite initialization fails', async () => { + mockedOpenTimeflowDatabase.mockRejectedValue(new Error('database unavailable')); + const controller = createController({ + accountId: 'acc_001', + accessToken: 'opaque-token', + expiresAt: 200_000, + username: 'timeflow_user', + }); + render(); + + await screen.findByText('本地日程存储初始化失败'); + fireEvent.press(screen.getByRole('button', { name: '退出登录' })); + + await waitFor(() => expect(screen.getByText('登录或注册')).toBeTruthy()); + expect(controller.getViewState()).toEqual({ status: 'unauthenticated' }); + }); + + it('can sign out while SQLite initialization is pending', async () => { + mockedOpenTimeflowDatabase.mockImplementation(() => new Promise(() => undefined)); + const controller = createController({ + accountId: 'acc_001', + accessToken: 'opaque-token', + expiresAt: 200_000, + username: 'timeflow_user', + }); + render(); + + await screen.findByText('正在准备日程'); + fireEvent.press(screen.getByRole('button', { name: '退出登录' })); + + await waitFor(() => expect(screen.getByText('登录或注册')).toBeTruthy()); + expect(controller.getViewState()).toEqual({ status: 'unauthenticated' }); }); it('signs out from the authenticated account shell', async () => { From d21291809055ebf0398c1bba962f17b9f14d954e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=90=E5=83=A7=E6=B4=97=E5=8F=91=E7=94=A8=E9=A3=98?= =?UTF-8?q?=E6=9F=94?= <2906266135@qq.com> Date: Fri, 14 Aug 2026 11:33:17 +0800 Subject: [PATCH 3/3] docs: remove PR screenshot asset --- .../pr-calendar-account-header-mobile.png | Bin 33754 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/pr-assets/pr-calendar-account-header-mobile.png diff --git a/docs/pr-assets/pr-calendar-account-header-mobile.png b/docs/pr-assets/pr-calendar-account-header-mobile.png deleted file mode 100644 index 08637f896e3454c229c2724affa6d2b691067e0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33754 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 h)~)-0zdCnsNs~<@dp>i|g1@