mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-18 17:16:19 -07:00
fix(jimaku): ignore stale media info responses
- Prevent closed or superseded modals from applying late media info - Add regression coverage
This commit is contained in:
@@ -488,3 +488,88 @@ test('a slow files reply for a previously selected entry is ignored', async () =
|
|||||||
Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
|
Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('media info arriving after the modal closed does not fill inputs or search', async () => {
|
||||||
|
const globals = globalThis as typeof globalThis & { window?: unknown; document?: unknown };
|
||||||
|
const previousWindow = globals.window;
|
||||||
|
const previousDocument = globals.document;
|
||||||
|
|
||||||
|
let resolveMediaInfo!: (info: unknown) => void;
|
||||||
|
let searchCalls = 0;
|
||||||
|
const electronAPI = {
|
||||||
|
getJimakuMediaInfo: () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
resolveMediaInfo = resolve;
|
||||||
|
}),
|
||||||
|
jimakuSearchEntries: async () => {
|
||||||
|
searchCalls += 1;
|
||||||
|
return { ok: true, data: [] };
|
||||||
|
},
|
||||||
|
notifyOverlayModalClosed: () => {},
|
||||||
|
} as unknown as ElectronAPI;
|
||||||
|
|
||||||
|
Object.defineProperty(globalThis, 'window', {
|
||||||
|
configurable: true,
|
||||||
|
value: { electronAPI },
|
||||||
|
});
|
||||||
|
Object.defineProperty(globalThis, 'document', {
|
||||||
|
configurable: true,
|
||||||
|
value: {
|
||||||
|
activeElement: null,
|
||||||
|
createElement: () => createElementStub(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const state = createRendererState();
|
||||||
|
const titleInput = { value: '' };
|
||||||
|
const status = { textContent: '', style: { color: '' } };
|
||||||
|
|
||||||
|
const ctx = {
|
||||||
|
dom: {
|
||||||
|
overlay: { classList: createClassList() },
|
||||||
|
jimakuModal: { classList: createClassList(['hidden']), setAttribute: () => {} },
|
||||||
|
jimakuTitleInput: titleInput,
|
||||||
|
jimakuSeasonInput: { value: '' },
|
||||||
|
jimakuEpisodeInput: { value: '' },
|
||||||
|
jimakuSearchButton: { addEventListener: () => {} },
|
||||||
|
jimakuCloseButton: { addEventListener: () => {} },
|
||||||
|
jimakuStatus: status,
|
||||||
|
jimakuEntriesSection: { classList: createClassList(['hidden']) },
|
||||||
|
jimakuEntriesList: createListStub(),
|
||||||
|
jimakuFilesSection: { classList: createClassList(['hidden']) },
|
||||||
|
jimakuFilesList: createListStub(),
|
||||||
|
jimakuBroadenButton: { classList: createClassList(['hidden']), addEventListener: () => {} },
|
||||||
|
jimakuTabAnimeButton: { classList: createClassList(['active']), setAttribute: () => {} },
|
||||||
|
jimakuTabLiveActionButton: { classList: createClassList(), setAttribute: () => {} },
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
|
||||||
|
const jimakuModal = createJimakuModal(ctx as never, {
|
||||||
|
modalStateReader: { isAnyModalOpen: () => false },
|
||||||
|
syncSettingsModalSubtitleSuppression: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
jimakuModal.openJimakuModal();
|
||||||
|
await flushAsyncWork();
|
||||||
|
jimakuModal.closeJimakuModal();
|
||||||
|
|
||||||
|
resolveMediaInfo({
|
||||||
|
title: 'Shinzanmono',
|
||||||
|
season: 1,
|
||||||
|
episode: 3,
|
||||||
|
confidence: 'high',
|
||||||
|
filename: 'Shinzanmono S01E03.mkv',
|
||||||
|
rawTitle: 'Shinzanmono S01E03',
|
||||||
|
});
|
||||||
|
await flushAsyncWork();
|
||||||
|
|
||||||
|
assert.equal(titleInput.value, '');
|
||||||
|
assert.equal(searchCalls, 0);
|
||||||
|
assert.equal(status.textContent, 'Loading media info...');
|
||||||
|
} finally {
|
||||||
|
Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
|
||||||
|
Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -307,9 +307,13 @@ export function createJimakuModal(
|
|||||||
resetJimakuLists();
|
resetJimakuLists();
|
||||||
renderTabs();
|
renderTabs();
|
||||||
|
|
||||||
|
// Media info can resolve after the user already closed the modal or
|
||||||
|
// started their own search; a stale reply must not touch the inputs.
|
||||||
|
const generation = searchGeneration;
|
||||||
window.electronAPI
|
window.electronAPI
|
||||||
.getJimakuMediaInfo()
|
.getJimakuMediaInfo()
|
||||||
.then((info: JimakuMediaInfo) => {
|
.then((info: JimakuMediaInfo) => {
|
||||||
|
if (generation !== searchGeneration) return;
|
||||||
ctx.dom.jimakuTitleInput.value = info.title || '';
|
ctx.dom.jimakuTitleInput.value = info.title || '';
|
||||||
ctx.dom.jimakuSeasonInput.value = info.season ? String(info.season) : '';
|
ctx.dom.jimakuSeasonInput.value = info.season ? String(info.season) : '';
|
||||||
ctx.dom.jimakuEpisodeInput.value = info.episode ? String(info.episode) : '';
|
ctx.dom.jimakuEpisodeInput.value = info.episode ? String(info.episode) : '';
|
||||||
@@ -324,6 +328,7 @@ export function createJimakuModal(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
if (generation !== searchGeneration) return;
|
||||||
setJimakuStatus('Failed to load media info.', true);
|
setJimakuStatus('Failed to load media info.', true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user