Slovenska različica / Slovenian version ↓
All tests run in Node.js using the built-in node:test runner — no external dependencies required.
| Test file | Tool | Tests | Groups |
|---|---|---|---|
edi2adif.test.js |
edi2adif.html |
122 | 9 |
edi-crosscheck.test.js |
edi-crosscheck.html |
56 | 8 |
adif-merge.test.js |
adif-merge.html |
112 | 21 |
adif-qrz-filter.test.js |
adif-qrz-filter.js |
48 | 4 |
vhf-logger/vhf-logger.test.js |
vhf-logger/vhf-logger.html |
191 | 17 |
adif-stats.test.js |
adif-stats.html |
133 | 21 |
adif2cab.test.js |
adif2cab.html |
191 | 31 |
edi-validator.test.js |
edi-validator.html |
109 | 22 |
The sections below document each test file in detail.
node --test --test-reporter=spec edi2adif.test.js
node --test --test-reporter=spec edi-crosscheck.test.js
node --test --test-reporter=spec adif-merge.test.js
node --test --test-reporter=spec adif-qrz-filter.test.js
node --test --test-reporter=spec vhf-logger/vhf-logger.test.js
node --test --test-reporter=spec adif-stats.test.js
node --test --test-reporter=spec adif2cab.test.js
node --test --test-reporter=spec edi-validator.test.js
Requires Node.js v18 or later (node:test was stabilised in v18;
the project was developed on v25).
edi2adif.html is a single-file browser app with no module system.
The test file extracts the embedded <script> block at runtime and evaluates
it inside a node:vm sandbox that provides a minimal DOM mock:
edi2adif.html ──► regex extract <script> ──► vm.createContext (mock DOM)
└── vm.runInContext(script)
│
function declarations promoted to ctx
│
ctx.normBand, ctx.parseEDI, … exposed
The mock provides no-op implementations for document.getElementById,
document.addEventListener, URL.createObjectURL, Blob, FileReader,
etc. — enough for the script to initialise without a real browser.
vm prototype note: Objects returned by functions running inside a vm context share the vm’s
Object.prototype, not the host’s. Usingassert.deepStrictEqualon them fails even when all properties are identical. All assertions therefore compare individual properties withassert.equal.
normBand (27 tests)Verifies the regex table that maps EDI PBand values to canonical ADIF
band names and nominal frequencies.
| Sub-group | What is checked |
|---|---|
| Empty / unknown input | Falsy input returns {band:'', freq:''}. Unrecognised strings pass through with no frequency. Whitespace is trimmed before matching. |
| 6 m – 23 cm | Each band matched by frequency (MHz), wavelength (e.g. 2m), and GHz strings with both dot and comma decimal separators. |
| Microwave bands | 13 cm through 6 mm — band name verified for all eight entries. |
parseEDI (36 tests)Exercises the EDI-to-QSO parser across header fields, record parsing, edge cases, and error handling.
| Sub-group | What is checked |
|---|---|
| Header extraction | PCall, PWWLo, TName, SPowe, MOpe1/2 stored under lowercased keys in the header object. Band resolved via normBand. |
| QSO count | ERROR callsigns and records with fewer than 10 semicolon-delimited fields are silently skipped. |
| Callsign normalisation | Lowercased callsigns are uppercased. |
| Mode mapping | EDI mode codes 1 → SSB, 2 → CW, 3 → CW, 4 → SSB, 5 → AM, 6 → FM, 7 → RTTY, 8 → SSTV, 9 → ATV. |
| Date parsing | YYYYMMDD stored; DD.MM.YYYY display string generated. YY ≥ 90 → 1900+YY; YY < 90 → 2000+YY. |
| Time parsing | HHMM stored; HH:MM display string generated. |
| RST & exchange | rstS, rstR, stx, srx fields extracted from correct column positions. |
| Locator validation | 6-character Maidenhead grids kept in mixed case (first 4 uppercase, last 2 lowercase — e.g. JN65ar); 4-character grids rejected (wwl set to ''). |
| Distance | Parsed as integer; zero preserved. |
| Duplicate flag | Column 13 value D → dupe=true; absent → dupe=false. |
| Key generation | _key follows CALL\|YYYYMMDD\|HHMM format; does not include band (that is added later by handleFiles). |
| Source tracking | src filename attached to every QSO. |
| Edge cases | Empty input, short records, CRLF line endings, minimum-field records (exactly 10 fields). |
handleFilesis not tested here because it depends on the async browserFileReaderAPI. Fields it adds to QSOs (myCall,myLoc,contest,pwr,ops,band,_bandKey) are therefore tested via the returnedheaderobject and the dedup group below.
adifField (10 tests)Verifies ADIF field serialisation: <TAG:length>value .
null, undefined, and '' return an empty string (field omitted).0 is serialised (not treated as falsy/empty).csvEsc (11 tests)Verifies CSV escaping for DARC QSL and generic CSV export.
\n), or carriage return (\r) are wrapped in double-quotes." → "").null and undefined coerced to ''; numbers coerced to string.modeBadge (8 tests)Verifies the mapping from mode string to CSS badge class used in the table renderer.
SSB and AM map to badge-ssb (analog voice modes).CW maps to badge-cw.FM maps to badge-fm.RTTY, SSTV, ATV, and unknown modes fall back to badge-digi.i18n (5 tests)Verifies the translation lookup function t(key) and setLang(lang).
sl).en and back to sl works correctly.Verifies the cross-file deduplication algorithm from finishLoad.
The _all array is a lexical let binding inside the vm scope and cannot
be mutated from outside. The 5-line algorithm is therefore reimplemented
inline and tested in isolation:
dupe=false._bandKey → second flagged.dupe=true by the EDI parser stays dupe=true;
a following identical entry is also flagged by the dedup pass.Verifies the row-generation logic for the generic CSV export.
stx, srx) have leading zeros stripped (001 → 1).csvEsc.0 is treated as absent and produces an empty cell; distance > 0 is kept.Verifies the save logic from commitEdit().
startEdit/commitEdit manipulate real DOM nodes and cannot be driven from
a vm context without full browser APIs. The mutation logic is replicated
inline and tested in isolation.
| Sub-group | What is checked |
|---|---|
| Basic fields | rstS, rstR, and mode are trimmed and saved directly to the QSO object. |
| Locator validation | Valid 6-char Maidenhead grid (A–R, 0–9, A–X) saved in mixed case (first 4 uppercase, last 2 lowercase; e.g. JN65ar). 4-char, 8-char, non-Maidenhead characters, S–Z first pair, and non-digit middle pair are all rejected and clear wwl to ''. |
| Area | Reason |
|---|---|
handleFiles |
Requires async browser FileReader; not polyfillable in a pure vm context. |
finishLoad / DOM update functions |
Call document.getElementById(...).style, .innerHTML, etc. on real DOM nodes; only meaningful in a browser. |
Export functions (exportADIF, exportDARC, exportCSV) |
Depend on _all state, DOM checkboxes, and Blob/URL.createObjectURL. End-to-end browser tests (e.g. Playwright) would be needed. |
Sorting (sortFiltered, setSort) |
Depends on _filtered state; testable only with a full state setup. |
Vsi testi tečejo v Node.js z vgrajenim izvajalcem node:test — brez zunanjih odvisnosti.
| Testna datoteka | Orodje | Testov | Skupin |
|---|---|---|---|
edi2adif.test.js |
edi2adif.html |
122 | 9 |
edi-crosscheck.test.js |
edi-crosscheck.html |
56 | 8 |
adif-merge.test.js |
adif-merge.html |
112 | 21 |
adif-qrz-filter.test.js |
adif-qrz-filter.js |
48 | 4 |
vhf-logger/vhf-logger.test.js |
vhf-logger/vhf-logger.html |
191 | 17 |
adif-stats.test.js |
adif-stats.html |
133 | 21 |
adif2cab.test.js |
adif2cab.html |
191 | 31 |
edi-validator.test.js |
edi-validator.html |
109 | 22 |
Spodnji razdelki dokumentirajo vsako testno datoteko podrobno.
node --test --test-reporter=spec edi2adif.test.js
node --test --test-reporter=spec edi-crosscheck.test.js
node --test --test-reporter=spec adif-merge.test.js
node --test --test-reporter=spec adif-qrz-filter.test.js
node --test --test-reporter=spec vhf-logger/vhf-logger.test.js
node --test --test-reporter=spec adif-stats.test.js
node --test --test-reporter=spec adif2cab.test.js
node --test --test-reporter=spec edi-validator.test.js
Zahteva Node.js v18 ali novejši (node:test je bil stabiliziran v v18;
projekt je bil razvit na v25).
edi2adif.html je enostranska brskalniška aplikacija brez sistema modulov.
Testna datoteka ob zagonu izvleče vgrajeni blok <script> in ga izvede
znotraj peskovnika node:vm, ki zagotavlja minimalni nadomestek DOM-a:
edi2adif.html ──► regex izvleče <script> ──► vm.createContext (nadom. DOM)
└── vm.runInContext(skripta)
│
deklaracije funkcij prenesene v ctx
│
ctx.normBand, ctx.parseEDI, … dostopni
Nadomestek zagotavlja brezdejavne implementacije za document.getElementById,
document.addEventListener, URL.createObjectURL, Blob, FileReader
itd. — dovolj, da se skripta inicializira brez pravega brskalnika.
Opomba o prototipih vm: Objekti, ki jih vrnejo funkcije v vm kontekstu, delijo
Object.prototypeiz vm, ne iz gostitelja. Zatoassert.deepStrictEqualna njih ne uspe, čeprav so vse lastnosti identične. Vsa primerjanja zato primerjajo posamezne lastnosti zassert.equal.
normBand (27 testov)Preverja tabelo regularnih izrazov, ki preslika vrednosti EDI PBand v
kanonična imena pasov ADIF in nominalne frekvence.
| Podskupina | Kaj se preverja |
|---|---|
| Prazen / neznan vnos | Lažni vnos vrne {band:'', freq:''}. Neprepoznani nizi se prenesejo brez frekvence. Beli prostor se obreže pred ujemanjem. |
| 6 m – 23 cm | Vsak pas se ujema po frekvenci (MHz), valovni dolžini (npr. 2m) in nizih GHz z decimalno piko in vejico. |
| Mikrovalovni pasovi | 13 cm do 6 mm — ime pasu preverjeno za vseh osem vnosov. |
parseEDI (36 testov)Preverja razčlenjevalnik EDI v QSO prek polj glave, razčlenjevanja zapisov, robnih primerov in obravnavanja napak.
| Podskupina | Kaj se preverja |
|---|---|
| Ekstrakcija glave | PCall, PWWLo, TName, SPowe, MOpe1/2 shranjeni pod ključi z malimi črkami v objektu header. Pas razrešen prek normBand. |
| Število QSO | Klicni znaki ERROR in zapisi z manj kot 10 polji (ločenimi s podpičjem) so tiho preskočeni. |
| Normalizacija klicnega znaka | Klicni znaki z malimi črkami se pretvorijo v velike. |
| Mapiranje načina | EDI kode načina 1 → SSB, 2 → CW, 3 → CW, 4 → SSB, 5 → AM, 6 → FM, 7 → RTTY, 8 → SSTV, 9 → ATV. |
| Razčlenjevanje datuma | Shranjeno YYYYMMDD; generiran prikazni niz DD.MM.YYYY. LL ≥ 90 → 1900+LL; LL < 90 → 2000+LL. |
| Razčlenjevanje časa | Shranjeno HHMM; generiran prikazni niz HH:MM. |
| RST in izmenjava | Polja rstS, rstR, stx, srx izvlečena iz pravilnih položajev stolpcev. |
| Validacija lokatorja | Maidenhead mreže s 6 znaki se ohranijo z mešanimi črkami (prvi 4 znaki z velikimi, zadnja 2 z malimi — npr. JN65ar); mreže s 4 znaki so zavrnjene (wwl nastavljeno na ''). |
| Razdalja | Razčlenjena kot celo število; ničla ohranjena. |
| Zastavica duplikata | Vrednost D v stolpcu 13 → dupe=true; odsotnost → dupe=false. |
| Generiranje ključa | _key sledi obliki KLICNI_ZNAK\|YYYYMMDD\|HHMM; ne vsebuje pasu (ta se doda pozneje v handleFiles). |
| Sledenje izvoru | Ime datoteke src je pripeto vsakemu QSO. |
| Robni primeri | Prazen vnos, kratki zapisi, zaključki vrstic CRLF, zapisi z minimalnim številom polj (točno 10). |
handleFilestu ni testiran, ker je odvisen od asinhronega brskalnikovega API-jaFileReader. Polja, ki jih doda QSO-jem (myCall,myLoc,contest,pwr,ops,band,_bandKey), so zato preverjena prek vrnjenega objektaheaderin spodnje skupine za deduplikacijo.
adifField (10 testov)Preverja serializacijo polj ADIF: <OZNAKA:dolžina>vrednost .
null, undefined in '' vrnejo prazen niz (polje izpuščeno).0 je serializirana (ne obravnavana kot lažna/prazna).csvEsc (11 testov)Preverja ubežanje CSV za izvoz DARC QSL in generični CSV.
\n) ali zaključkom vrstice (\r) so zaviti v dvojne narekovaje." → "").null in undefined pretvorjeni v ''; števila pretvorjena v niz.modeBadge (8 testov)Preverja preslikavo niza načina v razred CSS značke, ki se uporablja v prikazu tabele.
SSB in AM preslikata v badge-ssb (analogni govorni načini).CW preslika v badge-cw.FM preslika v badge-fm.RTTY, SSTV, ATV in neznani načini padejo na rezervno vrednost badge-digi.i18n (5 testov)Preverja funkcijo za iskanje prevodov t(ključ) in setLang(jezik).
sl).en in nazaj na sl deluje pravilno.Preverja algoritem deduplikacije iz finishLoad.
Polje _all je leksikalna vezava let znotraj obsega vm in je ni mogoče
mutirati od zunaj. 5-vrstični algoritem je zato reimplementiran neposredno
in testiran v izolaciji:
dupe=false._bandKey → drugi označen.dupe=true, ostane označen;
naslednji enaki vnos je prav tako označen s prehodom deduplikacije.Preverja logiko generiranja vrstic za generični CSV izvoz.
stx, srx) imajo odstranjene vodilne ničle (001 → 1).csvEsc.0 je obravnavana kot odsotna in ustvari prazno celico; razdalja > 0 je ohranjena.Preverja logiko shranjevanja iz commitEdit().
startEdit/commitEdit manipulirata z resničnimi vozlišči DOM in ju ni mogoče
izvajati iz vm konteksta brez polnih brskalniških API-jev. Logika mutacije
je reimplementirana neposredno in testirana v izolaciji.
| Podskupina | Kaj se preverja |
|---|---|
| Osnovna polja | rstS, rstR in mode so obrezani in shranjeni neposredno v objekt QSO. |
| Validacija lokatorja | Veljavna 6-znakovna Maidenhead mreža (A–R, 0–9, A–X) se shrani z mešanimi črkami (prvi 4 z velikimi, zadnja 2 z malimi; npr. JN65ar). 4-znakovni, 8-znakovni, znaki zunaj Maidenhead, prvi par S–Z in nečiselni srednji par so zavrnjeni in wwl se postavi na ''. |
edi-crosscheck.test.js — 56 tests · 8 groupsCovers the pure logic of edi-crosscheck.html: suffix stripping, edit distance, EDI parsing, and all crosscheck algorithms including configurable thresholds and missing-locator suggestions.
edi-crosscheck.html is evaluated inside a node:vm context, the same pattern as edi2adif.html. Unlike that tool, no code is stripped — instead a Proxy-based DOM mock absorbs all property access and method calls silently, so the startup event-wiring runs without error.
Module-level state (_histDB, _results) is const/let and therefore not accessible as ctx properties. Tests route all state through function declarations:
clearHist() — resets the DB and result set between testsaddToHistDB(qsos) — populates the historical databaserunCrosscheck(qsos) — runs the check and returns the results arraybaseCall (13 tests)Verifies suffix stripping for crosscheck matching.
/P, /M, /MM, /AM, /QRP, /R, /A, /B) stripped from trailing position./IV3, /I1, etc.) stripped — treated the same as /P because they indicate the same station operating from a different region./1, /2, etc.) stripped.OE/S59DGO, F/ON4AAA) left unchanged — they represent a different operating location (prefix is a country/region prefix, not a suffix).callsign/suffix pattern and the suffix is stripped; otherwise it’s a prefix/call pattern and kept as-is.levenshtein (9 tests)Verifies the Levenshtein distance function with maxDist=2 early-exit.
maxDist+1 when the length difference alone exceeds maxDist (early exit).parseEDI (9 tests)Verifies QSO extraction from an EDI file fragment.
PBand header and applied to all QSOs.JN65ar); invalid locators cleared to ''.DD.MM.YYYY; two-digit year expanded.ERROR callsigns skipped; CRLF line endings handled.runCrosscheck — locator mismatch (6 tests)| Test | What is verified |
|---|---|
| Clean match | No issue when locator equals historical mode |
| High severity | LOC_MISMATCH severity high when mode confidence ≥ 60% and new locator never seen |
| Medium severity | LOC_MISMATCH severity med when new locator appeared before (e.g. portable operation) |
| Threshold | No flag when callsign has fewer than 3 historical appearances |
| No locator | No LOC_MISMATCH when QSO has no locator (wwl = '') — instead LOC_MISSING may be raised |
| allLocs order | Historical locator list in the issue is sorted by count descending |
runCrosscheck — callsign check (8 tests)| Test | What is verified |
|---|---|
| CALL_SIMILAR d=1 | Call not in history; distance-1 match found and ranked first |
| CALL_UNKNOWN | Call not in history; no similar found within distance 2 |
| In history | No call issue when base call exists in DB |
| Portable normalisation | S59ABC/P matched against S59ABC history — no call flag |
| Sort order | Similar suggestions sorted by distance ASC, then count DESC |
| Distance 2 | Distance-2 matches also flagged (CALL_SIMILAR) |
| Combined issues | Unknown call produces only CALL_SIMILAR; no spurious LOC issue without history |
| Deduplication | Repeated unknown call in new log reuses precomputed similar-call list |
runCrosscheck — missing locator suggestion (4 tests)| Test | What is verified |
|---|---|
| Suggests mode locator | LOC_MISSING raised when new log QSO has no locator but history exists |
| Medium severity | LOC_MISSING severity med when mode confidence is below the threshold |
| Threshold | No LOC_MISSING when fewer than _minAppearances historical entries |
| Empty history locators | No LOC_MISSING when all historical entries have empty locators |
runCrosscheck — configurable thresholds (3 tests)| Test | What is verified |
|---|---|
_minAppearances |
No flag when historical count is below the slider threshold |
_minConfidence |
Severity respects the confidence slider (high vs med) |
| Empty locators ignored | Empty historical locators do not affect mode calculation |
runCrosscheck — callsign by locator (4 tests)| Test | What is verified |
|---|---|
| CALL_BY_LOC basic | Suggests calls historically seen from the same locator and within Levenshtein ≤ 2 |
| No match | No CALL_BY_LOC when no historical calls from that locator are within distance 2 |
| Separate from CALL_SIMILAR | CALL_BY_LOC and CALL_SIMILAR appear as distinct issues in the result |
| Redundant coexistence | CALL_BY_LOC is raised even when its candidates overlap with CALL_SIMILAR — both signals are shown as corroborating evidence |
adif-merge.test.js — 112 tests · 21 groupsCovers the pure logic of adif-merge.html: ADIF parsing, deduplication, field normalization, export helpers, XSS escaping, CSV escaping, i18n key completeness, and regression tests for code-review fixes.
adif-merge.html is evaluated inside a node:vm context using the same Proxy-based DOM mock as the other HTML tools. Module-level let bindings are not ctx properties; these test helpers are injected via a second vm.runInContext call:
_getAllForTest() / _setAllForTest(arr) — read and write the _all QSO array_getFilteredForTest() / _setFilteredForTest(arr) — access _filtered_getDeselForTest() — read the _desel Set_getSourcesForTest() / _setSourcesForTest(arr) — read and write _sources_getI18nForTest(lang, key) — read a value from the S i18n object_getLangKeys(lang) — list all keys for a given languageA local adif() helper computes :length from the actual string value of each field, so test fixtures are not brittle against length miscalculations.
parseADIF — basic extraction (9 tests)CALL uppercased, BAND lowercased, MODE uppercased regardless of input case.RST_SENT, RST_RCVD, and GRIDSQUARE extracted to convenience properties.COMMENT, TX_PWR) preserved in q.fields dict.src set to the filename argument.CALL silently skipped.parseADIF — date/time normalization (7 tests)YYYYMMDD stored as-is; ISO YYYY-MM-DD stripped of dashes.q.fields.QSO_DATE.HHMMSS and HH:MM:SS times stripped/truncated to HHMM for the dedup key.DD.MM.YYYY and HH:MM generated for UI.parseADIF — multi-record / edge cases (9 tests)<EOH> (headerless ADIF) handled gracefully.<EOR> markers skipped.q.fields.APP_* tags preserved; type specifier <TAG:len:TYPE> ignored cleanly.parseADIF — fields dict kept in sync (4 tests)Normalization writes back to q.fields so ADIF export is lossless and uses the normalized value:
CALL in dict: uppercase + trimmed; BAND: lowercase; MODE: uppercase; QSO_DATE: dashes stripped.updateKey (3 tests)CALL|BAND|MODE|DATE|TIME.recomputeDupes (7 tests)dupe=false; subsequent same key → dupe=true.dupe=false.dupe=true.dupe=true flags from previous state are cleared before recomputing.parseADIF dedup key uniqueness (4 tests)Integration — keys round-trip correctly through parse + updateKey:
_key.CALL or BAND normalize to the same key.adifField (7 tests)<TAG:length>value format (trailing space per ADIF convention).null, undefined, and '' → empty string (field skipped in export).100 serialised as '100'.htmlEsc (9 tests)&, <, >, " escaped to HTML entities; plain strings unchanged.null and undefined → ''; numbers coerced to string.<script>alert(1)</script> rendered safe.csvEsc (7 tests)null → ''; numbers coerced to string.modeBadge (13 tests)SSB, AM, USB, LSB → badge-ssb.CW → badge-cw; FM → badge-fm.FT8, FT4, RTTY, JS8, WSPR, unknown, and empty string → badge-digi.buildFilename (6 tests)STATION_CALLSIGN used when present; MY_CALLSIGN as fallback; "merged" when neither."merged"; correct extension appended (.adi, .csv)./ in callsign replaced with - for filesystem safety.ADIF export — field preservation (2 tests)TX_PWR, ANTENNA, NOTES, MY_GRIDSQUARE) survive parse → export via q.fields.CALL, QSO_DATE, TIME_ON, BAND, MODE) all present in q.fields.I18N (4 tests)dropTitle differs between SL and EN.parseADIF — real-world fixtures (4 tests)HHMMSS time truncated to HHMM, FREQ and STATION_CALLSIGN preserved.RST, TX_PWR, COMMENT extracted correctly.recomputeDupes.parseADIF — missing optional fields (5 tests)Regression — parser must not crash when optional fields are absent:
BAND → band = ''; missing TIME_ON → time = '', timeDisp = ''.QSO_DATE → date = '', dateDisp = ''.RST_SENT/RST_RCVD → ''; missing GRIDSQUARE → grid = ''.parseADIF — no submode property on QSO object (2 tests)Regression — SUBMODE was a dead property removed from the QSO object during code review:
submode is not a property of the parsed QSO object.SUBMODE tag is still preserved in q.fields for lossless ADIF export.adifField — export consistency (3 tests)adifField is idempotent w.r.t. tag case (lowercase input → same output as uppercase).APP_ADIFMERGE_SRC annotation built with correct :length from the source filename.<TAG:0>).updateKey — empty band handling (2 tests)Documents behavior when band is absent — no crash, produces CALL||MODE|DATE|TIME; two such QSOs with identical other fields share the same key (will be deduped).
I18N — errBand key (3 tests)Regression — errBand translation key added during code review:
parseADIF — re-merge safety (APP_ADIFMERGE_SRC) (2 tests)Regression — re-merging a previously merged ADIF file must not duplicate the provenance tag:
APP_ADIFMERGE_SRC from a prior merge stored in q.fields (history preserved).q.src always reflects the filename passed to parseADIF, not the old annotation, so exportADIF writes the correct new source tag.adif-qrz-filter.test.jsA separate test suite covers the Node.js CLI tool. It also uses node:test with no external dependencies.
Tests: 48 across 4 test groups
node --test adif-qrz-filter.test.js
node --test --test-reporter=spec adif-qrz-filter.test.js
| # | Group | Tests | What is checked |
|---|---|---|---|
| 1 | parseAdif |
6 | ADIF parsing: header extraction, record splitting, QSL_VIA extraction, CRLF handling, missing CALL skipping |
| 2 | extractField |
8 | Generic <TAG:length>value extraction for CALL, QSL_VIA, case-insensitivity, trimming, uppercasing, ADIF type specifier (<TAG:len:TYPE>) |
| 3 | usesQslBuro |
31 | Fuzzy logic: 12 positive cases (buro/bureau + European variants: buero/büro/buerau/boureau/burea/buiro; “Direct or Bureau” wins), 16 negative cases (no/direct only/only via LoTW/eQSL only/”QSL via CALL”), 3 edge cases (null/empty) |
| 4 | cache |
3 | JSON cache save/load round-trip, 7-day TTL purge, missing file handling |
The CLI tool is evaluated inside a node:vm context that stubs fs, https, process, and console. Pure functions (parseAdif, extractField, usesQslBuro, loadCache, saveCache) are extracted and tested directly.
Note on
deepStrictEqual: As with theedi2adif.htmlvm tests,assert.deepStrictEqualon vm-created objects can fail even when properties are identical. The cache tests therefore useassert.equalon individual properties orObject.keys().lengthfor empty-object checks.
vhf-logger/vhf-logger.test.js — 191 tests · 17 groupsCovers the pure logic of vhf-logger/vhf-logger.html: callsign normalization, band mapping, geo utilities, dupe detection, dupe recalculation, EDI build, crosscheck lookup, EDI import parsing, ZIP generation, band colors, manual time state, and backup/restore validation.
vhf-logger/vhf-logger.html is evaluated inside a node:vm context using the same Proxy-based DOM mock as edi-crosscheck.html. Because module-level let bindings are not ctx properties, helpers are injected via a second vm.runInContext call. The ctx also explicitly receives TextEncoder, TextDecoder, Uint8Array, DataView, and ArrayBuffer so that makeZip can run correctly:
_setCurrentForTest(session) — sets the active session for dupe-related tests_getCurrentForTest() — reads the active session back_getEditingExistingForTest() — reads the _editingExisting flag for session-edit tests_getI18nValueForTest(lang, key) — reads a value from the S i18n object for i18n coverage tests_getManualTimeForTest() — reads the _manualTime state variable_setManualTimeForTest(v) — sets _manualTime for state tests_getBandColorsForTest() — returns the BAND_COLORS mapbaseCall (10 tests)Verifies suffix stripping used for dupe detection and crosscheck lookup.
/P, /M, /MM, /AM, /QRP suffixes stripped from trailing position./1, /2) stripped.OE/S59DGO) kept unchanged (heuristic: slash before digit-containing part = suffix, otherwise = prefix).normBand (10 tests)Verifies the band mapping table.
144 MHz, 432 MHz), wavelength strings (2m, 70cm), and GHz strings (1.3 GHz).{band:'', freq:''}.locToLatLon (7 tests)Verifies Maidenhead locator → latitude/longitude conversion.
JN65VP → approx. lat 45.5°N, lon 13.8°E (Ajdovščina area).IO91wm → approx. lat 51.3°N, lon -0.1°E (London area).null.null (only 6-character supported).haversine (4 tests)Verifies great-circle distance calculation.
JN65VP → JN58UD ≈ 320 km (±30).0.calcBearing (5 tests)Verifies great-circle bearing calculation.
levenshtein (7 tests)Verifies the Levenshtein distance function with maxDist=2 early exit.
maxDist+1 when length difference alone exceeds maxDist (early exit).isDupe (7 tests)Verifies dupe detection using baseCall() normalization and excludeId.
/P suffix → also detected as dupe.excludeId param prevents false-dupe when checking the QSO being edited._current = null) → always returns false.recalcDupes (4 tests)Verifies full dupe-flag recalculation across a session.
dupe=false; subsequent → dupe=true./P portable call normalizes to base call — counted as dupe of plain base call.dupe=false.recalcDupes, the _current.qsos array is mutated in place.buildEdi (57 tests)Verifies REG1TEST EDI v1 output format.
[REG1TEST;1] header.TDate uses full YYYYMMDD;YYYYMMDD (start;end); QSO records use YYMMDD.TName, PCall, PWWLo, PBand, PClub, PSect, MOpe1 headers present and correct.SPowe, SAnte, STXEq, SRXEq, SAntH populated from band config.CQSOs, CQSOP, CWWLs, CWWLB, CExcs, CExcB, CDXCs, CDXCB, CToSc, CODXC — computed from non-dupe QSOs.[QSORecords N] section present with correct count.D for duped QSO, empty for normal QSO.nrS / nrR zero-padded to 3 digits.WWL in QSO line is 6 characters uppercase.PClub header populated from session.club; empty when not set.SSB → 1, CW → 2, FM → 6.TCall, TLocator, RAZ, RClub not emitted.PExch, PAdr1, PAdr2, RCall, RHBBS, RPoCo, RPhon — populated from session fields or emitted as empty lines.TName before TDate before PCall; STXEq before SPowe before SRXEq.CODXC and [Remarks]; no blank line between [Remarks] and [QSORecords;N].[END;S56OA HamLogTools VHF Logger] footer present.lookupCall (6 tests)Verifies crosscheck lookup against the weighted+raw baseline DB.
found=true, modeLoc set to most common locator./P portable → base call looked up correctly.found=false, similar array populated from Levenshtein search.similar list sorted by distance ASC, then count DESC.found=false, similar=[].sessionEdit (10 tests)Verifies state and i18n coverage for the session-editing feature.
_editingExisting flag initialises to false.btnEditSetup, setupEdit, btnSaveSetup, errBandHasQsos) are non-empty strings.sl.setupEdit and en.setupEdit are distinct strings (translation exists).parseEdiForImport (10 tests)Verifies EDI file parsing for the import feature.
YYMMDD date in QSO records converted to full YYYYMMDD (YY ≥ 80 → 19xx, YY < 80 → 20xx).1 → SSB, 2 → CW.D at col 13 detected; clean QSOs have dupe=false.PBand, PCall) extracted.HHMM.makeZip (5 tests)Verifies the minimal ZIP generator (STORE/no-compression).
Uint8Array.PK\x03\x04.PK\x05\x06.bandColors (4 tests)Verifies the BAND_COLORS map used to colour band tabs.
2m and 70cm.2m and 70cm have distinct colour values.#rrggbb hex strings.manualTime (7 tests)Verifies manual UTC time override state and i18n keys for new features.
_manualTime initialises to null.sl.toastImported contains ${n} placeholder.sl.errImportBand contains ${band} placeholder.sl.btnExportAll and en.btnExportAll are non-empty strings.sl.btnImport and en.btnImport are non-empty strings.backup (32 tests)Verifies validateBackup() structure checks and i18n strings for the backup/restore feature.
app, sessions array, valid sessions and QSOs) returns the sessions array.null for wrong app field, missing app, sessions not an array, null or raw array input.null if id is missing or empty, myCall missing, bands or qsos not arrays.myLoc must be a valid 6-character Maidenhead locator ([A-R]{2}[0-9]{2}[A-X]{2}, case-insensitive); returns null if absent or malformed.contest must be a non-empty string; returns null if absent or empty string.null if _id, band, or call missing from any QSO.sl.btnRestore ≠ en.btnRestore (distinct translations).sl.confirmRestore and en.confirmRestore contain ${n} placeholder.sl.toastRestoreDone and en.toastRestoreDone contain ${n} placeholder.I18N (3 tests)Verifies i18n key symmetry between SL and EN translations.
adif-stats.test.js — 133 tests · 21 groupsCovers the pure logic of adif-stats.html: DXCC prefix lookup, band/mode normalisation, locator conversion, QRB calculation, ADIF parsing, statistics aggregation, filter logic, date/month formatting, XSS escaping, SVG chart helpers, and i18n key completeness.
adif-stats.html is evaluated inside a node:vm context using the same mock setup as the other HTML tools. Only the <script> block is extracted; the minimal mock provides localStorage, document.getElementById, URL, Blob, setTimeout, and console.
Pure functions are accessed directly as context properties:
lookupCall, normBand, normMode, locToLatLon, haversine, parseADIF, computeStats, fmtDate, fmtMonth, htmlEsc, svgHBar, svgVBar, t.
An adif() helper builds minimal ADIF fixture strings with correct <TAG:length>value encoding.
lookupCall (17 tests)Verifies DXCC entity lookup by callsign prefix using the built-in PREFIX_DB table.
EU; digit 9 or 0 maps to AS./P suffix stripped — S59DGO/P resolved as S59DGO.OE/ prefix notation — OE/S59DGO resolved by the OE prefix (Austria), not S5.{ country: 'Unknown', cont: '?' }.Unknown.KL (Alaska), CU (Azores), TK (Corsica), 9H (Malta), UA2 (Kaliningrad) resolve correctly despite sharing prefixes with larger entities.normBand (5 tests)2M → 2m, 40M → 40m).null both return ''.normMode (5 tests)cw → CW).DIGI + FT8 → FT8; DIGI + FT4 → FT4).locToLatLon (5 tests)[lat, lon].JN65 → lat 45.5, lon 13.null and too-short input return null.JN65ar) produces the same result as the 4-character prefix — only the first 4 characters are used.haversine (5 tests)0.> 0.JN65 → FN20) > 6 000 km.null locator → 0.parseADIF — basic extraction (9 tests)CALL extracted; BAND lowercased; MODE uppercased regardless of input case.SUBMODE overrides MODE (DIGI + FT8 → mode FT8).src set to the filename argument.CALL silently skipped.<EOH>) handled; tag names case-insensitive.parseADIF — date/time/QRB (8 tests)QSO_DATE stored as YYYYMMDD; ISO format (YYYY-MM-DD) normalised.''.TIME_ON HHMMSS truncated to HHMM; HHMM kept as-is.DISTANCE field used as qrb when present.GRIDSQUARE + MY_GRIDSQUARE → qrb via haversine when DISTANCE absent.DISTANCE takes priority over grid-based calculation.parseADIF — country/cont (3 tests)S56OA → { country: 'Slovenia', cont: 'EU' }.JA1ZLO → { cont: 'AS' }.{ country: 'Unknown', cont: '?' }.computeStats — overview (7 tests)total=0, empty calls Set.total counts all QSOs.calls Set counts unique callsigns (same call twice = 1).dates Set counts unique dates.bestDX tracks the QSO with the highest qrb.firstDate / lastDate track the chronological span.countries Set excludes 'Unknown'.computeStats — aggregates (9 tests)byBand counts QSOs per band; tracks unique calls and best DX per band.byMode counts QSOs per mode.byCont tracks unique countries per continent.byHour is a 24-element array; correct slot incremented by time field.byMonth groups by YYYYMM key.topCalls Map sorted descending by count.'?') not added to byCont.applyFilters — date filter (6 tests)Replicates the date-range filtering logic in isolation:
from or to is set.from or after to excluded.fmtDate (4 tests)'20240315' → '15.03.2024'.fmtMonth (3 tests)'202403' → '03/2024'; '202412' → '12/2024'.htmlEsc (7 tests)&, <, >, " escaped to HTML entities; plain text unchanged.svgHBar (6 tests)<svg> tag.<svg> present; multiple items produce <rect> bars.<rect> (skipped by bw>0 guard).colorFn callback applied to bar fill.svgVBar (6 tests)<svg> tag.<svg> and <rect> present.<rect> elements.var(--muted) colour.I18N (8 tests)t('secOver') returns a non-empty translated string (not the key itself).secBand, secMode, secCont, secCountry, secTime, secTop) all non-empty.cTotal, cUniq, cCountries, cDays, cBestDX, cDateRange) all non-empty.secDxcc, secHeatmap, secBandHour, secQrb, dxccTotal, dxccCount, dxccProg, qrbRange, qrbNoData) all non-empty and differ from their key name.hmapDow has 7 |-separated day abbreviations; SL first = Po, last = Ne.hmapMon has 12 |-separated month abbreviations; first = Jan, last = Dec.hmapMore non-empty and differs from its key name.computeStats — byDay (3 tests)byDay Map counts QSOs per YYYYMMDD key.byDay.byDay.size === 3.computeStats — byBandHour (4 tests)byBandHour is a 24-element array.time field ('1430' → index 14).time → no band entry created in byBandHour.computeStats — byDxcc / byBandDxcc (5 tests)byDxcc counts unique DXCC entity names; each entry has .qso count and .bands Set.byDxcc.get('Germany').qso === 2.bands Set has both bands.byBandDxcc Map: per-band Set of countries; correct size per band.'Unknown' country excluded from both byDxcc and byBandDxcc.computeStats — qrbBuckets (8 tests)qrbBuckets is a 6-element array (bucket indices 0–5).< 500 km): qrb 200 and 499 both land here.500–1 000 km): qrb 500 and 999.1 000–2 000 km): qrb 1 000 and 1 999.2 000–5 000 km): qrb 2 000 and 4 999.5 000–10 000 km): qrb 5 000 and 9 999.≥ 10 000 km): qrb 10 000 and 15 000.qrb === 0 not bucketed (represents unknown distance).adif2cab.test.js — 191 tests · 31 groupsCovers the pure logic of adif2cab.html: Cabrillo mode mapping, RST defaults, frequency conversion, ADIF parsing, exchange extraction per contest, QSO line formatting, XSS escaping, badge helpers, CONTESTS array structure, and i18n key completeness.
adif2cab.html is evaluated inside a node:vm context using the same Proxy-based DOM mock as the other HTML tools. Only the <script> block is extracted. Pure functions are accessed directly as context properties.
Three injected helpers give access to const/let module state:
_getContests() — returns the CONTESTS array_getLangKeys(lang) — lists all i18n keys for a given language_getI18n(lang, key) — reads a value from the S i18n objectA local adif() helper builds ADIF fixture strings with correct <TAG:length>value encoding.
A makeQso() helper builds a minimal QSO object with sensible defaults; individual fields are overridden per test via spread.
modeToCAB — phone modes → PH (5 tests)SSB, USB, LSB, AM → PH.ssb normalised to uppercase before lookup → PH.modeToCAB — CW → CW (2 tests)CW (upper and lower) → CW.modeToCAB — FM-based voice → FM (5 tests)Cabrillo v3 spec defines FM as a distinct mode value (not PH).
FM, C4FM, DSTAR, DMR, DIGITALVOICE → FM.modeToCAB — RTTY → RY (2 tests)Cabrillo v3 spec defines RY for RTTY (not the DG catch-all).
RTTY (upper and lower) → RY.modeToCAB — digital modes → DG (8 tests)FT8, FT4, PSK31, JT65, JS8, WSPR → DG.null → DG (safe catch-all).dfltRST (7 tests)PH and FM → '59' (two-digit phone RST).CW, DG, RY → '599' (three-digit CW/digital RST; RTTY uses 3-digit by convention).'59' (phone default).freqToKHz — FREQ field conversion (6 tests)14.210 MHz → 14210, 144.300 MHz → 144300, 432.200 MHz → 432200.14.0005 → 14001).FREQ '0' and FREQ '' (not > 0) fall back to BAND_KHZ.freqToKHz — BAND_KHZ fallback (10 tests)20m → 14200, 2m → 144300, 40m → 7100, 70cm → 432200, 23cm → 1296100.6m → 50200, 4m → 70200.0.fields property falls back gracefully; no band → 0.parseADIF — basic extraction (8 tests)call, date, time, band, mode extracted correctly.CALL normalised to uppercase; BAND to lowercase; MODE to uppercase.RST_SENT → rstS, RST_RCVD → rstR.src set to the filename argument.CALL field are silently skipped.fields dict with uppercase keys.parseADIF — date/time normalization (7 tests)YYYYMMDD stored as-is; ISO YYYY-MM-DD stripped of dashes.DD.MM.YYYY.HHMMSS time truncated to HHMM; display time formatted as HH:MM.QSO_DATE or TIME_ON → empty string (no crash).parseADIF — multi-record / edge cases (11 tests)<EOH> (headerless ADIF) accepted.<TAG:len:type> type-specifier variant handled.STATION_CALLSIGN preserved in fields (used to auto-populate the header callsign).fields dict keys are uppercase regardless of input case.STX (WPX sent serial), SRX (received serial), and SRX_STRING preserved in fields.extractExchR — CQ-WW-SSB (6 tests)CQZONE field returned when present; whitespace trimmed.SRX_STRING, then SRX when CQZONE absent.CQZONE takes priority over SRX_STRING when both present.''.extractExchR — CQ-WW-CW (3 tests)CQZONE priority chain as CQ-WW-SSB.extractExchR — IARU-HF (6 tests)ITUZ returned when present; whitespace trimmed.SRX_STRING fallback accepts HQ abbreviations (e.g. DARC).ITUZ takes priority over SRX_STRING; SRX is the tertiary fallback.extractExchR — ARRL-DX (6 tests)STATE returned when present; whitespace trimmed.STATE takes priority over SRX_STRING; SRX tertiary fallback.extractExchR — GENERIC (5 tests)SRX_STRING returned; falls back to SRX; SRX_STRING has priority.contestId behaves as GENERIC.extractExchR — CQ-WW-RTTY (5 tests)CQZONE priority chain as CQ-WW-SSB/CW.CQZONE takes priority over SRX_STRING; SRX tertiary fallback.extractExchR — IARU-VHF (7 tests)GRIDSQUARE field returned and uppercased (ADIF stores mixed-case, Cabrillo expects uppercase).SRX_STRING and SRX fallbacks also uppercased.GRIDSQUARE takes priority over SRX_STRING.extractExchR — CQ-WPX-SSB (4 tests)SRX_STRING returned; falls back to SRX; SRX_STRING has priority.''.extractExchR — CQ-WPX-CW (3 tests)SRX_STRING/SRX priority chain as CQ-WPX-SSB.formatCabDate (7 tests)YYYYMMDD → YYYY-MM-DD (e.g. 20241026 → 2024-10-26).null, undefined, empty string → '0000-00-00' (safe sentinel).buildQSOLine — structure (12 tests)QSO: and ends with ` 0` (transmitter ID).YYYY-MM-DD), time (HHMM), my callsign, their callsign appear in the output.7100 → ' 7100'); zero freq → ' 0'.144300 → ' 144300', 432200 → ' 432200', 1296100 → '1296100'.buildQSOLine — exchange handling (6 tests)exchS used when non-empty; header-level default fills in when exchS = ''.exchR included in line; empty exchR produces padded empty field without crashing.contest.exchW: 4 for CQ WW, 6 for IARU.buildQSOLine — RST defaults (3 tests)rstS → dfltRST(cabMode) applied (59 for PH, 599 for CW).rstS value overrides the default.buildQSOLine — IARU-VHF locator exchange (4 tests)JN65AR) fits exchW:6 exactly.JN65) padded to 6 with trailing spaces.exchS) included in the line.exchR produces padded empty field without crashing.buildQSOLine — CQ WPX per-QSO serial (3 tests)exchS serial number used when set (e.g. '001').exchW:6.exchS is empty.htmlEsc (9 tests)&, <, >, " escaped to HTML entities.null and undefined → ''; numbers coerced to string.<script>alert(1)</script> neutralised.cabModeBadge (6 tests)Maps Cabrillo mode to CSS badge class for the preview table.
PH → badge-ssb.CW → badge-cw.FM, DG, RY, '' → badge-digi.modeBadge (9 tests)Maps ADIF mode to CSS badge class for the preview table.
SSB, USB, LSB, AM → badge-ssb.CW → badge-cw.FM → badge-fm.FT8, RTTY, '' → badge-digi.CONTESTS — structure (11 tests)id, name, exchSentLbl, exchRcvdLbl, exchRcvdField, exchW > 0.exchRcvdField correct per contest: CQ-WW-SSB/CQ-WW-CW/CQ-WW-RTTY → CQZONE, IARU-HF → ITUZ, IARU-VHF → GRIDSQUARE, ARRL-DX → STATE, CQ-WPX-SSB/CQ-WPX-CW/GENERIC → SRX_STRING.sl and en sub-keys.I18N (4 tests)dropTitle values differ (sanity check that translations are distinct).edi-validator.test.js — 109 tests · 22 groupsCovers the pure logic of edi-validator.html: Maidenhead geo utilities, and the full validate() function across all spec checks and issue types.
edi-validator.html is evaluated inside a node:vm context using the same Proxy-based DOM mock as the other HTML tools. The validate(text) function is accessed directly as a context property and accepts raw EDI file text, returning {issues[], qsoCount}.
The S i18n object (const S = {sl:{...}, en:{...}}) is exported via a second vm.runInContext('globalThis._S = S', ctx) call. A minimal valid EDI fixture is defined in the test file and used as a base for mutation-based tests (each test mutates one aspect of the fixture to trigger a specific issue code).
locToLatLon (4 tests)Verifies Maidenhead locator → latitude/longitude conversion.
JN65VP → approx lat 45.5°N, lon 13.8°E.null.haversine (2 tests)Verifies great-circle distance calculation.
0.JN65VP → JN78DG ≈ 294 km (±30).validate — clean EDI (4 tests)Baseline sanity check with a well-formed EDI fixture.
qsoCount matches the number of [QSORecords;N] QSO lines.[END;...] → iNoEnd (info).validate — structure (6 tests)Checks for required section markers and blank-line rules.
[REG1TEST;1] → eNoReg1test (error).[Remarks] → eNoRemarks (error).[QSORecords;N] → eNoQsoSection (error).eBlankInHeader (error).[Remarks] section → iBlankInRemarks (info, not error).validate — non-spec keywords (6 tests)Checks for keywords not defined in the REG1TEST v1 spec.
TCall, TLocator, RAZ, RClub, RBand → eNonSpecKw (error).eNonSpecKw.validate — header formats (8 tests)Checks header field syntax.
TDate=YYYYMMDD;YYYYMMDD valid format → no error.TDate wrong format → eTDateFormat (error).TDate single date (missing second half) → eTDateFormat.PWWLo 4-character locator → wPwwloFormat (warn; too short for QRB calculation).PWWLo invalid characters → wPwwloFormat.PBand unknown value → wPBandUnknown (warn).PBand 145 MHz (known value) → no wPBandUnknown.iKwOrder (info).validate — ZRS mandatory fields (6 tests)Checks ZRS-required fields are non-empty.
PSect, PClub, RName, RHBBS, SPowe each → wFieldEmpty (warn).wFieldEmpty.validate — QSO count (3 tests)Checks declared vs. actual QSO count.
eQsoCountMismatch.eQsoCountMismatch (error).eQsoCountMismatch.validate — QSO field count (3 tests)Checks each QSO record has exactly 15 semicolon-delimited fields.
eQsoFieldCount (error).eQsoFieldCount.eQsoFieldCount.validate — QSO date (6 tests)Checks QSO date field (YYMMDD at col 0).
eQsoDate.eQsoDate (error).00 → eQsoDate.13 → eQsoDate.00 → eQsoDate.eQsoDate but not wQsoDateOutOfRange (range check suppressed for structurally invalid dates).validate — QSO time (4 tests)Checks QSO time field (HHMM at col 1).
eQsoTime.eQsoTime (error).24 → eQsoTime.60 → eQsoTime.validate — QSO mode (5 tests)Checks QSO mode field (col 3, valid values 0–9; mode 0 = “none of below” per spec).
0 → valid, no error (spec-defined “none of below” value).1 and 9 → valid, no error.A (non-numeric) → eQsoMode (error).10 (out of range) → eQsoMode.validate — QSO dupe flag (6 tests)Checks QSO dupe flag field (col 14, valid values empty or D).
D → no error.X (invalid value) → eQsoDupe (error).d (lowercase) → eQsoDupe.D with non-zero QRB → iDupeNonZeroQrb (info; dupes typically report QRB 0).D with QRB 0 → no iDupeNonZeroQrb.validate — QSO WWL format (4 tests)Checks QSO locator field (col 9).
wQsoWwlFormat (warn).validate — QRB deviation (5 tests)Checks declared QRB (col 10) against haversine-calculated distance.
wQrbDeviation.wQrbDeviation (warn).PWWLo is 4-char → QRB check skipped (insufficient locator precision).wQsoQrbNum (warn); wQrbDeviation not raised.validate — line length (2 tests)Checks each line does not exceed 75 characters.
wLineTooLong.wLineTooLong (warn).validate — non-ASCII characters (3 tests)Checks for characters outside the 7-bit ASCII range (spec §15.3.4).
wNonAscii.wNonAscii (not eNonAscii).warn (not error).validate — duplicate keywords (4 tests)Checks that each header keyword appears at most once.
wDuplicateKw.TDate → wDuplicateKw (warn).PCall → wDuplicateKw.TDate means no eTDateFormat even when the keyword is duplicated.validate — QSO date day (5 tests)Checks that the day value is valid for the given month and year (leap year aware).
wQsoDateDay.wQsoDateDay.wQsoDateDay (warn).wQsoDateDay.wQsoDateDay.validate — QSO date range (5 tests)Checks that QSO dates fall within the TDate header range (YYYYMMDD;YYYYMMDD).
wQsoDateOutOfRange.wQsoDateOutOfRange (warn).wQsoDateOutOfRange.TDate in header → no wQsoDateOutOfRange.TDate format (not parseable) → no wQsoDateOutOfRange (range not checked).validate — QSO RST format (13 tests)Checks RST fields (col 4 = RST sent, col 6 = RST received) match the expected digit count for the mode. Rules: SSB/AM/FM (modes 1/4/5/6) → 2 digits; CW/RTTY (modes 2/3/7) → 3 digits; modes 0/8/9 → check skipped. Empty RST is always allowed.
59 → no wQsoRstFormat.599 → no wQsoRstFormat.599 (3 digits) → wQsoRstFormat (warn).59 (2 digits) → wQsoRstFormat.59 → no wQsoRstFormat.599 → no wQsoRstFormat.wQsoRstFormat (check skipped).wQsoRstFormat (allowed by spec).59 → no wQsoRstFormat.599 → no wQsoRstFormat.59 (2 digits) → wQsoRstFormat.59 → no wQsoRstFormat.599 (3 digits) → wQsoRstFormat.I18N (5 tests)Verifies i18n key completeness and translation distinctness.
sl.sevError and en.sevError are non-empty strings.sl.sevError ≠ en.sevError (translations are distinct).edi-crosscheck.test.js — 56 testov · 8 skupinPokriva čisto logiko edi-crosscheck.html: odstranjevanje pripon, razdalja urejanja, razčlenjevanje EDI in vse algoritme crosschecka, vključno z nastavljivimi pragovi in predlogi za manjkajoče lokatorje.
edi-crosscheck.html se izvede znotraj konteksta node:vm po enakem vzorcu kot edi2adif.html. Za razliko od tega orodja kode ne odstranjujemo — namesto tega nadomestek DOM na osnovi Proxy tiho absorbira vse dostope do lastnosti in klice metod, tako da se začetno priklapljanje poslušalcev dogodkov izvede brez napak.
Stanje na ravni modula (_histDB, _results) je const/let in zato ni dostopno kot lastnost ctx. Testi upravljajo z vsem stanjem prek deklaracij funkcij:
clearHist() — ponastavi bazo in rezultate med testiaddToHistDB(qsos) — polni zgodovinsko bazorunCrosscheck(qsos) — izvede crosscheck in vrne polje rezultatovbaseCall (13 testov)Preverja odstranjevanje pripon za ujemanje pri crosschecku.
/P, /M, /MM, /AM, /QRP, /R, /A, /B) se odstranijo z zadnjega mesta./IV3, /I1 itd.) se odstranijo — obravnavani so enako kot /P, ker gre za isto postajo, ki oddaja iz druge regije./1, /2 itd.) se odstranijo.OE/S59DGO, F/ON4AAA) ostanejo nespremenjeni — predstavljajo drugačno lokacijo delovanja (predpona je državna/regionalna, ne sufiks).klicniZnak/sufiks in sufiks se odstrani; sicer gre za predpona/klicniZnak in se ohrani nespremenjeno.levenshtein (9 testov)Preverja funkcijo Levenshteinove razdalje z zgodnjim izhodom pri maxDist=2.
maxDist+1, ko razlika v dolžini sama presega maxDist (zgodnji izhod).parseEDI (9 testov)Preverja ekstrakcijo QSO iz fragmenta EDI datoteke.
PBand in apliciran na vse QSO-je.JN65ar); neveljavni lokatorji počiščeni na ''.DD.MM.YYYY; dvo-cifreno leto razvito.ERROR preskočeni; obravnavani zaključki vrstic CRLF.runCrosscheck — neskladje lokatorja (6 testov)| Test | Kaj se preverja |
|---|---|
| Čisto ujemanje | Brez težave, ko se lokator ujema z zgodovinskim modusom |
| Visoka resnost | LOC_MISMATCH resnost high, ko zaupanje v modus ≥ 60% in nov lokator še nikoli ni bil viden |
| Srednja resnost | LOC_MISMATCH resnost med, ko je bil nov lokator že viden (npr. prenosna postaja) |
| Prag | Brez zastavice, ko ima klicni znak manj kot 3 zgodovinska pojavitev |
| Brez lokatorja | Ni LOC_MISMATCH, ko QSO nima lokatorja (wwl = '') — namesto tega se lahko pojavi LOC_MISSING |
| Vrstni red allLocs | Seznam zgodovinskih lokatorjev v težavi je razvrščen po številu padajoče |
runCrosscheck — preverjanje klicnega znaka (8 testov)| Test | Kaj se preverja |
|---|---|
| CALL_SIMILAR d=1 | Klicni znak ni v zgodovini; ujemanje z razdaljo 1 najdeno in razvrščeno na vrhu |
| CALL_UNKNOWN | Klicni znak ni v zgodovini; ni podobnega v razdalji 2 |
| V zgodovini | Brez težave z klicnim znakom, ko bazni klicni znak obstaja v bazi |
| Normalizacija prenosnih | S59ABC/P se primerja z zgodovino S59ABC — brez zastavice klicnega znaka |
| Vrstni red | Podobni predlogi razvrščeni po razdalji naraščajoče, nato po številu padajoče |
| Razdalja 2 | Ujemanja z razdaljo 2 so prav tako označena (CALL_SIMILAR) |
| Kombinacija težav | Neznani klicni znak ustvari samo CALL_SIMILAR; brez napačne LOC težave brez zgodovine |
| Deduplikacija | Ponavljajoči se neznani klicni znak v novem dnevniku ponovno uporabi preračunan seznam podobnih |
runCrosscheck — predlog za manjkajoč lokator (4 testa)| Test | Kaj se preverja |
|---|---|
| Predlagaj modus | LOC_MISSING se sproži, ko nov dnevnik nima lokatorja, a zgodovina obstaja |
| Srednja resnost | LOC_MISSING resnost med, ko je zaupanje v modus pod pragom |
| Prag | Ni LOC_MISSING, ko je zgodovinskih pojavitev manj kot _minAppearances |
| Prazni lokatorji v zgodovini | Ni LOC_MISSING, ko imajo vsi zgodovinski vnosi prazne lokatorje |
runCrosscheck — nastavljivi pragovi (3 teste)| Test | Kaj se preverja |
|---|---|
_minAppearances |
Ni zastavice, ko je zgodovinsko število pod pragom drsnika |
_minConfidence |
Resnost upošteva prag zaupanja drsnika (high vs med) |
| Prazni lokatorji prezrti | Prazni zgodovinski lokatorji ne vplivajo na izračun modusa |
runCrosscheck — klicni znak po lokatorju (4 testi)| Test | Kaj se preverja |
|---|---|
| CALL_BY_LOC osnovno | Predlaga klicne znake, ki so bili zgodovinsko videni z istega lokatorja in so v razdalji Levenshtein ≤ 2 |
| Brez ujemanja | Ni CALL_BY_LOC, ko noben zgodovinski klicni znak z istega lokatorja ni v razdalji 2 |
| Ločeno od CALL_SIMILAR | CALL_BY_LOC in CALL_SIMILAR se pojavita kot ločeni težavi v rezultatu |
| Redundantno soobstajanje | CALL_BY_LOC se sproži tudi, ko se kandidati prekrivajo s CALL_SIMILAR — oba signala se prikažeta kot potrjevalni dokaz |
adif-merge.test.js — 112 testov · 21 skupinPokriva čisto logiko adif-merge.html: razčlenjevanje ADIF, deduplikacijo, normalizacijo polj, pomožnike za izvoz, XSS ubežanje, ubežanje CSV, popolnost i18n ključev in regresijske teste za popravke iz code reviewa.
adif-merge.html se izvede znotraj konteksta node:vm z enakim nadomestkom DOM na osnovi Proxy kot ostala HTML orodja. Modularni let vezani niso lastnosti ctx; ti testni pomočniki se vbrizgajo prek drugega klica vm.runInContext:
_getAllForTest() / _setAllForTest(arr) — branje in pisanje polja QSO _all_getFilteredForTest() / _setFilteredForTest(arr) — dostop do _filtered_getDeselForTest() — branje množice _desel_getSourcesForTest() / _setSourcesForTest(arr) — branje in pisanje _sources_getI18nForTest(jezik, ključ) — branje vrednosti iz i18n objekta S_getLangKeys(jezik) — seznam vseh ključev za podan jezikLokalni pomočnik adif() izračuna :dolžino iz dejanske vrednosti niza vsakega polja, da fiksture testov niso krhke pri napakah v dolžini.
parseADIF — basic extraction (9 testov)CALL z velikimi črkami, BAND z malimi, MODE z velikimi — ne glede na vnos.RST_SENT, RST_RCVD in GRIDSQUARE izvlečeni v priročne lastnosti.COMMENT, TX_PWR) ohranjena v slovarju q.fields.src nastavljen na ime datoteke; zapisi brez CALL tiho preskočeni.parseADIF — date/time normalization (7 testov)YYYYMMDD shranjen kot je; ISO YYYY-MM-DD oblikovan brez pomišljajev.q.fields.QSO_DATE.HHMMSS in HH:MM:SS časi okrnjeni na HHMM za dedup ključ.DD.MM.YYYY in HH:MM generirani za UI.parseADIF — multi-record / edge cases (9 testov)<EOH> pravilno obravnavane.<EOR> preskočeni; oznake neobčutljive na velikost črk.APP_* ohranjene; type specifier <TAG:len:TYPE> ignoriran.parseADIF — fields dict kept in sync (4 testi)Normalizacija piše nazaj v q.fields, tako da je ADIF izvoz brez izgub:
CALL v slovarju: velike + obrezano; BAND: male; MODE: velike; QSO_DATE: brez pomišljajev.updateKey (3 testi)CALL|BAND|MODE|DATE|TIME.recomputeDupes (7 testov)dupe=false; naslednja z enakim ključem → dupe=true.dupe=false.dupe=true.dupe=true se počistijo pred ponovnim izračunom.parseADIF dedup key uniqueness (4 testi)Integracija — ključi se pravilno prenesejo skozi parse + updateKey:
_key.CALL ali BAND normalizira na enak ključ.adifField (7 testov)<OZNAKA:dolžina>vrednost (presledek na koncu po ADIF konvenciji).null, undefined in '' → prazen niz (polje izpuščeno v izvozu).100 serializirana kot '100'.htmlEsc (9 testov)&, <, >, " ubežani v HTML entitete; navadni nizi nespremenjeni.null in undefined → ''; števila pretvorjena v niz.<script>alert(1)</script> prikazan varno.csvEsc (7 testov)null → ''; števila pretvorjena v niz.modeBadge (13 testov)SSB, AM, USB, LSB → badge-ssb; CW → badge-cw; FM → badge-fm.FT8, FT4, RTTY, JS8, WSPR, neznan in prazen niz → badge-digi.buildFilename (6 testov)STATION_CALLSIGN uporabljen, če je prisoten; MY_CALLSIGN kot nadomestilo; "merged", ko ni nobenega."merged"; pravilna končnica dodana (.adi, .csv)./ v klicnem znaku nadomeščen z - za varnost datotečnega sistema.ADIF export — field preservation (2 testa)TX_PWR, ANTENNA, NOTES, MY_GRIDSQUARE) preživijo krog parse → izvoz prek q.fields.CALL, QSO_DATE, TIME_ON, BAND, MODE) prisotna v q.fields.I18N (4 testi)dropTitle se razlikuje med SL in EN.parseADIF — real-world fixtures (4 testi)HHMMSS okrnjen na HHMM, FREQ in STATION_CALLSIGN ohranjeni.RST, TX_PWR, COMMENT pravilno izvlečeni.recomputeDupes.parseADIF — missing optional fields (5 testov)Regresija — razčlenjevalnik se ne sme zrušiti, ko manjkajo neobvezna polja:
BAND → band = ''; manjkajoč TIME_ON → time = '', timeDisp = ''.QSO_DATE → date = '', dateDisp = ''.RST_SENT/RST_RCVD → ''; manjkajoč GRIDSQUARE → grid = ''.parseADIF — no submode property on QSO object (2 testa)Regresija — SUBMODE je bila mrtva lastnost, odstranjena iz objekta QSO med code reviewom:
submode ni lastnost razčlenjenega objekta QSO.SUBMODE je še vedno ohranjena v q.fields za ADIF izvoz brez izgub.adifField — export consistency (3 testi)adifField je idempotentna glede na velikost črk oznake (majhne ali velike → isti izhod).APP_ADIFMERGE_SRC zgrajena s pravilno :dolžino iz izvorne datoteke.<TAG:0>).updateKey — empty band handling (2 testa)Dokumentira vedenje pri odsotnem band — brez zrušitve, ustvari CALL||MODE|DATE|TIME; dva takšna QSO z enakimi ostalimi polji si delita ključ (bosta deduplicirani).
I18N — errBand key (3 testi)Regresija — prevajalski ključ errBand dodan med code reviewom:
parseADIF — re-merge safety (APP_ADIFMERGE_SRC) (2 testa)Regresija — ponovni merge predhodno merganega ADIF ne sme podvojiti oznake provenienc:
APP_ADIFMERGE_SRC iz prejšnjega mergea shranjen v q.fields (zgodovina ohranjena).q.src vedno odraža ime datoteke, podano parseADIF, ne staro anotacijo — exportADIF zapiše pravilno novo oznako izvora.adif-qrz-filter.test.jsLočena testna zbirka pokriva Node.js CLI orodje. Tudi ta uporablja node:test brez zunanjih odvisnosti.
Testov: 48 v 4 skupinah
node --test adif-qrz-filter.test.js
node --test --test-reporter=spec adif-qrz-filter.test.js
| # | Skupina | Testov | Kaj se preverja |
|---|---|---|---|
| 1 | parseAdif |
6 | Razčlenjevanje ADIF: ekstrakcija glave, razdelitev zapisov, izvleček QSL_VIA, obravnava CRLF, preskočitev manjkajočega CALL |
| 2 | extractField |
8 | Generična ekstrakcija <TAG:dolžina>vrednost za CALL, QSL_VIA, neobčutljivost na velikost črk, obrezovanje, pretvorba v velike črke, ADIF type specifier (<TAG:len:TYPE>) |
| 3 | usesQslBuro |
31 | Fuzzy logika: 12 pozitivnih primerov (buro/bureau + evropske črkovalice: buero/büro/buerau/boureau/burea/buiro; “Direct or Bureau” pravilno vrne true), 16 negativnih primerov (no/direct only/only via LoTW/eQSL only/”QSL via KLICNI_ZNAK”), 3 robni primeri (null/prazno) |
| 4 | cache |
3 | Krog shranjevanja/nalaganja JSON predpomnilnika, čiščenje po 7 dneh, obravnava manjkajoče datoteke |
CLI orodje se izvede znotraj konteksta node:vm, ki nadomesti fs, https, process in console. Čiste funkcije (parseAdif, extractField, usesQslBuro, loadCache, saveCache) se izvlečejo in testirajo neposredno.
Opomba o
deepStrictEqual: Tako kot pri testihedi2adif.htmlv vm kontekstu lahkoassert.deepStrictEqualna vm-ustvarjenih objektih ne uspe, čeprav so lastnosti identične. Testi predpomnilnika zato uporabljajoassert.equalna posameznih lastnostih aliObject.keys().lengthza preverjanje praznih objektov.
vhf-logger/vhf-logger.test.js — 191 testov · 17 skupinPokriva čisto logiko vhf-logger/vhf-logger.html: normalizacijo klicnih znakov, mapiranje pasov, geo pomožnike, zaznavanje duplikatov, preračun duplikatov, gradnjo EDI, crosscheck poizvedbe, razčlenjevanje uvoza EDI, generiranje ZIP, barve pasov, stanje ročnega časa in validacijo backup/obnovi.
vhf-logger/vhf-logger.html se izvede znotraj konteksta node:vm z enakim nadomestkom DOM na osnovi Proxy kot edi-crosscheck.html. Ker modularni let vezani niso lastnosti ctx, se pomožne funkcije vbrizgajo prek drugega klica vm.runInContext. Ctx izrecno prejme TextEncoder, TextDecoder, Uint8Array, DataView in ArrayBuffer za pravilno delovanje makeZip:
_setCurrentForTest(seja) — nastavi aktivno sejo za teste duplikatov_getCurrentForTest() — prebere aktivno sejo_getEditingExistingForTest() — prebere zastavico _editingExisting_getI18nValueForTest(jezik, ključ) — prebere vrednost iz S objekta za i18n teste_getManualTimeForTest() — prebere stanje _manualTime_setManualTimeForTest(v) — nastavi _manualTime za teste stanja_getBandColorsForTest() — vrne mapo BAND_COLORSbaseCall (10 testov)Preverja odstranjevanje pripon, ki se uporablja pri zaznavanju duplikatov in crosscheck poizvedbah.
/P, /M, /MM, /AM, /QRP se odstranijo z zadnjega mesta./1, /2) se odstranijo.OE/S59DGO) ostanejo nespremenjeni (hevristika: poševnica pred delom s številko = sufiks, sicer = predpona).normBand (10 testov)Preverja tabelo za mapiranje pasov.
144 MHz, 432 MHz), nize valovnih dolžin (2m, 70cm) in nize GHz (1.3 GHz).{band:'', freq:''}.locToLatLon (7 testov)Preverja pretvorbo Maidenhead lokatorja → zemljepisna širina/dolžina.
JN65VP → pribl. lat 45,5°S, lon 13,8°V (območje Ajdovščine).IO91wm → pribl. lat 51,3°S, lon −0,1°V (območje Londona).null.null (podprti so samo 6-znakovni).haversine (4 testi)Preverja izračun razdalje po velikem krogu.
JN65VP → JN58UD ≈ 320 km (±30).0.calcBearing (5 testov)Preverja izračun smeri po velikem krogu.
levenshtein (7 testov)Preverja funkcijo Levenshteinove razdalje z zgodnjim izhodom pri maxDist=2.
maxDist+1, ko razlika v dolžini sama presega maxDist (zgodnji izhod).isDupe (7 testov)Preverja zaznavanje duplikatov z normalizacijo baseCall() in parametrom excludeId.
/P → prav tako zaznan kot duplikat.excludeId preprečuje lažni duplikat pri preverjanju QSO, ki se ureja._current = null) → vedno vrne false.recalcDupes (4 testi)Preverja popolni preračun zastavic duplikatov v seji.
dupe=false; kasnejše → dupe=true./P se normalizira v bazni — šteje kot duplikat navadnega baznega klicnega znaka.dupe=false.recalcDupes je polje _current.qsos mutirano na mestu.buildEdi (57 testov)Preverja izhodni format REG1TEST EDI v1.
[REG1TEST;1].TDate uporablja polni YYYYMMDD;YYYYMMDD (začetek;konec); QSO zapisi uporabljajo YYMMDD.TName, PCall, PWWLo, PBand, PClub, PSect, MOpe1.SPowe, SAnte, STXEq, SRXEq, SAntH izpolnjene iz konfiguracije pasu.CQSOs, CQSOP, CWWLs, CWWLB, CExcs, CExcB, CDXCs, CDXCB, CToSc, CODXC — izračunani iz QSO-jev brez duplikatov.[QSORecords N] prisoten s pravilnim številom.D za podvojeni QSO, prazno za normalnega.nrS / nrR dopolnjeni z ničlami na 3 znake.WWL v vrstici QSO je 6 znakov z velikimi črkami.PClub izpolnjena iz session.club; prazna, ko ni nastavljena.SSB → 1, CW → 2, FM → 6.TCall, TLocator, RAZ, RClub se ne oddajo.PExch, PAdr1, PAdr2, RCall, RHBBS, RPoCo, RPhon — izpolnjene iz polj seje ali oddane kot prazne vrstice.TName pred TDate pred PCall; STXEq pred SPowe pred SRXEq.CODXC in [Remarks]; brez prazne vrstice med [Remarks] in [QSORecords;N].[END;S56OA HamLogTools VHF Logger] prisotno.lookupCall (6 testov)Preverja crosscheck poizvedbo v uteženi+raw baseline bazi.
found=true, modeLoc nastavljen na najpogostejši lokator./P → bazni klicni znak se pravilno poišče.found=false, polje similar izpolnjeno iz Levenshteinove iskanja.similar razvrščen po razdalji naraščajoče, nato po številu padajoče.found=false, similar=[].sessionEdit (10 testov)Preverja stanje in i18n pokritost za funkcijo urejanja seje.
_editingExisting se inicializira na false.btnEditSetup, setupEdit, btnSaveSetup, errBandHasQsos) so neprazni nizi.sl.setupEdit in en.setupEdit sta različna niza (prevod obstaja).backup (32 testov)Preverja strukturno validacijo validateBackup() in i18n nize za funkcijo backup/obnovi.
app, polje sessions, veljavne seje in QSO-ji) vrne polje sej.sessions je sprejemljivo.null za napačno polje app, manjkajoč app, sessions ki ni polje, null ali neovit niz.null, če id manjka ali je prazen, myCall manjka, bands ali qsos nista polji.myLoc seje mora biti veljaven 6-znakovni Maidenhead lokator ([A-R]{2}[0-9]{2}[A-X]{2}, neobčutljivo na velikost); vrne null, če manjka ali je napačen.contest seje mora biti neprazen niz; vrne null, če manjka ali je prazen niz.null, če v kateremkoli QSO manjka _id, band ali call.sl.btnRestore ≠ en.btnRestore (obstajata različna prevoda).sl.confirmRestore in en.confirmRestore vsebujeta ${n} placeholder.sl.toastRestoreDone in en.toastRestoreDone vsebujeta ${n} placeholder.I18N (3 testi)Preverja simetričnost i18n ključev med SL in EN prevodi.
adif-stats.test.js — 133 testov · 21 skupinPokriva čisto logiko adif-stats.html: iskanje DXCC predpon, normalizacijo pasu/načina, pretvorbo lokatorjev, izračun QRB, razčlenjevanje ADIF, agregacijo statistik, logiko filtrov, formatiranje datumov/mesecev, XSS ubežanje, pomočnike SVG grafikonov in popolnost i18n ključev.
adif-stats.html se izvede znotraj konteksta node:vm z enakim nadomestkom DOM kot ostala HTML orodja. Izvleče se samo blok <script>; minimalni nadomestek zagotavlja localStorage, document.getElementById, URL, Blob, setTimeout in console.
Čiste funkcije so dostopne neposredno kot lastnosti konteksta:
lookupCall, normBand, normMode, locToLatLon, haversine, parseADIF, computeStats, fmtDate, fmtMonth, htmlEsc, svgHBar, svgVBar, t.
Pomočnik adif() gradi minimalne ADIF fiksture z izračunom dolžin <TAG:dolžina>vrednost.
lookupCall (17 testov)Preverja iskanje DXCC entitete po predponi klicnega znaka v vgrajeni bazi PREFIX_DB.
EU; z mestom 9 ali 0 → AS./P odstranjena — S59DGO/P razrešen kot S59DGO.OE/ predpone — OE/S59DGO razrešen po predponi OE (Avstrija), ne S5.{ country: 'Unknown', cont: '?' }.Unknown.KL (Aljaska), CU (Azori), TK (Korzika), 9H (Malta), UA2 (Kaliningrad) se razrešijo pravilno kljub deljenim predponam z večjimi entitetami.normBand (5 testov)2M → 2m, 40M → 40m).null vrneta ''.normMode (5 testov)cw → CW).DIGI + FT8 → FT8).locToLatLon (5 testov)[lat, lon].JN65 → lat 45,5, lon 13.null in prekratek vnos vrneta null.haversine (5 testov)0.> 0.JN65 → FN20) > 6 000 km.null lokator → 0.parseADIF — basic extraction (9 testov)CALL izvlečen; BAND z malimi; MODE z velikimi — ne glede na vnos.SUBMODE preglasi MODE (DIGI + FT8 → način FT8).src nastavljen na ime datoteke.CALL tiho preskočeni; več zapisov razčlenjenih; ADIF brez <EOH> pravilno obravnavan; oznake neobčutljive na velikost.parseADIF — date/time/QRB (8 testov)QSO_DATE shranjen kot YYYYMMDD; ISO oblika (YYYY-MM-DD) normalizirana.''.TIME_ON HHMMSS okrnjen na HHMM; HHMM ohranjen.DISTANCE → qrb ko je prisotno.GRIDSQUARE + MY_GRIDSQUARE → qrb prek haversina ko DISTANCE manjka.DISTANCE ima prednost pred izračunom iz koordinat.parseADIF — country/cont (3 testi)S56OA → { country: 'Slovenia', cont: 'EU' }.JA1ZLO → { cont: 'AS' }.{ country: 'Unknown', cont: '?' }.computeStats — overview (7 testov)total=0, prazna množica calls.total šteje vse QSO-je.calls šteje unikatne klicne znake (isti klicni znak dvakrat = 1).dates šteje unikatne datume.bestDX sledi QSO z najvišjim qrb.firstDate / lastDate sledita kronoloŠkemu razponu.countries izključuje 'Unknown'.computeStats — aggregates (9 testov)byBand šteje QSO-je per pas; sledi unikatnim klicnim znakom in best DX per pas.byMode šteje QSO-je per način.byCont sledi unikatnim državam per kontinent.byHour je 24-elementno polje; pravilna reža naraščena po polju time.byMonth grupi po ključu YYYYMM.topCalls Map razvrščen padajoče po številu.'?') ni dodan v byCont.applyFilters — date filter (6 testov)Replicira logiko filtriranja datumskega obsega v izolaciji:
from ali to.from ali po to izključen.fmtDate (4 testi)'20240315' → '15.03.2024'.fmtMonth (3 testi)'202403' → '03/2024'; '202412' → '12/2024'.htmlEsc (7 testov)&, <, >, " ubežani v HTML entitete; navadno besedilo nespremenjeno.svgHBar (6 testov)<svg>.<svg> prisoten; več elementov ustvari palice <rect>.<rect> (preskočen z varovalko bw>0).colorFn apliciran na polnilo palice.svgVBar (6 testov)<svg>.<svg> in <rect>.<rect>.var(--muted).I18N (8 testov)t('secOver') vrne nepraznen prevedeni niz (ne sam ključ).secBand, secMode, secCont, secCountry, secTime, secTop) vsi vrnejo neprazne nize.cTotal, cUniq, cCountries, cDays, cBestDX, cDateRange) vsi neprazni.secDxcc, secHeatmap, secBandHour, secQrb, dxccTotal, dxccCount, dxccProg, qrbRange, qrbNoData) neprazni in se razlikujejo od imen ključev.hmapDow ima 7 |-ločenih krajšav dni; SL prvi = Po, zadnji = Ne.hmapMon ima 12 |-ločenih krajšav mesecev; prvi = Jan, zadnji = Dec.hmapMore je neprazen in se razlikuje od imen ključev.computeStats — byDay (3 testi)byDay Map šteje QSO-je per ključ YYYYMMDD.byDay.byDay.size === 3.computeStats — byBandHour (4 testi)byBandHour je 24-elementno polje.time ('1430' → indeks 14).time → ni vnosa pasu ustvarjenega v byBandHour.computeStats — byDxcc / byBandDxcc (5 testov)byDxcc šteje unikatna DXCC imena entitet; vsak vnos ima .qso in množico .bands.byDxcc.get('Germany').qso === 2.bands ima oba pasova.byBandDxcc Map: množica držav per pas; pravilna velikost per pas.'Unknown' izključena iz obeh byDxcc in byBandDxcc.computeStats — qrbBuckets (8 testov)qrbBuckets je 6-elementno polje (razredi 0–5).< 500 km): qrb 200 in 499 oba sem.500–1 000 km): qrb 500 in 999.1 000–2 000 km): qrb 1 000 in 1 999.2 000–5 000 km): qrb 2 000 in 4 999.5 000–10 000 km): qrb 5 000 in 9 999.≥ 10 000 km): qrb 10 000 in 15 000.qrb === 0 ne spade v noben razred (pomeni neznano razdaljo).adif2cab.test.js — 191 testov · 31 skupinPokriva čisto logiko adif2cab.html: preslikavo načinov Cabrillo, privzete RST, pretvorbo frekvence, razčlenjevanje ADIF, ekstrakcijo izmenjave per tekmovanje, formatiranje vrstic QSO, XSS ubežanje, pomočnike za značke, strukturo polja CONTESTS in celovitost i18n ključev.
adif2cab.html se izvede znotraj konteksta node:vm z enakim nadomestkom DOM na osnovi Proxy kot ostala HTML orodja. Izvleče se le blok <script>. Do čistih funkcij se dostopa neposredno kot do lastnosti konteksta.
Trije injicirani pomočniki omogočajo dostop do stanja modulne ravni (const/let):
_getContests() — vrne polje CONTESTS_getLangKeys(lang) — seznam vseh i18n ključev za podan jezik_getI18n(lang, key) — prebere vrednost iz i18n objekta SLokalni pomočnik adif() gradi testne nize ADIF s pravilnim kodiranjem <OZNAKA:dolžina>vrednost. Pomočnik makeQso() gradi minimalni objekt QSO s smiselnimi privzetimi vrednostmi.
modeToCAB — phone modes → PH (5 testov)SSB, USB, LSB, AM → PH.ssb normalizirane → PH.modeToCAB — CW → CW (2 testa)CW (velike in male) → CW.modeToCAB — FM-based voice → FM (5 testov)Cabrillo v3 specifikacija določa FM kot ločeno vrednost načina (ne PH).
FM, C4FM, DSTAR, DMR, DIGITALVOICE → FM.modeToCAB — RTTY → RY (2 testa)Cabrillo v3 specifikacija določa RY za RTTY (ne splošnega DG).
RTTY (velike in male) → RY.modeToCAB — digital modes → DG (8 testov)FT8, FT4, PSK31, JT65, JS8, WSPR → DG.null → DG (varni splošni primer).dfltRST (7 testov)PH in FM → '59' (dvomestni RST za foničke načine).CW, DG, RY → '599' (tromestni RST; RTTY po konvenciji tromestni).'59' (privzeto za foniko).freqToKHz — pretvorba polja FREQ (6 testov)14.210 MHz → 14210, 144.300 MHz → 144300, 432.200 MHz → 432200.FREQ '0' in FREQ '' padeta na BAND_KHZ.freqToKHz — rezervna vrednost BAND_KHZ (10 testov)20m → 14200, 2m → 144300, 40m → 7100, 70cm → 432200, 23cm → 1296100.6m → 50200, 4m → 70200.0. Brez polja fields deluje brez napake.parseADIF — osnovna ekstrakcija (8 testov)CALL normaliziran v velike črke, BAND v male, MODE v velike.RST_SENT → rstS, RST_RCVD → rstR; src nastavljen na ime datoteke.CALL tiho preskočeni; poljubna ADIF polja ohranjena v slovarju fields.parseADIF — normalizacija datuma/časa (7 testov)YYYYMMDD shranjen nespremenjen; ISO YYYY-MM-DD brez pomišljajev.DD.MM.YYYY; čas HHMMSS okrajšan na HHMM, prikazan kot HH:MM.QSO_DATE ali TIME_ON → prazen niz.parseADIF — večzapisni / robni primeri (11 testov)<EOH> sprejeta.<OZNAKA:dol:tip> podprt.STATION_CALLSIGN ohranjen v fields.fields so v velikih črkah.STX (oddana serijska številka WPX), SRX (sprejeta) in SRX_STRING ohranjeni v fields.extractExchR — CQ-WW-SSB (6 testov)CQZONE vrnjen ko je prisoten; beli prostor obrezan.CQZONE → SRX_STRING → SRX → ''.extractExchR — CQ-WW-CW (3 testi)CQZONE kot pri CQ-WW-SSB.extractExchR — IARU-HF (6 testov)ITUZ vrnjen ko je prisoten; beli prostor obrezan.SRX_STRING rezerva sprejme kratice HQ (npr. DARC). Vrstni red: ITUZ → SRX_STRING → SRX.extractExchR — ARRL-DX (6 testov)STATE vrnjen ko je prisoten; beli prostor obrezan. Vrstni red: STATE → SRX_STRING → SRX.extractExchR — GENERIC (5 testov)SRX_STRING ima prednost pred SRX. Neznan contestId se obnaša kot GENERIC.extractExchR — CQ-WW-RTTY (5 testov)CQZONE kot pri CQ-WW-SSB/CW.CQZONE → SRX_STRING → SRX → ''.extractExchR — IARU-VHF (7 testov)GRIDSQUARE vrnjen in pretvorjen v velike črke (ADIF shranjuje mešano, Cabrillo pričakuje velike).SRX_STRING in SRX prav tako v velikih črkah.GRIDSQUARE ima prednost pred SRX_STRING.extractExchR — CQ-WPX-SSB (4 testi)SRX_STRING vrnjen; rezerva SRX; SRX_STRING ima prednost.''.extractExchR — CQ-WPX-CW (3 testi)SRX_STRING/SRX kot pri CQ-WPX-SSB.formatCabDate (7 testov)YYYYMMDD → YYYY-MM-DD. null, undefined, prazen niz → '0000-00-00'.buildQSOLine — struktura (12 testov)QSO: in konča z ` 0` (ID oddajnika).7100 → ' 7100'); ničelna → ' 0'.144300 → ' 144300', 432200 → ' 432200', 1296100 → '1296100'.buildQSOLine — obdelava izmenjave (6 testov)exchS se uporabi ko je neprazna; privzeta vrednost glave zapolni pri exchS = ''.exchR vključen; prazna exchR ustvari zapolnjeno prazno polje brez napake.contest.exchW: 4 za CQ WW, 6 za IARU.buildQSOLine — privzeti RST (3 testi)rstS → dfltRST(cabMode) se uporabi (59 za PH, 599 za CW).rstS prepiše privzeto.buildQSOLine — izmenjava lokatorja IARU-VHF (4 testi)JN65AR) se natanko ujema z exchW:6.JN65) zapolnjen na 6 z repi presledkov.exchS) vključen v vrstico.exchR ustvari zapolnjeno prazno polje brez napake.buildQSOLine — per-QSO serijska številka CQ WPX (3 testi)exchS serijska številka se uporabi ko je nastavljena (npr. '001').exchW:6.exchS prazna.htmlEsc (9 testov)&, <, >, " ubežani v HTML entitete. Navadni nizi nespremenjeni.null in undefined → ''; števila pretvorjena v niz.cabModeBadge (6 testov)PH → badge-ssb; CW → badge-cw; FM, DG, RY, '' → badge-digi.modeBadge (9 testov)SSB, USB, LSB, AM → badge-ssb; CW → badge-cw; FM → badge-fm; FT8, RTTY, '' → badge-digi.CONTESTS — struktura (11 testov)exchW > 0.exchRcvdField pravilen per tekmovanje: CQ-WW-SSB/CQ-WW-CW/CQ-WW-RTTY → CQZONE, IARU-HF → ITUZ, IARU-VHF → GRIDSQUARE, ARRL-DX → STATE, CQ-WPX-SSB/CQ-WPX-CW/GENERIC → SRX_STRING.sl in en.I18N (4 testi)dropTitle v SL in EN sta različni (preverba, da so prevodi dejansko različni).edi-validator.test.js — 109 testov · 22 skupinPokriva čisto logiko edi-validator.html: Maidenhead geo pomočnike in celotno funkcijo validate() za vse preverbe specifikacije in tipe težav.
edi-validator.html se izvede znotraj konteksta node:vm z enakim nadomestkom DOM na osnovi Proxy kot ostala HTML orodja. Funkcija validate(text) je dostopna neposredno kot lastnost konteksta in sprejme surovo besedilo EDI datoteke ter vrne {issues[], qsoCount}.
I18n objekt S (const S = {sl:{...}, en:{...}}) se izvozi prek drugega klica vm.runInContext('globalThis._S = S', ctx). V testni datoteki je definirana minimalna veljavna EDI fikstura in se uporablja kot osnova za mutacijske teste (vsak test mutira en vidik fiksture, da sproži določeno kodo težave).
locToLatLon (4 testi)Preverja pretvorbo Maidenhead lokatorja → geografske koordinate.
JN65VP → pribl. lat 45,5°S, lon 13,8°V.null.haversine (2 testa)Preverja izračun razdalje po velikem krogu.
0.JN65VP → JN78DG ≈ 294 km (±30).validate — clean EDI (4 testi)Osnovna preverba z dobro oblikovano EDI fikstura.
qsoCount ustreza številu QSO vrstic v [QSORecords;N].[END;...] → iNoEnd (info).validate — structure (6 testov)Preverja obvezne razdelčne oznake in pravila za prazne vrstice.
[REG1TEST;1] → eNoReg1test (error).[Remarks] → eNoRemarks (error).[QSORecords;N] → eNoQsoSection (error).eBlankInHeader (error).[Remarks] → iBlankInRemarks (info, ne error).validate — non-spec keywords (6 testov)Preverja ključne besede, ki niso definirane v specifikaciji REG1TEST v1.
TCall, TLocator, RAZ, RClub, RBand → eNonSpecKw (error).eNonSpecKw.validate — header formats (8 testov)Preverja sintakso polj glave.
TDate=YYYYMMDD;YYYYMMDD — veljaven format, brez napake.TDate → eTDateFormat (error).TDate en sam datum (manjka drugi del) → eTDateFormat.PWWLo 4-znakovni lokator → wPwwloFormat (warn; prekratek za QRB izračun).PWWLo neveljavni znaki → wPwwloFormat.PBand → wPBandUnknown (warn).PBand 145 MHz (znana vrednost) → brez wPBandUnknown.iKwOrder (info).validate — ZRS mandatory fields (6 testov)Preverja, da obvezna polja ZRS niso prazna.
PSect, PClub, RName, RHBBS, SPowe → vsak wFieldEmpty (warn).wFieldEmpty.validate — QSO count (3 testi)Preverja deklarirano vs. dejansko število QSO.
eQsoCountMismatch.eQsoCountMismatch (error).eQsoCountMismatch.validate — QSO field count (3 testi)Preverja, da ima vsak zapis QSO natanko 15 polj, ločenih s podpičji.
eQsoFieldCount (error).eQsoFieldCount.eQsoFieldCount.validate — QSO date (6 testov)Preverja polje datuma QSO (YYMMDD pri stolpcu 0).
eQsoDate.eQsoDate (error).00 → eQsoDate.13 → eQsoDate.00 → eQsoDate.eQsoDate, a ne wQsoDateOutOfRange (preverba obsega je zatrta za strukturno neveljavne datume).validate — QSO time (4 testi)Preverja polje časa QSO (HHMM pri stolpcu 1).
eQsoTime.eQsoTime (error).24 → eQsoTime.60 → eQsoTime.validate — QSO mode (5 testov)Preverja polje načina QSO (stolpec 3, veljavne vrednosti 0–9; način 0 = “nobeden od spodaj” po spec).
0 → veljaven, brez napake (vrednost po spec “nobeden od spodaj”).1 in 9 → veljaven, brez napake.A (neštevilčen) → eQsoMode (error).10 (izven obsega) → eQsoMode.validate — QSO dupe flag (6 testov)Preverja polje zastavice duplikata QSO (stolpec 14, veljavne vrednosti prazno ali D).
D → brez napake.X (neveljavna vrednost) → eQsoDupe (error).d (mala črka) → eQsoDupe.D z neničelnim QRB → iDupeNonZeroQrb (info; duplikati tipično poročajo QRB 0).D z QRB 0 → brez iDupeNonZeroQrb.validate — QSO WWL format (4 testi)Preverja polje lokatorja QSO (stolpec 9).
wQsoWwlFormat (warn).validate — QRB deviation (5 testov)Preverja deklarirani QRB (stolpec 10) glede na haversinom izračunano razdaljo.
wQrbDeviation.wQrbDeviation (warn).PWWLo je 4-znakovni → QRB preverba preskočena (nezadostna natančnost lokatorja).wQsoQrbNum (warn); wQrbDeviation se ne sproži.validate — line length (2 testa)Preverja, da nobena vrstica ne presega 75 znakov.
wLineTooLong.wLineTooLong (warn).validate — non-ASCII characters (3 testi)Preverja znake izven 7-bitnega ASCII obsega (spec §15.3.4).
wNonAscii.wNonAscii (ne eNonAscii).warn (ne error).validate — duplicate keywords (4 testi)Preverja, da se vsaka ključna beseda glave pojavi največ enkrat.
wDuplicateKw.TDate → wDuplicateKw (warn).PCall → wDuplicateKw.TDate pomeni brez eTDateFormat, čeprav je ključna beseda podvojena.validate — QSO date day (5 testov)Preverja, da je vrednost dneva veljavna za dani mesec in leto (z upoštevanjem prestopnih let).
wQsoDateDay.wQsoDateDay.wQsoDateDay (warn).wQsoDateDay.wQsoDateDay.validate — QSO date range (5 testov)Preverja, da datumi QSO padejo znotraj obsega TDate (YYYYMMDD;YYYYMMDD) iz glave.
wQsoDateOutOfRange.wQsoDateOutOfRange (warn).wQsoDateOutOfRange.TDate v glavi → brez wQsoDateOutOfRange.TDate (ni razčlenljiv) → brez wQsoDateOutOfRange (obseg ni preverjen).validate — QSO RST format (13 testov)Preverja polji RST (stolpec 4 = oddani RST, stolpec 6 = sprejeti RST) glede na pričakovano število cifer za način. Pravila: SSB/AM/FM (načini 1/4/5/6) → 2 cifri; CW/RTTY (načini 2/3/7) → 3 cifre; načini 0/8/9 → preverba preskočena. Prazen RST je vedno dovoljen.
59 → brez wQsoRstFormat.599 → brez wQsoRstFormat.599 (3 cifre) → wQsoRstFormat (warn).59 (2 cifri) → wQsoRstFormat.59 → brez wQsoRstFormat.599 → brez wQsoRstFormat.wQsoRstFormat (preverba preskočena).wQsoRstFormat (dovoljeno po spec).59 → brez wQsoRstFormat.599 → brez wQsoRstFormat.59 (2 cifri) → wQsoRstFormat.59 → brez wQsoRstFormat.599 (3 cifre) → wQsoRstFormat.I18N (5 testov)Preverja celovitost i18n ključev in razlikovanje prevodov.
sl.sevError in en.sevError sta neprazna niza.sl.sevError ≠ en.sevError (prevoda sta različna).| Področje | Razlog |
|---|---|
handleFiles |
Zahteva asinhroni brskalniški FileReader; ni nadomestljiv v čistem vm kontekstu. |
finishLoad / funkcije za posodobitev DOM |
Kličejo .style, .innerHTML itd. na resničnih vozliščih DOM; smiselno le v brskalniku. |
Izvozne funkcije (exportADIF, exportDARC, exportCSV) |
Odvisne od stanja _all, potrditvenih polj DOM in Blob/URL.createObjectURL. Potrebni bi bili celostni brskalniški testi (npr. Playwright). |
startEdit / restoreCell / commitEdit (DOM del) |
Upravljanje z dejanskimi vozlišči TD; testabilno le z jsdom ali Playwright. Logika validacije je testirana v skupini 8. |
Razvrščanje (sortFiltered, setSort) |
Odvisno od stanja _filtered; testabilno le s celotno nastavitvijo stanja. |