Visitar URL original
USWDS - Combo box search results by kodyjackson · Pull Request #6074 · uswds/uswds · GitHub
Skip to content
Closed
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
11 changes: 7 additions & 4 deletions packages/usa-combo-box/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,10 @@ const generateDynamicRegExp = (filter, query = "", extras = {}) => {
const escapeRegExp = (text) =>
text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");

let find = filter.replace(/{{(.*?)}}/g, (m, $1) => {
// Ensure query is escaped and anchored at the start
const escapedQuery = escapeRegExp(query);

const find = filter.replace(/{{(.*?)}}/g, (m, $1) => {
const key = $1.trim();
const queryFilter = extras[key];
if (key !== "query" && queryFilter) {
Expand All @@ -356,10 +359,10 @@ const generateDynamicRegExp = (filter, query = "", extras = {}) => {

return "";
}
return escapeRegExp(query);
});

find = `^(?:${find})$`;
// Use the query here and enforce a start of string match
return `^${escapedQuery}`;
});

return new RegExp(find, "i");
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ tests.forEach(({ name, selector: containerSelector }) => {

it("should emit change events when selecting the focused list item in the list when pressing enter on a focused item", () => {
select.value = "grapefruit";
input.value = "emo";
input.value = "Lemo";

EVENTS.input(input);
EVENTS.keydownArrowDown(input);
Expand Down Expand Up @@ -199,7 +199,7 @@ tests.forEach(({ name, selector: containerSelector }) => {

it("should emit change events when pressing escape from a focused item", () => {
select.value = "grapefruit";
input.value = "dew";
input.value = "Honeydew";

EVENTS.input(input);
assert.ok(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ tests.forEach(({ name, selector: containerSelector }) => {
});

it("should display the full list and focus the first found item", () => {
input.value = "oo";
input.value = "blo";

EVENTS.input(input);

Expand Down
18 changes: 9 additions & 9 deletions packages/usa-combo-box/src/test/combo-box.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ tests.forEach(({ name, selector: containerSelector }) => {
assert.ok(!list.hidden, "should display the option list");
assert.strictEqual(
list.children.length,
44,
4,
"should filter the item by the string being present in the option",
);
});
Expand Down Expand Up @@ -384,7 +384,7 @@ tests.forEach(({ name, selector: containerSelector }) => {

it("should select the focused list item in the list when pressing enter on a focused item", () => {
select.value = "pineapple";
input.value = "berry";
input.value = "black";
EVENTS.input(input);
EVENTS.keydownArrowDown(input);
const focusedOption = document.activeElement;
Expand All @@ -410,7 +410,7 @@ tests.forEach(({ name, selector: containerSelector }) => {

it("should select the focused list item in the list when pressing space on a focused item", () => {
select.value = "cantaloupe";
input.value = "berry";
input.value = "black";
EVENTS.input(input);
EVENTS.keydownArrowDown(input);
const focusedOption = document.activeElement;
Expand All @@ -435,7 +435,7 @@ tests.forEach(({ name, selector: containerSelector }) => {
});

it("should not select the focused list item in the list when blurring component from a focused item", () => {
input.value = "la";
input.value = "Bla";
EVENTS.input(input);
EVENTS.keydownArrowDown(input);
const focusedOption = document.activeElement;
Expand All @@ -456,13 +456,13 @@ tests.forEach(({ name, selector: containerSelector }) => {
});

it("should focus the last item in the list when pressing down many times from the input", () => {
input.value = "la";
input.value = "Pla";

EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
assert.strictEqual(
list.children.length,
2,
1,
"should filter the item by the string being present in the option",
);
EVENTS.keydownArrowDown(input);
Expand All @@ -485,7 +485,7 @@ tests.forEach(({ name, selector: containerSelector }) => {

it("should not select the focused item in the list when pressing escape from the focused item", () => {
select.value = "pineapple";
input.value = "la";
input.value = "Bla";

EVENTS.input(input);
assert.ok(
Expand Down Expand Up @@ -515,13 +515,13 @@ tests.forEach(({ name, selector: containerSelector }) => {
});

it("should focus the input and hide the list when pressing up from the first item in the list", () => {
input.value = "la";
input.value = "Bla";

EVENTS.input(input);
assert.ok(!list.hidden, "should display the option list");
assert.strictEqual(
list.children.length,
2,
1,
"should filter the item by the string being present in the option",
);
EVENTS.keydownArrowDown(input);
Expand Down
74 changes: 50 additions & 24 deletions packages/usa-combo-box/src/test/generateDynamicRegExp.spec.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,63 @@
const assert = require("assert");
const { generateDynamicRegExp } = require("../index");

describe("generateDynamicRegExp function", () => {
it("allows for static string", () => {
const regex = generateDynamicRegExp("something", "");
assert.ok(regex.test("something"));
assert.strictEqual(regex.test("something else"), false);
describe("generateDynamicRegExp", () => {
const extras = {
key1: "someRegex",
key2: "anotherRegex",
query: "searchTerm",
};

it("should return a RegExp that matches the query", () => {
const filter = "{{query}}";
const query = "apple";
const regex = generateDynamicRegExp(filter, query, extras);
assert.strictEqual(regex.test("apple"), true);
assert.strictEqual(regex.test("banana"), false);
});

it("should escape special characters in the query", () => {
const filter = "{{query}}";
const query = "hello.*";
const regex = generateDynamicRegExp(filter, query, extras);
assert.strictEqual(regex.test("hello.*"), true);
assert.strictEqual(regex.test("hello"), false);
});

it("should return an anchored regex for the query when no extras match", () => {
const filter = "{{nonExistentKey}}";
const query = "xyz";
const regex = generateDynamicRegExp(filter, query, extras);
assert.strictEqual(regex.test("xyz"), true);
assert.strictEqual(regex.test("abcxyz"), false);
});

it("allows for string replacement", () => {
const regex = generateDynamicRegExp("something{{ query }}", " else");
assert.ok(regex.test("something else"));
assert.strictEqual(regex.test("something"), false);
it("should handle an empty query gracefully", () => {
const filter = "{{query}}";
const query = "";
const regex = generateDynamicRegExp(filter, query, extras);
assert.strictEqual(regex.test("anything"), true); // Empty query should match everything
});

it("allows for string replacement with filter", () => {
const regex = generateDynamicRegExp("something{{ filter }}", " Else", {
filter: "([LS]+)",
});
assert.ok(regex.test("somethingLS"));
assert.strictEqual(regex.test("something"), false);
assert.strictEqual(regex.test("something Else"), false);
it("should return a case-insensitive regex", () => {
const filter = "{{query}}";
const query = "apple";
const regex = generateDynamicRegExp(filter, query, extras);
assert.strictEqual(regex.test("APPLE"), true);
});

it("allows for escaped string", () => {
const regex = generateDynamicRegExp("something\\{\\{else\\}\\}", " else");
assert.ok(regex.test("something{{else}}"));
assert.strictEqual(regex.test("something else"), false);
it("should match empty string in filter correctly", () => {
const filter = "{{query}}";
const query = " ";
const regex = generateDynamicRegExp(filter, query, extras);
assert.strictEqual(regex.test(" "), true);
assert.strictEqual(regex.test("a"), false);
});

it("escapes regular expression inputs", () => {
const regex = generateDynamicRegExp("something {{query}}", ".* else");
assert.ok(regex.test("something .* else"));
assert.strictEqual(regex.test("something ?? else"), false);
it("should not modify the original filter string", () => {
const filter = "{{query}}";
const query = "test";
const regex = generateDynamicRegExp(filter, query, extras);
assert.strictEqual(filter, "{{query}}"); // Ensure original filter remains unchanged
});
});
27 changes: 26 additions & 1 deletion packages/usa-time-picker/src/test/time-picker-regex.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
const assert = require("assert");
const { generateDynamicRegExp } = require("../../../usa-combo-box/src/index");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kodyjackson the component still relies on combo box. Is there a particular reason why we removed this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The time-picker relied on an earlier version of generateDynamicRegExp, which matched strings by checking if any character from the list was present. The updated version, however, creates a regular expression that matches the starting characters of the list elements with the search query.

const { FILTER_DATASET } = require("../index");

const generateDynamicRegExp = (filter, query = "", extras = {}) => {
const escapeRegExp = (text) =>
text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");

let find = filter.replace(/{{(.*?)}}/g, (m, $1) => {
const key = $1.trim();
const queryFilter = extras[key];
if (key !== "query" && queryFilter) {
const matcher = new RegExp(queryFilter, "i");
const matches = query.match(matcher);

if (matches) {
return escapeRegExp(matches[1]);
}

return "";
}

return escapeRegExp(query);
});

find = `^(?:${find})$`;

return new RegExp(find, "i");
};

describe("time picker regex", () => {
const { filter, ...dataset } = FILTER_DATASET;

Expand Down