合入通用作品分享卡片与小程序直达路径 合入推荐页当前作品系统分享参数同步 合入小程序九宫切图与相关测试 # Conflicts: # .hermes/shared-memory/decision-log.md # docs/【开发运维】本地开发验证与生产运维-2026-05-15.md # docs/【玩法创作】平台入口与玩法链路-2026-05-15.md # src/components/custom-world-home/CustomWorldCreationHub.tsx # src/components/platform-entry/PlatformEntryFlowShellImpl.test.ts # src/components/platform-entry/PlatformEntryFlowShellImpl.tsx # src/components/rpg-entry/RpgEntryHomeView.tsx
1207 lines
37 KiB
TypeScript
1207 lines
37 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { act, fireEvent, render, screen } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { afterEach, expect, test, vi } from 'vitest';
|
|
|
|
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
|
|
import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contracts/customWorldAgent';
|
|
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
|
|
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
|
|
import type { CreationEntryConfig } from '../../services/creationEntryConfigService';
|
|
import { derivePlatformCreationTypes } from '../platform-entry/platformEntryCreationTypes';
|
|
import { CustomWorldCreationHub } from './CustomWorldCreationHub.testAdapter';
|
|
|
|
const noopCreateType = () => {};
|
|
|
|
const testEntryConfig = {
|
|
startCard: {
|
|
title: '新建作品',
|
|
description: '选择模板后进入对应的创作表单。',
|
|
idleBadge: '模板 Tab',
|
|
busyBadge: '正在开启',
|
|
},
|
|
typeModal: {
|
|
title: '选择创作类型',
|
|
description: '先选玩法类型,再进入对应创作工作台。',
|
|
},
|
|
eventBanner: {
|
|
title: '泥点挑战',
|
|
description: '创作活动测试横幅。',
|
|
coverImageSrc: '/creation-type-references/puzzle.webp',
|
|
prizePoolMudPoints: 1000,
|
|
startsAtText: '2026-05-01',
|
|
endsAtText: '2026-05-31',
|
|
},
|
|
creationTypes: [
|
|
{
|
|
id: 'rpg',
|
|
title: '文字冒险',
|
|
subtitle: '经典 RPG 体验',
|
|
badge: '可创建',
|
|
imageSrc: '/creation-type-references/rpg.webp',
|
|
visible: true,
|
|
open: true,
|
|
sortOrder: 10,
|
|
categoryId: 'recommended',
|
|
categoryLabel: '热门推荐',
|
|
categorySortOrder: 20,
|
|
updatedAtMicros: 1,
|
|
},
|
|
{
|
|
id: 'puzzle',
|
|
title: '拼图',
|
|
subtitle: '拼图关卡创作',
|
|
badge: '可创建',
|
|
imageSrc: '/creation-type-references/puzzle.webp',
|
|
visible: true,
|
|
open: true,
|
|
sortOrder: 30,
|
|
categoryId: 'recommended',
|
|
categoryLabel: '热门推荐',
|
|
categorySortOrder: 20,
|
|
updatedAtMicros: 1,
|
|
},
|
|
{
|
|
id: 'match3d',
|
|
title: '抓大鹅',
|
|
subtitle: '3D 消除关卡',
|
|
badge: '可创建',
|
|
imageSrc: '/creation-type-references/match3d.webp',
|
|
visible: true,
|
|
open: true,
|
|
sortOrder: 40,
|
|
categoryId: 'recommended',
|
|
categoryLabel: '热门推荐',
|
|
categorySortOrder: 20,
|
|
updatedAtMicros: 1,
|
|
},
|
|
{
|
|
id: 'square-hole',
|
|
title: '方洞',
|
|
subtitle: '形状投放挑战',
|
|
badge: '可创建',
|
|
imageSrc: '/creation-type-references/square-hole.webp',
|
|
visible: false,
|
|
open: true,
|
|
sortOrder: 50,
|
|
categoryId: 'recommended',
|
|
categoryLabel: '热门推荐',
|
|
categorySortOrder: 20,
|
|
updatedAtMicros: 1,
|
|
},
|
|
{
|
|
id: 'visual-novel',
|
|
title: '视觉小说',
|
|
subtitle: '分支叙事体验',
|
|
badge: '敬请期待',
|
|
imageSrc: '/creation-type-references/visual-novel.webp',
|
|
visible: false,
|
|
open: false,
|
|
sortOrder: 60,
|
|
categoryId: 'recommended',
|
|
categoryLabel: '热门推荐',
|
|
categorySortOrder: 20,
|
|
updatedAtMicros: 1,
|
|
},
|
|
{
|
|
id: 'airp',
|
|
title: 'AI RPG',
|
|
subtitle: '原生角色扮演',
|
|
badge: '即将开放',
|
|
imageSrc: '/creation-type-references/airp.webp',
|
|
visible: true,
|
|
open: false,
|
|
sortOrder: 70,
|
|
categoryId: 'recommended',
|
|
categoryLabel: '热门推荐',
|
|
categorySortOrder: 20,
|
|
updatedAtMicros: 1,
|
|
},
|
|
],
|
|
} satisfies CreationEntryConfig;
|
|
|
|
const testCreationTypes = derivePlatformCreationTypes(
|
|
testEntryConfig.creationTypes,
|
|
);
|
|
|
|
const originalClipboard = navigator.clipboard;
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
window.sessionStorage.clear();
|
|
Object.defineProperty(navigator, 'clipboard', {
|
|
configurable: true,
|
|
value: originalClipboard,
|
|
});
|
|
});
|
|
|
|
test('creation entry banner automatically rotates through backend configured banners', () => {
|
|
vi.useFakeTimers();
|
|
render(
|
|
<CustomWorldCreationHub
|
|
items={[]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
entryConfig={{
|
|
...testEntryConfig,
|
|
eventBanners: [
|
|
{
|
|
...testEntryConfig.eventBanner,
|
|
title: '第一张后台 Banner',
|
|
},
|
|
{
|
|
...testEntryConfig.eventBanner,
|
|
title: '第二张后台 Banner',
|
|
coverImageSrc: '/creation-type-references/match3d.webp',
|
|
},
|
|
],
|
|
}}
|
|
creationTypes={testCreationTypes}
|
|
mode="start-only"
|
|
/>,
|
|
);
|
|
|
|
const bannerTrack = screen.getByLabelText('创作公告横幅');
|
|
Object.defineProperty(bannerTrack, 'clientWidth', {
|
|
configurable: true,
|
|
value: 360,
|
|
});
|
|
const scrollTo = vi.fn();
|
|
Object.defineProperty(bannerTrack, 'scrollTo', {
|
|
configurable: true,
|
|
value: scrollTo,
|
|
});
|
|
|
|
act(() => {
|
|
vi.advanceTimersByTime(4200);
|
|
});
|
|
|
|
expect(scrollTo).toHaveBeenCalledWith({
|
|
left: 360,
|
|
behavior: 'smooth',
|
|
});
|
|
});
|
|
|
|
test('creation hub shows published metric growth from cached page snapshot', async () => {
|
|
window.sessionStorage.setItem(
|
|
'genarrative.creationHub.publishedMetrics.v1',
|
|
JSON.stringify({
|
|
'puzzle:puzzle:work-growth': {
|
|
'play-count': 7,
|
|
'remix-count': 1,
|
|
'like-count': 2,
|
|
},
|
|
}),
|
|
);
|
|
|
|
render(
|
|
<CustomWorldCreationHub
|
|
items={[]}
|
|
puzzleItems={[
|
|
{
|
|
workId: 'puzzle:work-growth',
|
|
profileId: 'puzzle-profile-growth',
|
|
ownerUserId: 'user-1',
|
|
authorDisplayName: '拼图作者',
|
|
levelName: '涨潮拼图',
|
|
summary: '公开指标会从缓存快照涨到最新值。',
|
|
themeTags: ['涨潮'],
|
|
coverImageSrc: null,
|
|
publicationStatus: 'published',
|
|
updatedAt: new Date('2026-04-22T12:00:00.000Z').toISOString(),
|
|
publishedAt: new Date('2026-04-22T12:10:00.000Z').toISOString(),
|
|
playCount: 10,
|
|
remixCount: 4,
|
|
likeCount: 2,
|
|
publishReady: true,
|
|
},
|
|
]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
onOpenPuzzleDetail={() => {}}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByLabelText('游玩 10次')).toBeTruthy();
|
|
expect(screen.getByLabelText('改造 4次')).toBeTruthy();
|
|
expect(await screen.findAllByText('↑')).toHaveLength(2);
|
|
});
|
|
|
|
const baseDraftItem: CustomWorldWorkSummary = {
|
|
workId: 'draft:session-1',
|
|
sourceType: 'agent_session',
|
|
status: 'draft',
|
|
title: '潮雾列岛',
|
|
subtitle: '补齐关键锚点',
|
|
summary: '玩家是失职返乡的守灯人。',
|
|
coverImageSrc: null,
|
|
updatedAt: new Date('2026-04-14T10:00:00.000Z').toISOString(),
|
|
publishedAt: null,
|
|
stage: 'object_refining',
|
|
stageLabel: '待完善草稿',
|
|
playableNpcCount: 3,
|
|
landmarkCount: 4,
|
|
sessionId: 'session-1',
|
|
profileId: null,
|
|
canResume: true,
|
|
canEnterWorld: false,
|
|
};
|
|
|
|
const hiddenSquareHoleItem: SquareHoleWorkSummary = {
|
|
workId: 'square-hole:work-hidden',
|
|
profileId: 'square-hole-profile-hidden',
|
|
ownerUserId: 'user-1',
|
|
gameName: '隐藏方洞挑战',
|
|
themeText: '方洞',
|
|
twistRule: '隐藏入口',
|
|
summary: '入口隐藏后,这条作品不应出现在创作页作品架。',
|
|
tags: ['方洞'],
|
|
coverImageSrc: null,
|
|
backgroundPrompt: '',
|
|
backgroundImageSrc: null,
|
|
shapeOptions: [],
|
|
holeOptions: [],
|
|
shapeCount: 0,
|
|
difficulty: 1,
|
|
publicationStatus: 'draft',
|
|
playCount: 0,
|
|
updatedAt: new Date('2026-05-10T10:00:00.000Z').toISOString(),
|
|
publishedAt: null,
|
|
publishReady: false,
|
|
sourceSessionId: 'square-hole-session-hidden',
|
|
};
|
|
|
|
const babyObjectMatchDraftItem: BabyObjectMatchDraft = {
|
|
draftId: 'baby-object-draft-delete',
|
|
profileId: 'baby-object-profile-delete',
|
|
templateId: 'baby-object-match',
|
|
templateName: '宝贝识物',
|
|
workTitle: '宝贝识物删除测试',
|
|
workDescription: '苹果和香蕉识物分类',
|
|
itemNames: ['苹果', '香蕉'],
|
|
itemAssets: [
|
|
{
|
|
itemId: 'baby-object-item-1',
|
|
itemName: '苹果',
|
|
imageSrc: '/apple.png',
|
|
assetObjectId: null,
|
|
generationProvider: 'placeholder',
|
|
prompt: '苹果',
|
|
},
|
|
{
|
|
itemId: 'baby-object-item-2',
|
|
itemName: '香蕉',
|
|
imageSrc: '/banana.png',
|
|
assetObjectId: null,
|
|
generationProvider: 'placeholder',
|
|
prompt: '香蕉',
|
|
},
|
|
],
|
|
visualPackage: null,
|
|
themeTags: ['寓教于乐'],
|
|
publicationStatus: 'draft',
|
|
createdAt: new Date('2026-05-11T10:00:00.000Z').toISOString(),
|
|
updatedAt: new Date('2026-05-11T10:00:00.000Z').toISOString(),
|
|
publishedAt: null,
|
|
};
|
|
|
|
const barkBattleDraftItem: BarkBattleWorkSummary = {
|
|
workId: 'bark-battle-work-draft-visible',
|
|
draftId: 'bark-battle-draft-visible',
|
|
ownerUserId: 'user-1',
|
|
authorDisplayName: '声浪作者',
|
|
title: '竖屏声浪草稿',
|
|
summary: '生成完成后也必须留在我的草稿里。',
|
|
themeDescription: '霓虹竖屏擂台',
|
|
playerImageDescription: '红围巾选手',
|
|
opponentImageDescription: '蓝头带对手',
|
|
onomatopoeia: ['炸场', '破阵'],
|
|
playerCharacterImageSrc: '/bark/player.png',
|
|
opponentCharacterImageSrc: '/bark/opponent.png',
|
|
uiBackgroundImageSrc: '/bark/background.png',
|
|
difficultyPreset: 'normal',
|
|
status: 'draft',
|
|
generationStatus: 'ready',
|
|
publishReady: true,
|
|
playCount: 0,
|
|
updatedAt: '2026-05-21T10:00:00.000Z',
|
|
publishedAt: null,
|
|
};
|
|
|
|
const barkBattlePublishedItem: BarkBattleWorkSummary = {
|
|
...barkBattleDraftItem,
|
|
workId: 'bark-battle-work-published-visible',
|
|
draftId: 'bark-battle-draft-published-visible',
|
|
title: '竖屏声浪已发布',
|
|
summary: '发布完成后必须留在已发布作品里。',
|
|
authorDisplayName: '发布作者',
|
|
status: 'published',
|
|
playCount: 9,
|
|
updatedAt: '2026-05-21T10:10:00.000Z',
|
|
publishedAt: '2026-05-21T10:10:00.000Z',
|
|
};
|
|
|
|
test('creation hub reflects updated draft title summary and counts after rerender', async () => {
|
|
const user = userEvent.setup();
|
|
const onCreateType = vi.fn();
|
|
const { rerender } = render(
|
|
<CustomWorldCreationHub
|
|
items={[baseDraftItem]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={onCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('潮雾列岛')).toBeTruthy();
|
|
expect(screen.getByText('玩家是失职返乡的守灯人。')).toBeTruthy();
|
|
expect(screen.queryByText('角色 3')).toBeNull();
|
|
expect(screen.queryByText('地点 4')).toBeNull();
|
|
const puzzleButton = screen.getByRole('button', {
|
|
name: /拼图.*拼图关卡创作/u,
|
|
});
|
|
const match3dButton = screen.getByRole('button', {
|
|
name: /抓大鹅.*3D 消除关卡/u,
|
|
});
|
|
const rpgButton = screen.getByRole('button', {
|
|
name: /文字冒险.*经典 RPG 体验/u,
|
|
});
|
|
expect(puzzleButton).toBeTruthy();
|
|
expect(match3dButton).toBeTruthy();
|
|
expect(rpgButton).toBeTruthy();
|
|
expect((puzzleButton as HTMLButtonElement).disabled).toBe(false);
|
|
expect((match3dButton as HTMLButtonElement).disabled).toBe(false);
|
|
expect((rpgButton as HTMLButtonElement).disabled).toBe(false);
|
|
expect(screen.queryByRole('button', { name: /方洞挑战/u })).toBeNull();
|
|
expect(screen.queryByText('反直觉形状分拣')).toBeNull();
|
|
expect(screen.queryByRole('button', { name: /智能创作/u })).toBeNull();
|
|
expect(screen.queryByRole('button', { name: /大鱼吃小鱼/u })).toBeNull();
|
|
|
|
await user.click(match3dButton);
|
|
expect(onCreateType).toHaveBeenCalledWith('match3d');
|
|
|
|
rerender(
|
|
<CustomWorldCreationHub
|
|
items={[
|
|
{
|
|
...baseDraftItem,
|
|
title: '潮雾列岛·回潮版',
|
|
summary: '世界总卡和角色网已经继续长出了新的支线。',
|
|
playableNpcCount: 5,
|
|
landmarkCount: 6,
|
|
updatedAt: new Date('2026-04-14T10:10:00.000Z').toISOString(),
|
|
},
|
|
]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('潮雾列岛·回潮版')).toBeTruthy();
|
|
expect(
|
|
screen.getByText('世界总卡和角色网已经继续长出了新的支线。'),
|
|
).toBeTruthy();
|
|
expect(screen.queryByText('角色 5')).toBeNull();
|
|
expect(screen.queryByText('地点 6')).toBeNull();
|
|
});
|
|
|
|
test('creation hub hides square hole works when the creation type is hidden', () => {
|
|
const onOpenSquareHoleDetail = vi.fn();
|
|
|
|
render(
|
|
<CustomWorldCreationHub
|
|
items={[]}
|
|
squareHoleItems={[hiddenSquareHoleItem]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
onOpenSquareHoleDetail={onOpenSquareHoleDetail}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.queryByText('隐藏方洞挑战')).toBeNull();
|
|
expect(
|
|
screen.queryByText('入口隐藏后,这条作品不应出现在创作页作品架。'),
|
|
).toBeNull();
|
|
});
|
|
|
|
test('creation hub mixes puzzle works into the same grid and uses puzzle tag to distinguish', () => {
|
|
render(
|
|
<CustomWorldCreationHub
|
|
items={[baseDraftItem]}
|
|
puzzleItems={[
|
|
{
|
|
workId: 'puzzle:work-1',
|
|
profileId: 'puzzle-profile-1',
|
|
ownerUserId: 'user-1',
|
|
authorDisplayName: '拼图作者',
|
|
levelName: '沉钟拼图',
|
|
summary: '拼图作品会与其他创作作品一起展示。',
|
|
themeTags: ['潮雾', '沉钟'],
|
|
coverImageSrc: null,
|
|
publicationStatus: 'published',
|
|
updatedAt: new Date('2026-04-22T12:00:00.000Z').toISOString(),
|
|
publishedAt: new Date('2026-04-22T12:10:00.000Z').toISOString(),
|
|
playCount: 8,
|
|
remixCount: 2,
|
|
likeCount: 3,
|
|
publishReady: true,
|
|
},
|
|
]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
onOpenPuzzleDetail={() => {}}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('潮雾列岛')).toBeTruthy();
|
|
expect(screen.getByText('沉钟拼图')).toBeTruthy();
|
|
expect(screen.getAllByText('拼图').length).toBeGreaterThan(0);
|
|
expect(screen.getByLabelText('游玩 8次')).toBeTruthy();
|
|
expect(screen.getByLabelText('改造 2次')).toBeTruthy();
|
|
expect(screen.getByLabelText('点赞 3赞')).toBeTruthy();
|
|
expect(screen.queryByText('Remix')).toBeNull();
|
|
expect(screen.queryByText('PZ-PROFILE1')).toBeNull();
|
|
expect(screen.queryByText('潮雾')).toBeNull();
|
|
expect(screen.queryByText('沉钟')).toBeNull();
|
|
expect(screen.queryByText('我的拼图作品')).toBeNull();
|
|
});
|
|
|
|
test('creation hub shows puzzle point incentive and claims without opening card', async () => {
|
|
const user = userEvent.setup();
|
|
const onClaimPuzzlePointIncentive = vi.fn();
|
|
const onOpenPuzzleDetail = vi.fn();
|
|
|
|
render(
|
|
<CustomWorldCreationHub
|
|
items={[]}
|
|
puzzleItems={[
|
|
{
|
|
workId: 'puzzle:work-incentive',
|
|
profileId: 'puzzle-profile-incentive',
|
|
ownerUserId: 'user-1',
|
|
authorDisplayName: '拼图作者',
|
|
levelName: '陶泥儿灯塔',
|
|
summary: '拼图作品会展示积分激励。',
|
|
themeTags: ['灯塔', '陶泥儿'],
|
|
coverImageSrc: null,
|
|
publicationStatus: 'published',
|
|
updatedAt: new Date('2026-05-01T12:00:00.000Z').toISOString(),
|
|
publishedAt: new Date('2026-05-01T12:10:00.000Z').toISOString(),
|
|
playCount: 8,
|
|
remixCount: 2,
|
|
likeCount: 3,
|
|
pointIncentiveTotalHalfPoints: 5,
|
|
pointIncentiveClaimedPoints: 1,
|
|
pointIncentiveTotalPoints: 2.5,
|
|
pointIncentiveClaimablePoints: 1,
|
|
publishReady: true,
|
|
},
|
|
]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
onOpenPuzzleDetail={onOpenPuzzleDetail}
|
|
onClaimPuzzlePointIncentive={onClaimPuzzlePointIncentive}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByLabelText('积分激励总数 2.5 泥点')).toBeTruthy();
|
|
expect(screen.getByLabelText('待领取积分 1 泥点')).toBeTruthy();
|
|
|
|
await user.click(screen.getByRole('button', { name: '领取积分' }));
|
|
|
|
expect(onClaimPuzzlePointIncentive).toHaveBeenCalledWith(
|
|
expect.objectContaining({ profileId: 'puzzle-profile-incentive' }),
|
|
);
|
|
expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('creation hub shows RPG public work code from published library entry', () => {
|
|
render(
|
|
<CustomWorldCreationHub
|
|
items={[
|
|
{
|
|
...baseDraftItem,
|
|
workId: 'published:world-public-1',
|
|
sourceType: 'published_profile',
|
|
status: 'published',
|
|
title: '潮雾列岛已发布版',
|
|
profileId: 'world-public-1',
|
|
canResume: false,
|
|
canEnterWorld: true,
|
|
},
|
|
]}
|
|
rpgLibraryEntries={[
|
|
{
|
|
ownerUserId: 'user-1',
|
|
profileId: 'world-public-1',
|
|
publicWorkCode: 'CW-00000001',
|
|
authorPublicUserCode: 'SY-00000001',
|
|
profile: {} as never,
|
|
visibility: 'published',
|
|
publishedAt: '2026-04-20T10:00:00.000Z',
|
|
updatedAt: '2026-04-20T10:00:00.000Z',
|
|
authorDisplayName: '测试玩家',
|
|
worldName: '潮雾列岛已发布版',
|
|
subtitle: '旧灯塔与失控航路',
|
|
summaryText: '已经发布的群岛世界作品。',
|
|
coverImageSrc: null,
|
|
themeMode: 'tide',
|
|
playableNpcCount: 3,
|
|
landmarkCount: 4,
|
|
playCount: 12,
|
|
remixCount: 4,
|
|
likeCount: 5,
|
|
},
|
|
]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('潮雾列岛已发布版')).toBeTruthy();
|
|
expect(screen.getByLabelText('游玩 12次')).toBeTruthy();
|
|
expect(screen.getByLabelText('改造 4次')).toBeTruthy();
|
|
expect(screen.getByLabelText('点赞 5赞')).toBeTruthy();
|
|
expect(screen.queryByText('Remix')).toBeNull();
|
|
expect(screen.queryByText('CW-00000001')).toBeNull();
|
|
});
|
|
|
|
test('creation hub keeps persisted draft delete action off the card header', () => {
|
|
const { container } = render(
|
|
<CustomWorldCreationHub
|
|
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
onDeletePublished={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
expect(
|
|
container.querySelector('.creation-work-card__swipe-underlay'),
|
|
).toBeTruthy();
|
|
expect(screen.queryByRole('button', { name: '删除' })).toBeNull();
|
|
});
|
|
|
|
test('creation hub reveals persisted draft delete action from left swipe', () => {
|
|
const { container } = render(
|
|
<CustomWorldCreationHub
|
|
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
onDeletePublished={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
const card = screen.getByRole('button', { name: /继续完善《潮雾列岛》/u });
|
|
fireEvent.touchStart(card, {
|
|
touches: [{ clientX: 180, clientY: 20 }],
|
|
});
|
|
fireEvent.touchMove(card, {
|
|
touches: [{ clientX: 92, clientY: 22 }],
|
|
});
|
|
fireEvent.touchEnd(card);
|
|
|
|
expect(
|
|
container.querySelector('.creation-work-card__swipe-button--danger'),
|
|
).toBeTruthy();
|
|
expect(
|
|
container.querySelector('.creation-work-card-shell--actions-visible'),
|
|
).toBeTruthy();
|
|
});
|
|
|
|
test('creation hub reveals persisted draft delete action from keyboard', async () => {
|
|
const user = userEvent.setup();
|
|
const { container } = render(
|
|
<CustomWorldCreationHub
|
|
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
onDeletePublished={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
screen.getByRole('button', { name: /继续完善《潮雾列岛》/u }).focus();
|
|
await user.keyboard('{ArrowLeft}');
|
|
|
|
expect(
|
|
container.querySelector('.creation-work-card__swipe-button--danger'),
|
|
).toBeTruthy();
|
|
expect(screen.queryByRole('button', { name: '分享' })).toBeNull();
|
|
});
|
|
|
|
test('creation hub reveals persisted draft delete action from long press menu', () => {
|
|
const { container } = render(
|
|
<CustomWorldCreationHub
|
|
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
onDeletePublished={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
const card = screen.getByRole('button', { name: /继续完善《潮雾列岛》/u });
|
|
fireEvent.contextMenu(card);
|
|
|
|
expect(
|
|
container.querySelector('.creation-work-card-shell--actions-visible'),
|
|
).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: '删除' })).toBeTruthy();
|
|
});
|
|
|
|
test('creation hub gives every deletable work card a side delete action', () => {
|
|
const { container } = render(
|
|
<CustomWorldCreationHub
|
|
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
|
babyObjectMatchItems={[babyObjectMatchDraftItem]}
|
|
puzzleItems={[
|
|
{
|
|
workId: 'puzzle:side-delete',
|
|
profileId: 'puzzle-profile-side-delete',
|
|
ownerUserId: 'user-1',
|
|
authorDisplayName: '拼图作者',
|
|
levelName: '侧边删除拼图',
|
|
summary: '不同来源都应有侧边删除。',
|
|
themeTags: ['灯塔'],
|
|
coverImageSrc: null,
|
|
publicationStatus: 'draft',
|
|
updatedAt: new Date('2026-05-02T12:00:00.000Z').toISOString(),
|
|
publishedAt: null,
|
|
playCount: 0,
|
|
remixCount: 0,
|
|
likeCount: 0,
|
|
publishReady: true,
|
|
},
|
|
]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
onDeletePublished={() => {}}
|
|
onDeleteBabyObjectMatch={() => {}}
|
|
onDeletePuzzle={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.queryByRole('button', { name: '删除' })).toBeNull();
|
|
expect(
|
|
container.querySelectorAll('.creation-work-card__swipe-underlay'),
|
|
).toHaveLength(3);
|
|
});
|
|
|
|
test('creation hub shows delete action for baby object match drafts', async () => {
|
|
const user = userEvent.setup();
|
|
const onDeleteBabyObjectMatch = vi.fn();
|
|
const onOpenBabyObjectMatchDetail = vi.fn();
|
|
|
|
const { container } = render(
|
|
<CustomWorldCreationHub
|
|
items={[]}
|
|
babyObjectMatchItems={[babyObjectMatchDraftItem]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
onOpenBabyObjectMatchDetail={onOpenBabyObjectMatchDetail}
|
|
onDeleteBabyObjectMatch={onDeleteBabyObjectMatch}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
screen.getByRole('button', { name: /继续创作《宝贝识物删除测试》/u }).focus();
|
|
await user.keyboard('{ArrowLeft}');
|
|
|
|
await user.click(
|
|
container.querySelector(
|
|
'.creation-work-card__swipe-button--danger',
|
|
) as HTMLButtonElement,
|
|
);
|
|
|
|
expect(onDeleteBabyObjectMatch).toHaveBeenCalledWith(
|
|
babyObjectMatchDraftItem,
|
|
);
|
|
expect(onOpenBabyObjectMatchDetail).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('creation hub works-only tab filters bark battle draft and published works', async () => {
|
|
const user = userEvent.setup();
|
|
const onOpenBarkBattleDetail = vi.fn();
|
|
|
|
render(
|
|
<CustomWorldCreationHub
|
|
mode="works-only"
|
|
items={[]}
|
|
barkBattleItems={[barkBattleDraftItem, barkBattlePublishedItem]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
onOpenBarkBattleDetail={onOpenBarkBattleDetail}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByRole('tab', { name: '全部 2' })).toBeTruthy();
|
|
expect(screen.getByRole('tab', { name: '草稿 1' })).toBeTruthy();
|
|
expect(screen.getByRole('tab', { name: '已发布 1' })).toBeTruthy();
|
|
expect(screen.getByText('竖屏声浪草稿')).toBeTruthy();
|
|
expect(screen.getByText('竖屏声浪已发布')).toBeTruthy();
|
|
|
|
await user.click(screen.getByRole('tab', { name: '草稿 1' }));
|
|
expect(screen.getByText('竖屏声浪草稿')).toBeTruthy();
|
|
expect(screen.queryByText('竖屏声浪已发布')).toBeNull();
|
|
|
|
await user.click(screen.getByRole('tab', { name: '已发布 1' }));
|
|
expect(screen.queryByText('竖屏声浪草稿')).toBeNull();
|
|
expect(screen.getByText('竖屏声浪已发布')).toBeTruthy();
|
|
|
|
await user.click(
|
|
screen.getByRole('button', { name: /查看详情《竖屏声浪已发布》/u }),
|
|
);
|
|
expect(onOpenBarkBattleDetail).toHaveBeenCalledWith(barkBattlePublishedItem);
|
|
});
|
|
|
|
test('creation hub published work delete action stays in revealed side actions', async () => {
|
|
const user = userEvent.setup();
|
|
const onDeletePuzzle = vi.fn();
|
|
const onOpenPuzzleDetail = vi.fn();
|
|
|
|
render(
|
|
<CustomWorldCreationHub
|
|
items={[]}
|
|
puzzleItems={[
|
|
{
|
|
workId: 'puzzle:work-delete',
|
|
profileId: 'puzzle-profile-delete',
|
|
ownerUserId: 'user-1',
|
|
authorDisplayName: '拼图作者',
|
|
levelName: '待删拼图',
|
|
summary: '已发布作品也可以从创作页删除。',
|
|
themeTags: ['灯塔'],
|
|
coverImageSrc: null,
|
|
publicationStatus: 'published',
|
|
updatedAt: new Date('2026-05-02T12:00:00.000Z').toISOString(),
|
|
publishedAt: new Date('2026-05-02T12:10:00.000Z').toISOString(),
|
|
playCount: 8,
|
|
remixCount: 2,
|
|
likeCount: 1,
|
|
publishReady: true,
|
|
},
|
|
]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
onOpenPuzzleDetail={onOpenPuzzleDetail}
|
|
onDeletePuzzle={onDeletePuzzle}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.queryByRole('button', { name: '删除' })).toBeNull();
|
|
expect(screen.getByRole('button', { name: '分享' })).toBeTruthy();
|
|
|
|
screen.getByRole('button', { name: /查看详情《待删拼图》/u }).focus();
|
|
await user.keyboard('{ArrowLeft}');
|
|
await user.click(screen.getByRole('button', { name: '删除' }));
|
|
|
|
expect(onDeletePuzzle).toHaveBeenCalledWith(
|
|
expect.objectContaining({ profileId: 'puzzle-profile-delete' }),
|
|
);
|
|
expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('creation hub reveals draft work delete action from keyboard', async () => {
|
|
const user = userEvent.setup();
|
|
const onDeletePuzzle = vi.fn();
|
|
const onOpenPuzzleDetail = vi.fn();
|
|
|
|
render(
|
|
<CustomWorldCreationHub
|
|
items={[]}
|
|
puzzleItems={[
|
|
{
|
|
workId: 'puzzle:direct-delete',
|
|
profileId: 'puzzle-profile-direct-delete',
|
|
ownerUserId: 'user-1',
|
|
authorDisplayName: '拼图作者',
|
|
levelName: '直接删除拼图',
|
|
summary: '作品卡片直接开放删除入口。',
|
|
themeTags: ['灯塔'],
|
|
coverImageSrc: null,
|
|
publicationStatus: 'draft',
|
|
updatedAt: new Date('2026-05-02T12:00:00.000Z').toISOString(),
|
|
publishedAt: null,
|
|
playCount: 0,
|
|
remixCount: 0,
|
|
likeCount: 0,
|
|
publishReady: true,
|
|
},
|
|
]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
onOpenPuzzleDetail={onOpenPuzzleDetail}
|
|
onDeletePuzzle={onDeletePuzzle}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.queryByRole('button', { name: '删除' })).toBeNull();
|
|
|
|
screen.getByRole('button', { name: /继续创作《直接删除拼图》/u }).focus();
|
|
await user.keyboard('{ArrowLeft}');
|
|
await user.click(screen.getByRole('button', { name: '删除' }));
|
|
|
|
expect(onDeletePuzzle).toHaveBeenCalledWith(
|
|
expect.objectContaining({ profileId: 'puzzle-profile-direct-delete' }),
|
|
);
|
|
expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('creation hub keeps swipe delete action available', async () => {
|
|
const user = userEvent.setup();
|
|
const onDeletePuzzle = vi.fn();
|
|
const onOpenPuzzleDetail = vi.fn();
|
|
|
|
const { container } = render(
|
|
<CustomWorldCreationHub
|
|
items={[]}
|
|
puzzleItems={[
|
|
{
|
|
workId: 'puzzle:swipe-delete',
|
|
profileId: 'puzzle-profile-swipe-delete',
|
|
ownerUserId: 'user-1',
|
|
authorDisplayName: '拼图作者',
|
|
levelName: '左滑删除拼图',
|
|
summary: '左滑仍然保留辅助删除入口。',
|
|
themeTags: ['灯塔'],
|
|
coverImageSrc: null,
|
|
publicationStatus: 'published',
|
|
updatedAt: new Date('2026-05-02T12:00:00.000Z').toISOString(),
|
|
publishedAt: new Date('2026-05-02T12:10:00.000Z').toISOString(),
|
|
playCount: 8,
|
|
remixCount: 2,
|
|
likeCount: 1,
|
|
publishReady: true,
|
|
},
|
|
]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
onOpenPuzzleDetail={onOpenPuzzleDetail}
|
|
onDeletePuzzle={onDeletePuzzle}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
const card = screen.getByRole('button', {
|
|
name: /查看详情《左滑删除拼图》/u,
|
|
});
|
|
fireEvent.touchStart(card, {
|
|
touches: [{ clientX: 180, clientY: 20 }],
|
|
});
|
|
fireEvent.touchMove(card, {
|
|
touches: [{ clientX: 80, clientY: 22 }],
|
|
});
|
|
fireEvent.touchEnd(card);
|
|
|
|
const swipeDeleteButton = container.querySelector(
|
|
'.creation-work-card__swipe-button--danger',
|
|
) as HTMLButtonElement | null;
|
|
expect(swipeDeleteButton).toBeTruthy();
|
|
await user.click(swipeDeleteButton!);
|
|
|
|
expect(onDeletePuzzle).toHaveBeenCalledWith(
|
|
expect.objectContaining({ profileId: 'puzzle-profile-swipe-delete' }),
|
|
);
|
|
expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('creation hub opens persisted rpg drafts by card click', async () => {
|
|
const user = userEvent.setup();
|
|
const openedItems: CustomWorldWorkSummary[] = [];
|
|
const persistedDraft = {
|
|
...baseDraftItem,
|
|
workId: 'draft:profile-1',
|
|
sourceType: 'published_profile' as const,
|
|
sessionId: null,
|
|
profileId: 'profile-1',
|
|
title: '可继续整理的草稿',
|
|
};
|
|
|
|
render(
|
|
<CustomWorldCreationHub
|
|
items={[persistedDraft]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={(item) => {
|
|
openedItems.push(item);
|
|
}}
|
|
onEnterPublished={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
await user.click(
|
|
screen.getByRole('button', { name: /继续完善《可继续整理的草稿》/u }),
|
|
);
|
|
|
|
expect(openedItems).toEqual([persistedDraft]);
|
|
});
|
|
|
|
test('creation hub published share icon opens unified share payload without opening the card', async () => {
|
|
const user = userEvent.setup();
|
|
const onOpenPuzzleDetail = vi.fn();
|
|
const onShareWork = vi.fn();
|
|
|
|
render(
|
|
<CustomWorldCreationHub
|
|
items={[]}
|
|
puzzleItems={[
|
|
{
|
|
workId: 'puzzle:work-1',
|
|
profileId: 'puzzle-profile-1',
|
|
ownerUserId: 'user-1',
|
|
authorDisplayName: '拼图作者',
|
|
levelName: '沉钟拼图',
|
|
summary: '拼图作品会与其他创作作品一起展示。',
|
|
themeTags: ['潮雾', '沉钟'],
|
|
coverImageSrc: null,
|
|
publicationStatus: 'published',
|
|
updatedAt: new Date('2026-04-22T12:00:00.000Z').toISOString(),
|
|
publishedAt: new Date('2026-04-22T12:10:00.000Z').toISOString(),
|
|
playCount: 8,
|
|
remixCount: 2,
|
|
likeCount: 0,
|
|
publishReady: true,
|
|
},
|
|
]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
onOpenPuzzleDetail={onOpenPuzzleDetail}
|
|
onShareWork={onShareWork}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
const shareButton = screen.getByRole('button', { name: '分享' });
|
|
expect(shareButton).toBeTruthy();
|
|
expect(screen.queryByText('删除')).toBeNull();
|
|
|
|
await user.click(shareButton);
|
|
|
|
expect(onShareWork).toHaveBeenCalledWith({
|
|
title: '沉钟拼图',
|
|
publicWorkCode: 'PZ-PROFILE1',
|
|
stage: 'puzzle-gallery-detail',
|
|
workTypeLabel: '拼图',
|
|
coverImageSrc: null,
|
|
fallbackCoverImageSrc: '/creation-type-references/puzzle.webp',
|
|
});
|
|
expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('creation hub published share icon is shown directly on the card header', () => {
|
|
render(
|
|
<CustomWorldCreationHub
|
|
items={[]}
|
|
puzzleItems={[
|
|
{
|
|
workId: 'puzzle:work-share-icon',
|
|
profileId: 'puzzle-profile-share-icon',
|
|
ownerUserId: 'user-1',
|
|
authorDisplayName: '拼图作者',
|
|
levelName: '沉钟拼图',
|
|
summary: '分享入口应直接露出在卡片右上角。',
|
|
themeTags: ['潮雾'],
|
|
coverImageSrc: null,
|
|
publicationStatus: 'published',
|
|
updatedAt: new Date('2026-04-22T12:00:00.000Z').toISOString(),
|
|
publishedAt: new Date('2026-04-22T12:10:00.000Z').toISOString(),
|
|
playCount: 8,
|
|
remixCount: 2,
|
|
likeCount: 0,
|
|
publishReady: true,
|
|
},
|
|
]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
onOpenPuzzleDetail={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByRole('button', { name: '分享' })).toBeTruthy();
|
|
expect(screen.queryByRole('button', { name: '删除' })).toBeNull();
|
|
});
|
|
|
|
test('creation hub shows RPG published share icon without library entry', () => {
|
|
render(
|
|
<CustomWorldCreationHub
|
|
items={[
|
|
{
|
|
...baseDraftItem,
|
|
workId: 'published:world-public-1',
|
|
sourceType: 'published_profile',
|
|
status: 'published',
|
|
title: '潮雾列岛已发布版',
|
|
profileId: 'world-public-1',
|
|
canResume: false,
|
|
canEnterWorld: true,
|
|
},
|
|
]}
|
|
rpgLibraryEntries={[]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={() => {}}
|
|
onEnterPublished={() => {}}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText('潮雾列岛已发布版')).toBeTruthy();
|
|
expect(screen.getByRole('button', { name: '分享' })).toBeTruthy();
|
|
expect(screen.queryByText('作者:玩家')).toBeNull();
|
|
});
|
|
|
|
test('creation hub left swipe draft reveals delete without opening card', () => {
|
|
const onDeletePublished = vi.fn();
|
|
const onOpenDraft = vi.fn();
|
|
|
|
const { container } = render(
|
|
<CustomWorldCreationHub
|
|
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
|
loading={false}
|
|
error={null}
|
|
onRetry={() => {}}
|
|
onCreateType={noopCreateType}
|
|
onOpenDraft={onOpenDraft}
|
|
onEnterPublished={() => {}}
|
|
onDeletePublished={onDeletePublished}
|
|
entryConfig={testEntryConfig}
|
|
creationTypes={testCreationTypes}
|
|
/>,
|
|
);
|
|
|
|
const card = screen.getByRole('button', { name: /继续完善《潮雾列岛》/u });
|
|
fireEvent.touchStart(card, {
|
|
touches: [{ clientX: 180, clientY: 20 }],
|
|
});
|
|
fireEvent.touchMove(card, {
|
|
touches: [{ clientX: 88, clientY: 22 }],
|
|
});
|
|
fireEvent.touchEnd(card);
|
|
|
|
expect(
|
|
container.querySelector('.creation-work-card__swipe-button--danger'),
|
|
).toBeTruthy();
|
|
expect(onOpenDraft).not.toHaveBeenCalled();
|
|
});
|