Files
SubMiner/src/anki-integration/known-word-maturity.test.ts
T

106 lines
4.8 KiB
TypeScript

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);
});