 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>
		
			
				
	
	
		
			489 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			489 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- Mode: js; js-indent-level: 2; -*- */
 | |
| /*
 | |
|  * Copyright 2011 Mozilla Foundation and contributors
 | |
|  * Licensed under the New BSD license. See LICENSE or:
 | |
|  * http://opensource.org/licenses/BSD-3-Clause
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * This is a helper function for getting values from parameter/options
 | |
|  * objects.
 | |
|  *
 | |
|  * @param args The object we are extracting values from
 | |
|  * @param name The name of the property we are getting.
 | |
|  * @param defaultValue An optional value to return if the property is missing
 | |
|  * from the object. If this is not specified and the property is missing, an
 | |
|  * error will be thrown.
 | |
|  */
 | |
| function getArg(aArgs, aName, aDefaultValue) {
 | |
|   if (aName in aArgs) {
 | |
|     return aArgs[aName];
 | |
|   } else if (arguments.length === 3) {
 | |
|     return aDefaultValue;
 | |
|   } else {
 | |
|     throw new Error('"' + aName + '" is a required argument.');
 | |
|   }
 | |
| }
 | |
| exports.getArg = getArg;
 | |
| 
 | |
| var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.-]*)(?::(\d+))?(.*)$/;
 | |
| var dataUrlRegexp = /^data:.+\,.+$/;
 | |
| 
 | |
| function urlParse(aUrl) {
 | |
|   var match = aUrl.match(urlRegexp);
 | |
|   if (!match) {
 | |
|     return null;
 | |
|   }
 | |
|   return {
 | |
|     scheme: match[1],
 | |
|     auth: match[2],
 | |
|     host: match[3],
 | |
|     port: match[4],
 | |
|     path: match[5]
 | |
|   };
 | |
| }
 | |
| exports.urlParse = urlParse;
 | |
| 
 | |
| function urlGenerate(aParsedUrl) {
 | |
|   var url = '';
 | |
|   if (aParsedUrl.scheme) {
 | |
|     url += aParsedUrl.scheme + ':';
 | |
|   }
 | |
|   url += '//';
 | |
|   if (aParsedUrl.auth) {
 | |
|     url += aParsedUrl.auth + '@';
 | |
|   }
 | |
|   if (aParsedUrl.host) {
 | |
|     url += aParsedUrl.host;
 | |
|   }
 | |
|   if (aParsedUrl.port) {
 | |
|     url += ":" + aParsedUrl.port
 | |
|   }
 | |
|   if (aParsedUrl.path) {
 | |
|     url += aParsedUrl.path;
 | |
|   }
 | |
|   return url;
 | |
| }
 | |
| exports.urlGenerate = urlGenerate;
 | |
| 
 | |
| /**
 | |
|  * Normalizes a path, or the path portion of a URL:
 | |
|  *
 | |
|  * - Replaces consecutive slashes with one slash.
 | |
|  * - Removes unnecessary '.' parts.
 | |
|  * - Removes unnecessary '<dir>/..' parts.
 | |
|  *
 | |
|  * Based on code in the Node.js 'path' core module.
 | |
|  *
 | |
|  * @param aPath The path or url to normalize.
 | |
|  */
 | |
