Add theme test & refactor context menu items

Extract shared mediaMenuItems to remove duplication and simplify context menu calls. Add a light-theme context menu handler and a "Run theme test" button that executes runContextMenuThemeTest(), which programmatically renders menus to verify the theme attribute and .puter-theme-dark class for dark, light, and default cases (mirrors existing Playwright assertions). Update testContextMenu and testContextMenuDark to use the new item factory and clean up rendered menus after checks. UI text is updated to show pass/fail results.
This commit is contained in:
jelveh
2026-06-24 11:29:45 -07:00
parent b01cd3bfa3
commit 4f68670aa7
+79 -43
View File
@@ -79,6 +79,8 @@
<button oncontextmenu="testContextMenu(event); return false;">Right-click me</button>
<button onclick="testContextMenuClick(event)">Click for Context Menu</button>
<button oncontextmenu="testContextMenuDark(event); return false;">Right-click me for dark menu</button>
<button oncontextmenu="testContextMenuLight(event); return false;">Right-click me for light menu</button>
<button onclick="runContextMenuThemeTest()">Run theme test &#x2713;</button>
<div class="result" id="menu-result">Right-click or click a button to test...</div>
</div>
@@ -195,53 +197,87 @@
paste: svg('<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1"/>'),
};
// Shared item list so the default/dark/light triggers stay identical
// apart from the `theme` they request.
const mediaMenuItems = () => [
{ label: 'Get Info', icon: ICONS.info, action: () => log('menu', 'Get Info') },
'-',
{ label: 'Play Next', icon: ICONS.play, action: () => log('menu', 'Play Next') },
{ label: 'Add to Queue', icon: ICONS.queue, action: () => log('menu', 'Add to Queue') },
'-',
{ label: 'Go to Artist', icon: ICONS.user, action: () => log('menu', 'Go to Artist') },
{ label: 'Go to Album', icon: ICONS.disc, action: () => log('menu', 'Go to Album') },
'-',
{ label: 'Add to Playlist', icon: ICONS.plus, items: [
{ label: 'Favorites', action: () => log('menu', 'Add to Favorites') },
{ label: 'Workout Mix', action: () => log('menu', 'Add to Workout Mix') },
{ label: 'Chill Vibes', action: () => log('menu', 'Add to Chill Vibes') },
]},
{ label: 'Download', icon: ICONS.download, action: () => log('menu', 'Download') },
{ label: 'Delete', icon: ICONS.trash, type: 'danger', action: () => log('menu', 'Delete') },
];
function testContextMenu(e) {
puter.ui.contextMenu({
x: e.clientX,
y: e.clientY,
items: [
{ label: 'Get Info', icon: ICONS.info, action: () => log('menu', 'Get Info') },
'-',
{ label: 'Play Next', icon: ICONS.play, action: () => log('menu', 'Play Next') },
{ label: 'Add to Queue', icon: ICONS.queue, action: () => log('menu', 'Add to Queue') },
'-',
{ label: 'Go to Artist', icon: ICONS.user, action: () => log('menu', 'Go to Artist') },
{ label: 'Go to Album', icon: ICONS.disc, action: () => log('menu', 'Go to Album') },
'-',
{ label: 'Add to Playlist', icon: ICONS.plus, items: [
{ label: 'Favorites', action: () => log('menu', 'Add to Favorites') },
{ label: 'Workout Mix', action: () => log('menu', 'Add to Workout Mix') },
{ label: 'Chill Vibes', action: () => log('menu', 'Add to Chill Vibes') },
]},
{ label: 'Download', icon: ICONS.download, action: () => log('menu', 'Download') },
{ label: 'Delete', icon: ICONS.trash, type: 'danger', action: () => log('menu', 'Delete') },
],
});
puter.ui.contextMenu({ x: e.clientX, y: e.clientY, items: mediaMenuItems() });
}
function testContextMenuDark(e) {
puter.ui.contextMenu({
x: e.clientX,
y: e.clientY,
theme: 'dark',
items: [
{ label: 'Get Info', icon: ICONS.info, action: () => log('menu', 'Get Info') },
'-',
{ label: 'Play Next', icon: ICONS.play, action: () => log('menu', 'Play Next') },
{ label: 'Add to Queue', icon: ICONS.queue, action: () => log('menu', 'Add to Queue') },
'-',
{ label: 'Go to Artist', icon: ICONS.user, action: () => log('menu', 'Go to Artist') },
{ label: 'Go to Album', icon: ICONS.disc, action: () => log('menu', 'Go to Album') },
'-',
{ label: 'Add to Playlist', icon: ICONS.plus, items: [
{ label: 'Favorites', action: () => log('menu', 'Add to Favorites') },
{ label: 'Workout Mix', action: () => log('menu', 'Add to Workout Mix') },
{ label: 'Chill Vibes', action: () => log('menu', 'Add to Chill Vibes') },
]},
{ label: 'Download', icon: ICONS.download, action: () => log('menu', 'Download') },
{ label: 'Delete', icon: ICONS.trash, type: 'danger', action: () => log('menu', 'Delete') },
],
});
puter.ui.contextMenu({ x: e.clientX, y: e.clientY, theme: 'dark', items: mediaMenuItems() });
}
function testContextMenuLight(e) {
puter.ui.contextMenu({ x: e.clientX, y: e.clientY, theme: 'light', items: mediaMenuItems() });
}
// Programmatic check that the `theme` option is forwarded onto the
// rendered <puter-context-menu>. Mirrors the Playwright assertions in
// tests/e2e/specs/menubar-contextmenu-theme.spec.js, but runs in-page so
// the plumbing can be verified manually without the e2e harness.
// Note: the base component toggles `.puter-theme-dark` on the host;
// "light" is the absence of that class (there is no .puter-theme-light).
function runContextMenuThemeTest() {
const resultEl = document.getElementById('menu-result');
const failures = [];
const check = (cond, msg) => { if ( ! cond ) failures.push(msg); };
// Render one menu for `theme`, then return the rendered host element.
// connectedCallback resolves the theme synchronously on append, so the
// attribute and class are already settled by the time we query.
const spawn = (theme) => {
document.querySelectorAll('puter-context-menu').forEach(el => el.remove());
puter.ui.contextMenu({
x: 40, y: 220,
...(theme ? { theme } : {}),
items: [{ label: 'Probe', action: () => {} }],
});
return document.querySelector('puter-context-menu');
};
// theme: 'dark' → theme attribute set + .puter-theme-dark on host.
const dark = spawn('dark');
check(dark, 'dark: no <puter-context-menu> was rendered');
check(dark?.getAttribute('theme') === 'dark', 'dark: theme attribute is not "dark"');
check(dark?.classList.contains('puter-theme-dark'), 'dark: missing .puter-theme-dark class');
// theme: 'light' → theme attribute set + .puter-theme-dark absent.
const light = spawn('light');
check(light?.getAttribute('theme') === 'light', 'light: theme attribute is not "light"');
check( ! light?.classList.contains('puter-theme-dark'), 'light: .puter-theme-dark class should be absent');
// No theme → no forced attribute; component follows system preference.
const auto = spawn(null);
check( ! auto?.hasAttribute('theme'), 'default: theme attribute should be absent');
// Leave the page clean.
document.querySelectorAll('puter-context-menu').forEach(el => el.remove());
if ( failures.length === 0 ) {
resultEl.textContent = '✓ theme test passed (dark / light / default)';
resultEl.style.color = 'green';
} else {
resultEl.textContent = '✗ theme test failed: ' + failures.join('; ');
resultEl.style.color = 'crimson';
}
}
function testContextMenuClick(e) {