Skip to content

Commit cbbaec3

Browse files
Fix creating duplicates with trailing spaces when also filtering.
A previous fix (b19f6b9) addressed the issue of not using the trimmed input when checking options for an existing match. However, it did not account for the options potentially being filtered first (via 'filterable') based on the untrimmed input. This led to no options available to check existence against in 'creatable' and so a duplicate option was still created. Input values are now trimmed consistently in the create options logic to ensure correct behavior when filtering and determining existence. Add a corresponding unit test to validate.
1 parent 56b3696 commit cbbaec3

2 files changed

Lines changed: 48 additions & 5 deletions

File tree

src/create-options.test.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
2+
import { describe, it, expect } from "vitest";
3+
import { createOptions } from "./create-options";
4+
5+
describe("createOptions", () => {
6+
it("should not offer to create a duplicate option when input has trailing space", () => {
7+
const { options } = createOptions(["Apple", "Banana"], {
8+
createable: true,
9+
filterable: true,
10+
format: (value, type, meta) => {
11+
if (type === "label") {
12+
return `${meta.prefix || ""}${value}`;
13+
}
14+
return value as any;
15+
},
16+
});
17+
18+
// Case 1: Exact match "Apple"
19+
const results1 = options("Apple");
20+
// Should pass filter and be present
21+
expect(results1.some((o) => o.text === "Apple")).toBe(true);
22+
// Should NOT offer to "Create Apple"
23+
expect(
24+
results1.some((o) => (o.label as string).includes("Create"))
25+
).toBe(false);
26+
27+
// Case 2: Trailing space "Apple "
28+
const results2 = options("Apple ");
29+
30+
// If the bug exists:
31+
// 1. "Apple" is filtered out (because "Apple " != "Apple").
32+
// 2. createable sees no "Apple", so it adds "Create Apple".
33+
// 3. results2 contains "Create Apple".
34+
35+
// Check if we have the "Create Apple" option
36+
// We Expect NOT to have it.
37+
const hasCreateOption = results2.some((o) =>
38+
(o.label as string).includes("Create")
39+
);
40+
41+
expect(hasCreateOption).toBe(false);
42+
});
43+
});

src/create-options.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ const createOptions = (
104104
config.disable(resolveValue(value));
105105

106106
const options = (inputValue: string) => {
107+
const trimmedValue = inputValue.trim();
107108
const initialValues =
108109
typeof values === "function" ? values(inputValue) : values;
109110

@@ -116,11 +117,11 @@ const createOptions = (
116117
};
117118
});
118119

119-
if (config.filterable && inputValue) {
120+
if (config.filterable && trimmedValue) {
120121
if (typeof config.filterable === "function") {
121-
createdOptions = config.filterable(inputValue, createdOptions);
122+
createdOptions = config.filterable(trimmedValue, createdOptions);
122123
} else {
123-
createdOptions = fuzzySort(inputValue, createdOptions, "text").map(
124+
createdOptions = fuzzySort(trimmedValue, createdOptions, "text").map(
124125
(result) => ({
125126
...result.item,
126127
label: format(result.item.value, "label", {
@@ -132,7 +133,6 @@ const createOptions = (
132133
}
133134

134135
if (config.createable !== undefined) {
135-
const trimmedValue = inputValue.trim();
136136
const exists = createdOptions.some((option) =>
137137
areEqualIgnoringCase(trimmedValue, option.text),
138138
);
@@ -210,4 +210,4 @@ export type {
210210
CreateOptionsFormatFunction,
211211
CreateOptionsFilterableFunction,
212212
CreateOptionsCreateableFunction,
213-
};
213+
};

0 commit comments

Comments
 (0)