mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-03 07:21:34 -07:00
c5140b0f7b
- Add `subminer anime` / `--anime` and a tray entry to open a browser that searches installed Aniyomi extension sources, shows cover art and episodes, and plays into mpv with overlay/mining attached - Add an Extensions tab to add repos and install/update/remove sources, and per-source settings for sources needing config - Support searching all sources at once with streaming, per-source results and status - Prefer Japanese audio/subtitle tracks from the source and keep the primary subtitle slot reserved for Japanese - Fix window/tray/Dock handling so the browser and mpv can be switched between without quitting the app or losing the Dock icon - Add anime.repos, anime.extensionsDir, anime.preferredQuality config keys (no bundled repos or discovery)
72 lines
2.2 KiB
TypeScript
72 lines
2.2 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { interleave, mapSourcesConcurrently } from './multi-source-search';
|
|
|
|
const source = (id: string) => ({ id, name: `Source ${id}` });
|
|
|
|
test('mapSourcesConcurrently returns results in source order, not completion order', async () => {
|
|
const sources = [source('a'), source('b'), source('c')];
|
|
const delays: Record<string, number> = { a: 20, b: 0, c: 10 };
|
|
|
|
const { results, failures } = await mapSourcesConcurrently(sources, async (target) => {
|
|
await new Promise((resolve) => setTimeout(resolve, delays[target.id]));
|
|
return target.id;
|
|
});
|
|
|
|
assert.deepEqual(results, ['a', 'b', 'c']);
|
|
assert.deepEqual(failures, []);
|
|
});
|
|
|
|
test('a failing source is reported without losing the others', async () => {
|
|
const sources = [source('a'), source('b'), source('c')];
|
|
|
|
const { results, failures } = await mapSourcesConcurrently(sources, async (target) => {
|
|
if (target.id === 'b') throw new Error('login required');
|
|
return target.id;
|
|
});
|
|
|
|
assert.deepEqual(results, ['a', 'c']);
|
|
assert.deepEqual(failures, [{ sourceId: 'b', sourceName: 'Source b', error: 'login required' }]);
|
|
});
|
|
|
|
test('mapSourcesConcurrently never runs more than the concurrency limit at once', async () => {
|
|
const sources = ['a', 'b', 'c', 'd', 'e'].map(source);
|
|
let running = 0;
|
|
let peak = 0;
|
|
|
|
await mapSourcesConcurrently(
|
|
sources,
|
|
async () => {
|
|
running += 1;
|
|
peak = Math.max(peak, running);
|
|
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
running -= 1;
|
|
},
|
|
2,
|
|
);
|
|
|
|
assert.equal(peak, 2);
|
|
});
|
|
|
|
test('mapSourcesConcurrently handles an empty source list', async () => {
|
|
const { results, failures } = await mapSourcesConcurrently([], async () => 'x');
|
|
assert.deepEqual(results, []);
|
|
assert.deepEqual(failures, []);
|
|
});
|
|
|
|
test('interleave takes one from each source before taking a second', () => {
|
|
assert.deepEqual(interleave([['a1', 'a2', 'a3'], ['b1'], ['c1', 'c2']]), [
|
|
'a1',
|
|
'b1',
|
|
'c1',
|
|
'a2',
|
|
'c2',
|
|
'a3',
|
|
]);
|
|
});
|
|
|
|
test('interleave ignores empty groups', () => {
|
|
assert.deepEqual(interleave([[], ['b1', 'b2'], []]), ['b1', 'b2']);
|
|
assert.deepEqual(interleave([]), []);
|
|
});
|