Skip to content

Commit

Permalink
feat(fe:FSADT1-1522): Create full search (data table) (#1244)
Browse files Browse the repository at this point in the history
* feat(FSADT1-1519): Draft for Create API endpoint for predictive search

* Added temporary feature flag for the search functionality

* feat(FSADT1-1519): Predictive search endpoint #1

* feat(FSADT1-1519): Predictive search #2

* feat(be:FSADT1-1519): Create API endpoint for predictive search

* feat(be:FSADT1-1519): Create API endpoint for predictive search

* chore: Added javadocs

* Removed unused imports

* feat(be:FSADT1-1519): Create API endpoint for predictive search

* Refactored code and included full seach

* Refactored code to have 1 query only

* Added count in the API

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

* feat(fe:FSADT1-1521): create the predictive search (#1223)

* 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]>

* Renamed files

* Added missing key

* fix(fe:FSADT1-1521): clear the AutoComplete field when preventSelection is true (#1230)

* test: fix tests

* fix: do not prevent the clearing action

* Added stubs for full search

* fix: apply the feature flag to the Client search button

And remove it from the check of user's authority.

* chore: add header x-total-count to stub

* feat: add css tag colors from nr-theme

* feat: set status tag colors

* fix: prevent emitting the full css theme

* feat: emit press:enter

* fix: prevent emitting press:enter when it's a selection

* feat: update full search behavior

* feat: prevent search with invalid value

* fix: reset error message

* fix: remove optional from validations

* chore: update stub file

* fix: display empty acronyms

Also sets column widths

* test: check results by index

* fix: fix displaying issue on status column

Also updates css class names

* test: add more tests

* fix: allow empty search

* test: search with no keywords

---------

Co-authored-by: Maria Martinez <[email protected]>
Co-authored-by: Maria Martinez <[email protected]>
  • Loading branch information
3 people authored Oct 17, 2024
1 parent e392859 commit a9fa926
Show file tree
Hide file tree
Showing 8 changed files with 646 additions and 89 deletions.
270 changes: 258 additions & 12 deletions frontend/cypress/e2e/pages/SearchPage.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,73 @@ describe("Search Page", () => {
count: 0,
};

const checkDisplayedResults = (clientList: ClientSearchResult[]) => {
clientList.forEach((client) => {
const fullSearchCounter = {
count: 0,
};

const checkAutocompleteResults = (clientList: ClientSearchResult[]) => {
clientList.forEach((client, index) => {
cy.get("#search-box")
.find(`cds-combo-box-item[data-value^="${client.clientNumber}"]`)
.should("exist");
.find("cds-combo-box-item")
.eq(index)
.should("exist")
.should("have.attr", "data-id", client.clientNumber);
});
};

const checkTableResults = (clientList: ClientSearchResult[]) => {
clientList.forEach((client, index) => {
cy.get("cds-table")
.find("cds-table-row")
.eq(index)
.contains(client.clientNumber)
.should("be.visible");

const acronymColumnIndex = 3;

// only the first client has an acronym
const expectedValue = index === 0 ? client.clientAcronym : "-";

cy.get("cds-table")
.find("cds-table-row")
.eq(index)
.find(`cds-table-cell:nth-child(${acronymColumnIndex})`)
.contains(expectedValue)
.should("be.visible");
});
};

beforeEach(() => {
// reset counter
// reset counters
predictiveSearchCounter.count = 0;
fullSearchCounter.count = 0;

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

cy.intercept("/api/clients/search?keyword=*", (req) => {
predictiveSearchCounter.count++;
req.continue();
}).as("predictiveSearch");
cy.intercept(
{
pathname: "/api/clients/search",
query: {
page: "*",
size: "*",
},
},
(req) => {
fullSearchCounter.count++;
req.continue();
},
).as("fullSearch");

cy.viewport(1920, 1080);
cy.visit("/");
Expand Down Expand Up @@ -63,7 +115,7 @@ describe("Search Page", () => {
.should("have.length", data.length)
.should("be.visible");

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

Expand Down Expand Up @@ -91,12 +143,12 @@ describe("Search Page", () => {
.should("have.length", data.length)
.should("be.visible");

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

describe("and user clicks a result", () => {
describe("and user clicks an Autocomplete result", () => {
const clientNumber = "00054076";
beforeEach(() => {
cy.get("#search-box")
Expand All @@ -116,6 +168,136 @@ describe("Search Page", () => {
);
});
});

describe("and clicks the Search button", () => {
beforeEach(() => {
cy.wait("@predictiveSearch");
cy.get("#search-button").click();
});
it("makes one API call with the entered keywords", () => {
cy.wait("@fullSearch").then((interception) => {
const { query } = interception.request;
expect(query.keyword).to.eq("car");
expect(query.page).to.eq("0");
});

cy.wait(100); // Waits additional time to make sure there's no duplicate API calls.
cy.wrap(fullSearchCounter).its("count").should("eq", 1);
});

it("displays the results on the table", () => {
cy.wait("@fullSearch").then((interception) => {
const data = interception.response.body;

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

cy.get("cds-table")
.find("cds-table-row")
.should("have.length", data.length)
.should("be.visible");

checkTableResults(data);
});
});

describe("and user clicks a result on the table", () => {
const clientNumber = "00191086";
beforeEach(() => {
cy.get("cds-table").contains("cds-table-row", 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("and clicks the Next page button on the table footer", () => {
beforeEach(() => {
cy.wait("@fullSearch");
cy.get('[tooltip-text="Next page"]').click();
});
it("makes an API call for the second page of results", () => {
cy.wait("@fullSearch").then((interception) => {
const { query } = interception.request;
expect(query.keyword).to.eq("car");
expect(query.page).to.eq("1");
});
cy.wrap(fullSearchCounter).its("count").should("eq", 2);
});

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

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

cy.get("cds-table")
.find("cds-table-row")
.should("have.length", data.length)
.should("be.visible");

checkTableResults(data);
});
});

describe("and clicks the Search button again", () => {
beforeEach(() => {
cy.wait("@fullSearch");

// sanity check
cy.get("#pages-select").should("have.value", "2");

cy.get("#search-button").click();
});
it("makes a new API call for the first page of results", () => {
cy.wait("@fullSearch").then((interception) => {
const { query } = interception.request;
expect(query.keyword).to.eq("car");
expect(query.page).to.eq("0");
});
cy.wrap(fullSearchCounter).its("count").should("eq", 3);
});

it("updates the results on the table", () => {
cy.wait("@fullSearch").then((interception) => {
// reset to page 1
cy.get("#pages-select").should("have.value", "1");

const data = interception.response.body;

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

cy.get("cds-table")
.find("cds-table-row")
.should("have.length", data.length)
.should("be.visible");

checkTableResults(data);
});
});
});
});
});

describe("and hits enter on the search box", () => {
beforeEach(() => {
cy.wait("@predictiveSearch");
cy.fillFormEntry("#search-box", "{enter}", { skipBlur: true });
});
it("makes the API call with the entered keywords", () => {
cy.wait("@fullSearch").then((interception) => {
const { query } = interception.request;
expect(query.keyword).to.eq("car");
expect(query.page).to.eq("0");
});
cy.wrap(fullSearchCounter).its("count").should("eq", 1);
});
});
});

describe("when user fills in the search box with an invalid value", () => {
Expand All @@ -130,5 +312,69 @@ describe("Search Page", () => {
cy.wait(500); // This time has to be greater than the debouncing time
cy.wrap(predictiveSearchCounter).its("count").should("eq", 0);
});

describe("and clicks the Search button", () => {
beforeEach(() => {
cy.get("#search-button").click();
});
it("makes no API call", () => {
cy.wait(500); // This time has to be greater than the debouncing time
cy.wrap(fullSearchCounter).its("count").should("eq", 0);
});
});
});

describe("Search with no keywords", () => {
beforeEach(() => {
cy.get("#search-button").click();
});
it("makes an API call even without any keywords", () => {
cy.wait("@fullSearch").then((interception) => {
const { query } = interception.request;
expect(query.keyword).to.eq("");
expect(query.page).to.eq("0");
});
});
});

describe("when the API is returning errors", () => {
beforeEach(() => {
// The "error" value actually triggers the error response
cy.fillFormEntry("#search-box", "error");
});
describe("and user clicks the Search button", () => {
beforeEach(() => {
cy.get("#search-button").click();
});

it("displays an error notification", () => {
cy.wait("@fullSearch");

cy.get("cds-actionable-notification")
.shadow()
.contains("Something went wrong")
.should("be.visible");
});

describe("and the API stops returning errors", () => {
beforeEach(() => {
cy.wait("@fullSearch");

// Replacing the "error" value actually triggers a successful response
cy.fillFormEntry("#search-box", "okay");
});
describe("and user clicks the Search button", () => {
beforeEach(() => {
cy.get("#search-button").click();
});

it("hides the error notification", () => {
cy.wait("@fullSearch");

cy.get("cds-actionable-notification").should("not.exist");
});
});
});
});
});
});
25 changes: 25 additions & 0 deletions frontend/src/assets/styles/global.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
@use "sass:map";
@use "@carbon/styles";
@use '@carbon/styles/scss/theme';
@use '@carbon/themes';
@use "@bcgov-nr/nr-fsa-theme/design-tokens/type-family.scss" as typeFamily;
@use '@bcgov-nr/nr-fsa-theme/design-tokens/light-tags.scss' as light-tag-overrides;

// adds cds-tag related tokens
@include theme.add-component-tokens(light-tag-overrides.$light-tag-token-overrides);

:root {
// the empty list passed in prevents from emitting the full theme
@include themes.theme(());

--others-transparent-transparent: rgba(255, 255, 255, 0);
--light-theme-border-subtle-01: #dfdfe1;
--light-theme-button-button-primary: #0073e6;
Expand Down Expand Up @@ -1486,6 +1495,22 @@ cds-table-header-cell {
width: 1.5rem;
}

.col-6_75rem {
width: 6.75rem;
}

.col-19_4375rem {
width: 19.4375rem;
}

.col-14_75rem {
width: 14.75rem;
}

.col-7_0625rem {
width: 7.0625rem;
}

cds-pagination {
border-radius: 0px 0px 4px 4px;
border-top: 1px solid var(--light-theme-border-border-subtle-01, #dfdfe1);
Expand Down
Loading

0 comments on commit a9fa926

Please sign in to comment.