test(admin-tags): pin merge/delete previews to the direct count (#698)
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m15s
CI / OCR Service Tests (pull_request) Successful in 20s
CI / Backend Unit Tests (pull_request) Successful in 3m24s
CI / fail2ban Regex (pull_request) Successful in 43s
CI / Semgrep Security Scan (pull_request) Successful in 19s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m5s
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m15s
CI / OCR Service Tests (pull_request) Successful in 20s
CI / Backend Unit Tests (pull_request) Successful in 3m24s
CI / fail2ban Regex (pull_request) Successful in 43s
CI / Semgrep Security Scan (pull_request) Successful in 19s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m5s
Characterization tests for AC#8: the merge preview and the delete-impact warning describe direct-document operations, so they must report the tag's direct documentCount, never a subtree rollup. Both tests pass a stray subtreeDocumentCount and assert it does not leak into the preview, so a future change can't silently desync a destructive-action preview. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,21 @@ describe('TagDeleteGuard', () => {
|
||||
renderWithConfirm();
|
||||
await expect.element(page.getByText(/3 Dokument/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Characterization (#698): the delete-impact warning describes a destructive single-tag delete,
|
||||
// which removes only the tag's DIRECT document_tags rows — so it must show documentCount, never
|
||||
// a subtree rollup. Pinned so a future change can't silently desync this warning.
|
||||
it('shows the direct documentCount in the impact summary, not a subtree rollup', async () => {
|
||||
const tagWithStraySubtree = {
|
||||
id: 't1',
|
||||
name: 'Familie',
|
||||
documentCount: 3,
|
||||
subtreeDocumentCount: 99
|
||||
};
|
||||
renderWithConfirm({ tag: tagWithStraySubtree, allTags });
|
||||
await expect.element(page.getByText(/3 Dokument/)).toBeInTheDocument();
|
||||
expect(document.body.textContent).not.toContain('99');
|
||||
});
|
||||
});
|
||||
|
||||
describe('TagDeleteGuard – confirmation dialog', () => {
|
||||
|
||||
@@ -66,6 +66,32 @@ describe('TagMergeZone – step flow', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('TagMergeZone – preview uses the direct document count (characterization, #698)', () => {
|
||||
it('shows the tag direct documentCount in the merge preview, not a subtree rollup', async () => {
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue([{ id: 't2', name: 'Reise' }])
|
||||
})
|
||||
);
|
||||
// documentCount (direct) = 3; a stray subtree rollup of 99 must NOT leak into the preview —
|
||||
// merge re-tags only direct documents, so the preview has to stay the direct count.
|
||||
const mergeTag = { id: 't1', name: 'Familie', documentCount: 3, subtreeDocumentCount: 99 };
|
||||
render(TagMergeZone, { tag: mergeTag, allTags, form: null });
|
||||
|
||||
const input = page.getByRole('combobox');
|
||||
await input.fill('R');
|
||||
await vi.advanceTimersByTimeAsync(300);
|
||||
await page.getByRole('option', { name: 'Reise' }).click();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
|
||||
await expect.element(page.getByTestId('merge-step2')).toBeInTheDocument();
|
||||
expect(document.body.textContent).toContain('3 Dokumente');
|
||||
expect(document.body.textContent).not.toContain('99');
|
||||
});
|
||||
});
|
||||
|
||||
describe('TagMergeZone – stale state reset', () => {
|
||||
it('resets target selection when tag prop changes', async () => {
|
||||
vi.stubGlobal(
|
||||
|
||||
Reference in New Issue
Block a user