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

View File

@@ -0,0 +1,29 @@
import { Config } from "@jest/types";
//#region src/types.d.ts
/**
* 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.
*/
type SnapshotData = Record<string, string>;
//#endregion
//#region src/utils.d.ts
declare const SNAPSHOT_VERSION = "1";
declare const SNAPSHOT_GUIDE_LINK = "https://jestjs.io/docs/snapshot-testing";
declare const SNAPSHOT_VERSION_WARNING: string;
declare const testNameToKey: (testName: string, count: number) => string;
declare const keyToTestName: (key: string) => string;
declare const getSnapshotData: (snapshotPath: string, update: Config.SnapshotUpdateState) => {
data: SnapshotData;
dirty: boolean;
};
declare const escapeBacktickString: (str: string) => string;
declare const ensureDirectoryExists: (filePath: string) => void;
declare const normalizeNewlines: (string: string) => string;
declare const saveSnapshotFile: (snapshotData: SnapshotData, snapshotPath: string) => void;
//#endregion
export { SNAPSHOT_GUIDE_LINK, SNAPSHOT_VERSION, SNAPSHOT_VERSION_WARNING, SnapshotData, ensureDirectoryExists, escapeBacktickString, getSnapshotData, keyToTestName, normalizeNewlines, saveSnapshotFile, testNameToKey };

View File

