update: upgrade dependencies and fix tests

- remove spectron
- use electron-mocha to run mocha test suites inside electron
This commit is contained in:
Xymorot 2020-12-28 19:58:20 +01:00
parent 4c169178d9
commit d5697540a8
23 changed files with 1227 additions and 1980 deletions

View File

@ -9,7 +9,6 @@
node_modules
.nyc_output
/src/**/*.js
/mocks/**/*.js
/frontend
/test-paths
/out

View File

@ -43,7 +43,6 @@
"devDependencies": [
"**/*.{spec,mock}.*",
"src/**/test/*",
"mocks/**/*",
"src/renderer/**/*",
"templates/**/*",
"scripts/**/*"
@ -154,8 +153,6 @@
"rules": {
"no-unused-expressions": "off",
"import/order": "off",
"@typescript-eslint/no-magic-numbers": "off",
"@typescript-eslint/typedef": "off",
"@typescript-eslint/no-var-requires": "off",

2
.gitignore vendored
View File

@ -7,8 +7,6 @@ node_modules
# generated code
/src/**/*.js
/src/**/*.js.map
/mocks/**/*.js
/mocks/**/*.js.map
/frontend
# created by testing

3
.mocharc.json Normal file
View File

@ -0,0 +1,3 @@
{
"spec": "src/**/*.spec.js"
}

View File

@ -1,2 +0,0 @@
# https://github.com/mochajs/mocha/blob/master/example/config
spec: 'src/**/*.spec.js'

View File

@ -16,7 +16,6 @@
node_modules
.nyc_output
/src/**/*.js
/mocks/**/*.js
/frontend
/test-paths
/out

View File

@ -137,34 +137,27 @@ The point of these libraries is to make uniform code possible over various edito
The testing framework of choice is [Mocha](https://mochajs.org/). Call `npm run test` to run all tests. Tests are written in typescript and need to be transpiled before testing.
- assertion is (mainly) done by [Chai](https://www.chaijs.com/)
- Electron specific testing is done by [Spectron](https://electronjs.org/spectron)
- it writes its electron files into a temporary directory, so the local app installation should not be compromised
- assertion is (mainly) done by [Chai](https://www.chaijs.com/)should not be compromised
- spies, stubs and mocks are provided by [Sinon.JS](https://sinonjs.org/)
- HTTP server mocking is done by [nock](https://github.com/nock/nock)
- property based testing is made possible by [fast-check](https://github.com/dubzzz/fast-check)
For the creation of test files look at existing ones, they are named `*.spec.ts`.
For the creation of test files look at existing ones, they are named `*.spec.ts`. They are run inside electron via [electron-mocha](https://github.com/jprichardson/electron-mocha).
#### Mocks
There are 2 ways in which mocks are defined/used:
Mocks are defined/used for own modules, just beside their file.
0. for external modules, in [mocks](mocks)
- uses the [rewiremock](https://github.com/theKashey/rewiremock) package
- use this only when there is some magic happening like for electron which normally runs in its own node process
1. for own modules, just beside their file
- name the file `*.mock.ts` and use existing mock files for orientation on how to build them
- use sparingly and only when not having a mock makes it more complex e.g. for modules which interact with the file system
- name the file `*.mock.ts` and use existing mock files for orientation on how to build them
- use sparingly and only when not having a mock makes it more complex e.g. for modules which interact with the file system
#### Tagging
Mocha does [not have a separate tagging feature](https://github.com/mochajs/mocha/wiki/Tagging), but it can filter via title. Use the following tags in your test titles:
| tag | usage when |
| ----------- | ------------------------- |
| `@slow` | test is particularly slow |
| `@spectron` | test uses spectron |
| tag | usage when |
| ------- | ------------------------- |
| `@slow` | test is particularly slow |
#### Coverage

View File

@ -1,28 +0,0 @@
import path from 'path';
import { rewiremock } from './rewiremock';
import WebContents = Electron.WebContents;
const electronMock: DeepPartial<typeof Electron> = {
app: {
on(): void {},
getPath(name: string): string {
return path.resolve('test-paths', name);
},
quit(): void {},
getAppPath(): string {
return path.resolve(__dirname, '..');
},
},
BrowserWindow: class {
public webContents: DeepPartial<WebContents> = {
openDevTools(): void {},
};
public loadFile(): void {}
public on(): void {}
},
ipcMain: {
on(): void {},
},
};
rewiremock('electron').with(electronMock);

View File

@ -1,6 +0,0 @@
import rewiremock from 'rewiremock';
rewiremock.overrideEntryPoint(module);
rewiremock.enable();
export { rewiremock };

2919
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,8 +15,9 @@
},
"scripts": {
"postinstall": "npm run rebuild",
"postupdate": "npm run rebuild",
"start": "electron . --enable-logging --env=dev",
"rebuild": "electron-rebuild -f -b -t prod,dev,optional",
"rebuild": "electron-rebuild -f -b -o better-sqlite3",
"electron-version": "electron scripts/electron-version.js",
"typeorm:migrate": "npm run typeorm:migrate:library && npm run typeorm:migrate:store",
"typeorm:migrate:library": "typeorm migration:run -c library",
@ -29,11 +30,10 @@
"dev:ts": "tsc -w --pretty --preserveWatchOutput",
"build": "concurrently -c green,yellow,cyan -n webpack,index,typescript \"npm run build:webpack\" \"npm run build:index\" \"npm run build:ts\"",
"dev": "concurrently -c green,yellow,cyan -n webpack,index,typescript \"npm run dev:webpack\" \"npm run dev:index\" \"npm run dev:ts\"",
"test:fast": "mocha --grep \"@(slow|spectron)\" --invert",
"test": "mocha",
"coverage:fast": "nyc npm run test:fast",
"test:fast": "electron-mocha --config .mocharc.json --grep \\\"@slow\\\" --invert",
"test": "electron-mocha --config .mocharc.json",
"coverage": "nyc npm run test",
"prelint": "eslint --print-config scripts/forge.config.js | eslint-config-prettier-check",
"prelint": "eslint-config-prettier src/main.ts",
"lint": "eslint ./**/*.* --max-warnings 1",
"lint:fix": "eslint ./**/*.* --fix",
"prettier": "prettier -c **/*.*",
@ -41,11 +41,11 @@
"fix": "npm run lint:fix && npm run prettier:fix",
"forge:make": "electron-forge --platform win32 --arch x64 make",
"forge": "npm audit --production --audit-level=high && npm run build && npm run forge:make",
"precommit": "npm run prettier && npm run lint && npm run build && npm run coverage:fast",
"precommit": "npm run prettier && npm run lint && npm run build && npm run test:fast",
"prepush": "npm run build && npm run coverage"
},
"dependencies": {
"better-sqlite3": "^7.1.1",
"better-sqlite3": "^7.1.2",
"electron-squirrel-startup": "^1.0.0",
"fs-extra": "^9.0.1",
"inversify": "^5.0.1",
@ -53,7 +53,7 @@
"node-fetch": "^2.6.1",
"reflect-metadata": "^0.1.13",
"typeorm": "^0.2.29",
"uuid": "^8.3.1"
"uuid": "^8.3.2"
},
"devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.54",
@ -61,40 +61,41 @@
"@types/better-sqlite3": "^5.4.1",
"@types/chai": "^4.2.14",
"@types/chai-fs": "^2.0.2",
"@types/fs-extra": "^9.0.3",
"@types/fs-extra": "^9.0.6",
"@types/glob": "^7.1.3",
"@types/minimist": "^1.2.1",
"@types/mocha": "^8.0.3",
"@types/node": "^12.19.3",
"@types/mocha": "^8.2.0",
"@types/node": "^14.14.16",
"@types/node-fetch": "^2.5.7",
"@types/sinon": "^9.0.8",
"@types/sinon": "^9.0.10",
"@types/uuid": "^8.3.0",
"@types/webpack": "^4.41.24",
"@typescript-eslint/eslint-plugin": "^4.7.0",
"@typescript-eslint/parser": "^4.7.0",
"@types/webpack": "^4.41.25",
"@typescript-eslint/eslint-plugin": "^4.11.1",
"@typescript-eslint/parser": "^4.11.1",
"chai": "^4.2.0",
"chai-fs": "^2.0.0",
"chokidar": "^3.4.3",
"concurrently": "^5.3.0",
"electron": "^10.1.5",
"electron-rebuild": "^2.3.2",
"eslint": "^7.13.0",
"eslint-config-prettier": "^6.15.0",
"electron": "^10.2.0",
"electron-mocha": "^10.0.0",
"electron-rebuild": "^2.3.4",
"eslint": "^7.16.0",
"eslint-config-prettier": "^7.1.0",
"eslint-plugin-import": "^2.22.1",
"fast-check": "^2.6.1",
"fast-check": "^2.10.0",
"glob": "^7.1.6",
"handlebars": "^4.7.6",
"husky": "^4.3.0",
"husky": "^4.3.6",
"lodash": "^4.17.20",
"mocha": "^8.2.1",
"nock": "^13.0.4",
"nock": "^13.0.5",
"nyc": "^15.1.0",
"prettier": "^2.1.2",
"rewiremock": "^3.14.3",
"sinon": "^9.2.1",
"spectron": "^12.0.0",
"svelte": "^3.29.6",
"prettier": "^2.2.1",
"sinon": "^9.2.2",
"svelte": "^3.31.0",
"svelte-loader": "^2.13.6",
"ts-loader": "^8.0.11",
"typescript": "^4.0.5",
"ts-loader": "^8.0.12",
"typescript": "^4.1.3",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12"
},

View File

@ -6,7 +6,6 @@ const ignoreList = [
/^\/\.nyc_output($|\/)/,
/^\/declarations($|\/)/,
/^\/mocks($|\/)/,
/^\/templates($|\/)/,
/^\/test-paths($|\/)/,
/^\/types($|\/)/,
@ -15,15 +14,17 @@ const ignoreList = [
/^\/\.editorconfig/,
/^\/\.eslintignore/,
/^\/\.prettierignore/,
/^\/\.eslintrc\.json/,
/^\/\.gitignore/,
/^\/\.mocharc\.yml/,
/^\/\.mocharc\.json/,
/^\/\.nycrc\.yml/,
/^\/\.prettierrc\.yml/,
/^\/CONTRIBUTING\.md/,
/^\/ormconfig\.yml/,
/^\/package-lock\.json/,
/^\/tsconfig\.json/,
/^\/tsconfig\.renderer\.json/,
/^\/node_modules\/\.cache($|\/)/,
// test and mock files:
@ -36,8 +37,10 @@ const ignoreList = [
const name = packageJson.productName;
if(name !== 'Renai') {
throw new TypeError(`The product name "${name}" in package.json is not "Renai"! Change it before building but do not commit it.`)
if (name !== 'Renai') {
throw new TypeError(
`The product name "${name}" in package.json is not "Renai"! Change it before building but do not commit it.`
);
}
module.exports = {

View File

@ -1,32 +0,0 @@
import { expect } from 'chai';
import rewiremock from 'rewiremock';
import 'mocha';
import { IApplicationContext } from './main/test/i-application-context';
import { createApplication } from './main/test/spectron-util';
rewiremock.disable();
describe('Application @spectron', function () {
this.timeout(20000);
before(function (this: IApplicationContext) {
this.app = createApplication();
return this.app.start();
});
after(function (this: IApplicationContext) {
if (this.app && this.app.isRunning()) {
return this.app.stop();
}
});
it('shows an initial window', function (this: IApplicationContext) {
if (!this.app) {
throw Error('this.app is falsy');
}
return this.app.client.getWindowCount().then((count: number) => {
expect(count).to.be.gte(1);
});
});
});

View File

@ -1,19 +1,8 @@
import rewiremock from 'rewiremock';
import '../../../mocks/electron';
import { expect } from 'chai';
import 'mocha';
import { Database, getConnection } from './database';
describe('Database Service', () => {
before(() => {
rewiremock.enable();
});
after(() => {
rewiremock.disable();
});
it('returns a connection', async () => {
const libraryConnection = await getConnection(Database.LIBRARY);
expect(libraryConnection).to.not.equal(undefined);

View File

@ -1,27 +1,16 @@
import rewiremock from 'rewiremock';
import '../../../mocks/electron';
import { expect } from 'chai';
import 'mocha';
import { isDev } from './env';
export function setDev(dev = true): void {
if (dev) {
process.argv.push('--env=dev');
process.argv.splice(2, 0, '--env=dev');
} else {
process.argv = process.argv.filter((value) => value !== '--env=dev');
}
}
describe('Environment Service', () => {
before(() => {
rewiremock.enable();
});
after(() => {
rewiremock.disable();
});
it('correctly identifies the development process argument', () => {
setDev();
expect(isDev()).to.be.true;

View File

@ -35,7 +35,7 @@ export abstract class UrlAppWindow extends AppWindow implements IUrlAppWindow {
const waitInterval = 1000;
let failedLoad = true;
do {
await new Promise((resolve) => {
await new Promise<void>((resolve) => {
if (!this._window) {
throw new WindowClosedError();
}

View File

@ -1,16 +1,13 @@
import rewiremock from 'rewiremock';
import '../../../../mocks/electron';
import { createInterface, Interface } from 'readline';
import chai, { expect } from 'chai';
import 'mocha';
import chaiFs from 'chai-fs';
import fc from 'fast-check';
import fs from 'fs-extra';
import { container } from '../../core/container';
import { setDev } from '../../core/env.spec';
import { ILogger } from './i-logger';
import fs from 'fs-extra';
import { createInterface, Interface } from 'readline';
import fc from 'fast-check';
import { LogLevel } from './log-level';
import chaiFs from 'chai-fs';
chai.use(chaiFs);
@ -44,14 +41,6 @@ describe('Logger Service', () => {
const logLevelNumberArbitrary = fc.constantFrom(...logLevelsNumber);
before(() => {
rewiremock.enable();
});
after(() => {
rewiremock.disable();
});
it('creates log files', () => {
const logger: ILogger = container.get('logger');
@ -68,8 +57,18 @@ describe('Logger Service', () => {
it("default log file doesn't get bigger than 50KB @slow", async () => {
const logger: ILogger = container.get('logger');
for (let i = 0; i < maxLogSize; i++) {
let prevLogFileSize = (await fs.stat(logger.getLogFile())).size;
let minNumberOfLines = maxLogSize;
const sizeIncreases = [];
for (let i = 0; i <= minNumberOfLines; i++) {
await logger.log(4, 'your waifu is trash');
const currLogFileSize = (await fs.stat(logger.getLogFile())).size;
const sizeIncrease = currLogFileSize - prevLogFileSize;
if (sizeIncrease) {
sizeIncreases.push(sizeIncrease);
minNumberOfLines = maxLogSize / (sizeIncreases.reduce((sum, e) => sum + e, 0) / sizeIncreases.length);
}
prevLogFileSize = currLogFileSize;
}
const logFileStats = await fs.stat(logger.getLogFile());
@ -79,12 +78,22 @@ describe('Logger Service', () => {
it("exception log file doesn't get bigger than 50KB @slow", async () => {
const logger: ILogger = container.get('logger');
for (let i = 0; i < maxLogSize; ++i) {
let prevLogFileSize = (await fs.stat(logger.getExceptionsLogFile())).size;
let minNumberOfLines = maxLogSize;
const sizeIncreases = [];
for (let i = 0; i <= minNumberOfLines; i++) {
await logger.exception(new Error('your waifu is trash'));
const currLogFileSize = (await fs.stat(logger.getExceptionsLogFile())).size;
const sizeIncrease = currLogFileSize - prevLogFileSize;
if (sizeIncrease) {
sizeIncreases.push(sizeIncrease);
minNumberOfLines = maxLogSize / (sizeIncreases.reduce((sum, e) => sum + e, 0) / sizeIncreases.length);
}
prevLogFileSize = currLogFileSize;
}
const logFileStats = await fs.stat(logger.getLogFile());
expect(logFileStats.size).lessThan(maxLogSize, 'log is bigger than its max size');
const logFileStats = await fs.stat(logger.getExceptionsLogFile());
expect(logFileStats.size).lessThan(maxLogSize, 'exception log is bigger than its max size');
}).timeout(15000);
it('logs different levels directly', () => {

View File

@ -180,11 +180,17 @@ export class NhentaiAppWindow extends UrlAppWindow implements INhentaiAppWindow
}
private async getBookTorrent(bookUrl: string): Promise<IFavorite> {
if (!this._window) {
throw new WindowClosedError();
}
const galleryId = getGalleryId(bookUrl);
const fileName = `${galleryId}.torrent`;
const filePath = path.resolve(os.tmpdir(), fileName);
await this.loadUrlSafe(bookUrl);
await new Promise<string>((resolve, reject) => {
const downloadLink: string = (await this._window.webContents.executeJavaScript(
`document.getElementById('${downloadLinkId}').href`
)) as string;
await new Promise<void>((resolve, reject) => {
if (!this._window) {
throw new WindowClosedError();
}
@ -206,7 +212,7 @@ export class NhentaiAppWindow extends UrlAppWindow implements INhentaiAppWindow
item.resume();
});
});
void this._window.webContents.executeJavaScript(`document.getElementById('${downloadLinkId}').click()`);
void this.loadUrlSafe(downloadLink);
});
const readable = createReadStream(filePath, { emitClose: true });

View File

@ -1,6 +1,3 @@
import rewiremock from 'rewiremock';
import '../../../../mocks/electron';
import { expect } from 'chai';
import 'mocha';
import { container } from '../../core/container';
@ -9,14 +6,6 @@ import { IStore } from './i-store';
describe('Store Service', function () {
this.timeout(10000);
before(() => {
rewiremock.enable();
});
after(() => {
rewiremock.disable();
});
it('loads saved data', () => {
const store: IStore = container.get('store');
const testData = {

View File

@ -1,6 +0,0 @@
import { Context } from 'mocha';
import { Application } from 'spectron';
export interface IApplicationContext extends Context {
app?: Application;
}

View File

@ -1,9 +0,0 @@
import * as electron from 'electron';
import { Application } from 'spectron';
export function createApplication(): Application {
return new Application({
path: ((electron as unknown) as { default: string }).default,
args: ['.', '--enable-logging'],
});
}

View File

@ -15,5 +15,5 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["declarations/**/*.ts", "types/**/*.ts", "src/**/*.ts", "mocks/**/*.ts"]
"include": ["declarations/**/*.ts", "types/**/*.ts", "src/**/*.ts"]
}

View File

@ -1,4 +1,8 @@
/**
* @see MethodDecorator
*/
type IpcControllerMethodDecorator = <T>(target: IIpcController, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) => void
type IpcControllerMethodDecorator = <T>(
target: IIpcController,
propertyKey: string,
descriptor: TypedPropertyDescriptor<T>
) => void;