Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,48 @@ Options can be set as attributes in the `<link>` tag.
| `data-phonetic` | `true` | Add phonetic information for screen readers. |
| `data-search` | `false` | Find and mark up untagged call signs in the document. |

# Testing
This project includes comprehensive unit tests using Jest with a primary focus on regex pattern validation.

## Running Tests
```bash
# Install dependencies
npm install

# Run all tests
npm test

# Run linting
npm run lint
```

## Test Coverage (71 tests total)
The test suite focuses primarily on validating the two core regex patterns that drive the library's functionality:

### 1. **SEARCH_REGEX Pattern Tests** (`tests/searchCallsigns.test.js`)
Tests the regex pattern `/([A-Z,\d]{1,3}\d[A-Z]{1,3}(?:\/\d)?)\s/` that detects call signs in text:
- Valid call sign pattern matching (single/double/triple letter prefixes)
- Portable indicator detection (`/3`, `/5`, etc.)
- Edge cases and boundary conditions
- Invalid pattern rejection (no trailing space, wrong format, etc.)
- Real-world call sign examples from multiple countries
- Whitespace handling and greedy matching behavior

### 2. **PARTS_REGEX Pattern Tests** (`tests/partsRegex.test.js`)
Tests the regex pattern `/([A-Z,\d]{1,3})(\d)([A-Z]{1,3})(?:\/(\d))?/` that parses call signs into components:
- Prefix parsing (1-3 characters: W, SM, VK2, etc.)
- Area digit extraction (0-9)
- Suffix parsing (1-3 letters: A, AB, ABC)
- Portable indicator capture group
- Greedy matching behavior with long prefixes
- Component extraction from embedded text

### 3. **Supporting Method Tests**
- `tests/getFlag.test.js` - ISO code to Unicode flag conversion (used after PREFIX_TABLE matching)
- `tests/getPhonetics.test.js` - Phonetic alphabet mapping for regex-parsed call signs

Test files are located in the `tests/` directory with clear documentation of each regex pattern's behavior and edge cases.

