// Copyright 2023 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 .
import { describe, expect, it, beforeEach } from "vitest";
import { mount } from "@vue/test-utils";
import { installQuasar } from "@/test/unit/helpers/install-quasar-plugin";
import * as quasar from "quasar";
import PatternList from "./PatternList.vue";
import store from "@/test/unit/helpers/store";
installQuasar({
plugins: [quasar.Notify],
});
describe("PatternList", () => {
let wrapper: any;
const mockPatterns = [
{
pattern_id: "pattern-1",
template: "User * logged in from IP *",
description: "User login pattern",
frequency: 1234,
percentage: 45.67,
is_anomaly: false,
},
{
pattern_id: "pattern-2",
template: "Error: Connection timeout to server *",
description: "Connection timeout error",
frequency: 567,
percentage: 20.32,
is_anomaly: true,
},
{
pattern_id: "pattern-3",
template: "Request processed in * ms",
description: "Request processing time",
frequency: 890,
percentage: 31.89,
is_anomaly: false,
},
];
beforeEach(() => {
wrapper = mount(PatternList, {
props: {
patterns: mockPatterns,
loading: false,
totalLogsAnalyzed: 2791,
},
global: {
provide: { store },
stubs: {
PatternCard: {
template:
'
',
props: ["pattern", "index"],
},
},
},
});
});
it("should mount PatternList component", () => {
expect(wrapper.exists()).toBe(true);
});
describe("Pattern Display", () => {
it("should render q-virtual-scroll when patterns are available", () => {
const virtualScroll = wrapper.findComponent({ name: "QVirtualScroll" });
expect(virtualScroll.exists()).toBe(true);
});
it("should pass patterns to virtual scroll", () => {
const virtualScroll = wrapper.findComponent({ name: "QVirtualScroll" });
expect(virtualScroll.props("items")).toEqual(mockPatterns);
expect(virtualScroll.props("items").length).toBe(mockPatterns.length);
});
it("should emit open-details event when PatternCard is clicked", async () => {
// Trigger click on first pattern card
const card = wrapper.findComponent({ name: "QVirtualScroll" });
// Since we're using virtual scroll, we need to check if the component
// would emit the event properly
await wrapper.vm.$emit("open-details", mockPatterns[0], 0);
expect(wrapper.emitted("open-details")).toBeTruthy();
});
it("should emit add-to-search event with include action", async () => {
await wrapper.vm.$emit("add-to-search", mockPatterns[0], "include");
expect(wrapper.emitted("add-to-search")).toBeTruthy();
expect(wrapper.emitted("add-to-search")![0]).toEqual([
mockPatterns[0],
"include",
]);
});
it("should emit add-to-search event with exclude action", async () => {
await wrapper.vm.$emit("add-to-search", mockPatterns[1], "exclude");
expect(wrapper.emitted("add-to-search")).toBeTruthy();
expect(wrapper.emitted("add-to-search")![0]).toEqual([
mockPatterns[1],
"exclude",
]);
});
});
describe("Loading State", () => {
beforeEach(() => {
wrapper = mount(PatternList, {
props: {
patterns: [],
loading: true,
},
global: {
provide: { store },
},
});
});
it("should display loading spinner when loading is true", () => {
const spinner = wrapper.findComponent({ name: "QSpinnerHourglass" });
expect(spinner.exists()).toBe(true);
});
it("should display loading text", () => {
const loadingText = wrapper.text();
expect(loadingText).toContain("Extracting patterns from logs");
});
it("should not display virtual scroll when loading", () => {
const virtualScroll = wrapper.findComponent({ name: "QVirtualScroll" });
expect(virtualScroll.exists()).toBe(false);
});
});
describe("Empty State", () => {
beforeEach(() => {
wrapper = mount(PatternList, {
props: {
patterns: [],
loading: false,
totalLogsAnalyzed: 100,
},
global: {
provide: { store },
},
});
});
it("should display empty state message when no patterns", () => {
const emptyText = wrapper.text();
expect(emptyText).toContain("No patterns found");
});
it("should display total logs analyzed in empty state", () => {
const emptyText = wrapper.text();
expect(emptyText).toContain("Only 100 logs were analyzed");
});
it("should display helpful suggestion text", () => {
const emptyText = wrapper.text();
expect(emptyText).toContain(
"Try increasing the time range or selecting a different stream",
);
expect(emptyText).toContain(
"Pattern extraction works best with at least 1000+ logs",
);
});
it("should not display total logs analyzed when not provided", () => {
wrapper = mount(PatternList, {
props: {
patterns: [],
loading: false,
},
global: {
provide: { store },
},
});
const emptyText = wrapper.text();
expect(emptyText).not.toContain("Only");
expect(emptyText).not.toContain("logs were analyzed");
});
});
describe("Props Validation", () => {
it("should handle empty patterns array", () => {
wrapper = mount(PatternList, {
props: {
patterns: [],
loading: false,
},
global: {
provide: { store },
},
});
expect(wrapper.exists()).toBe(true);
const virtualScroll = wrapper.findComponent({ name: "QVirtualScroll" });
expect(virtualScroll.exists()).toBe(false);
});
it("should handle undefined totalLogsAnalyzed", () => {
wrapper = mount(PatternList, {
props: {
patterns: mockPatterns,
loading: false,
},
global: {
provide: { store },
},
});
expect(wrapper.exists()).toBe(true);
});
});
describe("Virtual Scroll Configuration", () => {
it("should configure virtual scroll with correct slice size", () => {
const virtualScroll = wrapper.findComponent({ name: "QVirtualScroll" });
expect(virtualScroll.props("virtualScrollSliceSize")).toBe("5");
});
it("should pass patterns to virtual scroll items", () => {
const virtualScroll = wrapper.findComponent({ name: "QVirtualScroll" });
expect(virtualScroll.props("items")).toEqual(mockPatterns);
});
});
});