diff --git a/tests/browser/workspace-system.spec.mjs b/tests/browser/workspace-system.spec.mjs index 5c55a938..1f56f989 100644 --- a/tests/browser/workspace-system.spec.mjs +++ b/tests/browser/workspace-system.spec.mjs @@ -650,14 +650,16 @@ test('server settings that cannot be read say so in their rows', async ({ page } ? route.fulfill({ status: 500, contentType: 'application/json', body: JSON.stringify({ success: false, error: 'Injected failure' }) }) : route.continue())); await page.goto(`${workspace}#/system/appearance`); - for (const id of ['open-mode', 'timeout']) { + // #317: the Thumbnails rows are server settings too. + for (const id of ['open-mode', 'timeout', 'thumbnail-format', 'thumbnail-quality', 'thumbnail-edge']) { const row = page.locator('#workspace-main .setting-row').filter({ has: page.locator(`#${id}`) }); - await expect(row.locator('p.small')).toHaveText('Server settings could not be read.'); + await expect(row.locator('p.small').first()).toHaveText('Server settings could not be read.'); await expect(page.locator(`#${id}`)).toBeDisabled(); } // The browser rows are this browser's, and work as before. await expect(page.locator('#default-view')).toBeEnabled(); - await expect(page.locator('#workspace-main .setting-row p.small')).toHaveCount(2); + await expect(page.locator('#workspace-main .setting-row p.small', { hasText: 'Server settings could not be read.' })).toHaveCount(5); + await expect(page.locator('#workspace-main section.surface').first().locator('.setting-row p.small')).toHaveCount(2); }); test('stale revision shows the conflict status', async ({ page, request }) => { diff --git a/tests/js/workspace-system-thumbnails.test.js b/tests/js/workspace-system-thumbnails.test.js index 07d98ecf..e7169f11 100644 --- a/tests/js/workspace-system-thumbnails.test.js +++ b/tests/js/workspace-system-thumbnails.test.js @@ -67,7 +67,7 @@ const HOST = { thumbnail_format: 'webp', thumbnail_quality: 80, thumbnail_edge: const NO_JXL = { webp: { available: true }, jpeg: { available: true }, jxl: { available: false, encoder: null, reason: 'No JPEG XL encoder found. Install pillow-jxl-plugin.' } }; -async function mounted({ host = HOST, browserJxl = true, writeHost = async () => ({ success: true, persisted: true, revision: 'sha256:two' }) } = {}) { +async function mounted({ host = HOST, browserJxl = true, hostFails = false, writeHost = async () => ({ success: true, persisted: true, revision: 'sha256:two' }) } = {}) { const { mountThumbnailSettings } = await thumbnailsModule; const { createSystemDrafts } = await stateModule; const { createScope } = await coreModule; @@ -75,7 +75,7 @@ async function mounted({ host = HOST, browserJxl = true, writeHost = async () => let hostValues = null; const drafts = createSystemDrafts({ readBrowser: () => ({}), readRuntime: () => ({}), - readHost: async () => ({ values: hostValues, revision: 'sha256:one' }), + readHost: async () => { if (hostFails) throw new Error('offline'); return { values: hostValues, revision: 'sha256:one' }; }, writeBrowser: async () => ({ persisted: true }), writeHost, writeRuntime: async () => ({}), }); const scope = createScope(); @@ -181,3 +181,15 @@ test('Convert outdated opens Thumbnail maintenance on its convert scope; a forma assert.equal(reviews.length, 1, 'nothing starts by itself'); } finally { scope.dispose(); assert.equal(dom.listenerCount(), 0); dom.restore(); } }); + +test('rows whose server settings could not be read say so, like Appearance’s host rows', async () => { + const { HOST_UNAVAILABLE } = await import('../../web/js/workspace/system-appearance.mjs'); + const { dom, drafts, scope, surface, byId } = await mounted({ hostFails: true }); + const notes = () => surface.children.slice(2, 5).map(row => row.children[0].children.slice(2).map(note => note.textContent)); + try { + assert.deepEqual(notes(), [[], [], []]); + assert.equal(await drafts.reloadHost(), false); + assert.deepEqual(notes(), [[HOST_UNAVAILABLE], [HOST_UNAVAILABLE], [HOST_UNAVAILABLE]]); + for (const id of ['thumbnail-format', 'thumbnail-quality', 'thumbnail-edge']) assert.equal(byId(id).disabled, true, id); + } finally { scope.dispose(); assert.equal(dom.listenerCount(), 0); dom.restore(); } +}); diff --git a/web/js/workspace/system-thumbnails.mjs b/web/js/workspace/system-thumbnails.mjs index e51895e5..33b20b29 100644 --- a/web/js/workspace/system-thumbnails.mjs +++ b/web/js/workspace/system-thumbnails.mjs @@ -14,11 +14,13 @@ * automatic. After a save that changed the format, its line says existing previews keep their old format until * converted. * - * Host rows stay disabled until the server settings were read, like Appearance's. + * Host rows stay disabled until the server settings were read, like Appearance's, and say "Server settings could not + * be read." while a read failed. */ import { settingRow, operationRow } from './system-controls.mjs'; import { detectJxlSupport } from './thumbnail-support.mjs'; import { openThumbnailReview } from './thumbnail-review.mjs'; +import { HOST_UNAVAILABLE } from './system-appearance.mjs'; export const FORMAT_OPTIONS = Object.freeze([['webp', 'WebP'], ['jxl', 'JPEG XL'], ['jpeg', 'JPEG']]); export const QUALITY_LIMIT = Object.freeze({ min: 1, max: 100 }); @@ -84,7 +86,6 @@ export function mountThumbnailSettings(container, { scope, drafts, api, activity let browserJxl; let savedFormat; const format = controls[0]; - const notes = []; const repaint = () => { const encoders = drafts.get('host', 'thumbnail_encoders'); for (const { input, key } of controls) { @@ -95,12 +96,13 @@ export function mountThumbnailSettings(container, { scope, drafts, api, activity const jxl = Array.from(format.input.children).find(option => option.value === 'jxl'); const unavailable = encoders?.jxl?.available === false; if (jxl) jxl.disabled = unavailable; - for (const note of notes.splice(0)) note.remove(); - const lines = [unavailable ? encoders.jxl.reason : null, browserJxl === false ? BROWSER_NO_JXL : null]; - for (const line of lines.filter(Boolean)) { - const note = el('p', line, 'small'); - format.text.append(note); - notes.push(note); + const failed = drafts.hostState?.() === 'failed'; + for (const row of controls) { + for (const note of row.notes ?? []) note.remove(); + const lines = [failed && drafts.get('host', row.key) === undefined ? HOST_UNAVAILABLE : null]; + if (row === format) lines.push(unavailable ? encoders.jxl.reason : null, browserJxl === false ? BROWSER_NO_JXL : null); + row.notes = lines.filter(Boolean).map(line => el('p', line, 'small')); + row.text.append(...row.notes); } // A format save (not the first read) leaves the existing previews outdated: say so, never convert. const saved = drafts.saved('host').thumbnail_format;