import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
const source = readFileSync(new URL('./MusicApp.vue', import.meta.url), 'utf8')
const playlistPlaceholder = readFileSync(
new URL('../../assets/img/music/playlist-placeholder.jpg', import.meta.url),
)
const menuSource = source.match(//)?.[0] ?? ''
const menuItemsSource = source.slice(
source.indexOf('const menuItems = computed'),
source.indexOf('function eventValue'),
)
const menuDispatcherSource = source.slice(
source.indexOf('function selectMenuItem'),
source.indexOf('function shareTrack'),
)
describe('MusicApp action menu contract', () => {
it('uses the shared anchored Sky dropdown and its close paths', () => {
expect(source).toContain('SkyDropdown,')
expect(source).toContain('const menuTarget = ref(null)')
expect(source.split('menuTarget.value = event.currentTarget')).toHaveLength(
3,
)
expect(menuSource).toContain(':items="menuItems"')
expect(menuSource).toContain(':label="menuLabel"')
expect(menuSource).toContain(':opened="addMenuOpened || actionMenuOpened"')
expect(menuSource).toContain(':target="menuTarget"')
expect(menuSource).toContain('@backdropclick="dismissMenus"')
expect(menuSource).toContain('@escape="dismissMenus"')
expect(menuSource).toContain('@positionerror="dismissMenus"')
expect(menuSource).toContain('@select="selectMenuItem"')
})
it('exposes accessible expanded state on every plus and more trigger', () => {
expect(source.match(/aria-haspopup="menu"/g)).toHaveLength(5)
expect(source.split(':aria-expanded="addMenuOpened"')).toHaveLength(3)
expect(source.split('actionTrack?.id === track.id')).toHaveLength(4)
expect(source.split('actionTrack?.source === track.source')).toHaveLength(4)
})
it('keeps every add, playlist, and track action in the central dropdown', () => {
for (const id of [
'share-playlist',
'add-songs',
'rename-playlist',
'delete-playlist',
'add-youtube',
'new-playlist',
'share-track',
'add-to-playlist',
'remove-from-playlist',
'remove-from-library',
]) {
expect(menuItemsSource).toContain(`id: '${id}'`)
expect(menuDispatcherSource).toContain(`case '${id}'`)
}
for (const action of [
'shareActivePlaylist()',
'openActivePlaylistTrackPicker()',
"openSheet('rename')",
'requestDeletePlaylist()',
"openSheet('youtube')",
'openNewPlaylist()',
'shareTrack()',
'openPlaylistPicker(actionTrack.value)',
'void removeFromActivePlaylist()',
'requestRemoveTrack()',
]) {
expect(menuDispatcherSource).toContain(action)
}
expect(menuItemsSource.match(/destructive: true/g)).toHaveLength(3)
expect(menuItemsSource.match(/separatorBefore:/g)).toHaveLength(3)
})
it('uses the generated playlist artwork when no usable preview exists', () => {
expect(source).toContain(
"import playlistPlaceholder from '@/assets/img/music/playlist-placeholder.jpg'",
)
expect(source).toContain("phone.t('Apps.music.playlistActions')")
expect(source).not.toContain('playlist-placeholder.png')
expect([...playlistPlaceholder.subarray(0, 3)]).toEqual([0xff, 0xd8, 0xff])
expect(playlistPlaceholder.byteLength).toBeGreaterThan(80_000)
expect(playlistPlaceholder.byteLength).toBeLessThan(150_000)
expect(source).toContain('track.artwork.trim().length > 0')
expect(source).toContain('.filter(hasPlaylistArtwork)')
expect(source.match(/class="music-playlist-placeholder"/g)).toHaveLength(2)
expect(source.match(/@error="usePlaylistPlaceholder"/g)).toHaveLength(2)
expect(source).toContain('image.src = playlistPlaceholder')
expect(source).toContain(
'imageUrl: playlistArtwork(playlist)[0]?.artwork || playlistPlaceholder',
)
expect(source).toMatch(
/\.music-playlist-placeholder\s*\{[^}]*grid-column:\s*1 \/ -1;[^}]*grid-row:\s*1 \/ -1;/s,
)
})
it('does not retain the generic or manually positioned popover menu', () => {
expect(source).not.toContain('SkyPopover,')
expect(source).not.toContain('