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

22
frontend/node_modules/@jest/pattern/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) Meta Platforms, Inc. and affiliates.
Copyright Contributors to the Jest project.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

3
frontend/node_modules/@jest/pattern/README.md generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# @jest/pattern
`@jest/pattern` is a helper library for the jest library that implements the logic for parsing and matching patterns.

View File

@@ -0,0 +1,5 @@
{
"extends": "../../api-extractor.json",
"mainEntryPointFilePath": "/Users/cpojer/Dropbox/Projects/jest/packages/jest-pattern/build/index.d.ts",
"projectFolder": "/Users/cpojer/Dropbox/Projects/jest/packages/jest-pattern"
}

65
frontend/node_modules/@jest/pattern/build/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,65 @@
/**
* 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.
*/
export declare class TestPathPatterns {
readonly patterns: Array<string>;
constructor(patterns: Array<string>);
/**
* Return true if there are any patterns.
*/
isSet(): boolean;
/**
* Return true if the patterns are valid.
*/
isValid(): boolean;
/**
* Return a human-friendly version of the pattern regex.
*/
toPretty(): string;
/**
* Return a TestPathPatternsExecutor that can execute the patterns.
*/
toExecutor(
options: TestPathPatternsExecutorOptions,
): TestPathPatternsExecutor;
/** For jest serializers */
toJSON(): any;
}
export declare class TestPathPatternsExecutor {
readonly patterns: TestPathPatterns;
private readonly options;
constructor(
patterns: TestPathPatterns,
options: TestPathPatternsExecutorOptions,
);
private toRegex;
/**
* Return true if there are any patterns.
*/
isSet(): boolean;
/**
* Return true if the patterns are valid.
*/
isValid(): boolean;
/**
* Return true if the given ABSOLUTE path matches the patterns.
*
* Throws an error if the patterns form an invalid regex (see `validate`).
*/
isMatch(absPath: string): boolean;
/**
* Return a human-friendly version of the pattern regex.
*/
toPretty(): string;
}
export declare type TestPathPatternsExecutorOptions = {
rootDir: string;
};
export {};

214
frontend/node_modules/@jest/pattern/build/index.js generated vendored Normal file
View File

@@ -0,0 +1,214 @@
/*!
* /**
* * 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/TestPathPatterns.ts":
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports.TestPathPatternsExecutor = exports.TestPathPatterns = void 0;
function path() {
const data = _interopRequireWildcard(require("path"));
path = function () {
return data;
};
return data;
}
function _jestRegexUtil() {
const data = require("jest-regex-util");
_jestRegexUtil = function () {
return data;
};
return data;
}
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); }
/**
* 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.
*/
class TestPathPatterns {
constructor(patterns) {
this.patterns = patterns;
}
/**
* Return true if there are any patterns.
*/
isSet() {
return this.patterns.length > 0;
}
/**
* Return true if the patterns are valid.
*/
isValid() {
return this.toExecutor({
// isValid() doesn't require rootDir to be accurate, so just
// specify a dummy rootDir here
rootDir: '/'
}).isValid();
}
/**
* Return a human-friendly version of the pattern regex.
*/
toPretty() {
return this.patterns.join('|');
}
/**
* Return a TestPathPatternsExecutor that can execute the patterns.
*/
toExecutor(options) {
return new TestPathPatternsExecutor(this, options);
}
/** For jest serializers */
toJSON() {
return {
patterns: this.patterns,
type: 'TestPathPatterns'
};
}
}
exports.TestPathPatterns = TestPathPatterns;
class TestPathPatternsExecutor {
constructor(patterns, options) {
this.patterns = patterns;
this.options = options;
}
toRegex(s) {
return new RegExp(s, 'i');
}
/**
* Return true if there are any patterns.
*/
isSet() {
return this.patterns.isSet();
}
/**
* Return true if the patterns are valid.
*/
isValid() {
try {
for (const p of this.patterns.patterns) {
this.toRegex(p);
}
return true;
} catch {
return false;
}
}
/**
* Return true if the given ABSOLUTE path matches the patterns.
*
* Throws an error if the patterns form an invalid regex (see `validate`).
*/
isMatch(absPath) {
const relPath = path().relative(this.options.rootDir || '/', absPath);
if (this.patterns.patterns.length === 0) {
return true;
}
for (const p of this.patterns.patterns) {
const pathToTest = path().isAbsolute(p) ? absPath : relPath;
// special case: ./foo.spec.js (and .\foo.spec.js on Windows) should
// match /^foo.spec.js/ after stripping root dir
let regexStr = p.replace(/^\.\//, '^');
if (path().sep === '\\') {
regexStr = regexStr.replace(/^\.\\/, '^');
}
regexStr = (0, _jestRegexUtil().replacePathSepForRegex)(regexStr);
if (this.toRegex(regexStr).test(pathToTest)) {
return true;
}
if (this.toRegex(regexStr).test(absPath)) {
return true;
}
}
return false;
}
/**
* Return a human-friendly version of the pattern regex.
*/
toPretty() {
return this.patterns.toPretty();
}
}
exports.TestPathPatternsExecutor = TestPathPatternsExecutor;
/***/ })
/******/ });
/************************************************************************/
/******/ // 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
}));
Object.defineProperty(exports, "TestPathPatterns", ({
enumerable: true,
get: function () {
return _TestPathPatterns.TestPathPatterns;
}
}));
Object.defineProperty(exports, "TestPathPatternsExecutor", ({
enumerable: true,
get: function () {
return _TestPathPatterns.TestPathPatternsExecutor;
}
}));
var _TestPathPatterns = __webpack_require__("./src/TestPathPatterns.ts");
})();
module.exports = __webpack_exports__;
/******/ })()
;

