import { readFileSync } from 'node:fs' import { describe, expect, it } from 'vitest' const source = readFileSync( new URL('./PhoneDynamicIsland.vue', import.meta.url), 'utf8', ).replace(/\r\n/g, '\n') const appSource = readFileSync( new URL('../App.vue', import.meta.url), 'utf8', ).replace(/\r\n/g, '\n') const mainCss = readFileSync( new URL('../assets/main.css', import.meta.url), 'utf8', ) const recorderSource = readFileSync( new URL('./PhoneMemoRecorder.vue', import.meta.url), 'utf8', ) const memosSource = readFileSync( new URL('../views/apps/MemosApp.vue', import.meta.url), 'utf8', ) const clockSource = readFileSync( new URL('../views/apps/ClockApp.vue', import.meta.url), 'utf8', ) const serverMemosSource = readFileSync( new URL('../../../sky_phone/source/server/memos.lua', import.meta.url), 'utf8', ) describe('Phone Dynamic Island contract', () => { it('renders once at phone shell level instead of forcing calls into Phone', () => { expect(appSource).toContain( "import PhoneDynamicIsland from '@/components/PhoneDynamicIsland.vue'", ) expect(appSource).toContain( "'phone-device--island-expanded': dynamicIslandExpanded", ) expect(appSource).toMatch( /class="phone-device__frame"[\s\S]*? void router.push('/apps/phone'), 0)", ) }) it('prioritizes calls and exposes answer, decline, and hangup controls', () => { expect(source.indexOf("return 'incoming-call'")).toBeLessThan( source.indexOf("return 'recording'"), ) expect(source).toContain('@click.stop="calls.answer()"') expect(source).toContain('@click.stop="endCall"') expect(source).toContain('void calls.decline()') expect(source).toContain('void calls.hangup()') expect(source).toContain('call.answeredAt ?? call.startedAt') }) it('hides an activity in its owning foreground app but restores it after closing', () => { expect(source).toContain( 'if (!currentActivity || !phone.isOpen) return currentActivity', ) expect(source).toContain("activeAppId.value === 'phone'") expect(source).toContain( "currentActivity === 'recording' && activeAppId.value === 'memos'", ) expect(source).toContain("activeAppId.value === 'clock'") expect(source).toContain( "currentActivity === 'music' && activeAppId.value === 'music'", ) }) it('shows only a compact frame and island for background live activities', () => { expect(source).toContain("'live-activity-change': [activity:") expect(source).toContain("emit('live-activity-change', nextActivity)") expect(appSource).toContain( "'phone-stage--live-activity':\n !phone.isOpen && Boolean(dynamicIslandActivity || calls.activeCall)", ) expect(mainCss).toMatch( /\.phone-stage--live-activity[\s\S]*?pointer-events:\s*none;[\s\S]*?--phone-live-activity-peek-height/, ) expect(mainCss).toMatch( /\.phone-stage--live-activity[\s\S]*?> :not\(\.phone-screen\):not\(\.phone-device__frame\):not\(\.phone-dynamic-island\)[\s\S]*?\.phone-screen > \*[\s\S]*?visibility:\s*hidden;/, ) expect(appSource).toContain("? '190px'") expect(appSource).toContain("? '132px'") expect(appSource).toContain(": '112px'") expect(source).toContain( "!phone.isOpen ||\n activity.value === 'incoming-call' ||", ) }) it('connects music, recorder, timer, and stopwatch controls to their stores', () => { expect(source).toContain( "if (music.isPlaying && music.currentTrack) return 'music'", ) expect(source).toContain('@click.stop="music.previous()"') expect(source).toContain('@click.stop="music.toggle()"') expect(source).toContain('@click.stop="music.next()"') expect(source).toContain("postRecorderCommand('memo:recordStop')") expect(source).toContain('clock.pauseTimer(Date.now())') expect(source).toContain('clock.pauseStopwatch(Date.now())') expect(source).toContain('clock.addLap(Date.now())') expect(source).not.toContain('phone-dynamic-island__lap') expect(source).toContain('phone-dynamic-island__stopwatch-meta') expect(source).toContain('{{ stopwatchLapLabel }}') expect(source).toContain('{{ stopwatchLapValue }}') expect(source).toContain('{{ stopwatchTotalDisplay }}') }) it('matches the reference music player and timer control layouts', () => { expect(source).toContain('phone-dynamic-island__music-equalizer') expect(source).toContain('phone-dynamic-island__progress-track') expect(source).toContain('{{ musicElapsedLabel }}') expect(source).toContain('{{ musicRemainingLabel }}') expect(source).not.toContain('Airplay') expect(source).toContain('