后台模板上传弹窗表头层级修正与仓库卫生

- AdminAgcTemplatesPage 的上传弹窗把 TableHeader(thead)当列头使用,生成出 th > tr > thead 的非法层级;修正为 TableHeader > TableRow > TableHead,并补列头角色与层级回归。
- .gitignore 补 /server-rs/crates/*/server-rs/.data/:api-server 的相对 outbox 默认路径在嵌套 cwd 下会在仓库内生成内嵌数据目录,避免其被误提交。
This commit is contained in:
kdletters
2026-09-28 14:16:00 +08:00
parent 01394ed238
commit e1fe1837a2
3 changed files with 44 additions and 10 deletions
+3
View File
@@ -65,6 +65,9 @@ temp*build*/
/apps/preview-deployer-web/node_modules/
/server-rs/.spacetimedb/
/server-rs/.data/
# api-server 的 tracking / wallet-refund outbox 默认目录相对仓库根(server-rs/.data/…);
# 从 server-rs/crates/<crate> 目录运行测试或二进制时,会在仓库内生成嵌套副本,不能提交。
/server-rs/crates/*/server-rs/.data/
/public/generated-animations
/public/generated-character-drafts
/public/generated-characters
@@ -542,6 +542,37 @@ function pickUploadFiles(dialog: HTMLElement, accept: string, files: File[]) {
fireEvent.change(input, { target: { files } });
}
test('批量上传:表头是真实列头单元格,不是嵌套 thead', async () => {
render(<AdminAgcTemplatesPage token="token" onUnauthorized={vi.fn()} />);
const dialog = await openUploadDialog();
pickUploadFiles(dialog, '.zip,application/zip', [uploadZipFile('alpha.zip')]);
const table = Array.from(dialog.querySelectorAll('table')).find((candidate) =>
candidate.textContent?.includes('alpha.zip'),
);
expect(table).toBeTruthy();
// 每一列表头都必须是 `th`:把 `<TableHeader>`(渲染 `<thead>`)当单元格用会产出
// `<th><tr><thead>`,列头语义与样式一起丢失。
expect(
within(table as HTMLElement)
.getAllByRole('columnheader')
.map((cell) => cell.textContent),
).toEqual([
'模板包',
'ID',
'名称',
'版本',
'运行时',
'entry',
'封面',
'操作',
]);
expect(table?.querySelectorAll('thead thead')).toHaveLength(0);
expect(table?.querySelectorAll('thead > tr > th')).toHaveLength(8);
expect(table?.querySelectorAll('thead > th')).toHaveLength(0);
});
test('批量上传:多选 ZIP 生成行,封面匹配齐了才能提交', async () => {
render(<AdminAgcTemplatesPage token="token" onUnauthorized={vi.fn()} />);
const dialog = await openUploadDialog();
@@ -880,18 +880,18 @@ function TemplateUploadDialog({
) : (
<div className="admin-agc-template-upload-scroll">
<Table>
<TableHead>
<TableHeader>
<TableRow>
<TableHeader>模板包</TableHeader>
<TableHeader>ID</TableHeader>
<TableHeader>名称</TableHeader>
<TableHeader>版本</TableHeader>
<TableHeader>运行时</TableHeader>
<TableHeader>entry</TableHeader>
<TableHeader>封面</TableHeader>
<TableHeader>操作</TableHeader>
<TableHead>模板包</TableHead>
<TableHead>ID</TableHead>
<TableHead>名称</TableHead>
<TableHead>版本</TableHead>
<TableHead>运行时</TableHead>
<TableHead>entry</TableHead>
<TableHead>封面</TableHead>
<TableHead>操作</TableHead>
</TableRow>
</TableHead>
</TableHeader>
<TableBody>
{state.rows.map((row) => {
const rowError = state.errors[row.key];