Files
Will Miao f9cb3988aa fix(example-images): repair partial folders and surface failure reasons
- Add an atomic .lm-examples-complete marker; only a folder with media and
  the marker is skipped, so interrupted or legacy folders are re-checked by
  the per-image existence path. Clear the marker before each pass.
- Treat processed/failed folders without a marker as pending so the
  auto-download pre-check repairs them, and clear markers when provider
  metadata is refreshed or custom images are imported.
- Bound stale-metadata refreshes at 90s so one hung provider cannot park the
  serial run; count metadata-missing models as retryable.
- Record failure reasons in the progress file, expose failure_summary, and
  render a no-media/unavailable/retry breakdown in the progress panel with a
  warning completion toast.
- Add marker, partial-repair, timeout, failure-summary and UI tests.
2026-11-09 17:26:22 +08:00

144 lines
4.8 KiB
JavaScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
vi.mock('../../../static/js/utils/uiHelpers.js', () => ({
showToast: vi.fn(),
}));
vi.mock('../../../static/js/state/index.js', () => ({
state: { global: { settings: {} } },
}));
vi.mock('../../../static/js/utils/storageHelpers.js', () => ({
getStorageItem: vi.fn(() => false),
setStorageItem: vi.fn(),
}));
vi.mock('../../../static/js/managers/SettingsManager.js', () => ({
settingsManager: {},
}));
const { ExampleImagesManager } = await import(
'../../../static/js/managers/ExampleImagesManager.js'
);
describe('ExampleImagesManager ETA rendering', () => {
beforeEach(() => {
document.body.innerHTML = `
<span id="elapsedTime"></span>
<span id="remainingTime"></span>
<div id="downloadFailureSummary" class="hidden"></div>
`;
vi.stubGlobal('fetch', vi.fn(async () => ({
ok: true,
json: async () => ({ success: false }),
})));
});
it('uses active elapsed time and the throughput estimator', () => {
const manager = new ExampleImagesManager();
manager.etaTracker = {
update: vi.fn(() => 123),
reset: vi.fn(),
};
manager.updateTimeStats({
status: 'running',
completed: 5,
total: 10,
start_time: 1,
elapsed_seconds: 42.5,
});
expect(document.getElementById('elapsedTime').textContent).toBe('00:00:42');
expect(document.getElementById('remainingTime').textContent).toBe('00:02:03');
expect(manager.etaTracker.update).toHaveBeenCalledWith(5, 10, { running: true });
});
it('does not count paused time as progress', () => {
const manager = new ExampleImagesManager();
manager.etaTracker = {
update: vi.fn(() => null),
reset: vi.fn(),
};
manager.updateTimeStats({
status: 'paused',
completed: 5,
total: 10,
elapsed_seconds: 42.5,
});
expect(document.getElementById('remainingTime').textContent).toBe('--:--:--');
expect(manager.etaTracker.update).toHaveBeenCalledWith(5, 10, { running: false });
});
it('falls back to start_time when elapsed_seconds is absent', () => {
const manager = new ExampleImagesManager();
manager.etaTracker = {
update: vi.fn(() => 60),
reset: vi.fn(),
};
manager.updateTimeStats({
status: 'running',
completed: 5,
total: 10,
start_time: Date.now() / 1000 - 30,
});
const elapsed = Number(
document.getElementById('elapsedTime').textContent.split(':').pop()
);
expect(elapsed).toBeGreaterThanOrEqual(29);
expect(elapsed).toBeLessThanOrEqual(31);
expect(document.getElementById('remainingTime').textContent).toBe('00:01:00');
});
});
describe('ExampleImagesManager failure summary', () => {
beforeEach(() => {
document.body.innerHTML = `
<div id="downloadFailureSummary" class="hidden"></div>
`;
vi.stubGlobal('fetch', vi.fn(async () => ({
ok: true,
json: async () => ({ success: false }),
})));
});
it('renders skipped reasons and retry count', () => {
const manager = new ExampleImagesManager();
const visible = manager.updateFailureSummary({
failure_summary: {
no_media: 2,
unavailable: 3,
metadata_missing: 1,
unknown: 1,
total: 7,
},
retryable_count: 2,
});
const element = document.getElementById('downloadFailureSummary');
expect(visible).toBe(true);
expect(element.classList.contains('hidden')).toBe(false);
expect(element.textContent).toBe(
'Not downloaded: 2 without Civitai media · 3 unavailable at source · ' +
'1 metadata missing · 1 unclassified · 2 will retry'
);
expect(element.title).toContain('Unavailable at source');
expect(manager.hasReportedIssues({ failure_summary: { total: 7, unavailable: 3 } })).toBe(true);
});
it('hides itself when there is nothing to report', () => {
const manager = new ExampleImagesManager();
const visible = manager.updateFailureSummary({
failure_summary: { total: 0 },
retryable_count: 0,
});
const element = document.getElementById('downloadFailureSummary');
expect(visible).toBe(false);
expect(element.classList.contains('hidden')).toBe(true);
expect(element.textContent).toBe('');
expect(manager.hasReportedIssues({ failure_summary: { total: 0 } })).toBe(false);
});
});