@@ -0,0 +1,42 @@
/**
* 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 {Config} from '@jest/types';
export declare const ensureDirectoryExists: (filePath: string) => void;
export declare const escapeBacktickString: (str: string) => string;
export declare const getSnapshotData: (
snapshotPath: string,
update: Config.SnapshotUpdateState,
) => {
data: SnapshotData;
dirty: boolean;
};
export declare const keyToTestName: (key: string) => string;
export declare const normalizeNewlines: (string: string) => string;
export declare const saveSnapshotFile: (
snapshotData: SnapshotData,
snapshotPath: string,
) => void;
export declare const SNAPSHOT_GUIDE_LINK =
'https://jestjs.io/docs/snapshot-testing';
export declare const SNAPSHOT_VERSION = '1';
export declare const SNAPSHOT_VERSION_WARNING: string;
export declare type SnapshotData = Record<string, string>;
export declare const testNameToKey: (testName: string, count: number) => string;
export {};

View File

@@ -0,0 +1,221 @@
/*!
* /**
* * 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/index.ts":
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
var _utils = __webpack_require__("./src/utils.ts");
Object.keys(_utils).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _utils[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _utils[key];
}
});
});
var _types = __webpack_require__("./src/types.ts");
Object.keys(_types).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _types[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _types[key];
}
});
});
/***/ }),
/***/ "./src/types.ts":
/***/ (() => {
/***/ }),
/***/ "./src/utils.ts":
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports.testNameToKey = exports.saveSnapshotFile = exports.normalizeNewlines = exports.keyToTestName = exports.getSnapshotData = exports.escapeBacktickString = exports.ensureDirectoryExists = exports.SNAPSHOT_VERSION_WARNING = exports.SNAPSHOT_VERSION = exports.SNAPSHOT_GUIDE_LINK = void 0;
var path = _interopRequireWildcard(require("path"));
var _chalk = _interopRequireDefault(require("chalk"));
var fs = _interopRequireWildcard(require("graceful-fs"));
var _naturalCompare = _interopRequireDefault(require("natural-compare"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
var jestWriteFile = globalThis[Symbol.for('jest-native-write-file')] || fs.writeFileSync;
var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
var jestReadFile = globalThis[Symbol.for('jest-native-read-file')] || fs.readFileSync;
var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
var jestExistsFile = globalThis[Symbol.for('jest-native-exists-file')] || fs.existsSync;
/**
* 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 SNAPSHOT_VERSION = exports.SNAPSHOT_VERSION = '1';
const SNAPSHOT_VERSION_REGEXP = /^\/\/ Jest Snapshot v(.+),/;
const SNAPSHOT_GUIDE_LINK = exports.SNAPSHOT_GUIDE_LINK = 'https://jestjs.io/docs/snapshot-testing';
const SNAPSHOT_VERSION_WARNING = exports.SNAPSHOT_VERSION_WARNING = _chalk.default.yellow(`${_chalk.default.bold('Warning')}: Before you upgrade snapshots, ` + 'we recommend that you revert any local changes to tests or other code, ' + 'to ensure that you do not store invalid state.');
const writeSnapshotVersion = () => `// Jest Snapshot v${SNAPSHOT_VERSION}, ${SNAPSHOT_GUIDE_LINK}`;
const validateSnapshotVersion = snapshotContents => {
const versionTest = SNAPSHOT_VERSION_REGEXP.exec(snapshotContents);
const version = versionTest && versionTest[1];
if (!version) {
return new Error(_chalk.default.red(`${_chalk.default.bold('Outdated snapshot')}: No snapshot header found. ` + 'Jest 19 introduced versioned snapshots to ensure all developers ' + 'on a project are using the same version of Jest. ' + 'Please update all snapshots during this upgrade of Jest.\n\n') + SNAPSHOT_VERSION_WARNING);
}
if (version < SNAPSHOT_VERSION) {
return new Error(
// eslint-disable-next-line prefer-template
_chalk.default.red(`${_chalk.default.red.bold('Outdated snapshot')}: The version of the snapshot ` + 'file associated with this test is outdated. The snapshot file ' + 'version ensures that all developers on a project are using ' + 'the same version of Jest. ' + 'Please update all snapshots during this upgrade of Jest.') + '\n\n' + `Expected: v${SNAPSHOT_VERSION}\n` + `Received: v${version}\n\n` + SNAPSHOT_VERSION_WARNING);
}
if (version > SNAPSHOT_VERSION) {
return new Error(
// eslint-disable-next-line prefer-template
_chalk.default.red(`${_chalk.default.red.bold('Outdated Jest version')}: The version of this ` + 'snapshot file indicates that this project is meant to be used ' + 'with a newer version of Jest. The snapshot file version ensures ' + 'that all developers on a project are using the same version of ' + 'Jest. Please update your version of Jest and re-run the tests.') + '\n\n' + `Expected: v${SNAPSHOT_VERSION}\n` + `Received: v${version}`);
}
return null;
};
const normalizeTestNameForKey = testName => testName.replaceAll(/\r\n|\r|\n/g, match => {
switch (match) {
case '\r\n':
return '\\r\\n';
case '\r':
return '\\r';
case '\n':
return '\\n';
default:
return match;
}
});
const denormalizeTestNameFromKey = key => key.replaceAll(/\\r\\n|\\r|\\n/g, match => {
switch (match) {
case '\\r\\n':
return '\r\n';
case '\\r':
return '\r';
case '\\n':
return '\n';
default:
return match;
}
});
const testNameToKey = (testName, count) => `${normalizeTestNameForKey(testName)} ${count}`;
exports.testNameToKey = testNameToKey;
const keyToTestName = key => {
if (!/ \d+$/.test(key)) {
throw new Error('Snapshot keys must end with a number.');
}
const testNameWithoutCount = key.replace(/ \d+$/, '');
return denormalizeTestNameFromKey(testNameWithoutCount);
};
exports.keyToTestName = keyToTestName;
const getSnapshotData = (snapshotPath, update) => {
const data = Object.create(null);
let snapshotContents = '';
let dirty = false;
if (jestExistsFile(snapshotPath)) {
try {
snapshotContents = jestReadFile(snapshotPath, 'utf8');
// eslint-disable-next-line no-new-func
const populate = new Function('exports', snapshotContents);
populate(data);
} catch {}
}
const validationResult = validateSnapshotVersion(snapshotContents);
const isInvalid = snapshotContents && validationResult;
if (update === 'none' && isInvalid) {
throw validationResult;
}
if ((update === 'all' || update === 'new') && isInvalid) {
dirty = true;
}
return {
data,
dirty
};
};
exports.getSnapshotData = getSnapshotData;
const escapeBacktickString = str => str.replaceAll(/`|\\|\${/g, '\\$&');
exports.escapeBacktickString = escapeBacktickString;
const printBacktickString = str => `\`${escapeBacktickString(str)}\``;
const ensureDirectoryExists = filePath => {
try {
fs.mkdirSync(path.dirname(filePath), {
recursive: true
});
} catch {}
};
exports.ensureDirectoryExists = ensureDirectoryExists;
const normalizeNewlines = string => string.replaceAll(/\r\n|\r/g, '\n');
exports.normalizeNewlines = normalizeNewlines;
const saveSnapshotFile = (snapshotData, snapshotPath) => {
const snapshots = Object.keys(snapshotData).sort(_naturalCompare.default).map(key => `exports[${printBacktickString(key)}] = ${printBacktickString(normalizeNewlines(snapshotData[key]))};`);
ensureDirectoryExists(snapshotPath);
jestWriteFile(snapshotPath, `${writeSnapshotVersion()}\n\n${snapshots.join('\n\n')}\n`);
};
exports.saveSnapshotFile = saveSnapshotFile;
/***/ })
/******/ });
/************************************************************************/
/******/ // 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;
/******/ }
/******/
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module is referenced by other modules so it can't be inlined
/******/ var __webpack_exports__ = __webpack_require__("./src/index.ts");
/******/ module.exports = __webpack_exports__;
/******/
/******/ })()
;

View File

@@ -0,0 +1,12 @@
import cjsModule from './index.js';
export const SNAPSHOT_GUIDE_LINK = cjsModule.SNAPSHOT_GUIDE_LINK;
export const SNAPSHOT_VERSION = cjsModule.SNAPSHOT_VERSION;
export const SNAPSHOT_VERSION_WARNING = cjsModule.SNAPSHOT_VERSION_WARNING;
export const ensureDirectoryExists = cjsModule.ensureDirectoryExists;
export const escapeBacktickString = cjsModule.escapeBacktickString;
export const getSnapshotData = cjsModule.getSnapshotData;
export const keyToTestName = cjsModule.keyToTestName;
export const normalizeNewlines = cjsModule.normalizeNewlines;
export const saveSnapshotFile = cjsModule.saveSnapshotFile;
export const testNameToKey = cjsModule.testNameToKey;