fix: stabilize match3d demo discovery
This commit is contained in:
@@ -82,7 +82,10 @@ import {
|
||||
saveBabyObjectMatchDraft,
|
||||
} from '../../services/edutainment-baby-object';
|
||||
import { match3dCreationClient } from '../../services/match3d-creation';
|
||||
import { createServerMatch3DRuntimeAdapter } from '../../services/match3d-runtime';
|
||||
import {
|
||||
createLocalMatch3DRuntimeAdapter,
|
||||
createServerMatch3DRuntimeAdapter,
|
||||
} from '../../services/match3d-runtime';
|
||||
import {
|
||||
deleteMatch3DWork,
|
||||
getMatch3DWorkDetail,
|
||||
@@ -556,6 +559,7 @@ vi.mock('../../services/match3dGeneratedModelCache', () => ({
|
||||
}));
|
||||
|
||||
const match3dRuntimeServiceMocks = vi.hoisted(() => ({
|
||||
createLocalMatch3DRuntimeAdapter: vi.fn(),
|
||||
createServerMatch3DRuntimeAdapter: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -568,6 +572,15 @@ const match3dServerRuntimeAdapterMock = vi.hoisted(() => ({
|
||||
stopRun: vi.fn(),
|
||||
}));
|
||||
|
||||
const match3dLocalRuntimeAdapterMock = vi.hoisted(() => ({
|
||||
clickItem: vi.fn(),
|
||||
finishTimeUp: vi.fn(),
|
||||
getRun: vi.fn(),
|
||||
restartRun: vi.fn(),
|
||||
startRun: vi.fn(),
|
||||
stopRun: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/match3d-runtime', async () => {
|
||||
const actual = await vi.importActual<
|
||||
typeof import('../../services/match3d-runtime')
|
||||
@@ -2264,6 +2277,9 @@ beforeEach(() => {
|
||||
vi.mocked(createServerMatch3DRuntimeAdapter).mockReturnValue(
|
||||
match3dServerRuntimeAdapterMock,
|
||||
);
|
||||
vi.mocked(createLocalMatch3DRuntimeAdapter).mockReturnValue(
|
||||
match3dLocalRuntimeAdapterMock,
|
||||
);
|
||||
match3dServerRuntimeAdapterMock.startRun.mockRejectedValue(
|
||||
new Error('未启动抓大鹅运行态'),
|
||||
);
|
||||
@@ -2279,6 +2295,21 @@ beforeEach(() => {
|
||||
match3dServerRuntimeAdapterMock.stopRun.mockResolvedValue({
|
||||
run: buildMockMatch3DRun('match3d-profile-stopped'),
|
||||
});
|
||||
match3dLocalRuntimeAdapterMock.startRun.mockResolvedValue({
|
||||
run: buildMockMatch3DRun('match3d-demo-20260525'),
|
||||
});
|
||||
match3dLocalRuntimeAdapterMock.clickItem.mockRejectedValue(
|
||||
new Error('未执行本地抓大鹅点击'),
|
||||
);
|
||||
match3dLocalRuntimeAdapterMock.restartRun.mockResolvedValue({
|
||||
run: buildMockMatch3DRun('match3d-demo-20260525'),
|
||||
});
|
||||
match3dLocalRuntimeAdapterMock.finishTimeUp.mockResolvedValue({
|
||||
run: buildMockMatch3DRun('match3d-demo-20260525'),
|
||||
});
|
||||
match3dLocalRuntimeAdapterMock.stopRun.mockResolvedValue({
|
||||
run: buildMockMatch3DRun('match3d-demo-20260525'),
|
||||
});
|
||||
window.history.replaceState(null, '', '/');
|
||||
window.sessionStorage.clear();
|
||||
window.localStorage.clear();
|
||||
@@ -7683,6 +7714,38 @@ test('public code search opens a published Match3D work by M3 code and starts ru
|
||||
expect(getRpgEntryWorldGalleryDetailByCode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('public code search opens the local Match3D demo and starts local runtime', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
vi.mocked(listMatch3DGallery).mockResolvedValue({ items: [] });
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
await openDiscoverHub(user);
|
||||
|
||||
const searchInput =
|
||||
await screen.findByPlaceholderText('搜索作品号、名称、作者、描述');
|
||||
await user.type(searchInput, 'M3-20260525');
|
||||
await user.click(screen.getByRole('button', { name: '搜索' }));
|
||||
|
||||
expect(await screen.findByText('详情')).toBeTruthy();
|
||||
expect(screen.getByText('海底糖果集市')).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '启动' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(match3dLocalRuntimeAdapterMock.startRun).toHaveBeenCalledWith(
|
||||
'match3d-demo-20260525',
|
||||
{},
|
||||
);
|
||||
});
|
||||
expect(match3dServerRuntimeAdapterMock.startRun).not.toHaveBeenCalled();
|
||||
expect(getMatch3DWorkDetail).not.toHaveBeenCalledWith(
|
||||
'match3d-demo-20260525',
|
||||
);
|
||||
expect(
|
||||
await screen.findByText('抓大鹅运行态:match3d-run-match3d-demo-20260525'),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('published Match3D runtime receives persisted generated models', async () => {
|
||||
const user = userEvent.setup();
|
||||
const match3dWork: Match3DWorkSummary = {
|
||||
|
||||
@@ -67,6 +67,7 @@ import type {
|
||||
WechatMiniProgramPayParams,
|
||||
WechatNativePayment,
|
||||
} from '../../../packages/shared/src/contracts/runtime';
|
||||
import { isMatch3DDemoProfileId } from '../../data/match3dDemoGalleryCard';
|
||||
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
|
||||
import { buildPublicWorkDetailUrl } from '../../routing/appPageRoutes';
|
||||
import type { AuthUser } from '../../services/authService';
|
||||
@@ -3888,6 +3889,7 @@ export function RpgEntryHomeView({
|
||||
const [mobileCenteredCardKey, setMobileCenteredCardKey] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const hasManualCategoryTagSelectionRef = useRef(false);
|
||||
const pendingPublicAuthorKeysRef = useRef<Set<string>>(new Set());
|
||||
const [publicAuthorSummariesByKey, setPublicAuthorSummariesByKey] = useState<
|
||||
Record<string, PublicUserSummary | null>
|
||||
@@ -4111,16 +4113,33 @@ export function RpgEntryHomeView({
|
||||
useEffect(() => {
|
||||
if (categoryGroups.length === 0) {
|
||||
setSelectedCategoryTag(null);
|
||||
hasManualCategoryTagSelectionRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const firstCategoryGroup = categoryGroups[0];
|
||||
const firstCategoryGroup =
|
||||
categoryGroups.find((group) =>
|
||||
group.entries.some((entry) => !isMatch3DDemoProfileId(entry.profileId)),
|
||||
) ?? categoryGroups[0];
|
||||
const selectedCategoryGroup =
|
||||
categoryGroups.find((group) => group.tag === selectedCategoryTag) ?? null;
|
||||
if (
|
||||
firstCategoryGroup &&
|
||||
!categoryGroups.some((group) => group.tag === selectedCategoryTag)
|
||||
(!selectedCategoryGroup ||
|
||||
(!hasManualCategoryTagSelectionRef.current &&
|
||||
selectedCategoryGroup.entries.every((entry) =>
|
||||
isMatch3DDemoProfileId(entry.profileId),
|
||||
) &&
|
||||
firstCategoryGroup.tag !== selectedCategoryGroup.tag))
|
||||
) {
|
||||
setSelectedCategoryTag(firstCategoryGroup.tag);
|
||||
}
|
||||
if (
|
||||
selectedCategoryTag &&
|
||||
!categoryGroups.some((group) => group.tag === selectedCategoryTag)
|
||||
) {
|
||||
hasManualCategoryTagSelectionRef.current = false;
|
||||
}
|
||||
}, [categoryGroups, selectedCategoryTag]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -5442,7 +5461,10 @@ export function RpgEntryHomeView({
|
||||
<button
|
||||
key={group.tag}
|
||||
type="button"
|
||||
onClick={() => setSelectedCategoryTag(group.tag)}
|
||||
onClick={() => {
|
||||
hasManualCategoryTagSelectionRef.current = true;
|
||||
setSelectedCategoryTag(group.tag);
|
||||
}}
|
||||
className={`platform-category-chip ${active ? 'platform-category-chip--active' : ''}`}
|
||||
>
|
||||
{group.tag}
|
||||
@@ -5640,7 +5662,10 @@ export function RpgEntryHomeView({
|
||||
<button
|
||||
key={`${group.tag}:desktop-discover-category`}
|
||||
type="button"
|
||||
onClick={() => setSelectedCategoryTag(group.tag)}
|
||||
onClick={() => {
|
||||
hasManualCategoryTagSelectionRef.current = true;
|
||||
setSelectedCategoryTag(group.tag);
|
||||
}}
|
||||
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
|
||||
>
|
||||
{group.tag}
|
||||
@@ -6398,7 +6423,10 @@ export function RpgEntryHomeView({
|
||||
<button
|
||||
key={`${group.tag}:desktop-category`}
|
||||
type="button"
|
||||
onClick={() => setSelectedCategoryTag(group.tag)}
|
||||
onClick={() => {
|
||||
hasManualCategoryTagSelectionRef.current = true;
|
||||
setSelectedCategoryTag(group.tag);
|
||||
}}
|
||||
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
|
||||
>
|
||||
{group.tag}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
formatPlatformWorldTime,
|
||||
isBarkBattleGalleryEntry,
|
||||
isEdutainmentGalleryEntry,
|
||||
isMatch3DGalleryEntry,
|
||||
isVisualNovelGalleryEntry,
|
||||
isWoodenFishGalleryEntry,
|
||||
mapBabyObjectMatchDraftToPlatformGalleryCard,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
resolvePlatformPublicWorkCode,
|
||||
resolvePlatformWorldFallbackCoverImage,
|
||||
} from './rpgEntryWorldPresentation';
|
||||
import { buildMatch3DDemoGalleryCard } from '../../data/match3dDemoGalleryCard';
|
||||
|
||||
test('formatPlatformWorldTime formats backend seconds timestamp text as date', () => {
|
||||
expect(formatPlatformWorldTime('1777110165.990127Z')).toBe('2026-04-25');
|
||||
@@ -78,6 +80,24 @@ test('platform public cards use play type reference images as cover fallback', (
|
||||
);
|
||||
});
|
||||
|
||||
test('builds local Match3D demo gallery card with generated runtime assets intact', () => {
|
||||
const card = buildMatch3DDemoGalleryCard();
|
||||
|
||||
expect(isMatch3DGalleryEntry(card)).toBe(true);
|
||||
expect(card.publicWorkCode).toBe('M3-20260525');
|
||||
expect(resolvePlatformPublicWorkCode(card)).toBe('M3-20260525');
|
||||
expect(card.coverImageSrc).toBe(
|
||||
'/match3d-demo/undersea-candy-market/level-scene.png',
|
||||
);
|
||||
expect(card.generatedBackgroundAsset?.uiSpritesheetImageSrc).toBe(
|
||||
'/match3d-demo/undersea-candy-market/ui-spritesheet.png',
|
||||
);
|
||||
expect(card.generatedBackgroundAsset?.containerImageSrc).toBeNull();
|
||||
expect(card.generatedItemAssets?.[0]?.imageViews?.[0]?.imageSrc).toBe(
|
||||
'/match3d-demo/undersea-candy-market/item-slices/item-01/view-01.png',
|
||||
);
|
||||
});
|
||||
|
||||
test('buildPuzzleWorkCoverSlides prefers each level formal image', () => {
|
||||
const slides = buildPuzzleWorkCoverSlides({
|
||||
workId: 'work-1',
|
||||
|
||||
Reference in New Issue
Block a user