Files
tm_panel/cypress/e2e/customers/customers-management.cy.ts
T
AmirReza Jamali f48cb7e249 chore: update app version and enhance UI components
- 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.
2026-05-05 15:20:55 +03:30

378 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// <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");
});
});