From d5983433c572bf941583548c041d40293344092f Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 9 May 2026 20:18:10 -0400 Subject: [PATCH] =?UTF-8?q?feat(web):=20PlayerOverflowMenu=20=E2=80=94=20s?= =?UTF-8?q?huffle/repeat/queue/volume=20in=20a=20menu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../lib/components/PlayerOverflowMenu.svelte | 97 +++++++++++++++++++ .../lib/components/PlayerOverflowMenu.test.ts | 31 ++++++ 2 files changed, 128 insertions(+) create mode 100644 web/src/lib/components/PlayerOverflowMenu.svelte create mode 100644 web/src/lib/components/PlayerOverflowMenu.test.ts diff --git a/web/src/lib/components/PlayerOverflowMenu.svelte b/web/src/lib/components/PlayerOverflowMenu.svelte new file mode 100644 index 00000000..39eb9035 --- /dev/null +++ b/web/src/lib/components/PlayerOverflowMenu.svelte @@ -0,0 +1,97 @@ + + + { menuOpen = false; }} onkeydown={(e) => e.key === 'Escape' && (menuOpen = false)} /> + +
+ + + {#if menuOpen} + + {/if} +
diff --git a/web/src/lib/components/PlayerOverflowMenu.test.ts b/web/src/lib/components/PlayerOverflowMenu.test.ts new file mode 100644 index 00000000..8d71c12f --- /dev/null +++ b/web/src/lib/components/PlayerOverflowMenu.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/svelte'; +import PlayerOverflowMenu from './PlayerOverflowMenu.svelte'; + +describe('PlayerOverflowMenu', () => { + it('renders the trigger button closed by default', () => { + render(PlayerOverflowMenu); + const trigger = screen.getByLabelText('Player options'); + expect(trigger.getAttribute('aria-expanded')).toBe('false'); + }); + + it('opens the menu on click and shows shuffle/repeat/queue/volume', async () => { + render(PlayerOverflowMenu); + const trigger = screen.getByLabelText('Player options'); + await fireEvent.click(trigger); + expect(trigger.getAttribute('aria-expanded')).toBe('true'); + expect(screen.getByText(/shuffle/i)).toBeInTheDocument(); + expect(screen.getByText(/repeat/i)).toBeInTheDocument(); + expect(screen.getByText(/queue/i)).toBeInTheDocument(); + expect(screen.getByLabelText('Volume')).toBeInTheDocument(); + }); + + it('Escape key closes the menu', async () => { + render(PlayerOverflowMenu); + await fireEvent.click(screen.getByLabelText('Player options')); + expect(screen.getByLabelText('Player options').getAttribute('aria-expanded')).toBe('true'); + window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); + await Promise.resolve(); + expect(screen.getByLabelText('Player options').getAttribute('aria-expanded')).toBe('false'); + }); +});