 aacb45156b
			
		
	
	aacb45156b
	
	
	
		
			
			- 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>
		
			
				
	
	
		
			427 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			427 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| var __create = Object.create;
 | |
| var __defProp = Object.defineProperty;
 | |
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | |
| var __getOwnPropNames = Object.getOwnPropertyNames;
 | |
| var __getProtoOf = Object.getPrototypeOf;
 | |
| var __hasOwnProp = Object.prototype.hasOwnProperty;
 | |
| var __export = (target, all) => {
 | |
|   for (var name in all)
 | |
|     __defProp(target, name, { get: all[name], enumerable: true });
 | |
| };
 | |
| var __copyProps = (to, from, except, desc) => {
 | |
|   if (from && typeof from === "object" || typeof from === "function") {
 | |
|     for (let key of __getOwnPropNames(from))
 | |
|       if (!__hasOwnProp.call(to, key) && key !== except)
 | |
|         __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | |
|   }
 | |
|   return to;
 | |
| };
 | |
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
 | |
|   // If the importer is in node compatibility mode or this is not an ESM
 | |
|   // file that has been converted to a CommonJS file using a Babel-
 | |
|   // compatible transform (i.e. "__esModule" has not been set), then set
 | |
|   // "default" to the CommonJS "module.exports" for node compatibility.
 | |
|   isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
 | |
|   mod
 | |
| ));
 | |
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | |
| var util_exports = {};
 | |
| __export(util_exports, {
 | |
|   addSuffixToFilePath: () => addSuffixToFilePath,
 | |
|   ansiRegex: () => ansiRegex,
 | |
|   callLogText: () => callLogText,
 | |
|   createFileFiltersFromArguments: () => createFileFiltersFromArguments,
 | |
|   createFileMatcher: () => createFileMatcher,
 | |
|   createFileMatcherFromArguments: () => createFileMatcherFromArguments,
 | |
|   createTitleMatcher: () => createTitleMatcher,
 | |
|   debugTest: () => debugTest,
 | |
|   errorWithFile: () => errorWithFile,
 | |
|   expectTypes: () => expectTypes,
 | |
|   fileExistsAsync: () => fileExistsAsync,
 | |
|   fileIsModule: () => fileIsModule,
 | |
|   filterStackFile: () => filterStackFile,
 | |
|   filterStackTrace: () => filterStackTrace,
 | |
|   filteredStackTrace: () => filteredStackTrace,
 | |
|   forceRegExp: () => forceRegExp,
 | |
|   formatLocation: () => formatLocation,
 | |
|   getContainedPath: () => getContainedPath,
 | |
|   getPackageJsonPath: () => getPackageJsonPath,
 | |
|   mergeObjects: () => mergeObjects,
 | |
|   normalizeAndSaveAttachment: () => normalizeAndSaveAttachment,
 | |
|   relativeFilePath: () => relativeFilePath,
 | |
|   removeDirAndLogToConsole: () => removeDirAndLogToConsole,
 | |
|   resolveImportSpecifierAfterMapping: () => resolveImportSpecifierAfterMapping,
 | |
|   resolveReporterOutputPath: () => resolveReporterOutputPath,
 | |
|   sanitizeFilePathBeforeExtension: () => sanitizeFilePathBeforeExtension,
 | |
|   serializeError: () => serializeError,
 | |
|   stepTitle: () => stepTitle,
 | |
|   stripAnsiEscapes: () => stripAnsiEscapes,
 | |
|   trimLongString: () => trimLongString,
 | |
|   windowsFilesystemFriendlyLength: () => windowsFilesystemFriendlyLength
 | |
| });
 | |
| module.exports = __toCommonJS(util_exports);
 | |
| var import_fs = __toESM(require("fs"));
 | |
| var import_path = __toESM(require("path"));
 | |
| var import_url = __toESM(require("url"));
 | |
| var import_util = __toESM(require("util"));
 | |
| var import_utils = require("playwright-core/lib/utils");
 | |
| var import_utilsBundle = require("playwright-core/lib/utilsBundle");
 | |
| const PLAYWRIGHT_TEST_PATH = import_path.default.join(__dirname, "..");
 | |
| const PLAYWRIGHT_CORE_PATH = import_path.default.dirname(require.resolve("playwright-core/package.json"));
 | |
