scriptable-testlab
Version:
A lightweight, efficient tool designed to manage and update scripts for Scriptable.
180 lines (150 loc) • 5.46 kB
text/typescript
import {AbsAlert} from 'scriptable-abstract';
import {SystemState} from '../../system';
import {MockTextField} from './text-field';
interface AlertMockState {
title: string;
message: string;
actions: string[];
textFields: MockTextField[];
destructiveActionIndex: number;
cancelActionIndex: number;
presentedResult: number;
}
const DEFAULT_STATE: AlertMockState = {
title: '',
message: '',
actions: [],
textFields: [],
destructiveActionIndex: -1,
cancelActionIndex: -1,
presentedResult: 0,
};
export class MockAlert extends AbsAlert<AlertMockState> {
constructor() {
super(DEFAULT_STATE);
}
get title(): string {
return this.state.title;
}
set title(value: string) {
if (value === null) throw new Error('Title cannot be null');
this.setState({title: value});
}
get message(): string {
return this.state.message;
}
set message(value: string) {
if (value === null) throw new Error('Message cannot be null');
this.setState({message: value});
}
get actions(): readonly string[] {
return [...this.state.actions];
}
get textFields(): readonly MockTextField[] {
return new Proxy([...this.state.textFields], {
get(target, prop) {
if (typeof prop === 'string' && !isNaN(Number(prop))) {
const index = Number(prop);
if (index < 0 || index >= target.length) {
throw new Error('Invalid text field index');
}
}
return Reflect.get(target, prop);
},
});
}
get destructiveAction(): string | undefined {
return this.state.destructiveActionIndex >= 0 ? this.state.actions[this.state.destructiveActionIndex] : undefined;
}
get cancelAction(): string | undefined {
return this.state.cancelActionIndex >= 0 ? this.state.actions[this.state.cancelActionIndex] : undefined;
}
addAction(title: string): void {
if (!title) throw new Error('Action text cannot be empty');
const actions = [...this.state.actions];
const insertIndex = Math.min(
this.state.cancelActionIndex >= 0 ? this.state.cancelActionIndex : actions.length,
this.state.destructiveActionIndex >= 0 ? this.state.destructiveActionIndex : actions.length,
);
actions.splice(insertIndex, 0, title);
const newState: Partial<AlertMockState> = {actions};
if (this.state.cancelActionIndex >= insertIndex) {
newState.cancelActionIndex = this.state.cancelActionIndex + 1;
}
if (this.state.destructiveActionIndex >= insertIndex) {
newState.destructiveActionIndex = this.state.destructiveActionIndex + 1;
}
this.setState(newState);
}
addDestructiveAction(title: string): void {
if (!title) throw new Error('Action text cannot be empty');
const actions = [...this.state.actions];
const insertIndex = this.state.cancelActionIndex >= 0 ? this.state.cancelActionIndex : actions.length;
actions.splice(insertIndex, 0, title);
const newState: Partial<AlertMockState> = {
actions,
destructiveActionIndex: insertIndex,
};
if (this.state.cancelActionIndex >= insertIndex) {
newState.cancelActionIndex = this.state.cancelActionIndex + 1;
}
this.setState(newState);
}
addCancelAction(title: string): void {
if (!title) throw new Error('Action text cannot be empty');
const actions = [...this.state.actions, title];
this.setState({
actions,
cancelActionIndex: actions.length - 1,
});
}
addTextField(placeholder?: string, text?: string): TextField {
const textField = new MockTextField();
if (placeholder) textField.placeholder = placeholder;
if (text) textField.text = text;
const textFields = [...this.state.textFields, textField];
this.setState({textFields});
return textField;
}
addSecureTextField(placeholder?: string, text?: string): TextField {
const textField = new MockTextField();
textField.isSecure = true;
if (placeholder) textField.placeholder = placeholder;
if (text) textField.text = text;
const textFields = [...this.state.textFields, textField];
this.setState({textFields});
return textField;
}
textFieldValue(index: number): string {
if (index < 0 || index >= this.state.textFields.length) return '';
return this.state.textFields[index]?.text ?? '';
}
setPresentedResult(result: number): void {
this.setState({presentedResult: result});
}
async presentAlert(): Promise<number> {
const systemResponse = SystemState.getAlertResponse();
if (systemResponse !== undefined) return systemResponse;
if (this.state.presentedResult !== 0) return this.state.presentedResult;
if (this.state.actions.length === 0) return -1;
return this.state.presentedResult;
}
async presentSheet(): Promise<number> {
return this.presentAlert();
}
async present(): Promise<number> {
return this.presentAlert();
}
setState(newState: Partial<AlertMockState>): this {
if (newState === null || newState === undefined) {
throw new Error('State cannot be null or undefined');
}
if ('title' in newState && (newState.title === null || newState.title === undefined)) {
throw new Error('Title cannot be null or undefined');
}
if ('message' in newState && (newState.message === null || newState.message === undefined)) {
throw new Error('Message cannot be null or undefined');
}
return super.setState(newState);
}
}