mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-14 13:55:55 -07:00
Anki maturity-based known-word highlighting (#172)
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import type { AnkiConnectConfig } from '../types/anki';
|
||||
import {
|
||||
DEFAULT_MATURE_INTERVAL_THRESHOLD_DAYS,
|
||||
buildKnownWordMaturityTierQueries,
|
||||
classifyKnownWordNoteTier,
|
||||
getKnownWordMaturityEnabled,
|
||||
getMatureIntervalThresholdDays,
|
||||
maxKnownWordMaturityTier,
|
||||
sanitizeKnownWordMaturityTier,
|
||||
} from './known-word-maturity';
|
||||
|
||||
function makeConfig(knownWords: AnkiConnectConfig['knownWords']): AnkiConnectConfig {
|
||||
return { url: 'http://127.0.0.1:8765', knownWords } as AnkiConnectConfig;
|
||||
}
|
||||
|
||||
test('maturity is enabled only when both highlight and maturity flags are on', () => {
|
||||
assert.equal(
|
||||
getKnownWordMaturityEnabled(makeConfig({ highlightEnabled: true, maturityEnabled: true })),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
getKnownWordMaturityEnabled(makeConfig({ highlightEnabled: false, maturityEnabled: true })),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
getKnownWordMaturityEnabled(makeConfig({ highlightEnabled: true, maturityEnabled: false })),
|
||||
false,
|
||||
);
|
||||
assert.equal(getKnownWordMaturityEnabled(makeConfig({ highlightEnabled: true })), false);
|
||||
assert.equal(getKnownWordMaturityEnabled(makeConfig(undefined)), false);
|
||||
});
|
||||
|
||||
test('mature threshold falls back to default for invalid values', () => {
|
||||
assert.equal(DEFAULT_MATURE_INTERVAL_THRESHOLD_DAYS, 21);
|
||||
assert.equal(getMatureIntervalThresholdDays(makeConfig({ matureThresholdDays: 30 })), 30);
|
||||
assert.equal(getMatureIntervalThresholdDays(makeConfig({ matureThresholdDays: 14.9 })), 14);
|
||||
assert.equal(getMatureIntervalThresholdDays(makeConfig({ matureThresholdDays: 0 })), 21);
|
||||
assert.equal(getMatureIntervalThresholdDays(makeConfig({ matureThresholdDays: -5 })), 21);
|
||||
assert.equal(getMatureIntervalThresholdDays(makeConfig({ matureThresholdDays: Number.NaN })), 21);
|
||||
assert.equal(getMatureIntervalThresholdDays(makeConfig({})), 21);
|
||||
assert.equal(getMatureIntervalThresholdDays(makeConfig(undefined)), 21);
|
||||
});
|
||||
|
||||
test('tier queries append Anki search props to a deck scope query', () => {
|
||||
const queries = buildKnownWordMaturityTierQueries('deck:"Mining"', 21);
|
||||
assert.equal(queries.mature, 'deck:"Mining" prop:ivl>=21 -is:learn');
|
||||
assert.equal(queries.young, 'deck:"Mining" prop:ivl>=1 prop:ivl<21 -is:learn');
|
||||
assert.equal(queries.learning, 'deck:"Mining" is:learn');
|
||||
});
|
||||
|
||||
test('interval tiers exclude (re)learning cards so the buckets stay disjoint', () => {
|
||||
const queries = buildKnownWordMaturityTierQueries('deck:"Mining"', 21);
|
||||
// A lapsed card keeps an interval of at least the lapse minInt (>= 1), so
|
||||
// without the exclusion the young query would claim every relearning card
|
||||
// and the learning tier could only ever match brand-new cards mid-step.
|
||||
for (const intervalQuery of [queries.mature, queries.young]) {
|
||||
assert.ok(intervalQuery.includes('-is:learn'));
|
||||
}
|
||||
assert.equal(queries.learning, 'deck:"Mining" is:learn');
|
||||
});
|
||||
|
||||
test('tier queries with an empty scope query have no leading space', () => {
|
||||
const queries = buildKnownWordMaturityTierQueries('', 30);
|
||||
assert.equal(queries.mature, 'prop:ivl>=30 -is:learn');
|
||||
assert.equal(queries.young, 'prop:ivl>=1 prop:ivl<30 -is:learn');
|
||||
assert.equal(queries.learning, 'is:learn');
|
||||
});
|
||||
|
||||
test('note classification picks the most mature matching tier', () => {
|
||||
const sets = {
|
||||
mature: new Set([1, 4]),
|
||||
young: new Set([2, 4]),
|
||||
learning: new Set([3, 4, 2]),
|
||||
};
|
||||
assert.equal(classifyKnownWordNoteTier(1, sets), 'mature');
|
||||
assert.equal(classifyKnownWordNoteTier(2, sets), 'young');
|
||||
assert.equal(classifyKnownWordNoteTier(3, sets), 'learning');
|
||||
// Note with mature, young, and learning cards: most mature card wins.
|
||||
assert.equal(classifyKnownWordNoteTier(4, sets), 'mature');
|
||||
assert.equal(classifyKnownWordNoteTier(99, sets), 'new');
|
||||
});
|
||||
|
||||
test('maxKnownWordMaturityTier picks the higher tier and tolerates null', () => {
|
||||
assert.equal(maxKnownWordMaturityTier('mature', 'new'), 'mature');
|
||||
assert.equal(maxKnownWordMaturityTier('learning', 'young'), 'young');
|
||||
assert.equal(maxKnownWordMaturityTier('new', null), 'new');
|
||||
assert.equal(maxKnownWordMaturityTier(null, 'learning'), 'learning');
|
||||
assert.equal(maxKnownWordMaturityTier(null, null), null);
|
||||
assert.equal(maxKnownWordMaturityTier(undefined, undefined), null);
|
||||
});
|
||||
|
||||
test('sanitizeKnownWordMaturityTier accepts only valid tiers', () => {
|
||||
assert.equal(sanitizeKnownWordMaturityTier('mature'), 'mature');
|
||||
assert.equal(sanitizeKnownWordMaturityTier('young'), 'young');
|
||||
assert.equal(sanitizeKnownWordMaturityTier('learning'), 'learning');
|
||||
assert.equal(sanitizeKnownWordMaturityTier('new'), 'new');
|
||||
assert.equal(sanitizeKnownWordMaturityTier('MATURE'), null);
|
||||
assert.equal(sanitizeKnownWordMaturityTier(''), null);
|
||||
assert.equal(sanitizeKnownWordMaturityTier(21), null);
|
||||
assert.equal(sanitizeKnownWordMaturityTier(null), null);
|
||||
assert.equal(sanitizeKnownWordMaturityTier(undefined), null);
|
||||
});
|
||||
Reference in New Issue
Block a user