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:
14
frontend/node_modules/@testing-library/user-event/dist/esm/utils/dataTransfer/Blob.js
generated
vendored
Normal file
14
frontend/node_modules/@testing-library/user-event/dist/esm/utils/dataTransfer/Blob.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// jsdom does not implement Blob.text()
|
||||
function readBlobText(blob, FileReader) {
|
||||
return new Promise((res, rej)=>{
|
||||
const fr = new FileReader();
|
||||
fr.onerror = rej;
|
||||
fr.onabort = rej;
|
||||
fr.onload = ()=>{
|
||||
res(String(fr.result));
|
||||
};
|
||||
fr.readAsText(blob);
|
||||
});
|
||||
}
|
||||
|
||||
export { readBlobText };
|
||||
162
frontend/node_modules/@testing-library/user-event/dist/esm/utils/dataTransfer/Clipboard.js
generated
vendored
Normal file
162
frontend/node_modules/@testing-library/user-event/dist/esm/utils/dataTransfer/Clipboard.js
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
import { getWindow } from '../misc/getWindow.js';
|
||||
import { readBlobText } from './Blob.js';
|
||||
import { createDataTransfer, getBlobFromDataTransferItem } from './DataTransfer.js';
|
||||
|
||||
// Clipboard is not available in jsdom
|
||||
function _define_property(obj, key, value) {
|
||||
if (key in obj) {
|
||||
Object.defineProperty(obj, key, {
|
||||
value: value,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true
|
||||
});
|
||||
} else {
|
||||
obj[key] = value;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
// MDN lists string|Blob|Promise<Blob|string> as possible types in ClipboardItemData
|
||||
// lib.dom.d.ts lists only Promise<Blob|string>
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem/ClipboardItem#syntax
|
||||
function createClipboardItem(window, ...blobs) {
|
||||
const dataMap = Object.fromEntries(blobs.map((b)=>[
|
||||
typeof b === 'string' ? 'text/plain' : b.type,
|
||||
Promise.resolve(b)
|
||||
]));
|
||||
// use real ClipboardItem if available
|
||||
/* istanbul ignore if */ if (typeof window.ClipboardItem !== 'undefined') {
|
||||
return new window.ClipboardItem(dataMap);
|
||||
}
|
||||
return new class ClipboardItem {
|
||||
get types() {
|
||||
return Array.from(Object.keys(this.data));
|
||||
}
|
||||
async getType(type) {
|
||||
const value = await this.data[type];
|
||||
if (!value) {
|
||||
throw new Error(`${type} is not one of the available MIME types on this item.`);
|
||||
}
|
||||
return value instanceof window.Blob ? value : new window.Blob([
|
||||
value
|
||||
], {
|
||||
type
|
||||
});
|
||||
}
|
||||
constructor(d){
|
||||
_define_property(this, "data", undefined);
|
||||
this.data = d;
|
||||
}
|
||||
}(dataMap);
|
||||
}
|
||||
const ClipboardStubControl = Symbol('Manage ClipboardSub');
|
||||
function createClipboardStub(window, control) {
|
||||
return Object.assign(new class Clipboard extends window.EventTarget {
|
||||
async read() {
|
||||
return Array.from(this.items);
|
||||
}
|
||||
async readText() {
|
||||
let text = '';
|
||||
for (const item of this.items){
|
||||
const type = item.types.includes('text/plain') ? 'text/plain' : item.types.find((t)=>t.startsWith('text/'));
|
||||
if (type) {
|
||||
text += await item.getType(type).then((b)=>readBlobText(b, window.FileReader));
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
async write(data) {
|
||||
this.items = data;
|
||||
}
|
||||
async writeText(text) {
|
||||
this.items = [
|
||||
createClipboardItem(window, text)
|
||||
];
|
||||
}
|
||||
constructor(...args){
|
||||
super(...args), _define_property(this, "items", []);
|
||||
}
|
||||
}(), {
|
||||
[ClipboardStubControl]: control
|
||||
});
|
||||
}
|
||||
function isClipboardStub(clipboard) {
|
||||
return !!(clipboard === null || clipboard === undefined ? undefined : clipboard[ClipboardStubControl]);
|
||||
}
|
||||
function attachClipboardStubToView(window) {
|
||||
if (isClipboardStub(window.navigator.clipboard)) {
|
||||
return window.navigator.clipboard[ClipboardStubControl];
|
||||
}
|
||||
const realClipboard = Object.getOwnPropertyDescriptor(window.navigator, 'clipboard');
|
||||
let stub;
|
||||
const control = {
|
||||
resetClipboardStub: ()=>{
|
||||
stub = createClipboardStub(window, control);
|
||||
},
|
||||
detachClipboardStub: ()=>{
|
||||
/* istanbul ignore if */ if (realClipboard) {
|
||||
Object.defineProperty(window.navigator, 'clipboard', realClipboard);
|
||||
} else {
|
||||
Object.defineProperty(window.navigator, 'clipboard', {
|
||||
value: undefined,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
stub = createClipboardStub(window, control);
|
||||
Object.defineProperty(window.navigator, 'clipboard', {
|
||||
get: ()=>stub,
|
||||
configurable: true
|
||||
});
|
||||
return stub[ClipboardStubControl];
|
||||
}
|
||||
function resetClipboardStubOnView(window) {
|
||||
if (isClipboardStub(window.navigator.clipboard)) {
|
||||
window.navigator.clipboard[ClipboardStubControl].resetClipboardStub();
|
||||
}
|
||||
}
|
||||
function detachClipboardStubFromView(window) {
|
||||
if (isClipboardStub(window.navigator.clipboard)) {
|
||||
window.navigator.clipboard[ClipboardStubControl].detachClipboardStub();
|
||||
}
|
||||
}
|
||||
async function readDataTransferFromClipboard(document) {
|
||||
const window = document.defaultView;
|
||||
const clipboard = window === null || window === undefined ? undefined : window.navigator.clipboard;
|
||||
const items = clipboard && await clipboard.read();
|
||||
if (!items) {
|
||||
throw new Error('The Clipboard API is unavailable.');
|
||||
}
|
||||
const dt = createDataTransfer(window);
|
||||
for (const item of items){
|
||||
for (const type of item.types){
|
||||
dt.setData(type, await item.getType(type).then((b)=>readBlobText(b, window.FileReader)));
|
||||
}
|
||||
}
|
||||
return dt;
|
||||
}
|
||||
async function writeDataTransferToClipboard(document, clipboardData) {
|
||||
const window = getWindow(document);
|
||||
const clipboard = window.navigator.clipboard;
|
||||
const items = [];
|
||||
for(let i = 0; i < clipboardData.items.length; i++){
|
||||
const dtItem = clipboardData.items[i];
|
||||
const blob = await getBlobFromDataTransferItem(window, dtItem);
|
||||
items.push(createClipboardItem(window, blob));
|
||||
}
|
||||
const written = clipboard && await clipboard.write(items).then(()=>true, // Can happen with other implementations that e.g. require permissions
|
||||
/* istanbul ignore next */ ()=>false);
|
||||
if (!written) {
|
||||
throw new Error('The Clipboard API is unavailable.');
|
||||
}
|
||||
}
|
||||
const g = globalThis;
|
||||
/* istanbul ignore else */ if (typeof g.afterEach === 'function') {
|
||||
g.afterEach(()=>resetClipboardStubOnView(globalThis.window));
|
||||
}
|
||||
/* istanbul ignore else */ if (typeof g.afterAll === 'function') {
|
||||
g.afterAll(()=>detachClipboardStubFromView(globalThis.window));
|
||||
}
|
||||
|
||||
export { attachClipboardStubToView, createClipboardItem, detachClipboardStubFromView, readDataTransferFromClipboard, resetClipboardStubOnView, writeDataTransferToClipboard };
|
||||
133
frontend/node_modules/@testing-library/user-event/dist/esm/utils/dataTransfer/DataTransfer.js
generated
vendored
Normal file
133
frontend/node_modules/@testing-library/user-event/dist/esm/utils/dataTransfer/DataTransfer.js
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
import { createFileList } from './FileList.js';
|
||||
|
||||
function _define_property(obj, key, value) {
|
||||
if (key in obj) {
|
||||
Object.defineProperty(obj, key, {
|
||||
value: value,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true
|
||||
});
|
||||
} else {
|
||||
obj[key] = value;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
// DataTransfer is not implemented in jsdom.
|
||||
// DataTransfer with FileList is being created by the browser on certain events.
|
||||
class DataTransferItemStub {
|
||||
getAsFile() {
|
||||
return this.file;
|
||||
}
|
||||
getAsString(callback) {
|
||||
if (typeof this.data === 'string') {
|
||||
callback(this.data);
|
||||
}
|
||||
}
|
||||
/* istanbul ignore next */ webkitGetAsEntry() {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
constructor(dataOrFile, type){
|
||||
_define_property(this, "kind", undefined);
|
||||
_define_property(this, "type", undefined);
|
||||
_define_property(this, "file", null);
|
||||
_define_property(this, "data", undefined);
|
||||
if (typeof dataOrFile === 'string') {
|
||||
this.kind = 'string';
|
||||
this.type = String(type);
|
||||
this.data = dataOrFile;
|
||||
} else {
|
||||
this.kind = 'file';
|
||||
this.type = dataOrFile.type;
|
||||
this.file = dataOrFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
class DataTransferItemListStub extends Array {
|
||||
add(...args) {
|
||||
const item = new DataTransferItemStub(args[0], args[1]);
|
||||
this.push(item);
|
||||
return item;
|
||||
}
|
||||
clear() {
|
||||
this.splice(0, this.length);
|
||||
}
|
||||
remove(index) {
|
||||
this.splice(index, 1);
|
||||
}
|
||||
}
|
||||
function getTypeMatcher(type, exact) {
|
||||
const [group, sub] = type.split('/');
|
||||
const isGroup = !sub || sub === '*';
|
||||
return (item)=>{
|
||||
return exact ? item.type === (isGroup ? group : type) : isGroup ? item.type.startsWith(`${group}/`) : item.type === group;
|
||||
};
|
||||
}
|
||||
function createDataTransferStub(window) {
|
||||
return new class DataTransferStub {
|
||||
getData(format) {
|
||||
var _this_items_find;
|
||||
const match = (_this_items_find = this.items.find(getTypeMatcher(format, true))) !== null && _this_items_find !== undefined ? _this_items_find : this.items.find(getTypeMatcher(format, false));
|
||||
let text = '';
|
||||
match === null || match === undefined ? undefined : match.getAsString((t)=>{
|
||||
text = t;
|
||||
});
|
||||
return text;
|
||||
}
|
||||
setData(format, data) {
|
||||
const matchIndex = this.items.findIndex(getTypeMatcher(format, true));
|
||||
const item = new DataTransferItemStub(data, format);
|
||||
if (matchIndex >= 0) {
|
||||
this.items.splice(matchIndex, 1, item);
|
||||
} else {
|
||||
this.items.push(item);
|
||||
}
|
||||
}
|
||||
clearData(format) {
|
||||
if (format) {
|
||||
const matchIndex = this.items.findIndex(getTypeMatcher(format, true));
|
||||
if (matchIndex >= 0) {
|
||||
this.items.remove(matchIndex);
|
||||
}
|
||||
} else {
|
||||
this.items.clear();
|
||||
}
|
||||
}
|
||||
get types() {
|
||||
const t = [];
|
||||
if (this.files.length) {
|
||||
t.push('Files');
|
||||
}
|
||||
this.items.forEach((i)=>t.push(i.type));
|
||||
Object.freeze(t);
|
||||
return t;
|
||||
}
|
||||
/* istanbul ignore next */ setDragImage() {}
|
||||
constructor(){
|
||||
_define_property(this, "dropEffect", 'none');
|
||||
_define_property(this, "effectAllowed", 'uninitialized');
|
||||
_define_property(this, "items", new DataTransferItemListStub());
|
||||
_define_property(this, "files", createFileList(window, []));
|
||||
}
|
||||
}();
|
||||
}
|
||||
function createDataTransfer(window, files = []) {
|
||||
// Use real DataTransfer if available
|
||||
const dt = typeof window.DataTransfer === 'undefined' ? createDataTransferStub(window) : /* istanbul ignore next */ new window.DataTransfer();
|
||||
Object.defineProperty(dt, 'files', {
|
||||
get: ()=>createFileList(window, files)
|
||||
});
|
||||
return dt;
|
||||
}
|
||||
async function getBlobFromDataTransferItem(window, item) {
|
||||
if (item.kind === 'file') {
|
||||
return item.getAsFile();
|
||||
}
|
||||
return new window.Blob([
|
||||
await new Promise((r)=>item.getAsString(r))
|
||||
], {
|
||||
type: item.type
|
||||
});
|
||||
}
|
||||
|
||||
export { createDataTransfer, getBlobFromDataTransferItem };
|
||||
22
frontend/node_modules/@testing-library/user-event/dist/esm/utils/dataTransfer/FileList.js
generated
vendored
Normal file
22
frontend/node_modules/@testing-library/user-event/dist/esm/utils/dataTransfer/FileList.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// FileList can not be created per constructor.
|
||||
function createFileList(window, files) {
|
||||
const list = {
|
||||
...files,
|
||||
length: files.length,
|
||||
item: (index)=>list[index],
|
||||
[Symbol.iterator]: function* nextFile() {
|
||||
for(let i = 0; i < list.length; i++){
|
||||
yield list[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
list.constructor = window.FileList;
|
||||
// guard for environments without FileList
|
||||
/* istanbul ignore else */ if (window.FileList) {
|
||||
Object.setPrototypeOf(list, window.FileList.prototype);
|
||||
}
|
||||
Object.freeze(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
export { createFileList };
|
||||
Reference in New Issue
Block a user