Files
openobserve/web/src/composables/useFavoriteDashboards.spec.ts
T
2026-08-05 10:16:00 +00:00

213 lines
7.7 KiB
TypeScript

// Copyright 2026 OpenObserve Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { describe, it, expect, vi, beforeEach } from "vitest";
import i18nInstance from "@/locales";
const t = (i18nInstance.global as any).t;
vi.mock("@/services/settings", () => ({
default: {
getSetting: vi.fn(),
setUserSetting: vi.fn(),
},
}));
vi.mock("@/lib/feedback/Toast/useToast", () => ({ toast: vi.fn() }));
import settings from "@/services/settings";
import { toast } from "@/lib/feedback/Toast/useToast";
import { useFavoriteDashboards } from "@/composables/useFavoriteDashboards";
const D1 = { dashboardId: "abc", folderId: "default", label: "Payments" };
const D2 = { dashboardId: "def", folderId: "prod", label: "Latency" };
describe("useFavoriteDashboards", () => {
beforeEach(() => {
vi.clearAllMocks();
useFavoriteDashboards().favorites.value = []; // reset shared state
});
it("load populates the list from the user-resolved setting", async () => {
(settings.getSetting as any).mockResolvedValue({
data: { setting_value: [D1, D2] },
});
const { load, favorites } = useFavoriteDashboards();
await load("org1", "me@example.com");
expect(settings.getSetting).toHaveBeenCalledWith(
"org1",
"favorite_dashboards",
"me@example.com",
);
expect(favorites.value).toEqual([D1, D2]);
});
it("load drops malformed entries and non-array payloads", async () => {
(settings.getSetting as any).mockResolvedValue({
data: { setting_value: [D1, null, { folderId: "x" }] },
});
const { load, favorites } = useFavoriteDashboards();
await load("org1", "me@example.com");
expect(favorites.value).toEqual([D1]);
(settings.getSetting as any).mockResolvedValue({
data: { setting_value: { not: "an array" } },
});
await load("org1", "me@example.com");
expect(favorites.value).toEqual([]);
});
it("load resets to empty when the setting is absent (404)", async () => {
(settings.getSetting as any).mockRejectedValue({
response: { status: 404 },
});
const { load, favorites } = useFavoriteDashboards();
favorites.value = [D1];
await load("org1", "me@example.com");
expect(favorites.value).toEqual([]);
});
it("load no-ops without an org or user id", async () => {
const { load } = useFavoriteDashboards();
await load("", "me@example.com");
await load("org1", "");
expect(settings.getSetting).not.toHaveBeenCalled();
});
it("toggleFavorite adds optimistically and persists the full list", async () => {
(settings.setUserSetting as any).mockResolvedValue({});
const { toggleFavorite, favorites } = useFavoriteDashboards();
favorites.value = [D1];
const p = toggleFavorite("org1", "me@example.com", D2, t);
expect(favorites.value).toEqual([D1, D2]); // optimistic — before resolve
await p;
expect(settings.setUserSetting).toHaveBeenCalledWith(
"org1",
"me@example.com",
"favorite_dashboards",
[D1, D2],
"ui",
);
});
it("toggleFavorite removes an existing favorite", async () => {
(settings.setUserSetting as any).mockResolvedValue({});
const { toggleFavorite, favorites } = useFavoriteDashboards();
favorites.value = [D1, D2];
await toggleFavorite("org1", "me@example.com", D1, t);
expect(favorites.value).toEqual([D2]);
expect(settings.setUserSetting).toHaveBeenCalledWith(
"org1",
"me@example.com",
"favorite_dashboards",
[D2],
"ui",
);
});
it("toggleFavorite reverts and toasts on failure", async () => {
(settings.setUserSetting as any).mockRejectedValue({
response: { status: 500 },
});
const { toggleFavorite, favorites } = useFavoriteDashboards();
favorites.value = [D1];
await toggleFavorite("org1", "me@example.com", D2, t);
expect(favorites.value).toEqual([D1]); // reverted
expect(toast).toHaveBeenCalledWith(expect.objectContaining({ variant: "error" }));
});
it("toggleFavorite shows a permission message on 403", async () => {
(settings.setUserSetting as any).mockRejectedValue({
response: { status: 403 },
});
const { toggleFavorite, favorites } = useFavoriteDashboards();
await toggleFavorite("org1", "me@example.com", D1, t);
expect(favorites.value).toEqual([]);
expect(toast).toHaveBeenCalledWith(
expect.objectContaining({
variant: "error",
message: expect.stringMatching(/permission/i),
}),
);
});
it("toggleFavorite no-ops (no API call, no flash) without org or user", async () => {
const { toggleFavorite, favorites } = useFavoriteDashboards();
await toggleFavorite("", "me@example.com", D1, t);
await toggleFavorite("org1", "", D1, t);
expect(settings.setUserSetting).not.toHaveBeenCalled();
expect(favorites.value).toEqual([]);
});
it("removeFavorites drops deleted dashboards and persists the remainder", async () => {
(settings.setUserSetting as any).mockResolvedValue({});
const { removeFavorites, favorites } = useFavoriteDashboards();
favorites.value = [D1, D2];
await removeFavorites("org1", "me@example.com", ["abc"]);
expect(favorites.value).toEqual([D2]);
expect(settings.setUserSetting).toHaveBeenCalledWith(
"org1",
"me@example.com",
"favorite_dashboards",
[D2],
"ui",
);
});
it("removeFavorites skips the write when nothing matched", async () => {
const { removeFavorites, favorites } = useFavoriteDashboards();
favorites.value = [D1];
await removeFavorites("org1", "me@example.com", ["not-favorited"]);
expect(favorites.value).toEqual([D1]);
expect(settings.setUserSetting).not.toHaveBeenCalled();
});
it("removeFavorites keeps the row hidden even if the write fails", async () => {
(settings.setUserSetting as any).mockRejectedValue({
response: { status: 500 },
});
const { removeFavorites, favorites } = useFavoriteDashboards();
favorites.value = [D1, D2];
await removeFavorites("org1", "me@example.com", ["abc"]);
// No revert: the dashboard is already gone from the backend, so restoring
// the row would just re-create the ghost this cleanup exists to remove.
expect(favorites.value).toEqual([D2]);
expect(toast).not.toHaveBeenCalled();
});
it("removeFavorites no-ops without org, user, or ids", async () => {
const { removeFavorites, favorites } = useFavoriteDashboards();
favorites.value = [D1];
await removeFavorites("", "me@example.com", ["abc"]);
await removeFavorites("org1", "", ["abc"]);
await removeFavorites("org1", "me@example.com", []);
expect(settings.setUserSetting).not.toHaveBeenCalled();
expect(favorites.value).toEqual([D1]);
});
it("isFavorite reflects the current list", () => {
const { isFavorite, favorites } = useFavoriteDashboards();
favorites.value = [D1];
expect(isFavorite("abc")).toBe(true);
expect(isFavorite("def")).toBe(false);
});
it("state is shared across consumers (module-level ref)", () => {
const a = useFavoriteDashboards();
const b = useFavoriteDashboards();
a.favorites.value = [D1];
expect(b.isFavorite("abc")).toBe(true);
});
});