Enhance AniList character dictionary sync and subtitle features (#15)

This commit is contained in:
2026-03-07 18:30:59 -08:00
committed by GitHub
parent 2f07c3407a
commit e18985fb14
696 changed files with 14297 additions and 173564 deletions

View File

@@ -1,6 +1,10 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { serializeSubtitleMarkup, serializeSubtitleWebsocketMessage } from './subtitle-ws';
import {
serializeInitialSubtitleWebsocketMessage,
serializeSubtitleMarkup,
serializeSubtitleWebsocketMessage,
} from './subtitle-ws';
import { PartOfSpeech, type SubtitleData } from '../../types';
const frequencyOptions = {
@@ -78,6 +82,51 @@ test('serializeSubtitleMarkup includes known, n+1, jlpt, and frequency classes',
assert.match(markup, /word word-frequency-band-1/);
});
test('serializeSubtitleMarkup preserves tooltip attrs and name-match precedence', () => {
const payload: SubtitleData = {
text: 'ignored',
tokens: [
{
surface: '無事',
reading: 'ぶじ',
headword: '無事',
startPos: 0,
endPos: 2,
partOfSpeech: PartOfSpeech.other,
isMerged: false,
isKnown: true,
isNPlusOneTarget: false,
jlptLevel: 'N2',
frequencyRank: 745,
},
{
surface: 'アレクシア',
reading: 'あれくしあ',
headword: 'アレクシア',
startPos: 2,
endPos: 7,
partOfSpeech: PartOfSpeech.other,
isMerged: false,
isKnown: false,
isNPlusOneTarget: false,
isNameMatch: true,
frequencyRank: 12,
},
],
};
const markup = serializeSubtitleMarkup(payload, frequencyOptions);
assert.match(
markup,
/<span class="word word-known word-jlpt-n2" data-reading="ぶじ" data-headword="無事" data-frequency-rank="745" data-jlpt-level="N2">無事<\/span>/,
);
assert.match(
markup,
/<span class="word word-name-match" data-reading="あれくしあ" data-headword="アレクシア" data-frequency-rank="12">アレクシア<\/span>/,
);
assert.doesNotMatch(markup, /word-name-match word-known|word-known word-name-match/);
});
test('serializeSubtitleWebsocketMessage emits sentence payload', () => {
const payload: SubtitleData = {
text: '字幕',
@@ -85,5 +134,101 @@ test('serializeSubtitleWebsocketMessage emits sentence payload', () => {
};
const raw = serializeSubtitleWebsocketMessage(payload, frequencyOptions);
assert.deepEqual(JSON.parse(raw), { sentence: '字幕' });
assert.deepEqual(JSON.parse(raw), {
version: 1,
text: '字幕',
sentence: '字幕',
tokens: [],
});
});
test('serializeSubtitleWebsocketMessage emits structured token api payload', () => {
const payload: SubtitleData = {
text: '無事',
tokens: [
{
surface: '無事',
reading: 'ぶじ',
headword: '無事',
startPos: 0,
endPos: 2,
partOfSpeech: PartOfSpeech.other,
isMerged: false,
isKnown: true,
isNPlusOneTarget: false,
jlptLevel: 'N2',
frequencyRank: 745,
},
],
};
const raw = serializeSubtitleWebsocketMessage(payload, frequencyOptions);
assert.deepEqual(JSON.parse(raw), {
version: 1,
text: '無事',
sentence:
'<span class="word word-known word-jlpt-n2" data-reading="ぶじ" data-headword="無事" data-frequency-rank="745" data-jlpt-level="N2">無事</span>',
tokens: [
{
surface: '無事',
reading: 'ぶじ',
headword: '無事',
startPos: 0,
endPos: 2,
partOfSpeech: PartOfSpeech.other,
isMerged: false,
isKnown: true,
isNPlusOneTarget: false,
isNameMatch: false,
jlptLevel: 'N2',
frequencyRank: 745,
className: 'word word-known word-jlpt-n2',
frequencyRankLabel: '745',
jlptLevelLabel: 'N2',
},
],
});
});
test('serializeInitialSubtitleWebsocketMessage keeps annotated current subtitle content', () => {
const payload: SubtitleData = {
text: 'ignored fallback',
tokens: [
{
surface: '既知',
reading: '',
headword: '',
startPos: 0,
endPos: 2,
partOfSpeech: PartOfSpeech.other,
isMerged: false,
isKnown: true,
isNPlusOneTarget: false,
},
],
};
const raw = serializeInitialSubtitleWebsocketMessage(payload, frequencyOptions);
assert.deepEqual(JSON.parse(raw ?? ''), {
version: 1,
text: 'ignored fallback',
sentence: '<span class="word word-known">既知</span>',
tokens: [
{
surface: '既知',
reading: '',
headword: '',
startPos: 0,
endPos: 2,
partOfSpeech: PartOfSpeech.other,
isMerged: false,
isKnown: true,
isNPlusOneTarget: false,
isNameMatch: false,
className: 'word word-known',
frequencyRankLabel: null,
jlptLevelLabel: null,
},
],
});
});