Set up comprehensive frontend testing infrastructure

- Install Jest for unit testing with React Testing Library
- Install Playwright for end-to-end testing
- Configure Jest with proper TypeScript support and module mapping
- Create test setup files and utilities for both unit and e2e tests

Components:
* Jest configuration with coverage thresholds
* Playwright configuration with browser automation
* Unit tests for LoginForm, AuthContext, and useSocketIO hook
* E2E tests for authentication, dashboard, and agents workflows
* GitHub Actions workflow for automated testing
* Mock data and API utilities for consistent testing
* Test documentation with best practices

Testing features:
- Unit tests with 70% coverage threshold
- E2E tests with API mocking and user journey testing
- CI/CD integration for automated test runs
- Cross-browser testing support with Playwright
- Authentication system testing end-to-end

🚀 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-07-11 14:06:34 +10:00
parent c6d69695a8
commit aacb45156b
6109 changed files with 777927 additions and 1 deletions

76
frontend/node_modules/jest-circus/build/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {Circus, Global as Global_2} from '@jest/types';
export declare const addEventHandler: (handler: Circus.EventHandler) => void;
export declare const afterAll: THook;
export declare const afterEach: THook;
export declare const beforeAll: THook;
export declare const beforeEach: THook;
declare const _default: {
afterAll: THook;
afterEach: THook;
beforeAll: THook;
beforeEach: THook;
describe: {
(blockName: Circus.BlockNameLike, blockFn: Circus.BlockFn): void;
each: Global_2.EachTestFn<any>;
only: {
(blockName: Circus.BlockNameLike, blockFn: Circus.BlockFn): void;
each: Global_2.EachTestFn<any>;
};
skip: {
(blockName: Circus.BlockNameLike, blockFn: Circus.BlockFn): void;
each: Global_2.EachTestFn<any>;
};
};
it: Global_2.It;
test: Global_2.It;
};
export default _default;
export declare const describe: {
(blockName: Circus.BlockNameLike, blockFn: Circus.BlockFn): void;
each: Global_2.EachTestFn<any>;
only: {
(blockName: Circus.BlockNameLike, blockFn: Circus.BlockFn): void;
each: Global_2.EachTestFn<any>;
};
skip: {
(blockName: Circus.BlockNameLike, blockFn: Circus.BlockFn): void;
each: Global_2.EachTestFn<any>;
};
};
declare type Event_2 = Circus.Event;
export {Event_2 as Event};
export declare const getState: () => Circus.State;
export declare const it: Global_2.It;
export declare const removeEventHandler: (handler: Circus.EventHandler) => void;
export declare const resetState: () => void;
export declare const run: () => Promise<Circus.RunResult>;
export declare const setState: (state: Circus.State) => Circus.State;
export declare type State = Circus.State;
export declare const test: Global_2.It;
declare type THook = (fn: Circus.HookFn, timeout?: number) => void;
export {};

1672
frontend/node_modules/jest-circus/build/index.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

16
frontend/node_modules/jest-circus/build/index.mjs generated vendored Normal file
View File

@@ -0,0 +1,16 @@
import cjsModule from './index.js';
export const addEventHandler = cjsModule.addEventHandler;
export const afterAll = cjsModule.afterAll;
export const afterEach = cjsModule.afterEach;
export const beforeAll = cjsModule.beforeAll;
export const beforeEach = cjsModule.beforeEach;
export const describe = cjsModule.describe;
export const getState = cjsModule.getState;
export const it = cjsModule.it;
export const removeEventHandler = cjsModule.removeEventHandler;
export const resetState = cjsModule.resetState;
export const run = cjsModule.run;
export const setState = cjsModule.setState;
export const test = cjsModule.test;
export default cjsModule.default;

View File

@@ -0,0 +1,51 @@
import { JestExpect } from "@jest/expect";
import { TestFileEvent, TestResult } from "@jest/test-result";
import { SnapshotState } from "jest-snapshot";
import * as Process from "process";
import { JestEnvironment } from "@jest/environment";
import { Circus, Config, Global } from "@jest/types";
import Runtime from "jest-runtime";
//#region src/legacy-code-todo-rewrite/jestAdapterInit.d.ts
interface RuntimeGlobals extends Global.TestFrameworkGlobals {
expect: JestExpect;
}
declare const initialize: ({
config,
environment,
runtime,
globalConfig,
localRequire,
parentProcess,
sendMessageToJest,
setGlobalsForRuntime,
testPath
}: {
config: Config.ProjectConfig;
environment: JestEnvironment;
runtime: Runtime;
globalConfig: Config.GlobalConfig;
localRequire: <T = unknown>(path: string) => T;
testPath: string;
parentProcess: typeof Process;
sendMessageToJest?: TestFileEvent;
setGlobalsForRuntime: (globals: RuntimeGlobals) => void;
}) => Promise<{
globals: Global.TestFrameworkGlobals;
snapshotState: SnapshotState;
}>;
declare const runAndTransformResultsToJestFormat: ({
config,
globalConfig,
setupAfterEnvPerfStats,
testPath
}: {
config: Config.ProjectConfig;
globalConfig: Config.GlobalConfig;
testPath: string;
setupAfterEnvPerfStats: Config.SetupAfterEnvPerfStats;
}) => Promise<TestResult>;
declare const eventHandler: (event: Circus.Event) => Promise<void>;
//#endregion
export { eventHandler, initialize, runAndTransformResultsToJestFormat };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