4
frontend/node_modules/@jest/pattern/build/index.mjs generated vendored Normal file
View File

@@ -0,0 +1,4 @@
import cjsModule from './index.js';
export const TestPathPatterns = cjsModule.TestPathPatterns;
export const TestPathPatternsExecutor = cjsModule.TestPathPatternsExecutor;

32
frontend/node_modules/@jest/pattern/package.json generated vendored Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "@jest/pattern",
"version": "30.0.1",
"repository": {
"type": "git",
"url": "https://github.com/jestjs/jest.git",
"directory": "packages/jest-pattern"
},
"license": "MIT",
"main": "./build/index.js",
"types": "./build/index.d.ts",
"exports": {
".": {
"types": "./build/index.d.ts",
"require": "./build/index.js",
"import": "./build/index.mjs",
"default": "./build/index.js"
},
"./package.json": "./package.json"
},
"dependencies": {
"@types/node": "*",
"jest-regex-util": "30.0.1"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
"publishConfig": {
"access": "public"
},
"gitHead": "5ce865b4060189fe74cd486544816c079194a0f7"
}

View File

@@ -0,0 +1,132 @@
/**
* 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 * as path from 'path';
import {replacePathSepForRegex} from 'jest-regex-util';
export class TestPathPatterns {
constructor(readonly patterns: Array<string>) {}
/**
* Return true if there are any patterns.
*/
isSet(): boolean {
return this.patterns.length > 0;
}
/**
* Return true if the patterns are valid.
*/
isValid(): boolean {
return this.toExecutor({
// isValid() doesn't require rootDir to be accurate, so just
// specify a dummy rootDir here
rootDir: '/',
}).isValid();
}
/**
* Return a human-friendly version of the pattern regex.
*/
toPretty(): string {
return this.patterns.join('|');
}
/**
* Return a TestPathPatternsExecutor that can execute the patterns.
*/
toExecutor(
options: TestPathPatternsExecutorOptions,
): TestPathPatternsExecutor {
return new TestPathPatternsExecutor(this, options);
}
/** For jest serializers */
toJSON(): any {
return {
patterns: this.patterns,
type: 'TestPathPatterns',
};
}
}
export type TestPathPatternsExecutorOptions = {
rootDir: string;
};
export class TestPathPatternsExecutor {
constructor(
readonly patterns: TestPathPatterns,
private readonly options: TestPathPatternsExecutorOptions,
) {}
private toRegex(s: string): RegExp {
return new RegExp(s, 'i');
}
/**
* Return true if there are any patterns.
*/
isSet(): boolean {
return this.patterns.isSet();
}
/**
* Return true if the patterns are valid.
*/
isValid(): boolean {
try {
for (const p of this.patterns.patterns) {
this.toRegex(p);
}
return true;
} catch {
return false;
}
}
/**
* Return true if the given ABSOLUTE path matches the patterns.
*
* Throws an error if the patterns form an invalid regex (see `validate`).
*/
isMatch(absPath: string): boolean {
const relPath = path.relative(this.options.rootDir || '/', absPath);
if (this.patterns.patterns.length === 0) {
return true;
}
for (const p of this.patterns.patterns) {
const pathToTest = path.isAbsolute(p) ? absPath : relPath;
// special case: ./foo.spec.js (and .\foo.spec.js on Windows) should
// match /^foo.spec.js/ after stripping root dir
let regexStr = p.replace(/^\.\//, '^');
if (path.sep === '\\') {
regexStr = regexStr.replace(/^\.\\/, '^');
}
regexStr = replacePathSepForRegex(regexStr);
if (this.toRegex(regexStr).test(pathToTest)) {
return true;
}
if (this.toRegex(regexStr).test(absPath)) {
return true;
}
}
return false;
}
/**
* Return a human-friendly version of the pattern regex.
*/
toPretty(): string {
return this.patterns.toPretty();
}
}

View File

@@ -0,0 +1,259 @@
/**
* 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 * as path from 'path';
import {
TestPathPatterns,
TestPathPatternsExecutor,
type TestPathPatternsExecutorOptions,
} from '../TestPathPatterns';
const mockSep: jest.Mock<() => string> = jest.fn();
const mockIsAbsolute: jest.Mock<(p: string) => boolean> = jest.fn();
const mockRelative: jest.Mock<(from: string, to: string) => string> = jest.fn();
jest.mock('path', () => {
const actualPath = jest.requireActual('path');
return {
...actualPath,
isAbsolute(p) {
return mockIsAbsolute(p) || actualPath.isAbsolute(p);
},
relative(from, to) {
return mockRelative(from, to) || actualPath.relative(from, to);
},
get sep() {
return mockSep() || actualPath.sep;
},
} as typeof path;
});
const forcePosix = () => {
mockSep.mockReturnValue(path.posix.sep);
mockIsAbsolute.mockImplementation(path.posix.isAbsolute);
mockRelative.mockImplementation(path.posix.relative);
};
const forceWindows = () => {
mockSep.mockReturnValue(path.win32.sep);
mockIsAbsolute.mockImplementation(path.win32.isAbsolute);
mockRelative.mockImplementation(path.win32.relative);
};
beforeEach(() => {
jest.resetAllMocks();
forcePosix();
});
const config = {rootDir: ''};
interface TestPathPatternsLike {
isSet(): boolean;
isValid(): boolean;
toPretty(): string;
}
const testPathPatternsLikeTests = (
makePatterns: (
patterns: Array<string>,
options: TestPathPatternsExecutorOptions,
) => TestPathPatternsLike,
) => {
describe('isSet', () => {
it('returns false if no patterns specified', () => {
const testPathPatterns = makePatterns([], config);
expect(testPathPatterns.isSet()).toBe(false);
});
it('returns true if patterns specified', () => {
const testPathPatterns = makePatterns(['a'], config);
expect(testPathPatterns.isSet()).toBe(true);
});
});
describe('isValid', () => {
it('succeeds for empty patterns', () => {
const testPathPatterns = makePatterns([], config);
expect(testPathPatterns.isValid()).toBe(true);
});
it('succeeds for valid patterns', () => {
const testPathPatterns = makePatterns(['abc+', 'z.*'], config);
expect(testPathPatterns.isValid()).toBe(true);
});
it('fails for at least one invalid pattern', () => {
const testPathPatterns = makePatterns(['abc+', '(', 'z.*'], config);
expect(testPathPatterns.isValid()).toBe(false);
});
});
describe('toPretty', () => {
it('renders a human-readable string', () => {
const testPathPatterns = makePatterns(['a/b', 'c/d'], config);
expect(testPathPatterns.toPretty()).toMatchSnapshot();
});
});
};
describe('TestPathPatterns', () => {
testPathPatternsLikeTests(
(patterns: Array<string>, _: TestPathPatternsExecutorOptions) =>
new TestPathPatterns(patterns),
);
});
describe('TestPathPatternsExecutor', () => {
const makeExecutor = (
patterns: Array<string>,
options: TestPathPatternsExecutorOptions,
) => new TestPathPatternsExecutor(new TestPathPatterns(patterns), options);
testPathPatternsLikeTests(makeExecutor);
describe('isMatch', () => {
it('returns true with no patterns', () => {
const testPathPatterns = makeExecutor([], config);
expect(testPathPatterns.isMatch('/a/b')).toBe(true);
});
it('returns true for same path', () => {
const testPathPatterns = makeExecutor(['/a/b'], config);
expect(testPathPatterns.isMatch('/a/b')).toBe(true);
});
it('returns true for same path with case insensitive', () => {
const testPathPatternsUpper = makeExecutor(['/A/B'], config);
expect(testPathPatternsUpper.isMatch('/a/b')).toBe(true);
expect(testPathPatternsUpper.isMatch('/A/B')).toBe(true);
const testPathPatternsLower = makeExecutor(['/a/b'], config);
expect(testPathPatternsLower.isMatch('/A/B')).toBe(true);
expect(testPathPatternsLower.isMatch('/a/b')).toBe(true);
});
it('returns true for contained path', () => {
const testPathPatterns = makeExecutor(['b/c'], config);
expect(testPathPatterns.isMatch('/a/b/c/d')).toBe(true);
});
it('returns true for explicit relative path', () => {
const testPathPatterns = makeExecutor(['./b/c'], {
rootDir: '/a',
});
expect(testPathPatterns.isMatch('/a/b/c')).toBe(true);
});
it('returns true for explicit relative path for Windows with ./', () => {
forceWindows();
const testPathPatterns = makeExecutor(['./b/c'], {
rootDir: 'C:\\a',
});
expect(testPathPatterns.isMatch('C:\\a\\b\\c')).toBe(true);
});
it('returns true for explicit relative path for Windows with .\\', () => {
forceWindows();
const testPathPatterns = makeExecutor(['.\\b\\c'], {
rootDir: 'C:\\a',
});
expect(testPathPatterns.isMatch('C:\\a\\b\\c')).toBe(true);
});
it('returns true for partial file match', () => {
const testPathPatterns = makeExecutor(['aaa'], config);
expect(testPathPatterns.isMatch('/foo/..aaa..')).toBe(true);
expect(testPathPatterns.isMatch('/foo/..aaa')).toBe(true);
expect(testPathPatterns.isMatch('/foo/aaa..')).toBe(true);
});
it('returns true for path suffix', () => {
const testPathPatterns = makeExecutor(['c/d'], config);
expect(testPathPatterns.isMatch('/a/b/c/d')).toBe(true);
});
it('returns true if regex matches', () => {
const testPathPatterns = makeExecutor(['ab*c?'], config);
expect(testPathPatterns.isMatch('/foo/a')).toBe(true);
expect(testPathPatterns.isMatch('/foo/ab')).toBe(true);
expect(testPathPatterns.isMatch('/foo/abb')).toBe(true);
expect(testPathPatterns.isMatch('/foo/ac')).toBe(true);
expect(testPathPatterns.isMatch('/foo/abc')).toBe(true);
expect(testPathPatterns.isMatch('/foo/abbc')).toBe(true);
expect(testPathPatterns.isMatch('/foo/bc')).toBe(false);
});
it('returns true only if matches relative path', () => {
const rootDir = '/home/myuser/';
const testPathPatterns = makeExecutor(['home'], {
rootDir,
});
expect(
testPathPatterns.isMatch(
path.relative(rootDir, '/home/myuser/LoginPage.js'),
),
).toBe(false);
expect(
testPathPatterns.isMatch(
path.relative(rootDir, '/home/myuser/HomePage.js'),
),
).toBe(true);
});
it('matches absolute paths regardless of rootDir', () => {
forcePosix();
const testPathPatterns = makeExecutor(['/a/b'], {
rootDir: '/foo/bar',
});
expect(testPathPatterns.isMatch('/a/b')).toBe(true);
});
it('matches absolute paths for Windows', () => {
forceWindows();
const testPathPatterns = makeExecutor(['C:\\a\\b'], {
rootDir: 'C:\\foo\\bar',
});
expect(testPathPatterns.isMatch('C:\\a\\b')).toBe(true);
});
it('returns true if match any paths', () => {
const testPathPatterns = makeExecutor(['a/b', 'c/d'], config);
expect(testPathPatterns.isMatch('/foo/a/b')).toBe(true);
expect(testPathPatterns.isMatch('/foo/c/d')).toBe(true);
expect(testPathPatterns.isMatch('/foo/a')).toBe(false);
expect(testPathPatterns.isMatch('/foo/b/c')).toBe(false);
});
it('does not normalize Windows paths on POSIX', () => {
forcePosix();
const testPathPatterns = makeExecutor(['a\\z', 'a\\\\z'], config);
expect(testPathPatterns.isMatch('/foo/a/z')).toBe(false);
});
it('normalizes paths for Windows', () => {
forceWindows();
const testPathPatterns = makeExecutor(['a/b'], config);
expect(testPathPatterns.isMatch('C:\\foo\\a\\b')).toBe(true);
});
it('matches absolute path with absPath', () => {
const pattern = '^/home/app/';
const rootDir = '/home/app';
const absolutePath = '/home/app/packages/';
const testPathPatterns = makeExecutor([pattern], {
rootDir,
});
const relativePath = path.relative(rootDir, absolutePath);
expect(testPathPatterns.isMatch(relativePath)).toBe(false);
expect(testPathPatterns.isMatch(absolutePath)).toBe(true);
});
});
});

View File

@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`TestPathPatterns toPretty renders a human-readable string 1`] = `"a/b|c/d"`;
exports[`TestPathPatternsExecutor toPretty renders a human-readable string 1`] = `"a/b|c/d"`;

12
frontend/node_modules/@jest/pattern/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,12 @@
/**
* 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.
*/
export {
TestPathPatterns,
TestPathPatternsExecutor,
type TestPathPatternsExecutorOptions,
} from './TestPathPatterns';

10
frontend/node_modules/@jest/pattern/tsconfig.json generated vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "build"
},
"include": ["./src/**/*"],
"exclude": ["./**/__tests__/**/*"],
"references": [{"path": "../jest-regex-util"}]
}