722 lines
22 KiB
TypeScript
722 lines
22 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, expect, it, beforeEach, vi, afterEach } from "vitest";
|
|
import { mount } from "@vue/test-utils";
|
|
import { reactive } from "vue";
|
|
// Mock the zincutils utilities completely
|
|
vi.mock("@/utils/zincutils", async (importOriginal) => {
|
|
const actual = (await importOriginal()) as any;
|
|
return {
|
|
...actual,
|
|
getImageURL: vi.fn().mockReturnValue("/mock-image.svg"),
|
|
useLocalOrganization: vi.fn().mockReturnValue({
|
|
identifier: "test-org",
|
|
name: "Test Organization",
|
|
}),
|
|
useLocalCurrentUser: vi.fn().mockReturnValue({
|
|
email: "test@example.com",
|
|
name: "Test User",
|
|
}),
|
|
useLocalTimezone: vi.fn().mockReturnValue("UTC"),
|
|
b64EncodeUnicode: vi.fn().mockImplementation((str) => btoa(str)),
|
|
b64DecodeUnicode: vi.fn().mockImplementation((str) => atob(str)),
|
|
};
|
|
});
|
|
|
|
// Mock functions service to prevent MSW warnings
|
|
vi.mock("@/services/function_template", () => ({
|
|
default: {
|
|
get: vi.fn().mockResolvedValue({ data: [] }),
|
|
},
|
|
}));
|
|
|
|
// Mock composables that make API calls
|
|
vi.mock("@/composables/useFunctions", () => ({
|
|
default: vi.fn(() => ({
|
|
getAllFunctions: vi.fn().mockResolvedValue([]),
|
|
functions: { value: [] },
|
|
isLoading: { value: false },
|
|
})),
|
|
useFunctions: vi.fn(() => ({
|
|
getAllFunctions: vi.fn().mockResolvedValue([]),
|
|
functions: { value: [] },
|
|
isLoading: { value: false },
|
|
})),
|
|
}));
|
|
|
|
// Mock CodeQueryEditor to prevent document access errors
|
|
vi.mock("@/components/CodeQueryEditor.vue", () => ({
|
|
default: {
|
|
name: "CodeQueryEditor",
|
|
template: '<div data-test="code-query-editor">CodeQueryEditor Mock</div>',
|
|
props: ["query", "editorId", "keywords", "suggestions", "autoComplete", "readOnly", "language"],
|
|
emits: ["update:query", "updateQuery", "runQuery", "focus", "blur"],
|
|
},
|
|
}));
|
|
|
|
import DashboardQueryEditor from "@/components/dashboards/addPanel/DashboardQueryEditor.vue";
|
|
import useSqlSuggestions from "@/composables/useSuggestions";
|
|
import i18n from "@/locales";
|
|
import store from "@/test/unit/helpers/store";
|
|
import router from "@/test/unit/helpers/router";
|
|
|
|
// Create a reactive mock dashboard panel data
|
|
const createMockDashboardPanelData = () => {
|
|
const mockData = {
|
|
data: {
|
|
id: "panel-1",
|
|
title: "Test Panel",
|
|
type: "line",
|
|
queryType: "sql",
|
|
queries: [
|
|
{
|
|
query: "SELECT * FROM test_stream",
|
|
queryType: "sql",
|
|
customQuery: true,
|
|
stream: "test_stream",
|
|
vrlFunctionQuery: "",
|
|
},
|
|
],
|
|
},
|
|
layout: {
|
|
currentQueryIndex: 0,
|
|
vrlFunctionToggle: false,
|
|
showQueryBar: true,
|
|
hiddenQueries: [],
|
|
},
|
|
meta: {
|
|
errors: {
|
|
queryErrors: [],
|
|
},
|
|
dateTime: {
|
|
start_time: new Date(),
|
|
end_time: new Date(),
|
|
},
|
|
stream: {
|
|
customQueryFields: [],
|
|
streamResults: [],
|
|
},
|
|
streamFields: {
|
|
groupedFields: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
return {
|
|
// reactive() because the real composable's state is: the component watches
|
|
// the active query's stream, and a plain object would never fire the
|
|
// watcher — the test would pass or fail for reasons unrelated to the
|
|
// component.
|
|
dashboardPanelData: reactive(mockData),
|
|
promqlMode: false, // Make this a direct boolean instead of ref
|
|
addQuery: vi.fn(() => {
|
|
mockData.data.queries.push({
|
|
query: "",
|
|
queryType: "sql",
|
|
customQuery: true,
|
|
stream: "",
|
|
vrlFunctionQuery: "",
|
|
});
|
|
}),
|
|
removeQuery: vi.fn((index) => {
|
|
mockData.data.queries.splice(index, 1);
|
|
}),
|
|
selectedStreamFieldsBasedOnUserDefinedSchema: { value: [] },
|
|
};
|
|
};
|
|
|
|
// Mock the dashboard panel composable
|
|
vi.mock("@/composables/dashboard/useDashboardPanel", () => ({
|
|
default: vi.fn(() => createMockDashboardPanelData()),
|
|
}));
|
|
|
|
// Mock other composables
|
|
vi.mock("@/composables/usePromqlSuggestions", () => ({
|
|
default: vi.fn(() => ({
|
|
autoCompleteData: {
|
|
value: {
|
|
query: "",
|
|
position: { cursorIndex: 0 },
|
|
popup: { open: vi.fn(), close: vi.fn() },
|
|
},
|
|
},
|
|
autoCompletePromqlKeywords: { value: [] },
|
|
getSuggestions: vi.fn(),
|
|
updateMetricKeywords: vi.fn(),
|
|
})),
|
|
}));
|
|
|
|
vi.mock("@/composables/useSuggestions", () => ({
|
|
default: vi.fn(() => ({
|
|
// Mirrors the real composable's shape. autoCompleteData carries the stream
|
|
// context the field-value resolver looks values up under; omitting it here
|
|
// made every mount throw once the component started setting it.
|
|
autoCompleteData: {
|
|
value: {
|
|
org: "",
|
|
streamType: "",
|
|
streamName: "",
|
|
query: "",
|
|
cursorIndex: 0,
|
|
popup: { open: vi.fn(), close: vi.fn() },
|
|
},
|
|
},
|
|
resolveFieldValues: vi.fn(async () => []),
|
|
autoCompleteKeywords: { value: [] },
|
|
autoCompleteSuggestions: { value: [] },
|
|
effectiveKeywords: { value: [] },
|
|
effectiveSuggestions: { value: [] },
|
|
getSuggestions: vi.fn(),
|
|
updateFieldKeywords: vi.fn(),
|
|
updateFunctionKeywords: vi.fn(),
|
|
updateAllKeywords: vi.fn(),
|
|
updateStreamKeywords: vi.fn(),
|
|
})),
|
|
}));
|
|
|
|
vi.mock("@/composables/useNotifications", () => ({
|
|
default: vi.fn(() => ({
|
|
showErrorNotification: vi.fn(),
|
|
showPositiveNotification: vi.fn(),
|
|
})),
|
|
}));
|
|
|
|
const mockDashboardPanelData = {
|
|
data: {
|
|
id: "panel-1",
|
|
title: "Test Panel",
|
|
type: "line",
|
|
queryType: "sql",
|
|
queries: [
|
|
{
|
|
query: "SELECT * FROM test_stream",
|
|
queryType: "sql",
|
|
customQuery: true,
|
|
stream: "test_stream",
|
|
vrlFunctionQuery: "",
|
|
},
|
|
],
|
|
},
|
|
layout: {
|
|
currentQueryIndex: 0,
|
|
vrlFunctionToggle: false,
|
|
showQueryBar: true,
|
|
},
|
|
meta: {
|
|
errors: {
|
|
queryErrors: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
// Helper function for deep cloning to prevent data mutation between tests
|
|
const createFreshMockData = (overrides = {}) => {
|
|
const baseData = JSON.parse(JSON.stringify(mockDashboardPanelData));
|
|
return {
|
|
...baseData,
|
|
...overrides,
|
|
data: {
|
|
...baseData.data,
|
|
...overrides.data,
|
|
},
|
|
layout: {
|
|
...baseData.layout,
|
|
...overrides.layout,
|
|
},
|
|
};
|
|
};
|
|
|
|
describe("DashboardQueryEditor", () => {
|
|
let wrapper: any;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
|
|
store.state.selectedOrganization = { identifier: "test-org" };
|
|
store.state.theme = "light";
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (wrapper) {
|
|
wrapper.unmount();
|
|
}
|
|
});
|
|
|
|
const createWrapper = (props = {}) => {
|
|
return mount(DashboardQueryEditor, {
|
|
props: props,
|
|
global: {
|
|
plugins: [i18n, store, router],
|
|
provide: {
|
|
dashboardPanelDataPageKey: "dashboard",
|
|
},
|
|
stubs: {
|
|
QueryTypeSelector: {
|
|
template: '<div data-test="query-type-selector"></div>',
|
|
},
|
|
QueryEditor: {
|
|
template: '<div data-test="code-query-editor">QueryEditor Mock</div>',
|
|
props: [
|
|
"query",
|
|
"editorId",
|
|
"keywords",
|
|
"suggestions",
|
|
"autoComplete",
|
|
"readOnly",
|
|
"language",
|
|
],
|
|
emits: ["update:query", "updateQuery", "runQuery"],
|
|
},
|
|
},
|
|
mocks: {
|
|
$t: (key: string) => key,
|
|
$route: { params: {}, query: {} },
|
|
$router: { push: vi.fn() },
|
|
},
|
|
},
|
|
});
|
|
};
|
|
|
|
describe("Component Rendering", () => {
|
|
it("should render query editor container", () => {
|
|
wrapper = createWrapper();
|
|
|
|
expect(wrapper.find('[data-test="dashboard-panel-searchbar"]').exists()).toBe(true);
|
|
});
|
|
|
|
it("should render basic query data container", () => {
|
|
wrapper = createWrapper();
|
|
|
|
expect(wrapper.find('[data-test="dashboard-query-data"]').exists()).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("Query Tabs", () => {
|
|
it("should render component without tabs when conditions aren't met", () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Verify component renders
|
|
expect(wrapper.exists()).toBe(true);
|
|
|
|
// Since promqlMode is false and type is 'line', tabs should not exist
|
|
// Let's just test that the component works properly
|
|
expect(wrapper.find('[data-test="dashboard-panel-searchbar"]').exists()).toBe(true);
|
|
});
|
|
|
|
it("should handle different panel types gracefully", () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Test that component renders regardless of panel type
|
|
expect(wrapper.exists()).toBe(true);
|
|
expect(wrapper.vm.dashboardPanelData.data.type).toBe("line");
|
|
});
|
|
});
|
|
|
|
describe("Component State", () => {
|
|
it("should track current query index", () => {
|
|
wrapper = createWrapper();
|
|
|
|
expect(wrapper.vm.dashboardPanelData.layout.currentQueryIndex).toBe(0);
|
|
});
|
|
|
|
it("should track VRL function toggle state", () => {
|
|
wrapper = createWrapper();
|
|
|
|
expect(wrapper.vm.dashboardPanelData.layout.vrlFunctionToggle).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("Event Handling", () => {
|
|
it("should handle dropdown click events", async () => {
|
|
wrapper = createWrapper();
|
|
|
|
const dropdown = wrapper.find('[data-test="dashboard-panel-searchbar"]');
|
|
await dropdown.trigger("click");
|
|
|
|
expect(wrapper.emitted()).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe("Edge Cases", () => {
|
|
it("should handle empty queries array", () => {
|
|
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
|
|
const emptyQueriesPanelData = {
|
|
...mockDashboardPanelData,
|
|
data: { ...mockDashboardPanelData.data, queries: [] },
|
|
};
|
|
|
|
wrapper = createWrapper({ dashboardPanelData: emptyQueriesPanelData });
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
|
|
consoleWarnSpy.mockRestore();
|
|
consoleLogSpy.mockRestore();
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it("should handle missing panel data gracefully", () => {
|
|
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
|
|
// Don't pass null, just minimal data
|
|
const minimalData = {
|
|
data: { queries: [], type: "line" },
|
|
layout: { currentQueryIndex: 0, vrlFunctionToggle: false },
|
|
};
|
|
wrapper = createWrapper({ dashboardPanelData: minimalData });
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
|
|
consoleWarnSpy.mockRestore();
|
|
consoleLogSpy.mockRestore();
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe("Theme Integration", () => {
|
|
it("should work with light theme", () => {
|
|
store.state.theme = "light";
|
|
wrapper = createWrapper();
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
});
|
|
|
|
it("should work with dark theme", () => {
|
|
store.state.theme = "dark";
|
|
wrapper = createWrapper();
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("Query Management", () => {
|
|
it("should handle multiple queries", async () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Since component doesn't use props, directly manipulate the internal data
|
|
wrapper.vm.addTab();
|
|
await wrapper.vm.$nextTick();
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
expect(wrapper.vm.dashboardPanelData.data.queries.length).toBe(2);
|
|
});
|
|
|
|
it("should handle query index changes", async () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Add another query and change index
|
|
wrapper.vm.addTab();
|
|
await wrapper.vm.$nextTick();
|
|
wrapper.vm.dashboardPanelData.layout.currentQueryIndex = 1;
|
|
await wrapper.vm.$nextTick();
|
|
|
|
expect(wrapper.vm.dashboardPanelData.layout.currentQueryIndex).toBe(1);
|
|
});
|
|
|
|
// The field-value resolver looks values up under "org|streamType|
|
|
// streamName|field". This panel never set any of the three, so its
|
|
// resolver could only ever return [] — a working editor with value
|
|
// completion silently absent. Per QUERY, not per panel: each tab has its
|
|
// own stream and a stale context would offer the previous tab's values.
|
|
const sqlAutoCompleteData = () =>
|
|
(useSqlSuggestions as any).mock.results.at(-1).value.autoCompleteData.value;
|
|
|
|
it("publishes the active query's stream as the field-value lookup context", async () => {
|
|
wrapper = createWrapper();
|
|
wrapper.vm.dashboardPanelData.data.queries[0].fields = {
|
|
stream: "app_logs",
|
|
stream_type: "logs",
|
|
};
|
|
await wrapper.vm.$nextTick();
|
|
|
|
expect(sqlAutoCompleteData()).toMatchObject({
|
|
org: "test-org",
|
|
streamType: "logs",
|
|
streamName: "app_logs",
|
|
});
|
|
});
|
|
|
|
it("follows the stream when the user switches query tabs", async () => {
|
|
wrapper = createWrapper();
|
|
wrapper.vm.dashboardPanelData.data.queries[0].fields = {
|
|
stream: "app_logs",
|
|
stream_type: "logs",
|
|
};
|
|
wrapper.vm.dashboardPanelData.data.queries.push({
|
|
query: "",
|
|
queryType: "sql",
|
|
customQuery: true,
|
|
fields: { stream: "app_metrics", stream_type: "metrics" },
|
|
});
|
|
await wrapper.vm.$nextTick();
|
|
|
|
wrapper.vm.dashboardPanelData.layout.currentQueryIndex = 1;
|
|
await wrapper.vm.$nextTick();
|
|
|
|
expect(sqlAutoCompleteData()).toMatchObject({
|
|
streamType: "metrics",
|
|
streamName: "app_metrics",
|
|
});
|
|
});
|
|
|
|
it("should handle query editor configuration", () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Test query editor container exists
|
|
const queryContainer = wrapper.find('[data-test="dashboard-query-data"]');
|
|
expect(queryContainer.exists() || wrapper.exists()).toBe(true);
|
|
});
|
|
|
|
it("should handle SQL mode queries", () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Set query type and update query
|
|
wrapper.vm.dashboardPanelData.data.queries[0].queryType = "sql";
|
|
wrapper.vm.dashboardPanelData.data.queries[0].query =
|
|
"SELECT * FROM logs WHERE level='ERROR'";
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
expect(wrapper.vm.dashboardPanelData.data.queries[0].queryType).toBe("sql");
|
|
});
|
|
|
|
it("should handle PromQL mode queries", () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Set PromQL query - the component gets promqlMode from the composable, not props
|
|
wrapper.vm.dashboardPanelData.data.queries[0].queryType = "promql";
|
|
wrapper.vm.dashboardPanelData.data.queries[0].query = "rate(http_requests_total[5m])";
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
expect(wrapper.vm.dashboardPanelData.data.queries[0].queryType).toBe("promql");
|
|
});
|
|
});
|
|
|
|
describe("VRL Function Integration", () => {
|
|
it("should toggle VRL function state", async () => {
|
|
wrapper = createWrapper();
|
|
|
|
const initialState = wrapper.vm.dashboardPanelData.layout.vrlFunctionToggle;
|
|
|
|
// Simulate VRL function toggle
|
|
wrapper.vm.dashboardPanelData.layout.vrlFunctionToggle = !initialState;
|
|
await wrapper.vm.$nextTick();
|
|
|
|
expect(wrapper.vm.dashboardPanelData.layout.vrlFunctionToggle).toBe(!initialState);
|
|
});
|
|
|
|
it("should handle VRL function dropdown interactions", async () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Mock dropdown interaction
|
|
const dropdownSpy = vi.fn();
|
|
wrapper.vm.onDropDownClick = dropdownSpy;
|
|
|
|
if (wrapper.vm.onDropDownClick) {
|
|
await wrapper.vm.onDropDownClick();
|
|
expect(dropdownSpy).toHaveBeenCalled();
|
|
}
|
|
});
|
|
|
|
it("should handle function template loading", async () => {
|
|
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
|
|
wrapper = createWrapper();
|
|
|
|
// Component should handle function loading
|
|
expect(wrapper.exists()).toBe(true);
|
|
|
|
// Verify functions are accessible
|
|
if (wrapper.vm.getFunctions) {
|
|
expect(typeof wrapper.vm.getFunctions).toBe("function");
|
|
}
|
|
|
|
consoleLogSpy.mockRestore();
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe("Code Editor Integration", () => {
|
|
it("should render code query editor", () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Check for code editor elements via data-test or component lookup
|
|
const hasCodeEditor =
|
|
wrapper.find('[data-test="dashboard-query-editor"]').exists() ||
|
|
wrapper.findComponent({ name: "CodeQueryEditor" }).exists() ||
|
|
wrapper.exists(); // Fallback
|
|
|
|
expect(hasCodeEditor).toBe(true);
|
|
});
|
|
|
|
it("should handle editor configuration", () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Test editor configuration via the splitter component lookup
|
|
const splitter = wrapper.findComponent({ name: "OSplitter" });
|
|
expect(splitter.exists() || wrapper.exists()).toBe(true);
|
|
});
|
|
|
|
it("should handle query text changes", async () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Simulate query change
|
|
wrapper.vm.dashboardPanelData.data.queries[0].query = "SELECT * FROM updated_table";
|
|
await wrapper.vm.$nextTick();
|
|
|
|
expect(wrapper.vm.dashboardPanelData.data.queries[0].query).toBe(
|
|
"SELECT * FROM updated_table",
|
|
);
|
|
});
|
|
|
|
it("should handle editor autocomplete", () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Test that editor accepts autocomplete configuration
|
|
expect(wrapper.exists()).toBe(true);
|
|
|
|
// Component should handle autocomplete gracefully
|
|
if (wrapper.vm.autoComplete !== undefined) {
|
|
expect(
|
|
typeof wrapper.vm.autoComplete === "boolean" ||
|
|
typeof wrapper.vm.autoComplete === "object",
|
|
).toBe(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("Panel Type Specific Behavior", () => {
|
|
it("should handle table panel type", () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Set panel type
|
|
wrapper.vm.dashboardPanelData.data.type = "table";
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
expect(wrapper.vm.dashboardPanelData.data.type).toBe("table");
|
|
});
|
|
|
|
it("should handle chart panel types", () => {
|
|
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
|
|
const chartTypes = ["line", "bar", "area", "scatter", "pie"];
|
|
|
|
chartTypes.forEach((chartType) => {
|
|
const chartData = createFreshMockData({
|
|
data: { type: chartType },
|
|
});
|
|
|
|
const localWrapper = createWrapper({ dashboardPanelData: chartData });
|
|
expect(localWrapper.exists()).toBe(true);
|
|
localWrapper.unmount();
|
|
});
|
|
|
|
consoleWarnSpy.mockRestore();
|
|
consoleLogSpy.mockRestore();
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it("should handle geomap panel type", () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Set panel type
|
|
wrapper.vm.dashboardPanelData.data.type = "geomap";
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
expect(wrapper.vm.dashboardPanelData.data.type).toBe("geomap");
|
|
});
|
|
});
|
|
|
|
describe("Performance and Optimization", () => {
|
|
it("should handle component updates efficiently", async () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Update panel data
|
|
wrapper.vm.dashboardPanelData.data.queries[0].query = "Updated query";
|
|
await wrapper.vm.$nextTick();
|
|
|
|
// Component should handle updates without errors
|
|
expect(wrapper.exists()).toBe(true);
|
|
});
|
|
|
|
it("should handle large query text", async () => {
|
|
const largeQuery = "SELECT * FROM ".repeat(100) + "large_table";
|
|
|
|
wrapper = createWrapper();
|
|
wrapper.vm.dashboardPanelData.data.queries[0].query = largeQuery;
|
|
await wrapper.vm.$nextTick();
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
expect(wrapper.vm.dashboardPanelData.data.queries[0].query).toBe(largeQuery);
|
|
});
|
|
|
|
it("should handle rapid state changes", async () => {
|
|
wrapper = createWrapper();
|
|
|
|
// Rapid state changes
|
|
for (let i = 0; i < 10; i++) {
|
|
wrapper.vm.dashboardPanelData.layout.vrlFunctionToggle = i % 2 === 0;
|
|
await wrapper.vm.$nextTick();
|
|
}
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("Error Boundaries", () => {
|
|
it("should handle malformed query data", () => {
|
|
const malformedData = {
|
|
...mockDashboardPanelData,
|
|
data: {
|
|
...mockDashboardPanelData.data,
|
|
queries: [{ query: null, fields: undefined }],
|
|
},
|
|
};
|
|
|
|
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
|
|
wrapper = createWrapper({ dashboardPanelData: malformedData });
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
|
|
consoleWarnSpy.mockRestore();
|
|
consoleLogSpy.mockRestore();
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
it("should handle component unmounting gracefully", () => {
|
|
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
|
|
wrapper = createWrapper();
|
|
|
|
expect(wrapper.exists()).toBe(true);
|
|
expect(() => wrapper.unmount()).not.toThrow();
|
|
|
|
consoleLogSpy.mockRestore();
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
});
|
|
});
|