10
frontend/node_modules/jest-circus/build/runner.d.mts generated vendored Normal file
View File

@@ -0,0 +1,10 @@
import { JestEnvironment } from "@jest/environment";
import { TestFileEvent, TestResult } from "@jest/test-result";
import { Config } from "@jest/types";
import Runtime from "jest-runtime";
//#region src/legacy-code-todo-rewrite/jestAdapter.d.ts
declare const jestAdapter: (globalConfig: Config.GlobalConfig, config: Config.ProjectConfig, environment: JestEnvironment, runtime: Runtime, testPath: string, sendMessageToJest?: TestFileEvent) => Promise<TestResult>;
//#endregion
export { jestAdapter as default };

200
frontend/node_modules/jest-circus/build/runner.js generated vendored Normal file
View File

@@ -0,0 +1,200 @@
/*!
* /**
* * Copyright (c) Meta Platforms, Inc. and affiliates.
* *
* * This source code is licensed under the MIT license found in the
* * LICENSE file in the root directory of this source tree.
* * /
*/
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ "./src/legacy-code-todo-rewrite/jestAdapter.ts":
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports["default"] = void 0;
var _jestUtil = require("jest-util");
var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
var jestNow = globalThis[Symbol.for('jest-native-now')] || globalThis.Date.now;
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const FRAMEWORK_INITIALIZER = require.resolve('./jestAdapterInit');
const jestAdapter = async (globalConfig, config, environment, runtime, testPath, sendMessageToJest) => {
const {
initialize,
runAndTransformResultsToJestFormat
} = runtime.requireInternalModule(FRAMEWORK_INITIALIZER);
const {
globals,
snapshotState
} = await initialize({
config,
environment,
globalConfig,
localRequire: runtime.requireModule.bind(runtime),
parentProcess: process,
runtime,
sendMessageToJest,
setGlobalsForRuntime: runtime.setGlobalsForRuntime.bind(runtime),
testPath
});
if (config.fakeTimers.enableGlobally) {
if (config.fakeTimers.legacyFakeTimers) {
// during setup, this cannot be null (and it's fine to explode if it is)
environment.fakeTimers.useFakeTimers();
} else {
environment.fakeTimersModern.useFakeTimers();
}
}
globals.beforeEach(() => {
if (config.resetModules) {
runtime.resetModules();
}
if (config.clearMocks) {
runtime.clearAllMocks();
}
if (config.resetMocks) {
runtime.resetAllMocks();
if (config.fakeTimers.enableGlobally && config.fakeTimers.legacyFakeTimers) {
// during setup, this cannot be null (and it's fine to explode if it is)
environment.fakeTimers.useFakeTimers();
}
}
if (config.restoreMocks) {
runtime.restoreAllMocks();
}
});
const setupAfterEnvStart = jestNow();
for (const path of config.setupFilesAfterEnv) {
const esm = runtime.unstable_shouldLoadAsEsm(path);
if (esm) {
await runtime.unstable_importModule(path);
} else {
const setupFile = runtime.requireModule(path);
if (typeof setupFile === 'function') {
await setupFile();
}
}
}
const setupAfterEnvEnd = jestNow();
const esm = runtime.unstable_shouldLoadAsEsm(testPath);
if (esm) {
await runtime.unstable_importModule(testPath);
} else {
runtime.requireModule(testPath);
}
const setupAfterEnvPerfStats = {
setupAfterEnvEnd,
setupAfterEnvStart
};
const results = await runAndTransformResultsToJestFormat({
config,
globalConfig,
setupAfterEnvPerfStats,
testPath
});
_addSnapshotData(results, snapshotState);
// We need to copy the results object to ensure we don't leaks the prototypes
// from the VM. Jasmine creates the result objects in the parent process, we
// should consider doing that for circus as well.
return (0, _jestUtil.deepCyclicCopy)(results, {
keepPrototype: false
});
};
const _addSnapshotData = (results, snapshotState) => {
for (const {
fullName,
status,
failing
} of results.testResults) {
if (status === 'pending' || status === 'failed' || failing && status === 'passed') {
// If test is skipped or failed, we don't want to mark
// its snapshots as obsolete.
// When tests called with test.failing pass, they've thrown an exception,
// so maintain any snapshots after the error.
snapshotState.markSnapshotsAsCheckedForTest(fullName);
}
}
const uncheckedCount = snapshotState.getUncheckedCount();
const uncheckedKeys = snapshotState.getUncheckedKeys();
if (uncheckedCount) {
snapshotState.removeUncheckedKeys();
}
const status = snapshotState.save();
results.snapshot.fileDeleted = status.deleted;
results.snapshot.added = snapshotState.added;
results.snapshot.matched = snapshotState.matched;
results.snapshot.unmatched = snapshotState.unmatched;
results.snapshot.updated = snapshotState.updated;
results.snapshot.unchecked = status.deleted ? 0 : uncheckedCount;
// Copy the array to prevent memory leaks
results.snapshot.uncheckedKeys = [...uncheckedKeys];
};
var _default = exports["default"] = jestAdapter;
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry needs to be wrapped in an IIFE because it uses a non-standard name for the exports (exports).
(() => {
var exports = __webpack_exports__;
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports["default"] = void 0;
var _jestAdapter = _interopRequireDefault(__webpack_require__("./src/legacy-code-todo-rewrite/jestAdapter.ts"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// Allow people to use `jest-circus/runner` as a runner.
var _default = exports["default"] = _jestAdapter.default;
})();
module.exports = __webpack_exports__;
/******/ })()
;

