feat (string): add Hidden Character Detector tool to identify and analyze hidden Unicode characters

This commit is contained in:
AshAnand34
2025-07-18 17:12:08 -07:00
parent fc18dc0dc0
commit bc5746d359
7 changed files with 575 additions and 1 deletions

View File

@@ -0,0 +1,188 @@
import { expect, describe, it } from 'vitest';
import { analyzeHiddenCharacters, main } from './service';
import { InitialValuesType } from './types';
describe('Hidden Character Detector', () => {
const defaultOptions: InitialValuesType = {
showUnicodeCodes: true,
highlightRTL: true,
showInvisibleChars: true,
includeZeroWidthChars: true
};
describe('analyzeHiddenCharacters', () => {
it('should detect RTL Override characters', () => {
const text = 'Hello\u202EWorld'; // RTL Override
const result = analyzeHiddenCharacters(text, defaultOptions);
expect(result.totalHiddenChars).toBe(1);
expect(result.hasRTLOverride).toBe(true);
expect(result.hiddenCharacters[0].isRTL).toBe(true);
expect(result.hiddenCharacters[0].unicode).toBe('U+202E');
expect(result.hiddenCharacters[0].name).toBe('Right-to-Left Override');
});
it('should detect invisible characters', () => {
const text = 'Hello\u200BWorld'; // Zero Width Space
const result = analyzeHiddenCharacters(text, defaultOptions);
expect(result.totalHiddenChars).toBe(1);
expect(result.hasInvisibleChars).toBe(true);
expect(result.hiddenCharacters[0].isInvisible).toBe(true);
expect(result.hiddenCharacters[0].unicode).toBe('U+200B');
expect(result.hiddenCharacters[0].name).toBe('Zero Width Space');
});
it('should detect zero-width characters', () => {
const text = 'Hello\u200CWorld'; // Zero Width Non-Joiner
const result = analyzeHiddenCharacters(text, defaultOptions);
expect(result.totalHiddenChars).toBe(1);
expect(result.hasZeroWidthChars).toBe(true);
expect(result.hiddenCharacters[0].isZeroWidth).toBe(true);
expect(result.hiddenCharacters[0].unicode).toBe('U+200C');
});
it('should detect multiple hidden characters', () => {
const text = 'Hello\u202E\u200BWorld'; // RTL Override + Zero Width Space
const result = analyzeHiddenCharacters(text, defaultOptions);
expect(result.totalHiddenChars).toBe(2);
expect(result.hasRTLOverride).toBe(true);
expect(result.hasInvisibleChars).toBe(true);
expect(result.hasZeroWidthChars).toBe(true);
});
it('should detect control characters', () => {
const text = 'Hello\u0000World'; // Null character
const result = analyzeHiddenCharacters(text, defaultOptions);
expect(result.totalHiddenChars).toBe(1);
expect(result.hiddenCharacters[0].category).toBe('Control Character');
expect(result.hiddenCharacters[0].isInvisible).toBe(true);
});
it('should not detect regular characters', () => {
const text = 'Hello World';
const result = analyzeHiddenCharacters(text, defaultOptions);
expect(result.totalHiddenChars).toBe(0);
expect(result.hasRTLOverride).toBe(false);
expect(result.hasInvisibleChars).toBe(false);
expect(result.hasZeroWidthChars).toBe(false);
});
it('should filter based on options', () => {
const text = 'Hello\u202E\u200BWorld';
const options: InitialValuesType = {
...defaultOptions,
highlightRTL: false,
showInvisibleChars: true
};
const result = analyzeHiddenCharacters(text, options);
expect(result.totalHiddenChars).toBe(1); // Only invisible chars
expect(result.hasRTLOverride).toBe(false);
expect(result.hasInvisibleChars).toBe(true);
});
it('should provide correct character positions', () => {
const text = 'Hello\u202EWorld';
const result = analyzeHiddenCharacters(text, defaultOptions);
expect(result.hiddenCharacters[0].position).toBe(5);
expect(result.hiddenCharacters[0].char).toBe('\u202E');
});
});
describe('main function', () => {
it('should return message when no hidden characters found', () => {
const text = 'Hello World';
const result = main(text, defaultOptions);
expect(result).toBe('No hidden characters detected in the text.');
});
it('should return detailed analysis when hidden characters found', () => {
const text = 'Hello\u202EWorld';
const result = main(text, defaultOptions);
expect(result).toContain('Found 1 hidden character(s):');
expect(result).toContain('Position 5: Right-to-Left Override (U+202E)');
expect(result).toContain('Category: RTL Override');
expect(result).toContain('⚠️ RTL Override Character');
expect(result).toContain('WARNING: RTL Override characters detected!');
});
it('should include Unicode codes when showUnicodeCodes is true', () => {
const text = 'Hello\u200BWorld';
const options: InitialValuesType = {
...defaultOptions,
showUnicodeCodes: true
};
const result = main(text, options);
expect(result).toContain('Unicode: U+200B');
});
it('should not include Unicode codes when showUnicodeCodes is false', () => {
const text = 'Hello\u200BWorld';
const options: InitialValuesType = {
...defaultOptions,
showUnicodeCodes: false
};
const result = main(text, options);
expect(result).not.toContain('Unicode: U+200B');
});
it('should handle multiple RTL characters', () => {
const text = 'Hello\u202E\u202DWorld';
const result = main(text, defaultOptions);
expect(result).toContain('Found 2 hidden character(s):');
expect(result).toContain('Right-to-Left Override');
expect(result).toContain('Left-to-Right Override');
});
it('should handle mixed character types', () => {
const text = 'Hello\u202E\u200B\u200CWorld';
const result = main(text, defaultOptions);
expect(result).toContain('Found 3 hidden character(s):');
expect(result).toContain('RTL Override Character');
expect(result).toContain('Invisible Character');
expect(result).toContain('Zero Width Character');
});
});
describe('edge cases', () => {
it('should handle empty string', () => {
const result = analyzeHiddenCharacters('', defaultOptions);
expect(result.totalHiddenChars).toBe(0);
expect(result.originalText).toBe('');
});
it('should handle string with only hidden characters', () => {
const text = '\u202E\u200B\u200C';
const result = analyzeHiddenCharacters(text, defaultOptions);
expect(result.totalHiddenChars).toBe(3);
expect(result.hasRTLOverride).toBe(true);
expect(result.hasInvisibleChars).toBe(true);
expect(result.hasZeroWidthChars).toBe(true);
});
it('should handle very long strings', () => {
const text = 'A'.repeat(1000) + '\u202E' + 'B'.repeat(1000);
const result = analyzeHiddenCharacters(text, defaultOptions);
expect(result.totalHiddenChars).toBe(1);
expect(result.hiddenCharacters[0].position).toBe(1000);
});
});
});