diff --git a/.eslintignore b/.eslintignore index 570732e4d..75d7f18ad 100644 --- a/.eslintignore +++ b/.eslintignore @@ -268,6 +268,9 @@ packages/app-cli/tests/synchronizer_MigrationHandler.js.map packages/app-cli/tests/test-utils-synchronizer.d.ts packages/app-cli/tests/test-utils-synchronizer.js packages/app-cli/tests/test-utils-synchronizer.js.map +packages/app-cli/tests/test-utils.d.ts +packages/app-cli/tests/test-utils.js +packages/app-cli/tests/test-utils.js.map packages/app-desktop/ElectronAppWrapper.d.ts packages/app-desktop/ElectronAppWrapper.js packages/app-desktop/ElectronAppWrapper.js.map diff --git a/.gitignore b/.gitignore index 7d96d9d6a..2b4288080 100644 --- a/.gitignore +++ b/.gitignore @@ -257,6 +257,9 @@ packages/app-cli/tests/synchronizer_MigrationHandler.js.map packages/app-cli/tests/test-utils-synchronizer.d.ts packages/app-cli/tests/test-utils-synchronizer.js packages/app-cli/tests/test-utils-synchronizer.js.map +packages/app-cli/tests/test-utils.d.ts +packages/app-cli/tests/test-utils.js +packages/app-cli/tests/test-utils.js.map packages/app-desktop/ElectronAppWrapper.d.ts packages/app-desktop/ElectronAppWrapper.js packages/app-desktop/ElectronAppWrapper.js.map diff --git a/packages/app-cli/tests/test-utils.js b/packages/app-cli/tests/test-utils.ts similarity index 84% rename from packages/app-cli/tests/test-utils.js rename to packages/app-cli/tests/test-utils.ts index cf8878a14..7d977fdf9 100644 --- a/packages/app-cli/tests/test-utils.js +++ b/packages/app-cli/tests/test-utils.ts @@ -1,10 +1,24 @@ /* eslint-disable require-atomic-updates */ +import BaseApplication from '@joplin/lib/BaseApplication'; +import BaseModel from '@joplin/lib/BaseModel'; +import Logger, { TargetType, LoggerWrapper } from '@joplin/lib/Logger'; +import Setting from '@joplin/lib/models/Setting'; +import BaseService from '@joplin/lib/services/BaseService'; +import FsDriverNode from '@joplin/lib/fs-driver-node'; +import time from '@joplin/lib/time'; +import shim from '@joplin/lib/shim'; +import uuid from '@joplin/lib/uuid'; +import ResourceService from '@joplin/lib/services/ResourceService'; +import KeymapService from '@joplin/lib/services/KeymapService'; +import KvStore from '@joplin/lib/services/KvStore'; +import KeychainServiceDriver from '@joplin/lib/services/keychain/KeychainServiceDriver.node'; +import KeychainServiceDriverDummy from '@joplin/lib/services/keychain/KeychainServiceDriver.dummy'; +import PluginRunner from '../app/services/plugins/PluginRunner'; +import PluginService from '@joplin/lib/services/plugins/PluginService'; const fs = require('fs-extra'); const { JoplinDatabase } = require('@joplin/lib/joplin-database.js'); const { DatabaseDriverNode } = require('@joplin/lib/database-driver-node.js'); -const BaseApplication = require('@joplin/lib/BaseApplication').default; -const BaseModel = require('@joplin/lib/BaseModel').default; const Folder = require('@joplin/lib/models/Folder.js'); const Note = require('@joplin/lib/models/Note.js'); const ItemChange = require('@joplin/lib/models/ItemChange.js'); @@ -12,8 +26,6 @@ const Resource = require('@joplin/lib/models/Resource.js'); const Tag = require('@joplin/lib/models/Tag.js'); const NoteTag = require('@joplin/lib/models/NoteTag.js'); const Revision = require('@joplin/lib/models/Revision.js'); -const Logger = require('@joplin/lib/Logger').default; -const Setting = require('@joplin/lib/models/Setting').default; const MasterKey = require('@joplin/lib/models/MasterKey'); const BaseItem = require('@joplin/lib/models/BaseItem.js'); const { FileApi } = require('@joplin/lib/file-api.js'); @@ -23,12 +35,7 @@ const { FileApiDriverWebDav } = require('@joplin/lib/file-api-driver-webdav.js') const { FileApiDriverDropbox } = require('@joplin/lib/file-api-driver-dropbox.js'); const { FileApiDriverOneDrive } = require('@joplin/lib/file-api-driver-onedrive.js'); const { FileApiDriverAmazonS3 } = require('@joplin/lib/file-api-driver-amazon-s3.js'); -const BaseService = require('@joplin/lib/services/BaseService').default; -const FsDriverNode = require('@joplin/lib/fs-driver-node').default; -const time = require('@joplin/lib/time').default; const { shimInit } = require('@joplin/lib/shim-init-node.js'); -const shim = require('@joplin/lib/shim').default; -const uuid = require('@joplin/lib/uuid').default; const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry.js'); const SyncTargetMemory = require('@joplin/lib/SyncTargetMemory.js'); const SyncTargetFilesystem = require('@joplin/lib/SyncTargetFilesystem.js'); @@ -38,21 +45,14 @@ const SyncTargetDropbox = require('@joplin/lib/SyncTargetDropbox.js'); const SyncTargetAmazonS3 = require('@joplin/lib/SyncTargetAmazonS3.js'); const EncryptionService = require('@joplin/lib/services/EncryptionService.js'); const DecryptionWorker = require('@joplin/lib/services/DecryptionWorker.js'); -const ResourceService = require('@joplin/lib/services/ResourceService').default; const RevisionService = require('@joplin/lib/services/RevisionService.js'); const ResourceFetcher = require('@joplin/lib/services/ResourceFetcher.js'); -const KeymapService = require('@joplin/lib/services/KeymapService').default; -const KvStore = require('@joplin/lib/services/KvStore').default; const WebDavApi = require('@joplin/lib/WebDavApi'); const DropboxApi = require('@joplin/lib/DropboxApi'); const { OneDriveApi } = require('@joplin/lib/onedrive-api'); const { loadKeychainServiceAndSettings } = require('@joplin/lib/services/SettingUtils'); -const KeychainServiceDriver = require('@joplin/lib/services/keychain/KeychainServiceDriver.node').default; -const KeychainServiceDriverDummy = require('@joplin/lib/services/keychain/KeychainServiceDriver.dummy').default; const md5 = require('md5'); const S3 = require('aws-sdk/clients/s3'); -const PluginRunner = require('../app/services/plugins/PluginRunner').default; -const PluginService = require('@joplin/lib/services/plugins/PluginService').default; const { Dirnames } = require('@joplin/lib/services/synchronizer/utils/types'); const sharp = require('sharp'); @@ -63,16 +63,16 @@ const sharp = require('sharp'); // Jest, to make debugging easier, but it's not clear how to get this info). const suiteName_ = uuid.createNano(); -const databases_ = []; -let synchronizers_ = []; -const synchronizerContexts_ = {}; -const fileApis_ = {}; -const encryptionServices_ = []; -const revisionServices_ = []; -const decryptionWorkers_ = []; -const resourceServices_ = []; -const resourceFetchers_ = []; -const kvStores_ = []; +const databases_: any[] = []; +let synchronizers_: any[] = []; +const synchronizerContexts_: any = {}; +const fileApis_: any = {}; +const encryptionServices_: any[] = []; +const revisionServices_: any[] = []; +const decryptionWorkers_: any[] = []; +const resourceServices_: any[] = []; +const resourceFetchers_: any[] = []; +const kvStores_: KvStore[] = []; let currentClient_ = 1; // The line `process.on('unhandledRejection'...` in all the test files is going to @@ -113,7 +113,7 @@ SyncTargetRegistry.addClass(SyncTargetDropbox); SyncTargetRegistry.addClass(SyncTargetAmazonS3); let syncTargetName_ = ''; -let syncTargetId_ = null; +let syncTargetId_: number = null; let sleepTime = 0; let isNetworkSyncTarget_ = false; @@ -121,7 +121,7 @@ function syncTargetName() { return syncTargetName_; } -function setSyncTargetName(name) { +function setSyncTargetName(name: string) { if (name === syncTargetName_) return syncTargetName_; const previousName = syncTargetName_; syncTargetName_ = name; @@ -150,13 +150,11 @@ if (isNetworkSyncTarget_) defaultJestTimeout = 60 * 1000 * 10; jest.setTimeout(defaultJestTimeout); const dbLogger = new Logger(); -dbLogger.addTarget('console'); -// dbLogger.addTarget('file', { path: `${logDir}/log.txt` }); +dbLogger.addTarget(TargetType.Console); dbLogger.setLevel(Logger.LEVEL_WARN); const logger = new Logger(); -logger.addTarget('console'); -// logger.addTarget('file', { path: `${logDir}/log.txt` }); +logger.addTarget(TargetType.Console); logger.setLevel(Logger.LEVEL_WARN); // Set to DEBUG to display sync process in console Logger.initializeGlobalLogger(logger); @@ -186,16 +184,16 @@ function isNetworkSyncTarget() { return isNetworkSyncTarget_; } -function sleep(n) { - return new Promise((resolve, reject) => { +function sleep(n: number) { + return new Promise((resolve) => { shim.setTimeout(() => { resolve(); }, Math.round(n * 1000)); }); } -function msleep(ms) { - return new Promise((resolve, reject) => { +function msleep(ms: number) { + return new Promise((resolve) => { shim.setTimeout(() => { resolve(); }, ms); @@ -211,7 +209,7 @@ async function afterEachCleanUp() { KeymapService.destroyInstance(); } -async function switchClient(id, options = null) { +async function switchClient(id: number, options: any = null) { options = Object.assign({}, { keychainEnabled: false }, options); if (!databases_[id]) throw new Error(`Call setupDatabaseAndSynchronizer(${id}) first!!`); @@ -236,7 +234,7 @@ async function switchClient(id, options = null) { Setting.setValue('sync.wipeOutFailSafe', false); // To keep things simple, always disable fail-safe unless explicitely set in the test itself } -async function clearDatabase(id = null) { +async function clearDatabase(id: number = null) { if (id === null) id = currentClient_; if (!databases_[id]) return; @@ -267,13 +265,19 @@ async function clearDatabase(id = null) { await databases_[id].transactionExecBatch(queries); } -async function setupDatabase(id = null, options = null) { +async function setupDatabase(id: number = null, options: any = null) { options = Object.assign({}, { keychainEnabled: false }, options); if (id === null) id = currentClient_; Setting.cancelScheduleSave(); - Setting.cache_ = null; + + // Note that this was changed from `Setting.cache_ = []` to `await + // Setting.reset()` during the TypeScript conversion. Normally this is + // more correct but something to keep in mind anyway in case there are + // some strange async issue related to settings when the tests are + // running. + await Setting.reset(); if (databases_[id]) { BaseModel.setDb(databases_[id]); @@ -298,22 +302,22 @@ async function setupDatabase(id = null, options = null) { await loadKeychainServiceAndSettings(options.keychainEnabled ? KeychainServiceDriver : KeychainServiceDriverDummy); } -function resourceDirName(id = null) { +function resourceDirName(id: number = null) { if (id === null) id = currentClient_; return `resources-${id}`; } -function resourceDir(id = null) { +function resourceDir(id: number = null) { if (id === null) id = currentClient_; return `${dataDir}/${resourceDirName(id)}`; } -function pluginDir(id = null) { +function pluginDir(id: number = null) { if (id === null) id = currentClient_; return `${dataDir}/plugins-${id}`; } -async function setupDatabaseAndSynchronizer(id, options = null) { +async function setupDatabaseAndSynchronizer(id: number, options: any = null) { if (id === null) id = currentClient_; BaseService.logger_ = logger; @@ -350,12 +354,12 @@ async function setupDatabaseAndSynchronizer(id, options = null) { await fileApi().clearRoot(); } -function db(id = null) { +function db(id: number = null) { if (id === null) id = currentClient_; return databases_[id]; } -function synchronizer(id = null) { +function synchronizer(id: number = null) { if (id === null) id = currentClient_; return synchronizers_[id]; } @@ -363,7 +367,7 @@ function synchronizer(id = null) { // This is like calling synchronizer.start() but it handles the // complexity of passing around the sync context depending on // the client. -async function synchronizerStart(id = null, extraOptions = null) { +async function synchronizerStart(id: number = null, extraOptions: any = null) { if (id === null) id = currentClient_; const context = synchronizerContexts_[id]; const options = Object.assign({}, extraOptions); @@ -373,41 +377,41 @@ async function synchronizerStart(id = null, extraOptions = null) { return newContext; } -function encryptionService(id = null) { +function encryptionService(id: number = null) { if (id === null) id = currentClient_; return encryptionServices_[id]; } -function kvStore(id = null) { +function kvStore(id: number = null) { if (id === null) id = currentClient_; const o = kvStores_[id]; o.setDb(db(id)); return o; } -function revisionService(id = null) { +function revisionService(id: number = null) { if (id === null) id = currentClient_; return revisionServices_[id]; } -function decryptionWorker(id = null) { +function decryptionWorker(id: number = null) { if (id === null) id = currentClient_; const o = decryptionWorkers_[id]; o.setKvStore(kvStore(id)); return o; } -function resourceService(id = null) { +function resourceService(id: number = null) { if (id === null) id = currentClient_; return resourceServices_[id]; } -function resourceFetcher(id = null) { +function resourceFetcher(id: number = null) { if (id === null) id = currentClient_; return resourceFetchers_[id]; } -async function loadEncryptionMasterKey(id = null, useExisting = false) { +async function loadEncryptionMasterKey(id: number = null, useExisting = false) { const service = encryptionService(id); let masterKey = null; @@ -490,7 +494,7 @@ function fileApi() { return fileApis_[syncTargetId_]; } -function objectsEqual(o1, o2) { +function objectsEqual(o1: any, o2: any) { if (Object.getOwnPropertyNames(o1).length !== Object.getOwnPropertyNames(o2).length) return false; for (const n in o1) { if (!o1.hasOwnProperty(n)) continue; @@ -499,7 +503,7 @@ function objectsEqual(o1, o2) { return true; } -async function checkThrowAsync(asyncFn) { +async function checkThrowAsync(asyncFn: Function) { let hasThrown = false; try { await asyncFn(); @@ -509,7 +513,7 @@ async function checkThrowAsync(asyncFn) { return hasThrown; } -async function expectThrow(asyncFn, errorCode = undefined) { +async function expectThrow(asyncFn: Function, errorCode: any = undefined) { let hasThrown = false; let thrownError = null; try { @@ -520,7 +524,7 @@ async function expectThrow(asyncFn, errorCode = undefined) { } if (!hasThrown) { - expect('not throw').toBe('throw', 'Expected function to throw an error but did not'); + expect('not throw').toBe('throw'); } else if (thrownError.code !== errorCode) { console.error(thrownError); expect(`error code: ${thrownError.code}`).toBe(`error code: ${errorCode}`); @@ -529,7 +533,7 @@ async function expectThrow(asyncFn, errorCode = undefined) { } } -async function expectNotThrow(asyncFn) { +async function expectNotThrow(asyncFn: Function) { let thrownError = null; try { await asyncFn(); @@ -539,13 +543,13 @@ async function expectNotThrow(asyncFn) { if (thrownError) { console.error(thrownError); - expect(thrownError.message).toBe('', 'Expected function not to throw an error but it did'); + expect(thrownError.message).toBe(''); } else { expect(true).toBe(true); } } -function checkThrow(fn) { +function checkThrow(fn: Function) { let hasThrown = false; try { fn(); @@ -555,7 +559,7 @@ function checkThrow(fn) { return hasThrown; } -function fileContentEqual(path1, path2) { +function fileContentEqual(path1: string, path2: string) { const fs = require('fs-extra'); const content1 = fs.readFileSync(path1, 'base64'); const content2 = fs.readFileSync(path2, 'base64'); @@ -594,19 +598,19 @@ async function allSyncTargetItemsEncrypted() { return totalCount === encryptedCount; } -function id(a) { +function id(a: any) { return a.id; } -function ids(a) { +function ids(a: any[]) { return a.map(n => n.id); } -function sortedIds(a) { +function sortedIds(a: any[]) { return ids(a).sort(); } -function at(a, indexes) { +function at(a: any[], indexes: any[]) { const out = []; for (let i = 0; i < indexes.length; i++) { out.push(a[indexes[i]]); @@ -614,7 +618,7 @@ function at(a, indexes) { return out; } -async function createNTestFolders(n) { +async function createNTestFolders(n: number) { const folders = []; for (let i = 0; i < n; i++) { const folder = await Folder.save({ title: 'folder' }); @@ -624,7 +628,7 @@ async function createNTestFolders(n) { return folders; } -async function createNTestNotes(n, folder, tagIds = null, title = 'note') { +async function createNTestNotes(n: number, folder: any, tagIds: string[] = null, title: string = 'note') { const notes = []; for (let i = 0; i < n; i++) { const title_ = n > 1 ? `${title}${i}` : title; @@ -641,7 +645,7 @@ async function createNTestNotes(n, folder, tagIds = null, title = 'note') { return notes; } -async function createNTestTags(n) { +async function createNTestTags(n: number) { const tags = []; for (let i = 0; i < n; i++) { const tag = await Tag.save({ title: 'tag' }); @@ -651,7 +655,7 @@ async function createNTestTags(n) { return tags; } -function tempFilePath(ext) { +function tempFilePath(ext: string) { return `${Setting.value('tempDir')}/${md5(Date.now() + Math.random())}.${ext}`; } @@ -678,7 +682,7 @@ function newPluginService(appVersion = '1.4') { return service; } -function newPluginScript(script) { +function newPluginScript(script: string) { return ` /* joplin-manifest: { @@ -708,6 +712,11 @@ function newPluginScript(script) { // Application for feature integration testing class TestApp extends BaseApplication { + + private hasGui_: boolean; + private middlewareCalls_: any[]; + private logger_: LoggerWrapper; + constructor(hasGui = true) { super(); this.hasGui_ = hasGui; @@ -719,7 +728,7 @@ class TestApp extends BaseApplication { return this.hasGui_; } - async start(argv) { + async start(argv: any[]) { this.logger_.info('Test app starting...'); if (!argv.includes('--profile')) { @@ -740,7 +749,7 @@ class TestApp extends BaseApplication { this.logger_.info('Test app started...'); } - async generalMiddleware(store, next, action) { + async generalMiddleware(store: any, next: any, action: any) { this.middlewareCalls_.push(true); try { await super.generalMiddleware(store, next, action); diff --git a/packages/lib/services/KvStore.ts b/packages/lib/services/KvStore.ts index 374b7f89b..306a1d671 100644 --- a/packages/lib/services/KvStore.ts +++ b/packages/lib/services/KvStore.ts @@ -23,7 +23,7 @@ export default class KvStore extends BaseService { this.instance_ = null; } - private constructor() { + public constructor() { super(); this.incMutex_ = new Mutex(); }