 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>
		
			
				
	
	
		
			162 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			162 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| const path = require('path');
 | |
| const { promisify } = require('util');
 | |
| const glob = promisify(require('glob'));
 | |
| const minimatch = require('minimatch');
 | |
| const { defaults } = require('@istanbuljs/schema');
 | |
| const isOutsideDir = require('./is-outside-dir');
 | |
| 
 | |
| class TestExclude {
 | |
|     constructor(opts = {}) {
 | |
|         Object.assign(
 | |
|             this,
 | |
|             {relativePath: true},
 | |
|             defaults.testExclude
 | |
|         );
 | |
| 
 | |
|         for (const [name, value] of Object.entries(opts)) {
 | |
|             if (value !== undefined) {
 | |
|                 this[name] = value;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (typeof this.include === 'string') {
 | |
|             this.include = [this.include];
 | |
|         }
 | |
| 
 | |
|         if (typeof this.exclude === 'string') {
 | |
|             this.exclude = [this.exclude];
 | |
|         }
 | |
| 
 | |
|         if (typeof this.extension === 'string') {
 | |
|             this.extension = [this.extension];
 | |
|         } else if (this.extension.length === 0) {
 | |
|             this.extension = false;
 | |
|         }
 | |
| 
 | |
|         if (this.include && this.include.length > 0) {
 | |
|             this.include = prepGlobPatterns([].concat(this.include));
 | |
|         } else {
 | |
|             this.include = false;
 | |
|         }
 | |
| 
 | |
|         if (
 | |
|             this.excludeNodeModules &&
 | |
|             !this.exclude.includes('**/node_modules/**')
 | |
|         ) {
 | |
|             this.exclude = this.exclude.concat('**/node_modules/**');
 | |
|         }
 | |
| 
 | |
|         this.exclude = prepGlobPatterns([].concat(this.exclude));
 | |
| 
 | |
|         this.handleNegation();
 | |
|     }
 | |
| 
 | |
|     /* handle the special case of negative globs
 | |
|      * (!**foo/bar); we create a new this.excludeNegated set
 | |
|      * of rules, which is applied after excludes and we
 | |
|      * move excluded include rules into this.excludes.
 | |
|      */
 | |
|     handleNegation() {
 | |
|         const noNeg = e => e.charAt(0) !== '!';
 | |
|         const onlyNeg = e => e.charAt(0) === '!';
 | |
|         const stripNeg = e => e.slice(1);
 | |
| 
 | |
|         if (Array.isArray(this.include)) {
 | |
|             const includeNegated = this.include.filter(onlyNeg).map(stripNeg);
 | |
|             this.exclude.push(...prepGlobPatterns(includeNegated));
 | |
|             this.include = this.include.filter(noNeg);
 | |
|         }
 | |
| 
 | |
|         this.excludeNegated = this.exclude.filter(onlyNeg).map(stripNeg);
 | |
|         this.exclude = this.exclude.filter(noNeg);
 | |
|         this.excludeNegated = prepGlobPatterns(this.excludeNegated);
 | |
|     }
 | |
| 
 | |
|     shouldInstrument(filename, relFile) {
 | |
|         if (
 | |
|             this.extension &&
 | |
|             !this.extension.some(ext => filename.endsWith(ext))
 | |
|         ) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         let pathToCheck = filename;
 | |
| 
 | |
|         if (this.relativePath) {
 | |
|             relFile = relFile || path.relative(this.cwd, filename);
 | |
| 
 | |
|             // Don't instrument files that are outside of the current working directory.
 | |
|             if (isOutsideDir(this.cwd, filename)) {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             pathToCheck = relFile.replace(/^\.[\\/]/, ''); // remove leading './' or '.\'.
 | |
|         }
 | |
| 
 | |
|         const dot = { dot: true };
 | |
|         const matches = pattern => minimatch(pathToCheck, pattern, dot);
 | |
|         return (
 | |
|             (!this.include || this.include.some(matches)) &&
 | |
|             (!this.exclude.some(matches) || this.excludeNegated.some(matches))
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     globSync(cwd = this.cwd) {
 | |
|         const globPatterns = getExtensionPattern(this.extension || []);
 | |
|         const globOptions = { cwd, nodir: true, dot: true };
 | |
|         /* If we don't have any excludeNegated then we can optimize glob by telling
 | |
|          * it to not iterate into unwanted directory trees (like node_modules). */
 | |
|         if (this.excludeNegated.length === 0) {
 | |
|             globOptions.ignore = this.exclude;
 | |
|         }
 | |
| 
 | |
|         return glob
 | |
|             .sync(globPatterns, globOptions)
 | |
|             .filter(file => this.shouldInstrument(path.resolve(cwd, file)));
 | |
|     }
 | |
| 
 | |
|     async glob(cwd = this.cwd) {
 | |
|         const globPatterns = getExtensionPattern(this.extension || []);
 | |
|         const globOptions = { cwd, nodir: true, dot: true };
 | |
|         /* If we don't have any excludeNegated then we can optimize glob by telling
 | |
|          * it to not iterate into unwanted directory trees (like node_modules). */
 | |
|         if (this.excludeNegated.length === 0) {
 | |
|             globOptions.ignore = this.exclude;
 | |
|         }
 | |
| 
 | |
|         const list = await glob(globPatterns, globOptions);
 | |
|         return list.filter(file => this.shouldInstrument(path.resolve(cwd, file)));
 | |
|     }
 | |
| }
 | |
| 
 | |
| function prepGlobPatterns(patterns) {
 | |
|     return patterns.reduce((result, pattern) => {
 | |
|         // Allow gitignore style of directory exclusion
 | |
|         if (!/\/\*\*$/.test(pattern)) {
 | |
|             result = result.concat(pattern.replace(/\/$/, '') + '/**');
 | |
|         }
 | |
| 
 | |
|         // Any rules of the form **/foo.js, should also match foo.js.
 | |
|         if (/^\*\*\//.test(pattern)) {
 | |
|             result = result.concat(pattern.replace(/^\*\*\//, ''));
 | |
|         }
 | |
| 
 | |
|         return result.concat(pattern);
 | |
|     }, []);
 | |
| }
 | |
| 
 | |
| function getExtensionPattern(extension) {
 | |
|     switch (extension.length) {
 | |
|         case 0:
 | |
|             return '**';
 | |
|         case 1:
 | |
|             return `**/*${extension[0]}`;
 | |
|         default:
 | |
|             return `**/*{${extension.join()}}`;
 | |
|     }
 | |
| }
 | |
| 
 | |
| module.exports = TestExclude;
 |