fix(stats): count mined cards once and keep lexical stats exact after deletes (#280)

This commit is contained in:
sudacode authored and GitHub committed 2026-10-03 21:16:12 -07:00
1 parent 121b688afb
commit e77aa5c3b3
24 files changed
+736 -153

No files matched your search

@@ -67,7 +67,6 @@ test('anki action main deps builders map callbacks', async () => {
getPrimarySubtitle: () => ({ text: '正式な字幕', startTime: 1, endTime: 3 }),
showMpvOsd: (text) => calls.push(`mine:${text}`),
mineSentenceCardCore: async () => true,
recordCardsMined: (count) => calls.push(`cards:${count}`),
})();
assert.deepEqual(mine.getMpvClient(), { connected: true });
assert.deepEqual(mine.getPrimarySubtitle?.(), {
@@ -81,16 +80,6 @@ test('anki action main deps builders map callbacks', async () => {
mpvClient: { connected: true },
showMpvOsd: () => {},
});
mine.recordCardsMined(1);
assert.deepEqual(calls, [
'osd:x',
'update',
'fg:fg',
'trigger',
'audio:a',
'mark',
'mine:m',
'cards:1',
]);
assert.deepEqual(calls, ['osd:x', 'update', 'fg:fg', 'trigger', 'audio:a', 'mark', 'mine:m']);
});
@@ -80,7 +80,6 @@ export function createBuildMineSentenceCardMainDepsHandler<TAnki, TMpv>(deps: {
primarySubtitle?: PrimarySubtitle;
showMpvOsd: (text: string) => void;
}) => Promise<boolean>;
recordCardsMined: (count: number, noteIds?: number[]) => void;
}) {
return () => ({
getAnkiIntegration: () => deps.getAnkiIntegration(),
@@ -95,6 +94,5 @@ export function createBuildMineSentenceCardMainDepsHandler<TAnki, TMpv>(deps: {
primarySubtitle?: PrimarySubtitle;
showMpvOsd: (text: string) => void;
}) => deps.mineSentenceCardCore(options),
recordCardsMined: (count: number, noteIds?: number[]) => deps.recordCardsMined(count, noteIds),
});
}
+2 -4
View File
@@ -63,7 +63,7 @@ test('trigger and mark handlers delegate to core services', async () => {
assert.deepEqual(calls, ['osd:group', 'group-core', 'osd:mark', 'mark-core']);
});
test('mine sentence handler records mined cards only when core returns true', async () => {
test('mine sentence handler leaves card counting to card creation', async () => {
const calls: string[] = [];
const integration = {};
const mpvClient = {};
@@ -78,14 +78,13 @@ test('mine sentence handler records mined cards only when core returns true', as
options.showMpvOsd('mine');
return created;
},
recordCardsMined: (count) => calls.push(`cards:${count}`),
});
created = false;
await mineSentenceCard();
created = true;
await mineSentenceCard();
assert.deepEqual(calls, ['osd:mine', 'osd:mine', 'cards:1']);
assert.deepEqual(calls, ['osd:mine', 'osd:mine']);
});
test('mine sentence handler forwards the canonical primary subtitle snapshot', async () => {
@@ -99,7 +98,6 @@ test('mine sentence handler forwards the canonical primary subtitle snapshot', a
assert.equal(options.primarySubtitle, primarySubtitle);
return true;
},
recordCardsMined: () => {},
});
await mineSentenceCard();
+2 -5
View File
@@ -83,18 +83,15 @@ export function createMineSentenceCardHandler<TAnki, TMpv>(deps: {
primarySubtitle?: PrimarySubtitle;
showMpvOsd: (text: string) => void;
}) => Promise<boolean>;
recordCardsMined: (count: number, noteIds?: number[]) => void;
}) {
// Card creation records the mined card (with its note id) itself.
return async (): Promise<void> => {
const primarySubtitle = deps.getPrimarySubtitle?.();
const created = await deps.mineSentenceCardCore({
await deps.mineSentenceCardCore({
ankiIntegration: deps.getAnkiIntegration(),
mpvClient: deps.getMpvClient(),
...(primarySubtitle ? { primarySubtitle } : {}),
showMpvOsd: deps.showMpvOsd,
});
if (created) {
deps.recordCardsMined(1);
}
};
}
@@ -45,20 +45,17 @@ test('mining action main deps builders map callbacks', () => {
getCurrentSecondarySubText: () => 'sub',
showMpvOsd: (text) => calls.push(`mine-osd:${text}`),
logError: (message) => calls.push(`err:${message}`),
onCardsMined: (count) => calls.push(`cards:${count}`),
handleMineSentenceDigitCore: () => calls.push('mine-digit'),
})();
assert.equal(mineDigit.getCurrentSecondarySubText(), 'sub');
mineDigit.showMpvOsd('done');
mineDigit.logError('bad', null);
mineDigit.onCardsMined(2);
mineDigit.handleMineSentenceDigitCore(2, {
subtitleTimingTracker: { track: true },
ankiIntegration: { enabled: true },
getCurrentSecondarySubText: () => 'sub',
showMpvOsd: () => {},
logError: () => {},
onCardsMined: () => {},
});
assert.deepEqual(calls, [
@@ -70,7 +67,6 @@ test('mining action main deps builders map callbacks', () => {
'copy-current',
'mine-osd:done',
'err:bad',
'cards:2',
'mine-digit',
]);
});
@@ -57,7 +57,6 @@ export function createBuildHandleMineSentenceDigitMainDepsHandler<
getCurrentSecondarySubText: () => string | undefined;
showMpvOsd: (text: string) => void;
logError: (message: string, err: unknown) => void;
onCardsMined: (count: number) => void;
handleMineSentenceDigitCore: (
count: number,
options: {
@@ -66,7 +65,6 @@ export function createBuildHandleMineSentenceDigitMainDepsHandler<
getCurrentSecondarySubText: () => string | undefined;
showMpvOsd: (text: string) => void;
logError: (message: string, err: unknown) => void;
onCardsMined: (count: number) => void;
},
) => void;
}) {
@@ -76,7 +74,6 @@ export function createBuildHandleMineSentenceDigitMainDepsHandler<
getCurrentSecondarySubText: () => deps.getCurrentSecondarySubText(),
showMpvOsd: (text: string) => deps.showMpvOsd(text),
logError: (message: string, err: unknown) => deps.logError(message, err),
onCardsMined: (count: number) => deps.onCardsMined(count),
handleMineSentenceDigitCore: (
count: number,
options: {
@@ -85,7 +82,6 @@ export function createBuildHandleMineSentenceDigitMainDepsHandler<
getCurrentSecondarySubText: () => string | undefined;
showMpvOsd: (text: string) => void;
logError: (message: string, err: unknown) => void;
onCardsMined: (count: number) => void;
},
) => deps.handleMineSentenceDigitCore(count, options),
});
+1 -3
View File
@@ -53,7 +53,6 @@ test('mine sentence digit handler forwards all dependencies', () => {
getCurrentSecondarySubText: () => 'secondary',
showMpvOsd: (text) => calls.push(`osd:${text}`),
logError: (message) => calls.push(`err:${message}`),
onCardsMined: (count) => calls.push(`cards:${count}`),
handleMineSentenceDigitCore: (count, options) => {
assert.equal(count, 4);
assert.equal(options.subtitleTimingTracker, tracker);
@@ -61,10 +60,9 @@ test('mine sentence digit handler forwards all dependencies', () => {
assert.equal(options.getCurrentSecondarySubText(), 'secondary');
options.showMpvOsd('mine');
options.logError('boom', new Error('x'));
options.onCardsMined(2);
},
});
handleMineSentenceDigit(4);
assert.deepEqual(calls, ['osd:mine', 'err:boom', 'cards:2']);
assert.deepEqual(calls, ['osd:mine', 'err:boom']);
});
-3
View File
@@ -48,7 +48,6 @@ export function createHandleMineSentenceDigitHandler<
getCurrentSecondarySubText: () => string | undefined;
showMpvOsd: (text: string) => void;
logError: (message: string, err: unknown) => void;
onCardsMined: (count: number) => void;
handleMineSentenceDigitCore: (
count: number,
options: {
@@ -57,7 +56,6 @@ export function createHandleMineSentenceDigitHandler<
getCurrentSecondarySubText: () => string | undefined;
showMpvOsd: (text: string) => void;
logError: (message: string, err: unknown) => void;
onCardsMined: (count: number) => void;
},
) => void;
}) {
@@ -68,7 +66,6 @@ export function createHandleMineSentenceDigitHandler<
getCurrentSecondarySubText: deps.getCurrentSecondarySubText,
showMpvOsd: deps.showMpvOsd,
logError: deps.logError,
onCardsMined: deps.onCardsMined,
});
};
}