From 742f060ebac9f4685fb968aaa5a7d99fd1ba83a1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 8 Sep 2025 23:08:45 +0000
Subject: [PATCH 1/3] Initial plan
From d79a7d36456e33afc87b94b911a8d4ac42477667 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 8 Sep 2025 23:14:55 +0000
Subject: [PATCH 2/3] Add Jest testing framework with unit tests for core
functionalities
Co-authored-by: phieri <12006381+phieri@users.noreply.github.com>
---
README.md | 27 ++++++++
package.json | 12 +++-
tests/getFlag.test.js | 76 ++++++++++++++++++++
tests/getPhonetics.test.js | 126 ++++++++++++++++++++++++++++++++++
tests/searchCallsigns.test.js | 124 +++++++++++++++++++++++++++++++++
5 files changed, 364 insertions(+), 1 deletion(-)
create mode 100644 tests/getFlag.test.js
create mode 100644 tests/getPhonetics.test.js
create mode 100644 tests/searchCallsigns.test.js
diff --git a/README.md b/README.md
index e1c8aa8..c1154f6 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,33 @@ Options can be set as attributes in the `` 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 unit tests using Jest to ensure code quality and functionality.
+
+## Running Tests
+```bash
+# Install dependencies
+npm install
+
+# Run all tests
+npm test
+
+# Run linting
+npm run lint
+```
+
+## Test Coverage
+The test suite covers three core functionalities:
+
+1. **`getFlag` Method**: Tests conversion of ISO country codes to Unicode Regional Indicator Symbols (emoji flags)
+2. **`getPhonetics` Method**: Tests mapping of characters to their phonetic alphabet equivalents
+3. **`searchCallsigns` Method**: Tests basic functionality for detecting and wrapping untagged call signs
+
+Test files are located in the `tests/` directory:
+- `tests/getFlag.test.js` - Flag generation tests
+- `tests/getPhonetics.test.js` - Phonetic alphabet tests
+- `tests/searchCallsigns.test.js` - Call sign detection tests
+
# Minification
The files are intentionally not provided [minified](https://en.wikipedia.org/wiki/Minification_(programming)).
Amateur radio is about learning and experimenting.
diff --git a/package.json b/package.json
index 81b9c1a..ec11c1b 100644
--- a/package.json
+++ b/package.json
@@ -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": {}
}
}
diff --git a/tests/getFlag.test.js b/tests/getFlag.test.js
new file mode 100644
index 0000000..c64c4b0
--- /dev/null
+++ b/tests/getFlag.test.js
@@ -0,0 +1,76 @@
+/**
+ * Unit tests for the getFlag method
+ * Tests the conversion of ISO country codes to Unicode Regional Indicator Symbols (emoji flags)
+ */
+
+// Since the callsign.js file is meant for browser environments and uses custom elements,
+// we'll test the getFlag method by copying its implementation for testing purposes
+// This approach is necessary because the original file depends on browser APIs
+
+/**
+ * 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', () => {
+ test('should convert US code to US flag emoji', () => {
+ const result = getFlag('US');
+ // US flag emoji is represented by these code points
+ const expected = String.fromCodePoint(127482, 127480); // πΊπΈ
+ expect(result).toBe(expected);
+ });
+
+ test('should convert SE code to Swedish flag emoji', () => {
+ const result = getFlag('SE');
+ // SE flag emoji
+ const expected = String.fromCodePoint(127480, 127466); // πΈπͺ
+ expect(result).toBe(expected);
+ });
+
+ test('should convert GB code to UK flag emoji', () => {
+ const result = getFlag('GB');
+ // GB flag emoji
+ const expected = String.fromCodePoint(127468, 127463); // π¬π§
+ expect(result).toBe(expected);
+ });
+
+ test('should convert DE code to German flag emoji', () => {
+ const result = getFlag('DE');
+ // DE flag emoji
+ const expected = String.fromCodePoint(127465, 127466); // π©πͺ
+ expect(result).toBe(expected);
+ });
+
+ test('should convert JP code to Japanese flag emoji', () => {
+ const result = getFlag('JP');
+ // JP flag emoji
+ const expected = String.fromCodePoint(127471, 127477); // π―π΅
+ expect(result).toBe(expected);
+ });
+
+ test('should handle lowercase input by converting correctly', () => {
+ // The original function expects uppercase, but let's test with lowercase
+ const result = getFlag('us');
+ // This will produce different unicode points for lowercase
+ const expected = String.fromCodePoint(127514, 127512); // Different from uppercase
+ expect(result).toBe(expected);
+ });
+
+ test('should handle two-character codes correctly', () => {
+ const result = getFlag('CA');
+ const expected = String.fromCodePoint(127464, 127462); // π¨π¦
+ expect(result).toBe(expected);
+ });
+
+ test('should convert each character correctly using the offset', () => {
+ // Test the mathematical transformation: A = 65, 65 + 127397 = 127462
+ const result = getFlag('AA');
+ const expected = String.fromCodePoint(127462, 127462); // π¦π¦
+ expect(result).toBe(expected);
+ });
+});
\ No newline at end of file
diff --git a/tests/getPhonetics.test.js b/tests/getPhonetics.test.js
new file mode 100644
index 0000000..dacee5c
--- /dev/null
+++ b/tests/getPhonetics.test.js
@@ -0,0 +1,126 @@
+/**
+ * Unit tests for the getPhonetics method
+ * Tests the mapping of characters to their phonetic alphabet equivalents
+ */
+
+// 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', () => {
+ test('should convert single letter to phonetic equivalent', () => {
+ expect(getPhonetics('A')).toBe('Alfa');
+ expect(getPhonetics('B')).toBe('Bravo');
+ expect(getPhonetics('Z')).toBe('Zulu');
+ });
+
+ test('should convert single digit to phonetic equivalent', () => {
+ expect(getPhonetics('0')).toBe('Ziro');
+ expect(getPhonetics('1')).toBe('One');
+ expect(getPhonetics('9')).toBe('Niner');
+ });
+
+ test('should convert typical call sign letters', () => {
+ expect(getPhonetics('W1AW')).toBe('Whiskey One Alfa Whiskey');
+ expect(getPhonetics('SM8AYA')).toBe('Sierra Mike Eight Alfa Yankee Alfa');
+ expect(getPhonetics('DL1ABC')).toBe('Delta Lima One Alfa Bravo Charlie');
+ });
+
+ test('should handle mixed letters and numbers', () => {
+ expect(getPhonetics('K2ABC')).toBe('Kilo Two Alfa Bravo Charlie');
+ expect(getPhonetics('VE3XYZ')).toBe('Victor Echo Tree X-ray Yankee Zulu');
+ });
+
+ test('should return empty string for empty input', () => {
+ expect(getPhonetics('')).toBe('');
+ });
+
+ test('should handle single character input', () => {
+ expect(getPhonetics('X')).toBe('X-ray');
+ expect(getPhonetics('3')).toBe('Tree');
+ });
+
+ test('should handle all letters A-Z correctly', () => {
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ const expected = [
+ 'Alfa', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot',
+ 'Golf', 'Hotel', 'India', 'Juliett', 'Kilo', 'Lima',
+ 'Mike', 'November', 'Oscar', 'Papa', 'Quebec', 'Romeo',
+ 'Sierra', 'Tango', 'Uniform', 'Victor', 'Whiskey',
+ 'X-ray', 'Yankee', 'Zulu'
+ ].join(' ');
+
+ expect(getPhonetics(alphabet)).toBe(expected);
+ });
+
+ test('should handle all digits 0-9 correctly', () => {
+ const digits = '0123456789';
+ const expected = [
+ 'Ziro', 'One', 'Two', 'Tree', 'Four', 'Five',
+ 'Six', 'Seven', 'Eight', 'Niner'
+ ].join(' ');
+
+ expect(getPhonetics(digits)).toBe(expected);
+ });
+
+ test('should handle characters not in phonetic table', () => {
+ // Characters not in the table should result in undefined being added
+ const result = getPhonetics('A/B');
+ expect(result).toBe('Alfa undefined Bravo');
+ });
+
+ test('should handle typical amateur radio call signs', () => {
+ expect(getPhonetics('KD8ABC')).toBe('Kilo Delta Eight Alfa Bravo Charlie');
+ expect(getPhonetics('G0XYZ')).toBe('Golf Ziro X-ray Yankee Zulu');
+ expect(getPhonetics('JA1ABC')).toBe('Juliett Alfa One Alfa Bravo Charlie');
+ });
+});
\ No newline at end of file
diff --git a/tests/searchCallsigns.test.js b/tests/searchCallsigns.test.js
new file mode 100644
index 0000000..703cfd9
--- /dev/null
+++ b/tests/searchCallsigns.test.js
@@ -0,0 +1,124 @@
+/**
+ * Unit tests for the searchCallsigns method
+ * Tests basic functionality for detecting and wrapping untagged call signs in tags
+ */
+
+// Mock the search regex and the searchCallsigns functionality for testing
+const SEARCH_REGEX = /([A-Z,\d]{1,3}\d[A-Z]{1,3}(?:\/\d)?)\s/;
+
+/**
+ * Simplified version of searchCallsigns for testing
+ * @param {string} html Input HTML string
+ * @returns {string} HTML with call signs wrapped in call-sign tags
+ */
+function searchCallsigns(html) {
+ 'use strict';
+ let match;
+ let result = html;
+
+ while ((match = result.match(SEARCH_REGEX)) !== null) {
+ result = result.replace(match[1], '' + match[1] + '');
+ }
+
+ return result;
+}
+
+// Setup jsdom environment
+beforeEach(() => {
+ document.body.innerHTML = '';
+});
+
+describe('searchCallsigns method', () => {
+ test('should detect and wrap simple call sign with space after', () => {
+ const input = 'Contact W1AW today';
+ const expected = 'Contact W1AW today';
+ expect(searchCallsigns(input)).toBe(expected);
+ });
+
+ test('should detect and wrap multiple call signs', () => {
+ const input = 'Contact W1AW and SM8AYA today';
+ const expected = 'Contact W1AW and SM8AYA today';
+ expect(searchCallsigns(input)).toBe(expected);
+ });
+
+ test('should detect call signs with different prefix lengths', () => {
+ // 1-letter prefix + digit + letters
+ expect(searchCallsigns('Call K2ABC ')).toBe('Call K2ABC ');
+
+ // 2-letter prefix + digit + letters
+ expect(searchCallsigns('Call SM8AYA ')).toBe('Call SM8AYA ');
+
+ // 3-letter prefix + digit + letters
+ expect(searchCallsigns('Call VK2ABC ')).toBe('Call VK2ABC ');
+ });
+
+ test('should detect call signs with different suffix lengths', () => {
+ // Single letter suffix
+ expect(searchCallsigns('Call W1A ')).toBe('Call W1A ');
+
+ // Two letter suffix
+ expect(searchCallsigns('Call W1AB ')).toBe('Call W1AB ');
+
+ // Three letter suffix
+ expect(searchCallsigns('Call W1ABC ')).toBe('Call W1ABC ');
+ });
+
+ test('should detect call signs with portable indicators', () => {
+ const input = 'Call W1ABC/3 on the air';
+ const expected = 'Call W1ABC/3 on the air';
+ expect(searchCallsigns(input)).toBe(expected);
+ });
+
+ test('should not wrap call signs without trailing space', () => {
+ // The regex requires a space after the call sign
+ const input = 'CallW1ABC';
+ expect(searchCallsigns(input)).toBe(input); // No change expected
+ });
+
+ test('should handle call signs in different contexts', () => {
+ expect(searchCallsigns('Hello W1AW from K2ABC ')).toBe('Hello W1AW from K2ABC ');
+ });
+
+ test('should handle text with no call signs', () => {
+ const input = 'This is just normal text with no call signs.';
+ expect(searchCallsigns(input)).toBe(input);
+ });
+
+ test('should handle empty string', () => {
+ expect(searchCallsigns('')).toBe('');
+ });
+
+ test('should detect various valid call sign patterns', () => {
+ // US call signs
+ expect(searchCallsigns('W1AW ')).toBe('W1AW ');
+ expect(searchCallsigns('K2ABC ')).toBe('K2ABC ');
+ expect(searchCallsigns('N3XYZ ')).toBe('N3XYZ ');
+
+ // International call signs
+ expect(searchCallsigns('G0ABC ')).toBe('G0ABC ');
+ expect(searchCallsigns('DL1ABC ')).toBe('DL1ABC ');
+ expect(searchCallsigns('JA1XYZ ')).toBe('JA1XYZ ');
+ });
+
+ test('should handle call signs with numbers in prefix', () => {
+ // Some call signs can have numbers in the prefix
+ expect(searchCallsigns('9V1ABC ')).toBe('9V1ABC ');
+ });
+
+ test('should process multiple occurrences iteratively', () => {
+ // Test that the while loop processes all matches
+ const input = 'W1AW K2ABC SM8AYA ';
+ const result = searchCallsigns(input);
+ expect(result).toContain('W1AW');
+ expect(result).toContain('K2ABC');
+ expect(result).toContain('SM8AYA');
+ });
+
+ test('should handle already wrapped call signs correctly', () => {
+ // If a call sign is already wrapped, it shouldn't be wrapped again
+ const input = 'W1AW and K2ABC ';
+ const result = searchCallsigns(input);
+ // Should only wrap the unwrapped call sign
+ expect(result).toBe('W1AW and K2ABC ');
+ });
+});
\ No newline at end of file
From e2d5a59e2cd10c42a7248e1e9741f9fdf4755302 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 22:02:37 +0000
Subject: [PATCH 3/3] Refactor tests to focus primarily on regex pattern
validation
Co-authored-by: phieri <12006381+phieri@users.noreply.github.com>
---
README.md | 39 +++--
tests/getFlag.test.js | 88 +++-------
tests/getPhonetics.test.js | 125 +++++++-------
tests/partsRegex.test.js | 301 +++++++++++++++++++++++++++++++++
tests/searchCallsigns.test.js | 309 ++++++++++++++++++++++------------
5 files changed, 622 insertions(+), 240 deletions(-)
create mode 100644 tests/partsRegex.test.js
diff --git a/README.md b/README.md
index c1154f6..872acea 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ Options can be set as attributes in the `` tag.
| `data-search` | `false` | Find and mark up untagged call signs in the document. |
# Testing
-This project includes unit tests using Jest to ensure code quality and functionality.
+This project includes comprehensive unit tests using Jest with a primary focus on regex pattern validation.
## Running Tests
```bash
@@ -39,17 +39,32 @@ npm test
npm run lint
```
-## Test Coverage
-The test suite covers three core functionalities:
-
-1. **`getFlag` Method**: Tests conversion of ISO country codes to Unicode Regional Indicator Symbols (emoji flags)
-2. **`getPhonetics` Method**: Tests mapping of characters to their phonetic alphabet equivalents
-3. **`searchCallsigns` Method**: Tests basic functionality for detecting and wrapping untagged call signs
-
-Test files are located in the `tests/` directory:
-- `tests/getFlag.test.js` - Flag generation tests
-- `tests/getPhonetics.test.js` - Phonetic alphabet tests
-- `tests/searchCallsigns.test.js` - Call sign detection tests
+## 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)).
diff --git a/tests/getFlag.test.js b/tests/getFlag.test.js
index c64c4b0..96e397a 100644
--- a/tests/getFlag.test.js
+++ b/tests/getFlag.test.js
@@ -1,12 +1,9 @@
/**
* Unit tests for the getFlag method
- * Tests the conversion of ISO country codes to Unicode Regional Indicator Symbols (emoji flags)
+ * Validates the conversion of ISO country codes extracted by PREFIX_TABLE regex matching
+ * to Unicode Regional Indicator Symbols (emoji flags)
*/
-// Since the callsign.js file is meant for browser environments and uses custom elements,
-// we'll test the getFlag method by copying its implementation for testing purposes
-// This approach is necessary because the original file depends on browser APIs
-
/**
* Converts an ISO country code to a Unicode Regional Indicator Symbol (emoji flag).
* @param {!string} code The ISO 3166-1 alpha-2 code
@@ -17,60 +14,31 @@ function getFlag(code) {
return String.fromCodePoint(...[...code].map(c => c.charCodeAt() + 127397));
}
-describe('getFlag method', () => {
- test('should convert US code to US flag emoji', () => {
- const result = getFlag('US');
- // US flag emoji is represented by these code points
- const expected = String.fromCodePoint(127482, 127480); // πΊπΈ
- expect(result).toBe(expected);
- });
-
- test('should convert SE code to Swedish flag emoji', () => {
- const result = getFlag('SE');
- // SE flag emoji
- const expected = String.fromCodePoint(127480, 127466); // πΈπͺ
- expect(result).toBe(expected);
- });
-
- test('should convert GB code to UK flag emoji', () => {
- const result = getFlag('GB');
- // GB flag emoji
- const expected = String.fromCodePoint(127468, 127463); // π¬π§
- expect(result).toBe(expected);
- });
-
- test('should convert DE code to German flag emoji', () => {
- const result = getFlag('DE');
- // DE flag emoji
- const expected = String.fromCodePoint(127465, 127466); // π©πͺ
- expect(result).toBe(expected);
- });
-
- test('should convert JP code to Japanese flag emoji', () => {
- const result = getFlag('JP');
- // JP flag emoji
- const expected = String.fromCodePoint(127471, 127477); // π―π΅
- expect(result).toBe(expected);
- });
-
- test('should handle lowercase input by converting correctly', () => {
- // The original function expects uppercase, but let's test with lowercase
- const result = getFlag('us');
- // This will produce different unicode points for lowercase
- const expected = String.fromCodePoint(127514, 127512); // Different from uppercase
- expect(result).toBe(expected);
- });
-
- test('should handle two-character codes correctly', () => {
- const result = getFlag('CA');
- const expected = String.fromCodePoint(127464, 127462); // π¨π¦
- expect(result).toBe(expected);
- });
-
- test('should convert each character correctly using the offset', () => {
- // Test the mathematical transformation: A = 65, 65 + 127397 = 127462
- const result = getFlag('AA');
- const expected = String.fromCodePoint(127462, 127462); // π¦π¦
- expect(result).toBe(expected);
+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)
+ });
});
});
\ No newline at end of file
diff --git a/tests/getPhonetics.test.js b/tests/getPhonetics.test.js
index dacee5c..639f11d 100644
--- a/tests/getPhonetics.test.js
+++ b/tests/getPhonetics.test.js
@@ -1,6 +1,6 @@
/**
* Unit tests for the getPhonetics method
- * Tests the mapping of characters to their phonetic alphabet equivalents
+ * Tests phonetic alphabet mapping for characters extracted from call signs via PARTS_REGEX
*/
// Copy the phonetic table and getPhonetics method for testing
@@ -56,71 +56,82 @@ function getPhonetics(letters) {
return ret.slice(0, -1);
}
-describe('getPhonetics method', () => {
- test('should convert single letter to phonetic equivalent', () => {
- expect(getPhonetics('A')).toBe('Alfa');
- expect(getPhonetics('B')).toBe('Bravo');
- expect(getPhonetics('Z')).toBe('Zulu');
- });
-
- test('should convert single digit to phonetic equivalent', () => {
- expect(getPhonetics('0')).toBe('Ziro');
- expect(getPhonetics('1')).toBe('One');
- expect(getPhonetics('9')).toBe('Niner');
- });
+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 convert typical call sign letters', () => {
- expect(getPhonetics('W1AW')).toBe('Whiskey One Alfa Whiskey');
- 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 mixed letters and numbers', () => {
- expect(getPhonetics('K2ABC')).toBe('Kilo Two Alfa Bravo Charlie');
- expect(getPhonetics('VE3XYZ')).toBe('Victor Echo Tree X-ray Yankee Zulu');
+ 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);
+ });
+ });
});
- test('should return empty string for empty input', () => {
- expect(getPhonetics('')).toBe('');
- });
+ describe('Edge cases and regex-related scenarios', () => {
+ test('should handle empty string (no regex match)', () => {
+ expect(getPhonetics('')).toBe('');
+ });
- test('should handle single character input', () => {
- expect(getPhonetics('X')).toBe('X-ray');
- expect(getPhonetics('3')).toBe('Tree');
- });
+ 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 handle all letters A-Z correctly', () => {
- const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
- const expected = [
- 'Alfa', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot',
- 'Golf', 'Hotel', 'India', 'Juliett', 'Kilo', 'Lima',
- 'Mike', 'November', 'Oscar', 'Papa', 'Quebec', 'Romeo',
- 'Sierra', 'Tango', 'Uniform', 'Victor', 'Whiskey',
- 'X-ray', 'Yankee', 'Zulu'
- ].join(' ');
-
- expect(getPhonetics(alphabet)).toBe(expected);
- });
+ 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 handle all digits 0-9 correctly', () => {
- const digits = '0123456789';
- const expected = [
- 'Ziro', 'One', 'Two', 'Tree', 'Four', 'Five',
- 'Six', 'Seven', 'Eight', 'Niner'
- ].join(' ');
-
- expect(getPhonetics(digits)).toBe(expected);
+ 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');
+ });
});
- test('should handle characters not in phonetic table', () => {
- // Characters not in the table should result in undefined being added
- const result = getPhonetics('A/B');
- expect(result).toBe('Alfa undefined Bravo');
- });
+ 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 typical amateur radio call signs', () => {
- expect(getPhonetics('KD8ABC')).toBe('Kilo Delta Eight Alfa Bravo Charlie');
- expect(getPhonetics('G0XYZ')).toBe('Golf Ziro X-ray Yankee Zulu');
- expect(getPhonetics('JA1ABC')).toBe('Juliett Alfa One Alfa Bravo Charlie');
+ 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
+ });
});
});
\ No newline at end of file
diff --git a/tests/partsRegex.test.js b/tests/partsRegex.test.js
new file mode 100644
index 0000000..82e4251
--- /dev/null
+++ b/tests/partsRegex.test.js
@@ -0,0 +1,301 @@
+/**
+ * Unit tests focusing on PARTS_REGEX pattern validation
+ * The PARTS_REGEX pattern: /([A-Z,\d]{1,3})(\d)([A-Z]{1,3})(?:\/(\d))?/
+ * This pattern parses call signs into their components:
+ * - Capture group 1: Prefix (1-3 alphanumeric characters)
+ * - Capture group 2: Area digit (exactly 1 digit)
+ * - Capture group 3: Suffix (1-3 letters)
+ * - Capture group 4: Portable indicator digit (optional)
+ */
+
+const PARTS_REGEX = /([A-Z,\d]{1,3})(\d)([A-Z]{1,3})(?:\/(\d))?/;
+
+describe('PARTS_REGEX pattern validation', () => {
+ describe('Basic parsing of call sign components', () => {
+ test('should parse single-letter prefix call sign', () => {
+ const match = 'W1AW'.match(PARTS_REGEX);
+ expect(match).toBeTruthy();
+ expect(match[1]).toBe('W'); // prefix
+ expect(match[2]).toBe('1'); // area digit
+ expect(match[3]).toBe('AW'); // suffix
+ expect(match[4]).toBeUndefined(); // no portable indicator
+ });
+
+ test('should parse two-letter prefix call sign', () => {
+ const match = 'SM8AYA'.match(PARTS_REGEX);
+ expect(match).toBeTruthy();
+ expect(match[1]).toBe('SM'); // prefix
+ expect(match[2]).toBe('8'); // area digit
+ expect(match[3]).toBe('AYA'); // suffix
+ expect(match[4]).toBeUndefined();
+ });
+
+ test('should parse three-letter prefix call sign', () => {
+ const match = 'VK2ABC'.match(PARTS_REGEX);
+ expect(match).toBeTruthy();
+ expect(match[1]).toBe('VK'); // prefix (will match first 2 chars)
+ expect(match[2]).toBe('2'); // area digit
+ expect(match[3]).toBe('ABC'); // suffix
+ });
+
+ test('should parse call sign with portable indicator', () => {
+ const match = 'W1ABC/3'.match(PARTS_REGEX);
+ expect(match).toBeTruthy();
+ expect(match[1]).toBe('W'); // prefix
+ expect(match[2]).toBe('1'); // area digit
+ expect(match[3]).toBe('ABC'); // suffix
+ expect(match[4]).toBe('3'); // portable indicator
+ });
+ });
+
+ describe('Prefix variations (1-3 characters)', () => {
+ test('should parse minimum prefix length (1 character)', () => {
+ const match = 'K2ABC'.match(PARTS_REGEX);
+ expect(match[1]).toBe('K');
+ expect(match[2]).toBe('2');
+ expect(match[3]).toBe('ABC');
+ });
+
+ test('should parse medium prefix length (2 characters)', () => {
+ const match = 'DL1ABC'.match(PARTS_REGEX);
+ expect(match[1]).toBe('DL');
+ expect(match[2]).toBe('1');
+ expect(match[3]).toBe('ABC');
+ });
+
+ test('should parse maximum prefix length (3 characters)', () => {
+ const match = 'ABC1XYZ'.match(PARTS_REGEX);
+ expect(match[1]).toBe('ABC');
+ expect(match[2]).toBe('1');
+ expect(match[3]).toBe('XYZ');
+ });
+
+ test('should parse prefix with number', () => {
+ const match = '9V1ABC'.match(PARTS_REGEX);
+ expect(match[1]).toBe('9V');
+ expect(match[2]).toBe('1');
+ expect(match[3]).toBe('ABC');
+ });
+
+ test('should parse prefix with leading number', () => {
+ const match = '3D2XYZ'.match(PARTS_REGEX);
+ expect(match[1]).toBe('3D');
+ expect(match[2]).toBe('2');
+ expect(match[3]).toBe('XYZ');
+ });
+ });
+
+ describe('Area digit variations (0-9)', () => {
+ test('should parse all digits 0-9 in area position', () => {
+ for (let i = 0; i < 10; i++) {
+ const callsign = `W${i}AW`;
+ const match = callsign.match(PARTS_REGEX);
+ expect(match).toBeTruthy();
+ expect(match[2]).toBe(String(i));
+ }
+ });
+
+ test('should parse area digit 0', () => {
+ const match = 'G0ABC'.match(PARTS_REGEX);
+ expect(match[2]).toBe('0');
+ });
+
+ test('should parse area digit 9', () => {
+ const match = 'K9XYZ'.match(PARTS_REGEX);
+ expect(match[2]).toBe('9');
+ });
+ });
+
+ describe('Suffix variations (1-3 letters)', () => {
+ test('should parse minimum suffix length (1 letter)', () => {
+ const match = 'W1A'.match(PARTS_REGEX);
+ expect(match[1]).toBe('W');
+ expect(match[2]).toBe('1');
+ expect(match[3]).toBe('A');
+ });
+
+ test('should parse medium suffix length (2 letters)', () => {
+ const match = 'W1AB'.match(PARTS_REGEX);
+ expect(match[1]).toBe('W');
+ expect(match[2]).toBe('1');
+ expect(match[3]).toBe('AB');
+ });
+
+ test('should parse maximum suffix length (3 letters)', () => {
+ const match = 'W1ABC'.match(PARTS_REGEX);
+ expect(match[1]).toBe('W');
+ expect(match[2]).toBe('1');
+ expect(match[3]).toBe('ABC');
+ });
+ });
+
+ describe('Portable indicator parsing', () => {
+ test('should parse all portable indicators (0-9)', () => {
+ for (let i = 0; i < 10; i++) {
+ const callsign = `W1ABC/${i}`;
+ const match = callsign.match(PARTS_REGEX);
+ expect(match).toBeTruthy();
+ expect(match[4]).toBe(String(i));
+ }
+ });
+
+ test('should parse portable indicator 0', () => {
+ const match = 'W1ABC/0'.match(PARTS_REGEX);
+ expect(match[4]).toBe('0');
+ });
+
+ test('should parse portable indicator 9', () => {
+ const match = 'W1ABC/9'.match(PARTS_REGEX);
+ expect(match[4]).toBe('9');
+ });
+
+ test('should handle missing portable indicator', () => {
+ const match = 'W1ABC'.match(PARTS_REGEX);
+ expect(match[4]).toBeUndefined();
+ });
+ });
+
+ describe('Edge cases and special patterns', () => {
+ test('should parse minimum length call sign (1+1+1)', () => {
+ const match = 'W1A'.match(PARTS_REGEX);
+ expect(match[0]).toBe('W1A');
+ expect(match[1]).toBe('W');
+ expect(match[2]).toBe('1');
+ expect(match[3]).toBe('A');
+ });
+
+ test('should parse maximum length call sign (3+1+3+/+1)', () => {
+ const match = 'ABC1XYZ/5'.match(PARTS_REGEX);
+ expect(match[0]).toBe('ABC1XYZ/5');
+ expect(match[1]).toBe('ABC');
+ expect(match[2]).toBe('1');
+ expect(match[3]).toBe('XYZ');
+ expect(match[4]).toBe('5');
+ });
+
+ test('should parse call sign with comma in prefix', () => {
+ // The regex allows comma: [A-Z,\d]
+ const match = 'W,1ABC'.match(PARTS_REGEX);
+ expect(match[1]).toBe('W,');
+ expect(match[2]).toBe('1');
+ expect(match[3]).toBe('ABC');
+ });
+
+ test('should parse call sign when embedded in text', () => {
+ const text = 'Contact W1AW today';
+ const match = text.match(PARTS_REGEX);
+ expect(match).toBeTruthy();
+ expect(match[1]).toBe('W');
+ expect(match[2]).toBe('1');
+ expect(match[3]).toBe('AW');
+ });
+ });
+
+ describe('Invalid patterns (should match but with unexpected results)', () => {
+ test('should not parse lowercase letters correctly', () => {
+ // Lowercase won't match the [A-Z] pattern
+ const match = 'w1aw'.match(PARTS_REGEX);
+ expect(match).toBeNull();
+ });
+
+ test('should not parse patterns with no area digit', () => {
+ const match = 'WABC'.match(PARTS_REGEX);
+ expect(match).toBeNull();
+ });
+
+ test('should capture from long prefix correctly (greedy matching)', () => {
+ // The regex {1,3} is greedy, so ABCD1XYZ will match BCD1XYZ (last 3 chars + digit + suffix)
+ const match = 'ABCD1XYZ'.match(PARTS_REGEX);
+ expect(match).toBeTruthy();
+ expect(match[1]).toBe('BCD'); // Captures last 3 characters before digit
+ expect(match[2]).toBe('1');
+ expect(match[3]).toBe('XYZ');
+ });
+
+ test('should not parse special characters', () => {
+ const match = 'W-1ABC'.match(PARTS_REGEX);
+ expect(match).toBeNull();
+ });
+ });
+
+ describe('Real-world call sign parsing', () => {
+ test('should correctly parse common US call signs', () => {
+ const usCallSigns = [
+ { call: 'W1AW', prefix: 'W', digit: '1', suffix: 'AW' },
+ { call: 'K2ABC', prefix: 'K', digit: '2', suffix: 'ABC' },
+ { call: 'N3XYZ', prefix: 'N', digit: '3', suffix: 'XYZ' },
+ { call: 'AA1AA', prefix: 'AA', digit: '1', suffix: 'AA' },
+ { call: 'KD8ABC', prefix: 'KD', digit: '8', suffix: 'ABC' },
+ ];
+
+ usCallSigns.forEach(({ call, prefix, digit, suffix }) => {
+ const match = call.match(PARTS_REGEX);
+ expect(match[1]).toBe(prefix);
+ expect(match[2]).toBe(digit);
+ expect(match[3]).toBe(suffix);
+ });
+ });
+
+ test('should correctly parse common international call signs', () => {
+ const intlCallSigns = [
+ { call: 'SM8AYA', prefix: 'SM', digit: '8', suffix: 'AYA' },
+ { call: 'DL1ABC', prefix: 'DL', digit: '1', suffix: 'ABC' },
+ { call: 'G0ABC', prefix: 'G', digit: '0', suffix: 'ABC' },
+ { call: 'JA1XYZ', prefix: 'JA', digit: '1', suffix: 'XYZ' },
+ { call: 'VK2DEF', prefix: 'VK', digit: '2', suffix: 'DEF' },
+ ];
+
+ intlCallSigns.forEach(({ call, prefix, digit, suffix }) => {
+ const match = call.match(PARTS_REGEX);
+ expect(match[1]).toBe(prefix);
+ expect(match[2]).toBe(digit);
+ expect(match[3]).toBe(suffix);
+ });
+ });
+
+ test('should correctly parse call signs with portable indicators', () => {
+ const portableCallSigns = [
+ { call: 'W1ABC/3', prefix: 'W', digit: '1', suffix: 'ABC', portable: '3' },
+ { call: 'SM8AYA/5', prefix: 'SM', digit: '8', suffix: 'AYA', portable: '5' },
+ { call: 'K2ABC/0', prefix: 'K', digit: '2', suffix: 'ABC', portable: '0' },
+ ];
+
+ portableCallSigns.forEach(({ call, prefix, digit, suffix, portable }) => {
+ const match = call.match(PARTS_REGEX);
+ expect(match[1]).toBe(prefix);
+ expect(match[2]).toBe(digit);
+ expect(match[3]).toBe(suffix);
+ expect(match[4]).toBe(portable);
+ });
+ });
+ });
+
+ describe('Comparison with SEARCH_REGEX requirements', () => {
+ test('should parse same patterns that SEARCH_REGEX would match', () => {
+ // These are patterns that SEARCH_REGEX would match (with trailing space)
+ // PARTS_REGEX should parse them correctly
+ const validPatterns = [
+ 'W1AW',
+ 'K2ABC',
+ 'SM8AYA',
+ 'DL1ABC',
+ 'W1ABC/3',
+ '9V1ABC',
+ 'VK2ABC',
+ ];
+
+ validPatterns.forEach(pattern => {
+ const match = pattern.match(PARTS_REGEX);
+ expect(match).toBeTruthy();
+ expect(match[0]).toBe(pattern);
+ });
+ });
+
+ test('should not require trailing space (unlike SEARCH_REGEX)', () => {
+ // PARTS_REGEX doesn't require space, unlike SEARCH_REGEX
+ const match = 'W1AW'.match(PARTS_REGEX);
+ expect(match).toBeTruthy();
+ expect(match[0]).toBe('W1AW');
+ });
+ });
+});
diff --git a/tests/searchCallsigns.test.js b/tests/searchCallsigns.test.js
index 703cfd9..6a8123c 100644
--- a/tests/searchCallsigns.test.js
+++ b/tests/searchCallsigns.test.js
@@ -1,124 +1,211 @@
/**
- * Unit tests for the searchCallsigns method
- * Tests basic functionality for detecting and wrapping untagged call signs in tags
+ * Unit tests focusing on SEARCH_REGEX pattern validation
+ * The SEARCH_REGEX pattern: /([A-Z,\d]{1,3}\d[A-Z]{1,3}(?:\/\d)?)\s/
+ * This pattern detects call signs in text that:
+ * - Start with 1-3 alphanumeric characters (prefix)
+ * - Have exactly one digit (area number)
+ * - Have 1-3 letters (suffix)
+ * - Optionally have /digit (portable indicator)
+ * - Must be followed by a space
*/
-// Mock the search regex and the searchCallsigns functionality for testing
const SEARCH_REGEX = /([A-Z,\d]{1,3}\d[A-Z]{1,3}(?:\/\d)?)\s/;
-/**
- * Simplified version of searchCallsigns for testing
- * @param {string} html Input HTML string
- * @returns {string} HTML with call signs wrapped in call-sign tags
- */
-function searchCallsigns(html) {
- 'use strict';
- let match;
- let result = html;
-
- while ((match = result.match(SEARCH_REGEX)) !== null) {
- result = result.replace(match[1], '' + match[1] + '');
- }
-
- return result;
-}
-
-// Setup jsdom environment
-beforeEach(() => {
- document.body.innerHTML = '';
-});
-
-describe('searchCallsigns method', () => {
- test('should detect and wrap simple call sign with space after', () => {
- const input = 'Contact W1AW today';
- const expected = 'Contact W1AW today';
- expect(searchCallsigns(input)).toBe(expected);
- });
-
- test('should detect and wrap multiple call signs', () => {
- const input = 'Contact W1AW and SM8AYA today';
- const expected = 'Contact W1AW and SM8AYA today';
- expect(searchCallsigns(input)).toBe(expected);
- });
-
- test('should detect call signs with different prefix lengths', () => {
- // 1-letter prefix + digit + letters
- expect(searchCallsigns('Call K2ABC ')).toBe('Call K2ABC ');
-
- // 2-letter prefix + digit + letters
- expect(searchCallsigns('Call SM8AYA ')).toBe('Call SM8AYA ');
-
- // 3-letter prefix + digit + letters
- expect(searchCallsigns('Call VK2ABC ')).toBe('Call VK2ABC ');
- });
-
- test('should detect call signs with different suffix lengths', () => {
- // Single letter suffix
- expect(searchCallsigns('Call W1A ')).toBe('Call W1A ');
-
- // Two letter suffix
- expect(searchCallsigns('Call W1AB ')).toBe('Call W1AB ');
-
- // Three letter suffix
- expect(searchCallsigns('Call W1ABC ')).toBe('Call W1ABC ');
- });
-
- test('should detect call signs with portable indicators', () => {
- const input = 'Call W1ABC/3 on the air';
- const expected = 'Call W1ABC/3 on the air';
- expect(searchCallsigns(input)).toBe(expected);
- });
-
- test('should not wrap call signs without trailing space', () => {
- // The regex requires a space after the call sign
- const input = 'CallW1ABC';
- expect(searchCallsigns(input)).toBe(input); // No change expected
- });
-
- test('should handle call signs in different contexts', () => {
- expect(searchCallsigns('Hello W1AW from K2ABC ')).toBe('Hello W1AW from K2ABC ');
- });
-
- test('should handle text with no call signs', () => {
- const input = 'This is just normal text with no call signs.';
- expect(searchCallsigns(input)).toBe(input);
- });
-
- test('should handle empty string', () => {
- expect(searchCallsigns('')).toBe('');
- });
-
- test('should detect various valid call sign patterns', () => {
- // US call signs
- expect(searchCallsigns('W1AW ')).toBe('W1AW ');
- expect(searchCallsigns('K2ABC ')).toBe('K2ABC ');
- expect(searchCallsigns('N3XYZ ')).toBe('N3XYZ ');
-
- // International call signs
- expect(searchCallsigns('G0ABC ')).toBe('G0ABC ');
- expect(searchCallsigns('DL1ABC ')).toBe('DL1ABC ');
- expect(searchCallsigns('JA1XYZ ')).toBe('JA1XYZ ');
+describe('SEARCH_REGEX pattern validation', () => {
+ describe('Valid call sign patterns', () => {
+ test('should match single-letter prefix patterns', () => {
+ expect('W1AW '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('W1AW '.match(SEARCH_REGEX)[1]).toBe('W1AW');
+
+ expect('K2ABC '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('K2ABC '.match(SEARCH_REGEX)[1]).toBe('K2ABC');
+
+ expect('N3X '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('N3X '.match(SEARCH_REGEX)[1]).toBe('N3X');
+ });
+
+ test('should match two-letter prefix patterns', () => {
+ expect('SM8AYA '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('SM8AYA '.match(SEARCH_REGEX)[1]).toBe('SM8AYA');
+
+ expect('DL1ABC '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('DL1ABC '.match(SEARCH_REGEX)[1]).toBe('DL1ABC');
+
+ expect('G0XYZ '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('G0XYZ '.match(SEARCH_REGEX)[1]).toBe('G0XYZ');
+ });
+
+ test('should match three-letter prefix patterns', () => {
+ expect('VK2ABC '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('VK2ABC '.match(SEARCH_REGEX)[1]).toBe('VK2ABC');
+
+ expect('XX91A '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('XX91A '.match(SEARCH_REGEX)[1]).toBe('XX91A');
+ });
+
+ test('should match patterns with number in prefix', () => {
+ expect('9V1ABC '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('9V1ABC '.match(SEARCH_REGEX)[1]).toBe('9V1ABC');
+
+ expect('3D2XYZ '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('3D2XYZ '.match(SEARCH_REGEX)[1]).toBe('3D2XYZ');
+ });
+
+ test('should match patterns with varying suffix lengths (1-3 letters)', () => {
+ expect('W1A '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('W1A '.match(SEARCH_REGEX)[1]).toBe('W1A');
+
+ expect('W1AB '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('W1AB '.match(SEARCH_REGEX)[1]).toBe('W1AB');
+
+ expect('W1ABC '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('W1ABC '.match(SEARCH_REGEX)[1]).toBe('W1ABC');
+ });
+
+ test('should match patterns with portable indicators', () => {
+ expect('W1ABC/3 '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('W1ABC/3 '.match(SEARCH_REGEX)[1]).toBe('W1ABC/3');
+
+ expect('SM8AYA/5 '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('SM8AYA/5 '.match(SEARCH_REGEX)[1]).toBe('SM8AYA/5');
+
+ expect('K2ABC/0 '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('K2ABC/0 '.match(SEARCH_REGEX)[1]).toBe('K2ABC/0');
+ });
+
+ test('should match all digit variations (0-9) in area number position', () => {
+ for (let i = 0; i < 10; i++) {
+ const callsign = `W${i}AW `;
+ const match = callsign.match(SEARCH_REGEX);
+ expect(match).toBeTruthy();
+ expect(match[1]).toBe(`W${i}AW`);
+ }
+ });
});
- test('should handle call signs with numbers in prefix', () => {
- // Some call signs can have numbers in the prefix
- expect(searchCallsigns('9V1ABC ')).toBe('9V1ABC ');
+ describe('Invalid call sign patterns (should NOT match)', () => {
+ test('should NOT match patterns without trailing space', () => {
+ expect('W1AW'.match(SEARCH_REGEX)).toBeNull();
+ expect('W1AWX'.match(SEARCH_REGEX)).toBeNull();
+ expect('K2ABC!'.match(SEARCH_REGEX)).toBeNull();
+ });
+
+ test('should NOT match patterns with no digit in area position', () => {
+ expect('WAAW '.match(SEARCH_REGEX)).toBeNull();
+ expect('KAABC '.match(SEARCH_REGEX)).toBeNull();
+ });
+
+ test('should match patterns with multiple digits (regex allows this)', () => {
+ // The regex actually DOES match W12AW because {1,3} in prefix can capture W1, then 2 becomes the digit
+ const match = 'W12AW '.match(SEARCH_REGEX);
+ expect(match).toBeTruthy();
+ expect(match[1]).toBe('W12AW'); // Captures W1 as prefix, 2 as digit, AW as suffix
+ });
+
+ test('should NOT match patterns with suffix too short (0 letters)', () => {
+ expect('W1 '.match(SEARCH_REGEX)).toBeNull();
+ expect('K2 '.match(SEARCH_REGEX)).toBeNull();
+ });
+
+ test('should NOT match patterns with suffix too long (4+ letters)', () => {
+ expect('W1ABCD '.match(SEARCH_REGEX)).toBeNull();
+ expect('K2ABCDE '.match(SEARCH_REGEX)).toBeNull();
+ });
+
+ test('should match patterns with long prefixes (regex captures first 3 chars)', () => {
+ // ABCD1ABC will match as ABC (prefix) + D1A (but wait, the regex will actually match BCD1ABC)
+ // The regex is greedy and will match the LAST valid pattern
+ const match = 'ABCD1ABC '.match(SEARCH_REGEX);
+ expect(match).toBeTruthy();
+ // It matches BCD as prefix (3 chars), 1 as digit, ABC as suffix
+ expect(match[1]).toBe('BCD1ABC');
+ });
+
+ test('should NOT match patterns with lowercase letters', () => {
+ expect('w1aw '.match(SEARCH_REGEX)).toBeNull();
+ expect('K2abc '.match(SEARCH_REGEX)).toBeNull();
+ });
+
+ test('should NOT match patterns with special characters', () => {
+ expect('W-1AW '.match(SEARCH_REGEX)).toBeNull();
+ expect('K@2ABC '.match(SEARCH_REGEX)).toBeNull();
+ expect('W1A-W '.match(SEARCH_REGEX)).toBeNull();
+ });
+
+ test('should NOT match portable indicators with multiple digits', () => {
+ expect('W1ABC/34 '.match(SEARCH_REGEX)).toBeNull();
+ });
+
+ test('should NOT match portable indicators with letters', () => {
+ expect('W1ABC/M '.match(SEARCH_REGEX)).toBeNull();
+ expect('W1ABC/P '.match(SEARCH_REGEX)).toBeNull();
+ });
});
- test('should process multiple occurrences iteratively', () => {
- // Test that the while loop processes all matches
- const input = 'W1AW K2ABC SM8AYA ';
- const result = searchCallsigns(input);
- expect(result).toContain('W1AW');
- expect(result).toContain('K2ABC');
- expect(result).toContain('SM8AYA');
+ describe('Edge cases and boundary conditions', () => {
+ test('should match minimum length call sign (1+1+1)', () => {
+ expect('W1A '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('W1A '.match(SEARCH_REGEX)[1]).toBe('W1A');
+ });
+
+ test('should match maximum length call sign without portable (3+1+3)', () => {
+ expect('ABC1XYZ '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('ABC1XYZ '.match(SEARCH_REGEX)[1]).toBe('ABC1XYZ');
+ });
+
+ test('should match maximum length with portable indicator (3+1+3+/+1)', () => {
+ expect('ABC1XYZ/5 '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('ABC1XYZ/5 '.match(SEARCH_REGEX)[1]).toBe('ABC1XYZ/5');
+ });
+
+ test('should match call signs in the middle of text', () => {
+ const text = 'I heard W1AW on the air today';
+ const match = text.match(SEARCH_REGEX);
+ expect(match).toBeTruthy();
+ expect(match[1]).toBe('W1AW');
+ });
+
+ test('should match first occurrence only per match call', () => {
+ const text = 'W1AW K2ABC ';
+ const match = text.match(SEARCH_REGEX);
+ expect(match[1]).toBe('W1AW'); // Should get first one
+ });
+
+ test('should match whitespace characters (\\s includes tab, newline, space)', () => {
+ // The \\s in regex matches any whitespace, not just space
+ expect('W1AW\t'.match(SEARCH_REGEX)).toBeTruthy();
+ expect('W1AW\n'.match(SEARCH_REGEX)).toBeTruthy();
+ expect('W1AW '.match(SEARCH_REGEX)).toBeTruthy();
+ });
+
+ test('should match with comma in prefix (as per regex pattern)', () => {
+ // The regex allows comma in prefix: [A-Z,\d]
+ expect('W,1ABC '.match(SEARCH_REGEX)).toBeTruthy();
+ expect('A,1X '.match(SEARCH_REGEX)).toBeTruthy();
+ });
});
- test('should handle already wrapped call signs correctly', () => {
- // If a call sign is already wrapped, it shouldn't be wrapped again
- const input = 'W1AW and K2ABC ';
- const result = searchCallsigns(input);
- // Should only wrap the unwrapped call sign
- expect(result).toBe('W1AW and K2ABC ');
+ describe('Real-world call sign examples', () => {
+ test('should match common US call signs', () => {
+ expect('W1AW '.match(SEARCH_REGEX)[1]).toBe('W1AW');
+ expect('K2ABC '.match(SEARCH_REGEX)[1]).toBe('K2ABC');
+ expect('N3XYZ '.match(SEARCH_REGEX)[1]).toBe('N3XYZ');
+ expect('AA1AA '.match(SEARCH_REGEX)[1]).toBe('AA1AA');
+ expect('KD8ABC '.match(SEARCH_REGEX)[1]).toBe('KD8ABC');
+ });
+
+ test('should match common international call signs', () => {
+ expect('SM8AYA '.match(SEARCH_REGEX)[1]).toBe('SM8AYA');
+ expect('DL1ABC '.match(SEARCH_REGEX)[1]).toBe('DL1ABC');
+ expect('G0ABC '.match(SEARCH_REGEX)[1]).toBe('G0ABC');
+ expect('JA1XYZ '.match(SEARCH_REGEX)[1]).toBe('JA1XYZ');
+ expect('VK2DEF '.match(SEARCH_REGEX)[1]).toBe('VK2DEF');
+ });
+
+ test('should match special territory prefixes', () => {
+ expect('9V1ABC '.match(SEARCH_REGEX)[1]).toBe('9V1ABC');
+ expect('3D2XYZ '.match(SEARCH_REGEX)[1]).toBe('3D2XYZ');
+ expect('5N1ABC '.match(SEARCH_REGEX)[1]).toBe('5N1ABC');
+ });
});
});
\ No newline at end of file