UNPKG

scriptable-testlab

Version:

A lightweight, efficient tool designed to manage and update scripts for Scriptable.

154 lines (137 loc) 4.22 kB
import {Preferences} from './preferences'; import {GlobalMockManager} from './registry'; import {SystemState} from './system'; import type {RuntimeConfig} from './types'; import {RuntimePresets} from './types/runtime'; /** * Main entry point for Scriptable testing * Provides: * 1. Global mock management * 2. Preferences management * 3. Test utilities * 4. Assertions */ export class ScriptableRuntime { private static instance: ScriptableRuntime; private static isInitialized = false; private static isTestRunning = false; readonly preferences: Preferences; private constructor() { if (ScriptableRuntime.instance) { throw new Error('ScriptableRuntime is a singleton. Use ScriptableRuntime.getInstance()'); } this.preferences = Preferences.getInstance(); } /** * Get the singleton instance of ScriptableRuntime */ static getInstance(): ScriptableRuntime { if (!ScriptableRuntime.instance) { ScriptableRuntime.instance = new ScriptableRuntime(); } return ScriptableRuntime.instance; } /** * Configure runtime with preset or custom configuration * @param config Runtime configuration or preset name */ configure(config: RuntimeConfig | keyof typeof RuntimePresets): void { if (typeof config === 'string') { const preset = RuntimePresets[config]; if (!preset) { throw new Error(`Preset "${config}" not found`); } this.applyConfig(preset); } else { this.applyConfig(config); } } /** * Apply configuration to runtime * @param config Runtime configuration */ private applyConfig(config: RuntimeConfig): void { if (config.device) { this.preferences.updateMock('device', config.device); } if (config.widget) { this.preferences.updateMock('config', config.widget); } if (config.script) { this.preferences.updateMock('script', config.script); } if (config.location) { this.preferences.updateMock('location', { currentLocation: config.location.currentLocation ?? { latitude: 0, longitude: 0, altitude: 0, horizontalAccuracy: 5, verticalAccuracy: 5, }, accuracy: config.location.accuracy ?? 'best', simulatedDelay: config.location.simulatedDelay ?? 100, }); } } /** * Initialize the runtime and setup mocks * @throws Error if called while a test is running */ setupMocks(): void { this.assertTestNotRunning(); if (ScriptableRuntime.isInitialized) { throw new Error('ScriptableRuntime is already initialized. Call clearMocks() first.'); } GlobalMockManager.initialize(); this.preferences.reset(); ScriptableRuntime.isInitialized = true; ScriptableRuntime.isTestRunning = true; } /** * Clear all mocks and reset the runtime state * @throws Error if called while no test is running */ clearMocks(): void { SystemState.reset(); this.preferences.reset(); GlobalMockManager.clear(); ScriptableRuntime.isInitialized = false; ScriptableRuntime.isTestRunning = false; } private assertTestNotRunning(): void { if (ScriptableRuntime.isTestRunning) { throw new Error('A test is currently running. Call clearMocks() before starting a new test.'); } } // #region UI State Shortcuts get isDarkMode(): boolean { return this.preferences.get().device.appearance?.isUsingDarkAppearance ?? false; } set isDarkMode(value: boolean) { this.preferences.updateMock('device', { appearance: { isUsingDarkAppearance: value, }, }); } get widgetFamily(): string { return this.preferences.get().config.widgetFamily ?? 'small'; } set widgetFamily(value: string) { this.preferences.updateMock('config', { widgetFamily: value as any, }); } get alertResponse(): number | undefined { return this.preferences.get().device.alertResponse; } set alertResponse(value: number | undefined) { this.preferences.updateMock('device', { alertResponse: value, }); } // #endregion } // Export the singleton instance export const runtime = ScriptableRuntime.getInstance();