81
frontend/node_modules/jest-circus/build/runner.mjs generated vendored Normal file
View File

@@ -0,0 +1,81 @@
import { createRequire } from "node:module";
import { deepCyclicCopy } from "jest-util";
//#region rolldown:runtime
var __require = /* @__PURE__ */ createRequire(import.meta.url);
//#endregion
//#region src/legacy-code-todo-rewrite/jestAdapter.ts
const FRAMEWORK_INITIALIZER = __require.resolve("./jestAdapterInit");
const jestAdapter = async (globalConfig, config, environment, runtime, testPath, sendMessageToJest) => {
const { initialize, runAndTransformResultsToJestFormat } = runtime.requireInternalModule(FRAMEWORK_INITIALIZER);
const { globals, snapshotState } = await initialize({
config,
environment,
globalConfig,
localRequire: runtime.requireModule.bind(runtime),
parentProcess: process,
runtime,
sendMessageToJest,
setGlobalsForRuntime: runtime.setGlobalsForRuntime.bind(runtime),
testPath
});
if (config.fakeTimers.enableGlobally) if (config.fakeTimers.legacyFakeTimers) environment.fakeTimers.useFakeTimers();
else environment.fakeTimersModern.useFakeTimers();
globals.beforeEach(() => {
if (config.resetModules) runtime.resetModules();
if (config.clearMocks) runtime.clearAllMocks();
if (config.resetMocks) {
runtime.resetAllMocks();
if (config.fakeTimers.enableGlobally && config.fakeTimers.legacyFakeTimers) environment.fakeTimers.useFakeTimers();
}
if (config.restoreMocks) runtime.restoreAllMocks();
});
const setupAfterEnvStart = Date.now();
for (const path of config.setupFilesAfterEnv) {
const esm$1 = runtime.unstable_shouldLoadAsEsm(path);
if (esm$1) await runtime.unstable_importModule(path);
else {
const setupFile = runtime.requireModule(path);
if (typeof setupFile === "function") await setupFile();
}
}
const setupAfterEnvEnd = Date.now();
const esm = runtime.unstable_shouldLoadAsEsm(testPath);
if (esm) await runtime.unstable_importModule(testPath);
else runtime.requireModule(testPath);
const setupAfterEnvPerfStats = {
setupAfterEnvEnd,
setupAfterEnvStart
};
const results = await runAndTransformResultsToJestFormat({
config,
globalConfig,
setupAfterEnvPerfStats,
testPath
});
_addSnapshotData(results, snapshotState);
return deepCyclicCopy(results, { keepPrototype: false });
};
const _addSnapshotData = (results, snapshotState) => {
for (const { fullName, status: status$1, failing } of results.testResults) if (status$1 === "pending" || status$1 === "failed" || failing && status$1 === "passed") snapshotState.markSnapshotsAsCheckedForTest(fullName);
const uncheckedCount = snapshotState.getUncheckedCount();
const uncheckedKeys = snapshotState.getUncheckedKeys();
if (uncheckedCount) snapshotState.removeUncheckedKeys();
const status = snapshotState.save();
results.snapshot.fileDeleted = status.deleted;
results.snapshot.added = snapshotState.added;
results.snapshot.matched = snapshotState.matched;
results.snapshot.unmatched = snapshotState.unmatched;
results.snapshot.updated = snapshotState.updated;
results.snapshot.unchecked = status.deleted ? 0 : uncheckedCount;
results.snapshot.uncheckedKeys = [...uncheckedKeys];
};
var jestAdapter_default = jestAdapter;
//#endregion
//#region src/runner.ts
var runner_default = jestAdapter_default;
//#endregion
export { runner_default as default };