mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-13 20:12:54 -07:00
fix: address coderabbit feedback
This commit is contained in:
@@ -141,3 +141,37 @@ test('manual clipboard subtitle update replaces sentence audio without touching
|
||||
[true],
|
||||
);
|
||||
});
|
||||
|
||||
test('manual clipboard subtitle update skips audio when sentence audio field is missing', async () => {
|
||||
const { service, updatedFields, mergeCalls, storedMedia } = createManualUpdateService({
|
||||
client: {
|
||||
addNote: async () => 0,
|
||||
addTags: async () => undefined,
|
||||
notesInfo: async () => [
|
||||
{
|
||||
noteId: 42,
|
||||
fields: {
|
||||
Expression: { value: '単語' },
|
||||
Sentence: { value: '' },
|
||||
ExpressionAudio: { value: '[sound:auto-expression.mp3]' },
|
||||
},
|
||||
},
|
||||
],
|
||||
updateNoteFields: async (_noteId, fields) => {
|
||||
updatedFields.push(fields);
|
||||
},
|
||||
storeMediaFile: async (filename) => {
|
||||
storedMedia.push(filename);
|
||||
},
|
||||
findNotes: async () => [42],
|
||||
retrieveMediaFile: async () => '',
|
||||
},
|
||||
});
|
||||
|
||||
await service.updateLastAddedFromClipboard('字幕');
|
||||
|
||||
assert.equal(storedMedia.length, 1);
|
||||
assert.equal(updatedFields.length, 1);
|
||||
assert.deepEqual(updatedFields[0], { Sentence: '字幕' });
|
||||
assert.equal(mergeCalls.length, 0);
|
||||
});
|
||||
|
||||
@@ -218,7 +218,7 @@ export class CardCreationService {
|
||||
fields,
|
||||
this.deps.getConfig(),
|
||||
);
|
||||
const sentenceAudioField = this.getResolvedSentenceAudioFieldName(noteInfo);
|
||||
const sentenceAudioField = this.getResolvedSentenceOnlyAudioFieldName(noteInfo);
|
||||
const sentenceField = this.deps.getEffectiveSentenceCardConfig().sentenceField;
|
||||
|
||||
const sentence = blocks.join(' ');
|
||||
@@ -721,6 +721,13 @@ export class CardCreationService {
|
||||
);
|
||||
}
|
||||
|
||||
private getResolvedSentenceOnlyAudioFieldName(noteInfo: CardCreationNoteInfo): string | null {
|
||||
return this.deps.resolveNoteFieldName(
|
||||
noteInfo,
|
||||
this.deps.getEffectiveSentenceCardConfig().audioField || 'SentenceAudio',
|
||||
);
|
||||
}
|
||||
|
||||
private createPendingNoteInfo(fields: Record<string, string>): CardCreationNoteInfo {
|
||||
return {
|
||||
noteId: -1,
|
||||
|
||||
@@ -34,6 +34,18 @@ test('guessAnilistMediaInfo fills missing guessit episode from filename parser',
|
||||
});
|
||||
});
|
||||
|
||||
test('guessAnilistMediaInfo ignores low-confidence parser details when guessit omits them', async () => {
|
||||
const result = await guessAnilistMediaInfo('/tmp/Season 2/Guessit Title.mkv', null, {
|
||||
runGuessit: async () => JSON.stringify({ title: 'Guessit Title' }),
|
||||
});
|
||||
assert.deepEqual(result, {
|
||||
title: 'Guessit Title',
|
||||
season: null,
|
||||
episode: null,
|
||||
source: 'guessit',
|
||||
});
|
||||
});
|
||||
|
||||
test('guessAnilistMediaInfo parses Little Witch Academia release filename', async () => {
|
||||
const filename =
|
||||
'/tmp/Little Witch Academia (2017) - S01E02 - 002 - Papiliodia [Bluray-1080p][10bit][h265][AC3 2.0][JA].mkv';
|
||||
|
||||
@@ -237,12 +237,13 @@ export async function guessAnilistMediaInfo(
|
||||
const year = firstYear(parsed.year);
|
||||
if (title) {
|
||||
const fallback = parseMediaInfo(target);
|
||||
const canUseFallbackDetails = fallback.confidence !== 'low';
|
||||
return {
|
||||
title: buildGuessitTitle(title, alternativeTitle),
|
||||
...(alternativeTitle ? { alternativeTitle } : {}),
|
||||
...(year ? { year } : {}),
|
||||
season: season ?? fallback.season,
|
||||
episode: episode ?? fallback.episode,
|
||||
season: season ?? (canUseFallbackDetails ? fallback.season : null),
|
||||
episode: episode ?? (canUseFallbackDetails ? fallback.episode : null),
|
||||
source: 'guessit',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -567,6 +567,19 @@ test('shouldExcludeTokenFromSubtitleAnnotations excludes standalone connective p
|
||||
assert.equal(shouldExcludeTokenFromSubtitleAnnotations(token), true);
|
||||
});
|
||||
|
||||
test('shouldExcludeTokenFromSubtitleAnnotations keeps lexical verbs whose reading matches connective particles', () => {
|
||||
const token = makeToken({
|
||||
surface: '立って',
|
||||
headword: '立つ',
|
||||
reading: 'タッテ',
|
||||
partOfSpeech: PartOfSpeech.verb,
|
||||
pos1: '動詞',
|
||||
pos2: '自立',
|
||||
});
|
||||
|
||||
assert.equal(shouldExcludeTokenFromSubtitleAnnotations(token), false);
|
||||
});
|
||||
|
||||
test('shouldExcludeTokenFromSubtitleAnnotations excludes rhetorical もんか grammar particle phrases', () => {
|
||||
for (const surface of ['もんか', 'ものか']) {
|
||||
const token = makeToken({
|
||||
|
||||
@@ -57,7 +57,6 @@ export const SUBTITLE_ANNOTATION_EXCLUDED_TERMS = new Set([
|
||||
'貴方',
|
||||
'もんか',
|
||||
'ものか',
|
||||
...STANDALONE_GRAMMAR_PARTICLE_PHRASES,
|
||||
]);
|
||||
const SUBTITLE_ANNOTATION_EXCLUDED_TRAILING_PARTICLE_SUFFIXES = new Set([
|
||||
'って',
|
||||
|
||||
@@ -223,6 +223,41 @@ test('time-pos and pause handlers report progress with correct urgency', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('time-pos handler logs post-watch update rejection without blocking later handlers', async () => {
|
||||
const calls: string[] = [];
|
||||
const timeHandler = createHandleMpvTimePosChangeHandler({
|
||||
recordPlaybackPosition: (time) => calls.push(`time:${time}`),
|
||||
reportJellyfinRemoteProgress: (force) => calls.push(`progress:${force ? 'force' : 'normal'}`),
|
||||
refreshDiscordPresence: () => calls.push('presence'),
|
||||
maybeRunAnilistPostWatchUpdate: async () => {
|
||||
calls.push('post-watch');
|
||||
throw new Error('boom');
|
||||
},
|
||||
logError: (message, error) => calls.push(`error:${message}:${(error as Error).message}`),
|
||||
});
|
||||
const pauseHandler = createHandleMpvPauseChangeHandler({
|
||||
recordPauseState: (paused) => calls.push(`pause:${paused ? 'yes' : 'no'}`),
|
||||
reportJellyfinRemoteProgress: (force) => calls.push(`progress:${force ? 'force' : 'normal'}`),
|
||||
refreshDiscordPresence: () => calls.push('presence'),
|
||||
});
|
||||
|
||||
timeHandler({ time: 12.5 });
|
||||
pauseHandler({ paused: true });
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
'time:12.5',
|
||||
'progress:normal',
|
||||
'presence',
|
||||
'post-watch',
|
||||
'pause:yes',
|
||||
'progress:force',
|
||||
'presence',
|
||||
'error:AniList post-watch update failed unexpectedly:boom',
|
||||
]);
|
||||
});
|
||||
|
||||
test('subtitle metrics change handler forwards patch payload', () => {
|
||||
let received: Record<string, unknown> | null = null;
|
||||
const handler = createHandleMpvSubtitleMetricsChangeHandler({
|
||||
|
||||
@@ -78,23 +78,19 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
||||
deps.ensureImmersionTrackerInitialized();
|
||||
deps.appState.immersionTracker?.recordPlaybackPosition?.(normalizedTimeSec);
|
||||
};
|
||||
const hasInitialPlaybackQuitOnDisconnectArg = (): boolean =>
|
||||
Boolean(
|
||||
deps.appState.initialArgs?.managedPlayback ||
|
||||
deps.appState.initialArgs?.jellyfinPlay ||
|
||||
deps.appState.initialArgs?.youtubePlay,
|
||||
);
|
||||
|
||||
return () => ({
|
||||
reportJellyfinRemoteStopped: () => deps.reportJellyfinRemoteStopped(),
|
||||
syncOverlayMpvSubtitleSuppression: () => deps.syncOverlayMpvSubtitleSuppression(),
|
||||
hasInitialPlaybackQuitOnDisconnectArg: () =>
|
||||
Boolean(
|
||||
deps.appState.initialArgs?.managedPlayback ||
|
||||
deps.appState.initialArgs?.jellyfinPlay ||
|
||||
deps.appState.initialArgs?.youtubePlay,
|
||||
),
|
||||
hasInitialPlaybackQuitOnDisconnectArg,
|
||||
isOverlayRuntimeInitialized: () => deps.appState.overlayRuntimeInitialized,
|
||||
shouldQuitOnDisconnectWhenOverlayRuntimeInitialized: () =>
|
||||
Boolean(
|
||||
deps.appState.initialArgs?.managedPlayback ||
|
||||
deps.appState.initialArgs?.jellyfinPlay ||
|
||||
deps.appState.initialArgs?.youtubePlay,
|
||||
),
|
||||
shouldQuitOnDisconnectWhenOverlayRuntimeInitialized: hasInitialPlaybackQuitOnDisconnectArg,
|
||||
isQuitOnDisconnectArmed: () => deps.getQuitOnDisconnectArmed(),
|
||||
scheduleQuitCheck: (callback: () => void) => deps.scheduleQuitCheck(callback),
|
||||
isMpvConnected: () => Boolean(deps.appState.mpvClient?.connected),
|
||||
|
||||
@@ -909,6 +909,7 @@ test('subtitle annotation CSS underlines JLPT tokens without changing token colo
|
||||
// popup/selection state.
|
||||
assert.doesNotMatch(plainJlptBlock, /(?:^|\n)\s*color\s*:/m);
|
||||
assert.doesNotMatch(plainJlptBlock, /text-decoration-line:\s*underline;/);
|
||||
assert.doesNotMatch(plainJlptBlock, /text-decoration\s*:[^;]*\bunderline\b/i);
|
||||
assert.match(
|
||||
plainJlptBlock,
|
||||
new RegExp(`border-bottom:\\s*2px\\s+solid\\s+var\\(--subtitle-jlpt-n${level}-color,`),
|
||||
|
||||
Reference in New Issue
Block a user