mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-10-21 05:01:26 -07:00
219 lines
6.3 KiB
TypeScript
219 lines
6.3 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { act } from 'react';
|
|
import { createRoot } from 'react-dom/client';
|
|
import { apiClient } from '../../lib/api-client';
|
|
import type { StatsDuplicateLineCleanupResult } from '../../types/stats';
|
|
import { DuplicateLineCleanup } from './DuplicateLineCleanup';
|
|
import { installDom } from '../../test-utils/dom';
|
|
|
|
function findButton(container: Element, label: string): HTMLButtonElement {
|
|
const match = [...container.querySelectorAll('button')].find(
|
|
(button) => (button.textContent ?? '').trim() === label,
|
|
);
|
|
assert.ok(match, `expected a "${label}" button`);
|
|
return match as unknown as HTMLButtonElement;
|
|
}
|
|
|
|
/** The backdrop stays clickable during an apply, so it reaches the guard in `close`. */
|
|
function findBackdrop(container: Element): HTMLButtonElement {
|
|
const match = container.querySelector('button[aria-label="Close duplicate line cleanup"]');
|
|
assert.ok(match, 'expected the backdrop close button');
|
|
return match as unknown as HTMLButtonElement;
|
|
}
|
|
|
|
function deferred<T>(): { promise: Promise<T>; resolve: (value: T) => void } {
|
|
let resolve!: (value: T) => void;
|
|
const promise = new Promise<T>((done) => {
|
|
resolve = done;
|
|
});
|
|
return { promise, resolve };
|
|
}
|
|
|
|
function summary(
|
|
overrides: Partial<StatsDuplicateLineCleanupResult> = {},
|
|
): StatsDuplicateLineCleanupResult {
|
|
return {
|
|
dryRun: false,
|
|
lookbackDays: 30,
|
|
scannedLines: 900,
|
|
burstGroups: 2,
|
|
removedLines: 180,
|
|
removedWordOccurrences: 540,
|
|
removedKanjiOccurrences: 120,
|
|
samples: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
interface Harness {
|
|
container: Element;
|
|
cleanedCalls: () => number;
|
|
closedCalls: () => number;
|
|
teardown: () => void;
|
|
}
|
|
|
|
async function mount(cleanup: (typeof apiClient)['cleanupDuplicateLines']): Promise<Harness> {
|
|
const uninstallDom = installDom();
|
|
const originalCleanup = apiClient.cleanupDuplicateLines;
|
|
apiClient.cleanupDuplicateLines = cleanup;
|
|
|
|
let cleaned = 0;
|
|
let closed = 0;
|
|
const container = document.createElement('div');
|
|
document.body.append(container);
|
|
const root = createRoot(container);
|
|
|
|
await act(async () => {
|
|
root.render(
|
|
<DuplicateLineCleanup
|
|
onClose={() => {
|
|
closed += 1;
|
|
}}
|
|
onCleaned={() => {
|
|
cleaned += 1;
|
|
}}
|
|
/>,
|
|
);
|
|
});
|
|
|
|
return {
|
|
container,
|
|
cleanedCalls: () => cleaned,
|
|
closedCalls: () => closed,
|
|
teardown: () => {
|
|
apiClient.cleanupDuplicateLines = originalCleanup;
|
|
uninstallDom();
|
|
},
|
|
};
|
|
}
|
|
|
|
test('a reload is still owed after a later scan replaces the applied result', async () => {
|
|
const harness = await mount(async ({ dryRun } = {}) => summary({ dryRun: dryRun === true }));
|
|
|
|
try {
|
|
await act(async () => {
|
|
findButton(harness.container, 'Scan').click();
|
|
});
|
|
await act(async () => {
|
|
findButton(harness.container, 'Clean Up').click();
|
|
});
|
|
assert.equal(harness.cleanedCalls(), 0, 'reload must wait for the result to be read');
|
|
|
|
// The follow-up scan clears the applied summary, but the rows are already gone.
|
|
await act(async () => {
|
|
findButton(harness.container, 'Scan').click();
|
|
});
|
|
await act(async () => {
|
|
findButton(harness.container, 'Close').click();
|
|
});
|
|
|
|
assert.equal(harness.cleanedCalls(), 1);
|
|
assert.equal(harness.closedCalls(), 1);
|
|
} finally {
|
|
harness.teardown();
|
|
}
|
|
});
|
|
|
|
test('a reload is still owed after the lookback window changes', async () => {
|
|
const harness = await mount(async ({ dryRun } = {}) => summary({ dryRun: dryRun === true }));
|
|
|
|
try {
|
|
await act(async () => {
|
|
findButton(harness.container, 'Scan').click();
|
|
});
|
|
await act(async () => {
|
|
findButton(harness.container, 'Clean Up').click();
|
|
});
|
|
await act(async () => {
|
|
findButton(harness.container, '7 days').click();
|
|
});
|
|
await act(async () => {
|
|
findButton(harness.container, 'Close').click();
|
|
});
|
|
|
|
assert.equal(harness.cleanedCalls(), 1);
|
|
} finally {
|
|
harness.teardown();
|
|
}
|
|
});
|
|
|
|
test('closing is refused while an apply is in flight', async () => {
|
|
const pending = deferred<StatsDuplicateLineCleanupResult>();
|
|
const harness = await mount(async ({ dryRun } = {}) =>
|
|
dryRun === true ? summary({ dryRun: true }) : pending.promise,
|
|
);
|
|
|
|
try {
|
|
await act(async () => {
|
|
findButton(harness.container, 'Scan').click();
|
|
});
|
|
await act(async () => {
|
|
findButton(harness.container, 'Clean Up').click();
|
|
});
|
|
|
|
assert.equal(findButton(harness.container, 'Close').disabled, true);
|
|
// The backdrop is never disabled, so this is the path that has to be refused.
|
|
await act(async () => {
|
|
findBackdrop(harness.container).click();
|
|
});
|
|
assert.equal(harness.closedCalls(), 0, 'the modal must stay open mid-apply');
|
|
assert.equal(harness.cleanedCalls(), 0);
|
|
|
|
await act(async () => {
|
|
pending.resolve(summary({ removedLines: 12 }));
|
|
await pending.promise;
|
|
});
|
|
await act(async () => {
|
|
findButton(harness.container, 'Close').click();
|
|
});
|
|
|
|
assert.equal(harness.closedCalls(), 1);
|
|
assert.equal(harness.cleanedCalls(), 1);
|
|
} finally {
|
|
harness.teardown();
|
|
}
|
|
});
|
|
|
|
test('a scan on its own owes no reload', async () => {
|
|
const harness = await mount(async ({ dryRun } = {}) => summary({ dryRun: dryRun === true }));
|
|
|
|
try {
|
|
await act(async () => {
|
|
findButton(harness.container, 'Scan').click();
|
|
});
|
|
await act(async () => {
|
|
findButton(harness.container, 'Close').click();
|
|
});
|
|
|
|
assert.equal(harness.cleanedCalls(), 0);
|
|
assert.equal(harness.closedCalls(), 1);
|
|
} finally {
|
|
harness.teardown();
|
|
}
|
|
});
|
|
|
|
test('an apply that removes nothing owes no reload', async () => {
|
|
// The scan saw work to do, but by the time it ran another cleanup had taken it.
|
|
const harness = await mount(async ({ dryRun } = {}) =>
|
|
dryRun === true ? summary({ dryRun: true }) : summary({ burstGroups: 0, removedLines: 0 }),
|
|
);
|
|
|
|
try {
|
|
await act(async () => {
|
|
findButton(harness.container, 'Scan').click();
|
|
});
|
|
await act(async () => {
|
|
findButton(harness.container, 'Clean Up').click();
|
|
});
|
|
await act(async () => {
|
|
findButton(harness.container, 'Close').click();
|
|
});
|
|
|
|
assert.equal(harness.cleanedCalls(), 0);
|
|
assert.equal(harness.closedCalls(), 1);
|
|
} finally {
|
|
harness.teardown();
|
|
}
|
|
});
|