| function filterStackTrace(e) {
 | |
|   const name = e.name ? e.name + ": " : "";
 | |
|   const cause = e.cause instanceof Error ? filterStackTrace(e.cause) : void 0;
 | |
|   if (process.env.PWDEBUGIMPL)
 | |
|     return { message: name + e.message, stack: e.stack || "", cause };
 | |
|   const stackLines = (0, import_utils.stringifyStackFrames)(filteredStackTrace(e.stack?.split("\n") || []));
 | |
|   return {
 | |
|     message: name + e.message,
 | |
|     stack: `${name}${e.message}${stackLines.map((line) => "\n" + line).join("")}`,
 | |
|     cause
 | |
|   };
 | |
| }
 | |
| function filterStackFile(file) {
 | |
|   if (!process.env.PWDEBUGIMPL && file.startsWith(PLAYWRIGHT_TEST_PATH))
 | |
|     return false;
 | |
|   if (!process.env.PWDEBUGIMPL && file.startsWith(PLAYWRIGHT_CORE_PATH))
 | |
|     return false;
 | |
|   return true;
 | |
| }
 | |
| function filteredStackTrace(rawStack) {
 | |
|   const frames = [];
 | |
|   for (const line of rawStack) {
 | |
|     const frame = (0, import_utils.parseStackFrame)(line, import_path.default.sep, !!process.env.PWDEBUGIMPL);
 | |
|     if (!frame || !frame.file)
 | |
|       continue;
 | |
|     if (!filterStackFile(frame.file))
 | |
|       continue;
 | |
|     frames.push(frame);
 | |
|   }
 | |
|   return frames;
 | |
| }
 | |
| function serializeError(error) {
 | |
|   if (error instanceof Error)
 | |
|     return filterStackTrace(error);
 | |
|   return {
 | |
|     value: import_util.default.inspect(error)
 | |
|   };
 | |
| }
 | |
| function createFileFiltersFromArguments(args) {
 | |
|   return args.map((arg) => {
 | |
|     const match = /^(.*?):(\d+):?(\d+)?$/.exec(arg);
 | |
|     return {
 | |
|       re: forceRegExp(match ? match[1] : arg),
 | |
|       line: match ? parseInt(match[2], 10) : null,
 | |
|       column: match?.[3] ? parseInt(match[3], 10) : null
 | |
|     };
 | |
|   });
 | |
| }
 | |
| function createFileMatcherFromArguments(args) {
 | |
|   const filters = createFileFiltersFromArguments(args);
 | |
|   return createFileMatcher(filters.map((filter) => filter.re || filter.exact || ""));
 | |
| }
 | |
| function createFileMatcher(patterns) {
 | |
|   const reList = [];
 | |
|   const filePatterns = [];
 | |
|   for (const pattern of Array.isArray(patterns) ? patterns : [patterns]) {
 | |
|     if ((0, import_utils.isRegExp)(pattern)) {
 | |
|       reList.push(pattern);
 | |
|     } else {
 | |
|       if (!pattern.startsWith("**/"))
 | |
|         filePatterns.push("**/" + pattern);
 | |
|       else
 | |
|         filePatterns.push(pattern);
 | |
|     }
 | |
|   }
 | |
|   return (filePath) => {
 | |
|     for (const re of reList) {
 | |
|       re.lastIndex = 0;
 | |
|       if (re.test(filePath))
 | |
|         return true;
 | |
|     }
 | |
|     if (import_path.default.sep === "\\") {
 | |
|       const fileURL = import_url.default.pathToFileURL(filePath).href;
 | |
|       for (const re of reList) {
 | |
|         re.lastIndex = 0;
 | |
|         if (re.test(fileURL))
 | |
|           return true;
 | |
|       }
 | |
|     }
 | |
|     for (const pattern of filePatterns) {
 | |
|       if ((0, import_utilsBundle.minimatch)(filePath, pattern, { nocase: true, dot: true }))
 | |
|         return true;
 | |
|     }
 | |
|     return false;
 | |
|   };
 | |
| }
 | |
| function createTitleMatcher(patterns) {
 | |
|   const reList = Array.isArray(patterns) ? patterns : [patterns];
 | |
|   return (value) => {
 | |
|     for (const re of reList) {
 | |
|       re.lastIndex = 0;
 | |
|       if (re.test(value))
 | |
|         return true;
 | |
|     }
 | |
|     return false;
 | |
|   };
 | |
| }
 | |
