mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-04-27 15:05:56 +00:00
352 lines
13 KiB
JavaScript
352 lines
13 KiB
JavaScript
// SPDX-License-Identifier: 0BSD
|
|
|
|
import { mount } from "@vue/test-utils";
|
|
import { describe, it, expect } from "vitest";
|
|
import MapDrawingToolbar from "@/components/map/internal/MapDrawingToolbar.vue";
|
|
import MapBearingInstructions from "@/components/map/internal/MapBearingInstructions.vue";
|
|
import MapSearchBar from "@/components/map/internal/MapSearchBar.vue";
|
|
import MapExportInstructions from "@/components/map/internal/MapExportInstructions.vue";
|
|
import MapNoMapWarning from "@/components/map/internal/MapNoMapWarning.vue";
|
|
import MapLoadingOverlay from "@/components/map/internal/MapLoadingOverlay.vue";
|
|
import MapExportConfigPanel from "@/components/map/internal/MapExportConfigPanel.vue";
|
|
import MapExportProgressPanel from "@/components/map/internal/MapExportProgressPanel.vue";
|
|
import MapClusterPanel from "@/components/map/internal/MapClusterPanel.vue";
|
|
import MapMarkerPanel from "@/components/map/internal/MapMarkerPanel.vue";
|
|
import MapVectorExchangePanel from "@/components/map/internal/MapVectorExchangePanel.vue";
|
|
|
|
const DRAWING_TOOLS = [
|
|
{ type: "Select", icon: "cursor-default" },
|
|
{ type: "Point", icon: "map-marker-plus" },
|
|
{ type: "LineString", icon: "vector-line" },
|
|
{ type: "Polygon", icon: "vector-polygon" },
|
|
{ type: "Circle", icon: "circle-outline" },
|
|
{ type: "Export", icon: "crop-free" },
|
|
];
|
|
|
|
function t(key) {
|
|
return key;
|
|
}
|
|
|
|
describe("MapDrawingToolbar", () => {
|
|
it("emits toggle-bearing and bearing-from-here", async () => {
|
|
const wrapper = mount(MapDrawingToolbar, {
|
|
props: {
|
|
tools: DRAWING_TOOLS,
|
|
drawType: null,
|
|
measuring: false,
|
|
bearingMode: false,
|
|
bearingFromGps: false,
|
|
exportMode: false,
|
|
selectedFeature: null,
|
|
},
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
await wrapper.find('button[title="map.tool_bearing"]').trigger("click");
|
|
expect(wrapper.emitted("toggle-bearing")).toHaveLength(1);
|
|
await wrapper.find('button[title="map.tool_bearing_from_here"]').trigger("click");
|
|
expect(wrapper.emitted("bearing-from-here")).toHaveLength(1);
|
|
});
|
|
|
|
it("applies bearing highlight classes when bearingMode is true", () => {
|
|
const wrapper = mount(MapDrawingToolbar, {
|
|
props: {
|
|
tools: DRAWING_TOOLS,
|
|
bearingMode: true,
|
|
bearingFromGps: false,
|
|
measuring: false,
|
|
exportMode: false,
|
|
selectedFeature: null,
|
|
},
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
const bearingBtn = wrapper.find('button[title="map.tool_bearing"]');
|
|
expect(bearingBtn.classes().some((c) => c.includes("teal"))).toBe(true);
|
|
});
|
|
|
|
it("emits toggle-measure and toggle-draw", async () => {
|
|
const wrapper = mount(MapDrawingToolbar, {
|
|
props: {
|
|
tools: DRAWING_TOOLS,
|
|
bearingMode: false,
|
|
measuring: false,
|
|
exportMode: false,
|
|
selectedFeature: null,
|
|
},
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
await wrapper.find('button[title="map.tool_measure"]').trigger("click");
|
|
expect(wrapper.emitted("toggle-measure")).toHaveLength(1);
|
|
await wrapper.find('button[title="map.tool_point"]').trigger("click");
|
|
expect(wrapper.emitted("toggle-draw")).toEqual([["Point"]]);
|
|
});
|
|
});
|
|
|
|
describe("MapBearingInstructions", () => {
|
|
it("shows first-hint copy and emits use-my-location", async () => {
|
|
const wrapper = mount(MapBearingInstructions, {
|
|
props: { fromGpsActive: false, awaitingSecondTap: false },
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
expect(wrapper.text()).toContain("map.bearing_hint_first");
|
|
await wrapper.find("button").trigger("click");
|
|
expect(wrapper.emitted("use-my-location")).toHaveLength(1);
|
|
});
|
|
|
|
it("shows destination hint when fromGpsActive", () => {
|
|
const wrapper = mount(MapBearingInstructions, {
|
|
props: { fromGpsActive: true, awaitingSecondTap: true },
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
expect(wrapper.text()).toContain("map.bearing_hint_destination");
|
|
expect(wrapper.find("button").exists()).toBe(false);
|
|
});
|
|
|
|
it("shows second-tap hint for two-point mode", () => {
|
|
const wrapper = mount(MapBearingInstructions, {
|
|
props: { fromGpsActive: false, awaitingSecondTap: true },
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
expect(wrapper.text()).toContain("map.bearing_hint_second");
|
|
});
|
|
});
|
|
|
|
describe("MapSearchBar", () => {
|
|
it("updates modelValue, search, clear, and select", async () => {
|
|
const wrapper = mount(MapSearchBar, {
|
|
props: {
|
|
modelValue: "",
|
|
results: [{ display_name: "Somewhere", type: "city" }],
|
|
error: null,
|
|
searching: false,
|
|
showResults: true,
|
|
},
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
const input = wrapper.find("input");
|
|
await input.setValue("q");
|
|
expect(wrapper.emitted("update:modelValue")).toEqual([["q"]]);
|
|
await wrapper.setProps({ modelValue: "q" });
|
|
await input.trigger("keydown.enter");
|
|
expect(wrapper.emitted("search")).toHaveLength(1);
|
|
await wrapper.findAll("button").at(0).trigger("click");
|
|
expect(wrapper.emitted("clear")).toHaveLength(1);
|
|
});
|
|
|
|
it("renders error state when error is set", () => {
|
|
const wrapper = mount(MapSearchBar, {
|
|
props: {
|
|
modelValue: "x",
|
|
results: [],
|
|
error: "map.search_error",
|
|
searching: false,
|
|
showResults: true,
|
|
},
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
expect(wrapper.text()).toContain("map.search_error");
|
|
});
|
|
|
|
it("emits select when a result row is clicked", async () => {
|
|
const row = { display_name: "A", type: "town" };
|
|
const wrapper = mount(MapSearchBar, {
|
|
props: {
|
|
modelValue: "a",
|
|
results: [row],
|
|
showResults: true,
|
|
},
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
await wrapper.find("button.w-full").trigger("click");
|
|
expect(wrapper.emitted("select")).toEqual([[row]]);
|
|
});
|
|
});
|
|
|
|
describe("MapExportInstructions", () => {
|
|
it("emits select-preset for a preset button", async () => {
|
|
const presets = [{ id: "europe" }];
|
|
const wrapper = mount(MapExportInstructions, {
|
|
props: { presets },
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
await wrapper.find("button").trigger("click");
|
|
expect(wrapper.emitted("select-preset")).toEqual([[presets[0]]]);
|
|
});
|
|
});
|
|
|
|
describe("MapNoMapWarning", () => {
|
|
it("emits upload", async () => {
|
|
const wrapper = mount(MapNoMapWarning, {
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
await wrapper.find("button").trigger("click");
|
|
expect(wrapper.emitted("upload")).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("MapLoadingOverlay", () => {
|
|
it("shows custom message when provided", () => {
|
|
const wrapper = mount(MapLoadingOverlay, {
|
|
props: { message: "custom" },
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
expect(wrapper.text()).toContain("custom");
|
|
});
|
|
|
|
it("falls back to map.uploading when message is null", () => {
|
|
const wrapper = mount(MapLoadingOverlay, {
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
expect(wrapper.text()).toContain("map.uploading");
|
|
});
|
|
});
|
|
|
|
describe("MapExportConfigPanel", () => {
|
|
it("emits update:minZoom and start", async () => {
|
|
const wrapper = mount(MapExportConfigPanel, {
|
|
props: { minZoom: 5, maxZoom: 10, estimatedTiles: 100, exporting: false, tileLimitExceeded: false },
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
const inputs = wrapper.findAll('input[type="number"]');
|
|
await inputs.at(0).setValue(6);
|
|
expect(wrapper.emitted("update:minZoom")).toEqual([[6]]);
|
|
await wrapper
|
|
.findAll("button")
|
|
.filter((b) => b.text().includes("map.start_export"))
|
|
.at(0)
|
|
.trigger("click");
|
|
expect(wrapper.emitted("start")).toHaveLength(1);
|
|
});
|
|
|
|
it("disables start when tileLimitExceeded", () => {
|
|
const wrapper = mount(MapExportConfigPanel, {
|
|
props: {
|
|
minZoom: 0,
|
|
maxZoom: 20,
|
|
estimatedTiles: 9999999,
|
|
tileLimitExceeded: true,
|
|
},
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
const startBtn = wrapper
|
|
.findAll("button")
|
|
.filter((b) => b.text().includes("map.start_export"))
|
|
.at(0);
|
|
expect(startBtn.attributes("disabled")).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe("MapExportProgressPanel", () => {
|
|
it("shows progress for running export", () => {
|
|
const wrapper = mount(MapExportProgressPanel, {
|
|
props: {
|
|
status: { status: "running", progress: 40, current: 4, total: 10 },
|
|
exportId: "e1",
|
|
},
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
expect(wrapper.text()).toContain("map.exporting");
|
|
expect(wrapper.text()).toContain("40%");
|
|
});
|
|
|
|
it("emits dismiss when completed", async () => {
|
|
const wrapper = mount(MapExportProgressPanel, {
|
|
props: {
|
|
status: { status: "completed", progress: 100, current: 10, total: 10 },
|
|
exportId: "x",
|
|
},
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
expect(wrapper.text()).toContain("map.download_ready");
|
|
const link = wrapper.find('a[href="/api/v1/map/export/x/download"]');
|
|
expect(link.exists()).toBe(true);
|
|
await wrapper.find("button.text-gray-400").trigger("click");
|
|
expect(wrapper.emitted("dismiss")).toHaveLength(1);
|
|
});
|
|
|
|
it("emits cancel while running", async () => {
|
|
const wrapper = mount(MapExportProgressPanel, {
|
|
props: {
|
|
status: { status: "running", progress: 1, current: 1, total: 99 },
|
|
},
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
await wrapper.find("button.text-red-500").trigger("click");
|
|
expect(wrapper.emitted("cancel")).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("MapClusterPanel", () => {
|
|
it("emits close and select", async () => {
|
|
const cluster = {
|
|
count: 2,
|
|
items: [
|
|
{
|
|
kind: "telemetry",
|
|
label: "Peer",
|
|
identifier: "abc",
|
|
peer: { lxmf_user_icon: { icon_name: "account" } },
|
|
},
|
|
],
|
|
};
|
|
const wrapper = mount(MapClusterPanel, {
|
|
props: { cluster },
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
expect(wrapper.text()).toContain("2");
|
|
await wrapper.find('button[title="Close"]').trigger("click");
|
|
expect(wrapper.emitted("close")).toHaveLength(1);
|
|
await wrapper.find("button.w-full").trigger("click");
|
|
expect(wrapper.emitted("select")).toEqual([[cluster.items[0]]]);
|
|
});
|
|
});
|
|
|
|
describe("MapMarkerPanel", () => {
|
|
it("renders discovered node and emits close", async () => {
|
|
const marker = {
|
|
discovered: {
|
|
name: "NodeA",
|
|
latitude: 1.2,
|
|
longitude: 3.4,
|
|
interface: "eth0",
|
|
},
|
|
};
|
|
const wrapper = mount(MapMarkerPanel, {
|
|
props: { marker },
|
|
global: {
|
|
mocks: { $t: t },
|
|
stubs: { MiniChat: { template: "<div class='mini-chat-stub'></div>" } },
|
|
},
|
|
});
|
|
expect(wrapper.text()).toContain("NodeA");
|
|
expect(wrapper.text()).toContain("1.200000");
|
|
await wrapper.find("button.text-gray-500").trigger("click");
|
|
expect(wrapper.emitted("close")).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("MapVectorExchangePanel", () => {
|
|
it("emits export-geojson when export button is clicked", async () => {
|
|
const wrapper = mount(MapVectorExchangePanel, {
|
|
props: { disabled: false, hasFeatures: true },
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
const exportBtn = wrapper
|
|
.findAll("button")
|
|
.filter((b) => b.text().includes("map.vector_export_geojson"))
|
|
.at(0);
|
|
await exportBtn.trigger("click");
|
|
expect(wrapper.emitted("export-geojson")).toHaveLength(1);
|
|
});
|
|
|
|
it("toggle merge checkbox changes mergeImport", async () => {
|
|
const wrapper = mount(MapVectorExchangePanel, {
|
|
props: { hasFeatures: false },
|
|
global: { mocks: { $t: t } },
|
|
});
|
|
expect(wrapper.vm.mergeImport).toBe(true);
|
|
const cb = wrapper.find('input[type="checkbox"]');
|
|
await cb.setValue(false);
|
|
expect(wrapper.vm.mergeImport).toBe(false);
|
|
});
|
|
});
|