Merge branch 'codex/platform-creation-flow-polish'
# Conflicts: # docs/【玩法创作】平台入口与玩法链路-2026-05-15.md # src/components/rpg-entry/RpgEntryHomeView.tsx
This commit is contained in:
@@ -6113,10 +6113,19 @@ test('puzzle draft generation auto starts trial and runtime back opens draft res
|
||||
);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
window.history.back();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(window.location.pathname).toBe('/creation/puzzle/result');
|
||||
});
|
||||
await user.click(await screen.findByRole('button', { name: '返回上一页' }));
|
||||
|
||||
expect(await screen.findByText('拼图结果页')).toBeTruthy();
|
||||
expect(screen.getByDisplayValue('雨夜猫街')).toBeTruthy();
|
||||
const creationParams = new URLSearchParams(window.location.search);
|
||||
expect(creationParams.get('sessionId')).toBe('puzzle-session-auto-1');
|
||||
expect(creationParams.get('profileId')).toBe('puzzle-profile-auto-1');
|
||||
});
|
||||
|
||||
test('embedded puzzle form recovers when compile request times out after backend completion', async () => {
|
||||
@@ -7319,6 +7328,47 @@ test('home recommendation starts embedded puzzle without global auth reset on lo
|
||||
});
|
||||
});
|
||||
|
||||
test('home recommendation share opens publish share modal', async () => {
|
||||
const user = userEvent.setup();
|
||||
const publishedPuzzleWork = {
|
||||
workId: 'puzzle-work-share-1',
|
||||
profileId: 'SHARE001',
|
||||
ownerUserId: 'user-2',
|
||||
sourceSessionId: 'puzzle-session-share-1',
|
||||
authorDisplayName: '拼图作者',
|
||||
levelName: '星桥分享关',
|
||||
summary: '旋转碎片并接通星桥机关。',
|
||||
themeTags: ['机关', '星桥'],
|
||||
coverImageSrc: null,
|
||||
coverAssetId: null,
|
||||
publicationStatus: 'published',
|
||||
updatedAt: '2026-04-25T09:00:00.000Z',
|
||||
publishedAt: '2026-04-25T09:00:00.000Z',
|
||||
playCount: 3,
|
||||
likeCount: 0,
|
||||
publishReady: true,
|
||||
} satisfies PuzzleWorkSummary;
|
||||
|
||||
vi.mocked(listPuzzleGallery).mockResolvedValue({
|
||||
items: [publishedPuzzleWork],
|
||||
});
|
||||
vi.mocked(getPuzzleGalleryDetail).mockResolvedValue({
|
||||
item: publishedPuzzleWork,
|
||||
});
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
const meta = await screen.findByLabelText('星桥分享关 作品信息');
|
||||
await user.click(within(meta).getByRole('button', { name: '分享' }));
|
||||
|
||||
expect(
|
||||
await screen.findByRole('dialog', { name: '分享给朋友' }),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByText(/作品号:PZ-SHARE001/u)).toBeTruthy();
|
||||
expect(screen.getByText(/\/gallery\/puzzle\/detail\?work=PZ-SHARE001/u))
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
test('home recommendation keeps logged-in puzzle start on default auth instead of guest token', async () => {
|
||||
const publishedPuzzleWork = {
|
||||
workId: 'puzzle-work-public-2',
|
||||
@@ -11806,6 +11856,7 @@ test('creation hub gives jump hop wooden fish and bark battle cards the shared d
|
||||
sourceSessionId: 'jump-hop-session-delete',
|
||||
workTitle: '跳台删除草稿',
|
||||
workDescription: '跳一跳草稿也应接入统一删除。',
|
||||
themeText: '跳台',
|
||||
themeTags: ['跳台'],
|
||||
difficulty: 'standard',
|
||||
stylePreset: 'paper-toy',
|
||||
|
||||
@@ -4037,6 +4037,7 @@ test('logged in recommend runtime preloads adjacent work previews and drag switc
|
||||
const onSelectNextRecommendEntry = vi.fn();
|
||||
const onSelectPreviousRecommendEntry = vi.fn();
|
||||
const onLikeRecommendEntry = vi.fn();
|
||||
const onShareRecommendEntry = vi.fn();
|
||||
const onRemixRecommendEntry = vi.fn();
|
||||
const firstEntry = {
|
||||
...puzzlePublicEntry,
|
||||
@@ -4122,6 +4123,7 @@ test('logged in recommend runtime preloads adjacent work previews and drag switc
|
||||
onSelectNextRecommendEntry={onSelectNextRecommendEntry}
|
||||
onSelectPreviousRecommendEntry={onSelectPreviousRecommendEntry}
|
||||
onLikeRecommendEntry={onLikeRecommendEntry}
|
||||
onShareRecommendEntry={onShareRecommendEntry}
|
||||
onRemixRecommendEntry={onRemixRecommendEntry}
|
||||
onOpenLibraryDetail={vi.fn()}
|
||||
onSearchPublicCode={vi.fn()}
|
||||
@@ -4140,11 +4142,6 @@ test('logged in recommend runtime preloads adjacent work previews and drag switc
|
||||
expect(screen.getAllByText('上一拼图').length).toBeGreaterThanOrEqual(2);
|
||||
expect(screen.queryByText('评论')).toBeNull();
|
||||
expect(screen.queryByLabelText(/游玩/u)).toBeNull();
|
||||
const clipboardWriteText = vi.fn().mockResolvedValue(undefined);
|
||||
Object.defineProperty(navigator, 'clipboard', {
|
||||
configurable: true,
|
||||
value: { writeText: clipboardWriteText },
|
||||
});
|
||||
|
||||
const meta = screen.getByLabelText('当前拼图 作品信息') as HTMLElement;
|
||||
expect(meta.closest('[data-recommend-swipe-zone="true"]')).toBeTruthy();
|
||||
@@ -4166,10 +4163,9 @@ test('logged in recommend runtime preloads adjacent work previews and drag switc
|
||||
fireEvent.click(remixButton);
|
||||
|
||||
expect(onLikeRecommendEntry).toHaveBeenCalledWith(firstEntry);
|
||||
expect(onShareRecommendEntry).toHaveBeenCalledWith(firstEntry);
|
||||
expect(onRemixRecommendEntry).toHaveBeenCalledWith(firstEntry);
|
||||
expect(clipboardWriteText).toHaveBeenCalledWith(
|
||||
expect.stringContaining('作品号:PZ-FEED1'),
|
||||
);
|
||||
expect(activeRecommendCard.getByRole('button', { name: '分享' })).toBeTruthy();
|
||||
|
||||
act(() => {
|
||||
dispatchPointerEvent(meta, 'pointerdown', { pointerId: 1, clientY: 300 });
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
MessageCircle,
|
||||
Palette,
|
||||
Pencil,
|
||||
Plus,
|
||||
ScanLine,
|
||||
Search,
|
||||
Settings,
|
||||
@@ -203,6 +202,7 @@ export interface RpgEntryHomeViewProps {
|
||||
onSelectNextRecommendEntry?: (activeEntryKey?: string | null) => void;
|
||||
onSelectPreviousRecommendEntry?: (activeEntryKey?: string | null) => void;
|
||||
onLikeRecommendEntry?: (entry: PlatformPublicGalleryCard) => void;
|
||||
onShareRecommendEntry?: (entry: PlatformPublicGalleryCard) => void;
|
||||
onRemixRecommendEntry?: (entry: PlatformPublicGalleryCard) => void;
|
||||
onOpenLibraryDetail: (
|
||||
entry: CustomWorldLibraryEntry<CustomWorldProfile>,
|
||||
@@ -1070,7 +1070,6 @@ function RecommendSwipeCard({
|
||||
authorSummary,
|
||||
isActive,
|
||||
visual,
|
||||
shareState,
|
||||
onDragPointerDown,
|
||||
onDragPointerMove,
|
||||
onDragPointerUp,
|
||||
@@ -1084,7 +1083,6 @@ function RecommendSwipeCard({
|
||||
authorSummary?: PublicUserSummary | null;
|
||||
isActive: boolean;
|
||||
visual: ReactNode;
|
||||
shareState?: 'idle' | 'copied' | 'failed';
|
||||
onDragPointerDown?: (event: PointerEvent<HTMLElement>) => void;
|
||||
onDragPointerMove?: (event: PointerEvent<HTMLElement>) => void;
|
||||
onDragPointerUp?: (event: PointerEvent<HTMLElement>) => void;
|
||||
@@ -1108,7 +1106,6 @@ function RecommendSwipeCard({
|
||||
authorAvatarUrl={authorAvatarUrl}
|
||||
authorSummary={authorSummary}
|
||||
isActive={isActive}
|
||||
shareState={shareState}
|
||||
onDragPointerDown={onDragPointerDown}
|
||||
onDragPointerMove={onDragPointerMove}
|
||||
onDragPointerUp={onDragPointerUp}
|
||||
@@ -1130,7 +1127,6 @@ function RecommendRuntimeMeta({
|
||||
onDragPointerMove,
|
||||
onDragPointerUp,
|
||||
onDragPointerCancel,
|
||||
shareState = 'idle',
|
||||
onLike,
|
||||
onShare,
|
||||
onRemix,
|
||||
@@ -1143,7 +1139,6 @@ function RecommendRuntimeMeta({
|
||||
onDragPointerMove?: (event: PointerEvent<HTMLElement>) => void;
|
||||
onDragPointerUp?: (event: PointerEvent<HTMLElement>) => void;
|
||||
onDragPointerCancel?: (event: PointerEvent<HTMLElement>) => void;
|
||||
shareState?: 'idle' | 'copied' | 'failed';
|
||||
onLike?: () => void;
|
||||
onShare?: () => void;
|
||||
onRemix?: () => void;
|
||||
@@ -1234,13 +1229,7 @@ function RecommendRuntimeMeta({
|
||||
onShare?.();
|
||||
}}
|
||||
disabled={!isActive || !onShare}
|
||||
aria-label={
|
||||
shareState === 'copied'
|
||||
? '分享内容已复制'
|
||||
: shareState === 'failed'
|
||||
? '分享内容复制失败'
|
||||
: '分享'
|
||||
}
|
||||
aria-label="分享"
|
||||
title="分享"
|
||||
>
|
||||
<Share2 className="h-5 w-5" aria-hidden="true" />
|
||||
@@ -4134,6 +4123,7 @@ export function RpgEntryHomeView({
|
||||
onSelectNextRecommendEntry,
|
||||
onSelectPreviousRecommendEntry,
|
||||
onLikeRecommendEntry,
|
||||
onShareRecommendEntry,
|
||||
onRemixRecommendEntry,
|
||||
onOpenLibraryDetail,
|
||||
onDeleteLibraryEntry,
|
||||
@@ -4415,7 +4405,7 @@ export function RpgEntryHomeView({
|
||||
? {
|
||||
home: Sparkles,
|
||||
category: Compass,
|
||||
create: Plus,
|
||||
create: Sparkles,
|
||||
saves: Pencil,
|
||||
profile: UserRound,
|
||||
}
|
||||
@@ -5560,13 +5550,9 @@ export function RpgEntryHomeView({
|
||||
const [recommendDragOffsetY, setRecommendDragOffsetY] = useState(0);
|
||||
const [recommendDragCommitDirection, setRecommendDragCommitDirection] =
|
||||
useState<1 | -1 | null>(null);
|
||||
const [recommendShareState, setRecommendShareState] = useState<
|
||||
'idle' | 'copied' | 'failed'
|
||||
>('idle');
|
||||
const activeRecommendEntryKeyForSelection = activeRecommendEntry
|
||||
? buildPublicGalleryCardKey(activeRecommendEntry)
|
||||
: null;
|
||||
const recommendShareResetTimerRef = useRef<number | null>(null);
|
||||
const recommendCardStageRef = useRef<HTMLDivElement | null>(null);
|
||||
const recommendDragStartRef = useRef<{
|
||||
pointerId: number;
|
||||
@@ -5704,39 +5690,6 @@ export function RpgEntryHomeView({
|
||||
onSelectNextRecommendEntry,
|
||||
recommendedFeedEntries.length,
|
||||
]);
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (recommendShareResetTimerRef.current !== null) {
|
||||
window.clearTimeout(recommendShareResetTimerRef.current);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
useEffect(() => {
|
||||
setRecommendShareState('idle');
|
||||
}, [activeRecommendEntryKey]);
|
||||
const shareRecommendEntry = useCallback(
|
||||
(entry: PlatformPublicGalleryCard) => {
|
||||
const publicWorkCode = resolvePlatformPublicWorkCode(entry)?.trim();
|
||||
if (!publicWorkCode) {
|
||||
setRecommendShareState('failed');
|
||||
return;
|
||||
}
|
||||
|
||||
const shareText = `邀请你来玩《${entry.worldName}》\n作品号:${publicWorkCode}\n${buildPublicWorkDetailUrl(publicWorkCode)}`;
|
||||
void copyTextToClipboard(shareText).then((copied) => {
|
||||
setRecommendShareState(copied ? 'copied' : 'failed');
|
||||
if (recommendShareResetTimerRef.current !== null) {
|
||||
window.clearTimeout(recommendShareResetTimerRef.current);
|
||||
}
|
||||
recommendShareResetTimerRef.current = window.setTimeout(() => {
|
||||
recommendShareResetTimerRef.current = null;
|
||||
setRecommendShareState('idle');
|
||||
}, 1400);
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
const leadPublicEntry = featuredShelf[0] ?? generalLatestEntries[0] ?? null;
|
||||
const openLeadPublicEntry = () => {
|
||||
if (leadPublicEntry) {
|
||||
@@ -5880,9 +5833,8 @@ export function RpgEntryHomeView({
|
||||
onDragPointerMove={moveRecommendDrag}
|
||||
onDragPointerUp={endRecommendDrag}
|
||||
onDragPointerCancel={cancelRecommendDrag}
|
||||
shareState={recommendShareState}
|
||||
onLike={() => onLikeRecommendEntry?.(activeRecommendEntry)}
|
||||
onShare={() => shareRecommendEntry(activeRecommendEntry)}
|
||||
onShare={() => onShareRecommendEntry?.(activeRecommendEntry)}
|
||||
onRemix={() => onRemixRecommendEntry?.(activeRecommendEntry)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user