scriptable-testlab
Version:
A lightweight, efficient tool designed to manage and update scripts for Scriptable.
154 lines (137 loc) • 4.22 kB
text/typescript
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();