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:
126
frontend/node_modules/@testing-library/user-event/dist/cjs/utils/focus/cursor.js
generated
vendored
Normal file
126
frontend/node_modules/@testing-library/user-event/dist/cjs/utils/focus/cursor.js
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
'use strict';
|
||||
|
||||
var isContentEditable = require('../edit/isContentEditable.js');
|
||||
var isElementType = require('../misc/isElementType.js');
|
||||
|
||||
function getNextCursorPosition(node, offset, direction, inputType) {
|
||||
// The behavior at text node zero offset is inconsistent.
|
||||
// When walking backwards:
|
||||
// Firefox always moves to zero offset and jumps over last offset.
|
||||
// Chrome jumps over zero offset per default but over last offset when Shift is pressed.
|
||||
// The cursor always moves to zero offset if the focus area (contenteditable or body) ends there.
|
||||
// When walking forward both ignore zero offset.
|
||||
// When walking over input elements the cursor moves before or after that element.
|
||||
// When walking over line breaks the cursor moves inside any following text node.
|
||||
if (isTextNode(node) && offset + direction >= 0 && offset + direction <= node.nodeValue.length) {
|
||||
return {
|
||||
node,
|
||||
offset: offset + direction
|
||||
};
|
||||
}
|
||||
const nextNode = getNextCharacterContentNode(node, offset, direction);
|
||||
if (nextNode) {
|
||||
if (isTextNode(nextNode)) {
|
||||
return {
|
||||
node: nextNode,
|
||||
offset: direction > 0 ? Math.min(1, nextNode.nodeValue.length) : Math.max(nextNode.nodeValue.length - 1, 0)
|
||||
};
|
||||
} else if (isElementType.isElementType(nextNode, 'br')) {
|
||||
const nextPlusOne = getNextCharacterContentNode(nextNode, undefined, direction);
|
||||
if (!nextPlusOne) {
|
||||
// The behavior when there is no possible cursor position beyond the line break is inconsistent.
|
||||
// In Chrome outside of contenteditable moving before a leading line break is possible.
|
||||
// A leading line break can still be removed per deleteContentBackward.
|
||||
// A trailing line break on the other hand is not removed by deleteContentForward.
|
||||
if (direction < 0 && inputType === 'deleteContentBackward') {
|
||||
return {
|
||||
node: nextNode.parentNode,
|
||||
offset: getOffset(nextNode)
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
} else if (isTextNode(nextPlusOne)) {
|
||||
return {
|
||||
node: nextPlusOne,
|
||||
offset: direction > 0 ? 0 : nextPlusOne.nodeValue.length
|
||||
};
|
||||
} else if (direction < 0 && isElementType.isElementType(nextPlusOne, 'br')) {
|
||||
return {
|
||||
node: nextNode.parentNode,
|
||||
offset: getOffset(nextNode)
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
node: nextPlusOne.parentNode,
|
||||
offset: getOffset(nextPlusOne) + (direction > 0 ? 0 : 1)
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
node: nextNode.parentNode,
|
||||
offset: getOffset(nextNode) + (direction > 0 ? 1 : 0)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
function getNextCharacterContentNode(node, offset, direction) {
|
||||
const nextOffset = Number(offset) + (direction < 0 ? -1 : 0);
|
||||
if (offset !== undefined && isElement(node) && nextOffset >= 0 && nextOffset < node.children.length) {
|
||||
node = node.children[nextOffset];
|
||||
}
|
||||
return walkNodes(node, direction === 1 ? 'next' : 'previous', isTreatedAsCharacterContent);
|
||||
}
|
||||
function isTreatedAsCharacterContent(node) {
|
||||
if (isTextNode(node)) {
|
||||
return true;
|
||||
}
|
||||
if (isElement(node)) {
|
||||
if (isElementType.isElementType(node, [
|
||||
'input',
|
||||
'textarea'
|
||||
])) {
|
||||
return node.type !== 'hidden';
|
||||
} else if (isElementType.isElementType(node, 'br')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function getOffset(node) {
|
||||
let i = 0;
|
||||
while(node.previousSibling){
|
||||
i++;
|
||||
node = node.previousSibling;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
function isElement(node) {
|
||||
return node.nodeType === 1;
|
||||
}
|
||||
function isTextNode(node) {
|
||||
return node.nodeType === 3;
|
||||
}
|
||||
function walkNodes(node, direction, callback) {
|
||||
for(;;){
|
||||
var _node_ownerDocument;
|
||||
const sibling = node[`${direction}Sibling`];
|
||||
if (sibling) {
|
||||
node = getDescendant(sibling, direction === 'next' ? 'first' : 'last');
|
||||
if (callback(node)) {
|
||||
return node;
|
||||
}
|
||||
} else if (node.parentNode && (!isElement(node.parentNode) || !isContentEditable.isContentEditable(node.parentNode) && node.parentNode !== ((_node_ownerDocument = node.ownerDocument) === null || _node_ownerDocument === undefined ? undefined : _node_ownerDocument.body))) {
|
||||
node = node.parentNode;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
function getDescendant(node, direction) {
|
||||
while(node.hasChildNodes()){
|
||||
node = node[`${direction}Child`];
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
exports.getNextCursorPosition = getNextCursorPosition;
|
||||
23
frontend/node_modules/@testing-library/user-event/dist/cjs/utils/focus/getActiveElement.js
generated
vendored
Normal file
23
frontend/node_modules/@testing-library/user-event/dist/cjs/utils/focus/getActiveElement.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
var isDisabled = require('../misc/isDisabled.js');
|
||||
|
||||
function getActiveElement(document) {
|
||||
const activeElement = document.activeElement;
|
||||
if (activeElement === null || activeElement === undefined ? undefined : activeElement.shadowRoot) {
|
||||
return getActiveElement(activeElement.shadowRoot);
|
||||
} else {
|
||||
// Browser does not yield disabled elements as document.activeElement - jsdom does
|
||||
if (isDisabled.isDisabled(activeElement)) {
|
||||
return document.ownerDocument ? /* istanbul ignore next */ document.ownerDocument.body : document.body;
|
||||
}
|
||||
return activeElement;
|
||||
}
|
||||
}
|
||||
function getActiveElementOrBody(document) {
|
||||
var _getActiveElement;
|
||||
return (_getActiveElement = getActiveElement(document)) !== null && _getActiveElement !== undefined ? _getActiveElement : /* istanbul ignore next */ document.body;
|
||||
}
|
||||
|
||||
exports.getActiveElement = getActiveElement;
|
||||
exports.getActiveElementOrBody = getActiveElementOrBody;
|
||||
80
frontend/node_modules/@testing-library/user-event/dist/cjs/utils/focus/getTabDestination.js
generated
vendored
Normal file
80
frontend/node_modules/@testing-library/user-event/dist/cjs/utils/focus/getTabDestination.js
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
'use strict';
|
||||
|
||||
var isDisabled = require('../misc/isDisabled.js');
|
||||
var isElementType = require('../misc/isElementType.js');
|
||||
var isVisible = require('../misc/isVisible.js');
|
||||
var selector = require('./selector.js');
|
||||
|
||||
function getTabDestination(activeElement, shift) {
|
||||
const document = activeElement.ownerDocument;
|
||||
const focusableElements = document.querySelectorAll(selector.FOCUSABLE_SELECTOR);
|
||||
const enabledElements = Array.from(focusableElements).filter((el)=>el === activeElement || !(Number(el.getAttribute('tabindex')) < 0 || isDisabled.isDisabled(el)));
|
||||
// tabindex has no effect if the active element has negative tabindex
|
||||
if (Number(activeElement.getAttribute('tabindex')) >= 0) {
|
||||
enabledElements.sort((a, b)=>{
|
||||
const i = Number(a.getAttribute('tabindex'));
|
||||
const j = Number(b.getAttribute('tabindex'));
|
||||
if (i === j) {
|
||||
return 0;
|
||||
} else if (i === 0) {
|
||||
return 1;
|
||||
} else if (j === 0) {
|
||||
return -1;
|
||||
}
|
||||
return i - j;
|
||||
});
|
||||
}
|
||||
const checkedRadio = {};
|
||||
let prunedElements = [
|
||||
document.body
|
||||
];
|
||||
const activeRadioGroup = isElementType.isElementType(activeElement, 'input', {
|
||||
type: 'radio'
|
||||
}) ? activeElement.name : undefined;
|
||||
enabledElements.forEach((currentElement)=>{
|
||||
const el = currentElement;
|
||||
// For radio groups keep only the active radio
|
||||
// If there is no active radio, keep only the checked radio
|
||||
// If there is no checked radio, treat like everything else
|
||||
if (isElementType.isElementType(el, 'input', {
|
||||
type: 'radio'
|
||||
}) && el.name) {
|
||||
// If the active element is part of the group, add only that
|
||||
if (el === activeElement) {
|
||||
prunedElements.push(el);
|
||||
return;
|
||||
} else if (el.name === activeRadioGroup) {
|
||||
return;
|
||||
}
|
||||
// If we stumble upon a checked radio, remove the others
|
||||
if (el.checked) {
|
||||
prunedElements = prunedElements.filter((e)=>!isElementType.isElementType(e, 'input', {
|
||||
type: 'radio',
|
||||
name: el.name
|
||||
}));
|
||||
prunedElements.push(el);
|
||||
checkedRadio[el.name] = el;
|
||||
return;
|
||||
}
|
||||
// If we already found the checked one, skip
|
||||
if (typeof checkedRadio[el.name] !== 'undefined') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
prunedElements.push(el);
|
||||
});
|
||||
for(let index = prunedElements.findIndex((el)=>el === activeElement);;){
|
||||
index += shift ? -1 : 1;
|
||||
// loop at overflow
|
||||
if (index === prunedElements.length) {
|
||||
index = 0;
|
||||
} else if (index === -1) {
|
||||
index = prunedElements.length - 1;
|
||||
}
|
||||
if (prunedElements[index] === activeElement || prunedElements[index] === document.body || isVisible.isVisible(prunedElements[index])) {
|
||||
return prunedElements[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.getTabDestination = getTabDestination;
|
||||
9
frontend/node_modules/@testing-library/user-event/dist/cjs/utils/focus/isFocusable.js
generated
vendored
Normal file
9
frontend/node_modules/@testing-library/user-event/dist/cjs/utils/focus/isFocusable.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
var selector = require('./selector.js');
|
||||
|
||||
function isFocusable(element) {
|
||||
return element.matches(selector.FOCUSABLE_SELECTOR);
|
||||
}
|
||||
|
||||
exports.isFocusable = isFocusable;
|
||||
20
frontend/node_modules/@testing-library/user-event/dist/cjs/utils/focus/selection.js
generated
vendored
Normal file
20
frontend/node_modules/@testing-library/user-event/dist/cjs/utils/focus/selection.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
var isClickableInput = require('../click/isClickableInput.js');
|
||||
var isEditable = require('../edit/isEditable.js');
|
||||
|
||||
/**
|
||||
* Determine if the element has its own selection implementation
|
||||
* and does not interact with the Document Selection API.
|
||||
*/ function hasOwnSelection(node) {
|
||||
return isElement(node) && isEditable.isEditableInputOrTextArea(node);
|
||||
}
|
||||
function hasNoSelection(node) {
|
||||
return isElement(node) && isClickableInput.isClickableInput(node);
|
||||
}
|
||||
function isElement(node) {
|
||||
return node.nodeType === 1;
|
||||
}
|
||||
|
||||
exports.hasNoSelection = hasNoSelection;
|
||||
exports.hasOwnSelection = hasOwnSelection;
|
||||
14
frontend/node_modules/@testing-library/user-event/dist/cjs/utils/focus/selector.js
generated
vendored
Normal file
14
frontend/node_modules/@testing-library/user-event/dist/cjs/utils/focus/selector.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const FOCUSABLE_SELECTOR = [
|
||||
'input:not([type=hidden]):not([disabled])',
|
||||
'button:not([disabled])',
|
||||
'select:not([disabled])',
|
||||
'textarea:not([disabled])',
|
||||
'[contenteditable=""]',
|
||||
'[contenteditable="true"]',
|
||||
'a[href]',
|
||||
'[tabindex]:not([disabled])'
|
||||
].join(', ');
|
||||
|
||||
exports.FOCUSABLE_SELECTOR = FOCUSABLE_SELECTOR;
|
||||
Reference in New Issue
Block a user