Skip to content

Commit

Permalink
feat(fe:FSADT1-1521): create the predictive search (#1223)
Browse files Browse the repository at this point in the history
* feat: add search box with predictive search

* fix: fix predictive-search stubs

* feat: update style of search controls

* docs: update interface name

* fix: use placeholder instead of label

* test: add search test file

* feat: navigate to client details

* test: implement tests

* refactor: move functions to the GlobalValidators

* feat: validate autocomplete while typing

* feat: validate keywords

* test: validate keywords

* feat: prevent selection on AutoComplete

* feat: open client details in a new tab

* chore: update endpoint stub

* test: update tests

* feat(fe:FSADT1-1541): Display an error message when the BE is down (#1227)

* fix: rename event from click to click:option

---------

Co-authored-by: Maria Martinez <[email protected]>
  • Loading branch information
fterra-encora and mamartinezmejia authored Oct 10, 2024
1 parent 7127d58 commit 258ed26
Show file tree
Hide file tree
Showing 12 changed files with 519 additions and 96 deletions.
139 changes: 139 additions & 0 deletions frontend/cypress/e2e/pages/SearchPage.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import type { ClientSearchResult } from "@/dto/CommonTypesDto";

describe("Search Page", () => {
const predictiveSearchCounter = {
count: 0,
};

const checkDisplayedResults = (clientList: ClientSearchResult[]) => {
clientList.forEach((client) => {
cy.get("#search-box")
.find(`cds-combo-box-item[data-value^="${client.clientNumber}"]`)
.should("exist");
});
};
beforeEach(() => {
// reset counter
predictiveSearchCounter.count = 0;

cy.intercept("/api/clients/search?keyword=*", (req) => {
predictiveSearchCounter.count++;
req.continue();
}).as("predictiveSearch");

cy.viewport(1920, 1080);
cy.visit("/");

cy.login("[email protected]", "Uat Test", "idir", {
given_name: "James",
family_name: "Baxter",
"cognito:groups": ["CLIENT_VIEWER"],
});

// Check if the Client search button is visible
cy.get("#menu-list-search").should("be.visible").click();

cy.get("h1").should("be.visible").should("contain", "Client search");

cy.window().then((win) => {
cy.stub(win, "open").as("windowOpen");
});
});

describe("when user fills in the search box with a valid value", () => {
beforeEach(() => {
cy.fillFormEntry("#search-box", "car", { skipBlur: true });
});

it("makes the API call with the entered keywords", () => {
cy.wait("@predictiveSearch").then((interception) => {
expect(interception.request.query.keyword).to.eq("car");
});
cy.wrap(predictiveSearchCounter).its("count").should("eq", 1);
});

it("displays autocomplete results", () => {
cy.get("#search-box")
.find("cds-combo-box-item")
.should("have.length", 3)
.should("be.visible");

cy.wait("@predictiveSearch").then((interception) => {
const data = interception.response.body;

cy.wrap(data).should("be.an", "array").and("have.length", 3);

cy.get("#search-box")
.find("cds-combo-box-item")
.should("have.length", data.length)
.should("be.visible");

checkDisplayedResults(data);
});
});

describe("and types more characters", () => {
beforeEach(() => {
cy.wait("@predictiveSearch");
cy.fillFormEntry("#search-box", "d", { skipBlur: true });
});

it("makes another the API call with the updated keywords", () => {
cy.wait("@predictiveSearch").then((interception) => {
expect(interception.request.query.keyword).to.eq("card");
});
cy.wrap(predictiveSearchCounter).its("count").should("eq", 2);
});

it("updates the autocomplete results", () => {
cy.wait("@predictiveSearch").then((interception) => {
const data = interception.response.body;

cy.wrap(data).should("be.an", "array").and("have.length.greaterThan", 3);

cy.get("#search-box")
.find("cds-combo-box-item")
.should("have.length", data.length)
.should("be.visible");

checkDisplayedResults(data);
});
});
});

describe("and user clicks a result", () => {
const clientNumber = "00001297";
beforeEach(() => {
cy.get("#search-box")
.find("cds-combo-box-item")
.should("have.length", 3)
.should("be.visible");

cy.get("#search-box").find(`cds-combo-box-item[data-value^="${clientNumber}"]`).click();
});
it("navigates to the client details", () => {
const greenDomain = "green-domain.com";
cy.get("@windowOpen").should(
"be.calledWith",
`https://${greenDomain}/int/client/client02MaintenanceAction.do?bean.clientNumber=${clientNumber}`,
"_blank",
"noopener",
);
});
});
});

describe("when user fills in the search box with an invalid value", () => {
beforeEach(() => {
cy.fillFormEntry("#search-box", "até", { skipBlur: true });
});

it("shows an error message", () => {
cy.contains("The search terms can only contain: A-Z, a-z, 0-9, space or common symbols");
});
it("makes no API call", () => {
cy.wait(500); // This time has to be greater than the debouncing time
cy.wrap(predictiveSearchCounter).its("count").should("eq", 0);
});
});
});
46 changes: 38 additions & 8 deletions frontend/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,45 @@ Cypress.Commands.add("getMany", (names: string[]): Cypress.Chainable<any[]> => {
return cy.wrap(values);
});

Cypress.Commands.add("fillFormEntry",(field: string, value: string, delayMS: number = 10, area: boolean = false) =>{
interface FillFormEntryOptions {
delayMS?: number;
area?: boolean;
skipBlur?: boolean;
}

interface FillFormEntry {
(field: string, value: string, delayMS?: number, area?: boolean): void;
(field: string, value: string, options?: FillFormEntryOptions): void;
}

const fillFormEntry: FillFormEntry = (
field: string,
value: string,
arg3: number | FillFormEntryOptions = 10,
arg4: boolean | never = false,
) => {
const options =
typeof arg3 === "object"
? arg3
: {
delayMS: arg3,
area: arg4,
};
const { delayMS, area, skipBlur } = options;
cy.get(field)
.should("exist")
.shadow()
.find(area ? "textarea" : "input")
.focus()
.type(value,{ delay: delayMS })
.blur();
});
.should("exist")
.shadow()
.find(area ? "textarea" : "input")
.focus()
.type(value, { delay: delayMS })
.then((subject) => {
if (!skipBlur) {
cy.wrap(subject).blur();
}
});
};

Cypress.Commands.add("fillFormEntry", fillFormEntry);

Cypress.Commands.add("clearFormEntry",(field: string, area: boolean = false) =>{
cy.get(field)
Expand Down
15 changes: 13 additions & 2 deletions frontend/cypress/support/cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,23 @@ declare namespace Cypress {
login(email: string, name: string, provider: string, extras: any = "{}"): Chainable<void>;
logout(): Chainable<void>;
getMany(names: string[]): Chainable<any[]>;
fillFormEntry(field: string, value: string, delayMS: number = 10, area: boolean = false): Chainable<void>;
fillFormEntry(
field: string,
value: string,
delayMS: number = 10,
area: boolean = false,
): Chainable<void>;
fillFormEntry(field: string, value: string, options: FillFormEntryOptions): Chainable<void>;
clearFormEntry(field: string, area: boolean = false): Chainable<void>;
selectFormEntry(field: string, value: string, box: boolean): Chainable<void>;
markCheckbox(field: string): Chainable<void>;
unmarkCheckbox(field: string): Chainable<void>;
selectAutocompleteEntry(field: string, value: string, dataid: string, delayTarget: string =''): Chainable<void>;
selectAutocompleteEntry(
field: string,
value: string,
dataid: string,
delayTarget: string = "",
): Chainable<void>;
checkInputErrorMessage(field: string, message: string): Chainable<void>;
checkAutoCompleteErrorMessage(field: string, message: string): Chainable<void>;
checkAccordionItemState(additionalSelector: string, open: boolean): Chainable<void>;
Expand Down
6 changes: 3 additions & 3 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"scripts": {
"start": "vite --host --port 3000",
"build": "vue-tsc --noEmit && vite build",
"preview": "cross-env VITE_NODE_ENV=test VITE_FEATURE_FLAGS={\\\"STAFF_SEARCH\\\":true} start-server-and-test stub http://127.0.0.1:8080 start",
"preview": "cross-env VITE_NODE_ENV=test VITE_GREEN_DOMAIN=green-domain.com VITE_FEATURE_FLAGS={\\\"STAFF_SEARCH\\\":true} start-server-and-test stub http://127.0.0.1:8080 start",
"preview:app": "cross-env VITE_NODE_ENV=test vite --host --port 3000",
"typecheck": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
"lint": "eslint . --fix --ignore-path .gitignore",
Expand All @@ -36,12 +36,12 @@
"posttest:component": "mv reports/.nyc_report reports/component",
"test:unit": "cross-env VITE_NODE_ENV=test NODE_ENV=test vitest run --coverage",
"posttest:unit": "mv reports/.vite_report reports/unit",
"test:e2e": "cross-env VITE_NODE_ENV=test VITE_FEATURE_FLAGS={\\\"STAFF_SEARCH\\\":true} start-server-and-test preview http://127.0.0.1:3000 'cypress run --headless'",
"test:e2e": "cross-env VITE_NODE_ENV=test VITE_GREEN_DOMAIN=green-domain.com VITE_FEATURE_FLAGS={\\\"STAFF_SEARCH\\\":true} start-server-and-test preview http://127.0.0.1:3000 'cypress run --headless'",
"posttest:e2e": "mv reports/.nyc_report reports/e2e",
"pretest:report:merge": "rm -rf reports-merge && mkdir -p reports-merge && for name in component e2e unit; do cp reports/$name/coverage-final.json reports-merge/$name.json; done",
"test:report:merge": "mkdir -p .nyc_output && rm -rf coverage && nyc --config nyc.config.json merge reports-merge && mv coverage.json .nyc_output/out.json && nyc --config nyc.config.json report --reporter lcov --reporter text-summary --report-dir coverage --temp-dir .nyc_output",
"test:report:clean": "rm -rf reports && mkdir -p reports/.nyc_output/processinfo && mkdir -p coverage",
"test:build": "cross-env VITE_MODE=test VITE_NODE_ENV=test VITE_FEATURE_FLAGS={\\\"STAFF_SEARCH\\\":true} start-server-and-test preview http://127.0.0.1:3000 'cypress open'",
"test:build": "cross-env VITE_MODE=test VITE_NODE_ENV=test VITE_GREEN_DOMAIN=green-domain.com VITE_FEATURE_FLAGS={\\\"STAFF_SEARCH\\\":true} start-server-and-test preview http://127.0.0.1:3000 'cypress open'",
"test:flush": "rm -rf reports && rm -rf .nyc_output && rm -rf coverage && rm -rf reports-merge",
"posttest:flush": "npm run coverage",
"test:unit:devtools": "cross-env VITE_NODE_ENV=test NODE_ENV=test vitest run --inspect-brk --pool threads --poolOptions.threads.singleThread"
Expand Down
34 changes: 19 additions & 15 deletions frontend/src/assets/styles/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1609,6 +1609,25 @@ div.internal-grouping-01:has(svg.warning) span.body-compact-01 {
background-color: color-mix(in srgb, var(--cds-support-warning) 40%, white);
}

#datatable {
position: relative;
right: 0;
overflow-x: scroll;
}

#search-line {
display: flex;
align-items: flex-end;

.grouping-02 {
flex-grow: 1;
}
}

#search-button {
width: 7.875rem;
}

/* Small (up to 671px) */
@media screen and (max-width: 671px) {
:root {
Expand Down Expand Up @@ -1807,11 +1826,6 @@ div.internal-grouping-01:has(svg.warning) span.body-compact-01 {
.submission-details--title svg {
width: 5rem;
}
#datatable {
position: relative;
right: 0;
overflow-x: scroll;
}
cds-table {
width: 82rem;
}
Expand Down Expand Up @@ -1920,11 +1934,6 @@ div.internal-grouping-01:has(svg.warning) span.body-compact-01 {
.paginator {
width: 100vw;
}
#datatable {
position: relative;
right: 0;
overflow-x: scroll;
}
cds-table {
width: 82rem;
}
Expand Down Expand Up @@ -1999,11 +2008,6 @@ div.internal-grouping-01:has(svg.warning) span.body-compact-01 {
);
}

#datatable {
position: relative;
right: 0;
overflow-x: scroll;
}
cds-table {
min-width: 66rem;
}
Expand Down
Loading

0 comments on commit 258ed26

Please sign in to comment.