修复泥点消耗刷新不及时 (#138)
refactor: - 主站复用game agent的zustand store fix: - 修复旧请求覆盖问题 - 修复账号切换问题 - 主站原本已经每 4 秒轮询 external generation 任务状态, 在这里补充触发余额刷新的时机 --------- Co-authored-by: 段舒康 <kdletters@qq.com> Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/138 Co-authored-by: 王德宇 <kvtodev@outlook.com> Co-committed-by: 王德宇 <kvtodev@outlook.com>
This commit was merged in pull request #138.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { act, render, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { StrictMode, useEffect, useState } from 'react';
|
||||
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import type { AuthSessionSummary, AuthUser } from '../../services/authService';
|
||||
@@ -16,6 +16,26 @@ import { useAuthUi } from './AuthUiContext';
|
||||
|
||||
const browserReloadMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
const walletLifecycleMocks = vi.hoisted(() => ({
|
||||
usePlatformWalletLifecycle: vi.fn(),
|
||||
}));
|
||||
|
||||
function createMemoryStorage(): Storage {
|
||||
const values = new Map<string, string>();
|
||||
return {
|
||||
get length() {
|
||||
return values.size;
|
||||
},
|
||||
clear: () => values.clear(),
|
||||
getItem: (key) => values.get(key) ?? null,
|
||||
key: (index) => Array.from(values.keys())[index] ?? null,
|
||||
removeItem: (key) => values.delete(key),
|
||||
setItem: (key, value) => values.set(key, String(value)),
|
||||
};
|
||||
}
|
||||
|
||||
const memoryLocalStorage = createMemoryStorage();
|
||||
|
||||
const authMocks = vi.hoisted(() => ({
|
||||
authEntry: vi.fn(),
|
||||
changePassword: vi.fn(),
|
||||
@@ -77,6 +97,8 @@ vi.mock('../../services/authService', () => ({
|
||||
startWechatLogin: authMocks.startWechatLogin,
|
||||
}));
|
||||
|
||||
vi.mock('../../stores/usePlatformWalletStore', () => walletLifecycleMocks);
|
||||
|
||||
const hostBridgeMocks = vi.hoisted(() => ({
|
||||
getHostRuntime: vi.fn(() => ({
|
||||
kind: 'browser',
|
||||
@@ -136,6 +158,10 @@ const mockUser: AuthUser = {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
configurable: true,
|
||||
value: memoryLocalStorage,
|
||||
});
|
||||
window.localStorage.clear();
|
||||
window.history.replaceState(null, '', '/');
|
||||
setAuthGateReloadForTest(vi.fn());
|
||||
@@ -381,6 +407,32 @@ test('auth gate keeps a valid local token login when refresh rotation fails afte
|
||||
expect(authMocks.getCurrentAuthUser).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('auth root binds the single wallet lifecycle under StrictMode', async () => {
|
||||
authMocks.getStoredAccessToken.mockReturnValue('jwt-existing-token');
|
||||
authMocks.refreshStoredAccessToken.mockRejectedValue(
|
||||
new Error('refresh cookie 失效'),
|
||||
);
|
||||
authMocks.getCurrentAuthUser.mockResolvedValue({
|
||||
user: mockUser,
|
||||
availableLoginMethods: ['phone'],
|
||||
});
|
||||
|
||||
render(
|
||||
<StrictMode>
|
||||
<AuthGate>
|
||||
<LogoutStateProbe />
|
||||
</AuthGate>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
expect(await screen.findByText('当前用户:测试玩家')).toBeTruthy();
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
walletLifecycleMocks.usePlatformWalletLifecycle,
|
||||
).toHaveBeenCalledWith('user-1', true);
|
||||
});
|
||||
});
|
||||
|
||||
test('auth gate does not auto-create a guest account when dev guest switch is not explicitly enabled', async () => {
|
||||
authMocks.getAuthLoginOptions.mockResolvedValue({
|
||||
availableLoginMethods: [],
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
reloadHostWebView,
|
||||
requestHostLogin,
|
||||
} from '../../services/host-bridge/hostBridge';
|
||||
import { usePlatformWalletLifecycle } from '../../stores/usePlatformWalletStore';
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { AccountModal } from './AccountModal';
|
||||
import { AuthUiContext, type PlatformSettingsSection } from './AuthUiContext';
|
||||
@@ -117,10 +118,7 @@ function normalizeAvailableLoginMethods(
|
||||
|
||||
// 登录面板的核心入口必须稳定展示,login-options 只补充微信等环境相关入口。
|
||||
return Array.from(
|
||||
new Set<AuthLoginMethod>([
|
||||
...REQUIRED_LOGIN_METHODS,
|
||||
...normalizedMethods,
|
||||
]),
|
||||
new Set<AuthLoginMethod>([...REQUIRED_LOGIN_METHODS, ...normalizedMethods]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -192,10 +190,7 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
}
|
||||
|
||||
const markAuthStateReloadIfChanged = useCallback(
|
||||
(
|
||||
nextUser: AuthUser | null,
|
||||
options: { reloadOnChange?: boolean } = {},
|
||||
) => {
|
||||
(nextUser: AuthUser | null, options: { reloadOnChange?: boolean } = {}) => {
|
||||
const nextHasUser = Boolean(nextUser);
|
||||
const previousHasUser = lastStableAuthPresenceRef.current;
|
||||
if (previousHasUser === null) {
|
||||
@@ -204,23 +199,23 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
}
|
||||
|
||||
lastStableAuthPresenceRef.current = nextHasUser;
|
||||
if (
|
||||
previousHasUser !== nextHasUser &&
|
||||
options.reloadOnChange !== false
|
||||
) {
|
||||
if (previousHasUser !== nextHasUser && options.reloadOnChange !== false) {
|
||||
pendingAuthStateReloadRef.current = true;
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const activateReadyUser = useCallback((nextUser: AuthUser) => {
|
||||
// 受保护业务 hook 只在 readyUser 暴露后启动,必须先保证请求层能带 Bearer token。
|
||||
authHydrateVersionRef.current += 1;
|
||||
markAuthStateReloadIfChanged(nextUser);
|
||||
setUser(nextUser);
|
||||
setStatus('ready');
|
||||
}, [markAuthStateReloadIfChanged]);
|
||||
const activateReadyUser = useCallback(
|
||||
(nextUser: AuthUser) => {
|
||||
// 受保护业务 hook 只在 readyUser 暴露后启动,必须先保证请求层能带 Bearer token。
|
||||
authHydrateVersionRef.current += 1;
|
||||
markAuthStateReloadIfChanged(nextUser);
|
||||
setUser(nextUser);
|
||||
setStatus('ready');
|
||||
},
|
||||
[markAuthStateReloadIfChanged],
|
||||
);
|
||||
|
||||
const clearLocalAuthenticatedState = useCallback(
|
||||
(options: { reloadOnChange?: boolean } = {}) => {
|
||||
@@ -687,6 +682,11 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
],
|
||||
);
|
||||
|
||||
usePlatformWalletLifecycle(
|
||||
readyUser?.id ?? null,
|
||||
status === 'ready' && Boolean(readyUser),
|
||||
);
|
||||
|
||||
if (status === 'checking' && !canKeepPlatformContentMounted) {
|
||||
return (
|
||||
<div
|
||||
@@ -969,7 +969,11 @@ export function AuthGate({ children }: AuthGateProps) {
|
||||
const registrationInviteCode =
|
||||
pendingInviteCode || readInviteCodeFromLocation();
|
||||
const response = registrationInviteCode
|
||||
? await loginWithPhoneCode(phone, code, registrationInviteCode)
|
||||
? await loginWithPhoneCode(
|
||||
phone,
|
||||
code,
|
||||
registrationInviteCode,
|
||||
)
|
||||
: await loginWithPhoneCode(phone, code);
|
||||
const autoRedeemedInvite = response.referral?.ok === true;
|
||||
setStoredLastLoginPhone(phone);
|
||||
|
||||
Reference in New Issue
Block a user