继续收口生成页顶部返回按钮
抽出生成页共用返回按钮壳并复用共享图标按钮能力 将自定义世界生成页接入共享返回按钮壳 将汪汪声浪生成页接入共享返回按钮壳并保留禁用态 补充两个生成页返回按钮的样式与交互回归测试
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
/* @vitest-environment jsdom */
|
/* @vitest-environment jsdom */
|
||||||
|
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
import type { CustomWorldGenerationProgress } from '../../packages/shared/src/contracts/runtime';
|
import type { CustomWorldGenerationProgress } from '../../packages/shared/src/contracts/runtime';
|
||||||
import { CustomWorldGenerationView } from './CustomWorldGenerationView';
|
import { CustomWorldGenerationView } from './CustomWorldGenerationView';
|
||||||
@@ -104,6 +105,12 @@ describe('CustomWorldGenerationView', () => {
|
|||||||
expect(
|
expect(
|
||||||
screen.getByRole('button', { name: '返回创作中心' }).className,
|
screen.getByRole('button', { name: '返回创作中心' }).className,
|
||||||
).toContain('text-xs');
|
).toContain('text-xs');
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: '返回创作中心' }).className,
|
||||||
|
).toContain('bg-transparent');
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: '返回创作中心' }).className,
|
||||||
|
).toContain('gap-2');
|
||||||
expect(screen.getByText('世界建设中')).toBeTruthy();
|
expect(screen.getByText('世界建设中')).toBeTruthy();
|
||||||
expect(screen.getByText('世界建设中').className).toContain('text-xs');
|
expect(screen.getByText('世界建设中').className).toContain('text-xs');
|
||||||
expect(screen.getByText('世界建设中').className).toContain(
|
expect(screen.getByText('世界建设中').className).toContain(
|
||||||
@@ -289,4 +296,29 @@ describe('CustomWorldGenerationView', () => {
|
|||||||
expect(screen.queryByText('大鱼吃小鱼题材')).toBeNull();
|
expect(screen.queryByText('大鱼吃小鱼题材')).toBeNull();
|
||||||
expect(screen.getByTestId('generation-page-background-video')).toBeTruthy();
|
expect(screen.getByTestId('generation-page-background-video')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('keeps the shared generation back button click behavior', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onBack = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CustomWorldGenerationView
|
||||||
|
settingText="大鱼吃小鱼题材"
|
||||||
|
progress={createProgress()}
|
||||||
|
isGenerating
|
||||||
|
error={null}
|
||||||
|
onBack={onBack}
|
||||||
|
onEditSetting={() => {}}
|
||||||
|
onRetry={() => {}}
|
||||||
|
backLabel="返回创作中心"
|
||||||
|
settingDescription={null}
|
||||||
|
settingActionLabel={null}
|
||||||
|
progressTitle="大鱼吃小鱼草稿生成进度"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: '返回创作中心' }));
|
||||||
|
|
||||||
|
expect(onBack).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { ArrowLeft } from 'lucide-react';
|
|
||||||
|
|
||||||
import type { CustomWorldGenerationProgress } from '../../packages/shared/src/contracts/runtime';
|
import type { CustomWorldGenerationProgress } from '../../packages/shared/src/contracts/runtime';
|
||||||
import type { CustomWorldStructuredAnchorEntry } from '../services/customWorldAgentGenerationProgress';
|
import type { CustomWorldStructuredAnchorEntry } from '../services/customWorldAgentGenerationProgress';
|
||||||
import { PlatformActionButton } from './common/PlatformActionButton';
|
import { PlatformActionButton } from './common/PlatformActionButton';
|
||||||
import { PlatformPillBadge } from './common/PlatformPillBadge';
|
import { PlatformPillBadge } from './common/PlatformPillBadge';
|
||||||
import {
|
import {
|
||||||
GenerationCurrentStepCard,
|
GenerationCurrentStepCard,
|
||||||
|
GenerationHeaderBackButton,
|
||||||
GenerationPageBackdrop,
|
GenerationPageBackdrop,
|
||||||
GenerationProgressHero,
|
GenerationProgressHero,
|
||||||
} from './GenerationProgressHero';
|
} from './GenerationProgressHero';
|
||||||
@@ -137,14 +136,7 @@ export function CustomWorldGenerationView({
|
|||||||
<div className="relative isolate z-[1] -mx-3 -my-3 flex h-[calc(100%+1.5rem)] min-h-0 flex-col overflow-hidden bg-transparent px-4 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-4 text-[#3d1f10] sm:mx-0 sm:my-0 sm:h-full sm:rounded-[2rem] sm:px-5 sm:pt-5">
|
<div className="relative isolate z-[1] -mx-3 -my-3 flex h-[calc(100%+1.5rem)] min-h-0 flex-col overflow-hidden bg-transparent px-4 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-4 text-[#3d1f10] sm:mx-0 sm:my-0 sm:h-full sm:rounded-[2rem] sm:px-5 sm:pt-5">
|
||||||
<GenerationPageBackdrop />
|
<GenerationPageBackdrop />
|
||||||
<div className="relative z-30 mb-4 flex shrink-0 items-center justify-between gap-3 py-2 sm:mb-5">
|
<div className="relative z-30 mb-4 flex shrink-0 items-center justify-between gap-3 py-2 sm:mb-5">
|
||||||
<button
|
<GenerationHeaderBackButton label={backLabel} onClick={onBack} />
|
||||||
type="button"
|
|
||||||
onClick={onBack}
|
|
||||||
className="inline-flex items-center gap-2 rounded-full bg-transparent px-0 py-2 text-xs font-black text-[#171411] sm:text-sm"
|
|
||||||
>
|
|
||||||
<ArrowLeft className="h-5 w-5 shrink-0" strokeWidth={2.6} />
|
|
||||||
<span className="break-keep">{backLabel}</span>
|
|
||||||
</button>
|
|
||||||
<PlatformPillBadge
|
<PlatformPillBadge
|
||||||
tone="warning"
|
tone="warning"
|
||||||
size="xs"
|
size="xs"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Clock3, Hourglass } from 'lucide-react';
|
import { ArrowLeft, Clock3, Hourglass } from 'lucide-react';
|
||||||
import { useEffect, useId, useRef } from 'react';
|
import { useEffect, useId, useRef } from 'react';
|
||||||
|
|
||||||
import generationHeroVideo from '../../media/create_bg_video.mp4';
|
import generationHeroVideo from '../../media/create_bg_video.mp4';
|
||||||
|
import { PlatformIconButton } from './common/PlatformIconButton';
|
||||||
import { PlatformProgressBar } from './common/PlatformProgressBar';
|
import { PlatformProgressBar } from './common/PlatformProgressBar';
|
||||||
|
|
||||||
const GENERATION_PROGRESS_RING_GAP_DEGREES = 90;
|
const GENERATION_PROGRESS_RING_GAP_DEGREES = 90;
|
||||||
@@ -35,6 +36,14 @@ type GenerationCurrentStepCardProps = {
|
|||||||
progressValue: number;
|
progressValue: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GenerationHeaderBackButtonProps = {
|
||||||
|
label: string;
|
||||||
|
onClick: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
disabledOpacity?: number;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
function clampGenerationProgress(value: number) {
|
function clampGenerationProgress(value: number) {
|
||||||
return Math.max(0, Math.min(100, Math.round(value)));
|
return Math.max(0, Math.min(100, Math.round(value)));
|
||||||
}
|
}
|
||||||
@@ -51,6 +60,34 @@ function buildGenerationRingMetrics(progressValue: number) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GenerationHeaderBackButton({
|
||||||
|
label,
|
||||||
|
onClick,
|
||||||
|
disabled = false,
|
||||||
|
disabledOpacity,
|
||||||
|
className,
|
||||||
|
}: GenerationHeaderBackButtonProps) {
|
||||||
|
return (
|
||||||
|
<PlatformIconButton
|
||||||
|
label={label}
|
||||||
|
title={label}
|
||||||
|
variant="darkMini"
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
className={[
|
||||||
|
'gap-2 rounded-full !border-transparent !bg-transparent px-0 py-2 text-xs font-black !text-[#171411] shadow-none hover:!bg-transparent hover:!text-[#171411] sm:text-sm',
|
||||||
|
className,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')}
|
||||||
|
style={disabled && disabledOpacity != null ? { opacity: disabledOpacity } : undefined}
|
||||||
|
icon={<ArrowLeft className="h-5 w-5 shrink-0" strokeWidth={2.6} />}
|
||||||
|
>
|
||||||
|
<span className="break-keep">{label}</span>
|
||||||
|
</PlatformIconButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function GenerationPageBackdrop() {
|
export function GenerationPageBackdrop() {
|
||||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/* @vitest-environment jsdom */
|
/* @vitest-environment jsdom */
|
||||||
|
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
import { render, screen, waitFor } from '@testing-library/react';
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
@@ -87,6 +88,12 @@ describe('BarkBattleGeneratingView', () => {
|
|||||||
expect(
|
expect(
|
||||||
screen.getByRole('button', { name: '返回编辑' }).className,
|
screen.getByRole('button', { name: '返回编辑' }).className,
|
||||||
).toContain('text-xs');
|
).toContain('text-xs');
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: '返回编辑' }).className,
|
||||||
|
).toContain('bg-transparent');
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: '返回编辑' }).className,
|
||||||
|
).toContain('gap-2');
|
||||||
expect(screen.getByText('生成中').className).toContain('text-[11px]');
|
expect(screen.getByText('生成中').className).toContain('text-[11px]');
|
||||||
expect(screen.getByText('生成中').className).toContain(
|
expect(screen.getByText('生成中').className).toContain(
|
||||||
'border-[var(--platform-warm-border)]',
|
'border-[var(--platform-warm-border)]',
|
||||||
@@ -479,4 +486,42 @@ describe('BarkBattleGeneratingView', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps the shared generation back button disabled state and click behavior', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onBack = vi.fn();
|
||||||
|
vi.mocked(generateAllBarkBattleImageAssets).mockReturnValue(
|
||||||
|
new Promise<BarkBattleImageGenerationBatchResult>(() => {}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { rerender } = render(
|
||||||
|
<BarkBattleGeneratingView
|
||||||
|
draft={draft}
|
||||||
|
isBusy
|
||||||
|
onBack={onBack}
|
||||||
|
onComplete={() => {}}
|
||||||
|
onError={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const busyBackButton = screen.getByRole('button', { name: '返回编辑' });
|
||||||
|
expect(busyBackButton.getAttribute('disabled')).toBe('');
|
||||||
|
expect(busyBackButton.style.opacity).toBe('0.45');
|
||||||
|
|
||||||
|
await user.click(busyBackButton);
|
||||||
|
expect(onBack).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<BarkBattleGeneratingView
|
||||||
|
draft={draft}
|
||||||
|
isBusy={false}
|
||||||
|
onBack={onBack}
|
||||||
|
onComplete={() => {}}
|
||||||
|
onError={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: '返回编辑' }));
|
||||||
|
expect(onBack).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ArrowLeft } from 'lucide-react';
|
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
import type { BarkBattleDraftConfig } from '../../../packages/shared/src/contracts/barkBattle';
|
import type { BarkBattleDraftConfig } from '../../../packages/shared/src/contracts/barkBattle';
|
||||||
@@ -15,6 +14,7 @@ import {
|
|||||||
import { PlatformPillBadge } from '../common/PlatformPillBadge';
|
import { PlatformPillBadge } from '../common/PlatformPillBadge';
|
||||||
import {
|
import {
|
||||||
GenerationCurrentStepCard,
|
GenerationCurrentStepCard,
|
||||||
|
GenerationHeaderBackButton,
|
||||||
GenerationPageBackdrop,
|
GenerationPageBackdrop,
|
||||||
GenerationProgressHero,
|
GenerationProgressHero,
|
||||||
} from '../GenerationProgressHero';
|
} from '../GenerationProgressHero';
|
||||||
@@ -367,15 +367,12 @@ export function BarkBattleGeneratingView({
|
|||||||
<div className="relative isolate z-[1] -mx-3 -my-3 flex h-[calc(100%+1.5rem)] min-h-0 flex-col overflow-hidden bg-transparent px-4 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-4 text-[#3d1f10] sm:mx-0 sm:my-0 sm:h-full sm:rounded-[2rem] sm:px-5 sm:pt-5 xl:px-6 xl:pb-5 xl:pt-5">
|
<div className="relative isolate z-[1] -mx-3 -my-3 flex h-[calc(100%+1.5rem)] min-h-0 flex-col overflow-hidden bg-transparent px-4 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-4 text-[#3d1f10] sm:mx-0 sm:my-0 sm:h-full sm:rounded-[2rem] sm:px-5 sm:pt-5 xl:px-6 xl:pb-5 xl:pt-5">
|
||||||
<GenerationPageBackdrop />
|
<GenerationPageBackdrop />
|
||||||
<div className="relative z-30 mx-auto mb-4 flex w-full max-w-[48rem] shrink-0 items-center justify-between gap-3 sm:mb-5">
|
<div className="relative z-30 mx-auto mb-4 flex w-full max-w-[48rem] shrink-0 items-center justify-between gap-3 sm:mb-5">
|
||||||
<button
|
<GenerationHeaderBackButton
|
||||||
type="button"
|
label="返回编辑"
|
||||||
onClick={onBack}
|
onClick={onBack}
|
||||||
disabled={isBusy}
|
disabled={isBusy}
|
||||||
className={`inline-flex items-center gap-2 rounded-full bg-transparent px-0 py-2 text-xs font-black text-[#171411] sm:text-sm ${isBusy ? 'opacity-45' : ''}`}
|
disabledOpacity={0.45}
|
||||||
>
|
/>
|
||||||
<ArrowLeft className="h-5 w-5" strokeWidth={2.6} />
|
|
||||||
<span className="break-keep">返回编辑</span>
|
|
||||||
</button>
|
|
||||||
<PlatformPillBadge
|
<PlatformPillBadge
|
||||||
tone="warning"
|
tone="warning"
|
||||||
size="xs"
|
size="xs"
|
||||||
|
|||||||
Reference in New Issue
Block a user