Merge remote-tracking branch 'origin/master' into codex/editor-asset-library
# Conflicts: # docs/project-memory/shared-memory/decision-log.md
This commit is contained in:
@@ -200,6 +200,7 @@ const authServiceMocks = vi.hoisted(() => ({
|
||||
token: 'runtime-guest-token',
|
||||
expiresAt: '2099-01-01T00:00:00.000Z',
|
||||
})),
|
||||
isWechatMiniProgramWebViewRuntime: vi.fn(() => false),
|
||||
getPublicAuthUserByCode: vi.fn(
|
||||
async (publicUserCode: string): Promise<PublicUserSummary> => ({
|
||||
id: `public-user-${publicUserCode}`,
|
||||
@@ -222,6 +223,8 @@ const authServiceMocks = vi.hoisted(() => ({
|
||||
|
||||
vi.mock('../../services/authService', () => ({
|
||||
ensureRuntimeGuestToken: authServiceMocks.ensureRuntimeGuestToken,
|
||||
isWechatMiniProgramWebViewRuntime:
|
||||
authServiceMocks.isWechatMiniProgramWebViewRuntime,
|
||||
getPublicAuthUserByCode: authServiceMocks.getPublicAuthUserByCode,
|
||||
getPublicAuthUserById: authServiceMocks.getPublicAuthUserById,
|
||||
}));
|
||||
|
||||
@@ -779,6 +779,7 @@ function ProfileHomeViewHarness({
|
||||
userOverrides = {},
|
||||
activeTab = 'profile',
|
||||
profileTaskRefreshKey = 0,
|
||||
profileGenerationQueueStatus = null,
|
||||
profilePlayStats = null,
|
||||
isProfilePlayStatsOpen = false,
|
||||
}: {
|
||||
@@ -789,6 +790,7 @@ function ProfileHomeViewHarness({
|
||||
userOverrides?: Partial<AuthUser>;
|
||||
activeTab?: RpgEntryHomeViewProps['activeTab'];
|
||||
profileTaskRefreshKey?: number;
|
||||
profileGenerationQueueStatus?: RpgEntryHomeViewProps['profileGenerationQueueStatus'];
|
||||
profilePlayStats?: ProfilePlayStatsResponse | null;
|
||||
isProfilePlayStatsOpen?: boolean;
|
||||
}) {
|
||||
@@ -856,6 +858,7 @@ function ProfileHomeViewHarness({
|
||||
onSearchPublicCode={vi.fn()}
|
||||
onRechargeSuccess={onRechargeSuccess}
|
||||
profileTaskRefreshKey={profileTaskRefreshKey}
|
||||
profileGenerationQueueStatus={profileGenerationQueueStatus}
|
||||
/>
|
||||
</AuthUiContext.Provider>
|
||||
);
|
||||
@@ -871,6 +874,7 @@ function renderProfileView(
|
||||
profileStatsOptions: {
|
||||
profilePlayStats?: ProfilePlayStatsResponse | null;
|
||||
isProfilePlayStatsOpen?: boolean;
|
||||
profileGenerationQueueStatus?: RpgEntryHomeViewProps['profileGenerationQueueStatus'];
|
||||
} = {},
|
||||
) {
|
||||
return render(
|
||||
@@ -879,6 +883,9 @@ function renderProfileView(
|
||||
profileDashboardOverrides={profileDashboardOverrides}
|
||||
userOverrides={userOverrides}
|
||||
profileTaskRefreshKey={profileTaskRefreshKey}
|
||||
profileGenerationQueueStatus={
|
||||
profileStatsOptions.profileGenerationQueueStatus
|
||||
}
|
||||
profilePlayStats={profileStatsOptions.profilePlayStats}
|
||||
isProfilePlayStatsOpen={profileStatsOptions.isProfilePlayStatsOpen}
|
||||
/>,
|
||||
@@ -2596,7 +2603,11 @@ test('profile daily task shortcut reflects task progress and claim updates', asy
|
||||
await user.click(screen.getByRole('button', { name: /每日任务/u }));
|
||||
|
||||
const taskTitle = await screen.findByText('每日登录');
|
||||
const taskPanel = taskTitle.closest('.platform-subpanel') as HTMLElement;
|
||||
const taskPanel = screen
|
||||
.getByRole('button', { name: '领取' })
|
||||
.closest('.rounded-\\[1rem\\]') as HTMLElement;
|
||||
expect(taskTitle).toBeTruthy();
|
||||
expect(taskPanel).toBeTruthy();
|
||||
expect(taskPanel.className).toContain('rounded-[1rem]');
|
||||
expect(taskPanel.className).toContain('p-4');
|
||||
expect(mockGetRpgProfileTasks).toHaveBeenCalledTimes(1);
|
||||
@@ -2892,6 +2903,46 @@ test('profile stats cards are centered without update timestamp', async () => {
|
||||
await screen.findByText('1 / 1');
|
||||
});
|
||||
|
||||
test('profile page shows external generation queue status', async () => {
|
||||
renderProfileView(
|
||||
vi.fn(),
|
||||
{},
|
||||
{},
|
||||
0,
|
||||
{
|
||||
profileGenerationQueueStatus: {
|
||||
currentStatus: 'queued',
|
||||
currentProgress: 18,
|
||||
pendingCount: 6,
|
||||
runningCount: 2,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await screen.findByText('1 / 1');
|
||||
const queueRegion = screen.getByRole('region', { name: '生成队列' });
|
||||
expect(queueRegion.className).toContain(
|
||||
'platform-profile-generation-queue-card',
|
||||
);
|
||||
expect(within(queueRegion).getByText('排队中')).toBeTruthy();
|
||||
expect(within(queueRegion).getByText('排队中 18%')).toBeTruthy();
|
||||
expect(within(queueRegion).getByText('排队')).toBeTruthy();
|
||||
expect(within(queueRegion).getByText('6')).toBeTruthy();
|
||||
expect(within(queueRegion).getByText('生成')).toBeTruthy();
|
||||
expect(within(queueRegion).getByText('2')).toBeTruthy();
|
||||
const progressbar = within(queueRegion).getByRole('progressbar', {
|
||||
name: '生成队列进度',
|
||||
});
|
||||
expect(progressbar.getAttribute('aria-valuenow')).toBe('18');
|
||||
});
|
||||
|
||||
test('profile page hides external generation queue card without queue state', async () => {
|
||||
renderProfileView();
|
||||
|
||||
await screen.findByText('1 / 1');
|
||||
expect(screen.queryByRole('region', { name: '生成队列' })).toBeNull();
|
||||
});
|
||||
|
||||
test('mobile profile page matches the reference layout sections', async () => {
|
||||
mockNarrowMobileLayout();
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ import {
|
||||
ProfileStatCardSkeleton,
|
||||
} from '../platform-entry/PlatformProfilePrimitives';
|
||||
import { PlatformProfileModalShell } from '../platform-entry/PlatformProfileModalShell';
|
||||
import { PlatformProfileGenerationQueueCard } from '../platform-entry/PlatformProfileGenerationQueueCard';
|
||||
import { PlatformProfilePlayedWorksModal } from '../platform-entry/PlatformProfilePlayedWorksModal';
|
||||
import { PlatformProfileQrScannerModal } from '../platform-entry/PlatformProfileQrScannerModal';
|
||||
import { PlatformProfileRechargeModal } from '../platform-entry/PlatformProfileRechargeModal';
|
||||
@@ -133,6 +134,7 @@ import { PlatformProfileReferralModal } from '../platform-entry/PlatformProfileR
|
||||
import { PlatformProfileRewardCodeRedeemModal } from '../platform-entry/PlatformProfileRewardCodeRedeemModal';
|
||||
import { PlatformProfileTaskCenterModal } from '../platform-entry/PlatformProfileTaskCenterModal';
|
||||
import { PlatformProfileWalletLedgerModal } from '../platform-entry/PlatformProfileWalletLedgerModal';
|
||||
import type { ExternalGenerationQueueStatus } from '../platform-entry/platformExternalGenerationQueueStatusModel';
|
||||
import { getInitialPlatformDesktopLayout } from '../platform-entry/platformEntryResponsive';
|
||||
import {
|
||||
type RechargePaymentResult,
|
||||
@@ -276,6 +278,7 @@ export interface RpgEntryHomeViewProps {
|
||||
onOpenProjects?: () => void;
|
||||
onRechargeSuccess?: () => void | Promise<void>;
|
||||
profileTaskRefreshKey?: number;
|
||||
profileGenerationQueueStatus?: ExternalGenerationQueueStatus | null;
|
||||
createTabContent?: ReactNode;
|
||||
draftTabContent?: ReactNode;
|
||||
hasUnreadDraftUpdate?: boolean;
|
||||
@@ -2571,6 +2574,7 @@ export function RpgEntryHomeView({
|
||||
onOpenProjects,
|
||||
onRechargeSuccess,
|
||||
profileTaskRefreshKey = 0,
|
||||
profileGenerationQueueStatus = null,
|
||||
createTabContent,
|
||||
draftTabContent,
|
||||
hasUnreadDraftUpdate = false,
|
||||
@@ -3424,6 +3428,8 @@ export function RpgEntryHomeView({
|
||||
const [recommendDragOffsetY, setRecommendDragOffsetY] = useState(0);
|
||||
const [recommendDragCommitDirection, setRecommendDragCommitDirection] =
|
||||
useState<RecommendSwipeDirection | null>(null);
|
||||
const [isRecommendDragResetting, setIsRecommendDragResetting] =
|
||||
useState(false);
|
||||
const activeRecommendEntryKeyForSelection =
|
||||
recommendFeedWindow.activeEntryKey;
|
||||
const recommendCardStageRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -3438,6 +3444,7 @@ export function RpgEntryHomeView({
|
||||
return;
|
||||
}
|
||||
|
||||
setIsRecommendDragResetting(false);
|
||||
setRecommendDragCommitDirection(direction);
|
||||
const panelHeight =
|
||||
recommendCardStageRef.current?.getBoundingClientRect().height ?? 0;
|
||||
@@ -3454,8 +3461,12 @@ export function RpgEntryHomeView({
|
||||
} else {
|
||||
onSelectPreviousRecommendEntry?.(activeRecommendEntryKeyForSelection);
|
||||
}
|
||||
setIsRecommendDragResetting(true);
|
||||
setRecommendDragOffsetY(0);
|
||||
setRecommendDragCommitDirection(null);
|
||||
window.requestAnimationFrame(() => {
|
||||
setIsRecommendDragResetting(false);
|
||||
});
|
||||
}, RECOMMEND_ENTRY_COMMIT_ANIMATION_MS);
|
||||
},
|
||||
[
|
||||
@@ -3517,6 +3528,7 @@ export function RpgEntryHomeView({
|
||||
const deltaY = event.clientY - drag.startY;
|
||||
const commitDirection = resolveRecommendDragCommitDirection(deltaY);
|
||||
if (!commitDirection) {
|
||||
setIsRecommendDragResetting(false);
|
||||
setRecommendDragOffsetY(0);
|
||||
return;
|
||||
}
|
||||
@@ -3532,6 +3544,7 @@ export function RpgEntryHomeView({
|
||||
event.currentTarget.releasePointerCapture?.(drag.pointerId);
|
||||
}
|
||||
recommendDragStartRef.current = null;
|
||||
setIsRecommendDragResetting(false);
|
||||
setRecommendDragOffsetY(0);
|
||||
},
|
||||
[],
|
||||
@@ -3542,6 +3555,7 @@ export function RpgEntryHomeView({
|
||||
const recommendRailClassName = buildRecommendSwipeRailClassName({
|
||||
offsetY: recommendDragOffsetY,
|
||||
commitDirection: recommendDragCommitDirection,
|
||||
isResetting: isRecommendDragResetting,
|
||||
});
|
||||
const selectNextRecommendEntry = useCallback(() => {
|
||||
if (
|
||||
@@ -4338,6 +4352,10 @@ export function RpgEntryHomeView({
|
||||
/>
|
||||
</button>
|
||||
|
||||
<PlatformProfileGenerationQueueCard
|
||||
queueStatus={profileGenerationQueueStatus}
|
||||
/>
|
||||
|
||||
<section
|
||||
className="platform-profile-shortcut-panel"
|
||||
aria-label="常用功能"
|
||||
|
||||
@@ -38,6 +38,13 @@ describe('rpgEntryRecommendSwipeDeckModel', () => {
|
||||
expect(
|
||||
buildRecommendSwipeRailClassName({ offsetY: -320, commitDirection: 1 }),
|
||||
).toBe('platform-recommend-swipe-rail--committing');
|
||||
expect(
|
||||
buildRecommendSwipeRailClassName({
|
||||
offsetY: 0,
|
||||
commitDirection: null,
|
||||
isResetting: true,
|
||||
}),
|
||||
).toBe('platform-recommend-swipe-rail--resetting');
|
||||
|
||||
expect(
|
||||
shouldAnimateRecommendSwipe({
|
||||
|
||||
@@ -9,6 +9,7 @@ export type RecommendSwipeDirection = 1 | -1;
|
||||
export type RecommendSwipeRailState = {
|
||||
offsetY: number;
|
||||
commitDirection: RecommendSwipeDirection | null;
|
||||
isResetting?: boolean;
|
||||
};
|
||||
|
||||
/** 收口推荐卡纵向滑动的纯判定,页面只保留 pointer 与动画副作用。 */
|
||||
@@ -47,6 +48,10 @@ export function resolveRecommendCommitOffset(
|
||||
export function buildRecommendSwipeRailClassName(
|
||||
state: RecommendSwipeRailState,
|
||||
) {
|
||||
if (state.isResetting) {
|
||||
return 'platform-recommend-swipe-rail--resetting';
|
||||
}
|
||||
|
||||
if (state.commitDirection) {
|
||||
return 'platform-recommend-swipe-rail--committing';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user