Merge branch 'master' into codex/tiaoyitiao

This commit is contained in:
2026-06-07 00:57:38 +08:00
37 changed files with 2734 additions and 194 deletions

View File

@@ -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',
@@ -11881,6 +11931,7 @@ test('creation hub gives jump hop wooden fish and bark battle cards the shared d
themeText: '跳台删除草稿',
workTitle: '跳台删除草稿',
workDescription: '跳一跳草稿也应接入统一删除。',
themeText: '跳台',
themeTags: ['跳台'],
difficulty: 'standard',
stylePreset: 'paper-toy',

View File

@@ -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 });

View File

@@ -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>