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:
92
frontend/node_modules/@testing-library/user-event/dist/cjs/document/UI.js
generated
vendored
Normal file
92
frontend/node_modules/@testing-library/user-event/dist/cjs/document/UI.js
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
'use strict';
|
||||
|
||||
const UIValue = Symbol('Displayed value in UI');
|
||||
const UISelection = Symbol('Displayed selection in UI');
|
||||
const InitialValue = Symbol('Initial value to compare on blur');
|
||||
function isUIValue(value) {
|
||||
return typeof value === 'object' && UIValue in value;
|
||||
}
|
||||
function isUISelectionStart(start) {
|
||||
return !!start && typeof start === 'object' && UISelection in start;
|
||||
}
|
||||
function setUIValue(element, value) {
|
||||
if (element[InitialValue] === undefined) {
|
||||
element[InitialValue] = element.value;
|
||||
}
|
||||
element[UIValue] = value;
|
||||
// eslint-disable-next-line no-new-wrappers
|
||||
element.value = Object.assign(new String(value), {
|
||||
[UIValue]: true
|
||||
});
|
||||
}
|
||||
function getUIValue(element) {
|
||||
return element[UIValue] === undefined ? element.value : String(element[UIValue]);
|
||||
}
|
||||
/** Flag the IDL value as clean. This does not change the value.*/ function setUIValueClean(element) {
|
||||
element[UIValue] = undefined;
|
||||
}
|
||||
function clearInitialValue(element) {
|
||||
element[InitialValue] = undefined;
|
||||
}
|
||||
function getInitialValue(element) {
|
||||
return element[InitialValue];
|
||||
}
|
||||
function setUISelectionRaw(element, selection) {
|
||||
element[UISelection] = selection;
|
||||
}
|
||||
function setUISelection(element, { focusOffset: focusOffsetParam, anchorOffset: anchorOffsetParam = focusOffsetParam }, mode = 'replace') {
|
||||
const valueLength = getUIValue(element).length;
|
||||
const sanitizeOffset = (o)=>Math.max(0, Math.min(valueLength, o));
|
||||
const anchorOffset = mode === 'replace' || element[UISelection] === undefined ? sanitizeOffset(anchorOffsetParam) : element[UISelection].anchorOffset;
|
||||
const focusOffset = sanitizeOffset(focusOffsetParam);
|
||||
const startOffset = Math.min(anchorOffset, focusOffset);
|
||||
const endOffset = Math.max(anchorOffset, focusOffset);
|
||||
element[UISelection] = {
|
||||
anchorOffset,
|
||||
focusOffset
|
||||
};
|
||||
if (element.selectionStart === startOffset && element.selectionEnd === endOffset) {
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-new-wrappers
|
||||
const startObj = Object.assign(new Number(startOffset), {
|
||||
[UISelection]: true
|
||||
});
|
||||
try {
|
||||
element.setSelectionRange(startObj, endOffset);
|
||||
} catch {
|
||||
// DOMException for invalid state is expected when calling this
|
||||
// on an element without support for setSelectionRange
|
||||
}
|
||||
}
|
||||
function getUISelection(element) {
|
||||
var _element_selectionStart, _element_selectionEnd, _element_UISelection;
|
||||
const sel = (_element_UISelection = element[UISelection]) !== null && _element_UISelection !== undefined ? _element_UISelection : {
|
||||
anchorOffset: (_element_selectionStart = element.selectionStart) !== null && _element_selectionStart !== undefined ? _element_selectionStart : 0,
|
||||
focusOffset: (_element_selectionEnd = element.selectionEnd) !== null && _element_selectionEnd !== undefined ? _element_selectionEnd : 0
|
||||
};
|
||||
return {
|
||||
...sel,
|
||||
startOffset: Math.min(sel.anchorOffset, sel.focusOffset),
|
||||
endOffset: Math.max(sel.anchorOffset, sel.focusOffset)
|
||||
};
|
||||
}
|
||||
function hasUISelection(element) {
|
||||
return !!element[UISelection];
|
||||
}
|
||||
/** Flag the IDL selection as clean. This does not change the selection. */ function setUISelectionClean(element) {
|
||||
element[UISelection] = undefined;
|
||||
}
|
||||
|
||||
exports.clearInitialValue = clearInitialValue;
|
||||
exports.getInitialValue = getInitialValue;
|
||||
exports.getUISelection = getUISelection;
|
||||
exports.getUIValue = getUIValue;
|
||||
exports.hasUISelection = hasUISelection;
|
||||
exports.isUISelectionStart = isUISelectionStart;
|
||||
exports.isUIValue = isUIValue;
|
||||
exports.setUISelection = setUISelection;
|
||||
exports.setUISelectionClean = setUISelectionClean;
|
||||
exports.setUISelectionRaw = setUISelectionRaw;
|
||||
exports.setUIValue = setUIValue;
|
||||
exports.setUIValueClean = setUIValueClean;
|
||||
29
frontend/node_modules/@testing-library/user-event/dist/cjs/document/copySelection.js
generated
vendored
Normal file
29
frontend/node_modules/@testing-library/user-event/dist/cjs/document/copySelection.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
var DataTransfer = require('../utils/dataTransfer/DataTransfer.js');
|
||||
require('../utils/dataTransfer/Clipboard.js');
|
||||
var getWindow = require('../utils/misc/getWindow.js');
|
||||
var selection = require('../utils/focus/selection.js');
|
||||
var UI = require('./UI.js');
|
||||
|
||||
function copySelection(target) {
|
||||
const data = selection.hasOwnSelection(target) ? {
|
||||
'text/plain': readSelectedValueFromInput(target)
|
||||
} : {
|
||||
'text/plain': String(target.ownerDocument.getSelection())
|
||||
};
|
||||
const dt = DataTransfer.createDataTransfer(getWindow.getWindow(target));
|
||||
for(const type in data){
|
||||
if (data[type]) {
|
||||
dt.setData(type, data[type]);
|
||||
}
|
||||
}
|
||||
return dt;
|
||||
}
|
||||
function readSelectedValueFromInput(target) {
|
||||
const sel = UI.getUISelection(target);
|
||||
const val = UI.getUIValue(target);
|
||||
return val.substring(sel.startOffset, sel.endOffset);
|
||||
}
|
||||
|
||||
exports.copySelection = copySelection;
|
||||
18
frontend/node_modules/@testing-library/user-event/dist/cjs/document/getValueOrTextContent.js
generated
vendored
Normal file
18
frontend/node_modules/@testing-library/user-event/dist/cjs/document/getValueOrTextContent.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
require('../utils/dataTransfer/Clipboard.js');
|
||||
var isContentEditable = require('../utils/edit/isContentEditable.js');
|
||||
var UI = require('./UI.js');
|
||||
|
||||
function getValueOrTextContent(element) {
|
||||
// istanbul ignore if
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
if (isContentEditable.isContentEditable(element)) {
|
||||
return element.textContent;
|
||||
}
|
||||
return UI.getUIValue(element);
|
||||
}
|
||||
|
||||
exports.getValueOrTextContent = getValueOrTextContent;
|
||||
17
frontend/node_modules/@testing-library/user-event/dist/cjs/document/index.js
generated
vendored
Normal file
17
frontend/node_modules/@testing-library/user-event/dist/cjs/document/index.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
var UI = require('./UI.js');
|
||||
var getValueOrTextContent = require('./getValueOrTextContent.js');
|
||||
var copySelection = require('./copySelection.js');
|
||||
var trackValue = require('./trackValue.js');
|
||||
|
||||
|
||||
|
||||
exports.clearInitialValue = UI.clearInitialValue;
|
||||
exports.getUISelection = UI.getUISelection;
|
||||
exports.getUIValue = UI.getUIValue;
|
||||
exports.setUISelection = UI.setUISelection;
|
||||
exports.setUIValue = UI.setUIValue;
|
||||
exports.getValueOrTextContent = getValueOrTextContent.getValueOrTextContent;
|
||||
exports.copySelection = copySelection.copySelection;
|
||||
exports.commitValueAfterInput = trackValue.commitValueAfterInput;
|
||||
104
frontend/node_modules/@testing-library/user-event/dist/cjs/document/interceptor.js
generated
vendored
Normal file
104
frontend/node_modules/@testing-library/user-event/dist/cjs/document/interceptor.js
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
'use strict';
|
||||
|
||||
var isElementType = require('../utils/misc/isElementType.js');
|
||||
require('../utils/dataTransfer/Clipboard.js');
|
||||
var trackValue = require('./trackValue.js');
|
||||
var UI = require('./UI.js');
|
||||
|
||||
const Interceptor = Symbol('Interceptor for programmatical calls');
|
||||
function prepareInterceptor(element, propName, interceptorImpl) {
|
||||
const prototypeDescriptor = Object.getOwnPropertyDescriptor(element.constructor.prototype, propName);
|
||||
const objectDescriptor = Object.getOwnPropertyDescriptor(element, propName);
|
||||
const target = (prototypeDescriptor === null || prototypeDescriptor === undefined ? undefined : prototypeDescriptor.set) ? 'set' : 'value';
|
||||
/* istanbul ignore if */ if (typeof (prototypeDescriptor === null || prototypeDescriptor === undefined ? undefined : prototypeDescriptor[target]) !== 'function' || prototypeDescriptor[target][Interceptor]) {
|
||||
throw new Error(`Element ${element.tagName} does not implement "${String(propName)}".`);
|
||||
}
|
||||
function intercept(...args) {
|
||||
const { applyNative = false, realArgs, then } = interceptorImpl.call(this, ...args);
|
||||
const realFunc = (!applyNative && objectDescriptor || prototypeDescriptor)[target];
|
||||
if (target === 'set') {
|
||||
realFunc.call(this, realArgs);
|
||||
} else {
|
||||
realFunc.call(this, ...realArgs);
|
||||
}
|
||||
then === null || then === undefined ? undefined : then();
|
||||
}
|
||||
intercept[Interceptor] = Interceptor;
|
||||
Object.defineProperty(element, propName, {
|
||||
...objectDescriptor !== null && objectDescriptor !== undefined ? objectDescriptor : prototypeDescriptor,
|
||||
[target]: intercept
|
||||
});
|
||||
}
|
||||
function prepareValueInterceptor(element) {
|
||||
prepareInterceptor(element, 'value', function interceptorImpl(v) {
|
||||
const isUI = UI.isUIValue(v);
|
||||
if (isUI) {
|
||||
trackValue.startTrackValue(this);
|
||||
}
|
||||
return {
|
||||
applyNative: !!isUI,
|
||||
realArgs: sanitizeValue(this, v),
|
||||
then: isUI ? undefined : ()=>trackValue.trackOrSetValue(this, String(v))
|
||||
};
|
||||
});
|
||||
}
|
||||
function sanitizeValue(element, v) {
|
||||
// Workaround for JSDOM
|
||||
if (isElementType.isElementType(element, 'input', {
|
||||
type: 'number'
|
||||
}) && String(v) !== '' && !Number.isNaN(Number(v))) {
|
||||
// Setting value to "1." results in `null` in JSDOM
|
||||
return String(Number(v));
|
||||
}
|
||||
return String(v);
|
||||
}
|
||||
function prepareSelectionInterceptor(element) {
|
||||
prepareInterceptor(element, 'setSelectionRange', function interceptorImpl(start, ...others) {
|
||||
const isUI = UI.isUISelectionStart(start);
|
||||
return {
|
||||
applyNative: !!isUI,
|
||||
realArgs: [
|
||||
Number(start),
|
||||
...others
|
||||
],
|
||||
then: ()=>isUI ? undefined : UI.setUISelectionClean(element)
|
||||
};
|
||||
});
|
||||
prepareInterceptor(element, 'selectionStart', function interceptorImpl(v) {
|
||||
return {
|
||||
realArgs: v,
|
||||
then: ()=>UI.setUISelectionClean(element)
|
||||
};
|
||||
});
|
||||
prepareInterceptor(element, 'selectionEnd', function interceptorImpl(v) {
|
||||
return {
|
||||
realArgs: v,
|
||||
then: ()=>UI.setUISelectionClean(element)
|
||||
};
|
||||
});
|
||||
prepareInterceptor(element, 'select', function interceptorImpl() {
|
||||
return {
|
||||
realArgs: [],
|
||||
then: ()=>UI.setUISelectionRaw(element, {
|
||||
anchorOffset: 0,
|
||||
focusOffset: UI.getUIValue(element).length
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
function prepareRangeTextInterceptor(element) {
|
||||
prepareInterceptor(element, 'setRangeText', function interceptorImpl(...realArgs) {
|
||||
return {
|
||||
realArgs,
|
||||
then: ()=>{
|
||||
UI.setUIValueClean(element);
|
||||
UI.setUISelectionClean(element);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
exports.prepareInterceptor = prepareInterceptor;
|
||||
exports.prepareRangeTextInterceptor = prepareRangeTextInterceptor;
|
||||
exports.prepareSelectionInterceptor = prepareSelectionInterceptor;
|
||||
exports.prepareValueInterceptor = prepareValueInterceptor;
|
||||
104
frontend/node_modules/@testing-library/user-event/dist/cjs/document/patchFocus.js
generated
vendored
Normal file
104
frontend/node_modules/@testing-library/user-event/dist/cjs/document/patchFocus.js
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
'use strict';
|
||||
|
||||
var dispatchEvent = require('../event/dispatchEvent.js');
|
||||
require('../utils/dataTransfer/Clipboard.js');
|
||||
var getActiveElement = require('../utils/focus/getActiveElement.js');
|
||||
require('@testing-library/dom');
|
||||
|
||||
const patched = Symbol('patched focus/blur methods');
|
||||
function patchFocus(HTMLElement) {
|
||||
if (HTMLElement.prototype[patched]) {
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
const { focus, blur } = HTMLElement.prototype;
|
||||
Object.defineProperties(HTMLElement.prototype, {
|
||||
focus: {
|
||||
configurable: true,
|
||||
get: ()=>patchedFocus
|
||||
},
|
||||
blur: {
|
||||
configurable: true,
|
||||
get: ()=>patchedBlur
|
||||
},
|
||||
[patched]: {
|
||||
configurable: true,
|
||||
get: ()=>({
|
||||
focus,
|
||||
blur
|
||||
})
|
||||
}
|
||||
});
|
||||
let activeCall;
|
||||
function patchedFocus(options) {
|
||||
if (this.ownerDocument.visibilityState !== 'hidden') {
|
||||
return focus.call(this, options);
|
||||
}
|
||||
const blurred = getActiveTarget(this.ownerDocument);
|
||||
if (blurred === this) {
|
||||
return;
|
||||
}
|
||||
const thisCall = Symbol('focus call');
|
||||
activeCall = thisCall;
|
||||
if (blurred) {
|
||||
blur.call(blurred);
|
||||
dispatchEvent.dispatchDOMEvent(blurred, 'blur', {
|
||||
relatedTarget: this
|
||||
});
|
||||
dispatchEvent.dispatchDOMEvent(blurred, 'focusout', {
|
||||
relatedTarget: activeCall === thisCall ? this : null
|
||||
});
|
||||
}
|
||||
if (activeCall === thisCall) {
|
||||
focus.call(this, options);
|
||||
dispatchEvent.dispatchDOMEvent(this, 'focus', {
|
||||
relatedTarget: blurred
|
||||
});
|
||||
}
|
||||
if (activeCall === thisCall) {
|
||||
dispatchEvent.dispatchDOMEvent(this, 'focusin', {
|
||||
relatedTarget: blurred
|
||||
});
|
||||
}
|
||||
}
|
||||
function patchedBlur() {
|
||||
if (this.ownerDocument.visibilityState !== 'hidden') {
|
||||
return blur.call(this);
|
||||
}
|
||||
const blurred = getActiveTarget(this.ownerDocument);
|
||||
if (blurred !== this) {
|
||||
return;
|
||||
}
|
||||
const thisCall = Symbol('blur call');
|
||||
activeCall = thisCall;
|
||||
blur.call(this);
|
||||
dispatchEvent.dispatchDOMEvent(blurred, 'blur', {
|
||||
relatedTarget: null
|
||||
});
|
||||
dispatchEvent.dispatchDOMEvent(blurred, 'focusout', {
|
||||
relatedTarget: null
|
||||
});
|
||||
}
|
||||
}
|
||||
function getActiveTarget(document) {
|
||||
const active = getActiveElement.getActiveElement(document);
|
||||
return (active === null || active === undefined ? undefined : active.tagName) === 'BODY' ? null : active;
|
||||
}
|
||||
function restoreFocus(HTMLElement) {
|
||||
if (HTMLElement.prototype[patched]) {
|
||||
const { focus, blur } = HTMLElement.prototype[patched];
|
||||
Object.defineProperties(HTMLElement.prototype, {
|
||||
focus: {
|
||||
configurable: true,
|
||||
get: ()=>focus
|
||||
},
|
||||
blur: {
|
||||
configurable: true,
|
||||
get: ()=>blur
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
exports.patchFocus = patchFocus;
|
||||
exports.restoreFocus = restoreFocus;
|
||||
58
frontend/node_modules/@testing-library/user-event/dist/cjs/document/prepareDocument.js
generated
vendored
Normal file
58
frontend/node_modules/@testing-library/user-event/dist/cjs/document/prepareDocument.js
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
var dispatchEvent = require('../event/dispatchEvent.js');
|
||||
var isElementType = require('../utils/misc/isElementType.js');
|
||||
require('../utils/dataTransfer/Clipboard.js');
|
||||
var UI = require('./UI.js');
|
||||
require('@testing-library/dom');
|
||||
var interceptor = require('./interceptor.js');
|
||||
|
||||
const isPrepared = Symbol('Node prepared with document state workarounds');
|
||||
function prepareDocument(document) {
|
||||
if (document[isPrepared]) {
|
||||
return;
|
||||
}
|
||||
document.addEventListener('focus', (e)=>{
|
||||
const el = e.target;
|
||||
prepareElement(el);
|
||||
}, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
// Our test environment defaults to `document.body` as `activeElement`.
|
||||
// In other environments this might be `null` when preparing.
|
||||
// istanbul ignore else
|
||||
if (document.activeElement) {
|
||||
prepareElement(document.activeElement);
|
||||
}
|
||||
document.addEventListener('blur', (e)=>{
|
||||
const el = e.target;
|
||||
const initialValue = UI.getInitialValue(el);
|
||||
if (initialValue !== undefined) {
|
||||
if (el.value !== initialValue) {
|
||||
dispatchEvent.dispatchDOMEvent(el, 'change');
|
||||
}
|
||||
UI.clearInitialValue(el);
|
||||
}
|
||||
}, {
|
||||
capture: true,
|
||||
passive: true
|
||||
});
|
||||
document[isPrepared] = isPrepared;
|
||||
}
|
||||
function prepareElement(el) {
|
||||
if (el[isPrepared]) {
|
||||
return;
|
||||
}
|
||||
if (isElementType.isElementType(el, [
|
||||
'input',
|
||||
'textarea'
|
||||
])) {
|
||||
interceptor.prepareValueInterceptor(el);
|
||||
interceptor.prepareSelectionInterceptor(el);
|
||||
interceptor.prepareRangeTextInterceptor(el);
|
||||
}
|
||||
el[isPrepared] = isPrepared;
|
||||
}
|
||||
|
||||
exports.prepareDocument = prepareDocument;
|
||||
57
frontend/node_modules/@testing-library/user-event/dist/cjs/document/trackValue.js
generated
vendored
Normal file
57
frontend/node_modules/@testing-library/user-event/dist/cjs/document/trackValue.js
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
require('../utils/dataTransfer/Clipboard.js');
|
||||
var getWindow = require('../utils/misc/getWindow.js');
|
||||
var UI = require('./UI.js');
|
||||
|
||||
const TrackChanges = Symbol('Track programmatic changes for React workaround');
|
||||
// When the input event happens in the browser, React executes all event handlers
|
||||
// and if they change state of a controlled value, nothing happens.
|
||||
// But when we trigger the event handlers in test environment with React@17,
|
||||
// the changes are rolled back before the state update is applied.
|
||||
// This results in a reset cursor.
|
||||
// There might be a better way to work around if we figure out
|
||||
// why the batched update is executed differently in our test environment.
|
||||
function isReact17Element(element) {
|
||||
return Object.getOwnPropertyNames(element).some((k)=>k.startsWith('__react')) && getWindow.getWindow(element).REACT_VERSION === 17;
|
||||
}
|
||||
function startTrackValue(element) {
|
||||
if (!isReact17Element(element)) {
|
||||
return;
|
||||
}
|
||||
element[TrackChanges] = {
|
||||
previousValue: String(element.value),
|
||||
tracked: []
|
||||
};
|
||||
}
|
||||
function trackOrSetValue(element, v) {
|
||||
var _element_TrackChanges_tracked, _element_TrackChanges;
|
||||
(_element_TrackChanges = element[TrackChanges]) === null || _element_TrackChanges === undefined ? undefined : (_element_TrackChanges_tracked = _element_TrackChanges.tracked) === null || _element_TrackChanges_tracked === undefined ? undefined : _element_TrackChanges_tracked.push(v);
|
||||
if (!element[TrackChanges]) {
|
||||
UI.setUIValueClean(element);
|
||||
UI.setUISelection(element, {
|
||||
focusOffset: v.length
|
||||
});
|
||||
}
|
||||
}
|
||||
function commitValueAfterInput(element, cursorOffset) {
|
||||
var _changes_tracked;
|
||||
const changes = element[TrackChanges];
|
||||
element[TrackChanges] = undefined;
|
||||
if (!(changes === null || changes === undefined ? undefined : (_changes_tracked = changes.tracked) === null || _changes_tracked === undefined ? undefined : _changes_tracked.length)) {
|
||||
return;
|
||||
}
|
||||
const isJustReactStateUpdate = changes.tracked.length === 2 && changes.tracked[0] === changes.previousValue && changes.tracked[1] === element.value;
|
||||
if (!isJustReactStateUpdate) {
|
||||
UI.setUIValueClean(element);
|
||||
}
|
||||
if (UI.hasUISelection(element)) {
|
||||
UI.setUISelection(element, {
|
||||
focusOffset: isJustReactStateUpdate ? cursorOffset : element.value.length
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
exports.commitValueAfterInput = commitValueAfterInput;
|
||||
exports.startTrackValue = startTrackValue;
|
||||
exports.trackOrSetValue = trackOrSetValue;
|
||||
Reference in New Issue
Block a user