scriptable-testlab
Version:
A lightweight, efficient tool designed to manage and update scripts for Scriptable.
225 lines (205 loc) • 5.26 kB
text/typescript
import {MockArgs} from './mocks/system/args';
import {MockConfig} from './mocks/system/config';
import {MockDevice} from './mocks/system/device';
import {MockLocation} from './mocks/system/location';
import {MockScript} from './mocks/system/script';
import {GlobalRegistry} from './registry';
import type {GlobalPreferences} from './types';
/**
* Default preferences for all mocks
*/
export const DEFAULT_PREFERENCES: GlobalPreferences = {
device: {
name: 'iPhone Mock',
systemName: 'iOS',
systemVersion: '16.0',
model: 'iPhone',
screenSize: {
width: 390,
height: 844,
},
screenResolution: {
width: 1170,
height: 2532,
},
screenScale: 3,
screenBrightness: 1,
orientation: {
isInPortrait: true,
isInPortraitUpsideDown: false,
isInLandscapeLeft: false,
isInLandscapeRight: false,
isFaceUp: false,
isFaceDown: false,
},
battery: {
level: 1,
isDischarging: false,
isCharging: false,
isFullyCharged: true,
},
locale: {
preferredLanguages: ['en'],
locale: 'en_US',
language: 'en',
},
appearance: {
isUsingDarkAppearance: false,
},
volume: 0.5,
},
config: {
widgetFamily: 'small',
runsInWidget: false,
runsInApp: true,
runsWithSiri: false,
runsInActionExtension: false,
runsInNotification: false,
runsFromHomeScreen: false,
runsInAccessoryWidget: false,
},
script: {
name: 'Test Script',
},
args: {
plainTexts: [],
fileURLs: [],
images: [],
queryParameters: {},
},
location: {
currentLocation: {
latitude: 0,
longitude: 0,
altitude: 0,
horizontalAccuracy: 5,
verticalAccuracy: 5,
},
accuracy: 'best',
simulatedDelay: 100,
},
};
/**
* Preferences manager for Scriptable testing
* Handles:
* 1. Loading/saving preferences
* 2. Merging updates
* 3. Resetting to defaults
* 4. Synchronizing with mocks
*/
export class Preferences {
private static instance: Preferences;
private current: GlobalPreferences;
private constructor() {
this.current = {...DEFAULT_PREFERENCES};
}
static getInstance(): Preferences {
if (!Preferences.instance) {
Preferences.instance = new Preferences();
}
return Preferences.instance;
}
/**
* Get current preferences
*/
get(): GlobalPreferences {
return this.current;
}
/**
* Update preferences
* @param preferences Partial preferences to merge
*/
update(preferences: Partial<GlobalPreferences>): void {
this.current = this.merge(this.current, preferences);
this.syncMocks();
}
/**
* Reset preferences to defaults
*/
reset(): void {
this.current = {...DEFAULT_PREFERENCES};
this.syncMocks();
}
/**
* Load preferences from JSON string
*/
loadFromJSON(json: string): void {
try {
const preferences = JSON.parse(json);
this.current = this.merge(DEFAULT_PREFERENCES, preferences);
this.syncMocks();
} catch (error) {
throw new Error(`Failed to parse preferences: ${error}`);
}
}
/**
* Save preferences to JSON string
*/
saveToJSON(): string {
return JSON.stringify(this.current, null, 2);
}
/**
* Get preferences for specific mock
*/
getMock<K extends keyof GlobalPreferences>(mockName: K): GlobalPreferences[K] {
return this.current[mockName];
}
/**
* Update preferences for specific mock
*/
updateMock<K extends keyof GlobalPreferences>(mockName: K, preferences: Partial<GlobalPreferences[K]>): void {
this.current = {
...this.current,
[mockName]: {
...this.current[mockName],
...preferences,
},
};
this.syncMocks();
}
/**
* Synchronize current preferences with all mocks
*/
private syncMocks(): void {
// Sync device
if (GlobalRegistry.has('Device')) {
GlobalRegistry.get<MockDevice>('Device').setState(this.current.device);
}
// Sync config
if (GlobalRegistry.has('config')) {
GlobalRegistry.get<MockConfig>('config').setState(this.current.config);
}
// Sync script
if (GlobalRegistry.has('Script')) {
GlobalRegistry.get<MockScript>('Script').setState(this.current.script);
}
// Sync args
if (GlobalRegistry.has('args')) {
GlobalRegistry.get<MockArgs>('args').setState(this.current.args);
}
// Sync location
if (GlobalRegistry.has('Location')) {
GlobalRegistry.get<MockLocation>('Location').setState(this.current.location);
}
}
/**
* Deep merge preferences
*/
private merge(target: GlobalPreferences, source: Partial<GlobalPreferences>): GlobalPreferences {
const result = {...target};
for (const key in source) {
if (source.hasOwnProperty(key)) {
const value = source[key as keyof GlobalPreferences];
if (value && typeof value === 'object' && !Array.isArray(value)) {
result[key as keyof GlobalPreferences] = {
...result[key as keyof GlobalPreferences],
...value,
};
} else {
result[key as keyof GlobalPreferences] = value as any;
}
}
}
return result;
}
}