 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>
		
			
				
	
	
		
			197 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { CODE_POINTS as $, getSurrogatePairCodePoint, isControlCodePoint, isSurrogate, isSurrogatePair, isUndefinedCodePoint, } from '../common/unicode.js';
 | |
| import { ERR } from '../common/error-codes.js';
 | |
| //Const
 | |
| const DEFAULT_BUFFER_WATERLINE = 1 << 16;
 | |
| //Preprocessor
 | |
| //NOTE: HTML input preprocessing
 | |
| //(see: http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#preprocessing-the-input-stream)
 | |
| export class Preprocessor {
 | |
|     constructor(handler) {
 | |
|         this.handler = handler;
 | |
|         this.html = '';
 | |
|         this.pos = -1;
 | |
|         // NOTE: Initial `lastGapPos` is -2, to ensure `col` on initialisation is 0
 | |
|         this.lastGapPos = -2;
 | |
|         this.gapStack = [];
 | |
|         this.skipNextNewLine = false;
 | |
|         this.lastChunkWritten = false;
 | |
|         this.endOfChunkHit = false;
 | |
|         this.bufferWaterline = DEFAULT_BUFFER_WATERLINE;
 | |
|         this.isEol = false;
 | |
|         this.lineStartPos = 0;
 | |
|         this.droppedBufferSize = 0;
 | |
|         this.line = 1;
 | |
|         //NOTE: avoid reporting errors twice on advance/retreat
 | |
|         this.lastErrOffset = -1;
 | |
|     }
 | |
|     /** The column on the current line. If we just saw a gap (eg. a surrogate pair), return the index before. */
 | |
|     get col() {
 | |
|         return this.pos - this.lineStartPos + Number(this.lastGapPos !== this.pos);
 | |
|     }
 | |
|     get offset() {
 | |
|         return this.droppedBufferSize + this.pos;
 | |
|     }
 | |
|     getError(code, cpOffset) {
 | |
|         const { line, col, offset } = this;
 | |
|         const startCol = col + cpOffset;
 | |
|         const startOffset = offset + cpOffset;
 | |
|         return {
 | |
|             code,
 | |
|             startLine: line,
 | |
|             endLine: line,
 | |
|             startCol,
 | |
|             endCol: startCol,
 | |
|             startOffset,
 | |
|             endOffset: startOffset,
 | |
|         };
 | |
|     }
 | |
|     _err(code) {
 | |
|         if (this.handler.onParseError && this.lastErrOffset !== this.offset) {
 | |
|             this.lastErrOffset = this.offset;
 | |
|             this.handler.onParseError(this.getError(code, 0));
 | |
|         }
 | |
|     }
 | |
|     _addGap() {
 | |
|         this.gapStack.push(this.lastGapPos);
 | |
|         this.lastGapPos = this.pos;
 | |
|     }
 | |
|     _processSurrogate(cp) {
 | |
|         //NOTE: try to peek a surrogate pair
 | |
|         if (this.pos !== this.html.length - 1) {
 | |
|             const nextCp = this.html.charCodeAt(this.pos + 1);
 | |
|             if (isSurrogatePair(nextCp)) {
 | |
|                 //NOTE: we have a surrogate pair. Peek pair character and recalculate code point.
 | |
|                 this.pos++;
 | |
|                 //NOTE: add a gap that should be avoided during retreat
 | |
|                 this._addGap();
 | |
|                 return getSurrogatePairCodePoint(cp, nextCp);
 | |
|             }
 | |
|         }
 | |
|         //NOTE: we are at the end of a chunk, therefore we can't infer the surrogate pair yet.
 | |
|         else if (!this.lastChunkWritten) {
 | |
|             this.endOfChunkHit = true;
 | |
|             return $.EOF;
 | |
|         }
 | |
|         //NOTE: isolated surrogate
 | |
|         this._err(ERR.surrogateInInputStream);
 | |
|         return cp;
 | |
|     }
 | |
|     willDropParsedChunk() {
 | |
|         return this.pos > this.bufferWaterline;
 | |
|     }
 | |
|     dropParsedChunk() {
 | |
|         if (this.willDropParsedChunk()) {
 | |
|             this.html = this.html.substring(this.pos);
 | |
|             this.lineStartPos -= this.pos;
 | |
|             this.droppedBufferSize += this.pos;
 | |
|             this.pos = 0;
 | |
|             this.lastGapPos = -2;
 | |
|             this.gapStack.length = 0;
 | |
|         }
 | |
|     }
 | |
|     write(chunk, isLastChunk) {
 | |
|         if (this.html.length > 0) {
 | |
|             this.html += chunk;
 | |
|         }
 | |
|         else {
 | |
|             this.html = chunk;
 | |
|         }
 | |
|         this.endOfChunkHit = false;
 | |
|         this.lastChunkWritten = isLastChunk;
 | |
|     }
 | |
|     insertHtmlAtCurrentPos(chunk) {
 | |
|         this.html = this.html.substring(0, this.pos + 1) + chunk + this.html.substring(this.pos + 1);
 | |
|         this.endOfChunkHit = false;
 | |
|     }
 | |
|     startsWith(pattern, caseSensitive) {
 | |
|         // Check if our buffer has enough characters
 | |
|         if (this.pos + pattern.length > this.html.length) {
 | |
|             this.endOfChunkHit = !this.lastChunkWritten;
 | |
|             return false;
 | |
|         }
 | |
|         if (caseSensitive) {
 | |
|             return this.html.startsWith(pattern, this.pos);
 | |
|         }
 | |
|         for (let i = 0; i < pattern.length; i++) {
 | |
|             const cp = this.html.charCodeAt(this.pos + i) | 0x20;
 | |
|             if (cp !== pattern.charCodeAt(i)) {
 | |
|                 return false;
 | |
|             }
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
|     peek(offset) {
 | |
|         const pos = this.pos + offset;
 | |
|         if (pos >= this.html.length) {
 | |
|             this.endOfChunkHit = !this.lastChunkWritten;
 | |
|             return $.EOF;
 | |
|         }
 | |
|         const code = this.html.charCodeAt(pos);
 | |
|         return code === $.CARRIAGE_RETURN ? $.LINE_FEED : code;
 | |
|     }
 | |
|     advance() {
 | |
|         this.pos++;
 | |
|         //NOTE: LF should be in the last column of the line
 | |
|         if (this.isEol) {
 | |
|             this.isEol = false;
 | |
|             this.line++;
 | |
|             this.lineStartPos = this.pos;
 | |
|         }
 | |
|         if (this.pos >= this.html.length) {
 | |
|             this.endOfChunkHit = !this.lastChunkWritten;
 | |
|             return $.EOF;
 | |
|         }
 | |
|         let cp = this.html.charCodeAt(this.pos);
 | |
|         //NOTE: all U+000D CARRIAGE RETURN (CR) characters must be converted to U+000A LINE FEED (LF) characters
 | |
|         if (cp === $.CARRIAGE_RETURN) {
 | |
|             this.isEol = true;
 | |
|             this.skipNextNewLine = true;
 | |
|             return $.LINE_FEED;
 | |
|         }
 | |
|         //NOTE: any U+000A LINE FEED (LF) characters that immediately follow a U+000D CARRIAGE RETURN (CR) character
 | |
|         //must be ignored.
 | |
|         if (cp === $.LINE_FEED) {
 | |
|             this.isEol = true;
 | |
|             if (this.skipNextNewLine) {
 | |
|                 // `line` will be bumped again in the recursive call.
 | |
|                 this.line--;
 | |
|                 this.skipNextNewLine = false;
 | |
|                 this._addGap();
 | |
|                 return this.advance();
 | |
|             }
 | |
|         }
 | |
|         this.skipNextNewLine = false;
 | |
|         if (isSurrogate(cp)) {
 | |
|             cp = this._processSurrogate(cp);
 | |
|         }
 | |
|         //OPTIMIZATION: first check if code point is in the common allowed
 | |
|         //range (ASCII alphanumeric, whitespaces, big chunk of BMP)
 | |
|         //before going into detailed performance cost validation.
 | |
|         const isCommonValidRange = this.handler.onParseError === null ||
 | |
|             (cp > 0x1f && cp < 0x7f) ||
 | |
|             cp === $.LINE_FEED ||
 | |
|             cp === $.CARRIAGE_RETURN ||
 | |
|             (cp > 0x9f && cp < 64976);
 | |
|         if (!isCommonValidRange) {
 | |
|             this._checkForProblematicCharacters(cp);
 | |
|         }
 | |
|         return cp;
 | |
|     }
 | |
|     _checkForProblematicCharacters(cp) {
 | |
|         if (isControlCodePoint(cp)) {
 | |
|             this._err(ERR.controlCharacterInInputStream);
 | |
|         }
 | |
|         else if (isUndefinedCodePoint(cp)) {
 | |
|             this._err(ERR.noncharacterInInputStream);
 | |
|         }
 | |
|     }
 | |
|     retreat(count) {
 | |
|         this.pos -= count;
 | |
|         while (this.pos < this.lastGapPos) {
 | |
|             this.lastGapPos = this.gapStack.pop();
 | |
|             this.pos--;
 | |
|         }
 | |
|         this.isEol = false;
 | |
|     }
 | |
| }
 |