 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>
		
			
				
	
	
		
			308 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			308 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | ||
| var __defProp = Object.defineProperty;
 | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | ||
| var __getOwnPropNames = Object.getOwnPropertyNames;
 | ||
| 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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | ||
| var testTree_exports = {};
 | ||
| __export(testTree_exports, {
 | ||
|   TestTree: () => TestTree,
 | ||
|   collectTestIds: () => collectTestIds,
 | ||
|   sortAndPropagateStatus: () => sortAndPropagateStatus,
 | ||
|   statusEx: () => statusEx
 | ||
| });
 | ||
| module.exports = __toCommonJS(testTree_exports);
 | ||
| class TestTree {
 | ||
|   constructor(rootFolder, rootSuite, loadErrors, projectFilters, pathSeparator) {
 | ||
|     this._treeItemById = /* @__PURE__ */ new Map();
 | ||
|     this._treeItemByTestId = /* @__PURE__ */ new Map();
 | ||
|     const filterProjects = projectFilters && [...projectFilters.values()].some(Boolean);
 | ||
|     this.pathSeparator = pathSeparator;
 | ||
|     this.rootItem = {
 | ||
|       kind: "group",
 | ||
|       subKind: "folder",
 | ||
|       id: rootFolder,
 | ||
|       title: "",
 | ||
|       location: { file: "", line: 0, column: 0 },
 | ||
|       duration: 0,
 | ||
|       parent: void 0,
 | ||
|       children: [],
 | ||
|       status: "none",
 | ||
|       hasLoadErrors: false
 | ||
|     };
 | ||
|     this._treeItemById.set(rootFolder, this.rootItem);
 | ||
|     const visitSuite = (project, parentSuite, parentGroup) => {
 | ||
|       for (const suite of parentSuite.suites) {
 | ||
|         if (!suite.title) {
 | ||
|           visitSuite(project, suite, parentGroup);
 | ||
|           continue;
 | ||
|         }
 | ||
|         let group = parentGroup.children.find((item) => item.kind === "group" && item.title === suite.title);
 | ||
|         if (!group) {
 | ||
|           group = {
 | ||
|             kind: "group",
 | ||
|             subKind: "describe",
 | ||
|             id: "suite:" + parentSuite.titlePath().join("") + "" + suite.title,
 | ||
|             // account for anonymous suites
 | ||
|             title: suite.title,
 | ||
|             location: suite.location,
 | ||
|             duration: 0,
 | ||
|             parent: parentGroup,
 | ||
|             children: [],
 | ||
|             status: "none",
 | ||
|             hasLoadErrors: false
 | ||
|           };
 | ||
|           this._addChild(parentGroup, group);
 | ||
|         }
 | ||
|         visitSuite(project, suite, group);
 | ||
|       }
 | ||
|       for (const test of parentSuite.tests) {
 | ||
|         const title = test.title;
 | ||
|         let testCaseItem = parentGroup.children.find((t) => t.kind !== "group" && t.title === title);
 | ||
|         if (!testCaseItem) {
 | ||
|           testCaseItem = {
 | ||
|             kind: "case",
 | ||
|             id: "test:" + test.titlePath().join(""),
 | ||
|             title,
 | ||
|             parent: parentGroup,
 | ||
|             children: [],
 | ||
|             tests: [],
 | ||
|             location: test.location,
 | ||
|             duration: 0,
 | ||
|             status: "none",
 | ||
|             project: void 0,
 | ||
|             test: void 0,
 | ||
|             tags: test.tags
 | ||
|           };
 | ||
|           this._addChild(parentGroup, testCaseItem);
 | ||
|         }
 | ||
|         const result = test.results[0];
 | ||
|         let status = "none";
 | ||
|         if (result?.[statusEx] === "scheduled")
 | ||
|           status = "scheduled";
 | ||
|         else if (result?.[statusEx] === "running")
 | ||
|           status = "running";
 | ||
|         else if (result?.status === "skipped")
 | ||
|           status = "skipped";
 | ||
|         else if (result?.status === "interrupted")
 | ||
|           status = "none";
 | ||
|         else if (result && test.outcome() !== "expected")
 | ||
|           status = "failed";
 | ||
|         else if (result && test.outcome() === "expected")
 | ||
|           status = "passed";
 | ||
|         testCaseItem.tests.push(test);
 | ||
|         const testItem = {
 | ||
|           kind: "test",
 | ||
|           id: test.id,
 | ||
|           title: project.name,
 | ||
|           location: test.location,
 | ||
|           test,
 | ||
|           parent: testCaseItem,
 | ||
|           children: [],
 | ||
|           status,
 | ||
|           duration: test.results.length ? Math.max(0, test.results[0].duration) : 0,
 | ||
|           project
 | ||
|         };
 | ||
|         this._addChild(testCaseItem, testItem);
 | ||
|         this._treeItemByTestId.set(test.id, testItem);
 | ||
|         testCaseItem.duration = testCaseItem.children.reduce((a, b) => a + b.duration, 0);
 | ||
|       }
 | ||
|     };
 | ||
|     for (const projectSuite of rootSuite?.suites || []) {
 | ||
|       if (filterProjects && !projectFilters.get(projectSuite.title))
 | ||
|         continue;
 | ||
|       for (const fileSuite of projectSuite.suites) {
 | ||
|         const fileItem = this._fileItem(fileSuite.location.file.split(pathSeparator), true);
 | ||
|         visitSuite(projectSuite.project(), fileSuite, fileItem);
 | ||
|       }
 | ||
|     }
 | ||
|     for (const loadError of loadErrors) {
 | ||
|       if (!loadError.location)
 | ||
|         continue;
 | ||
|       const fileItem = this._fileItem(loadError.location.file.split(pathSeparator), true);
 | ||
|       fileItem.hasLoadErrors = true;
 | ||
|     }
 | ||
|   }
 | ||
|   _addChild(parent, child) {
 | ||
|     parent.children.push(child);
 | ||
|     child.parent = parent;
 | ||
|     this._treeItemById.set(child.id, child);
 | ||
|   }
 | ||
|   filterTree(filterText, statusFilters, runningTestIds) {
 | ||
|     const tokens = filterText.trim().toLowerCase().split(" ");
 | ||
|     const filtersStatuses = [...statusFilters.values()].some(Boolean);
 | ||
|     const filter = (testCase) => {
 | ||
|       const titleWithTags = [...testCase.tests[0].titlePath(), ...testCase.tests[0].tags].join(" ").toLowerCase();
 | ||
|       if (!tokens.every((token) => titleWithTags.includes(token)) && !testCase.tests.some((t) => runningTestIds?.has(t.id)))
 | ||
|         return false;
 | ||
|       testCase.children = testCase.children.filter((test) => {
 | ||
|         return !filtersStatuses || runningTestIds?.has(test.test.id) || statusFilters.get(test.status);
 | ||
|       });
 | ||
|       testCase.tests = testCase.children.map((c) => c.test);
 | ||
|       return !!testCase.children.length;
 | ||
|     };
 | ||
|     const visit = (treeItem) => {
 | ||
|       const newChildren = [];
 | ||
|       for (const child of treeItem.children) {
 | ||
|         if (child.kind === "case") {
 | ||
|           if (filter(child))
 | ||
|             newChildren.push(child);
 | ||
|         } else {
 | ||
|           visit(child);
 | ||
|           if (child.children.length || child.hasLoadErrors)
 | ||
|             newChildren.push(child);
 | ||
|         }
 | ||
|       }
 | ||
|       treeItem.children = newChildren;
 | ||
|     };
 | ||
|     visit(this.rootItem);
 | ||
|   }
 | ||
|   _fileItem(filePath, isFile) {
 | ||
|     if (filePath.length === 0)
 | ||
|       return this.rootItem;
 | ||
|     const fileName = filePath.join(this.pathSeparator);
 | ||
|     const existingFileItem = this._treeItemById.get(fileName);
 | ||
|     if (existingFileItem)
 | ||
|       return existingFileItem;
 | ||
|     const parentFileItem = this._fileItem(filePath.slice(0, filePath.length - 1), false);
 | ||
|     const fileItem = {
 | ||
|       kind: "group",
 | ||
|       subKind: isFile ? "file" : "folder",
 | ||
|       id: fileName,
 | ||
|       title: filePath[filePath.length - 1],
 | ||
|       location: { file: fileName, line: 0, column: 0 },
 | ||
|       duration: 0,
 | ||
|       parent: parentFileItem,
 | ||
|       children: [],
 | ||
|       status: "none",
 | ||
|       hasLoadErrors: false
 | ||
|     };
 | ||
|     this._addChild(parentFileItem, fileItem);
 | ||
|     return fileItem;
 | ||
|   }
 | ||
|   sortAndPropagateStatus() {
 | ||
|     sortAndPropagateStatus(this.rootItem);
 | ||
|   }
 | ||
|   flattenForSingleProject() {
 | ||
|     const visit = (treeItem) => {
 | ||
|       if (treeItem.kind === "case" && treeItem.children.length === 1) {
 | ||
|         treeItem.project = treeItem.children[0].project;
 | ||
|         treeItem.test = treeItem.children[0].test;
 | ||
|         treeItem.children = [];
 | ||
|         this._treeItemByTestId.set(treeItem.test.id, treeItem);
 | ||
|       } else {
 | ||
|         treeItem.children.forEach(visit);
 | ||
|       }
 | ||
|     };
 | ||
|     visit(this.rootItem);
 | ||
|   }
 | ||
|   shortenRoot() {
 | ||
|     let shortRoot = this.rootItem;
 | ||
|     while (shortRoot.children.length === 1 && shortRoot.children[0].kind === "group" && shortRoot.children[0].subKind === "folder")
 | ||
|       shortRoot = shortRoot.children[0];
 | ||
|     shortRoot.location = this.rootItem.location;
 | ||
|     this.rootItem = shortRoot;
 | ||
|   }
 | ||
|   testIds() {
 | ||
|     const result = /* @__PURE__ */ new Set();
 | ||
|     const visit = (treeItem) => {
 | ||
|       if (treeItem.kind === "case")
 | ||
|         treeItem.tests.forEach((t) => result.add(t.id));
 | ||
|       treeItem.children.forEach(visit);
 | ||
|     };
 | ||
|     visit(this.rootItem);
 | ||
|     return result;
 | ||
|   }
 | ||
|   fileNames() {
 | ||
|     const result = /* @__PURE__ */ new Set();
 | ||
|     const visit = (treeItem) => {
 | ||
|       if (treeItem.kind === "group" && treeItem.subKind === "file")
 | ||
|         result.add(treeItem.id);
 | ||
|       else
 | ||
|         treeItem.children.forEach(visit);
 | ||
|     };
 | ||
|     visit(this.rootItem);
 | ||
|     return [...result];
 | ||
|   }
 | ||
|   flatTreeItems() {
 | ||
|     const result = [];
 | ||
|     const visit = (treeItem) => {
 | ||
|       result.push(treeItem);
 | ||
|       treeItem.children.forEach(visit);
 | ||
|     };
 | ||
|     visit(this.rootItem);
 | ||
|     return result;
 | ||
|   }
 | ||
|   treeItemById(id) {
 | ||
|     return this._treeItemById.get(id);
 | ||
|   }
 | ||
|   collectTestIds(treeItem) {
 | ||
|     return treeItem ? collectTestIds(treeItem) : /* @__PURE__ */ new Set();
 | ||
|   }
 | ||
| }
 | ||
