ui_migration test(store) add tests for wot, and settings
This commit is contained in:
parent
674e52e224
commit
4f1938696a
2 changed files with 327 additions and 0 deletions
83
webapp/src/tests/unit/stores/settings.spec.js
Normal file
83
webapp/src/tests/unit/stores/settings.spec.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import { setActivePinia, createPinia } from "pinia";
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { useSettingsStore } from "../../../stores/settings";
|
||||
|
||||
describe("Settings Store", () => {
|
||||
beforeEach(() => {
|
||||
// Mock the browser's window.location BEFORE the store initializes
|
||||
// This prevents the getOriginFromLocation() function from crashing.
|
||||
vi.stubGlobal("location", {
|
||||
// The following basUri is arbitrary set
|
||||
href: "http://microscope-test.local:5000/",
|
||||
});
|
||||
|
||||
// Create a fresh Pinia instance for every single test
|
||||
// This guarantees state doesn't leak between the 'it' blocks.
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
describe("Initialization", () => {
|
||||
it("parses the baseUri correctly from window.location", () => {
|
||||
const store = useSettingsStore();
|
||||
// It should extract the origin and append /api/v3
|
||||
expect(store.baseUri).toBe("http://microscope-test.local:5000/api/v3");
|
||||
});
|
||||
|
||||
it("initializes with correct default state", () => {
|
||||
const store = useSettingsStore();
|
||||
expect(store.ready).toBe(false);
|
||||
expect(store.waiting).toBe(false);
|
||||
expect(store.appTheme).toBe("system");
|
||||
expect(store.activeStreams).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Actions", () => {
|
||||
it("setConnected() updates waiting and ready states", () => {
|
||||
const store = useSettingsStore();
|
||||
|
||||
// Mutate state to ensure setConnected overrides it
|
||||
store.waiting = true;
|
||||
store.ready = false;
|
||||
|
||||
store.setConnected();
|
||||
|
||||
expect(store.waiting).toBe(false);
|
||||
expect(store.ready).toBe(true);
|
||||
});
|
||||
|
||||
it("resetState() clears connection status and sets error", () => {
|
||||
const store = useSettingsStore();
|
||||
|
||||
// Mutate state
|
||||
store.waiting = true;
|
||||
store.ready = true;
|
||||
store.error = "";
|
||||
|
||||
store.resetState();
|
||||
|
||||
expect(store.waiting).toBe(false);
|
||||
expect(store.ready).toBe(false);
|
||||
expect(store.error).toBe("Microscope is not connected.");
|
||||
});
|
||||
|
||||
it("addStream() adds a stream ID to activeStreams", () => {
|
||||
const store = useSettingsStore();
|
||||
|
||||
store.addStream("mjpeg_stream");
|
||||
|
||||
expect(store.activeStreams["mjpeg_stream"]).toBe(true);
|
||||
});
|
||||
|
||||
it("removeStream() sets a stream ID to false in activeStreams", () => {
|
||||
const store = useSettingsStore();
|
||||
|
||||
// Setup initial state
|
||||
store.activeStreams = { mjpeg_stream: true };
|
||||
|
||||
store.removeStream("mjpeg_stream");
|
||||
|
||||
expect(store.activeStreams["mjpeg_stream"]).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
244
webapp/src/tests/unit/stores/wot.spec.js
Normal file
244
webapp/src/tests/unit/stores/wot.spec.js
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
import { setActivePinia, createPinia } from "pinia";
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import axios from "axios";
|
||||
import { useWotStore, findFormHref } from "../../../stores/wot";
|
||||
|
||||
// Mock Axios
|
||||
vi.mock("axios");
|
||||
|
||||
describe("WoT Store", () => {
|
||||
// A reusable dummy Thing Description (TD)
|
||||
// This will be reworked by using the HAR fixtures or the fixtures/common_definitions.js
|
||||
// This test uses arbitrary values for simplicity but this could add confusion
|
||||
// How-to-test documentation would require a glossary of defined mock values
|
||||
const mockTD = {
|
||||
base: "http://microscope.local/api/v3/",
|
||||
properties: {
|
||||
status: {
|
||||
forms: [{ op: ["readproperty", "writeproperty"], href: "status" }],
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
toggle: {
|
||||
forms: [{ op: "invokeaction", href: "http://external.com/api/v3/toggle" }],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// New Pinia for every test to avoid leaks and inter-playing states
|
||||
// Clear axios call history between tests
|
||||
setActivePinia(createPinia());
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("Standalone Helper: findFormHref", () => {
|
||||
it("returns undefined if affordance is missing", () => {
|
||||
expect(findFormHref(undefined, "read")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("finds href when op is an array", () => {
|
||||
const affordance = mockTD.properties.status;
|
||||
expect(findFormHref(affordance, "readproperty")).toBe("status");
|
||||
});
|
||||
|
||||
it("finds href when op is a string", () => {
|
||||
const affordance = mockTD.actions.toggle;
|
||||
expect(findFormHref(affordance, "invokeaction")).toBe("http://external.com/api/v3/toggle");
|
||||
});
|
||||
|
||||
it("returns undefined if op does not match", () => {
|
||||
const affordance = mockTD.properties.status;
|
||||
expect(findFormHref(affordance, "fakeoperation")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("State & Basic Actions", () => {
|
||||
it("adds, removes, and clears thing descriptions", () => {
|
||||
const store = useWotStore();
|
||||
|
||||
store.addThingDescription("camera", mockTD);
|
||||
expect(store.thingDescriptions["camera"]).toEqual(mockTD);
|
||||
expect(store.thingList).toContain("camera");
|
||||
|
||||
store.removeThingDescription("camera");
|
||||
expect(store.thingDescriptions["camera"]).toBeUndefined();
|
||||
expect(store.thingList.length).toBe(0);
|
||||
|
||||
// Test clear all
|
||||
store.addThingDescription("cam1", mockTD);
|
||||
store.addThingDescription("cam2", mockTD);
|
||||
store.removeAllThingDescriptions();
|
||||
expect(store.thingList.length).toBe(0);
|
||||
});
|
||||
|
||||
it("correctly identifies if a thing is available", () => {
|
||||
const store = useWotStore();
|
||||
store.addThingDescription("camera", mockTD);
|
||||
|
||||
expect(store.thingAvailable("camera")).toBe(true);
|
||||
expect(store.thingAvailable("stage")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Network Actions (Axios)", () => {
|
||||
it("fetches a single Thing Description WITH a provided name", async () => {
|
||||
const store = useWotStore();
|
||||
axios.get.mockResolvedValueOnce({ data: mockTD });
|
||||
|
||||
await store.fetchThingDescription("http://microscope.local/api/v3/camera", "myCamera");
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith("http://microscope.local/api/v3/camera");
|
||||
expect(store.thingDescriptions["myCamera"]).toEqual(mockTD);
|
||||
});
|
||||
|
||||
it("fetches a single Thing Description and extracts name from URI if missing", async () => {
|
||||
const store = useWotStore();
|
||||
axios.get.mockResolvedValueOnce({ data: mockTD });
|
||||
|
||||
// The logic should split by "/" and grab "stage"
|
||||
await store.fetchThingDescription("http://microscope.local/api/v3/stage/");
|
||||
|
||||
expect(store.thingDescriptions["stage"]).toEqual(mockTD);
|
||||
});
|
||||
|
||||
it("fetches multiple Thing Descriptions", async () => {
|
||||
const store = useWotStore();
|
||||
axios.get.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
data: {
|
||||
"/sensor/": { title: "Sensor TD" },
|
||||
motor: { title: "Motor TD" },
|
||||
},
|
||||
});
|
||||
|
||||
await store.fetchThingDescriptions("http://microscope.local/api/v3/things");
|
||||
|
||||
// The logic strips slashes: "/sensor/" -> "sensor"
|
||||
expect(store.thingDescriptions["sensor"].title).toBe("Sensor TD");
|
||||
expect(store.thingDescriptions["motor"].title).toBe("Motor TD");
|
||||
});
|
||||
|
||||
it("throws an error if fetchThingDescriptions fails", async () => {
|
||||
const store = useWotStore();
|
||||
axios.get.mockResolvedValueOnce({ status: 404 });
|
||||
|
||||
await expect(store.fetchThingDescriptions("http://bad-url")).rejects.toMatch(
|
||||
"Could not retrieve",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("TD Parsing & URL Generation", () => {
|
||||
beforeEach(() => {
|
||||
const store = useWotStore();
|
||||
const mockTD = {
|
||||
base: "http://microscope.local/api/v3/",
|
||||
properties: {
|
||||
status: {
|
||||
forms: [{ op: ["readproperty", "writeproperty"], href: "status" }],
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
toggle: {
|
||||
forms: [{ op: "invokeaction", href: "http://external.com/api/v3/toggle" }],
|
||||
},
|
||||
},
|
||||
};
|
||||
store.addThingDescription("microscope", mockTD);
|
||||
});
|
||||
|
||||
it("checks affordance availability (and covers Line 55)", () => {
|
||||
const store = useWotStore();
|
||||
|
||||
// Success case
|
||||
expect(store.thingAffordanceAvailable("microscope", "properties", "status")).toBe(true);
|
||||
|
||||
// Missing Thing (!td)
|
||||
expect(store.thingAffordanceAvailable("fakeThing", "properties", "status")).toBe(false);
|
||||
|
||||
// Missing Property inside an existing Category
|
||||
expect(store.thingAffordanceAvailable("microscope", "properties", "fakeProp")).toBe(false);
|
||||
|
||||
// Thing exists, but the Category/AffordanceType ('events') does not exist at all!
|
||||
expect(store.thingAffordanceAvailable("microscope", "events", "someEvent")).toBe(false);
|
||||
});
|
||||
|
||||
it("handles completely missing forms, affordances, and operations", () => {
|
||||
const store = useWotStore();
|
||||
|
||||
// Missing Thing entirely
|
||||
expect(
|
||||
store.thingFormUrl("fakeThing", "properties", "missing", "read", true),
|
||||
).toBeUndefined();
|
||||
expect(() => {
|
||||
store.thingFormUrl("fakeThing", "properties", "missing", "read", false);
|
||||
}).toThrowError(/Could not find form/);
|
||||
|
||||
// The 'microscope' exists, but 'fakeProp' does not.
|
||||
expect(
|
||||
store.thingFormUrl("microscope", "properties", "fakeProp", "read", true),
|
||||
).toBeUndefined();
|
||||
expect(() => {
|
||||
store.thingFormUrl("microscope", "properties", "fakeProp", "read", false);
|
||||
}).toThrowError(/Could not find form/);
|
||||
|
||||
// 'microscope' and 'status' exist, but 'fakeOp' is missing.
|
||||
expect(
|
||||
store.thingFormUrl("microscope", "properties", "status", "fakeOp", true),
|
||||
).toBeUndefined();
|
||||
expect(() => {
|
||||
store.thingFormUrl("microscope", "properties", "status", "fakeOp", false);
|
||||
}).toThrowError(/Could not find form/);
|
||||
});
|
||||
|
||||
it("thingFormUrl concats base url with relative href", () => {
|
||||
const store = useWotStore();
|
||||
const url = store.thingFormUrl("microscope", "properties", "status", "readproperty");
|
||||
expect(url).toBe("http://microscope.local/api/v3/status");
|
||||
});
|
||||
|
||||
it("thingFormUrl uses absolute href directly if it starts with http", () => {
|
||||
const store = useWotStore();
|
||||
const url = store.thingFormUrl("microscope", "actions", "toggle", "invokeaction");
|
||||
expect(url).toBe("http://external.com/api/v3/toggle");
|
||||
});
|
||||
|
||||
it("returns relative href if the Thing Description has no base property", () => {
|
||||
const store = useWotStore();
|
||||
store.addThingDescription("noBaseThing", {
|
||||
properties: {
|
||||
status: { forms: [{ op: "readproperty", href: "relative-path-only" }] },
|
||||
},
|
||||
});
|
||||
|
||||
const url = store.thingFormUrl("noBaseThing", "properties", "status", "readproperty");
|
||||
expect(url).toBe("relative-path-only");
|
||||
});
|
||||
|
||||
it("normalizes slashes when base lacks trailing slash and href has leading slash", () => {
|
||||
const store = useWotStore();
|
||||
store.addThingDescription("messyThing", {
|
||||
// No trailing slash
|
||||
base: "http://microscope.local/api/v3",
|
||||
properties: {
|
||||
// Extra leading slash
|
||||
status: { forms: [{ op: "readproperty", href: "/status" }] },
|
||||
},
|
||||
});
|
||||
|
||||
const url = store.thingFormUrl("messyThing", "properties", "status", "readproperty");
|
||||
expect(url).toBe("http://microscope.local/api/v3/status");
|
||||
});
|
||||
|
||||
it("thingPropertyUrl and thingActionUrl proxy correctly", () => {
|
||||
const store = useWotStore();
|
||||
expect(store.thingPropertyUrl("microscope", "status", "readproperty")).toBe(
|
||||
"http://microscope.local/api/v3/status",
|
||||
);
|
||||
expect(store.thingActionUrl("microscope", "toggle", "invokeaction")).toBe(
|
||||
"http://external.com/api/v3/toggle",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue