feat: add desktop repo (#1071)
This commit is contained in:
203
packages/desktop/test/driver.ts
Normal file
203
packages/desktop/test/driver.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ChildProcess, spawn } from 'child_process'
|
||||
import electronPath, { MenuItem } from 'electron'
|
||||
import path from 'path'
|
||||
import { deleteDir, ensureDirectoryExists, readJSONFile } from '../app/javascripts/Main/Utils/fileUtils'
|
||||
import { Language } from '../app/javascripts/Main/spellcheckerManager'
|
||||
import { StoreKeys } from '../app/javascripts/Main/store'
|
||||
import { UpdateState } from '../app/javascripts/Main/updateManager'
|
||||
import { CommandLineArgs } from '../app/javascripts/Shared/CommandLineArgs'
|
||||
import { AppMessageType, AppTestMessage, MessageType, TestIPCMessage, TestIPCMessageResult } from './TestIpcMessage'
|
||||
|
||||
function spawnAppprocess(userDataPath: string) {
|
||||
const p = spawn(
|
||||
electronPath as any,
|
||||
[path.join(__dirname, '..'), CommandLineArgs.Testing, CommandLineArgs.UserDataPath, userDataPath],
|
||||
{
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
||||
},
|
||||
)
|
||||
return p
|
||||
}
|
||||
|
||||
class Driver {
|
||||
private appProcess: ChildProcess
|
||||
|
||||
private calls: Array<{
|
||||
resolve: (...args: any) => void
|
||||
reject: (...args: any) => void
|
||||
} | null> = []
|
||||
|
||||
private awaitedOnMessages: Array<{
|
||||
type: AppMessageType
|
||||
resolve: (...args: any) => void
|
||||
}> = []
|
||||
|
||||
appReady: Promise<unknown>
|
||||
windowLoaded: Promise<unknown>
|
||||
|
||||
constructor(readonly userDataPath: string) {
|
||||
this.appProcess = spawnAppprocess(userDataPath)
|
||||
this.appProcess.on('message', this.receive)
|
||||
this.appReady = this.waitOn(AppMessageType.Ready)
|
||||
this.windowLoaded = this.waitOn(AppMessageType.WindowLoaded)
|
||||
}
|
||||
|
||||
private receive = (message: TestIPCMessageResult | AppTestMessage) => {
|
||||
if ('type' in message) {
|
||||
if (message.type === AppMessageType.Log) {
|
||||
console.log(message)
|
||||
}
|
||||
|
||||
this.awaitedOnMessages = this.awaitedOnMessages.filter(({ type, resolve }) => {
|
||||
if (type === message.type) {
|
||||
resolve()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
if ('id' in message) {
|
||||
const call = this.calls[message.id]!
|
||||
this.calls[message.id] = null
|
||||
if (message.reject) {
|
||||
call.reject(message.reject)
|
||||
} else {
|
||||
call.resolve(message.resolve)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private waitOn = (messageType: AppMessageType) => {
|
||||
return new Promise((resolve) => {
|
||||
this.awaitedOnMessages.push({
|
||||
type: messageType,
|
||||
resolve,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private send = (type: MessageType, ...args: any): Promise<any> => {
|
||||
const id = this.calls.length
|
||||
const message: TestIPCMessage = {
|
||||
id,
|
||||
type,
|
||||
args,
|
||||
}
|
||||
|
||||
this.appProcess.send(message)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.calls.push({ resolve, reject })
|
||||
})
|
||||
}
|
||||
|
||||
windowCount = (): Promise<number> => this.send(MessageType.WindowCount)
|
||||
|
||||
appStateCall = (methodName: string, ...args: any): Promise<any> =>
|
||||
this.send(MessageType.AppStateCall, methodName, ...args)
|
||||
|
||||
readonly window = {
|
||||
signOut: (): Promise<void> => this.send(MessageType.SignOut),
|
||||
}
|
||||
|
||||
readonly storage = {
|
||||
dataOnDisk: async (): Promise<{ [key in StoreKeys]: any }> => {
|
||||
const location = await this.send(MessageType.StoreSettingsLocation)
|
||||
return readJSONFile(location)
|
||||
},
|
||||
dataLocation: (): Promise<string> => this.send(MessageType.StoreSettingsLocation),
|
||||
setZoomFactor: (factor: number) => this.send(MessageType.StoreSet, 'zoomFactor', factor),
|
||||
setLocalStorageValue: (key: string, value: string): Promise<void> =>
|
||||
this.send(MessageType.SetLocalStorageValue, key, value),
|
||||
}
|
||||
|
||||
readonly appMenu = {
|
||||
items: (): Promise<MenuItem[]> => this.send(MessageType.AppMenuItems),
|
||||
clickLanguage: (language: Language) => this.send(MessageType.ClickLanguage, language),
|
||||
hasReloaded: () => this.send(MessageType.HasReloadedMenu),
|
||||
}
|
||||
|
||||
readonly spellchecker = {
|
||||
manager: () => this.send(MessageType.SpellCheckerManager),
|
||||
languages: () => this.send(MessageType.SpellCheckerLanguages),
|
||||
}
|
||||
|
||||
readonly backups = {
|
||||
enabled: (): Promise<boolean> => this.send(MessageType.BackupsAreEnabled),
|
||||
toggleEnabled: (): Promise<boolean> => this.send(MessageType.ToggleBackupsEnabled),
|
||||
location: (): Promise<string> => this.send(MessageType.BackupsLocation),
|
||||
copyDecryptScript: async (location: string) => {
|
||||
await this.send(MessageType.CopyDecryptScript, location)
|
||||
},
|
||||
changeLocation: (location: string) => this.send(MessageType.ChangeBackupsLocation, location),
|
||||
save: (data: any) => this.send(MessageType.DataArchive, data),
|
||||
perform: async () => {
|
||||
await this.windowLoaded
|
||||
await this.send(MessageType.PerformBackup)
|
||||
await this.waitOn(AppMessageType.SavedBackup)
|
||||
},
|
||||
}
|
||||
|
||||
readonly updates = {
|
||||
state: (): Promise<UpdateState> => this.send(MessageType.UpdateState),
|
||||
autoUpdateEnabled: (): Promise<boolean> => this.send(MessageType.AutoUpdateEnabled),
|
||||
check: () => this.send(MessageType.CheckForUpdate),
|
||||
}
|
||||
|
||||
readonly net = {
|
||||
getJSON: (url: string) => this.send(MessageType.GetJSON, url),
|
||||
downloadFile: (url: string, filePath: string) => this.send(MessageType.DownloadFile, url, filePath),
|
||||
}
|
||||
|
||||
stop = async () => {
|
||||
this.appProcess.kill()
|
||||
|
||||
/** Give the process a little time before cleaning up */
|
||||
await new Promise((resolve) => setTimeout(resolve, 150))
|
||||
|
||||
/**
|
||||
* Windows can throw EPERM or EBUSY errors when we try to delete the
|
||||
* user data directory too quickly.
|
||||
*/
|
||||
const maxTries = 5
|
||||
for (let i = 0; i < maxTries; i++) {
|
||||
try {
|
||||
await deleteDir(this.userDataPath)
|
||||
return
|
||||
} catch (error: any) {
|
||||
if (error.code === 'EPERM' || error.code === 'EBUSY') {
|
||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error(`Couldn't delete user data directory after ${maxTries} tries`)
|
||||
}
|
||||
|
||||
restart = async () => {
|
||||
this.appProcess.kill()
|
||||
this.appProcess = spawnAppprocess(this.userDataPath)
|
||||
this.appProcess.on('message', this.receive)
|
||||
this.appReady = this.waitOn(AppMessageType.Ready)
|
||||
this.windowLoaded = this.waitOn(AppMessageType.WindowLoaded)
|
||||
await this.appReady
|
||||
}
|
||||
}
|
||||
|
||||
export type { Driver }
|
||||
|
||||
export async function createDriver() {
|
||||
const userDataPath = path.join(
|
||||
__dirname,
|
||||
'data',
|
||||
'tmp',
|
||||
`userData-${Date.now()}-${Math.round(Math.random() * 10000)}`,
|
||||
)
|
||||
await ensureDirectoryExists(userDataPath)
|
||||
const driver = new Driver(userDataPath)
|
||||
await driver.appReady
|
||||
return driver
|
||||
}
|
||||
Reference in New Issue
Block a user