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:
31
frontend/node_modules/jest-environment-node/build/index.d.mts
generated
vendored
Normal file
31
frontend/node_modules/jest-environment-node/build/index.d.mts
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Context } from "vm";
|
||||
import { LegacyFakeTimers, ModernFakeTimers } from "@jest/fake-timers";
|
||||
import { ModuleMocker } from "jest-mock";
|
||||
import { EnvironmentContext, JestEnvironment, JestEnvironmentConfig } from "@jest/environment";
|
||||
import { Global } from "@jest/types";
|
||||
|
||||
//#region src/index.d.ts
|
||||
|
||||
type Timer = {
|
||||
id: number;
|
||||
ref: () => Timer;
|
||||
unref: () => Timer;
|
||||
};
|
||||
declare class NodeEnvironment implements JestEnvironment<Timer> {
|
||||
context: Context | null;
|
||||
fakeTimers: LegacyFakeTimers<Timer> | null;
|
||||
fakeTimersModern: ModernFakeTimers | null;
|
||||
global: Global.Global;
|
||||
moduleMocker: ModuleMocker | null;
|
||||
customExportConditions: string[];
|
||||
private readonly _configuredExportConditions?;
|
||||
private _globalProxy;
|
||||
constructor(config: JestEnvironmentConfig, _context: EnvironmentContext);
|
||||
setup(): Promise<void>;
|
||||
teardown(): Promise<void>;
|
||||
exportConditions(): Array<string>;
|
||||
getVmContext(): Context | null;
|
||||
}
|
||||
declare const TestEnvironment: typeof NodeEnvironment;
|
||||
//#endregion
|
||||
export { TestEnvironment, NodeEnvironment as default };
|
||||
43
frontend/node_modules/jest-environment-node/build/index.d.ts
generated
vendored
Normal file
43
frontend/node_modules/jest-environment-node/build/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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 {Context} from 'vm';
|
||||
import {
|
||||
EnvironmentContext,
|
||||
JestEnvironment,
|
||||
JestEnvironmentConfig,
|
||||
} from '@jest/environment';
|
||||
import {LegacyFakeTimers, ModernFakeTimers} from '@jest/fake-timers';
|
||||
import {Global as Global_2} from '@jest/types';
|
||||
import {ModuleMocker} from 'jest-mock';
|
||||
|
||||
declare class NodeEnvironment implements JestEnvironment<Timer> {
|
||||
context: Context | null;
|
||||
fakeTimers: LegacyFakeTimers<Timer> | null;
|
||||
fakeTimersModern: ModernFakeTimers | null;
|
||||
global: Global_2.Global;
|
||||
moduleMocker: ModuleMocker | null;
|
||||
customExportConditions: Array<string>;
|
||||
private readonly _configuredExportConditions?;
|
||||
private _globalProxy;
|
||||
constructor(config: JestEnvironmentConfig, _context: EnvironmentContext);
|
||||
setup(): Promise<void>;
|
||||
teardown(): Promise<void>;
|
||||
exportConditions(): Array<string>;
|
||||
getVmContext(): Context | null;
|
||||
}
|
||||
export default NodeEnvironment;
|
||||
|
||||
export declare const TestEnvironment: typeof NodeEnvironment;
|
||||
|
||||
declare type Timer = {
|
||||
id: number;
|
||||
ref: () => Timer;
|
||||
unref: () => Timer;
|
||||
};
|
||||
|
||||
export {};
|
||||
340
frontend/node_modules/jest-environment-node/build/index.js
generated
vendored
Normal file
340
frontend/node_modules/jest-environment-node/build/index.js
generated
vendored
Normal file
@@ -0,0 +1,340 @@
|
||||
/*!
|
||||
* /**
|
||||
* * 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_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"] = exports.TestEnvironment = void 0;
|
||||
function _vm() {
|
||||
const data = require("vm");
|
||||
_vm = function () {
|
||||
return data;
|
||||
};
|
||||
return data;
|
||||
}
|
||||
function _fakeTimers() {
|
||||
const data = require("@jest/fake-timers");
|
||||
_fakeTimers = function () {
|
||||
return data;
|
||||
};
|
||||
return data;
|
||||
}
|
||||
function _jestMock() {
|
||||
const data = require("jest-mock");
|
||||
_jestMock = function () {
|
||||
return data;
|
||||
};
|
||||
return data;
|
||||
}
|
||||
function _jestUtil() {
|
||||
const data = require("jest-util");
|
||||
_jestUtil = function () {
|
||||
return data;
|
||||
};
|
||||
return data;
|
||||
}
|
||||
function _jestValidate() {
|
||||
const data = require("jest-validate");
|
||||
_jestValidate = function () {
|
||||
return data;
|
||||
};
|
||||
return data;
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// some globals we do not want, either because deprecated or we set it ourselves
|
||||
const denyList = new Set(['GLOBAL', 'root', 'global', 'globalThis', 'Buffer', 'ArrayBuffer', 'Uint8Array',
|
||||
// if env is loaded within a jest test
|
||||
'jest-symbol-do-not-touch']);
|
||||
const nodeGlobals = new Map(Object.getOwnPropertyNames(globalThis).filter(global => !denyList.has(global)).map(nodeGlobalsKey => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(globalThis, nodeGlobalsKey);
|
||||
if (!descriptor) {
|
||||
throw new Error(`No property descriptor for ${nodeGlobalsKey}, this is a bug in Jest.`);
|
||||
}
|
||||
return [nodeGlobalsKey, descriptor];
|
||||
}));
|
||||
function isString(value) {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
const timerIdToRef = id => ({
|
||||
id,
|
||||
ref() {
|
||||
return this;
|
||||
},
|
||||
unref() {
|
||||
return this;
|
||||
}
|
||||
});
|
||||
const timerRefToId = timer => timer?.id;
|
||||
class NodeEnvironment {
|
||||
context;
|
||||
fakeTimers;
|
||||
fakeTimersModern;
|
||||
global;
|
||||
moduleMocker;
|
||||
customExportConditions = ['node', 'node-addons'];
|
||||
_configuredExportConditions;
|
||||
_globalProxy;
|
||||
|
||||
// while `context` is unused, it should always be passed
|
||||
constructor(config, _context) {
|
||||
const {
|
||||
projectConfig
|
||||
} = config;
|
||||
const globalsCleanupMode = readGlobalsCleanupConfig(projectConfig);
|
||||
(0, _jestUtil().initializeGarbageCollectionUtils)(globalThis, globalsCleanupMode);
|
||||
this._globalProxy = new GlobalProxy();
|
||||
this.context = (0, _vm().createContext)(this._globalProxy.proxy());
|
||||
const global = (0, _vm().runInContext)('this', Object.assign(this.context, projectConfig.testEnvironmentOptions));
|
||||
this.global = global;
|
||||
const contextGlobals = new Set(Object.getOwnPropertyNames(global));
|
||||
for (const [nodeGlobalsKey, descriptor] of nodeGlobals) {
|
||||
(0, _jestUtil().protectProperties)(globalThis[nodeGlobalsKey]);
|
||||
if (!contextGlobals.has(nodeGlobalsKey)) {
|
||||
if (descriptor.configurable) {
|
||||
Object.defineProperty(global, nodeGlobalsKey, {
|
||||
configurable: true,
|
||||
enumerable: descriptor.enumerable,
|
||||
get() {
|
||||
const value = globalThis[nodeGlobalsKey];
|
||||
|
||||
// override lazy getter
|
||||
Object.defineProperty(global, nodeGlobalsKey, {
|
||||
configurable: true,
|
||||
enumerable: descriptor.enumerable,
|
||||
value,
|
||||
writable: true
|
||||
});
|
||||
return value;
|
||||
},
|
||||
set(value) {
|
||||
// override lazy getter
|
||||
Object.defineProperty(global, nodeGlobalsKey, {
|
||||
configurable: true,
|
||||
enumerable: descriptor.enumerable,
|
||||
value,
|
||||
writable: true
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if ('value' in descriptor) {
|
||||
Object.defineProperty(global, nodeGlobalsKey, {
|
||||
configurable: false,
|
||||
enumerable: descriptor.enumerable,
|
||||
value: descriptor.value,
|
||||
writable: descriptor.writable
|
||||
});
|
||||
} else {
|
||||
Object.defineProperty(global, nodeGlobalsKey, {
|
||||
configurable: false,
|
||||
enumerable: descriptor.enumerable,
|
||||
get: descriptor.get,
|
||||
set: descriptor.set
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
global.global = global;
|
||||
global.Buffer = Buffer;
|
||||
global.ArrayBuffer = ArrayBuffer;
|
||||
// TextEncoder (global or via 'util') references a Uint8Array constructor
|
||||
// different than the global one used by users in tests. This makes sure the
|
||||
// same constructor is referenced by both.
|
||||
global.Uint8Array = Uint8Array;
|
||||
(0, _jestUtil().installCommonGlobals)(global, projectConfig.globals, globalsCleanupMode);
|
||||
if ('asyncDispose' in Symbol && !('asyncDispose' in global.Symbol)) {
|
||||
const globalSymbol = global.Symbol;
|
||||
// @ts-expect-error - it's readonly - but we have checked above that it's not there
|
||||
globalSymbol.asyncDispose = globalSymbol.for('nodejs.asyncDispose');
|
||||
// @ts-expect-error - it's readonly - but we have checked above that it's not there
|
||||
globalSymbol.dispose = globalSymbol.for('nodejs.dispose');
|
||||
}
|
||||
|
||||
// Node's error-message stack size is limited at 10, but it's pretty useful
|
||||
// to see more than that when a test fails.
|
||||
global.Error.stackTraceLimit = 100;
|
||||
if ('customExportConditions' in projectConfig.testEnvironmentOptions) {
|
||||
const {
|
||||
customExportConditions
|
||||
} = projectConfig.testEnvironmentOptions;
|
||||
if (Array.isArray(customExportConditions) && customExportConditions.every(isString)) {
|
||||
this._configuredExportConditions = customExportConditions;
|
||||
} else {
|
||||
throw new Error('Custom export conditions specified but they are not an array of strings');
|
||||
}
|
||||
}
|
||||
this.moduleMocker = new (_jestMock().ModuleMocker)(global);
|
||||
this.fakeTimers = new (_fakeTimers().LegacyFakeTimers)({
|
||||
config: projectConfig,
|
||||
global,
|
||||
moduleMocker: this.moduleMocker,
|
||||
timerConfig: {
|
||||
idToRef: timerIdToRef,
|
||||
refToId: timerRefToId
|
||||
}
|
||||
});
|
||||
this.fakeTimersModern = new (_fakeTimers().ModernFakeTimers)({
|
||||
config: projectConfig,
|
||||
global
|
||||
});
|
||||
this._globalProxy.envSetupCompleted();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
async setup() {}
|
||||
async teardown() {
|
||||
if (this.fakeTimers) {
|
||||
this.fakeTimers.dispose();
|
||||
}
|
||||
if (this.fakeTimersModern) {
|
||||
this.fakeTimersModern.dispose();
|
||||
}
|
||||
this.context = null;
|
||||
this.fakeTimers = null;
|
||||
this.fakeTimersModern = null;
|
||||
this._globalProxy.clear();
|
||||
}
|
||||
exportConditions() {
|
||||
return this._configuredExportConditions ?? this.customExportConditions;
|
||||
}
|
||||
getVmContext() {
|
||||
return this.context;
|
||||
}
|
||||
}
|
||||
exports["default"] = NodeEnvironment;
|
||||
const TestEnvironment = exports.TestEnvironment = NodeEnvironment;
|
||||
|
||||
/**
|
||||
* Creates a new empty global object and wraps it with a {@link Proxy}.
|
||||
*
|
||||
* The purpose is to register any property set on the global object,
|
||||
* and {@link #deleteProperties} on them at environment teardown,
|
||||
* to clean up memory and prevent leaks.
|
||||
*/
|
||||
class GlobalProxy {
|
||||
global = Object.create(Object.getPrototypeOf(globalThis));
|
||||
globalProxy = new Proxy(this.global, this);
|
||||
isEnvSetup = false;
|
||||
propertyToValue = new Map();
|
||||
leftovers = [];
|
||||
constructor() {
|
||||
this.register = this.register.bind(this);
|
||||
}
|
||||
proxy() {
|
||||
return this.globalProxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks that the environment setup has completed, and properties set on
|
||||
* the global object from now on should be deleted at teardown.
|
||||
*/
|
||||
envSetupCompleted() {
|
||||
this.isEnvSetup = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes any property that was set on the global object, except for:
|
||||
* 1. Properties that were set before {@link #envSetupCompleted} was invoked.
|
||||
* 2. Properties protected by {@link #protectProperties}.
|
||||
*/
|
||||
clear() {
|
||||
for (const {
|
||||
value
|
||||
} of [...[...this.propertyToValue.entries()].map(([property, value]) => ({
|
||||
property,
|
||||
value
|
||||
})), ...this.leftovers]) {
|
||||
(0, _jestUtil().deleteProperties)(value);
|
||||
}
|
||||
this.propertyToValue.clear();
|
||||
this.leftovers = [];
|
||||
this.global = {};
|
||||
this.globalProxy = {};
|
||||
}
|
||||
defineProperty(target, property, attributes) {
|
||||
const newAttributes = {
|
||||
...attributes
|
||||
};
|
||||
if ('set' in newAttributes && newAttributes.set !== undefined) {
|
||||
const originalSet = newAttributes.set;
|
||||
const register = this.register;
|
||||
newAttributes.set = value => {
|
||||
originalSet(value);
|
||||
const newValue = Reflect.get(target, property);
|
||||
register(property, newValue);
|
||||
};
|
||||
}
|
||||
const result = Reflect.defineProperty(target, property, newAttributes);
|
||||
if ('value' in newAttributes) {
|
||||
this.register(property, newAttributes.value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
deleteProperty(target, property) {
|
||||
const result = Reflect.deleteProperty(target, property);
|
||||
const value = this.propertyToValue.get(property);
|
||||
if (value) {
|
||||
this.leftovers.push({
|
||||
property,
|
||||
value
|
||||
});
|
||||
this.propertyToValue.delete(property);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
register(property, value) {
|
||||
const currentValue = this.propertyToValue.get(property);
|
||||
if (value !== currentValue) {
|
||||
if (!this.isEnvSetup && (0, _jestUtil().canDeleteProperties)(value)) {
|
||||
(0, _jestUtil().protectProperties)(value);
|
||||
}
|
||||
if (currentValue) {
|
||||
this.leftovers.push({
|
||||
property,
|
||||
value: currentValue
|
||||
});
|
||||
}
|
||||
this.propertyToValue.set(property, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
function readGlobalsCleanupConfig(projectConfig) {
|
||||
const rawConfig = projectConfig.testEnvironmentOptions.globalsCleanup;
|
||||
const config = rawConfig?.toString()?.toLowerCase();
|
||||
switch (config) {
|
||||
case 'off':
|
||||
case 'on':
|
||||
case 'soft':
|
||||
return config;
|
||||
default:
|
||||
{
|
||||
if (config !== undefined) {
|
||||
(0, _jestValidate().logValidationWarning)('testEnvironmentOptions.globalsCleanup', `Unknown value given: ${rawConfig}`, 'Available options are: [on, soft, off]');
|
||||
}
|
||||
return 'soft';
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
module.exports = __webpack_exports__;
|
||||
/******/ })()
|
||||
;
|
||||
4
frontend/node_modules/jest-environment-node/build/index.mjs
generated
vendored
Normal file
4
frontend/node_modules/jest-environment-node/build/index.mjs
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import cjsModule from './index.js';
|
||||
|
||||
export const TestEnvironment = cjsModule.TestEnvironment;
|
||||
export default cjsModule.default;
|
||||
Reference in New Issue
Block a user