Files
sky_phone-sky-systems/frontend/src/AppDevelopmentPreview.contract.test.ts
T
Leon.Schmidt c766cb5044 ENH - Browser preview + convert app SVGs to WebP
Introduce a browserPreview mode (isBrowserPreview) with adjusted viewport scaling and CSS (.phone-stage--browser-preview) and update related contract tests. Allow apiBase via URL params in nui utils so the dev build can call a local browser API base. Replace many SVG app icons with WebP (and update imports), swap crypto header PNG to WebP, and add a demo wallpaper image. Testserver now installs demo apps and configures a demo wallpaper; smoke tests updated to assert these browser-demo conditions.
2026-08-19 15:17:26 +02:00

100 lines
4.2 KiB
TypeScript

import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
const source = readFileSync(new URL('./App.vue', import.meta.url), 'utf8')
const mainCss = readFileSync(
new URL('./assets/main.css', import.meta.url),
'utf8',
)
describe('browser development preview contract', () => {
it('starts unlocked while preserving an explicit lock screen preview', () => {
expect(source).toContain("developmentParameters.has('lockScreenPreview')")
expect(source).toContain(': !isDevelopment || developmentLockScreenPreview')
expect(source).toContain("developmentParameters.has('setupPreview')")
})
it('loads authenticated app data without replacing direct app routes', () => {
expect(source).toContain(
"if (isLocked.value || setupRequired.value) void router.replace('/')",
)
expect(source).toContain('else loadUnlockedPhoneData()')
})
it('requires the passcode again after a full device lock', () => {
expect(source).toContain('const passcodeRequired = ref(false)')
expect(source).toContain(
'if (phone.security.enabled && passcodeRequired.value)',
)
expect(source).toContain(
'passcodeRequired.value = isLocked.value && phone.security.enabled',
)
expect(source).toMatch(
/function lockPhone\(\): void \{[\s\S]*?passcodeRequired\.value = phone\.security\.enabled[\s\S]*?isLocked\.value = true/,
)
})
it('keeps hairlines at one rendered device pixel through phone zoom', () => {
expect(source).toContain(
'...getHairlinePixelStyle(phoneZoom.value, browserDevicePixelRatio.value)',
)
expect(source).toContain(':device-pixel-ratio="browserDevicePixelRatio"')
})
it('maps the visible device side controls to phone actions', () => {
expect(source).toContain('@click="toggleHardwareAlertMute"')
expect(source).toContain('@click="changeHardwareAlertVolume(10)"')
expect(source).toContain('@click="changeHardwareAlertVolume(-10)"')
expect(source).toContain('@click="toggleHardwareLock"')
expect(source).toMatch(
/function toggleHardwareLock\(\): void \{[\s\S]*?unlockPhone\(\)[\s\S]*?lockPhone\(\)/,
)
expect(source).toMatch(
/function changeHardwareAlertVolume\(delta: number\): void \{[\s\S]*?phone\.setAlertVolumes\(volume\)/,
)
expect(mainCss).toMatch(
/\.phone-hardware-button\s*\{[\s\S]*?position:\s*absolute;[\s\S]*?z-index:\s*101;/,
)
expect(mainCss).toContain('.phone-hardware-button--power')
expect(source).toContain('class="phone-volume-hud"')
expect(source).toContain('showHardwareVolumeHud()')
expect(mainCss).toContain('.phone-volume-hud__level')
})
it('uses layout zoom so the fixed-resolution phone stays sharply rasterized', () => {
expect(source).toContain('phone-resolution-canvas--primary')
expect(source).toContain("'--phone-rendered-height'")
expect(source).toContain("'--phone-rendered-width'")
expect(mainCss).toMatch(
/\.phone-resolution-canvas\s*\{[^}]*zoom:\s*var\(--phone-zoom, 1\);/s,
)
expect(mainCss).not.toMatch(
/\.phone-resolution-canvas\s*\{[^}]*transform:\s*scale\(/s,
)
})
it('matches the production phone size and bottom-right stage in development', () => {
expect(source).toContain('const PHONE_BASE_SCALE = 0.69 * 1.2')
expect(source).toContain('() => viewportScale.value * PHONE_BASE_SCALE')
expect(source).not.toContain('DEVELOPMENT_PHONE_SCALE')
expect(source).not.toContain("'phone-stage--dev': isDevelopment")
expect(mainCss).toMatch(
/\.phone-stage\s*\{[^}]*place-items:\s*end;[^}]*padding:\s*0 var\(--phone-edge-gap, 24px\) var\(--phone-edge-gap, 24px\) 0;/s,
)
expect(mainCss).not.toContain('.phone-stage--dev')
})
it('fills the dedicated browser embed without clipping the hardware controls', () => {
expect(source).toContain("developmentParameters.has('browserPreview')")
expect(source).toContain("'phone-stage--browser-preview': isBrowserPreview")
expect(source).toContain('return (availableScale * 0.94) / PHONE_BASE_SCALE')
expect(mainCss).toMatch(
/\.phone-stage--browser-preview\s*\{[^}]*place-items:\s*center;[^}]*padding:\s*0;/s,
)
expect(mainCss).toMatch(
/\.phone-stage--browser-preview \.phone-device\s*\{[^}]*filter:\s*none;/s,
)
})
})