mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 03:09:57 +00:00
The section_map plugin pins a ~20px clickable bar (#section-map, z-index:5) to the top of #player. The v3 left-rail hover-catcher (.v3-railzone::before) is full-height at z-index:30 with pointer-events:auto, so its top-left corner swallowed every click on the section map's first section — the left-most section was never clickable on the v3 desktop (macOS/Windows) UI. Drop the catcher below the 20px bar when the section map is present, mirroring the existing #section-map ~ #player-hud special-case in static/style.css. The rail still reveals from anywhere below the bar. Adds a Playwright regression test (hit-test of the top-left corner) with a negative control that re-raises the catcher to reproduce the bug. Fixes #616 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
94 lines
4.5 KiB
TypeScript
94 lines
4.5 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
// Regression coverage for the v3 Section Map "leftmost section unclickable" bug.
|
|
//
|
|
// The Section Map plugin pins a ~20px clickable bar (#section-map, z-index:5)
|
|
// to the very top of #player. The v3 chrome has a full-height invisible rail
|
|
// "catcher" (.v3-railzone::before, z-index:30, width:96px, pinned left/top:0)
|
|
// that reveals the hover rail. Because the catcher sat at top:0 and outranks
|
|
// the bar, its top-left corner swallowed every click on the section map's first
|
|
// section. Fix (static/v3/v3.css): `#section-map ~ #v3-railzone::before { top: 20px }`
|
|
// drops the catcher below the bar when the section map is present.
|
|
//
|
|
// We reproduce the plugin's bar exactly (first child of #player, the rendered
|
|
// position:relative / z-index:5 / 20px-tall state) and hit-test the top-left
|
|
// corner with elementFromPoint — that is precisely what a real click resolves
|
|
// against. A negative control re-raises the catcher to prove the test catches
|
|
// the bug.
|
|
|
|
// A fresh profile shows the blocking onboarding overlay; onboard via the API so
|
|
// it isn't (re)created over the player. Idempotent once onboarded.
|
|
test.beforeEach(async ({ request }) => {
|
|
await request.post('/api/profile', { data: { display_name: 'Section Map Tester' } });
|
|
await request.post('/api/progression/paths', { data: { add: ['guitar'] } });
|
|
await request.post('/api/progression/onboarding', { data: { action: 'skip' } });
|
|
});
|
|
|
|
async function openPlayerWithSectionMap(page) {
|
|
await page.goto('/');
|
|
await page.waitForSelector('.screen.active', { timeout: 10000 });
|
|
// The bug affects an already-onboarded user mid-song. The API skip above
|
|
// handles the common path; this persistent hide also covers a slow async
|
|
// profile render that could otherwise re-create the full-screen overlay and
|
|
// intercept the top-left hit-test (mirrors settings-tabbed.spec.ts).
|
|
await page.addStyleTag({ content: '#v3-onboarding{display:none!important;pointer-events:none!important}' });
|
|
await page.evaluate(() => {
|
|
// @ts-ignore — show the player screen (static #v3-railzone markup lives here).
|
|
window.showScreen('player');
|
|
const player = document.getElementById('player');
|
|
if (!player) throw new Error('#player missing');
|
|
|
|
// Reproduce the section_map plugin's rendered bar: first child of #player,
|
|
// 20px tall, full width, z-index:5, position:relative (its post-_smRender
|
|
// state), with a left-edge "first section" block at left:0.
|
|
const bar = document.createElement('div');
|
|
bar.id = 'section-map';
|
|
bar.style.cssText =
|
|
'position:relative;top:0;left:0;right:0;z-index:5;height:20px;background:rgba(8,8,16,0.7);cursor:pointer;';
|
|
const block = document.createElement('div');
|
|
block.id = 'sm-first-block';
|
|
block.style.cssText =
|
|
'position:absolute;left:0;width:30%;top:0;bottom:0;background:#3b82f6;';
|
|
bar.appendChild(block);
|
|
player.insertBefore(bar, player.firstChild);
|
|
});
|
|
await page.waitForSelector('#section-map', { state: 'attached', timeout: 5000 });
|
|
await page.waitForSelector('#v3-railzone', { state: 'attached', timeout: 5000 });
|
|
}
|
|
|
|
// What element does a click at the top-left strip land on? (x within the 96px
|
|
// catcher, y within the 20px bar.)
|
|
function hitTopLeft(page, x = 10, y = 8) {
|
|
return page.evaluate(({ x, y }) => {
|
|
const el = document.elementFromPoint(x, y) as HTMLElement | null;
|
|
return el ? { id: el.id, cls: el.className, tag: el.tagName } : null;
|
|
}, { x, y });
|
|
}
|
|
|
|
test('top-left of the section map receives clicks, not the rail catcher (fix present)', async ({ page }) => {
|
|
await openPlayerWithSectionMap(page);
|
|
|
|
const hit = await hitTopLeft(page);
|
|
// Click must resolve to the section map (the bar or its first-section block),
|
|
// never the rail hover-zone.
|
|
expect(hit).not.toBeNull();
|
|
expect(hit!.id).not.toBe('v3-railzone');
|
|
expect(['section-map', 'sm-first-block']).toContain(hit!.id);
|
|
});
|
|
|
|
test('negative control: re-raising the catcher to top:0 reproduces the bug', async ({ page }) => {
|
|
await openPlayerWithSectionMap(page);
|
|
|
|
// Undo the fix at runtime (highest-specificity inline-ish override) so the
|
|
// catcher again covers the bar's top-left — this is the pre-fix layout.
|
|
await page.evaluate(() => {
|
|
const style = document.createElement('style');
|
|
style.textContent = '#section-map ~ #v3-railzone::before { top: 0 !important; }';
|
|
document.head.appendChild(style);
|
|
});
|
|
|
|
const hit = await hitTopLeft(page);
|
|
// Without the fix, the rail catcher swallows the click.
|
|
expect(hit!.id).toBe('v3-railzone');
|
|
});
|