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:
22
frontend/node_modules/babel-plugin-jest-hoist/LICENSE
generated
vendored
Normal file
22
frontend/node_modules/babel-plugin-jest-hoist/LICENSE
generated
vendored
Normal 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.
|
||||
33
frontend/node_modules/babel-plugin-jest-hoist/README.md
generated
vendored
Normal file
33
frontend/node_modules/babel-plugin-jest-hoist/README.md
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# babel-plugin-jest-hoist
|
||||
|
||||
Babel plugin to hoist `jest.disableAutomock`, `jest.enableAutomock`, `jest.unmock`, `jest.mock`, calls above `import` statements. This plugin is automatically included when using [babel-jest](https://github.com/jestjs/jest/tree/main/packages/babel-jest).
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
$ yarn add --dev babel-plugin-jest-hoist
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Via `babel.config.js` (Recommended)
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
plugins: ['jest-hoist'],
|
||||
};
|
||||
```
|
||||
|
||||
### Via CLI
|
||||
|
||||
```sh
|
||||
$ babel --plugins jest-hoist script.js
|
||||
```
|
||||
|
||||
### Via Node API
|
||||
|
||||
```javascript
|
||||
require('@babel/core').transform('code', {
|
||||
plugins: ['jest-hoist'],
|
||||
});
|
||||
```
|
||||
17
frontend/node_modules/babel-plugin-jest-hoist/build/index.d.ts
generated
vendored
Normal file
17
frontend/node_modules/babel-plugin-jest-hoist/build/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 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 {PluginObj} from '@babel/core';
|
||||
import {Identifier} from '@babel/types';
|
||||
|
||||
declare function jestHoist(): PluginObj<{
|
||||
declareJestObjGetterIdentifier: () => Identifier;
|
||||
jestObjGetterIdentifier?: Identifier;
|
||||
}>;
|
||||
export default jestHoist;
|
||||
|
||||
export {};
|
||||
277
frontend/node_modules/babel-plugin-jest-hoist/build/index.js
generated
vendored
Normal file
277
frontend/node_modules/babel-plugin-jest-hoist/build/index.js
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
/*!
|
||||
* /**
|
||||
* * 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"] = jestHoist;
|
||||
function _template() {
|
||||
const data = require("@babel/template");
|
||||
_template = function () {
|
||||
return data;
|
||||
};
|
||||
return data;
|
||||
}
|
||||
function _types() {
|
||||
const data = require("@babel/types");
|
||||
_types = 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.
|
||||
*
|
||||
*/
|
||||
|
||||
const JEST_GLOBAL_NAME = 'jest';
|
||||
const JEST_GLOBALS_MODULE_NAME = '@jest/globals';
|
||||
const JEST_GLOBALS_MODULE_JEST_EXPORT_NAME = 'jest';
|
||||
const hoistedVariables = new WeakSet();
|
||||
const hoistedJestGetters = new WeakSet();
|
||||
const hoistedJestExpressions = new WeakSet();
|
||||
|
||||
// We allow `jest`, `expect`, `require`, all default Node.js globals and all
|
||||
// ES2015 built-ins to be used inside of a `jest.mock` factory.
|
||||
// We also allow variables prefixed with `mock` as an escape-hatch.
|
||||
const ALLOWED_IDENTIFIERS = new Set(['Array', 'ArrayBuffer', 'Boolean', 'BigInt', 'DataView', 'Date', 'Error', 'EvalError', 'Float32Array', 'Float64Array', 'Function', 'Generator', 'GeneratorFunction', 'Infinity', 'Int16Array', 'Int32Array', 'Int8Array', 'InternalError', 'Intl', 'JSON', 'Map', 'Math', 'NaN', 'Number', 'Object', 'Promise', 'Proxy', 'RangeError', 'ReferenceError', 'Reflect', 'RegExp', 'Set', 'String', 'Symbol', 'SyntaxError', 'TypeError', 'URIError', 'Uint16Array', 'Uint32Array', 'Uint8Array', 'Uint8ClampedArray', 'WeakMap', 'WeakSet', 'arguments', 'console', 'expect', 'isNaN', 'jest', 'parseFloat', 'parseInt', 'exports', 'require', 'module', '__filename', '__dirname', 'undefined', ...Object.getOwnPropertyNames(globalThis)].sort());
|
||||
const IDVisitor = {
|
||||
ReferencedIdentifier(path, {
|
||||
ids
|
||||
}) {
|
||||
ids.add(path);
|
||||
},
|
||||
denylist: ['TypeAnnotation', 'TSTypeAnnotation', 'TSTypeQuery', 'TSTypeReference']
|
||||
};
|
||||
const FUNCTIONS = Object.create(null);
|
||||
FUNCTIONS.mock = args => {
|
||||
if (args.length === 1) {
|
||||
return args[0].isStringLiteral() || args[0].isLiteral();
|
||||
} else if (args.length === 2 || args.length === 3) {
|
||||
const moduleFactory = args[1];
|
||||
if (!moduleFactory.isFunction()) {
|
||||
throw moduleFactory.buildCodeFrameError('The second argument of `jest.mock` must be an inline function.\n', TypeError);
|
||||
}
|
||||
const ids = new Set();
|
||||
const parentScope = moduleFactory.parentPath.scope;
|
||||
// @ts-expect-error: ReferencedIdentifier and denylist are not known on visitors
|
||||
moduleFactory.traverse(IDVisitor, {
|
||||
ids
|
||||
});
|
||||
for (const id of ids) {
|
||||
const {
|
||||
name
|
||||
} = id.node;
|
||||
let found = false;
|
||||
let scope = id.scope;
|
||||
while (scope !== parentScope) {
|
||||
if (scope.bindings[name] != null) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
scope = scope.parent;
|
||||
}
|
||||
if (!found) {
|
||||
let isAllowedIdentifier = scope.hasGlobal(name) && ALLOWED_IDENTIFIERS.has(name) || /^mock/i.test(name) ||
|
||||
// Allow istanbul's coverage variable to pass.
|
||||
/^(?:__)?cov/.test(name);
|
||||
if (!isAllowedIdentifier) {
|
||||
const binding = scope.bindings[name];
|
||||
if (binding?.path.isVariableDeclarator()) {
|
||||
const {
|
||||
node
|
||||
} = binding.path;
|
||||
const initNode = node.init;
|
||||
if (initNode && binding.constant && scope.isPure(initNode, true)) {
|
||||
hoistedVariables.add(node);
|
||||
isAllowedIdentifier = true;
|
||||
}
|
||||
} else if (binding?.path.isImportSpecifier()) {
|
||||
const importDecl = binding.path.parentPath;
|
||||
const imported = binding.path.node.imported;
|
||||
if (importDecl.node.source.value === JEST_GLOBALS_MODULE_NAME && ((0, _types().isIdentifier)(imported) ? imported.name : imported.value) === JEST_GLOBALS_MODULE_JEST_EXPORT_NAME) {
|
||||
isAllowedIdentifier = true;
|
||||
// Imports are already hoisted, so we don't need to add it
|
||||
// to hoistedVariables.
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isAllowedIdentifier) {
|
||||
throw id.buildCodeFrameError('The module factory of `jest.mock()` is not allowed to ' + 'reference any out-of-scope variables.\n' + `Invalid variable access: ${name}\n` + `Allowed objects: ${[...ALLOWED_IDENTIFIERS].join(', ')}.\n` + 'Note: This is a precaution to guard against uninitialized mock ' + 'variables. If it is ensured that the mock is required lazily, ' + 'variable names prefixed with `mock` (case insensitive) are permitted.\n', ReferenceError);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
FUNCTIONS.unmock = args => args.length === 1 && args[0].isStringLiteral();
|
||||
FUNCTIONS.deepUnmock = args => args.length === 1 && args[0].isStringLiteral();
|
||||
FUNCTIONS.disableAutomock = FUNCTIONS.enableAutomock = args => args.length === 0;
|
||||
const createJestObjectGetter = (0, _template().statement)`
|
||||
function GETTER_NAME() {
|
||||
const { JEST_GLOBALS_MODULE_JEST_EXPORT_NAME } = require("JEST_GLOBALS_MODULE_NAME");
|
||||
GETTER_NAME = () => JEST_GLOBALS_MODULE_JEST_EXPORT_NAME;
|
||||
return JEST_GLOBALS_MODULE_JEST_EXPORT_NAME;
|
||||
}
|
||||
`;
|
||||
const isJestObject = expression => {
|
||||
// global
|
||||
if (expression.isIdentifier() && expression.node.name === JEST_GLOBAL_NAME && !expression.scope.hasBinding(JEST_GLOBAL_NAME)) {
|
||||
return true;
|
||||
}
|
||||
// import { jest } from '@jest/globals'
|
||||
if (expression.referencesImport(JEST_GLOBALS_MODULE_NAME, JEST_GLOBALS_MODULE_JEST_EXPORT_NAME)) {
|
||||
return true;
|
||||
}
|
||||
// import * as JestGlobals from '@jest/globals'
|
||||
if (expression.isMemberExpression() && !expression.node.computed && expression.get('object').referencesImport(JEST_GLOBALS_MODULE_NAME, '*') && expression.node.property.type === 'Identifier' && expression.node.property.name === JEST_GLOBALS_MODULE_JEST_EXPORT_NAME) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const extractJestObjExprIfHoistable = expr => {
|
||||
if (!expr.isCallExpression()) {
|
||||
return null;
|
||||
}
|
||||
const callee = expr.get('callee');
|
||||
const args = expr.get('arguments');
|
||||
if (!callee.isMemberExpression() || callee.node.computed) {
|
||||
return null;
|
||||
}
|
||||
const object = callee.get('object');
|
||||
const property = callee.get('property');
|
||||
const propertyName = property.node.name;
|
||||
const jestObjExpr = isJestObject(object) ? object :
|
||||
// The Jest object could be returned from another call since the functions are all chainable.
|
||||
extractJestObjExprIfHoistable(object)?.path;
|
||||
if (!jestObjExpr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Important: Call the function check last
|
||||
// It might throw an error to display to the user,
|
||||
// which should only happen if we're already sure it's a call on the Jest object.
|
||||
const functionIsHoistable = FUNCTIONS[propertyName]?.(args) ?? false;
|
||||
let functionHasHoistableScope = functionIsHoistable;
|
||||
for (let path = expr; path && !functionHasHoistableScope; path = path.parentPath) {
|
||||
functionHasHoistableScope = hoistedJestExpressions.has(
|
||||
// @ts-expect-error: it's ok if path.node is not an Expression, .has will
|
||||
// just return false.
|
||||
path.node);
|
||||
}
|
||||
if (functionHasHoistableScope) {
|
||||
hoistedJestExpressions.add(expr.node);
|
||||
return {
|
||||
hoist: functionIsHoistable,
|
||||
path: jestObjExpr
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/* eslint-disable sort-keys */
|
||||
function jestHoist() {
|
||||
return {
|
||||
pre({
|
||||
path: program
|
||||
}) {
|
||||
this.declareJestObjGetterIdentifier = () => {
|
||||
if (this.jestObjGetterIdentifier) {
|
||||
return this.jestObjGetterIdentifier;
|
||||
}
|
||||
this.jestObjGetterIdentifier = program.scope.generateUidIdentifier('getJestObj');
|
||||
program.unshiftContainer('body', [createJestObjectGetter({
|
||||
GETTER_NAME: this.jestObjGetterIdentifier.name,
|
||||
JEST_GLOBALS_MODULE_JEST_EXPORT_NAME,
|
||||
JEST_GLOBALS_MODULE_NAME
|
||||
})]);
|
||||
return this.jestObjGetterIdentifier;
|
||||
};
|
||||
},
|
||||
visitor: {
|
||||
ExpressionStatement(exprStmt) {
|
||||
const jestObjInfo = extractJestObjExprIfHoistable(exprStmt.get('expression'));
|
||||
if (jestObjInfo) {
|
||||
const jestCallExpr = (0, _types().callExpression)(this.declareJestObjGetterIdentifier(), []);
|
||||
jestObjInfo.path.replaceWith(jestCallExpr);
|
||||
if (jestObjInfo.hoist) {
|
||||
hoistedJestGetters.add(jestCallExpr);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// in `post` to make sure we come after an import transform and can unshift above the `require`s
|
||||
post({
|
||||
path: program
|
||||
}) {
|
||||
const stack = [{
|
||||
calls: [],
|
||||
vars: []
|
||||
}];
|
||||
program.traverse({
|
||||
BlockStatement: {
|
||||
enter() {
|
||||
stack.push({
|
||||
calls: [],
|
||||
vars: []
|
||||
});
|
||||
},
|
||||
exit(path) {
|
||||
const item = stack.pop();
|
||||
path.node.body.unshift(...item.vars, ...item.calls);
|
||||
}
|
||||
},
|
||||
CallExpression(callExpr) {
|
||||
if (hoistedJestGetters.has(callExpr.node)) {
|
||||
const mockStmt = callExpr.getStatementParent();
|
||||
if (mockStmt?.parentPath.isBlock()) {
|
||||
stack.at(-1).calls.push(mockStmt.node);
|
||||
mockStmt.remove();
|
||||
}
|
||||
}
|
||||
},
|
||||
VariableDeclarator(varDecl) {
|
||||
if (hoistedVariables.has(varDecl.node)) {
|
||||
// should be assert function, but it's not. So let's cast below
|
||||
varDecl.parentPath.assertVariableDeclaration();
|
||||
const {
|
||||
kind,
|
||||
declarations
|
||||
} = varDecl.parent;
|
||||
if (declarations.length === 1) {
|
||||
varDecl.parentPath.remove();
|
||||
} else {
|
||||
varDecl.remove();
|
||||
}
|
||||
stack.at(-1).vars.push((0, _types().variableDeclaration)(kind, [varDecl.node]));
|
||||
}
|
||||
}
|
||||
});
|
||||
const item = stack.pop();
|
||||
program.node.body.unshift(...item.vars, ...item.calls);
|
||||
}
|
||||
};
|
||||
}
|
||||
/* eslint-enable */
|
||||
})();
|
||||
|
||||
module.exports = __webpack_exports__;
|
||||
/******/ })()
|
||||
;
|
||||
3
frontend/node_modules/babel-plugin-jest-hoist/build/index.mjs
generated
vendored
Normal file
3
frontend/node_modules/babel-plugin-jest-hoist/build/index.mjs
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import cjsModule from './index.js';
|
||||
|
||||
export default cjsModule.default;
|
||||
44
frontend/node_modules/babel-plugin-jest-hoist/package.json
generated
vendored
Normal file
44
frontend/node_modules/babel-plugin-jest-hoist/package.json
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "babel-plugin-jest-hoist",
|
||||
"version": "30.0.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jestjs/jest.git",
|
||||
"directory": "packages/babel-plugin-jest-hoist"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||
},
|
||||
"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": {
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.27.3",
|
||||
"@types/babel__core": "^7.20.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.4",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/preset-typescript": "^7.27.1",
|
||||
"@prettier/sync": "^0.5.5",
|
||||
"@types/babel__template": "^7.4.4",
|
||||
"@types/babel__traverse": "^7.20.7",
|
||||
"@types/node": "*",
|
||||
"babel-plugin-tester": "^11.0.4",
|
||||
"prettier": "^3.0.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "5ce865b4060189fe74cd486544816c079194a0f7"
|
||||
}
|
||||
Reference in New Issue
Block a user