Skip to content

Commit

Permalink
fix(measure): UI buttons unresponsive/streamline socket URL handling (#…
Browse files Browse the repository at this point in the history
…191)

* fix(measure): streamline socket URL handling

* test(measure): write tests for dynamic socket connection

* chore: prevent jest for failing for ts errors

Seemingly Jest can fail even though tsc is passing properly
Since we catch errors in "yarn tsc" already, we don't need jest to bother us about it anyway
I think ts-jest doesn't recognize properly the .d.ts definition basically

---------

Co-authored-by: almouro <contact@almouro.com>
  • Loading branch information
nnnoel and Almouro committed Jan 17, 2024
1 parent 1a44141 commit 66b0899
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 14 deletions.
10 changes: 8 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
const commonOptions = {
transform: {
"^.+\\.tsx?$": "ts-jest",
"^.+\\.tsx?$": [
"ts-jest",
{
// we already get TS errors with tsc, so no need to have them here
diagnostics: false,
},
],
},
testPathIgnorePatterns: [
"\\.snap$",
Expand Down Expand Up @@ -49,6 +55,6 @@ module.exports = {
...commonOptions,
displayName: name,
testEnvironment,
testMatch: [`<rootDir>/packages/${name}/**/__tests__/*.{ts,tsx}`],
testMatch: [`<rootDir>/packages/${name}/**/__tests__/**/*.{ts,tsx}`],
})),
};
4 changes: 3 additions & 1 deletion packages/commands/measure/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@
"@perf-profiler/web-reporter-ui": "^0.14.0",
"@testing-library/react": "^12.1.1",
"@types/express": "^4.17.17",
"@types/supertest": "^6.0.2",
"ink-testing-library": "^2.1.0",
"parcel": "^2.7.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"socket.io-client": "^4.7.0"
"socket.io-client": "^4.7.0",
"supertest": "^6.3.4"
},
"scripts": {
"build": "yarn parcel build src/webapp/index.html",
Expand Down
20 changes: 19 additions & 1 deletion packages/commands/measure/src/__tests__/measure.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
} from "@perf-profiler/e2e/src/utils/test/mockEmitMeasures";
import { fireEvent, render as webRender, screen, waitFor, act } from "@testing-library/react";
import { render as cliRender } from "ink-testing-library";
import { MeasureWebApp } from "../webapp/MeasureWebApp";
import React from "react";
import { ServerApp } from "../server/ServerApp";
import { open } from "@perf-profiler/shell";
Expand All @@ -24,7 +23,26 @@ Math.random = () => 0.5;
// Set me to LogLevel.DEBUG to see the debug logs
Logger.setLogLevel(LogLevel.SILENT);

let originalWindow: Window & typeof globalThis;
let MeasureWebApp: React.FC;

