Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ You can also check the
rare cases
- Sorting of values in the multi filter panel is now consistent with the rest
of the application
- Cascading filters in the editing mode now work correctly again and not
exclude the last filter anymore
- Styles
- Added missing paddings in details panel autocomplete component and in banner
component for smaller breakpoints
Expand Down
196 changes: 195 additions & 1 deletion app/rdf/query-dimension-values.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import rdf from "rdf-ext";
import ParsingClient from "sparql-http-client/ParsingClient";
import { describe, expect, it, vi } from "vitest";

import { FilterValue } from "@/config-types";
import { Filters, FilterValue } from "@/config-types";
import * as ns from "@/rdf/namespace";
import {
getFiltersList,
getQueryFilters,
loadDimensionValuesWithMetadata,
} from "@/rdf/query-dimension-values";
Expand Down Expand Up @@ -98,3 +99,196 @@ describe("getQueryFilters", () => {
expect(queryPart).toContain("<http://example.com/dimension1>");
});
});

describe("getFiltersList", () => {
describe("when no filters are provided", () => {
it("should return empty array for undefined filters", () => {
const result = getFiltersList(undefined, "http://example.com/dimension1");
expect(result).toEqual([]);
});

it("should return empty array for empty filters object", () => {
const result = getFiltersList({}, "http://example.com/dimension1");
expect(result).toEqual([]);
});
});

describe("normal usage pattern (full filter set)", () => {
const filters: Filters = {
"http://example.com/dimension1": { type: "single", value: "value1" },
"http://example.com/dimension2": { type: "single", value: "value2" },
"http://example.com/dimension3": {
type: "multi",
values: { value3a: true, value3b: true },
},
"http://example.com/dimension4": { type: "single", value: "value4" },
};

it("should return filters before the current dimension (first dimension)", () => {
const result = getFiltersList(filters, "http://example.com/dimension1");
expect(result).toEqual([]);
});

it("should return filters before the current dimension (middle dimension)", () => {
const result = getFiltersList(filters, "http://example.com/dimension3");
expect(result).toEqual([
["http://example.com/dimension1", { type: "single", value: "value1" }],
["http://example.com/dimension2", { type: "single", value: "value2" }],
]);
});

it("should return filters before the current dimension (last dimension)", () => {
const result = getFiltersList(filters, "http://example.com/dimension4");
expect(result).toEqual([
["http://example.com/dimension1", { type: "single", value: "value1" }],
["http://example.com/dimension2", { type: "single", value: "value2" }],
[
"http://example.com/dimension3",
{ type: "multi", values: { value3a: true, value3b: true } },
],
]);
});

it("should handle mixed filter types correctly", () => {
const mixedFilters: Filters = {
"http://example.com/filter1": { type: "single", value: "single_value" },
"http://example.com/filter2": {
type: "multi",
values: { multi1: true, multi2: true },
},
"http://example.com/filter3": {
type: "range",
from: "2020",
to: "2023",
},
"http://example.com/current": {
type: "single",
value: "current_value",
},
};

const result = getFiltersList(mixedFilters, "http://example.com/current");
expect(result).toEqual([
[
"http://example.com/filter1",
{ type: "single", value: "single_value" },
],
[
"http://example.com/filter2",
{ type: "multi", values: { multi1: true, multi2: true } },
],
[
"http://example.com/filter3",
{ type: "range", from: "2020", to: "2023" },
],
]);
});
});

describe("pre-sliced usage pattern (DataFilterSelectGeneric)", () => {
const preSlicedFilters: Filters = {
"http://example.com/constraint1": { type: "single", value: "value1" },
"http://example.com/constraint2": {
type: "multi",
values: { value2a: true, value2b: true },
},
};

it("should return all filters when current dimension is not found (pre-sliced case)", () => {
const result = getFiltersList(
preSlicedFilters,
"http://example.com/target_dimension"
);
expect(result).toEqual([
["http://example.com/constraint1", { type: "single", value: "value1" }],
[
"http://example.com/constraint2",
{ type: "multi", values: { value2a: true, value2b: true } },
],
]);
});

it("should preserve filter order when using all filters", () => {
const orderedFilters: Filters = {
"http://example.com/first": { type: "single", value: "first_value" },
"http://example.com/second": { type: "single", value: "second_value" },
"http://example.com/third": { type: "single", value: "third_value" },
};

const result = getFiltersList(
orderedFilters,
"http://example.com/not_found"
);
expect(result).toEqual([
["http://example.com/first", { type: "single", value: "first_value" }],
[
"http://example.com/second",
{ type: "single", value: "second_value" },
],
["http://example.com/third", { type: "single", value: "third_value" }],
]);
});
});

describe("edge cases", () => {
it("should handle single filter correctly", () => {
const singleFilter: Filters = {
"http://example.com/only_filter": {
type: "single",
value: "only_value",
},
};

const result = getFiltersList(
singleFilter,
"http://example.com/not_found"
);
expect(result).toEqual([
[
"http://example.com/only_filter",
{ type: "single", value: "only_value" },
],
]);
});

it("should return empty array when current dimension is the only filter", () => {
const singleFilter: Filters = {
"http://example.com/current": {
type: "single",
value: "current_value",
},
};

const result = getFiltersList(singleFilter, "http://example.com/current");
expect(result).toEqual([]);
});

it("should handle filters with special characters in IRIs", () => {
const specialFilters: Filters = {
"https://example.com/path/to/dimension?param=1": {
type: "single",
value: "value1",
},
"https://example.com/path/to/dimension#fragment": {
type: "single",
value: "value2",
},
};

const result = getFiltersList(
specialFilters,
"http://example.com/target"
);
expect(result).toEqual([
[
"https://example.com/path/to/dimension?param=1",
{ type: "single", value: "value1" },
],
[
"https://example.com/path/to/dimension#fragment",
{ type: "single", value: "value2" },
],
]);
});
});
});
22 changes: 13 additions & 9 deletions app/rdf/query-dimension-values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ const parseDimensionValue = (
};

const parseMaybeUndefined = (value: string, fallbackValue: string) => {
return value === ns.cube.Undefined.value ? "-" : fallbackValue ?? value;
return value === ns.cube.Undefined.value ? "-" : (fallbackValue ?? value);
};

type LoadMaxDimensionValuesProps = Omit<LoadDimensionValuesProps, "locale">;
Expand Down Expand Up @@ -463,21 +463,25 @@ ${getQueryFilters(filterList, cubeDimensions, dimensionIri)}`
}
}

const getFiltersList = (filters: Filters | undefined, dimensionIri: string) => {
export const getFiltersList = (
filters: Filters | undefined,
dimensionIri: string
) => {
if (!filters) {
return [];
}

const entries = Object.entries(filters);
const currentIndex = entries.findIndex(([iri]) => iri == dimensionIri);
const filteredEntries = entries.slice(
0,
// Make sure to not exclude the last filter in case of pre-sliced filters
currentIndex >= 0 ? currentIndex : undefined
);

// Consider filters before the current filter to fetch the values for
// the current filter
return sortBy(
entries.slice(
0,
entries.findIndex(([iri]) => iri == dimensionIri)
),
([, v]) => getFilterOrder(v)
);
return sortBy(filteredEntries, ([, v]) => getFilterOrder(v));
};

export const getQueryFilters = (
Expand Down