diff --git a/src/puter-js/test/web-components-test.html b/src/puter-js/test/web-components-test.html
index 47ceb0179..a8e2337d6 100644
--- a/src/puter-js/test/web-components-test.html
+++ b/src/puter-js/test/web-components-test.html
@@ -79,6 +79,8 @@
+
+
@@ -195,53 +197,87 @@
paste: svg(''),
};
+ // 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 . 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 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) {