Files
Genarrative/apps/preview-deployer-web/src/PreviewDeployerApp.test.tsx
T
kdletters 38ae9d7d07
Project CI / Repository checks (push) Successful in 1m6s
Project CI / Frontend tests (push) Successful in 2m55s
Project CI / Backend tests (push) Successful in 3m53s
Project CI / Native shell tests (push) Successful in 15m2s
完善预览发布记录管理
发布记录改用 Jenkins 构建编号并展示

失败与停止记录增加自动清理期限

构建详情改为局域网 Jenkins 地址

同步测试、部署配置和运维文档
2026-08-17 12:42:29 +08:00

197 lines
6.1 KiB
TypeScript

/* @vitest-environment jsdom */
import {
cleanup,
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
import * as api from './api';
import { PreviewDeployerApp } from './PreviewDeployerApp';
vi.mock('./api');
afterEach(() => {
cleanup();
});
beforeEach(() => {
vi.mocked(api.getSession).mockResolvedValue({ authenticated: true });
vi.mocked(api.createSession).mockResolvedValue({ authenticated: true });
vi.mocked(api.deleteSession).mockResolvedValue(undefined);
vi.mocked(api.listDeployments).mockResolvedValue([]);
vi.mocked(api.searchBranches).mockResolvedValue([]);
vi.mocked(api.searchCommits).mockResolvedValue([]);
vi.mocked(api.createDeployment).mockResolvedValue({
id: null,
branch: 'master',
status: 'queued',
health: 'pending',
createdAt: '2026-08-15T00:00:00Z',
updatedAt: '2026-08-15T00:00:00Z',
});
vi.mocked(api.uninstallDeployment).mockResolvedValue({
id: '1',
branch: 'master',
status: 'uninstalling',
health: 'pending',
createdAt: '2026-08-15T00:00:00Z',
updatedAt: '2026-08-15T00:02:00Z',
});
});
test('requires an access token before showing deployments', async () => {
const user = userEvent.setup();
vi.mocked(api.getSession).mockResolvedValue({ authenticated: false });
render(<PreviewDeployerApp />);
expect(await screen.findByText('访问口令')).toBeTruthy();
await user.type(screen.getByPlaceholderText('请输入访问口令'), 'team-token');
await user.click(screen.getByRole('button', { name: '进入发布面板' }));
await waitFor(() => {
expect(api.createSession).toHaveBeenCalledWith('team-token');
});
expect(await screen.findByText('构建并发布一个分支')).toBeTruthy();
});
test('submits a branch with an optional commit hash', async () => {
const user = userEvent.setup();
render(<PreviewDeployerApp />);
const branchInput = await screen.findByPlaceholderText('master');
await user.clear(branchInput);
await user.type(branchInput, 'feature/preview');
await user.type(
screen.getByPlaceholderText('留空则构建分支最新提交'),
'aa5221abc',
);
await user.click(screen.getByRole('button', { name: '开始构建' }));
await waitFor(() => {
expect(api.createDeployment).toHaveBeenCalledWith({
branch: 'feature/preview',
commitHash: 'aa5221abc',
});
});
});
test('shows health and web url, then confirms uninstall', async () => {
vi.mocked(api.listDeployments).mockResolvedValue([
{
id: '42',
branch: 'feature/demo',
resolvedCommit: '1234567890abcdef',
status: 'running',
health: 'healthy',
webPort: 8400,
webUrl: 'http://192.168.35.82:8400',
createdAt: 1_787_270_400,
updatedAt: 1_787_270_460,
},
]);
render(<PreviewDeployerApp />);
expect(await screen.findByText('健康')).toBeTruthy();
expect(screen.getByText('端口 8400')).toBeTruthy();
expect(screen.getByText('构建 #42')).toBeTruthy();
expect(
screen.getByRole('link', { name: /打开 Web/u }).getAttribute('href'),
).toBe('http://192.168.35.82:8400');
fireEvent.click(screen.getByRole('button', { name: '卸载' }));
expect(screen.getByRole('dialog')).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: '确认卸载' }));
await waitFor(() => {
expect(api.uninstallDeployment).toHaveBeenCalledWith('42');
});
});
test('shows debounced branch results and selects one with the keyboard', async () => {
const user = userEvent.setup();
vi.mocked(api.searchBranches).mockResolvedValue([
{ name: 'feature/search-one', commitHash: '111111111111' },
{ name: 'feature/search-two', commitHash: '222222222222' },
]);
render(<PreviewDeployerApp />);
const branchInput = await screen.findByRole('combobox', { name: '分支名' });
await user.clear(branchInput);
await user.type(branchInput, 'feature/search');
expect(
await screen.findByRole('option', { name: /feature\/search-one/u }),
).toBeTruthy();
expect(api.searchBranches).toHaveBeenLastCalledWith(
'feature/search',
expect.any(AbortSignal),
);
await user.keyboard('{ArrowDown}{ArrowDown}{Enter}');
expect((branchInput as HTMLInputElement).value).toBe('feature/search-two');
});
test('searches commits in the current branch and selects a result', async () => {
const user = userEvent.setup();
vi.mocked(api.searchCommits).mockResolvedValue([
{
commitHash: 'aa5221abcdef0123456789',
shortHash: 'aa5221a',
subject: '补充预览搜索',
},
]);
render(<PreviewDeployerApp />);
const commitInput = await screen.findByRole('combobox', {
name: 'Commit Hash',
});
await user.type(commitInput, 'aa5221a');
const option = await screen.findByRole('option', {
name: /aa5221a.*补充预览搜索/u,
});
expect(api.searchCommits).toHaveBeenCalledWith(
'master',
'aa5221a',
expect.any(AbortSignal),
);
await user.click(option);
expect((commitInput as HTMLInputElement).value).toBe(
'aa5221abcdef0123456789',
);
});
test('clears commit and ignores stale commit responses when branch changes', async () => {
const user = userEvent.setup();
let resolveOldSearch: ((items: api.CommitRef[]) => void) | undefined;
vi.mocked(api.searchCommits).mockImplementation(
() =>
new Promise<api.CommitRef[]>((resolve) => {
resolveOldSearch = resolve;
}),
);
render(<PreviewDeployerApp />);
const branchInput = await screen.findByRole('combobox', { name: '分支名' });
const commitInput = screen.getByRole('combobox', { name: 'Commit Hash' });
await user.type(commitInput, 'abcdef1');
await waitFor(() => expect(api.searchCommits).toHaveBeenCalled());
await user.clear(branchInput);
await user.type(branchInput, 'feature/new');
expect((commitInput as HTMLInputElement).value).toBe('');
resolveOldSearch?.([
{
commitHash: 'abcdef1234567890',
shortHash: 'abcdef1',
subject: '旧分支提交',
},
]);
await Promise.resolve();
expect(screen.queryByText('旧分支提交')).toBeNull();
});