# Minification
The files are intentionally not provided [minified](https://en.wikipedia.org/wiki/Minification_(programming)).
Amateur radio is about learning and experimenting.
Expand Down
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
{
"type": "module",
"scripts": {
"test": "jest",
"lint": "eslint src/"
},
"devDependencies": {
"eslint": "^9.4.0"
"eslint": "^9.4.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0"
},
"jest": {
"testEnvironment": "jsdom",
"transform": {}
}
}
44 changes: 44 additions & 0 deletions tests/getFlag.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Unit tests for the getFlag method
* Validates the conversion of ISO country codes extracted by PREFIX_TABLE regex matching
* to Unicode Regional Indicator Symbols (emoji flags)
*/

/**
* Converts an ISO country code to a Unicode Regional Indicator Symbol (emoji flag).
* @param {!string} code The ISO 3166-1 alpha-2 code
* @returns {string}
*/
function getFlag(code) {
'use strict';
return String.fromCodePoint(...[...code].map(c => c.charCodeAt() + 127397));
}

describe('getFlag method - ISO code to emoji conversion', () => {
test('should correctly convert common country codes from PREFIX_TABLE', () => {
// Test codes that would be matched from PREFIX_TABLE after regex parsing
expect(getFlag('US')).toBe(String.fromCodePoint(127482, 127480)); // πŸ‡ΊπŸ‡Έ
expect(getFlag('SE')).toBe(String.fromCodePoint(127480, 127466)); // πŸ‡ΈπŸ‡ͺ
expect(getFlag('DE')).toBe(String.fromCodePoint(127465, 127466)); // πŸ‡©πŸ‡ͺ
expect(getFlag('GB')).toBe(String.fromCodePoint(127468, 127463)); // πŸ‡¬πŸ‡§
expect(getFlag('JP')).toBe(String.fromCodePoint(127471, 127477)); // πŸ‡―πŸ‡΅
expect(getFlag('CA')).toBe(String.fromCodePoint(127464, 127462)); // πŸ‡¨πŸ‡¦
});

test('should apply correct mathematical transformation (charCode + 127397)', () => {
// A = 65, 65 + 127397 = 127462 (Regional Indicator A)
// Z = 90, 90 + 127397 = 127487 (Regional Indicator Z)
expect(getFlag('AA')).toBe(String.fromCodePoint(127462, 127462));
expect(getFlag('ZZ')).toBe(String.fromCodePoint(127487, 127487));
});

test('should handle all ISO codes from PREFIX_TABLE entries', () => {
// Test a sample of codes that appear in the PREFIX_TABLE
const prefixCodes = ['AU', 'BR', 'FR', 'IT', 'MX', 'ES', 'CN', 'IN'];
prefixCodes.forEach(code => {
const result = getFlag(code);
expect(result).toBeTruthy();
expect(result.length).toBe(4); // Each emoji flag is 2 code points (4 bytes in UTF-16)
});
});
});
137 changes: 137 additions & 0 deletions tests/getPhonetics.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/**
* Unit tests for the getPhonetics method
* Tests phonetic alphabet mapping for characters extracted from call signs via PARTS_REGEX
*/

// Copy the phonetic table and getPhonetics method for testing
const PHONETIC_TABLE = new Map([
['A', 'Alfa'],
['B', 'Bravo'],
['C', 'Charlie'],
['D', 'Delta'],
['E', 'Echo'],
['F', 'Foxtrot'],
['G', 'Golf'],
['H', 'Hotel'],
['I', 'India'],
['J', 'Juliett'],
['K', 'Kilo'],
['L', 'Lima'],
['M', 'Mike'],
['N', 'November'],
['O', 'Oscar'],
['P', 'Papa'],
['Q', 'Quebec'],
['R', 'Romeo'],
['S', 'Sierra'],
['T', 'Tango'],
['U', 'Uniform'],
['V', 'Victor'],
['W', 'Whiskey'],
['X', 'X-ray'],
['Y', 'Yankee'],
['Z', 'Zulu'],
['0', 'Ziro'],
['1', 'One'],
['2', 'Two'],
['3', 'Tree'],
['4', 'Four'],
['5', 'Five'],
['6', 'Six'],
['7', 'Seven'],
['8', 'Eight'],
['9', 'Niner'],
]);

/**
* @param {string} letters The string of letters to expand
* @returns {string}
*/
function getPhonetics(letters) {
'use strict';
let ret = "";
for (var i = 0; i < letters.length; i++) {
ret += PHONETIC_TABLE.get(letters.charAt(i)) + " ";
}
return ret.slice(0, -1);
}

describe('getPhonetics method - phonetic mapping for regex-parsed call signs', () => {
describe('Complete call sign phonetic conversion', () => {
test('should convert call signs matched by SEARCH_REGEX', () => {
// These would be matched by SEARCH_REGEX and parsed by PARTS_REGEX
expect(getPhonetics('W1AW')).toBe('Whiskey One Alfa Whiskey');
expect(getPhonetics('K2ABC')).toBe('Kilo Two Alfa Bravo Charlie');
expect(getPhonetics('SM8AYA')).toBe('Sierra Mike Eight Alfa Yankee Alfa');
expect(getPhonetics('DL1ABC')).toBe('Delta Lima One Alfa Bravo Charlie');
});

test('should handle all alphabet characters A-Z from PHONETIC_TABLE', () => {
const tests = [
['A', 'Alfa'], ['B', 'Bravo'], ['C', 'Charlie'], ['D', 'Delta'],
['E', 'Echo'], ['F', 'Foxtrot'], ['G', 'Golf'], ['H', 'Hotel'],
['I', 'India'], ['J', 'Juliett'], ['K', 'Kilo'], ['L', 'Lima'],
['M', 'Mike'], ['N', 'November'], ['O', 'Oscar'], ['P', 'Papa'],
['Q', 'Quebec'], ['R', 'Romeo'], ['S', 'Sierra'], ['T', 'Tango'],
['U', 'Uniform'], ['V', 'Victor'], ['W', 'Whiskey'], ['X', 'X-ray'],
['Y', 'Yankee'], ['Z', 'Zulu']
];

tests.forEach(([char, phonetic]) => {
expect(getPhonetics(char)).toBe(phonetic);
});
});

test('should handle all digits 0-9 from PHONETIC_TABLE', () => {
const tests = [
['0', 'Ziro'], ['1', 'One'], ['2', 'Two'], ['3', 'Tree'],
['4', 'Four'], ['5', 'Five'], ['6', 'Six'], ['7', 'Seven'],
['8', 'Eight'], ['9', 'Niner']
];

tests.forEach(([digit, phonetic]) => {
expect(getPhonetics(digit)).toBe(phonetic);
});
});
});

describe('Edge cases and regex-related scenarios', () => {
test('should handle empty string (no regex match)', () => {
expect(getPhonetics('')).toBe('');
});

test('should handle characters not in PHONETIC_TABLE (like / in portable indicators)', () => {
// Slash in W1ABC/3 is not in the phonetic table
const result = getPhonetics('W1ABC/3');
expect(result).toContain('undefined'); // Will have undefined for /
});

test('should correctly process minimum length call signs (PARTS_REGEX captures)', () => {
// W1A is minimum valid call sign pattern
expect(getPhonetics('W1A')).toBe('Whiskey One Alfa');
});

test('should correctly process maximum length call signs', () => {
// ABC1XYZ is maximum length pattern (3+1+3)
expect(getPhonetics('ABC1XYZ')).toBe('Alfa Bravo Charlie One X-ray Yankee Zulu');
});
});

describe('Real-world regex-matched patterns', () => {
test('should handle common US prefixes matched by PREFIX_TABLE', () => {
// US prefixes: W, K, N, AA-AL, etc.
expect(getPhonetics('W')).toBe('Whiskey');
expect(getPhonetics('K')).toBe('Kilo');
expect(getPhonetics('N')).toBe('November');
expect(getPhonetics('AA')).toBe('Alfa Alfa');
});

test('should handle international prefixes from PREFIX_TABLE', () => {
// Common international prefixes
expect(getPhonetics('SM')).toBe('Sierra Mike'); // Sweden
expect(getPhonetics('DL')).toBe('Delta Lima'); // Germany
expect(getPhonetics('JA')).toBe('Juliett Alfa'); // Japan
expect(getPhonetics('VK')).toBe('Victor Kilo'); // Australia
});
});
});
Loading