describe("flashlight measure interactive", () => {
beforeAll(async () => {
originalWindow = global.window;

global.window = Object.create(window);
Object.defineProperty(window, "__FLASHLIGHT_DATA__", {
value: { socketServerUrl: `http://localhost:${DEFAULT_PORT}` },
writable: true,
});

MeasureWebApp = (await import("../webapp/MeasureWebApp")).MeasureWebApp;
});

afterAll(() => {
global.window = originalWindow;
});

const expectWebAppToBeOpened = () =>
waitFor(() => expect(open).toHaveBeenCalledWith(`http://localhost:${DEFAULT_PORT}`));

Expand Down
42 changes: 42 additions & 0 deletions packages/commands/measure/src/__tests__/server/ServerApp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import supertest from "supertest";
import express from "express";
import fs from "fs";

import { createExpressApp } from "../../server/ServerApp";
import type { FlashlightData } from "../../common/types";

jest.mock("fs", () => ({
promises: {
readFile: jest.fn(),
},
}));

describe("ServerApp", () => {
let app: express.Express;
const injected: FlashlightData = { socketServerUrl: "http://localhost:9999" };

beforeAll(() => {
jest.spyOn(express, "static").mockImplementation(() => (req, res, next) => next());
});

beforeEach(() => {
(fs.promises.readFile as jest.Mock).mockResolvedValue("<html>/* %FLASHLIGHT_DATA% */</html>");

app = createExpressApp(injected);
});

describe("GET /", () => {
it("injects FlashlightData into index.html", async () => {
const response = await supertest(app).get("/");

expect(response.statusCode).toBe(200);
expect(response.text).toContain(`window.__FLASHLIGHT_DATA__ = ${JSON.stringify(injected)};`);
});
});

test("index.html contains the FlashlightData placeholder", async () => {
const fsPromises = jest.requireActual("fs").promises;
const fileContent = await fsPromises.readFile(`${__dirname}/../../webapp/index.html`, "utf8");
expect(fileContent).toContain("/* %FLASHLIGHT_DATA% */");
});
});
37 changes: 37 additions & 0 deletions packages/commands/measure/src/__tests__/webapp/socket.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { io } from "socket.io-client";

jest.mock("socket.io-client", () => {
return {
...jest.requireActual("socket.io-client"),
io: jest.fn().mockImplementation(() => {
return {
on: jest.fn(),
close: jest.fn(),
};
}),
};
});

let originalWindow: Window & typeof globalThis;

describe("socket", () => {
beforeAll(async () => {
originalWindow = global.window;

global.window = Object.create(window);
Object.defineProperty(window, "__FLASHLIGHT_DATA__", {
value: { socketServerUrl: "http://localhost:9999" },
writable: true,
});
});

afterAll(() => {
// Restore the original window object
global.window = originalWindow;
});

it("sets the expected socket server URL", async () => {
await import("../../webapp/socket");
expect(io).toHaveBeenCalledWith("http://localhost:9999");
});
});
3 changes: 3 additions & 0 deletions packages/commands/measure/src/common/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface FlashlightData {
socketServerUrl: string;
}
27 changes: 23 additions & 4 deletions packages/commands/measure/src/server/ServerApp.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import express from "express";
import http from "http";
import { promises as fs } from "fs";
import path from "path";
import cors from "cors";
import { Server } from "socket.io";
import { open } from "@perf-profiler/shell";
Expand All @@ -10,18 +12,35 @@ import { getWebAppUrl } from "./constants";
import { ServerSocketConnectionApp } from "./ServerSocketConnectionApp";
import { useInput } from "ink";
import { profiler } from "@perf-profiler/profiler";
import type { FlashlightData } from "../common/types";

const createExpressApp = () => {
const pathToDist = path.join(__dirname, "../../dist");

export const createExpressApp = (injected: FlashlightData) => {
const app = express();
app.use(cors({ origin: true }));

app.get("/", async (_, res) => {
try {
const indexHtml = path.join(pathToDist, "index.html");
let data = await fs.readFile(indexHtml, "utf8");
data = data.replace(
"/* %FLASHLIGHT_DATA% */",
`window.__FLASHLIGHT_DATA__ = ${JSON.stringify(injected)};`
);
res.send(data);
} catch (err) {
res.status(500).send("Error loading the page");
}
});

// Serve the webapp folder built by parcel
app.use(express.static(`${__dirname}/../../dist`));
app.use(express.static(pathToDist));
return app;
};

const allowOnlyOneSocketClient = (io: SocketServer, onConnect: (socket: SocketType) => void) => {
let currentSocketClient: SocketType | null = null;

io.on("connection", (socket) => {
currentSocketClient?.disconnect(true);
onConnect(socket);
Expand All @@ -48,7 +67,7 @@ export const ServerApp = ({ port }: ServerAppProps) => {
const [socket, setSocket] = useState<SocketType | null>(null);
const webAppUrl = getWebAppUrl(port);
useEffect(() => {
const app = createExpressApp();
const app = createExpressApp({ socketServerUrl: webAppUrl });

const server = http.createServer(app);
const io: SocketServer = new Server(server, {
Expand Down
9 changes: 9 additions & 0 deletions packages/commands/measure/src/webapp/globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { FlashlightData } from "../common/types";

declare global {
interface Window {
__FLASHLIGHT_DATA__: FlashlightData;
}
}

export {};
3 changes: 3 additions & 0 deletions packages/commands/measure/src/webapp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@
</style>
<div id="app"></div>
<script type="module" src="index.js"></script>
<script>
/* %FLASHLIGHT_DATA% */
</script>
</body>
</html>
5 changes: 3 additions & 2 deletions packages/commands/measure/src/webapp/socket.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { io, Socket } from "socket.io-client";
import { ServerToClientEvents, ClientToServerEvents } from "../server/socket/socketInterface";

export const socket: Socket<ServerToClientEvents, ClientToServerEvents> =
io("http://localhost:3000/");
export const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
window.__FLASHLIGHT_DATA__.socketServerUrl
);

socket.on("disconnect", () => socket.close());

0 comments on commit 66b0899

Please sign in to comment.