Skip to content

Commit

Permalink
add tests for WindowManager and use mocha-electron for testing
Browse files Browse the repository at this point in the history
so we have access to the Electron APIs
  • Loading branch information
olvidalo committed May 8, 2023
1 parent 47e1115 commit a730d87
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 31 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"ci:dist": "electron-builder",
"release": "electron-builder",
"test:winery-launcher": "mvn -pl winery-launcher test",
"test:electron": "mocha -r ts-node/register test/**/*.unit.test.ts test/**/*.integration.test.ts",
"test:electron": "electron-mocha -r ts-node/register test/**/*.unit.test.ts test/**/*.integration.test.ts",
"test:electron:coverage": "nyc npm run test:electron",
"test": "npm-run-all test:winery-launcher test:electron"
},
Expand Down Expand Up @@ -115,6 +115,7 @@
"css-loader": "^6.7.3",
"electron": "^24.1.2",
"electron-builder": "^24.2.1",
"electron-mocha": "^12.0.0",
"eslint": "^8.39.0",
"eslint-plugin-tsdoc": "^0.2.17",
"execa": "^7.1.1",
Expand Down
3 changes: 0 additions & 3 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,6 @@ function isValidRepository(repositoryPath: string) {
function checkUrlType(url: URL): NavigationUrlType {
const parsedMainWindowUrl = new URL(mainWindowUrl)

console.log(url.pathname)
console.log(wineryProcess.toscaManagerUrl.pathname)

if (url.href.startsWith(parsedMainWindowUrl.href)) {
return "mainWindow"
} else if (
Expand Down
14 changes: 3 additions & 11 deletions src/main/windowManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,13 @@ export class WindowManager extends EventEmitter {
private topologyModelerWindowSet = new Set<BrowserWindow>()

constructor(private urlTypeChecker: (url: URL) => NavigationUrlType) {
if (!urlTypeChecker) {
throw new Error("Could not initialize Window Manager: Need to pass in an URL type checker!")
}
super()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
app['bw'] = BrowserWindow
}

get mainWindow() { return this._mainWindow }
get toscaManagerWindows() { return Array.from(this.toscaManagerWindowSet) }
get topologyModelerWindows() { return Array.from(this.topologyModelerWindowSet) }
get wineryWindows() { return [...this.toscaManagerWindows, ...this.topologyModelerWindows] }
get toscaManagerWindows() { return Object.freeze(Array.from(this.toscaManagerWindowSet)) }
get topologyModelerWindows() { return Object.freeze(Array.from(this.topologyModelerWindowSet)) }
get wineryWindows() { return Object.freeze([...this.toscaManagerWindows, ...this.topologyModelerWindows]) }

/**
* Opens the main "workspace selection" window. Makes sure it is created as needed and that there is only one main
Expand Down Expand Up @@ -178,8 +172,6 @@ export class WindowManager extends EventEmitter {
private wineryWindowOpenHandler(details: HandlerDetails): ReturnType<WindowOpenHandler> {
const parsedUrl = new URL(details.url)
const urlType = this.urlTypeChecker(parsedUrl)
console.log(urlType)
console.log(details.url)

this.openWindowFor(parsedUrl).catch()

Expand Down
18 changes: 18 additions & 0 deletions test/main/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {mainWindowUrl, wineryApiPath} from "../../src/main/resources";
import {createLogger, transports} from "winston";
import {PassThrough} from "stream";
import {match} from "sinon";

export const PORT = 8000
const wineryApiUrl = new URL(wineryApiPath, `http://localhost:${PORT}`).toString()
export const wineryApiUrlMatcher = match((url: URL) => url.toString() === wineryApiUrl)
export const mainWindowUrlMatcher = match((url: URL) => url.toString() === mainWindowUrl)

// Helper function: create da test dummy logger to listen for "logged" events
export const createTestLogger = () => {
const testTransport = new transports.Stream({stream: new PassThrough()})
const testLogger = createLogger({
transports: [testTransport]
})
return {testTransport, testLogger};
};
105 changes: 105 additions & 0 deletions test/main/window-manager.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {expect} from 'chai';
import {BrowserWindow, shell} from 'electron';
import sinon, {SinonStub} from 'sinon';
import {NavigationUrlType, WindowManager} from "../../src/main/windowManager";
import {mainWindowUrlMatcher} from "./utils";
import {mainWindowUrl} from "../../src/main/resources";

type LoadURLStub = SinonStub<[string, Electron.LoadURLOptions?], Promise<void>>

describe('WindowManager', () => {
let windowManager: WindowManager;
let urlTypeCheckerStub: SinonStub<[URL], NavigationUrlType>;

beforeEach(() => {
urlTypeCheckerStub = sinon.stub<[URL]>()
windowManager = new WindowManager(urlTypeCheckerStub)

// make sure electron does not try to load any urls during tests (which would open an error dialog)
sinon
.stub(BrowserWindow.prototype, 'loadURL')
.withArgs(mainWindowUrlMatcher)
.resolves()
});

afterEach(() => {
sinon.restore();
});

describe('openMainWindow', () => {
it('should create a new main window when called for the first time', () => {
expect(windowManager.mainWindow).to.be.null;
windowManager.openMainWindow();
expect(windowManager.mainWindow).to.be.instanceOf(BrowserWindow);
});

it('should not create a new main window when called multiple times', () => {
windowManager.openMainWindow();
const firstWindow = windowManager.mainWindow;
windowManager.openMainWindow();
const secondWindow = windowManager.mainWindow;
expect(firstWindow).to.equal(secondWindow);
});
});

describe('openWindowFor', () => {
it('should create and load a TOSCA Manager window when called with a TOSCA Manager URL', async () => {
const url = "about:blank"
urlTypeCheckerStub.returns("toscaManager");

const window = await windowManager.openWindowFor(new URL(url))

expect((window.loadURL as LoadURLStub).calledOnceWith(url.toString())).to.be.true;
expect(windowManager.toscaManagerWindows.length).to.equal(1)
expect(windowManager.toscaManagerWindows).to.contain(window)
});

it('should create and load a new Topology Modeler window with the specified URL', async () => {
const url = "about:blank"
urlTypeCheckerStub.returns("topologyModeler");
const window = await windowManager.openWindowFor(new URL(url))

expect((window.loadURL as LoadURLStub).calledOnceWith(url.toString())).to.be.true;
expect(windowManager.topologyModelerWindows.length).to.equal(1)
expect(windowManager.topologyModelerWindows).to.contain(window)
});

it('should open external links in the user\'s web browser', async () => {
const externalUrl = new URL('http://example.com');
urlTypeCheckerStub.returns("external");
const shellOpenExternalStub = sinon.stub(shell, 'openExternal')
.withArgs(externalUrl.toString())
.resolves()

await windowManager.openWindowFor(externalUrl);
expect(shellOpenExternalStub.calledOnceWith(externalUrl.toString())).to.be.true;
});

it('should throw an error if the specified URL is the main window URL', async () => {
urlTypeCheckerStub.returns("mainWindow");
try {
await windowManager.openWindowFor(new URL(mainWindowUrl))
expect.fail("openWindowFor should fail when trying to open the mainWindowUrl")
} catch (e) { /* empty */ }
});
});

