mirror of
https://github.com/hanzo-apps/world.git
synced 2026-10-30 14:53:09 +00:00
New opt-in "News Wall" panel: every live news channel plays at once in a responsive grid of controllable YouTube players (all muted). HOVER a tile → it gets audio focus (unmuted, others muted); leaving the wall mutes everything. The wall + its N players build lazily on first visibility and pause when it scrolls off (IntersectionObserver), so it costs nothing until toggled on and never decodes a hidden wall. Decomplect — one source each, shared with LiveNewsPanel, no duplication: - src/services/youtube.ts: the ONE YouTube IFrame API loader + player types (was braided into LiveNewsPanel; the grid embedder needed the same loader). - src/config/live-channels.ts: the ONE channel list + liveChannels()/getDefaultLiveChannel() (data belongs in config, not a panel). LiveNewsPanel + immersive now import it. Wiring: registered in createPanels, exported from the barrel, added to FULL_PANELS (enabled:false — opt-in like Live Webcams), styled (.stations-* responsive auto-fill grid with a live focus ring). e2e (stations-wall.spec.ts): one tile per channel; hover moves the single audio focus; leaving mutes all. variant-switch-inplace still green (the LiveNewsPanel refactor is behavior-preserving).
57 lines
3.0 KiB
TypeScript
57 lines
3.0 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
// News Wall — every live news channel at once, hover a tile for AUDIO FOCUS.
|
|
//
|
|
// The panel is opt-in (enabled:false, like Live Webcams), and its grid + N YouTube
|
|
// players build LAZILY the first time it scrolls into view. This test un-hides it and
|
|
// brings it into view to trigger that build, then asserts: one tile per channel, and
|
|
// that hovering a tile gives EXACTLY that tile audio focus (the .stations-audio-on ring
|
|
// + 🔊 badge) while muting the rest — the "hover to enable voice" behavior.
|
|
//
|
|
// The YouTube players themselves need real GL + network; the audio-FOCUS logic (class +
|
|
// badge toggling, and the mute()/unMute() calls it drives) runs regardless of whether a
|
|
// player finished loading, so the focus behavior is what we assert. We block the YT
|
|
// embed so the test stays hermetic and fast — the tiles + their hover handlers are built
|
|
// before (and independent of) the players.
|
|
|
|
test.describe('News Wall — all stations at once, hover for audio', () => {
|
|
test('one tile per channel; hover moves single audio focus; leaving mutes all', async ({ page }) => {
|
|
await page.route('**/*youtube*/**', (r) => r.abort());
|
|
|
|
await page.goto('/?variant=full');
|
|
|
|
// Un-hide the opt-in panel and scroll it into view → its IntersectionObserver fires
|
|
// → the wall builds. (Toggling it on via the Panels menu is a separate trivial path.)
|
|
await page.locator('.panel[data-panel="stations-wall"]').waitFor({ state: 'attached', timeout: 30000 });
|
|
await page.evaluate(() => {
|
|
const el = document.querySelector('.panel[data-panel="stations-wall"]') as HTMLElement | null;
|
|
el?.classList.remove('hidden');
|
|
el?.scrollIntoView();
|
|
});
|
|
|
|
// One tile per channel (full variant ships ≥ 4 news channels).
|
|
const tiles = page.locator('.stations-tile');
|
|
await expect(tiles.first()).toBeVisible({ timeout: 30000 });
|
|
expect(await tiles.count()).toBeGreaterThanOrEqual(4);
|
|
|
|
// Nothing focused initially → no tile is unmuted.
|
|
await expect(page.locator('.stations-tile.stations-audio-on')).toHaveCount(0);
|
|
|
|
// Hover the first tile → exactly it gets audio focus + the 🔊 badge.
|
|
await tiles.nth(0).hover();
|
|
await expect(page.locator('.stations-tile.stations-audio-on')).toHaveCount(1);
|
|
await expect(tiles.nth(0)).toHaveClass(/stations-audio-on/);
|
|
await expect(tiles.nth(0).locator('.stations-audio')).toHaveText('🔊');
|
|
|
|
// Hover a second tile → focus MOVES (still exactly one); the first is muted again.
|
|
await tiles.nth(1).hover();
|
|
await expect(page.locator('.stations-tile.stations-audio-on')).toHaveCount(1);
|
|
await expect(tiles.nth(1)).toHaveClass(/stations-audio-on/);
|
|
await expect(tiles.nth(0)).not.toHaveClass(/stations-audio-on/);
|
|
|
|
// Leaving the wall entirely mutes everything.
|
|
await page.locator('.stations-grid').dispatchEvent('mouseleave');
|
|
await expect(page.locator('.stations-tile.stations-audio-on')).toHaveCount(0);
|
|
});
|
|
});
|