Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Fixed

- We fixed an issue with filter selector dropdown not choosing the best placement on small viewports.
- We fixed an issue where selecting Empty or Not empty could cause keyboard focus to jump away from the filter controls.

## [3.9.0] - 2026-03-23

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import "@testing-library/jest-dom";
import { act, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Big } from "big.js";
import { resetIdCounter } from "downshift";
import { AttributeMetaData } from "mendix";
import { createContext } from "react";
import { requirePlugin } from "@mendix/widget-plugin-external-events/plugin";
import { FilterAPI } from "@mendix/widget-plugin-filtering/context";
import { NumberInputFilterStore } from "@mendix/widget-plugin-filtering/stores/input/NumberInputFilterStore";
import { ObservableFilterHost } from "@mendix/widget-plugin-filtering/typings/ObservableFilterHost";
import "@testing-library/jest-dom";
import { AttributeMetaData } from "mendix";

import {
actionValue,
dynamic,
dynamicValue,
EditableValueBuilder,
ListAttributeValueBuilder
} from "@mendix/widget-plugin-test-utils";
import { act, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { createContext } from "react";

import { NumberInputFilterStore } from "@mendix/widget-plugin-filtering/stores/input/NumberInputFilterStore";
import { Big } from "big.js";
import { resetIdCounter } from "downshift";
import { DatagridNumberFilterContainerProps } from "../../../typings/DatagridNumberFilterProps";
import DatagridNumberFilter from "../../DatagridNumberFilter";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Fixed

- We fixed an issue with filter selector dropdown not choosing the best placement on small viewports.
- We fixed an issue where selecting Empty or Not empty could cause keyboard focus to jump away from the filter controls.

## [3.8.1] - 2026-02-19

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { FilterAPI } from "@mendix/widget-plugin-filtering/context";
import { ObservableFilterHost } from "@mendix/widget-plugin-filtering/typings/ObservableFilterHost";
import "@testing-library/jest-dom";

import { act, render, screen } from "@testing-library/react";
Expand All @@ -8,7 +6,9 @@ import { resetIdCounter } from "downshift";
import { AttributeMetaData } from "mendix";
import { createContext } from "react";
import { requirePlugin } from "@mendix/widget-plugin-external-events/plugin";
import { FilterAPI } from "@mendix/widget-plugin-filtering/context";
import { StringInputFilterStore } from "@mendix/widget-plugin-filtering/stores/input/StringInputFilterStore";
import { ObservableFilterHost } from "@mendix/widget-plugin-filtering/typings/ObservableFilterHost";
import {
actionValue,
dynamicValue,
Expand Down Expand Up @@ -237,6 +237,19 @@ describe("Text Filter", () => {
expect(attribute.setValue).toHaveBeenLastCalledWith(undefined);
});

it("keeps focus in filter controls when empty operator is selected", async () => {
render(<DatagridTextFilter {...commonProps} />);

const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
const triggerButton = screen.getByRole("combobox", { name: "Equal" });

await user.click(triggerButton);
await user.click(screen.getByRole("option", { name: "Empty" }));

expect(screen.getByRole("textbox")).toBeDisabled();
expect(document.body).not.toHaveFocus();
});

afterAll(() => {
(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ exports[`Text Filter with single instance with multiple attributes renders corre
>
<button
aria-activedescendant=""
aria-controls="downshift-:r8:-menu"
aria-controls="downshift-:r9:-menu"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Equal"
class="btn btn-default filter-selector-button button-icon equal"
id="downshift-:r8:-toggle-button"
id="downshift-:r9:-toggle-button"
role="combobox"
tabindex="0"
>
Expand All @@ -28,7 +28,7 @@ exports[`Text Filter with single instance with multiple attributes renders corre
<ul
aria-label="Select filter type"
class="filter-selectors hidden"
id="downshift-:r8:-menu"
id="downshift-:r9:-menu"
role="listbox"
style="position: fixed; left: 0px; top: 0px; transform: translate(0px, 0px);"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { describe, expect, it, jest } from "@jest/globals";
import { NumberFilterController } from "../controllers/input/NumberInputController";
import { Number_InputFilterInterface } from "../typings/InputFilterInterface";

describe("NumberFilterController", () => {
function createFilter(): Number_InputFilterInterface {
return {
storeType: "input",
filterFunction: "equal",
defaultState: ["equal"],
arg1: {
type: "number",
value: undefined,
displayValue: "42",
isValid: true
},
arg2: {
type: "number",
value: undefined,
displayValue: "",
isValid: true
},
reset: jest.fn(),
clear: jest.fn(),
UNSAFE_overwriteFilterFunction: jest.fn(),
UNSAFE_setDefaults: jest.fn()
};
}

it("focuses input for value-based operators", () => {
const controller = new NumberFilterController({
filter: createFilter(),
defaultFilter: "equal",
adjustableFilterFunction: true
});

const focus = jest.fn();
Object.defineProperty(controller.inputRef, "current", {
value: { focus },
writable: true
});

controller.handleFilterFnChange("greater");

expect(focus).toHaveBeenCalledTimes(1);
});

it("does not focus input and clears value for empty", () => {
const controller = new NumberFilterController({
filter: createFilter(),
defaultFilter: "equal",
adjustableFilterFunction: true
});

const focus = jest.fn();
Object.defineProperty(controller.inputRef, "current", {
value: { focus },
writable: true
});

controller.handleFilterFnChange("empty");

expect(controller.input1.value).toBe("");
expect(focus).not.toHaveBeenCalled();
});

it("does not focus input and clears value for notEmpty", () => {
const controller = new NumberFilterController({
filter: createFilter(),
defaultFilter: "equal",
adjustableFilterFunction: true
});

const focus = jest.fn();
Object.defineProperty(controller.inputRef, "current", {
value: { focus },
writable: true
});

controller.handleFilterFnChange("notEmpty");

expect(controller.input1.value).toBe("");
expect(focus).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { describe, expect, it, jest } from "@jest/globals";
import { StringFilterController } from "../controllers/input/StringInputController";
import { String_InputFilterInterface } from "../typings/InputFilterInterface";

describe("StringFilterController", () => {
function createFilter(): String_InputFilterInterface {
return {
storeType: "input",
filterFunction: "equal",
defaultState: ["equal"],
arg1: {
type: "string",
value: undefined,
displayValue: "initial",
isValid: true
},
arg2: {
type: "string",
value: undefined,
displayValue: "",
isValid: true
},
reset: jest.fn(),
clear: jest.fn(),
UNSAFE_overwriteFilterFunction: jest.fn(),
UNSAFE_setDefaults: jest.fn()
};
}

it("focuses input for value-based operators", () => {
const controller = new StringFilterController({
filter: createFilter(),
defaultFilter: "equal",
adjustableFilterFunction: true
});

const focus = jest.fn();
Object.defineProperty(controller.inputRef, "current", {
value: { focus },
writable: true
});

controller.handleFilterFnChange("contains");

expect(focus).toHaveBeenCalledTimes(1);
});

it("does not focus input and clears value for empty", () => {
const controller = new StringFilterController({
filter: createFilter(),
defaultFilter: "equal",
adjustableFilterFunction: true
});

const focus = jest.fn();
Object.defineProperty(controller.inputRef, "current", {
value: { focus },
writable: true
});

controller.handleFilterFnChange("empty");

expect(controller.input1.value).toBe("");
expect(focus).not.toHaveBeenCalled();
});

it("does not focus input and clears value for notEmpty", () => {
const controller = new StringFilterController({
filter: createFilter(),
defaultFilter: "equal",
adjustableFilterFunction: true
});

const focus = jest.fn();
Object.defineProperty(controller.inputRef, "current", {
value: { focus },
writable: true
});

controller.handleFilterFnChange("notEmpty");

expect(controller.input1.value).toBe("");
expect(focus).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export class NumberFilterController {
this.filter.filterFunction = fn;
if (fn === "empty" || fn === "notEmpty") {
this.input1.setValue("");
return;
}
this.inputRef.current?.focus();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class StringFilterController {
this.filter.filterFunction = fn;
if (fn === "empty" || fn === "notEmpty") {
this.input1.setValue("");
return;
}
this.inputRef.current?.focus();
};
Expand Down
Loading