fix(epg): keep loading shimmer visible in both themes

This commit is contained in:
4gray committed 2026-09-05 14:18:44 +02:00
1 parent 28ece6f514
commit edebe0c019
4 files changed
+78 -4

No files matched your search

@@ -24,6 +24,7 @@ import {
applyTheme,
expectTextContrast,
expectThemeSurface,
expectSkeletonContrast,
} from './theme-contrast';
const epgCredentials = {
@@ -182,6 +183,32 @@ test('@epg @xtream @electron opens the programme dialog from a timeline block an
guide.locator('[data-when="now"] .desc').first()
);
}
// Keep a fresh channel's EPG IPC pending so the real list loading
// template stays mounted through both theme changes.
await app.electronApp.evaluate(({ ipcMain }) => {
ipcMain.removeHandler('XTREAM_REQUEST');
ipcMain.handle(
'XTREAM_REQUEST',
() =>
new Promise(() => {
// Released when this isolated Electron test app closes.
})
);
});
await app.mainWindow
.locator('[data-test-id="channel-item"]')
.nth(1)
.click();
const skeleton = guide.locator('.sk-time').first();
await expect(skeleton).toBeVisible();
for (const theme of ['light', 'dark'] as const) {
await applyTheme(app.mainWindow, theme);
await expectSkeletonContrast(skeleton, guide);
await expectSkeletonContrast(
guide.locator('.sk-title').first(),
guide
);
}
} finally {
await closeElectronApp(app);
}
@@ -153,3 +153,48 @@ export async function expectOverlayContrastOnWhite(
);
}
}
/** Skeletons are decorative, but their faintest gradient stop must remain
* distinguishable from the guide's background in either theme. */
export async function expectSkeletonContrast(
skeleton: Locator,
guide: Locator
) {
const background = await guide.evaluate((element) =>
getComputedStyle(element)
.backgroundColor.match(/[\d.]+/g)!
.slice(0, 3)
.map(Number)
);
const { data, info } = await sharp(
await skeleton.screenshot({ animations: 'disabled' })
)
.removeAlpha()
.raw()
.toBuffer({ resolveWithObject: true });
const luminance = (rgb: number[]) =>
rgb
.map((value) => {
const s = value / 255;
return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
})
.reduce(
(sum, value, index) =>
sum + value * [0.2126, 0.7152, 0.0722][index],
0
);
const bg = luminance(background);
// Middle row avoids the rounded transparent corners.
for (let x = 8; x < info.width - 8; x++) {
const offset =
(Math.floor(info.height / 2) * info.width + x) * info.channels;
const fg = luminance([
data[offset],
data[offset + 1],
data[offset + 2],
]);
expect(
(Math.max(fg, bg) + 0.05) / (Math.min(fg, bg) + 0.05)
).toBeGreaterThanOrEqual(1.3);
}
}
+3 -1
View File
@@ -95,7 +95,9 @@ EPG timeline, list, empty states and programme details use the library-local
selection and live accents. Text pairs with the actual surface in both themes;
current/playing titles must not force white onto a light selection tint.
Past programme text remains readable without reducing opacity on the whole
card. Theme changes resolve through CSS on the mounted components immediately.
card. List loading shimmer uses translucent primary text stops so placeholders
remain visible on either theme’s content surface. Theme changes resolve through
CSS on the mounted components immediately.
Electron E2E measures app-panel foreground/background contrast (including
translucency, ancestor opacity and the timeline’s sibling progress fill),
@@ -402,9 +402,9 @@
border-radius: 6px;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.06),
rgba(255, 255, 255, 0.12),
rgba(255, 255, 255, 0.06)
color-mix(in srgb, $text-primary 18%, transparent),
color-mix(in srgb, $text-primary 28%, transparent),
color-mix(in srgb, $text-primary 18%, transparent)
);
background-size: 200% 100%;
animation: epg-list-shimmer 1.4s linear infinite;