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

37
frontend/node_modules/cssstyle/lib/utils/camelize.js generated vendored Normal file
View File

@@ -0,0 +1,37 @@
"use strict";
const { asciiLowercase } = require("./strings");
// Utility to translate from `border-width` to `borderWidth`.
// NOTE: For values prefixed with webkit, e.g. `-webkit-foo`, we need to provide
// both `webkitFoo` and `WebkitFoo`. Here we only return `webkitFoo`.
exports.dashedToCamelCase = function (dashed) {
if (dashed.startsWith("--")) {
return dashed;
}
let camel = "";
let nextCap = false;
// skip leading hyphen in vendor prefixed value, e.g. -webkit-foo
let i = /^-webkit-/.test(dashed) ? 1 : 0;
for (; i < dashed.length; i++) {
if (dashed[i] !== "-") {
camel += nextCap ? dashed[i].toUpperCase() : dashed[i];
nextCap = false;
} else {
nextCap = true;
}
}
return camel;
};
// Utility to translate from `borderWidth` to `border-width`.
exports.camelCaseToDashed = function (camelCase) {
if (camelCase.startsWith("--")) {
return camelCase;
}
const dashed = asciiLowercase(camelCase.replace(/(?<=[a-z])[A-Z]/g, "-$&"));
if (/^webkit-/.test(dashed)) {
return `-${dashed}`;
}
return dashed;
};

View File

@@ -0,0 +1,16 @@
"use strict";
const prepareValue = require("../parsers").prepareValue;
module.exports.getPropertyDescriptor = function getPropertyDescriptor(property) {
return {
set(v) {
this._setProperty(property, prepareValue(v));
},
get() {
return this.getPropertyValue(property);
},
enumerable: true,
configurable: true
};
};

167
frontend/node_modules/cssstyle/lib/utils/strings.js generated vendored Normal file
View File

@@ -0,0 +1,167 @@
// Forked from https://github.com/jsdom/jsdom/blob/main/lib/jsdom/living/helpers/strings.js
"use strict";
// https://infra.spec.whatwg.org/#ascii-whitespace
const asciiWhitespaceRe = /^[\t\n\f\r ]$/;
exports.asciiWhitespaceRe = asciiWhitespaceRe;
// https://infra.spec.whatwg.org/#ascii-lowercase
exports.asciiLowercase = (s) => {
const len = s.length;
const out = new Array(len);
for (let i = 0; i < len; i++) {
const code = s.charCodeAt(i);
// If the character is between 'A' (65) and 'Z' (90), convert using bitwise OR with 32
out[i] = code >= 65 && code <= 90 ? String.fromCharCode(code | 32) : s[i];
}
return out.join("");
};
// https://infra.spec.whatwg.org/#ascii-uppercase
exports.asciiUppercase = (s) => {
const len = s.length;
const out = new Array(len);
for (let i = 0; i < len; i++) {
const code = s.charCodeAt(i);
// If the character is between 'a' (97) and 'z' (122), convert using bitwise AND with ~32
out[i] = code >= 97 && code <= 122 ? String.fromCharCode(code & ~32) : s[i];
}
return out.join("");
};
// https://infra.spec.whatwg.org/#strip-newlines
exports.stripNewlines = (s) => {
return s.replace(/[\n\r]+/g, "");
};
// https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace
exports.stripLeadingAndTrailingASCIIWhitespace = (s) => {
return s.replace(/^[ \t\n\f\r]+/, "").replace(/[ \t\n\f\r]+$/, "");
};
// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
exports.stripAndCollapseASCIIWhitespace = (s) => {
return s
.replace(/[ \t\n\f\r]+/g, " ")
.replace(/^[ \t\n\f\r]+/, "")
.replace(/[ \t\n\f\r]+$/, "");
};
// https://html.spec.whatwg.org/multipage/infrastructure.html#valid-simple-colour
exports.isValidSimpleColor = (s) => {
return /^#[a-fA-F\d]{6}$/.test(s);
};
// https://infra.spec.whatwg.org/#ascii-case-insensitive
exports.asciiCaseInsensitiveMatch = (a, b) => {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; ++i) {
if ((a.charCodeAt(i) | 32) !== (b.charCodeAt(i) | 32)) {
return false;
}
}
return true;
};
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-integers
// Error is represented as null.
const parseInteger = (exports.parseInteger = (input) => {
// The implementation here is slightly different from the spec's. We want to use parseInt(), but parseInt() trims
// Unicode whitespace in addition to just ASCII ones, so we make sure that the trimmed prefix contains only ASCII
// whitespace ourselves.
const numWhitespace = input.length - input.trimStart().length;
if (/[^\t\n\f\r ]/.test(input.slice(0, numWhitespace))) {
return null;
}
// We don't allow hexadecimal numbers here.
// eslint-disable-next-line radix
const value = parseInt(input, 10);
if (Number.isNaN(value)) {
return null;
}
// parseInt() returns -0 for "-0". Normalize that here.
return value === 0 ? 0 : value;
});
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-non-negative-integers
// Error is represented as null.
exports.parseNonNegativeInteger = (input) => {
const value = parseInteger(input);
if (value === null) {
return null;
}
if (value < 0) {
return null;
}
return value;
};
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-floating-point-number
const floatingPointNumRe = /^-?(?:\d+|\d*\.\d+)(?:[eE][-+]?\d+)?$/;
exports.isValidFloatingPointNumber = (str) => floatingPointNumRe.test(str);
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-floating-point-number-values
// Error is represented as null.
exports.parseFloatingPointNumber = (str) => {
// The implementation here is slightly different from the spec's. We need to use parseFloat() in order to retain
// accuracy, but parseFloat() trims Unicode whitespace in addition to just ASCII ones, so we make sure that the
// trimmed prefix contains only ASCII whitespace ourselves.
const numWhitespace = str.length - str.trimStart().length;
if (/[^\t\n\f\r ]/.test(str.slice(0, numWhitespace))) {
return null;
}
const parsed = parseFloat(str);
return isFinite(parsed) ? parsed : null;
};
// https://infra.spec.whatwg.org/#split-on-ascii-whitespace
exports.splitOnASCIIWhitespace = (str) => {
let position = 0;
const tokens = [];
while (position < str.length && asciiWhitespaceRe.test(str[position])) {
position++;
}
if (position === str.length) {
return tokens;
}
while (position < str.length) {
const start = position;
while (position < str.length && !asciiWhitespaceRe.test(str[position])) {
position++;
}
tokens.push(str.slice(start, position));
while (position < str.length && asciiWhitespaceRe.test(str[position])) {
position++;
}
}
return tokens;
};
// https://infra.spec.whatwg.org/#split-on-commas
exports.splitOnCommas = (str) => {
let position = 0;
const tokens = [];
while (position < str.length) {
let start = position;
while (position < str.length && str[position] !== ",") {
position++;
}
let end = position;
while (start < str.length && asciiWhitespaceRe.test(str[start])) {
start++;
}
while (end > start && asciiWhitespaceRe.test(str[end - 1])) {
end--;
}
tokens.push(str.slice(start, end));
if (position < str.length) {
position++;
}
}
return tokens;
};