describe('closeAllWineryWindows', () => {
it('should close all winery windows', () => {
const destroySpy = sinon.spy(BrowserWindow.prototype, 'destroy');

// Open two Tosca Manager windows and one Topology Modeler window
urlTypeCheckerStub.returns("toscaManager");
windowManager.openWindowFor(new URL('about:blank'));
windowManager.openWindowFor(new URL('about:blank'));

urlTypeCheckerStub.returns("topologyModeler");
windowManager.openWindowFor(new URL('about:blank'));

expect(windowManager.wineryWindows.length).to.equal(3)

windowManager.closeAllWineryWindows();
expect(windowManager.wineryWindows).to.be.empty
});
});
});
17 changes: 2 additions & 15 deletions test/main/winery-manager.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,9 @@ import child_process, {ChildProcess} from "child_process";
import {Duplex, PassThrough, Writable} from "stream";
import {expect} from "chai";
import {WineryManager} from "../../src/main/wineryManager";
import {createLogger, LogEntry, transports} from "winston";
import {LogEntry} from "winston";
import * as fse from "fs-extra";
import {wineryApiPath} from "../../src/main/resources";

const PORT = 8000
const wineryApiUrl = new URL(wineryApiPath, `http://localhost:${PORT}`).toString()
const wineryApiUrlMatcher = match((url: URL) => url.toString() === wineryApiUrl)
import {createTestLogger, PORT, wineryApiUrlMatcher} from "./utils";

class MockChildProcess extends ChildProcess {
constructor(
Expand All @@ -27,15 +23,6 @@ class MockChildProcess extends ChildProcess {
}


// Helper function: create da test dummy logger to listen for "logged" events
const createTestLogger = () => {
const testTransport = new transports.Stream({stream: new PassThrough()})
const testLogger = createLogger({
transports: [testTransport]
})
return {testTransport, testLogger};
};

describe('Winery Manager Unit Tests', () => {
let fetchStub: SinonStub;
let writeFileStub: SinonStub;
Expand Down
2 changes: 1 addition & 1 deletion test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"esModuleInterop": true
},
"include": [
"./**/*.test.ts"
"./**/*.ts"
],
"exclude": [
"../node_modules"
Expand Down

0 comments on commit a730d87

Please sign in to comment.