diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx index ea5a571..3589fc6 100644 --- a/src/app/search/page.tsx +++ b/src/app/search/page.tsx @@ -21,6 +21,7 @@ import LotwSyncIndicator from '@/components/LotwSyncIndicator'; import QRZSyncIndicator from '@/components/QRZSyncIndicator'; import DynamicContactMap from '@/components/DynamicContactMap'; import { useUser } from '@/contexts/UserContext'; +import { AMATEUR_BANDS } from '@/lib/bands'; interface Contact { id: number; @@ -81,7 +82,13 @@ interface PaginationInfo { } const MODES = ['AM', 'FM', 'FT8', 'MFSK', 'RTTY', 'SSB', 'CW', 'FT4', 'PSK31', 'DMR', 'DSTAR', 'YSF']; -const BANDS = ['2m', '6m', '10m', '12m', '15m', '17m', '20m', '30m', '40m', '60m', '80m', '160m', '70cm', '23cm']; +// Band options come from the canonical band plan (@/lib/bands) — the same list +// the logging forms (new-contact, QuickLogCard) store — so every band an +// operator can log is also filterable here. The previous hard-coded lowercase +// list had drifted and silently omitted 2200M, 630M, 4M, 1.25M, 33CM and 13CM, +// making QSOs on those bands unsearchable. Matching is case-insensitive on the +// server (UPPER(band) = UPPER($n)), so the uppercase values match stored data. +const BANDS = AMATEUR_BANDS; interface FilterChip { key: keyof SearchFilters; diff --git a/tests/bands.spec.ts b/tests/bands.spec.ts index 4090854..f5f7000 100644 --- a/tests/bands.spec.ts +++ b/tests/bands.spec.ts @@ -46,4 +46,24 @@ test.describe('bands module', () => { expect(frequencyToBand(100.0)).toBe('OTHER'); expect(AMATEUR_BANDS).not.toContain('OTHER'); }); + + test('covers every band the search filter must offer, including the six the old list dropped', () => { + // The contact-search band dropdown sources its options from AMATEUR_BANDS + // (the same list the logging forms store), so an operator can always filter + // for a band they were able to log. It previously hard-coded a shorter + // lowercase list that omitted these six — a 4M or 630M QSO could be logged + // from a one-click pill but never filtered back out in search. + const previouslyUnfilterable = ['2200M', '630M', '4M', '1.25M', '33CM', '13CM']; + for (const band of previouslyUnfilterable) { + expect(AMATEUR_BANDS).toContain(band); + } + // And the bands the old list did have are still present, so nothing regressed. + const legacySearchBands = [ + '2M', '6M', '10M', '12M', '15M', '17M', '20M', '30M', '40M', '60M', '80M', + '160M', '70CM', '23CM', + ]; + for (const band of legacySearchBands) { + expect(AMATEUR_BANDS).toContain(band); + } + }); });