Files
formbricks/apps/web/modules/ee/analysis/lib/query-builder.test.ts
T
Dhruwang Jariwala ffd4478184 chore: merge epic/dashboards into epic/v5 (#7798)
Signed-off-by: gulshank0 <gulshanbahadur002@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Theodór Tómas <theodortomas@gmail.com>
Co-authored-by: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com>
Co-authored-by: Bhagya Amarasinghe <b.sithumini@yahoo.com>
Co-authored-by: Chowdhury Tafsir Ahmed Siddiki <ctafsiras@gmail.com>
Co-authored-by: neila <40727091+neila@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Tiago <1585571+xernobyl@users.noreply.github.com>
Co-authored-by: Harsh Bhat <90265455+harshsbhat@users.noreply.github.com>
Co-authored-by: Harsh Bhat <harshbhat@Harshs-MacBook-Air.local>
Co-authored-by: Johannes <johannes@formbricks.com>
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
Co-authored-by: Balázs Úr <balazs@urbalazs.hu>
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
Co-authored-by: Gulshan <gulshanbahadur002@gmail.com>
Co-authored-by: Tiago Farto <tiago@formbricks.com>
Co-authored-by: Harsh Bhat <harsh121102@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 12:24:05 +04:00

227 lines
8.0 KiB
TypeScript

import { describe, expect, test } from "vitest";
import type { TChartQuery } from "@formbricks/types/analysis";
import { type ChartBuilderState, buildCubeQuery, parseQueryToState } from "./query-builder";
describe("query-builder", () => {
describe("buildCubeQuery", () => {
test("builds minimal query with measures only", () => {
const config: ChartBuilderState = {
selectedMeasures: ["FeedbackRecords.count"],
selectedDimensions: [],
filters: [],
filterLogic: "and",
timeDimension: null,
};
const query = buildCubeQuery(config);
expect(query.measures).toEqual(["FeedbackRecords.count"]);
expect(query.dimensions).toBeUndefined();
expect(query.timeDimensions).toBeUndefined();
expect(query.filters).toBeUndefined();
});
test("adds dimensions when present", () => {
const config: ChartBuilderState = {
selectedMeasures: ["FeedbackRecords.count"],
selectedDimensions: ["FeedbackRecords.sentiment"],
filters: [],
filterLogic: "and",
timeDimension: null,
};
const query = buildCubeQuery(config);
expect(query.dimensions).toEqual(["FeedbackRecords.sentiment"]);
});
test("adds time dimension with string dateRange", () => {
const config: ChartBuilderState = {
selectedMeasures: ["FeedbackRecords.count"],
selectedDimensions: [],
filters: [],
filterLogic: "and",
timeDimension: {
dimension: "FeedbackRecords.collectedAt",
granularity: "day",
dateRange: "last 30 days",
},
};
const query = buildCubeQuery(config);
expect(query.timeDimensions).toEqual([
{ dimension: "FeedbackRecords.collectedAt", granularity: "day", dateRange: "last 30 days" },
]);
});
test("adds time dimension without granularity (filter only)", () => {
const config: ChartBuilderState = {
selectedMeasures: ["FeedbackRecords.count"],
selectedDimensions: [],
filters: [],
filterLogic: "and",
timeDimension: {
dimension: "FeedbackRecords.collectedAt",
dateRange: "last 30 days",
},
};
const query = buildCubeQuery(config);
expect(query.timeDimensions).toEqual([
{ dimension: "FeedbackRecords.collectedAt", dateRange: "last 30 days" },
]);
});
test("adds time dimension with Date array dateRange", () => {
const config: ChartBuilderState = {
selectedMeasures: ["FeedbackRecords.count"],
selectedDimensions: [],
filters: [],
filterLogic: "and",
timeDimension: {
dimension: "FeedbackRecords.collectedAt",
granularity: "month",
dateRange: [new Date("2024-01-15"), new Date("2024-06-20")],
},
};
const query = buildCubeQuery(config);
expect(query.timeDimensions).toEqual([
{
dimension: "FeedbackRecords.collectedAt",
granularity: "month",
dateRange: ["2024-01-15", "2024-06-20"],
},
]);
});
test("adds AND filters as member filters", () => {
const config: ChartBuilderState = {
selectedMeasures: ["FeedbackRecords.count"],
selectedDimensions: [],
filters: [
{ id: "f1", field: "FeedbackRecords.sentiment", operator: "equals", values: ["positive"] },
{ id: "f2", field: "FeedbackRecords.sourceType", operator: "set", values: null },
],
filterLogic: "and",
timeDimension: null,
};
const query = buildCubeQuery(config);
expect(query.filters).toEqual([
{ member: "FeedbackRecords.sentiment", operator: "equals", values: ["positive"] },
{ member: "FeedbackRecords.sourceType", operator: "set" },
]);
});
test("adds OR filters wrapped in or", () => {
const config: ChartBuilderState = {
selectedMeasures: ["FeedbackRecords.count"],
selectedDimensions: [],
filters: [{ id: "f1", field: "FeedbackRecords.sentiment", operator: "equals", values: ["positive"] }],
filterLogic: "or",
timeDimension: null,
};
const query = buildCubeQuery(config);
expect(query.filters).toEqual([
{
or: [{ member: "FeedbackRecords.sentiment", operator: "equals", values: ["positive"] }],
},
]);
});
});
describe("parseQueryToState", () => {
test("parses minimal query", () => {
const state = parseQueryToState({ measures: ["FeedbackRecords.count"] });
expect(state.selectedMeasures).toEqual(["FeedbackRecords.count"]);
expect(state.selectedDimensions).toEqual([]);
expect(state.filters).toEqual([]);
expect(state.filterLogic).toBe("and");
expect(state.timeDimension).toBeNull();
});
test("parses AND member filters", () => {
const query = {
measures: ["FeedbackRecords.count"],
filters: [{ member: "FeedbackRecords.sentiment", operator: "equals", values: ["positive"] }],
};
const state = parseQueryToState(query);
expect(state.filterLogic).toBe("and");
expect(state.filters?.map(({ field, operator, values }) => ({ field, operator, values }))).toEqual([
{ field: "FeedbackRecords.sentiment", operator: "equals", values: ["positive"] },
]);
});
test("parses OR filters", () => {
const query = {
measures: ["FeedbackRecords.count"],
filters: [
{
or: [{ member: "FeedbackRecords.sentiment", operator: "equals", values: ["positive"] }],
},
],
};
const state = parseQueryToState(query);
expect(state.filterLogic).toBe("or");
expect(state.filters?.map(({ field, operator, values }) => ({ field, operator, values }))).toEqual([
{ field: "FeedbackRecords.sentiment", operator: "equals", values: ["positive"] },
]);
});
test("parses time dimension with granularity and dateRange", () => {
const query: TChartQuery = {
measures: ["FeedbackRecords.count"],
timeDimensions: [
{
dimension: "FeedbackRecords.collectedAt",
granularity: "day",
dateRange: "last 30 days",
},
],
};
const state = parseQueryToState(query);
expect(state.timeDimension).toEqual({
dimension: "FeedbackRecords.collectedAt",
granularity: "day",
dateRange: "last 30 days",
});
});
test("parses time dimension without granularity (filter only)", () => {
const query = {
measures: ["FeedbackRecords.count"],
timeDimensions: [
{
dimension: "FeedbackRecords.collectedAt",
dateRange: "last 30 days",
},
],
};
const state = parseQueryToState(query);
expect(state.timeDimension).toEqual({
dimension: "FeedbackRecords.collectedAt",
dateRange: "last 30 days",
});
});
});
describe("round-trip", () => {
test("buildCubeQuery then parseQueryToState restores state", () => {
const config: ChartBuilderState = {
selectedMeasures: ["FeedbackRecords.count"],
selectedDimensions: ["FeedbackRecords.sentiment"],
filters: [{ id: "f1", field: "FeedbackRecords.sourceType", operator: "equals", values: ["survey"] }],
filterLogic: "and",
timeDimension: {
dimension: "FeedbackRecords.collectedAt",
granularity: "week",
dateRange: "last 7 days",
},
};
const query = buildCubeQuery(config);
const restored = parseQueryToState(query);
expect(restored.selectedMeasures).toEqual(config.selectedMeasures);
expect(restored.selectedDimensions).toEqual(config.selectedDimensions);
expect(restored.filterLogic).toBe(config.filterLogic);
expect(restored.timeDimension).toEqual(config.timeDimension);
expect(restored.filters?.map(({ field, operator, values }) => ({ field, operator, values }))).toEqual(
config.filters.map(({ field, operator, values }) => ({ field, operator, values }))
);
});
});
});