Add big fish settlement actions and publish feedback
This commit is contained in:
80
src/components/big-fish-runtime/BigFishRuntimeShell.test.tsx
Normal file
80
src/components/big-fish-runtime/BigFishRuntimeShell.test.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import type { BigFishRuntimeSnapshotResponse } from '../../../packages/shared/src/contracts/bigFish';
|
||||
import { BigFishRuntimeShell } from './BigFishRuntimeShell';
|
||||
|
||||
vi.mock('../ResolvedAssetImage', () => ({
|
||||
ResolvedAssetImage: ({
|
||||
src,
|
||||
alt,
|
||||
className,
|
||||
}: {
|
||||
src?: string | null;
|
||||
alt?: string;
|
||||
className?: string;
|
||||
}) => (src ? <img src={src} alt={alt} className={className} /> : null),
|
||||
}));
|
||||
|
||||
function createRun(
|
||||
status: BigFishRuntimeSnapshotResponse['status'],
|
||||
): BigFishRuntimeSnapshotResponse {
|
||||
return {
|
||||
runId: 'big-fish-run-1',
|
||||
sessionId: 'big-fish-session-1',
|
||||
status,
|
||||
tick: 18,
|
||||
playerLevel: 2,
|
||||
winLevel: 5,
|
||||
leaderEntityId: null,
|
||||
ownedEntities: [],
|
||||
wildEntities: [],
|
||||
cameraCenter: { x: 0, y: 0 },
|
||||
lastInput: { x: 0, y: 0 },
|
||||
eventLog: ['己方鱼群已经耗尽'],
|
||||
updatedAt: '2026-04-26T12:00:00.000Z',
|
||||
};
|
||||
}
|
||||
|
||||
describe('BigFishRuntimeShell', () => {
|
||||
test('renders restart and exit actions after a failed run', () => {
|
||||
const onBack = vi.fn();
|
||||
const onRestart = vi.fn();
|
||||
|
||||
render(
|
||||
<BigFishRuntimeShell
|
||||
run={createRun('failed')}
|
||||
onBack={onBack}
|
||||
onRestart={onRestart}
|
||||
onSubmitInput={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '重来' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '退出' }));
|
||||
|
||||
expect(screen.getByText('本轮失败')).toBeTruthy();
|
||||
expect(onRestart).toHaveBeenCalledTimes(1);
|
||||
expect(onBack).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('keeps an exit action after a won run', () => {
|
||||
const onBack = vi.fn();
|
||||
|
||||
render(
|
||||
<BigFishRuntimeShell
|
||||
run={createRun('won')}
|
||||
onBack={onBack}
|
||||
onSubmitInput={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('通关完成')).toBeTruthy();
|
||||
expect(screen.queryByRole('button', { name: '重来' })).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '退出' }));
|
||||
expect(onBack).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ArrowLeft, Loader2 } from 'lucide-react';
|
||||
import { useEffect, useRef, useState, type PointerEvent } from 'react';
|
||||
import { ArrowLeft, Loader2, RotateCcw } from 'lucide-react';
|
||||
import { type PointerEvent, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import type {
|
||||
BigFishAssetSlotResponse,
|
||||
@@ -21,6 +21,7 @@ type BigFishRuntimeShellProps = {
|
||||
isBusy?: boolean;
|
||||
error?: string | null;
|
||||
onBack: () => void;
|
||||
onRestart?: () => void;
|
||||
onSubmitInput: (payload: SubmitBigFishInputRequest) => void;
|
||||
};
|
||||
|
||||
@@ -188,6 +189,7 @@ export function BigFishRuntimeShell({
|
||||
isBusy = false,
|
||||
error = null,
|
||||
onBack,
|
||||
onRestart,
|
||||
onSubmitInput,
|
||||
}: BigFishRuntimeShellProps) {
|
||||
const stageRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -200,6 +202,10 @@ export function BigFishRuntimeShell({
|
||||
}, [stick]);
|
||||
|
||||
useEffect(() => {
|
||||
if (run?.status !== 'running') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const timer = window.setInterval(() => {
|
||||
const current = stickRef.current;
|
||||
// 即使没有方向输入也持续回传当前状态,让后端持续推进刷怪、清理与胜负裁决。
|
||||
@@ -209,7 +215,7 @@ export function BigFishRuntimeShell({
|
||||
return () => {
|
||||
window.clearInterval(timer);
|
||||
};
|
||||
}, [onSubmitInput]);
|
||||
}, [onSubmitInput, run?.status]);
|
||||
|
||||
const submitDirection = (direction: SubmitBigFishInputRequest) => {
|
||||
setStick(direction);
|
||||
@@ -318,16 +324,39 @@ export function BigFishRuntimeShell({
|
||||
</div>
|
||||
|
||||
{settlementCopy ? (
|
||||
<div className="pointer-events-none absolute inset-0 z-40 flex items-center justify-center px-5">
|
||||
<div className="absolute inset-0 z-40 flex items-center justify-center px-5">
|
||||
<div
|
||||
className={`w-full max-w-[20rem] rounded-[2rem] border border-white/24 bg-gradient-to-br ${settlementCopy.tone} p-6 text-center shadow-2xl shadow-slate-950/45 backdrop-blur-xl`}
|
||||
className={`w-full max-w-[20rem] rounded-[1.5rem] border border-white/24 bg-gradient-to-br ${settlementCopy.tone} p-6 text-center shadow-2xl shadow-slate-950/45 backdrop-blur-xl`}
|
||||
>
|
||||
<div className="text-3xl font-black tracking-[0.22em] text-white [text-shadow:0_2px_12px_rgba(2,6,23,0.6)]">
|
||||
<div className="text-3xl font-black text-white [text-shadow:0_2px_12px_rgba(2,6,23,0.6)]">
|
||||
{settlementCopy.title}
|
||||
</div>
|
||||
<div className="mt-3 text-sm font-semibold leading-6 text-white/82">
|
||||
{settlementCopy.message}
|
||||
</div>
|
||||
<div className="mt-5 grid grid-cols-2 gap-2">
|
||||
{run.status === 'failed' && onRestart ? (
|
||||
<button
|
||||
type="button"
|
||||
disabled={isBusy}
|
||||
onClick={onRestart}
|
||||
className="inline-flex h-11 items-center justify-center gap-2 rounded-full bg-white px-4 text-sm font-bold text-slate-950 shadow-lg shadow-slate-950/20 disabled:opacity-45"
|
||||
>
|
||||
<RotateCcw className="h-4 w-4" />
|
||||
重来
|
||||
</button>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onBack}
|
||||
className={`inline-flex h-11 items-center justify-center gap-2 rounded-full border border-white/30 bg-black/24 px-4 text-sm font-bold text-white backdrop-blur ${
|
||||
run.status === 'failed' && onRestart ? '' : 'col-span-2'
|
||||
}`}
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
退出
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user