Files
Genarrative/src/components/creation-home/ClientDownloadEntry.test.tsx
T
kdletters e3682fd06f
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 7m31s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 7m32s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 7m33s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 7m40s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m21s
Project CI / AI game creator shell Rust crates (push) Successful in 3m11s
Project CI / Native shell tests (push) Successful in 16m15s
Project CI / Frontend tests (push) Successful in 14m57s
Project CI / Repository checks (push) Successful in 14m48s
Project CI / Backend tests (push) Successful in 20m7s
Project CI / AI game creator shell web tests (push) Successful in 6m20s
固定客户端dev服务并支持官网多平台下载
移除服务器选择并按来源隔离登录凭据
新增官网客户端下载入口和匿名平台聚合接口
根据发布清单自动展示Windows与macOS首装包
补齐Mac首装元数据和上传顺序校验
同步定向测试与下载发布规范
2026-09-19 16:19:28 +08:00

224 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* @vitest-environment jsdom */
import {
act,
cleanup,
fireEvent,
render,
screen,
waitFor,
within,
} from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { ClientDownloadEntry } from './ClientDownloadEntry';
const fetchMock = vi.fn();
const installerUrl = (version: string) =>
`https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/dev-win/${version}/setup.exe`;
const releaseResponse = (version: string) =>
new Response(
JSON.stringify({
downloads: [
{
platform: 'windows',
architecture: 'x86_64',
version,
downloadUrl: installerUrl(version),
},
],
unavailablePlatforms: [],
}),
);
const macDownload = (architecture: 'aarch64' | 'x86_64') => ({
platform: 'macos',
architecture,
version: '0.2.0',
downloadUrl: `https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/dev-mac/0.2.0/app_${architecture}.dmg`,
});
describe('官网下载入口', () => {
beforeEach(() => {
fetchMock.mockReset();
vi.stubGlobal('fetch', fetchMock);
});
afterEach(() => {
cleanup();
vi.unstubAllGlobals();
vi.useRealTimers();
});
it('无需登录,每次打开重新获取最新版本和安装包链接', async () => {
fetchMock.mockImplementationOnce(() =>
Promise.resolve(releaseResponse('0.1.73')),
);
render(<ClientDownloadEntry />);
expect(fetchMock).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole('button', { name: '下载客户端' }));
expect(
(
await screen.findByRole('link', { name: '下载 Windows 版' })
).getAttribute('href'),
).toBe(installerUrl('0.1.73'));
expect(screen.getByText('最新版本 v0.1.73')).toBeTruthy();
expect(fetchMock).toHaveBeenCalledWith(
'/api/client-downloads',
expect.objectContaining({ cache: 'no-store', credentials: 'omit' }),
);
fireEvent.keyDown(document, { key: 'Escape' });
await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());
fetchMock.mockImplementationOnce(() =>
Promise.resolve(releaseResponse('0.1.74')),
);
fireEvent.click(screen.getByRole('button', { name: '下载客户端' }));
expect(
(
await screen.findByRole('link', { name: '下载 Windows 版' })
).getAttribute('href'),
).toBe(installerUrl('0.1.74'));
});
it('服务不可用时不显示下载链接,重试后使用成功响应', async () => {
fetchMock.mockResolvedValueOnce(new Response('', { status: 502 }));
render(<ClientDownloadEntry />);
fireEvent.click(screen.getByRole('button', { name: '下载客户端' }));
expect(await screen.findByRole('alert')).toBeTruthy();
expect(screen.queryByRole('link')).toBeNull();
fetchMock.mockImplementationOnce(() =>
Promise.resolve(releaseResponse('0.1.75')),
);
fireEvent.click(screen.getByRole('button', { name: '重试' }));
expect(
(
await screen.findByRole('link', { name: '下载 Windows 版' })
).getAttribute('href'),
).toBe(installerUrl('0.1.75'));
});
it('Mac 发布后重新打开即显示各架构和独立版本,未发布时不显示', async () => {
fetchMock.mockImplementationOnce(() =>
Promise.resolve(releaseResponse('0.1.73')),
);
render(<ClientDownloadEntry />);
fireEvent.click(screen.getByRole('button', { name: '下载客户端' }));
await screen.findByRole('link', { name: '下载 Windows 版' });
expect(screen.queryByText(/macOS/u)).toBeNull();
fireEvent.keyDown(document, { key: 'Escape' });
fetchMock.mockImplementationOnce(async () => {
const windows = await releaseResponse('0.1.74').json();
return new Response(
JSON.stringify({
...windows,
downloads: [
...windows.downloads,
macDownload('aarch64'),
macDownload('x86_64'),
],
}),
);
});
fireEvent.click(screen.getByRole('button', { name: '下载客户端' }));
const arm = await screen.findByRole('region', {
name: 'macOSApple Silicon',
});
const intel = screen.getByRole('region', { name: 'macOSIntel' });
expect(within(arm).getByText('最新版本 v0.2.0')).toBeTruthy();
expect(within(arm).getByRole('link').getAttribute('href')).toBe(
macDownload('aarch64').downloadUrl,
);
expect(within(intel).getByRole('link').getAttribute('href')).toBe(
macDownload('x86_64').downloadUrl,
);
expect(screen.getByText('最新版本 v0.1.74')).toBeTruthy();
expect(screen.getAllByRole('link')).toHaveLength(3);
});
it('部分平台失败仍可下载其他平台,重试成功后补齐列表', async () => {
fetchMock.mockImplementationOnce(async () => {
const windows = await releaseResponse('0.1.74').json();
return new Response(
JSON.stringify({ ...windows, unavailablePlatforms: ['macos'] }),
);
});
render(<ClientDownloadEntry />);
fireEvent.click(screen.getByRole('button', { name: '下载客户端' }));
expect(
(
await screen.findByRole('link', { name: '下载 Windows 版' })
).getAttribute('href'),
).toBe(installerUrl('0.1.74'));
expect(screen.getByRole('alert').textContent).toContain('macOS');
fetchMock.mockResolvedValueOnce(
new Response(
JSON.stringify({
downloads: [macDownload('aarch64')],
unavailablePlatforms: [],
}),
),
);
fireEvent.click(screen.getByRole('button', { name: '重试' }));
await screen.findByRole('region', { name: 'macOSApple Silicon' });
expect(screen.queryByRole('alert')).toBeNull();
expect(screen.queryByRole('link', { name: '下载 Windows 版' })).toBeNull();
});
it('所有平台尚未发布时显示空状态而不伪造链接', async () => {
fetchMock.mockResolvedValueOnce(
new Response(JSON.stringify({ downloads: [], unavailablePlatforms: [] })),
);
render(<ClientDownloadEntry />);
fireEvent.click(screen.getByRole('button', { name: '下载客户端' }));
expect(await screen.findByText('暂无可下载版本')).toBeTruthy();
expect(screen.queryByRole('link')).toBeNull();
expect(screen.getByRole('button', { name: '重试' })).toBeTruthy();
});
it('响应缺少平台列表时显示可重试错误', async () => {
fetchMock.mockResolvedValueOnce(new Response('{}'));
render(<ClientDownloadEntry />);
fireEvent.click(screen.getByRole('button', { name: '下载客户端' }));
expect(await screen.findByRole('alert')).toBeTruthy();
expect(screen.queryByRole('link')).toBeNull();
});
it('关闭取消请求,迟到响应不覆盖重开后版本', async () => {
let resolveFirst!: (response: Response) => void;
fetchMock.mockImplementationOnce(
() =>
new Promise<Response>((resolve) => {
resolveFirst = resolve;
}),
);
render(<ClientDownloadEntry />);
fireEvent.click(screen.getByRole('button', { name: '下载客户端' }));
const signal = fetchMock.mock.calls[0][1].signal as AbortSignal;
fireEvent.keyDown(document, { key: 'Escape' });
expect(signal.aborted).toBe(true);
fetchMock.mockImplementationOnce(() =>
Promise.resolve(releaseResponse('0.1.76')),
);
fireEvent.click(screen.getByRole('button', { name: '下载客户端' }));
await screen.findByText('最新版本 v0.1.76');
await act(async () => {
resolveFirst(releaseResponse('0.1.73'));
});
expect(screen.getByText('最新版本 v0.1.76')).toBeTruthy();
});
it('请求超时后退出加载并允许重试', async () => {
vi.useFakeTimers();
fetchMock.mockImplementationOnce(() => new Promise(() => {}));
render(<ClientDownloadEntry />);
fireEvent.click(screen.getByRole('button', { name: '下载客户端' }));
const signal = fetchMock.mock.calls[0][1].signal as AbortSignal;
await act(async () => {
vi.advanceTimersByTime(15_000);
});
expect(signal.aborted).toBe(true);
expect(screen.getByRole('alert')).toBeTruthy();
expect(screen.getByRole('button', { name: '重试' })).toBeTruthy();
});
});