| function mergeObjects(a, b, c) {
 | |
|   const result = { ...a };
 | |
|   for (const x of [b, c].filter(Boolean)) {
 | |
|     for (const [name, value] of Object.entries(x)) {
 | |
|       if (!Object.is(value, void 0))
 | |
|         result[name] = value;
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| function forceRegExp(pattern) {
 | |
|   const match = pattern.match(/^\/(.*)\/([gi]*)$/);
 | |
|   if (match)
 | |
|     return new RegExp(match[1], match[2]);
 | |
|   return new RegExp(pattern, "gi");
 | |
| }
 | |
| function relativeFilePath(file) {
 | |
|   if (!import_path.default.isAbsolute(file))
 | |
|     return file;
 | |
|   return import_path.default.relative(process.cwd(), file);
 | |
| }
 | |
| function formatLocation(location) {
 | |
|   return relativeFilePath(location.file) + ":" + location.line + ":" + location.column;
 | |
| }
 | |
| function errorWithFile(file, message) {
 | |
|   return new Error(`${relativeFilePath(file)}: ${message}`);
 | |
| }
 | |
| function expectTypes(receiver, types, matcherName) {
 | |
|   if (typeof receiver !== "object" || !types.includes(receiver.constructor.name)) {
 | |
|     const commaSeparated = types.slice();
 | |
|     const lastType = commaSeparated.pop();
 | |
|     const typesString = commaSeparated.length ? commaSeparated.join(", ") + " or " + lastType : lastType;
 | |
|     throw new Error(`${matcherName} can be only used with ${typesString} object${types.length > 1 ? "s" : ""}`);
 | |
|   }
 | |
| }
 | |
| const windowsFilesystemFriendlyLength = 60;
 | |
| function trimLongString(s, length = 100) {
 | |
|   if (s.length <= length)
 | |
|     return s;
 | |
|   const hash = (0, import_utils.calculateSha1)(s);
 | |
|   const middle = `-${hash.substring(0, 5)}-`;
 | |
|   const start = Math.floor((length - middle.length) / 2);
 | |
|   const end = length - middle.length - start;
 | |
|   return s.substring(0, start) + middle + s.slice(-end);
 | |
| }
 | |
| function addSuffixToFilePath(filePath, suffix) {
 | |
|   const ext = import_path.default.extname(filePath);
 | |
|   const base = filePath.substring(0, filePath.length - ext.length);
 | |
|   return base + suffix + ext;
 | |
| }
 | |
| function sanitizeFilePathBeforeExtension(filePath, ext) {
 | |
|   ext ??= import_path.default.extname(filePath);
 | |
|   const base = filePath.substring(0, filePath.length - ext.length);
 | |
|   return (0, import_utils.sanitizeForFilePath)(base) + ext;
 | |
| }
 | |
| function getContainedPath(parentPath, subPath = "") {
 | |
|   const resolvedPath = import_path.default.resolve(parentPath, subPath);
 | |
|   if (resolvedPath === parentPath || resolvedPath.startsWith(parentPath + import_path.default.sep))
 | |
|     return resolvedPath;
 | |
|   return null;
 | |
| }
 | |
| const debugTest = (0, import_utilsBundle.debug)("pw:test");
 | |
| const callLogText = (log) => {
 | |
|   if (!log || !log.some((l) => !!l))
 | |
|     return "";
 | |
|   return `
 | |
| Call log:
 | |
| ${import_utilsBundle.colors.dim(log.join("\n"))}
 | |
| `;
 | |
| };
 | |
| const folderToPackageJsonPath = /* @__PURE__ */ new Map();
 | |
| function getPackageJsonPath(folderPath) {
 | |
|   const cached = folderToPackageJsonPath.get(folderPath);
 | |
|   if (cached !== void 0)
 | |
|     return cached;
 | |
|   const packageJsonPath = import_path.default.join(folderPath, "package.json");
 | |
|   if (import_fs.default.existsSync(packageJsonPath)) {
 | |
|     folderToPackageJsonPath.set(folderPath, packageJsonPath);
 | |
|     return packageJsonPath;
 | |
|   }
 | |
|   const parentFolder = import_path.default.dirname(folderPath);
 | |
|   if (folderPath === parentFolder) {
 | |
|     folderToPackageJsonPath.set(folderPath, "");
 | |
|     return "";
 | |
|   }
 | |
|   const result = getPackageJsonPath(parentFolder);
 | |
|   folderToPackageJsonPath.set(folderPath, result);
 | |
|   return result;
 | |
| }
 | |
| function resolveReporterOutputPath(defaultValue, configDir, configValue) {
 | |
|   if (configValue)
 | |
|     return import_path.default.resolve(configDir, configValue);
 | |
|   let basePath = getPackageJsonPath(configDir);
 | |
|   basePath = basePath ? import_path.default.dirname(basePath) : process.cwd();
 | |
|   return import_path.default.resolve(basePath, defaultValue);
 | |
| }
 | |
| async function normalizeAndSaveAttachment(outputPath, name, options = {}) {
 | |
|   if (options.path === void 0 && options.body === void 0)
 | |
|     return { name, contentType: "text/plain" };
 | |
|   if ((options.path !== void 0 ? 1 : 0) + (options.body !== void 0 ? 1 : 0) !== 1)
 | |
|     throw new Error(`Exactly one of "path" and "body" must be specified`);
 | |
|   if (options.path !== void 0) {
 | |
|     const hash = (0, import_utils.calculateSha1)(options.path);
 | |
|     if (!(0, import_utils.isString)(name))
 | |
|       throw new Error('"name" should be string.');
 | |
|     const sanitizedNamePrefix = (0, import_utils.sanitizeForFilePath)(name) + "-";
 | |
|     const dest = import_path.default.join(outputPath, "attachments", sanitizedNamePrefix + hash + import_path.default.extname(options.path));
 | |
|     await import_fs.default.promises.mkdir(import_path.default.dirname(dest), { recursive: true });
 | |
|     await import_fs.default.promises.copyFile(options.path, dest);
 | |
|     const contentType = options.contentType ?? (import_utilsBundle.mime.getType(import_path.default.basename(options.path)) || "application/octet-stream");
 | |
|     return { name, contentType, path: dest };
 | |
|   } else {
 | |
|     const contentType = options.contentType ?? (typeof options.body === "string" ? "text/plain" : "application/octet-stream");
 | |
|     return { name, contentType, body: typeof options.body === "string" ? Buffer.from(options.body) : options.body };
 | |
|   }
 | |
| }
 | |
| function fileIsModule(file) {
 | |
|   if (file.endsWith(".mjs") || file.endsWith(".mts"))
 | |
|     return true;
 | |
|   if (file.endsWith(".cjs") || file.endsWith(".cts"))
 | |
|     return false;
 | |
|   const folder = import_path.default.dirname(file);
 | |
|   return folderIsModule(folder);
 | |
| }
 | |
| function folderIsModule(folder) {
 | |
|   const packageJsonPath = getPackageJsonPath(folder);
 | |
|   if (!packageJsonPath)
 | |
|     return false;
 | |
|   return require(packageJsonPath).type === "module";
 | |
| }
 | |
| const packageJsonMainFieldCache = /* @__PURE__ */ new Map();
 | |
| function getMainFieldFromPackageJson(packageJsonPath) {
 | |
|   if (!packageJsonMainFieldCache.has(packageJsonPath)) {
 | |
|     let mainField;
 | |
|     try {
 | |
|       mainField = JSON.parse(import_fs.default.readFileSync(packageJsonPath, "utf8")).main;
 | |
|     } catch {
 | |
|     }
 | |
|     packageJsonMainFieldCache.set(packageJsonPath, mainField);
 | |
|   }
 | |
|   return packageJsonMainFieldCache.get(packageJsonPath);
 | |
| }
 | |
| const kExtLookups = /* @__PURE__ */ new Map([
 | |
|   [".js", [".jsx", ".ts", ".tsx"]],
 | |
|   [".jsx", [".tsx"]],
 | |
|   [".cjs", [".cts"]],
 | |
|   [".mjs", [".mts"]],
 | |
|   ["", [".js", ".ts", ".jsx", ".tsx", ".cjs", ".mjs", ".cts", ".mts"]]
 | |
| ]);
 | |
| function resolveImportSpecifierExtension(resolved) {
 | |
|   if (fileExists(resolved))
 | |
|     return resolved;
 | |
|   for (const [ext, others] of kExtLookups) {
 | |
|     if (!resolved.endsWith(ext))
 | |
|       continue;
 | |
|     for (const other of others) {
 | |
|       const modified = resolved.substring(0, resolved.length - ext.length) + other;
 | |
|       if (fileExists(modified))
 | |
|         return modified;
 | |
|     }
 | |
|     break;
 | |
|   }
 | |
| }
 | |
| function resolveImportSpecifierAfterMapping(resolved, afterPathMapping) {
 | |
|   const resolvedFile = resolveImportSpecifierExtension(resolved);
 | |
|   if (resolvedFile)
 | |
|     return resolvedFile;
 | |
|   if (dirExists(resolved)) {
 | |
|     const packageJsonPath = import_path.default.join(resolved, "package.json");
 | |
|     if (afterPathMapping) {
 | |
|       const mainField = getMainFieldFromPackageJson(packageJsonPath);
 | |
|       const mainFieldResolved = mainField ? resolveImportSpecifierExtension(import_path.default.resolve(resolved, mainField)) : void 0;
 | |
|       return mainFieldResolved || resolveImportSpecifierExtension(import_path.default.join(resolved, "index"));
 | |
|     }
 | |
|     if (fileExists(packageJsonPath))
 | |
|       return resolved;
 | |
|     const dirImport = import_path.default.join(resolved, "index");
 | |
|     return resolveImportSpecifierExtension(dirImport);
 | |
|   }
 | |
| }
 | |
| function fileExists(resolved) {
 | |
|   return import_fs.default.statSync(resolved, { throwIfNoEntry: false })?.isFile();
 | |
| }
 | |
| async function fileExistsAsync(resolved) {
 | |
|   try {
 | |
|     const stat = await import_fs.default.promises.stat(resolved);
 | |
|     return stat.isFile();
 | |
|   } catch {
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| function dirExists(resolved) {
 | |
|   return import_fs.default.statSync(resolved, { throwIfNoEntry: false })?.isDirectory();
 | |
| }
 | |
| async function removeDirAndLogToConsole(dir) {
 | |
|   try {
 | |
|     if (!import_fs.default.existsSync(dir))
 | |
|       return;
 | |
|     console.log(`Removing ${await import_fs.default.promises.realpath(dir)}`);
 | |
|     await import_fs.default.promises.rm(dir, { recursive: true, force: true });
 | |
|   } catch {
 | |
|   }
 | |
| }
 | |
| const ansiRegex = new RegExp("([\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))", "g");
 | |
| function stripAnsiEscapes(str) {
 | |
|   return str.replace(ansiRegex, "");
 | |
| }
 | |
| function stepTitle(category, title) {
 | |
|   switch (category) {
 | |
|     case "fixture":
 | |
|       return `Fixture ${(0, import_utils.escapeWithQuotes)(title, '"')}`;
 | |
|     case "expect":
 | |
|       return `Expect ${(0, import_utils.escapeWithQuotes)(title, '"')}`;
 | |
|     case "test.step":
 | |
|       return title;
 | |
|     case "test.attach":
 | |
|       return `Attach ${(0, import_utils.escapeWithQuotes)(title, '"')}`;
 | |
|     case "hook":
 | |
|     case "pw:api":
 | |
|       return title;
 | |
|     default:
 | |
|       return `[${category}] ${title}`;
 | |
|   }
 | |
| }
 | |
| // Annotate the CommonJS export names for ESM import in node:
 | |
| 0 && (module.exports = {
 | |
|   addSuffixToFilePath,
 | |
|   ansiRegex,
 | |
|   callLogText,
 | |
|   createFileFiltersFromArguments,
 | |
|   createFileMatcher,
 | |
|   createFileMatcherFromArguments,
 | |
|   createTitleMatcher,
 | |
|   debugTest,
 | |
|   errorWithFile,
 | |
|   expectTypes,
 | |
|   fileExistsAsync,
 | |
|   fileIsModule,
 | |
|   filterStackFile,
 | |
|   filterStackTrace,
 | |
|   filteredStackTrace,
 | |
|   forceRegExp,
 | |
|   formatLocation,
 | |
|   getContainedPath,
 | |
|   getPackageJsonPath,
 | |
|   mergeObjects,
 | |
|   normalizeAndSaveAttachment,
 | |
|   relativeFilePath,
 | |
|   removeDirAndLogToConsole,
 | |
|   resolveImportSpecifierAfterMapping,
 | |
|   resolveReporterOutputPath,
 | |
|   sanitizeFilePathBeforeExtension,
 | |
|   serializeError,
 | |
|   stepTitle,
 | |
|   stripAnsiEscapes,
 | |
|   trimLongString,
 | |
|   windowsFilesystemFriendlyLength
 | |
| });
 |