Skip to content

Commit

Permalink
chore: re-write GUI to typescript part 3 (#521)
Browse files Browse the repository at this point in the history
* chore: rename gui/app to typescript

* chore: rewrite gui/app to typescript

* chore: rewrite gui/routes/plugins to typescript

* chore: rewrite gui server to typescript
  • Loading branch information
shadowusr authored Nov 21, 2023
1 parent 6d0a7b3 commit f91cf2c
Show file tree
Hide file tree
Showing 18 changed files with 371 additions and 275 deletions.
2 changes: 1 addition & 1 deletion lib/cli-commands/gui.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const {cliCommands} = require('.');
const runGui = require('../gui').default;
const Api = require('../gui/api');
const {Api} = require('../gui/api');

const {GUI: commandName} = cliCommands;

Expand Down
16 changes: 0 additions & 16 deletions lib/gui/api/facade.js

This file was deleted.

16 changes: 16 additions & 0 deletions lib/gui/api/facade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import EventEmitter2 from 'eventemitter2';
import {GuiEvents} from '../constants';

export class ApiFacade extends EventEmitter2 {
events: GuiEvents;

static create<T extends ApiFacade>(this: new () => T): T {
return new this();
}

constructor() {
super();

this.events = GuiEvents;
}
}
21 changes: 0 additions & 21 deletions lib/gui/api/index.js

This file was deleted.

29 changes: 29 additions & 0 deletions lib/gui/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {ApiFacade} from './facade';
import Hermione from 'hermione';
import {Express} from 'express';

export interface ServerReadyData {
url: string;
}

type HermioneWithGui = Hermione & { gui: ApiFacade };

export class Api {
private _gui: ApiFacade;

static create<T extends Api>(this: new (hermione: HermioneWithGui) => T, hermione: HermioneWithGui): T {
return new this(hermione);
}

constructor(hermione: HermioneWithGui) {
this._gui = hermione.gui = ApiFacade.create();
}

async initServer(server: Express): Promise<void> {
await this._gui.emitAsync(this._gui.events.SERVER_INIT, server);
}

async serverReady(data: ServerReadyData): Promise<void> {
await this._gui.emitAsync(this._gui.events.SERVER_READY, data);
}
}
86 changes: 0 additions & 86 deletions lib/gui/app.js

This file was deleted.

96 changes: 96 additions & 0 deletions lib/gui/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {Response} from 'express';
import _ from 'lodash';

import {ToolRunner, ToolRunnerTree, UndoAcceptImagesResult} from './tool-runner';
import {HtmlReporterApi} from '../types';
import Hermione, {Config} from 'hermione';
import {GuiConfigs} from './index';
import {TestSpec} from './tool-runner/runner/runner';
import {TestBranch, TestEqualDiffsData, TestRefUpdateData} from '../tests-tree-builder/gui';

type BrowserConfig = ReturnType<Config['forBrowser']>;

type AppArgs = [paths: string[], hermione: Hermione & HtmlReporterApi, configs: GuiConfigs];

export class App {
private _toolRunner: ToolRunner;
private _browserConfigs: BrowserConfig[];
private _retryCache: Record<string, number>;

static create<T extends App>(this: new (...args: AppArgs) => T, ...args: AppArgs): T {
return new this(...args);
}

constructor(...[paths, hermione, configs]: AppArgs) {
this._toolRunner = ToolRunner.create(paths, hermione, configs);

this._browserConfigs = [];
this._retryCache = {};
}

get data(): ToolRunnerTree | null {
return this._toolRunner.tree;
}

async initialize(): Promise<void> {
return await this._toolRunner.initialize();
}

async finalize(): Promise<void> {
return this._toolRunner.finalize();
}

async run(tests: TestSpec[]): Promise<boolean> {
return _.isEmpty(tests)
? this._toolRunner.run()
: this._runWithoutRetries(tests);
}

private async _runWithoutRetries(tests: TestSpec[]): Promise<boolean> {
if (_.isEmpty(this._browserConfigs)) {
this._browserConfigs = _.map(this._toolRunner.config.getBrowserIds(), (id) => this._toolRunner.config.forBrowser(id));
}

this._disableRetries();

return this._toolRunner.run(tests)
.finally(() => this._restoreRetries());
}

getTestsDataToUpdateRefs(imageIds: string[] = []): TestRefUpdateData[] {
return this._toolRunner.getTestsDataToUpdateRefs(imageIds);
}

getImageDataToFindEqualDiffs(imageIds: string[] = []): TestEqualDiffsData[] {
return this._toolRunner.getImageDataToFindEqualDiffs(imageIds);
}

async updateReferenceImage(failedTests: TestRefUpdateData[] = []): Promise<TestBranch[]> {
return this._toolRunner.updateReferenceImage(failedTests);
}

async undoAcceptImages(imageIds: TestRefUpdateData[]): Promise<UndoAcceptImagesResult> {
return this._toolRunner.undoAcceptImages(imageIds);
}

async findEqualDiffs(data: TestEqualDiffsData[]): Promise<string[]> {
return this._toolRunner.findEqualDiffs(data);
}

addClient(connection: Response): void {
this._toolRunner.addClient(connection);
}

private _disableRetries(): void {
this._browserConfigs.forEach((broConfig) => {
this._retryCache[broConfig.id] = broConfig.retry;
broConfig.retry = 0;
});
}

private _restoreRetries(): void {
this._browserConfigs.forEach((broConfig) => {
broConfig.retry = this._retryCache[broConfig.id];
});
}
}
14 changes: 9 additions & 5 deletions lib/gui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ import type {CommanderStatic} from '@gemini-testing/commander';
import chalk from 'chalk';
import opener from 'opener';

import server from './server';
import * as server from './server';
import {logger} from '../common-utils';
import * as utils from '../server-utils';
import {ReporterConfig} from '../types';
import {HtmlReporterApi, ReporterConfig} from '../types';
import Hermione from 'hermione';
import {Api} from './api';

const {logError} = utils;

export interface GuiCliOptions {
autoRun: boolean;
open: unknown;
port: number;
hostname: string;
}

export interface GuiConfigs {
Expand All @@ -20,10 +24,10 @@ export interface GuiConfigs {
pluginConfig: ReporterConfig;
}

interface ServerArgs {
export interface ServerArgs {
paths: string[];
hermione: unknown;
guiApi: unknown;
hermione: Hermione & HtmlReporterApi;
guiApi: Api;
configs: GuiConfigs;
}

Expand Down
21 changes: 10 additions & 11 deletions lib/gui/routes/plugins.js → lib/gui/routes/plugins.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
'use strict';
import {Router} from 'express';
import {INTERNAL_SERVER_ERROR, BAD_REQUEST} from 'http-codes';

const {Router} = require('express');
const {INTERNAL_SERVER_ERROR, BAD_REQUEST} = require('http-codes');

const {
import {
isUnexpectedPlugin,
getPluginClientScriptPath,
getPluginMiddleware,
forEachPlugin,
logError
} = require('../../server-utils');
} from '../../server-utils';
import {ReporterConfig} from '../../types';

module.exports = function(router, pluginConfig) {
export const initPluginsRoutes = (router: Router, pluginConfig: ReporterConfig): Router => {
if (!pluginConfig.pluginsEnabled) {
return router;
}

router.get(`/plugins/:pluginName/plugin.js`, (req, res) => {
router.get<{pluginName: string}>(`/plugins/:pluginName/plugin.js`, (req, res) => {
if (isUnexpectedPlugin(pluginConfig.plugins, req.params.pluginName)) {
res.status(BAD_REQUEST).send({error: `Unexpected plugin "${req.params.pluginName}" requested.`});
return;
Expand All @@ -42,12 +41,12 @@ module.exports = function(router, pluginConfig) {
initPluginMiddleware(pluginRouter);

router.use(`/plugin-routes/${pluginName}`, pluginRouter);
} catch (err) {
logError(err);
} catch (err: unknown) {
logError(err as Error);
}
});

router.use('/plugin-routes/:pluginName', (req, res) => {
router.use<{pluginName: string}>('/plugin-routes/:pluginName', (req, res) => {
res.status(BAD_REQUEST).send({error: `No middleware is registered for "${req.params.pluginName}".`});
});

Expand Down
Loading

0 comments on commit f91cf2c

Please sign in to comment.