f48cb7e249
- Bumped NEXT_PUBLIC_APP_VERSION to 2.0.3 for the latest release. - Improved Cypress test for customer feedback page to handle delayed route transitions. - Refactored CompanyDetailsPage to enhance layout and added new components for better structure. - Updated TenderDetails to conditionally render location and currency information based on validity checks. - Added data-cy attributes to various form elements and tables for improved testing capabilities.
378 lines
11 KiB
TypeScript
378 lines
11 KiB
TypeScript
/// <reference types="cypress" />
|
||
|
||
const makeCustomer = (overrides: Partial<Record<string, any>> = {}, index = 1) => ({
|
||
id: `507f1f77bcf86cd7994390${String(index).padStart(2, "0")}`,
|
||
full_name: `Customer ${index}`,
|
||
email: `customer${index}@example.com`,
|
||
username: `customer${index}`,
|
||
phone: "1234567890",
|
||
type: "individual",
|
||
status: "active",
|
||
role: "admin",
|
||
companies: [],
|
||
created_at: 1710000000 + index,
|
||
updated_at: 1710000500 + index,
|
||
...overrides,
|
||
});
|
||
|
||
const makeCompany = (overrides: Partial<Record<string, any>> = {}, index = 1) => ({
|
||
id: `company-${index}`,
|
||
name: `Company ${index}`,
|
||
email: `company${index}@example.com`,
|
||
phone: "1234567890",
|
||
website: "https://example.com",
|
||
type: "vendor",
|
||
description: "Test company",
|
||
founded_year: 2020,
|
||
employee_count: 25,
|
||
annual_revenue: 1000000,
|
||
currency: "USD",
|
||
industry: "Technology",
|
||
language: "en",
|
||
tax_id: `TAX-${index}`,
|
||
registration_number: `REG-${index}`,
|
||
timezone: "UTC",
|
||
address: {
|
||
street: "Main St",
|
||
city: "Tehran",
|
||
state: "Tehran",
|
||
postal_code: "12345",
|
||
country: "IR",
|
||
},
|
||
contact_person: {
|
||
full_name: "Jane Doe",
|
||
first_name: "Jane",
|
||
last_name: "Doe",
|
||
email: "jane.doe@example.com",
|
||
phone: "1234567890",
|
||
mobile: "1234567890",
|
||
position: "Manager",
|
||
is_primary: true,
|
||
},
|
||
tags: {
|
||
keywords: [],
|
||
categories: [],
|
||
specializations: [],
|
||
certifications: [],
|
||
cpv_codes: [],
|
||
},
|
||
...overrides,
|
||
});
|
||
|
||
const customersResponse = ({
|
||
customers,
|
||
offset = 0,
|
||
limit = 10,
|
||
total = customers.length,
|
||
pages = Math.max(1, Math.ceil(total / limit)),
|
||
}: {
|
||
customers: Record<string, any>[];
|
||
offset?: number;
|
||
limit?: number;
|
||
total?: number;
|
||
pages?: number;
|
||
}) => ({
|
||
success: true,
|
||
message: "ok",
|
||
data: { customers },
|
||
meta: { offset, limit, total, pages, page: Math.floor(offset / limit) + 1 },
|
||
});
|
||
|
||
describe("customers management - list, actions, filters, feedback", () => {
|
||
beforeEach(() => {
|
||
cy.setAuthCookies();
|
||
});
|
||
|
||
it("renders table columns and row data from API", () => {
|
||
const customers = [
|
||
makeCustomer(
|
||
{
|
||
full_name: "John Carter",
|
||
email: "john.carter@example.com",
|
||
companies: [{ id: "c1", name: "Acme" }],
|
||
},
|
||
1,
|
||
),
|
||
makeCustomer({ status: "inactive", role: "analyst" }, 2),
|
||
];
|
||
|
||
cy.intercept("GET", "**/api/proxy/customers**", {
|
||
statusCode: 200,
|
||
body: customersResponse({ customers }),
|
||
}).as("getCustomers");
|
||
|
||
cy.visit("/customers");
|
||
cy.wait("@getCustomers");
|
||
|
||
[
|
||
"row",
|
||
"full name",
|
||
"email",
|
||
"created at",
|
||
"type",
|
||
"status",
|
||
"company",
|
||
"role",
|
||
"actions",
|
||
].forEach((header) => cy.contains("th", new RegExp(`^${header}$`, "i")).should("be.visible"));
|
||
|
||
cy.contains("John Carter").should("be.visible");
|
||
cy.contains("john.carter@example.com").should("be.visible");
|
||
cy.contains("Acme").should("be.visible");
|
||
});
|
||
|
||
it("shows empty state when API returns no customers", () => {
|
||
cy.intercept("GET", "**/api/proxy/customers**", {
|
||
statusCode: 200,
|
||
body: customersResponse({ customers: [], total: 0 }),
|
||
}).as("getCustomersEmpty");
|
||
|
||
cy.visit("/customers");
|
||
cy.wait("@getCustomersEmpty");
|
||
|
||
cy.getByCy("empty-list-message").should("contain", "No Customers were found");
|
||
});
|
||
|
||
it("shows loading skeleton while list request is in flight", () => {
|
||
cy.intercept("GET", "**/api/proxy/customers**", (req) => {
|
||
req.reply({
|
||
delay: 1200,
|
||
statusCode: 200,
|
||
body: customersResponse({ customers: [makeCustomer()] }),
|
||
});
|
||
}).as("getCustomersSlow");
|
||
|
||
cy.visit("/customers");
|
||
cy.getByCy("table-skeleton").should("be.visible");
|
||
cy.wait("@getCustomersSlow");
|
||
cy.getByCy("table-skeleton").should("not.exist");
|
||
});
|
||
|
||
it("paginates with correct offset when moving to next page", () => {
|
||
const pageOne = Array.from({ length: 10 }).map((_, i) => makeCustomer({}, i + 1));
|
||
const pageTwo = [makeCustomer({ id: "507f1f77bcf86cd799439099", full_name: "Page 2 Customer" }, 99)];
|
||
|
||
cy.intercept("GET", "**/api/proxy/customers**", (req) => {
|
||
const offset = Number(req.query.offset ?? 0);
|
||
if (offset === 10) {
|
||
req.alias = "getCustomersPage2";
|
||
req.reply({
|
||
statusCode: 200,
|
||
body: customersResponse({
|
||
customers: pageTwo,
|
||
offset: 10,
|
||
limit: 10,
|
||
total: 11,
|
||
pages: 2,
|
||
}),
|
||
});
|
||
return;
|
||
}
|
||
req.alias = "getCustomersPage1";
|
||
req.reply({
|
||
statusCode: 200,
|
||
body: customersResponse({
|
||
customers: pageOne,
|
||
offset: 0,
|
||
limit: 10,
|
||
total: 11,
|
||
pages: 2,
|
||
}),
|
||
});
|
||
});
|
||
|
||
cy.visit("/customers");
|
||
cy.wait("@getCustomersPage1");
|
||
cy.contains("Page 2 Customer").should("not.exist");
|
||
cy.contains("›").click();
|
||
cy.wait("@getCustomersPage2")
|
||
.its("request.query.offset")
|
||
.should("eq", "10");
|
||
cy.contains("Page 2 Customer").should("be.visible");
|
||
});
|
||
|
||
it("deletes customer with confirm modal and sends correct request", () => {
|
||
const customer = makeCustomer({ id: "507f1f77bcf86cd799439066", full_name: "Delete Me" }, 66);
|
||
|
||
cy.intercept("GET", "**/api/proxy/customers**", {
|
||
statusCode: 200,
|
||
body: customersResponse({ customers: [customer] }),
|
||
}).as("getCustomers");
|
||
|
||
cy.intercept("DELETE", `**/api/proxy/customers/${customer.id}`, {
|
||
statusCode: 200,
|
||
body: { success: true, data: { message: "Customer deleted successfully" } },
|
||
}).as("deleteCustomer");
|
||
|
||
cy.visit("/customers");
|
||
cy.wait("@getCustomers");
|
||
cy.contains("Delete Me").should("be.visible");
|
||
|
||
cy.get('button[data-tooltip-id="delete"]').first().click();
|
||
cy.getByCy("confirmation-modal").should("be.visible");
|
||
cy.getByCy("confirmation-modal-cancel").click();
|
||
cy.getByCy("confirmation-modal").should("not.exist");
|
||
|
||
cy.intercept("GET", "**/api/proxy/customers**", {
|
||
statusCode: 200,
|
||
body: customersResponse({ customers: [], total: 0 }),
|
||
}).as("getCustomersAfterDelete");
|
||
|
||
cy.get('button[data-tooltip-id="delete"]').first().click();
|
||
cy.getByCy("confirmation-modal-confirm").click();
|
||
cy.wait("@deleteCustomer");
|
||
cy.wait("@getCustomersAfterDelete");
|
||
cy.getByCy("empty-list-message").should("contain", "No Customers were found");
|
||
});
|
||
|
||
it("assigns customer to company with correct payload", () => {
|
||
const customer = makeCustomer(
|
||
{ id: "507f1f77bcf86cd799439033", companies: [{ id: "comp-2", name: "Global LLC" }] },
|
||
33,
|
||
);
|
||
const companies = [
|
||
makeCompany({ id: "comp-1", name: "Acme Corp" }, 1),
|
||
makeCompany({ id: "comp-2", name: "Global LLC" }, 2),
|
||
];
|
||
|
||
cy.intercept("GET", "**/api/proxy/customers**", {
|
||
statusCode: 200,
|
||
body: customersResponse({ customers: [customer] }),
|
||
}).as("getCustomers");
|
||
|
||
cy.intercept("GET", "**/api/proxy/companies**", {
|
||
statusCode: 200,
|
||
body: {
|
||
success: true,
|
||
message: "ok",
|
||
data: { companies },
|
||
meta: { offset: 0, limit: 10, total: companies.length },
|
||
},
|
||
}).as("getCompanies");
|
||
|
||
cy.intercept("POST", `**/api/proxy/customers/${customer.id}/companies`, (req) => {
|
||
expect(req.body).to.deep.equal({ company_ids: ["comp-2"] });
|
||
req.reply({
|
||
statusCode: 200,
|
||
body: { success: true, message: "Selected Companies were assigned", data: {} },
|
||
});
|
||
}).as("assignCompany");
|
||
|
||
cy.visit("/customers");
|
||
cy.wait("@getCustomers");
|
||
cy.get('button[data-tooltip-id="assign-company"]').first().click();
|
||
cy.wait("@getCompanies");
|
||
cy.getByCy("modal-confirm-button").click();
|
||
cy.wait("@assignCompany");
|
||
});
|
||
|
||
it("surfaces assign-company API failures (404)", () => {
|
||
const customer = makeCustomer(
|
||
{ id: "507f1f77bcf86cd799439034", companies: [{ id: "comp-1", name: "Acme Corp" }] },
|
||
34,
|
||
);
|
||
|
||
cy.intercept("GET", "**/api/proxy/customers**", {
|
||
statusCode: 200,
|
||
body: customersResponse({ customers: [customer] }),
|
||
}).as("getCustomers");
|
||
|
||
cy.intercept("GET", "**/api/proxy/companies**", {
|
||
statusCode: 200,
|
||
body: {
|
||
success: true,
|
||
message: "ok",
|
||
data: { companies: [makeCompany({ id: "comp-1", name: "Acme Corp" }, 1)] },
|
||
},
|
||
}).as("getCompanies");
|
||
|
||
cy.intercept("POST", `**/api/proxy/customers/${customer.id}/companies`, {
|
||
statusCode: 404,
|
||
body: { error: { message: "Route not found" } },
|
||
}).as("assignCompany404");
|
||
|
||
cy.visit("/customers");
|
||
cy.wait("@getCustomers");
|
||
cy.get('button[data-tooltip-id="assign-company"]').first().click();
|
||
cy.wait("@getCompanies");
|
||
cy.getByCy("modal-confirm-button").click();
|
||
cy.wait("@assignCompany404").its("response.statusCode").should("eq", 404);
|
||
cy.getByCy("modal-content").should("be.visible");
|
||
});
|
||
|
||
it("applies status/role/type filters and keeps close action side-effect free", () => {
|
||
const unfiltered = [makeCustomer({ full_name: "Unfiltered Customer" }, 11)];
|
||
const filtered = [makeCustomer({ full_name: "Filtered Customer", status: "inactive" }, 12)];
|
||
|
||
cy.intercept("GET", "**/api/proxy/customers**", (req) => {
|
||
const isFiltered =
|
||
req.query.status === "inactive" &&
|
||
req.query.role === "analyst" &&
|
||
req.query.type === "company";
|
||
req.alias = isFiltered ? "getCustomersFiltered" : "getCustomersDefault";
|
||
req.reply({
|
||
statusCode: 200,
|
||
body: customersResponse({
|
||
customers: isFiltered ? filtered : unfiltered,
|
||
total: 1,
|
||
}),
|
||
});
|
||
});
|
||
|
||
cy.visit("/customers");
|
||
cy.wait("@getCustomersDefault");
|
||
|
||
cy.contains("button", "Filter").click();
|
||
cy.get('select[name="status"]').select("inactive");
|
||
cy.get('select[name="role"]').select("analyst");
|
||
cy.get('select[name="type"]').select("company");
|
||
cy.contains("button", "Search").click();
|
||
|
||
cy.wait("@getCustomersFiltered");
|
||
cy.contains("Filtered Customer").should("be.visible");
|
||
|
||
cy.contains("button", "Filter").click();
|
||
cy.contains("button", "Close").click();
|
||
cy.get("@getCustomersFiltered.all").should("have.length", 1);
|
||
});
|
||
|
||
it("navigates to feedback page and loads customer feedback list", () => {
|
||
const customer = makeCustomer({ id: "507f1f77bcf86cd799439044", full_name: "Feedback Target" }, 44);
|
||
|
||
cy.intercept("GET", "**/api/proxy/customers**", {
|
||
statusCode: 200,
|
||
body: customersResponse({ customers: [customer] }),
|
||
}).as("getCustomers");
|
||
|
||
cy.intercept("GET", "**/api/proxy/feedback**", (req) => {
|
||
expect(req.query.customer_id).to.eq(customer.id);
|
||
req.reply({
|
||
statusCode: 200,
|
||
body: {
|
||
success: true,
|
||
message: "ok",
|
||
data: [
|
||
{
|
||
id: "fb-1",
|
||
feedback_type: "bug",
|
||
created_at: 1710001000,
|
||
updated_at: 1710001200,
|
||
},
|
||
],
|
||
},
|
||
});
|
||
}).as("getCustomerFeedback");
|
||
|
||
cy.visit("/customers");
|
||
cy.wait("@getCustomers");
|
||
cy.get('button[data-tooltip-id="feedback"]').first().click();
|
||
// Route transition can be delayed in headless runs.
|
||
cy.location("pathname", { timeout: 15000 }).should(
|
||
"eq",
|
||
`/customers/feedback/${customer.id}`,
|
||
);
|
||
cy.wait("@getCustomerFeedback");
|
||
cy.contains("feedback type").should("be.visible");
|
||
});
|
||
});
|