import { mount } from "@vue/test-utils"; import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import CommandPalette from "../../meshchatx/src/frontend/components/CommandPalette.vue"; import GlobalEmitter from "../../meshchatx/src/frontend/js/GlobalEmitter"; describe("CommandPalette.vue", () => { let axiosMock; let routerMock; beforeEach(() => { axiosMock = { get: vi.fn().mockResolvedValue({ data: { announces: [], contacts: [], }, }), }; window.axios = axiosMock; routerMock = { push: vi.fn(), }; GlobalEmitter.off("sync-propagation-node"); GlobalEmitter.off("toggle-orbit"); }); afterEach(() => { delete window.axios; GlobalEmitter.off("sync-propagation-node"); GlobalEmitter.off("toggle-orbit"); vi.clearAllMocks(); }); const mountCommandPalette = () => { return mount(CommandPalette, { global: { mocks: { $t: (key) => key, $router: routerMock, }, stubs: { MaterialDesignIcon: { template: '
' }, LxmfUserIcon: { template: '' }, }, directives: { "click-outside": {}, }, }, }); }; it("renders nothing when closed", () => { const wrapper = mountCommandPalette(); expect(wrapper.find(".fixed").exists()).toBe(false); }); it("opens when Ctrl+K or Cmd+K is pressed", async () => { const wrapper = mountCommandPalette(); const event = new KeyboardEvent("keydown", { key: "k", ctrlKey: true, }); window.dispatchEvent(event); await wrapper.vm.$nextTick(); expect(wrapper.vm.isOpen).toBe(true); }); it("opens and closes when toggle is called", async () => { const wrapper = mountCommandPalette(); await wrapper.vm.toggle(); expect(wrapper.vm.isOpen).toBe(true); await wrapper.vm.toggle(); expect(wrapper.vm.isOpen).toBe(false); }); it("loads peers and contacts when opened", async () => { axiosMock.get .mockResolvedValueOnce({ data: { announces: [ { destination_hash: "peer1", display_name: "Peer 1", lxmf_user_icon: { icon_name: "account" }, }, ], }, }) .mockResolvedValueOnce({ data: [ { id: 1, name: "Contact 1", remote_identity_hash: "contact1", }, ], }); const wrapper = mountCommandPalette(); await wrapper.vm.open(); await wrapper.vm.$nextTick(); expect(axiosMock.get).toHaveBeenCalledWith("/api/v1/announces", { params: { aspect: "lxmf.delivery", limit: 20 }, }); expect(axiosMock.get).toHaveBeenCalledWith("/api/v1/telephone/contacts"); }); it("filters results based on query", async () => { const wrapper = mountCommandPalette(); wrapper.vm.isOpen = true; await wrapper.vm.$nextTick(); wrapper.vm.query = "messages"; await wrapper.vm.$nextTick(); const results = wrapper.vm.filteredResults; expect(results.length).toBeGreaterThan(0); expect(results.some((r) => r.title.toLowerCase().includes("messages"))).toBe(true); }); it("shows navigation and action items when query is empty", async () => { const wrapper = mountCommandPalette(); wrapper.vm.isOpen = true; wrapper.vm.query = ""; await wrapper.vm.$nextTick(); const results = wrapper.vm.filteredResults; const hasNavigation = results.some((r) => r.type === "navigation"); const hasAction = results.some((r) => r.type === "action"); expect(hasNavigation || hasAction).toBe(true); }); it("highlights first result when filtered results change", async () => { const wrapper = mountCommandPalette(); wrapper.vm.isOpen = true; await wrapper.vm.$nextTick(); const results = wrapper.vm.filteredResults; if (results.length > 0) { expect(wrapper.vm.highlightedId).toBe(results[0].id); } }); it("moves highlight up and down with arrow keys", async () => { const wrapper = mountCommandPalette(); wrapper.vm.isOpen = true; await wrapper.vm.$nextTick(); const initialHighlight = wrapper.vm.highlightedId; wrapper.vm.moveHighlight(1); expect(wrapper.vm.highlightedId).not.toBe(initialHighlight); wrapper.vm.moveHighlight(-1); expect(wrapper.vm.highlightedId).toBe(initialHighlight); }); it("wraps highlight when moving past boundaries", async () => { const wrapper = mountCommandPalette(); wrapper.vm.isOpen = true; await wrapper.vm.$nextTick(); const results = wrapper.vm.filteredResults; if (results.length > 0) { wrapper.vm.highlightedId = results[results.length - 1].id; wrapper.vm.moveHighlight(1); expect(wrapper.vm.highlightedId).toBe(results[0].id); wrapper.vm.highlightedId = results[0].id; wrapper.vm.moveHighlight(-1); expect(wrapper.vm.highlightedId).toBe(results[results.length - 1].id); } }); it("navigates to route when navigation result is executed", async () => { const wrapper = mountCommandPalette(); wrapper.vm.isOpen = true; await wrapper.vm.$nextTick(); const navResult = wrapper.vm.filteredResults.find((r) => r.type === "navigation"); if (navResult) { wrapper.vm.executeResult(navResult); expect(routerMock.push).toHaveBeenCalledWith(navResult.route); expect(wrapper.vm.isOpen).toBe(false); } }); it("navigates to messages when peer result is executed", async () => { const wrapper = mountCommandPalette(); wrapper.vm.isOpen = true; wrapper.vm.peers = [ { destination_hash: "peer123", display_name: "Test Peer", }, ]; await wrapper.vm.$nextTick(); const peerResult = wrapper.vm.filteredResults.find((r) => r.type === "peer"); if (peerResult) { wrapper.vm.executeResult(peerResult); expect(routerMock.push).toHaveBeenCalledWith({ name: "messages", params: { destinationHash: "peer123" }, }); } }); it("navigates to call when contact result is executed", async () => { const wrapper = mountCommandPalette(); wrapper.vm.isOpen = true; wrapper.vm.contacts = [ { id: 1, name: "Test Contact", remote_identity_hash: "contact123", }, ]; await wrapper.vm.$nextTick(); const contactResult = wrapper.vm.filteredResults.find((r) => r.type === "contact"); if (contactResult) { wrapper.vm.executeResult(contactResult); expect(routerMock.push).toHaveBeenCalledWith({ name: "call", query: { destination_hash: "contact123" }, }); } }); it("emits sync event when sync action is executed", async () => { const wrapper = mountCommandPalette(); const emitSpy = vi.spyOn(GlobalEmitter, "emit"); wrapper.vm.isOpen = true; await wrapper.vm.$nextTick(); const syncResult = wrapper.vm.filteredResults.find((r) => r.action === "sync"); if (syncResult) { wrapper.vm.executeResult(syncResult); expect(emitSpy).toHaveBeenCalledWith("sync-propagation-node"); } }); it("emits toggle-orbit event when orbit action is executed", async () => { const wrapper = mountCommandPalette(); const emitSpy = vi.spyOn(GlobalEmitter, "emit"); wrapper.vm.isOpen = true; await wrapper.vm.$nextTick(); const orbitResult = wrapper.vm.filteredResults.find((r) => r.action === "toggle-orbit"); if (orbitResult) { wrapper.vm.executeResult(orbitResult); expect(emitSpy).toHaveBeenCalledWith("toggle-orbit"); } }); it("closes when ESC key is pressed", async () => { const wrapper = mountCommandPalette(); wrapper.vm.isOpen = true; await wrapper.vm.$nextTick(); const input = wrapper.find("input"); await input.trigger("keydown.esc"); expect(wrapper.vm.isOpen).toBe(false); }); it("executes highlighted action when Enter is pressed", async () => { const wrapper = mountCommandPalette(); wrapper.vm.isOpen = true; await wrapper.vm.$nextTick(); const executeSpy = vi.spyOn(wrapper.vm, "executeAction"); const input = wrapper.find("input"); await input.trigger("keydown.enter"); expect(executeSpy).toHaveBeenCalled(); }); it("groups results by type", async () => { const wrapper = mountCommandPalette(); wrapper.vm.isOpen = true; wrapper.vm.peers = [ { destination_hash: "peer1", display_name: "Peer 1", }, ]; await wrapper.vm.$nextTick(); const grouped = wrapper.vm.groupedResults; expect(Object.keys(grouped).length).toBeGreaterThan(0); }); it("shows no results message when query has no matches", async () => { const wrapper = mountCommandPalette(); wrapper.vm.isOpen = true; wrapper.vm.query = "nonexistentquery12345"; await wrapper.vm.$nextTick(); const results = wrapper.vm.filteredResults; expect(results.length).toBe(0); }); });