mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-11-10 16:46:06 -08:00
feat(plugins): add JavaScript plugin system with overlay panel (#303)
This commit is contained in:
1 parent
90fc7cf940
commit
a9a4c4f083
110 files changed
+5897
-344
No files matched your search
@@ -3,9 +3,10 @@ import test from 'node:test';
|
||||
|
||||
import { PollingRunner } from './polling';
|
||||
|
||||
test('polling runner records newly added cards after initialization', async () => {
|
||||
test('polling runner records new cards and reports each ready after enrichment', async () => {
|
||||
const originalDateNow = Date.now;
|
||||
const recordedCards: number[] = [];
|
||||
const events: string[] = [];
|
||||
let tracked = new Set<number>();
|
||||
const responses = [
|
||||
[10, 11],
|
||||
@@ -16,12 +17,17 @@ test('polling runner records newly added cards after initialization', async () =
|
||||
const runner = new PollingRunner({
|
||||
getDeck: () => 'Mining',
|
||||
getPollingRate: () => 250,
|
||||
findNotes: async () => responses.shift() ?? [],
|
||||
findNotes: async (query) => (query.includes('tag:') ? [] : (responses.shift() ?? [])),
|
||||
shouldAutoUpdateNewCards: () => true,
|
||||
processNewCard: async () => undefined,
|
||||
processNewCard: async (noteId) => {
|
||||
events.push(`process ${noteId}`);
|
||||
},
|
||||
recordCardsAdded: (count) => {
|
||||
recordedCards.push(count);
|
||||
},
|
||||
onCardsReady: (noteIds) => {
|
||||
events.push(`ready ${noteIds.join(',')}`);
|
||||
},
|
||||
isUpdateInProgress: () => false,
|
||||
setUpdateInProgress: () => undefined,
|
||||
getTrackedNoteIds: () => tracked,
|
||||
@@ -38,7 +44,132 @@ test('polling runner records newly added cards after initialization', async () =
|
||||
await runner.pollOnce();
|
||||
|
||||
assert.deepEqual(recordedCards, [2]);
|
||||
assert.deepEqual(events, ['process 12', 'ready 12', 'process 13', 'ready 13']);
|
||||
} finally {
|
||||
Date.now = originalDateNow;
|
||||
}
|
||||
});
|
||||
|
||||
test('polling runner keeps going after one note fails to enrich', async () => {
|
||||
const originalDateNow = Date.now;
|
||||
const events: string[] = [];
|
||||
const warnings: unknown[][] = [];
|
||||
let tracked = new Set<number>();
|
||||
const responses = [[10], [10, 12, 13]];
|
||||
try {
|
||||
Date.now = () => 120_000;
|
||||
const runner = new PollingRunner({
|
||||
getDeck: () => undefined,
|
||||
getPollingRate: () => 250,
|
||||
findNotes: async (query) => (query.includes('tag:') ? [] : (responses.shift() ?? [])),
|
||||
shouldAutoUpdateNewCards: () => true,
|
||||
processNewCard: async (noteId) => {
|
||||
events.push(`process ${noteId}`);
|
||||
if (noteId === 12) throw new Error('overlay callback failed');
|
||||
},
|
||||
onCardsReady: (noteIds) => {
|
||||
events.push(`ready ${noteIds.join(',')}`);
|
||||
},
|
||||
isUpdateInProgress: () => false,
|
||||
setUpdateInProgress: () => undefined,
|
||||
getTrackedNoteIds: () => tracked,
|
||||
setTrackedNoteIds: (noteIds) => {
|
||||
tracked = noteIds;
|
||||
},
|
||||
showStatusNotification: () => undefined,
|
||||
logDebug: () => undefined,
|
||||
logInfo: () => undefined,
|
||||
logWarn: (...args) => {
|
||||
warnings.push(args);
|
||||
},
|
||||
});
|
||||
|
||||
await runner.pollOnce();
|
||||
await runner.pollOnce();
|
||||
|
||||
assert.deepEqual(events, ['process 12', 'ready 12', 'process 13', 'ready 13']);
|
||||
assert.deepEqual(warnings, [['Failed to auto-enrich note 12:', 'overlay callback failed']]);
|
||||
} finally {
|
||||
Date.now = originalDateNow;
|
||||
}
|
||||
});
|
||||
|
||||
function createStatsHarness(autoUpdate: boolean, sourceLookupFails = false) {
|
||||
let currentIds: number[] = [];
|
||||
let tracked = new Set<number>();
|
||||
const recorded: Array<{ count: number; noteIds: number[] }> = [];
|
||||
const processed: number[] = [];
|
||||
const ready: number[][] = [];
|
||||
const warnings: unknown[][] = [];
|
||||
const queries: string[] = [];
|
||||
const runner = new PollingRunner({
|
||||
getDeck: () => 'Mining',
|
||||
getPollingRate: () => 250,
|
||||
findNotes: async (query) => {
|
||||
queries.push(query);
|
||||
if (!query.includes('tag:')) return currentIds;
|
||||
if (sourceLookupFails) throw new Error('source lookup failed');
|
||||
return [2];
|
||||
},
|
||||
shouldAutoUpdateNewCards: () => autoUpdate,
|
||||
processNewCard: async (noteId) => {
|
||||
processed.push(noteId);
|
||||
},
|
||||
recordCardsAdded: (count, noteIds) => {
|
||||
recorded.push({ count, noteIds });
|
||||
},
|
||||
onCardsReady: (noteIds) => {
|
||||
ready.push(noteIds);
|
||||
},
|
||||
isUpdateInProgress: () => false,
|
||||
setUpdateInProgress: () => {},
|
||||
getTrackedNoteIds: () => tracked,
|
||||
setTrackedNoteIds: (noteIds) => {
|
||||
tracked = noteIds;
|
||||
},
|
||||
showStatusNotification: () => assert.fail('source lookup failures must not back off polling'),
|
||||
logDebug: () => {},
|
||||
logInfo: () => {},
|
||||
logWarn: (...args) => {
|
||||
warnings.push(args);
|
||||
},
|
||||
});
|
||||
return {
|
||||
recorded,
|
||||
processed,
|
||||
ready,
|
||||
warnings,
|
||||
queries,
|
||||
poll: async (noteIds: number[]) => {
|
||||
currentIds = noteIds;
|
||||
await runner.pollOnce();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
for (const autoUpdate of [true, false]) {
|
||||
test(`stats cards count without triggering plugins when auto-update is ${autoUpdate}`, async () => {
|
||||
const h = createStatsHarness(autoUpdate);
|
||||
await h.poll([]);
|
||||
await h.poll([1, 2]);
|
||||
assert.deepEqual(h.recorded, [{ count: 2, noteIds: [1, 2] }]);
|
||||
assert.deepEqual(h.processed, autoUpdate ? [1, 2] : []);
|
||||
assert.deepEqual(h.ready, [[1]]);
|
||||
assert.ok(h.queries.includes('nid:1,2 tag:"SubMiner::Stats"'));
|
||||
assert.deepEqual(h.warnings, []);
|
||||
});
|
||||
}
|
||||
|
||||
test('a failed source lookup skips plugin events without stopping counting or enrichment', async () => {
|
||||
const h = createStatsHarness(true, true);
|
||||
await h.poll([]);
|
||||
await h.poll([1, 2]);
|
||||
await h.poll([1, 2, 3]);
|
||||
assert.deepEqual(h.processed, [1, 2, 3]);
|
||||
assert.deepEqual(h.recorded, [
|
||||
{ count: 2, noteIds: [1, 2] },
|
||||
{ count: 1, noteIds: [3] },
|
||||
]);
|
||||
assert.deepEqual(h.ready, []);
|
||||
assert.equal(h.warnings.length, 2);
|
||||
});
|
||||
Reference in new issue
Block a user