| function sortAndPropagateStatus(treeItem) {
 | ||
|   for (const child of treeItem.children)
 | ||
|     sortAndPropagateStatus(child);
 | ||
|   if (treeItem.kind === "group") {
 | ||
|     treeItem.children.sort((a, b) => {
 | ||
|       const fc = a.location.file.localeCompare(b.location.file);
 | ||
|       return fc || a.location.line - b.location.line;
 | ||
|     });
 | ||
|   }
 | ||
|   let allPassed = treeItem.children.length > 0;
 | ||
|   let allSkipped = treeItem.children.length > 0;
 | ||
|   let hasFailed = false;
 | ||
|   let hasRunning = false;
 | ||
|   let hasScheduled = false;
 | ||
|   for (const child of treeItem.children) {
 | ||
|     allSkipped = allSkipped && child.status === "skipped";
 | ||
|     allPassed = allPassed && (child.status === "passed" || child.status === "skipped");
 | ||
|     hasFailed = hasFailed || child.status === "failed";
 | ||
|     hasRunning = hasRunning || child.status === "running";
 | ||
|     hasScheduled = hasScheduled || child.status === "scheduled";
 | ||
|   }
 | ||
|   if (hasRunning)
 | ||
|     treeItem.status = "running";
 | ||
|   else if (hasScheduled)
 | ||
|     treeItem.status = "scheduled";
 | ||
|   else if (hasFailed)
 | ||
|     treeItem.status = "failed";
 | ||
|   else if (allSkipped)
 | ||
|     treeItem.status = "skipped";
 | ||
|   else if (allPassed)
 | ||
|     treeItem.status = "passed";
 | ||
| }
 | ||
| function collectTestIds(treeItem) {
 | ||
|   const testIds = /* @__PURE__ */ new Set();
 | ||
|   const visit = (treeItem2) => {
 | ||
|     if (treeItem2.kind === "case")
 | ||
|       treeItem2.tests.map((t) => t.id).forEach((id) => testIds.add(id));
 | ||
|     else if (treeItem2.kind === "test")
 | ||
|       testIds.add(treeItem2.id);
 | ||
|     else
 | ||
|       treeItem2.children?.forEach(visit);
 | ||
|   };
 | ||
|   visit(treeItem);
 | ||
|   return testIds;
 | ||
| }
 | ||
| const statusEx = Symbol("statusEx");
 | ||
| // Annotate the CommonJS export names for ESM import in node:
 | ||
| 0 && (module.exports = {
 | ||
|   TestTree,
 | ||
|   collectTestIds,
 | ||
|   sortAndPropagateStatus,
 | ||
|   statusEx
 | ||
| });
 |