| function normalize(aPath) {
 | |
|   var path = aPath;
 | |
|   var url = urlParse(aPath);
 | |
|   if (url) {
 | |
|     if (!url.path) {
 | |
|       return aPath;
 | |
|     }
 | |
|     path = url.path;
 | |
|   }
 | |
|   var isAbsolute = exports.isAbsolute(path);
 | |
| 
 | |
|   var parts = path.split(/\/+/);
 | |
|   for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
 | |
|     part = parts[i];
 | |
|     if (part === '.') {
 | |
|       parts.splice(i, 1);
 | |
|     } else if (part === '..') {
 | |
|       up++;
 | |
|     } else if (up > 0) {
 | |
|       if (part === '') {
 | |
|         // The first part is blank if the path is absolute. Trying to go
 | |
|         // above the root is a no-op. Therefore we can remove all '..' parts
 | |
|         // directly after the root.
 | |
|         parts.splice(i + 1, up);
 | |
|         up = 0;
 | |
|       } else {
 | |
|         parts.splice(i, 2);
 | |
|         up--;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   path = parts.join('/');
 | |
| 
 | |
|   if (path === '') {
 | |
|     path = isAbsolute ? '/' : '.';
 | |
|   }
 | |
| 
 | |
|   if (url) {
 | |
|     url.path = path;
 | |
|     return urlGenerate(url);
 | |
|   }
 | |
|   return path;
 | |
| }
 | |
| exports.normalize = normalize;
 | |
| 
 | |
| /**
 | |
|  * Joins two paths/URLs.
 | |
|  *
 | |
|  * @param aRoot The root path or URL.
 | |
|  * @param aPath The path or URL to be joined with the root.
 | |
|  *
 | |
|  * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
 | |
|  *   scheme-relative URL: Then the scheme of aRoot, if any, is prepended
 | |
|  *   first.
 | |
|  * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
 | |
|  *   is updated with the result and aRoot is returned. Otherwise the result
 | |
|  *   is returned.
 | |
|  *   - If aPath is absolute, the result is aPath.
 | |
|  *   - Otherwise the two paths are joined with a slash.
 | |
|  * - Joining for example 'http://' and 'www.example.com' is also supported.
 | |
|  */
 | |
| function join(aRoot, aPath) {
 | |
|   if (aRoot === "") {
 | |
|     aRoot = ".";
 | |
|   }
 | |
|   if (aPath === "") {
 | |
|     aPath = ".";
 | |
|   }
 | |
|   var aPathUrl = urlParse(aPath);
 | |
|   var aRootUrl = urlParse(aRoot);
 | |
|   if (aRootUrl) {
 | |
|     aRoot = aRootUrl.path || '/';
 | |
|   }
 | |
| 
 | |
|   // `join(foo, '//www.example.org')`
 | |
|   if (aPathUrl && !aPathUrl.scheme) {
 | |
|     if (aRootUrl) {
 | |
|       aPathUrl.scheme = aRootUrl.scheme;
 | |
|     }
 | |
|     return urlGenerate(aPathUrl);
 | |
|   }
 | |
| 
 | |
|   if (aPathUrl || aPath.match(dataUrlRegexp)) {
 | |
|     return aPath;
 | |
|   }
 | |
| 
 | |
|   // `join('http://', 'www.example.com')`
 | |
|   if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
 | |
|     aRootUrl.host = aPath;
 | |
|     return urlGenerate(aRootUrl);
 | |
|   }
 | |
| 
 | |
|   var joined = aPath.charAt(0) === '/'
 | |
|     ? aPath
 | |
|     : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
 | |
| 
 | |
|   if (aRootUrl) {
 | |
|     aRootUrl.path = joined;
 | |
|     return urlGenerate(aRootUrl);
 | |
|   }
 | |
|   return joined;
 | |
| }
 | |
| exports.join = join;
 | |
| 
 | |
| exports.isAbsolute = function (aPath) {
 | |
|   return aPath.charAt(0) === '/' || urlRegexp.test(aPath);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Make a path relative to a URL or another path.
 | |
|  *
 | |
|  * @param aRoot The root path or URL.
 | |
|  * @param aPath The path or URL to be made relative to aRoot.
 | |
|  */
 | |
| function relative(aRoot, aPath) {
 | |
|   if (aRoot === "") {
 | |
|     aRoot = ".";
 | |
|   }
 | |
| 
 | |
|   aRoot = aRoot.replace(/\/$/, '');
 | |
| 
 | |
|   // It is possible for the path to be above the root. In this case, simply
 | |
|   // checking whether the root is a prefix of the path won't work. Instead, we
 | |
|   // need to remove components from the root one by one, until either we find
 | |
|   // a prefix that fits, or we run out of components to remove.
 | |
|   var level = 0;
 | |
|   while (aPath.indexOf(aRoot + '/') !== 0) {
 | |
|     var index = aRoot.lastIndexOf("/");
 | |
|     if (index < 0) {
 | |
|       return aPath;
 | |
|     }
 | |
| 
 | |
|     // If the only part of the root that is left is the scheme (i.e. http://,
 | |
|     // file:///, etc.), one or more slashes (/), or simply nothing at all, we
 | |
|     // have exhausted all components, so the path is not relative to the root.
 | |
|     aRoot = aRoot.slice(0, index);
 | |
|     if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
 | |
|       return aPath;
 | |
|     }
 | |
| 
 | |
|     ++level;
 | |
|   }
 | |
| 
 | |
|   // Make sure we add a "../" for each component we removed from the root.
 | |
|   return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
 | |
| }
 | |
| exports.relative = relative;
 | |
| 
 | |
| var supportsNullProto = (function () {
 | |
|   var obj = Object.create(null);
 | |
|   return !('__proto__' in obj);
 | |
| }());
 | |
| 
 | |
| function identity (s) {
 | |
|   return s;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Because behavior goes wacky when you set `__proto__` on objects, we
 | |
|  * have to prefix all the strings in our set with an arbitrary character.
 | |
|  *
 | |
|  * See https://github.com/mozilla/source-map/pull/31 and
 | |
|  * https://github.com/mozilla/source-map/issues/30
 | |
|  *
 | |
|  * @param String aStr
 | |
|  */
 | |
| function toSetString(aStr) {
 | |
|   if (isProtoString(aStr)) {
 | |
|     return '$' + aStr;
 | |
|   }
 | |
| 
 | |
|   return aStr;
 | |
| }
 | |
| exports.toSetString = supportsNullProto ? identity : toSetString;
 | |
| 
 | |
| function fromSetString(aStr) {
 | |
|   if (isProtoString(aStr)) {
 | |
|     return aStr.slice(1);
 | |
|   }
 | |
| 
 | |
|   return aStr;
 | |
| }
 | |
| exports.fromSetString = supportsNullProto ? identity : fromSetString;
 | |
| 
 | |
| function isProtoString(s) {
 | |
|   if (!s) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   var length = s.length;
 | |
| 
 | |
|   if (length < 9 /* "__proto__".length */) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (s.charCodeAt(length - 1) !== 95  /* '_' */ ||
 | |
|       s.charCodeAt(length - 2) !== 95  /* '_' */ ||
 | |
|       s.charCodeAt(length - 3) !== 111 /* 'o' */ ||
 | |
|       s.charCodeAt(length - 4) !== 116 /* 't' */ ||
 | |
|       s.charCodeAt(length - 5) !== 111 /* 'o' */ ||
 | |
|       s.charCodeAt(length - 6) !== 114 /* 'r' */ ||
 | |
|       s.charCodeAt(length - 7) !== 112 /* 'p' */ ||
 | |
|       s.charCodeAt(length - 8) !== 95  /* '_' */ ||
 | |
|       s.charCodeAt(length - 9) !== 95  /* '_' */) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   for (var i = length - 10; i >= 0; i--) {
 | |
|     if (s.charCodeAt(i) !== 36 /* '$' */) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Comparator between two mappings where the original positions are compared.
 | |
|  *
 | |
|  * Optionally pass in `true` as `onlyCompareGenerated` to consider two
 | |
|  * mappings with the same original source/line/column, but different generated
 | |
|  * line and column the same. Useful when searching for a mapping with a
 | |
|  * stubbed out mapping.
 | |
|  */
 | |
| function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
 | |
|   var cmp = strcmp(mappingA.source, mappingB.source);
 | |
|   if (cmp !== 0) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   cmp = mappingA.originalLine - mappingB.originalLine;
 | |
|   if (cmp !== 0) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   cmp = mappingA.originalColumn - mappingB.originalColumn;
 | |
|   if (cmp !== 0 || onlyCompareOriginal) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   cmp = mappingA.generatedColumn - mappingB.generatedColumn;
 | |
|   if (cmp !== 0) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   cmp = mappingA.generatedLine - mappingB.generatedLine;
 | |
|   if (cmp !== 0) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   return strcmp(mappingA.name, mappingB.name);
 | |
| }
 | |
| exports.compareByOriginalPositions = compareByOriginalPositions;
 | |
| 
 | |
| /**
 | |
|  * Comparator between two mappings with deflated source and name indices where
 | |
|  * the generated positions are compared.
 | |
|  *
 | |
|  * Optionally pass in `true` as `onlyCompareGenerated` to consider two
 | |
|  * mappings with the same generated line and column, but different
 | |
|  * source/name/original line and column the same. Useful when searching for a
 | |
|  * mapping with a stubbed out mapping.
 | |
|  */
 | |
| function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
 | |
|   var cmp = mappingA.generatedLine - mappingB.generatedLine;
 | |
|   if (cmp !== 0) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   cmp = mappingA.generatedColumn - mappingB.generatedColumn;
 | |
|   if (cmp !== 0 || onlyCompareGenerated) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   cmp = strcmp(mappingA.source, mappingB.source);
 | |
|   if (cmp !== 0) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   cmp = mappingA.originalLine - mappingB.originalLine;
 | |
|   if (cmp !== 0) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   cmp = mappingA.originalColumn - mappingB.originalColumn;
 | |
|   if (cmp !== 0) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   return strcmp(mappingA.name, mappingB.name);
 | |
| }
 | |
| exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
 | |
| 
 | |
| function strcmp(aStr1, aStr2) {
 | |
|   if (aStr1 === aStr2) {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   if (aStr1 === null) {
 | |
|     return 1; // aStr2 !== null
 | |
|   }
 | |
| 
 | |
|   if (aStr2 === null) {
 | |
|     return -1; // aStr1 !== null
 | |
|   }
 | |
| 
 | |
|   if (aStr1 > aStr2) {
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Comparator between two mappings with inflated source and name strings where
 | |
|  * the generated positions are compared.
 | |
|  */
 | |
| function compareByGeneratedPositionsInflated(mappingA, mappingB) {
 | |
|   var cmp = mappingA.generatedLine - mappingB.generatedLine;
 | |
|   if (cmp !== 0) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   cmp = mappingA.generatedColumn - mappingB.generatedColumn;
 | |
|   if (cmp !== 0) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   cmp = strcmp(mappingA.source, mappingB.source);
 | |
|   if (cmp !== 0) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   cmp = mappingA.originalLine - mappingB.originalLine;
 | |
|   if (cmp !== 0) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   cmp = mappingA.originalColumn - mappingB.originalColumn;
 | |
|   if (cmp !== 0) {
 | |
|     return cmp;
 | |
|   }
 | |
| 
 | |
|   return strcmp(mappingA.name, mappingB.name);
 | |
| }
 | |
| exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
 | |
| 
 | |
| /**
 | |
|  * Strip any JSON XSSI avoidance prefix from the string (as documented
 | |
|  * in the source maps specification), and then parse the string as
 | |
|  * JSON.
 | |
|  */
 | |
| function parseSourceMapInput(str) {
 | |
|   return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, ''));
 | |
| }
 | |
| exports.parseSourceMapInput = parseSourceMapInput;
 | |
| 
 | |
| /**
 | |
|  * Compute the URL of a source given the the source root, the source's
 | |
|  * URL, and the source map's URL.
 | |
|  */
 | |
| function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) {
 | |
|   sourceURL = sourceURL || '';
 | |
| 
 | |
|   if (sourceRoot) {
 | |
|     // This follows what Chrome does.
 | |
|     if (sourceRoot[sourceRoot.length - 1] !== '/' && sourceURL[0] !== '/') {
 | |
|       sourceRoot += '/';
 | |
|     }
 | |
|     // The spec says:
 | |
|     //   Line 4: An optional source root, useful for relocating source
 | |
|     //   files on a server or removing repeated values in the
 | |
|     //   “sources” entry.  This value is prepended to the individual
 | |
|     //   entries in the “source” field.
 | |
|     sourceURL = sourceRoot + sourceURL;
 | |
|   }
 | |
| 
 | |
|   // Historically, SourceMapConsumer did not take the sourceMapURL as
 | |
|   // a parameter.  This mode is still somewhat supported, which is why
 | |
|   // this code block is conditional.  However, it's preferable to pass
 | |
|   // the source map URL to SourceMapConsumer, so that this function
 | |
|   // can implement the source URL resolution algorithm as outlined in
 | |
|   // the spec.  This block is basically the equivalent of:
 | |
|   //    new URL(sourceURL, sourceMapURL).toString()
 | |
|   // ... except it avoids using URL, which wasn't available in the
 | |
|   // older releases of node still supported by this library.
 | |
|   //
 | |
|   // The spec says:
 | |
|   //   If the sources are not absolute URLs after prepending of the
 | |
|   //   “sourceRoot”, the sources are resolved relative to the
 | |
|   //   SourceMap (like resolving script src in a html document).
 | |
|   if (sourceMapURL) {
 | |
|     var parsed = urlParse(sourceMapURL);
 | |
|     if (!parsed) {
 | |
|       throw new Error("sourceMapURL could not be parsed");
 | |
|     }
 | |
|     if (parsed.path) {
 | |
|       // Strip the last path component, but keep the "/".
 | |
|       var index = parsed.path.lastIndexOf('/');
 | |
|       if (index >= 0) {
 | |
|         parsed.path = parsed.path.substring(0, index + 1);
 | |
|       }
 | |
|     }
 | |
|     sourceURL = join(urlGenerate(parsed), sourceURL);
 | |
|   }
 | |
| 
 | |
|   return normalize(sourceURL);
 | |
| }
 | |
| exports.computeSourceURL = computeSourceURL;
 |