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,7 @@
'use strict';
function cloneEvent(event) {
return new event.constructor(event.type, event);
}
exports.cloneEvent = cloneEvent;

View File

@@ -0,0 +1,14 @@
'use strict';
function findClosest(element, callback) {
let el = element;
do {
if (callback(el)) {
return el;
}
el = el.parentElement;
}while (el && el !== element.ownerDocument.body)
return undefined;
}
exports.findClosest = findClosest;

View File

@@ -0,0 +1,10 @@
'use strict';
function getDocumentFromNode(el) {
return isDocument(el) ? el : el.ownerDocument;
}
function isDocument(node) {
return node.nodeType === 9;
}
exports.getDocumentFromNode = getDocumentFromNode;

View File

@@ -0,0 +1,25 @@
'use strict';
function getTreeDiff(a, b) {
const treeA = [];
for(let el = a; el; el = el.parentElement){
treeA.push(el);
}
const treeB = [];
for(let el = b; el; el = el.parentElement){
treeB.push(el);
}
let i = 0;
for(;; i++){
if (i >= treeA.length || i >= treeB.length || treeA[treeA.length - 1 - i] !== treeB[treeB.length - 1 - i]) {
break;
}
}
return [
treeA.slice(0, treeA.length - i),
treeB.slice(0, treeB.length - i),
treeB.slice(treeB.length - i)
];
}
exports.getTreeDiff = getTreeDiff;

View File

@@ -0,0 +1,19 @@
'use strict';
function getWindow(node) {
var _node_ownerDocument;
if (isDocument(node) && node.defaultView) {
return node.defaultView;
} else if ((_node_ownerDocument = node.ownerDocument) === null || _node_ownerDocument === undefined ? undefined : _node_ownerDocument.defaultView) {
return node.ownerDocument.defaultView;
}
throw new Error(`Could not determine window of node. Node was ${describe(node)}`);
}
function isDocument(node) {
return node.nodeType === 9;
}
function describe(val) {
return typeof val === 'function' ? `function ${val.name}` : val === null ? 'null' : String(val);
}
exports.getWindow = getWindow;

View File

@@ -0,0 +1,14 @@
'use strict';
function isDescendantOrSelf(potentialDescendant, potentialAncestor) {
let el = potentialDescendant;
do {
if (el === potentialAncestor) {
return true;
}
el = el.parentElement;
}while (el)
return false;
}
exports.isDescendantOrSelf = isDescendantOrSelf;

View File

@@ -0,0 +1,33 @@
'use strict';
var isElementType = require('./isElementType.js');
// This should probably just rely on the :disabled pseudo-class, but JSDOM doesn't implement it properly.
function isDisabled(element) {
for(let el = element; el; el = el.parentElement){
if (isElementType.isElementType(el, [
'button',
'input',
'select',
'textarea',
'optgroup',
'option'
])) {
if (el.hasAttribute('disabled')) {
return true;
}
} else if (isElementType.isElementType(el, 'fieldset')) {
var _el_querySelector;
if (el.hasAttribute('disabled') && !((_el_querySelector = el.querySelector(':scope > legend')) === null || _el_querySelector === undefined ? undefined : _el_querySelector.contains(element))) {
return true;
}
} else if (el.tagName.includes('-')) {
if (el.constructor.formAssociated && el.hasAttribute('disabled')) {
return true;
}
}
}
return false;
}
exports.isDisabled = isDisabled;

View File

@@ -0,0 +1,20 @@
'use strict';
function isElementType(element, tag, props) {
if (element.namespaceURI && element.namespaceURI !== 'http://www.w3.org/1999/xhtml') {
return false;
}
tag = Array.isArray(tag) ? tag : [
tag
];
// tagName is uppercase in HTMLDocument and lowercase in XMLDocument
if (!tag.includes(element.tagName.toLowerCase())) {
return false;
}
if (props) {
return Object.entries(props).every(([k, v])=>element[k] === v);
}
return true;
}
exports.isElementType = isElementType;

View File

@@ -0,0 +1,19 @@
'use strict';
var getWindow = require('./getWindow.js');
function isVisible(element) {
const window = getWindow.getWindow(element);
for(let el = element; el === null || el === undefined ? undefined : el.ownerDocument; el = el.parentElement){
const { display, visibility } = window.getComputedStyle(el);
if (display === 'none') {
return false;
}
if (visibility === 'hidden') {
return false;
}
}
return true;
}
exports.isVisible = isVisible;

View File

@@ -0,0 +1,17 @@
'use strict';
var ApiLevel = /*#__PURE__*/ function(ApiLevel) {
ApiLevel[ApiLevel["Trigger"] = 2] = "Trigger";
ApiLevel[ApiLevel["Call"] = 1] = "Call";
return ApiLevel;
}({});
function setLevelRef(instance, level) {
instance.levelRefs[level] = {};
}
function getLevelRef(instance, level) {
return instance.levelRefs[level];
}
exports.ApiLevel = ApiLevel;
exports.getLevelRef = getLevelRef;
exports.setLevelRef = setLevelRef;

View File

@@ -0,0 +1,14 @@
'use strict';
function wait(config) {
const delay = config.delay;
if (typeof delay !== 'number') {
return;
}
return Promise.all([
new Promise((resolve)=>globalThis.setTimeout(()=>resolve(), delay)),
config.advanceTimers(delay)
]);
}
exports.wait = wait;