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:
15
frontend/node_modules/@testing-library/user-event/dist/esm/utils/edit/isContentEditable.js
generated
vendored
Normal file
15
frontend/node_modules/@testing-library/user-event/dist/esm/utils/edit/isContentEditable.js
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
//jsdom is not supporting isContentEditable
|
||||
function isContentEditable(element) {
|
||||
return element.hasAttribute('contenteditable') && (element.getAttribute('contenteditable') == 'true' || element.getAttribute('contenteditable') == '');
|
||||
}
|
||||
/**
|
||||
* If a node is a contenteditable or inside one, return that element.
|
||||
*/ function getContentEditable(node) {
|
||||
const element = getElement(node);
|
||||
return element && (element.closest('[contenteditable=""]') || element.closest('[contenteditable="true"]'));
|
||||
}
|
||||
function getElement(node) {
|
||||
return node.nodeType === 1 ? node : node.parentElement;
|
||||
}
|
||||
|
||||
export { getContentEditable, isContentEditable };
|
||||
26
frontend/node_modules/@testing-library/user-event/dist/esm/utils/edit/isEditable.js
generated
vendored
Normal file
26
frontend/node_modules/@testing-library/user-event/dist/esm/utils/edit/isEditable.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import { isElementType } from '../misc/isElementType.js';
|
||||
import { isContentEditable } from './isContentEditable.js';
|
||||
|
||||
function isEditable(element) {
|
||||
return isEditableInputOrTextArea(element) && !element.readOnly || isContentEditable(element);
|
||||
}
|
||||
var editableInputTypes = /*#__PURE__*/ function(editableInputTypes) {
|
||||
editableInputTypes["text"] = "text";
|
||||
editableInputTypes["date"] = "date";
|
||||
editableInputTypes["datetime-local"] = "datetime-local";
|
||||
editableInputTypes["email"] = "email";
|
||||
editableInputTypes["month"] = "month";
|
||||
editableInputTypes["number"] = "number";
|
||||
editableInputTypes["password"] = "password";
|
||||
editableInputTypes["search"] = "search";
|
||||
editableInputTypes["tel"] = "tel";
|
||||
editableInputTypes["time"] = "time";
|
||||
editableInputTypes["url"] = "url";
|
||||
editableInputTypes["week"] = "week";
|
||||
return editableInputTypes;
|
||||
}(editableInputTypes || {});
|
||||
function isEditableInputOrTextArea(element) {
|
||||
return isElementType(element, 'textarea') || isElementType(element, 'input') && element.type in editableInputTypes;
|
||||
}
|
||||
|
||||
export { isEditable, isEditableInputOrTextArea };
|
||||
23
frontend/node_modules/@testing-library/user-event/dist/esm/utils/edit/maxLength.js
generated
vendored
Normal file
23
frontend/node_modules/@testing-library/user-event/dist/esm/utils/edit/maxLength.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import { isElementType } from '../misc/isElementType.js';
|
||||
|
||||
var maxLengthSupportedTypes = /*#__PURE__*/ function(maxLengthSupportedTypes) {
|
||||
maxLengthSupportedTypes["email"] = "email";
|
||||
maxLengthSupportedTypes["password"] = "password";
|
||||
maxLengthSupportedTypes["search"] = "search";
|
||||
maxLengthSupportedTypes["telephone"] = "telephone";
|
||||
maxLengthSupportedTypes["text"] = "text";
|
||||
maxLengthSupportedTypes["url"] = "url";
|
||||
return maxLengthSupportedTypes;
|
||||
}(maxLengthSupportedTypes || {});
|
||||
// can't use .maxLength property because of a jsdom bug:
|
||||
// https://github.com/jsdom/jsdom/issues/2927
|
||||
function getMaxLength(element) {
|
||||
var _element_getAttribute;
|
||||
const attr = (_element_getAttribute = element.getAttribute('maxlength')) !== null && _element_getAttribute !== undefined ? _element_getAttribute : '';
|
||||
return /^\d+$/.test(attr) && Number(attr) >= 0 ? Number(attr) : undefined;
|
||||
}
|
||||
function supportsMaxLength(element) {
|
||||
return isElementType(element, 'textarea') || isElementType(element, 'input') && element.type in maxLengthSupportedTypes;
|
||||
}
|
||||
|
||||
export { getMaxLength, supportsMaxLength };
|
||||
57
frontend/node_modules/@testing-library/user-event/dist/esm/utils/edit/setFiles.js
generated
vendored
Normal file
57
frontend/node_modules/@testing-library/user-event/dist/esm/utils/edit/setFiles.js
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
// It is not possible to create a real FileList programmatically.
|
||||
// Therefore assigning `files` property with a programmatically created FileList results in an error.
|
||||
// Just assigning the property (as per fireEvent) breaks the interweaving with the `value` property.
|
||||
const fakeFiles = Symbol('files and value properties are mocked');
|
||||
function restoreProperty(obj, prop, descriptor) {
|
||||
if (descriptor) {
|
||||
Object.defineProperty(obj, prop, descriptor);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete obj[prop];
|
||||
}
|
||||
}
|
||||
function setFiles(el, files) {
|
||||
var _el_fakeFiles;
|
||||
(_el_fakeFiles = el[fakeFiles]) === null || _el_fakeFiles === undefined ? undefined : _el_fakeFiles.restore();
|
||||
const typeDescr = Object.getOwnPropertyDescriptor(el, 'type');
|
||||
const valueDescr = Object.getOwnPropertyDescriptor(el, 'value');
|
||||
const filesDescr = Object.getOwnPropertyDescriptor(el, 'files');
|
||||
function restore() {
|
||||
restoreProperty(el, 'type', typeDescr);
|
||||
restoreProperty(el, 'value', valueDescr);
|
||||
restoreProperty(el, 'files', filesDescr);
|
||||
}
|
||||
el[fakeFiles] = {
|
||||
restore
|
||||
};
|
||||
Object.defineProperties(el, {
|
||||
files: {
|
||||
configurable: true,
|
||||
get: ()=>files
|
||||
},
|
||||
value: {
|
||||
configurable: true,
|
||||
get: ()=>files.length ? `C:\\fakepath\\${files[0].name}` : '',
|
||||
set (v) {
|
||||
if (v === '') {
|
||||
restore();
|
||||
} else {
|
||||
var _valueDescr_set;
|
||||
valueDescr === null || valueDescr === undefined ? undefined : (_valueDescr_set = valueDescr.set) === null || _valueDescr_set === undefined ? undefined : _valueDescr_set.call(el, v);
|
||||
}
|
||||
}
|
||||
},
|
||||
type: {
|
||||
configurable: true,
|
||||
get: ()=>'file',
|
||||
set (v) {
|
||||
if (v !== 'file') {
|
||||
restore();
|
||||
el.type = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export { setFiles };
|
||||
37
frontend/node_modules/@testing-library/user-event/dist/esm/utils/edit/timeValue.js
generated
vendored
Normal file
37
frontend/node_modules/@testing-library/user-event/dist/esm/utils/edit/timeValue.js
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
const parseInt = globalThis.parseInt;
|
||||
function buildTimeValue(value) {
|
||||
const onlyDigitsValue = value.replace(/\D/g, '');
|
||||
if (onlyDigitsValue.length < 2) {
|
||||
return value;
|
||||
}
|
||||
const firstDigit = parseInt(onlyDigitsValue[0], 10);
|
||||
const secondDigit = parseInt(onlyDigitsValue[1], 10);
|
||||
if (firstDigit >= 3 || firstDigit === 2 && secondDigit >= 4) {
|
||||
let index;
|
||||
if (firstDigit >= 3) {
|
||||
index = 1;
|
||||
} else {
|
||||
index = 2;
|
||||
}
|
||||
return build(onlyDigitsValue, index);
|
||||
}
|
||||
if (value.length === 2) {
|
||||
return value;
|
||||
}
|
||||
return build(onlyDigitsValue, 2);
|
||||
}
|
||||
function build(onlyDigitsValue, index) {
|
||||
const hours = onlyDigitsValue.slice(0, index);
|
||||
const validHours = Math.min(parseInt(hours, 10), 23);
|
||||
const minuteCharacters = onlyDigitsValue.slice(index);
|
||||
const parsedMinutes = parseInt(minuteCharacters, 10);
|
||||
const validMinutes = Math.min(parsedMinutes, 59);
|
||||
return `${validHours.toString().padStart(2, '0')}:${validMinutes.toString().padStart(2, '0')}`;
|
||||
}
|
||||
function isValidDateOrTimeValue(element, value) {
|
||||
const clone = element.cloneNode();
|
||||
clone.value = value;
|
||||
return clone.value === value;
|
||||
}
|
||||
|
||||
export { buildTimeValue, isValidDateOrTimeValue };
|
||||
Reference in New Issue
Block a user