diff --git a/package-lock.json b/package-lock.json
index 165f2bc..eeb57ac 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,18 +8,22 @@
"name": "autocomplete-component",
"version": "0.1.0",
"dependencies": {
- "@testing-library/jest-dom": "^5.17.0",
- "@testing-library/react": "^13.4.0",
- "@testing-library/user-event": "^13.5.0",
- "@types/jest": "^27.5.2",
- "@types/node": "^16.18.70",
- "@types/react": "^18.2.47",
- "@types/react-dom": "^18.2.18",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
+ "@babel/plugin-transform-private-property-in-object": "^7.23.4",
+ "@testing-library/jest-dom": "5.17.0",
+ "@testing-library/react": "13.4.0",
+ "@testing-library/user-event": "13.5.0",
+ "@types/jest": "27.5.2",
+ "@types/node": "16.18.70",
+ "@types/react": "18.2.47",
+ "@types/react-dom": "18.2.18",
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
"react-scripts": "5.0.1",
- "typescript": "^4.9.5",
- "web-vitals": "^2.1.4"
+ "typescript": "4.9.5",
+ "web-vitals": "2.1.4"
+ },
+ "devDependencies": {
+ "@babel/plugin-proposal-private-property-in-object": "^7.21.11"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -648,9 +652,17 @@
}
},
"node_modules/@babel/plugin-proposal-private-property-in-object": {
- "version": "7.21.0-placeholder-for-preset-env.2",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
- "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
+ "version": "7.21.11",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz",
+ "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==",
+ "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.18.6",
+ "@babel/helper-create-class-features-plugin": "^7.21.0",
+ "@babel/helper-plugin-utils": "^7.20.2",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5"
+ },
"engines": {
"node": ">=6.9.0"
},
@@ -1893,6 +1905,17 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": {
+ "version": "7.21.0-placeholder-for-preset-env.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
+ "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/preset-env/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
diff --git a/package.json b/package.json
index a9c1d17..512b735 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
+ "@babel/plugin-transform-private-property-in-object": "^7.23.4",
"@testing-library/jest-dom": "5.17.0",
"@testing-library/react": "13.4.0",
"@testing-library/user-event": "13.5.0",
@@ -39,5 +40,8 @@
"last 1 firefox version",
"last 1 safari version"
]
+ },
+ "devDependencies": {
+ "@babel/plugin-proposal-private-property-in-object": "^7.21.11"
}
}
diff --git a/src/App.test.tsx b/src/App.test.tsx
deleted file mode 100644
index 2a68616..0000000
--- a/src/App.test.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from 'react';
-import { render, screen } from '@testing-library/react';
-import App from './App';
-
-test('renders learn react link', () => {
- render();
- const linkElement = screen.getByText(/learn react/i);
- expect(linkElement).toBeInTheDocument();
-});
diff --git a/src/components/autoComplete.test.tsx b/src/components/autoComplete.test.tsx
new file mode 100644
index 0000000..cf2cb5b
--- /dev/null
+++ b/src/components/autoComplete.test.tsx
@@ -0,0 +1,180 @@
+import {
+ render,
+ screen,
+ fireEvent,
+ waitFor,
+ act,
+ within,
+ cleanup,
+} from "@testing-library/react";
+import AutoComplete from "./autoComplete";
+
+afterEach(() => {
+ cleanup();
+});
+
+describe("AutoComplete Component", () => {
+ it("renders the input box", () => {
+ render();
+ const searchInput = screen.getByLabelText("search-input");
+ expect(searchInput).toBeInTheDocument();
+ });
+
+ it("renders the suggestion list on input focus", async () => {
+ render();
+
+ const suggestionList = screen.queryByTestId("suggestion-list");
+ expect(suggestionList).not.toBeInTheDocument();
+
+ const searchInput = screen.getByLabelText("search-input");
+
+ act(() => {
+ searchInput.focus();
+ });
+
+ await waitFor(
+ () => {
+ const showSuggestionList = screen.queryByTestId("suggestion-list");
+ expect(showSuggestionList).toBeInTheDocument();
+ },
+ { timeout: 5000 }
+ );
+ });
+
+ it("hides the suggestion list on click outside the text box", async () => {
+ render();
+
+ const suggestionList = screen.queryByTestId("suggestion-list");
+ expect(suggestionList).not.toBeInTheDocument();
+
+ const searchInput = screen.getByLabelText("search-input");
+
+ // show the suggestion list
+ act(() => {
+ searchInput.focus();
+ });
+
+ // confirm its showing
+ await waitFor(
+ () => {
+ const showSuggestionList = screen.getByTestId("suggestion-list");
+ expect(showSuggestionList).toBeInTheDocument();
+ },
+ { timeout: 5000 }
+ );
+
+ // click out of the input box
+ fireEvent.click(document.body);
+
+ // check that the suggestion list is nolonger showiung
+ await waitFor(
+ () => {
+ const showSuggestionList = screen.queryByTestId("suggestion-list");
+ expect(showSuggestionList).not.toBeInTheDocument();
+ },
+ { timeout: 5000 }
+ );
+ });
+
+ it("updates the suggestion list based on user input", async () => {
+ render();
+
+ const searchInput = screen.getByLabelText("search-input");
+ const inputValue = "cle";
+
+ fireEvent.focus(searchInput);
+
+ fireEvent.change(searchInput, { target: { value: inputValue } });
+
+ await waitFor(
+ () => {
+ const suggestions = screen.getAllByTestId("suggestion-list");
+ const suggestionContent = suggestions.map((suggestion) =>
+ suggestion.textContent?.toLocaleLowerCase().includes(inputValue)
+ );
+ expect(suggestionContent).toBeTruthy();
+ },
+ { timeout: 5000 }
+ );
+ });
+
+ it("populates the input field when a suggestion item is clicked", async () => {
+ render();
+ const inputValue = "cle";
+ const searchInput = screen.getByLabelText(
+ "search-input"
+ ) as HTMLInputElement;
+ let suggestionItems: HTMLElement[] = [];
+
+ fireEvent.change(searchInput, { target: { value: inputValue } });
+
+ await waitFor(
+ () => {
+ const suggestions = screen.queryByTestId(
+ "suggestion-list"
+ ) as HTMLElement;
+ expect(suggestions).toBeInTheDocument();
+
+ suggestionItems = within(suggestions)
+ .getAllByRole("listitem")
+ .filter((value) =>
+ value.textContent?.toLocaleLowerCase().includes(inputValue)
+ );
+ },
+ { timeout: 5000 }
+ );
+
+ fireEvent.click(suggestionItems[0]);
+
+ await waitFor(
+ () => {
+ const searchInput = screen.getByLabelText(
+ "search-input"
+ ) as HTMLInputElement;
+
+ expect(searchInput.value).toBe("Clementine Bauch");
+ },
+ { timeout: 5000 }
+ );
+ });
+
+ it("should return a div with no-options if no suggestion list is empty", async () => {
+ render();
+ const inputValue = "les";
+ const searchInput = screen.getByLabelText(
+ "search-input"
+ ) as HTMLInputElement;
+
+ fireEvent.change(searchInput, { target: { value: inputValue } });
+
+ await waitFor(
+ () => {
+ const noOption = screen.getByText("No options");
+ expect(noOption).toBeInTheDocument();
+ },
+ { timeout: 5000 }
+ );
+ });
+
+ it("moves selected suggestion move and down when using the keyboard arrow keys", async () => {
+ render();
+ const inputValue = "c";
+ const searchInput = screen.getByLabelText(
+ "search-input"
+ ) as HTMLInputElement;
+ let suggestions: HTMLElement | null = null;
+
+ fireEvent.change(searchInput, { target: { value: inputValue } });
+
+ await waitFor(
+ () => {
+ const suggestions = screen.queryByTestId(
+ "suggestion-list"
+ ) as HTMLElement;
+ expect(suggestions).toBeInTheDocument();
+ },
+ { timeout: 5000 }
+ );
+ if (suggestions) fireEvent.keyDown(suggestions, { key: "ArrowDown" });
+ });
+});
diff --git a/src/components/autoComplete.tsx b/src/components/autoComplete.tsx
index a2b7b5f..a0597a1 100644
--- a/src/components/autoComplete.tsx
+++ b/src/components/autoComplete.tsx
@@ -22,7 +22,7 @@ function AutoComplete() {
const handleOnClick = (event: React.MouseEvent) => {
setShowSuggestions(false);
- setSearchText(event.currentTarget.innerText);
+ if(event.currentTarget.textContent) setSearchText(event.currentTarget.textContent);
};
const handleKeyDown = (event: React.KeyboardEvent) => {
diff --git a/src/components/autoCompleteState.tsx b/src/components/autoCompleteState.tsx
index b81cecd..53fa38c 100644
--- a/src/components/autoCompleteState.tsx
+++ b/src/components/autoCompleteState.tsx
@@ -14,9 +14,9 @@ const AutoCompleteState = () => {
useEffect(() => {
(async () => {
const filteredData = await FileteredData(debouncedInputValue);
- if (filteredData) {
- setSuggestions(filteredData);
- }
+ if (!filteredData) return
+
+ setSuggestions(filteredData);
return filteredData;
})();
diff --git a/src/components/inputField.tsx b/src/components/inputField.tsx
index f7f8777..c0926bd 100644
--- a/src/components/inputField.tsx
+++ b/src/components/inputField.tsx
@@ -19,6 +19,7 @@ const InputField: FC = ({
= ({
showSuggestions,
handleOnClick,
selectedSuggestion,
- searchText
+ searchText,
}) => {
if (showSuggestions) {
return suggestions.length ? (
-
+
{suggestions.map((suggestion, index) => (
- = ({
))}
) : (
- No options
+ No options
);
} else return null;
};