diff --git a/package.json b/package.json
index cddd7e9f..62e7f17b 100644
--- a/package.json
+++ b/package.json
@@ -31,11 +31,6 @@
"@google-cloud/logging": "^9.6.0",
"@google-cloud/profiler": "^4.1.3",
"@google-cloud/storage": "^5.8.5",
- "@types/express": "^4.17.11",
- "@types/jest": "^27.0.0",
- "@types/node": "^15.12.2",
- "@types/react": "^18.2.51",
- "@types/react-dom": "^18.2.18",
"axios": "^1.7.4",
"blaise-api-node-client": "git+https://github.com/ONSdigital/blaise-api-node-client#1.1.0",
"blaise-design-system-react-components": "git+https://github.com/ONSdigital/blaise-design-system-react-components#0.14.0",
@@ -74,9 +69,14 @@
"@testing-library/react": "^14.2.1",
"@testing-library/user-event": "^13.2.1",
"@types/ejs": "^3.1.0",
+ "@types/express": "^4.17.11",
+ "@types/jest": "^27.0.0",
"@types/jsonwebtoken": "^8.5.5",
"@types/lodash": "^4.14.170",
+ "@types/node": "^15.12.2",
"@types/pino-http": "^5.7.0",
+ "@types/react": "^18.2.51",
+ "@types/react-dom": "^18.2.18",
"@types/react-router-dom": "^5.3.3",
"@types/react-timeago": "^4.1.7",
"@types/supertest": "^2.0.11",
diff --git a/src/components/questionnaireList.test.tsx b/src/components/questionnaireList.test.tsx
index 3d8304a3..1ce64f06 100644
--- a/src/components/questionnaireList.test.tsx
+++ b/src/components/questionnaireList.test.tsx
@@ -1,51 +1,243 @@
/**
* @jest-environment jsdom
*/
-
+import React from "react";
+import "@testing-library/jest-dom";
import flushPromises from "../tests/utils";
-import { render, waitFor, screen } from "@testing-library/react";
+import { render, waitFor, screen, act } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
-import { act } from "react-dom/test-utils";
-import React from "react";
import { Authenticate } from "blaise-login-react/blaise-login-react-client";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import QuestionnaireList from "./questionnaireList";
+import userEvent from "@testing-library/user-event";
const mock = new MockAdapter(axios);
-// mock login
+// Mock the blaise-login-react Authenticate component
jest.mock("blaise-login-react/blaise-login-react-client");
const { MockAuthenticate } = jest.requireActual("blaise-login-react/blaise-login-react-client");
Authenticate.prototype.render = MockAuthenticate.prototype.render;
MockAuthenticate.OverrideReturnValues(null, true);
-describe("Questionnaire Details page ", () => {
+const MOCK_QUESTIONNAIRE_LIST = [
+ {
+ "name": "IPS2409A",
+ "id": "ef8980d9-5f5c-416d-9b4b-9570c15c85c0",
+ "serverParkName": "gusty",
+ "installDate": "2024-10-16T13:14:01.7557563+01:00",
+ "status": "Active",
+ "dataRecordCount": 1,
+ "hasData": true,
+ "blaiseVersion": "5.14.4.3668",
+ "nodes": [
+ {
+ "nodeName": "blaise-gusty-mgmt",
+ "nodeStatus": "Active"
+ }
+ ],
+ "fieldPeriod": "September 2024"
+ },
+ {
+ "name": "IPS2409B",
+ "id": "ef8980d9-5f5c-416d-9b4b-9570c15c85c0",
+ "serverParkName": "gusty",
+ "installDate": "2024-10-16T13:14:01.7557563+01:00",
+ "status": "Active",
+ "dataRecordCount": 1,
+ "hasData": true,
+ "blaiseVersion": "5.14.4.3668",
+ "nodes": [
+ {
+ "nodeName": "blaise-gusty-mgmt",
+ "nodeStatus": "Active"
+ }
+ ],
+ "fieldPeriod": "September 2024"
+ },
+ {
+ "name": "IPS_ContactInfo",
+ "id": "10effca3-caec-4fc0-a037-20a43cf050c2",
+ "serverParkName": "gusty",
+ "installDate": "2024-10-18T12:59:23.4164608+01:00",
+ "status": "Active",
+ "dataRecordCount": 0,
+ "hasData": false,
+ "blaiseVersion": "5.14.4.3668",
+ "nodes": [
+ {
+ "nodeName": "blaise-gusty-mgmt",
+ "nodeStatus": "Active"
+ }
+ ],
+ "fieldPeriod": "Field period unknown"
+ },
+ {
+ "name": "IPS_Attempts",
+ "id": "10effca3-caec-4fc0-a037-20a43cf050c4",
+ "serverParkName": "gusty",
+ "installDate": "2024-10-18T12:59:23.4164608+01:00",
+ "status": "Active",
+ "dataRecordCount": 0,
+ "hasData": false,
+ "blaiseVersion": "5.14.4.3668",
+ "nodes": [
+ {
+ "nodeName": "blaise-gusty-mgmt",
+ "nodeStatus": "Active"
+ }
+ ],
+ "fieldPeriod": "Field period unknown"
+ },
+ {
+ "name": "DST2304Z",
+ "id": "10effca3-caec-4fc0-a037-20a43cf050c5",
+ "serverParkName": "gusty",
+ "installDate": "2024-10-18T12:59:23.4164608+01:00",
+ "status": "Active",
+ "dataRecordCount": 0,
+ "hasData": false,
+ "blaiseVersion": "5.14.4.3668",
+ "nodes": [
+ {
+ "nodeName": "blaise-gusty-mgmt",
+ "nodeStatus": "Active"
+ }
+ ],
+ "fieldPeriod": "Field period unknown"
+ }
+];
+
+describe("Questionnaire List displays valid user questionnaires", () => {
beforeEach(() => {
+ mock.onGet("/api/questionnaires").reply(200, MOCK_QUESTIONNAIRE_LIST);
+ });
+
+ afterEach(() => {
+ mock.reset();
+ });
+
+ it("should not display any questionnaires if no questionnaires were fetched back from the server", async () => {
+ // Arrange
mock.onGet("/api/questionnaires").reply(200, []);
+ render(
+
+
+
+ );
+
+ // Act
+ await act(async () => {
+ await flushPromises();
+ });
+
+ // Assert
+ await waitFor(() => {
+ expect(screen.getByText(/Filter by questionnaire name/i)).toBeVisible();
+ expect(screen.getByText(/No installed questionnaires found/i)).toBeVisible();
+ });
+ });
+
+ it("should display a list of questionnaires containing only questionnaires that match the filter", async () => {
+ // Arrange
+ render(
+
+
+
+ );
+
+ // Act
+ await act(async () => {
+ await flushPromises();
+ });
+ const filterInput = screen.getByTestId(/filter-by-name/i);
+ userEvent.type(filterInput, "IPS2409A");
+
+ // Assert
+ await waitFor(() => {
+ expect(screen.getByText(/Filter by questionnaire name/i)).toBeVisible();
+ expect(screen.getByText(/1 results of 1/i)).toBeVisible();
+ expect(screen.getByText(/IPS2409A/i)).toBeVisible();
+ });
+ });
+
+});
+
+describe("Questionnaire List displays hidden questionnaires that match when using the search filter", () => {
+ beforeEach(() => {
+ mock.onGet("/api/questionnaires").reply(200, MOCK_QUESTIONNAIRE_LIST);
});
afterEach(() => {
mock.reset();
});
- it("should redirect to the homepage when no questionnaire has been provided ", async () => {
+ it("should display the hidden ContactInfo questionnaire", async () => {
+ // Arrange
+ render(
+
+
+
+ );
+
+ // Act
+ await act(async () => {
+ await flushPromises();
+ });
+ const filterInput = screen.getByTestId(/filter-by-name/i);
+ userEvent.type(filterInput, "ContactInfo");
- // Go direct to the questionnaire details page not from a link
+ // Assert
+ await waitFor(() => {
+ expect(screen.getByText(/Filter by questionnaire name/i)).toBeVisible();
+ expect(screen.getByText(/1 results of 1/i)).toBeVisible();
+ expect(screen.getByText(/IPS_ContactInfo/i)).toBeVisible();
+ });
+ });
+
+ it("should display the hidden Attempts questionnaire", async () => {
+ // Arrange
+ render(
+
+
+
+ );
+
+ // Act
+ await act(async () => {
+ await flushPromises();
+ });
+ const filterInput = screen.getByTestId(/filter-by-name/i);
+ userEvent.type(filterInput, "Attempts");
+
+ // Assert
+ await waitFor(() => {
+ expect(screen.getByText(/Filter by questionnaire name/i)).toBeVisible();
+ expect(screen.getByText(/1 results of 1/i)).toBeVisible();
+ expect(screen.getByText(/IPS_Attempts/i)).toBeVisible();
+ });
+ });
+
+ it("should display the hidden DST2304Z test questionnaire", async () => {
+ // Arrange
render(
);
+ // Act
await act(async () => {
await flushPromises();
});
+ const filterInput = screen.getByTestId(/filter-by-name/i);
+ userEvent.type(filterInput, "DST2304Z");
+ // Assert
await waitFor(() => {
- expect(screen.queryByText("Questionnaire settings")).toEqual(null);
- expect(screen.getByText(/Filter by questionnaire name/i)).toBeDefined();
- expect(screen.queryByText(/Questionnaire details/i)).toEqual(null);
+ expect(screen.getByText(/Filter by questionnaire name/i)).toBeVisible();
+ expect(screen.getByText(/1 results of 1/i)).toBeVisible();
+ expect(screen.getByText(/DST2304Z/i)).toBeVisible();
});
});
});
diff --git a/src/components/questionnaireList.tsx b/src/components/questionnaireList.tsx
index 8c26bc7a..7b733662 100644
--- a/src/components/questionnaireList.tsx
+++ b/src/components/questionnaireList.tsx
@@ -15,18 +15,25 @@ type Props = {
setErrored: (errored: boolean) => void
}
-function questionnaireTableRow(questionnaire: Questionnaire): ReactElement {
- function questionnaireName(questionnaire: Questionnaire) {
- if (questionnaire.name.toUpperCase().startsWith("DST")) {
- return (
- <>
- <>{questionnaire.name}>
- >
- );
- }
- return <>{questionnaire.name}>;
+function isHiddenQuestionnaire(questionnaireName: string): boolean {
+ const QUESTIONNAIRE_KEYWORDS = ["DST", "CONTACTINFO", "ATTEMPTS"];
+ return QUESTIONNAIRE_KEYWORDS.some(keyword => {
+ return questionnaireName.toUpperCase().includes(keyword);
+ });
+}
+
+function questionnaireName(questionnaire: Questionnaire) {
+ if (isHiddenQuestionnaire(questionnaire.name)) {
+ return (
+ <>
+ <>{questionnaire.name}>
+ >
+ );
}
+ return <>{questionnaire.name}>;
+}
+function questionnaireTableRow(questionnaire: Questionnaire): ReactElement {
return (
@@ -56,43 +63,62 @@ function questionnaireTableRow(questionnaire: Questionnaire): ReactElement {
);
}
+function questionnaireTable(filteredList: Questionnaire[], tableColumns: TableColumns[], message: string): ReactElement {
+ if (filteredList && filteredList.length > 0) {
+ return
+ {
+ filteredList.map((item: Questionnaire) => {
+ return questionnaireTableRow(item);
+ })
+ }
+ ;
+ }
+ return {message};
+}
+
export const QuestionnaireList = ({ setErrored }: Props): ReactElement => {
const [questionnaires, setQuestionnaires] = useState([]);
const [realQuestionnaireCount, setRealQuestionnaireCount] = useState(0);
const [loaded, setLoaded] = useState(false);
const [message, setMessage] = useState("");
+ const [filterText, setFilterText] = useState("");
const [filteredList, setFilteredList] = useState([]);
- function filterTestQuestionnaires(questionnairesToFilter: Questionnaire[], filterValue: string): Questionnaire[] {
- if (!filterValue.toUpperCase().startsWith("DST")) {
- questionnairesToFilter = filter(questionnairesToFilter, (questionnaire) => {
- if (!questionnaire?.name) {
- return false;
- }
- return !questionnaire.name.toUpperCase().startsWith("DST");
- });
- setRealQuestionnaireCount(questionnairesToFilter.length);
- }
- return questionnairesToFilter;
- }
+ const handleFilterChange = (value: string) => {
+ setFilterText(value);
+ filterQuestionnaireList(questionnaires, value);
+ };
- function filterList(filterValue: string) {
- // Filter by the search field
- if (filterValue === "") {
- setFilteredList(filterTestQuestionnaires(questionnaires, filterValue));
+ const filterQuestionnaireList = (questionnaireList: Questionnaire[], filterValue: string) =>{
+ if (questionnaireList.length === 0) {
+ setMessage("No installed questionnaires found.");
+ return [];
}
- const newFilteredList = filter(filterTestQuestionnaires(questionnaires, filterValue), (questionnaire) => questionnaire.name.includes(filterValue.toUpperCase()));
+
+ const newFilteredList = filter(questionnaireList, (questionnaire) => {
+ if (filterValue === "") {
+ return questionnaire.name.toUpperCase().includes(filterValue.toUpperCase()) && !isHiddenQuestionnaire(questionnaire.name);
+ }
+ return questionnaire.name.toUpperCase().includes(filterValue.toUpperCase());
+ });
+
// Order by date
newFilteredList.sort((a: Questionnaire, b: Questionnaire) => Date.parse(b.installDate) - Date.parse(a.installDate));
setFilteredList(newFilteredList);
-
- if (questionnaires.length > 0 && newFilteredList.length === 0) {
+ setRealQuestionnaireCount(newFilteredList.length);
+
+ if (questionnaireList.length > 0 && newFilteredList.length === 0) {
setMessage(`No questionnaires containing ${filterValue} found`);
+ return [];
}
- }
+ return newFilteredList;
+ };
- async function getQuestionnairesList() {
+ const getQuestionnairesList = async () => {
let questionnaires: Questionnaire[];
try {
questionnaires = await getQuestionnaires();
@@ -109,75 +135,59 @@ export const QuestionnaireList = ({ setErrored }: Props): ReactElement => {
}
return questionnaires;
- }
+ };
useEffect(() => {
getQuestionnairesList().then((questionnaireList: Questionnaire[]) => {
setQuestionnaires(questionnaireList);
- const nonTestQuestionnaires = filterTestQuestionnaires(questionnaireList, "");
- setFilteredList(nonTestQuestionnaires);
- if (nonTestQuestionnaires.length === 0) {
- setMessage("No installed questionnaires found.");
- }
+ const filteredQuestionnaireList = filterQuestionnaireList(questionnaireList, "");
+ setFilteredList(filteredQuestionnaireList);
setLoaded(true);
});
}, []);
- function QuestionnaireTable(): ReactElement {
- if (filteredList && filteredList.length > 0) {
- return
- {
- filteredList.map((item: Questionnaire) => {
- return questionnaireTableRow(item);
- })
- }
- ;
- }
- return {message};
- }
-
- const tableColumns: TableColumns[] =
- [
- {
- title: "Questionnaire"
- },
- {
- title: "Field period"
- },
- {
- title: "Status"
- },
- {
- title: "Install date"
- },
- {
- title: "Cases"
- }
- ];
-
if (!loaded) {
return ;
}
+
return (
<>
>
diff --git a/yarn.lock b/yarn.lock
index 0333d873..0a463904 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -13174,16 +13174,7 @@ string-natural-compare@^3.0.1:
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
-"string-width-cjs@npm:string-width@^4.2.0":
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -13266,14 +13257,7 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -14634,7 +14618,7 @@ workbox-window@6.6.1:
"@types/trusted-types" "^2.0.2"
workbox-core "6.6.1"
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -14652,15 +14636,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|