104 lines
3.9 KiB
TypeScript
104 lines
3.9 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
||
import { generateAnytoneContactsCsv, generateAnytoneChannelsCsv, ANYTONE_CH_HEADER } from "./cps-anytone";
|
||
import type { Repeater, CpsTalkgroup } from "./types";
|
||
|
||
const makeRepeater = (overrides: Partial<Repeater> = {}): Repeater => ({
|
||
id: 1, callsign: "DB0ABC", freq_tx: 439.5, freq_rx: 431.9,
|
||
freq_offset: "-7.6", band: "70cm", lat: 52.5, lng: 13.4,
|
||
city: "Berlin", state: "Berlin", country: "Germany", color_code: 1,
|
||
ts_linked: "1,2", trustee: "DL1ABC", ipsc_network: "BrandMeister",
|
||
networks: ["BrandMeister"], hotspot: 0, status: "On-air",
|
||
last_seen: null, bm_status: 1, bm_status_text: "Connected",
|
||
hardware: "", firmware: "", pep: 0, agl: 0, website: "", description: "",
|
||
import_freq_inconsistent: false, inactive: false, last_polled: null,
|
||
...overrides,
|
||
});
|
||
|
||
describe("generateAnytoneContactsCsv", () => {
|
||
const tgs: CpsTalkgroup[] = [
|
||
{ id: 262, name: "TG262 Deutschlan", slot: "1" },
|
||
{ id: 2628, name: "TG2628 Bayern", slot: "1" },
|
||
];
|
||
|
||
it("generates CSV with correct header", () => {
|
||
const csv = generateAnytoneContactsCsv(tgs);
|
||
const lines = csv.split("\r\n");
|
||
expect(lines[0]).toBe("No.,Radio ID,Name,Call Type,Call Alert");
|
||
});
|
||
|
||
it("generates correct data rows", () => {
|
||
const csv = generateAnytoneContactsCsv(tgs);
|
||
const lines = csv.split("\r\n");
|
||
expect(lines).toHaveLength(3); // header + 2 rows
|
||
expect(lines[1]).toBe("1,262,TG262 Deutschlan,Group Call,None");
|
||
expect(lines[2]).toBe("2,2628,TG2628 Bayern,Group Call,None");
|
||
});
|
||
|
||
it("uses TG ID as fallback name", () => {
|
||
const tg: CpsTalkgroup[] = [{ id: 99999, name: "", slot: "1" }];
|
||
const csv = generateAnytoneContactsCsv(tg);
|
||
expect(csv).toContain("TG99999");
|
||
});
|
||
|
||
it("has 5 columns per row", () => {
|
||
const csv = generateAnytoneContactsCsv(tgs);
|
||
const lines = csv.split("\r\n");
|
||
for (const line of lines) {
|
||
expect(line.split(",")).toHaveLength(5);
|
||
}
|
||
});
|
||
});
|
||
|
||
describe("generateAnytoneChannelsCsv", () => {
|
||
const tgs: CpsTalkgroup[] = [
|
||
{ id: 262, name: "TG262", slot: "1" },
|
||
];
|
||
const repeaters = [makeRepeater()];
|
||
|
||
it("generates CSV with correct header", () => {
|
||
const csv = generateAnytoneChannelsCsv(repeaters, tgs);
|
||
const lines = csv.split("\r\n");
|
||
expect(lines[0]).toBe(ANYTONE_CH_HEADER);
|
||
});
|
||
|
||
it("creates one row per repeater × talkgroup", () => {
|
||
const tgs2: CpsTalkgroup[] = [
|
||
{ id: 262, name: "TG262", slot: "1" },
|
||
{ id: 2628, name: "TG2628", slot: "2" },
|
||
];
|
||
const csv = generateAnytoneChannelsCsv(repeaters, tgs2);
|
||
const lines = csv.split("\r\n");
|
||
expect(lines).toHaveLength(3); // header + 2 channels
|
||
});
|
||
|
||
it("has 55 columns per row (matching header)", () => {
|
||
const csv = generateAnytoneChannelsCsv(repeaters, tgs);
|
||
const lines = csv.split("\r\n");
|
||
const headerCols = lines[0].split(",").length;
|
||
expect(headerCols).toBe(55);
|
||
for (let i = 1; i < lines.length; i++) {
|
||
expect(lines[i].split(",")).toHaveLength(headerCols);
|
||
}
|
||
});
|
||
|
||
it("includes correct frequencies", () => {
|
||
const csv = generateAnytoneChannelsCsv(repeaters, tgs);
|
||
expect(csv).toContain("439.500000");
|
||
expect(csv).toContain("431.900000");
|
||
});
|
||
|
||
it("includes color code and timeslot", () => {
|
||
const csv = generateAnytoneChannelsCsv(repeaters, tgs);
|
||
const lines = csv.split("\r\n");
|
||
const cols = lines[1].split(",");
|
||
// Color code at index 20, slot at index 21
|
||
expect(cols[20]).toBe("1");
|
||
expect(cols[21]).toBe("1");
|
||
});
|
||
});
|
||
|
||
describe("ANYTONE_CH_HEADER", () => {
|
||
it("has 55 columns", () => {
|
||
expect(ANYTONE_CH_HEADER.split(",")).toHaveLength(55);
|
||
});
|
||
});
|