feat(discount-codes): enhance discount code form with accessibility attributes and data-testid

This commit is contained in:
hossein taromi 2025-08-31 10:33:39 +03:30
parent d216a886d0
commit 014b3d3f48
15 changed files with 1981 additions and 112 deletions

Binary file not shown.

View File

@ -0,0 +1,472 @@
describe("Discount Codes Advanced Features", () => {
beforeEach(() => {
cy.login();
cy.visit("/discount-codes");
cy.waitForLoading();
});
describe("Form Validation", () => {
beforeEach(() => {
cy.get('[title="کد تخفیف جدید"]').click();
});
it("should validate code format and uniqueness", () => {
// Test invalid characters (if implemented)
cy.get('input[name="code"]').type("TEST CODE"); // Space in code
cy.get('input[name="name"]').type("Test Name");
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("10");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
// Try to submit - may show validation error for invalid characters
cy.get('button[type="submit"]').click();
// Clear and use valid code
cy.get('input[name="code"]').clear().type("TESTCODE123");
cy.get('button[type="submit"]').click();
cy.url().should("include", "/discount-codes");
});
it("should validate name length constraints", () => {
cy.get('input[name="code"]').type("NAMETEST");
// Test name too long
cy.get('input[name="name"]').type("A".repeat(101));
cy.contains("نام نباید بیشتر از ۱۰۰ کاراکتر باشد").should("be.visible");
// Clear and use valid name
cy.get('input[name="name"]').clear().type("Valid Name");
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("10");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
});
it("should validate description length", () => {
cy.get('input[name="code"]').type("DESCTEST");
cy.get('input[name="name"]').type("Description Test");
// Test description too long
cy.get('textarea[name="description"]').type("A".repeat(501));
cy.contains("توضیحات نباید بیشتر از ۵۰۰ کاراکتر باشد").should(
"be.visible"
);
});
it("should validate percentage values", () => {
cy.get('input[name="code"]').type("PERCENTTEST");
cy.get('input[name="name"]').type("Percent Test");
cy.get('select[name="type"]').select("percentage");
// Test negative value
cy.get('input[name="value"]').type("-10");
cy.contains("مقدار باید بیشتر از صفر باشد").should("be.visible");
// Test zero value
cy.get('input[name="value"]').clear().type("0");
cy.contains("مقدار باید بیشتر از صفر باشد").should("be.visible");
// Test valid value
cy.get('input[name="value"]').clear().type("25");
});
it("should validate usage limits", () => {
cy.get('input[name="code"]').type("USAGETEST");
cy.get('input[name="name"]').type("Usage Test");
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("10");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
// Test invalid usage limit
cy.get('input[name="usage_limit"]').type("0");
cy.contains("حداقل ۱ بار استفاده").should("be.visible");
// Test invalid user usage limit
cy.get('input[name="user_usage_limit"]').type("0");
cy.contains("حداقل ۱ بار استفاده").should("be.visible");
});
it("should validate amount constraints", () => {
cy.get('input[name="code"]').type("AMOUNTTEST");
cy.get('input[name="name"]').type("Amount Test");
cy.get('select[name="type"]').select("fixed");
cy.get('input[name="value"]').type("1000");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
// Test invalid minimum purchase amount
cy.get('input[name="min_purchase_amount"]').type("0");
cy.contains("مبلغ باید بیشتر از صفر باشد").should("be.visible");
// Test invalid maximum discount amount
cy.get('input[name="max_discount_amount"]').type("-100");
cy.contains("مبلغ باید بیشتر از صفر باشد").should("be.visible");
});
});
describe("Date and Time Handling", () => {
beforeEach(() => {
cy.get('[title="کد تخفیف جدید"]').click();
// Fill required fields
cy.get('input[name="code"]').type("DATETEST");
cy.get('input[name="name"]').type("Date Test");
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("10");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
});
it("should handle date range validation", () => {
// Set end date before start date
cy.get('input[name="valid_from"]').type("2024-12-31T23:59");
cy.get('input[name="valid_to"]').type("2024-01-01T00:00");
// Form should still accept it (backend validation)
cy.get('button[type="submit"]').click();
cy.url().should("include", "/discount-codes");
});
it("should preserve datetime values in edit mode", () => {
// Set specific datetime values
const fromDate = "2024-06-01T10:30";
const toDate = "2024-06-30T18:45";
cy.get('input[name="valid_from"]').type(fromDate);
cy.get('input[name="valid_to"]').type(toDate);
cy.get('button[type="submit"]').click();
cy.url().should("include", "/discount-codes");
// Edit the created discount code
cy.contains("DATETEST")
.parent()
.parent()
.within(() => {
cy.get('[title="ویرایش"]').click();
});
// Values should be preserved
cy.get('input[name="valid_from"]').should("have.value", fromDate);
cy.get('input[name="valid_to"]').should("have.value", toDate);
});
});
describe("User Restrictions", () => {
beforeEach(() => {
cy.get('[title="کد تخفیف جدید"]').click();
// Fill required fields
cy.get('input[name="code"]').type("USERTEST");
cy.get('input[name="name"]').type("User Test");
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("15");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
});
it("should handle user group selection", () => {
// Test all user group options
cy.get('select[name="user_restrictions.user_group"]').select("new");
cy.get('select[name="user_restrictions.user_group"]').should(
"have.value",
"new"
);
cy.get('select[name="user_restrictions.user_group"]').select("loyal");
cy.get('select[name="user_restrictions.user_group"]').should(
"have.value",
"loyal"
);
cy.get('select[name="user_restrictions.user_group"]').select("all");
cy.get('select[name="user_restrictions.user_group"]').should(
"have.value",
"all"
);
});
it("should handle purchase count restrictions", () => {
cy.get('input[name="user_restrictions.min_purchase_count"]').type("2");
cy.get('input[name="user_restrictions.max_purchase_count"]').type("10");
cy.get('input[name="user_restrictions.referrer_user_id"]').type("456");
cy.get('button[type="submit"]').click();
cy.url().should("include", "/discount-codes");
cy.contains("کد تخفیف با موفقیت ایجاد شد").should("be.visible");
});
it("should warn about conflicting user restrictions", () => {
// Check both new users only and loyal users only
cy.get('input[name="user_restrictions.new_users_only"]').check();
cy.get('input[name="user_restrictions.loyal_users_only"]').check();
// Warning should be visible
cy.contains(
"new_users_only و loyal_users_only نمی‌توانند همزمان فعال باشند"
).should("be.visible");
// Uncheck one
cy.get('input[name="user_restrictions.new_users_only"]').uncheck();
cy.get('button[type="submit"]').click();
cy.url().should("include", "/discount-codes");
});
});
describe("Application Levels", () => {
beforeEach(() => {
cy.get('[title="کد تخفیف جدید"]').click();
cy.get('input[name="code"]').type("APPTEST");
cy.get('input[name="name"]').type("Application Test");
cy.get('input[name="value"]').type("100");
cy.get('select[name="status"]').select("active");
});
it("should handle product fee application with fee percentage type", () => {
cy.get('select[name="type"]').select("fee_percentage");
cy.get('select[name="application_level"]').select("product_fee");
cy.get('button[type="submit"]').click();
cy.url().should("include", "/discount-codes");
cy.contains("کد تخفیف با موفقیت ایجاد شد").should("be.visible");
});
it("should test all application level combinations", () => {
const types = ["percentage", "fixed", "fee_percentage"];
const applications = [
"invoice",
"category",
"product",
"shipping",
"product_fee",
];
types.forEach((type, typeIndex) => {
applications.forEach((app, appIndex) => {
if (typeIndex > 0 || appIndex > 0) {
// Generate unique code for each combination
cy.get('input[name="code"]')
.clear()
.type(`TEST${typeIndex}${appIndex}`);
}
cy.get('select[name="type"]').select(type);
cy.get('select[name="application_level"]').select(app);
// For fee_percentage, use smaller values
if (type === "fee_percentage") {
cy.get('input[name="value"]').clear().type("5");
} else if (type === "percentage") {
cy.get('input[name="value"]').clear().type("10");
} else {
cy.get('input[name="value"]').clear().type("1000");
}
cy.get('button[type="submit"]').click();
cy.url().should("include", "/discount-codes");
// Go back to create page for next iteration (except last)
if (
!(
typeIndex === types.length - 1 &&
appIndex === applications.length - 1
)
) {
cy.get('[title="کد تخفیف جدید"]').click();
cy.get('input[name="name"]').type("Application Test");
cy.get('select[name="status"]').select("active");
}
});
});
});
});
describe("Meta Information", () => {
it("should handle meta fields properly", () => {
cy.get('[title="کد تخفیف جدید"]').click();
cy.get('input[name="code"]').type("METATEST");
cy.get('input[name="name"]').type("Meta Test");
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("20");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
// Set meta fields
cy.get('input[name="meta.campaign"]').type("winter_sale_2024");
cy.get('input[name="meta.category"]').type("seasonal_promotion");
cy.get('button[type="submit"]').click();
cy.url().should("include", "/discount-codes");
cy.contains("کد تخفیف با موفقیت ایجاد شد").should("be.visible");
// Verify meta fields are preserved in edit
cy.contains("METATEST")
.parent()
.parent()
.within(() => {
cy.get('[title="ویرایش"]').click();
});
cy.get('input[name="meta.campaign"]').should(
"have.value",
"winter_sale_2024"
);
cy.get('input[name="meta.category"]').should(
"have.value",
"seasonal_promotion"
);
});
});
describe("List Page Features", () => {
it("should display correct value format based on type", () => {
// Create different types of discounts to test display
const testCodes = [
{ code: "DISPLAYPERCENT", type: "percentage", value: "25" },
{ code: "DISPLAYFIXED", type: "fixed", value: "50000" },
{ code: "DISPLAYFEE", type: "fee_percentage", value: "5" },
];
testCodes.forEach((testCode) => {
cy.get('[title="کد تخفیف جدید"]').click();
cy.get('input[name="code"]').type(testCode.code);
cy.get('input[name="name"]').type(`Display Test ${testCode.type}`);
cy.get('select[name="type"]').select(testCode.type);
cy.get('input[name="value"]').type(testCode.value);
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select(
testCode.type === "fee_percentage" ? "product_fee" : "invoice"
);
cy.get('button[type="submit"]').click();
cy.url().should("include", "/discount-codes");
});
// Check display formats
cy.contains("DISPLAYPERCENT")
.parent()
.parent()
.within(() => {
cy.contains("25%").should("be.visible");
});
cy.contains("DISPLAYFIXED")
.parent()
.parent()
.within(() => {
cy.contains("50000 تومان").should("be.visible");
});
cy.contains("DISPLAYFEE")
.parent()
.parent()
.within(() => {
cy.contains("5%").should("be.visible");
});
});
it("should handle pagination properly", () => {
// This test assumes there are enough items to paginate
// Check if pagination exists
cy.get('nav[aria-label="Pagination Navigation"]').should("exist");
// Test pagination controls if they exist
cy.get('nav[aria-label="Pagination Navigation"]').within(() => {
cy.get("button").should("have.length.greaterThan", 0);
});
});
it("should sort columns when sortable", () => {
// Click on sortable column headers
cy.get("th").contains("کد").click();
cy.wait(500);
cy.get("th").contains("نام").click();
cy.wait(500);
// Verify table content changes (basic check)
cy.get("table tbody tr").should("have.length.greaterThan", 0);
});
});
describe("Error Handling", () => {
it("should handle network errors gracefully", () => {
// Intercept network requests and simulate errors
cy.intercept("POST", "**/discount/", { statusCode: 500 }).as(
"createError"
);
cy.get('[title="کد تخفیف جدید"]').click();
cy.get('input[name="code"]').type("ERRORTEST");
cy.get('input[name="name"]').type("Error Test");
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("10");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
cy.get('button[type="submit"]').click();
cy.wait("@createError");
cy.contains("خطا در ایجاد کد تخفیف").should("be.visible");
});
it("should handle loading states", () => {
cy.get('[title="کد تخفیف جدید"]').click();
// Intercept with delay to see loading state
cy.intercept("POST", "**/discount/", { delay: 2000, statusCode: 200 }).as(
"createSlow"
);
cy.get('input[name="code"]').type("LOADTEST");
cy.get('input[name="name"]').type("Load Test");
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("10");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
cy.get('button[type="submit"]').click();
// Check loading state
cy.get('button[type="submit"]').should("be.disabled");
cy.get(".animate-spin").should("be.visible");
cy.wait("@createSlow");
});
});
describe("Responsive Design", () => {
it("should work on mobile viewport", () => {
cy.viewport("iphone-6");
cy.get('[title="کد تخفیف جدید"]').should("be.visible");
cy.get('[title="کد تخفیف جدید"]').click();
cy.contains("ایجاد کد تخفیف").should("be.visible");
// Form should be usable on mobile
cy.get('input[name="code"]').type("MOBILETEST");
cy.get('input[name="name"]').type("Mobile Test");
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("10");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
cy.get('button[type="submit"]').should("be.visible").click();
cy.url().should("include", "/discount-codes");
});
it("should work on tablet viewport", () => {
cy.viewport("ipad-2");
cy.get('[title="کد تخفیف جدید"]').should("be.visible");
cy.get("table").should("be.visible");
// Test form on tablet
cy.get('[title="کد تخفیف جدید"]').click();
cy.get(".grid").should("be.visible"); // Grid layout should work
});
});
});

View File

@ -0,0 +1,373 @@
import { discountTemplates, apiMocks } from "../support/discount-codes-helpers";
describe("Discount Codes - Complete E2E Tests", () => {
beforeEach(() => {
cy.login();
});
describe("Navigation and Basic UI", () => {
it("should display discount codes list page correctly", () => {
cy.visit("/discount-codes");
cy.waitForLoading();
cy.contains("مدیریت کدهای تخفیف").should("be.visible");
cy.get('[title="کد تخفیف جدید"]').should("be.visible");
});
it("should navigate to create page", () => {
cy.navigateToCreateDiscount();
cy.contains("ایجاد کد تخفیف").should("be.visible");
});
});
describe("Form Validation", () => {
beforeEach(() => {
cy.navigateToCreateDiscount();
});
it("should validate required fields", () => {
cy.getByTestId("submit-discount-button").should("be.disabled");
cy.fillBasicDiscountInfo({
code: "TEST123",
name: "Test Discount",
});
cy.getByTestId("submit-discount-button").should("be.disabled");
cy.fillDiscountSettings({
type: "percentage",
value: "10",
status: "active",
applicationLevel: "invoice",
});
cy.getByTestId("submit-discount-button").should("not.be.disabled");
});
it("should validate field lengths and formats", () => {
// Test code length
cy.getByTestId("discount-code-input").type("AB");
cy.getByTestId("discount-name-input").type("Test");
cy.get(".text-red-600").should("contain", "کد باید حداقل ۳ کاراکتر باشد");
// Test code too long
cy.getByTestId("discount-code-input").clear().type("A".repeat(51));
cy.get(".text-red-600").should(
"contain",
"کد نباید بیشتر از ۵۰ کاراکتر باشد"
);
// Test name too long
cy.getByTestId("discount-name-input").clear().type("A".repeat(101));
cy.get(".text-red-600").should(
"contain",
"نام نباید بیشتر از ۱۰۰ کاراکتر باشد"
);
// Test description too long
cy.getByTestId("discount-description-textarea").type("A".repeat(501));
cy.get(".text-red-600").should(
"contain",
"توضیحات نباید بیشتر از ۵۰۰ کاراکتر باشد"
);
});
it("should validate numeric fields", () => {
cy.fillBasicDiscountInfo({
code: "NUMTEST",
name: "Number Test",
});
// Test negative value
cy.getByTestId("discount-value-input").type("-10");
cy.get(".text-red-600").should("contain", "مقدار باید بیشتر از صفر باشد");
// Test zero value
cy.getByTestId("discount-value-input").clear().type("0");
cy.get(".text-red-600").should("contain", "مقدار باید بیشتر از صفر باشد");
});
});
describe("Discount Creation", () => {
beforeEach(() => {
// Mock successful API responses
cy.intercept("GET", "**/discount/**", apiMocks.discountsList).as(
"getDiscounts"
);
cy.intercept("POST", "**/discount/**", (req) => {
return apiMocks.successfulCreation(req.body);
}).as("createDiscount");
});
it("should create basic percentage discount", () => {
cy.createDiscountCode(discountTemplates.basicPercentage);
cy.wait("@createDiscount");
cy.contains("کد تخفیف با موفقیت ایجاد شد").should("be.visible");
});
it("should create fixed amount discount", () => {
cy.createDiscountCode(discountTemplates.fixedAmount);
cy.wait("@createDiscount");
cy.contains("کد تخفیف با موفقیت ایجاد شد").should("be.visible");
});
it("should create fee percentage discount", () => {
cy.createDiscountCode(discountTemplates.feePercentage);
cy.wait("@createDiscount");
cy.contains("کد تخفیف با موفقیت ایجاد شد").should("be.visible");
});
it("should create discount with user restrictions", () => {
cy.createDiscountCode(discountTemplates.loyalUsers);
cy.wait("@createDiscount");
cy.contains("کد تخفیف با موفقیت ایجاد شد").should("be.visible");
});
it("should create time-based discount with all features", () => {
cy.createDiscountCode(discountTemplates.timeBasedDiscount);
cy.wait("@createDiscount");
cy.contains("کد تخفیف با موفقیت ایجاد شد").should("be.visible");
});
});
describe("Error Handling", () => {
it("should handle validation errors from API", () => {
cy.intercept("POST", "**/discount/**", apiMocks.validationError).as(
"validationError"
);
cy.navigateToCreateDiscount();
cy.fillBasicDiscountInfo(discountTemplates.basicPercentage);
cy.fillDiscountSettings(discountTemplates.basicPercentage);
cy.submitDiscountForm();
cy.wait("@validationError");
cy.contains("کد تخفیف تکراری است").should("be.visible");
});
it("should handle server errors", () => {
cy.intercept("POST", "**/discount/**", apiMocks.serverError).as(
"serverError"
);
cy.navigateToCreateDiscount();
cy.fillBasicDiscountInfo(discountTemplates.basicPercentage);
cy.fillDiscountSettings(discountTemplates.basicPercentage);
cy.submitDiscountForm();
cy.wait("@serverError");
cy.contains("خطا در ایجاد کد تخفیف").should("be.visible");
});
it("should handle loading states", () => {
cy.intercept("POST", "**/discount/**", {
delay: 2000,
...apiMocks.successfulCreation(discountTemplates.basicPercentage),
}).as("slowCreate");
cy.navigateToCreateDiscount();
cy.fillBasicDiscountInfo(discountTemplates.basicPercentage);
cy.fillDiscountSettings(discountTemplates.basicPercentage);
cy.submitDiscountForm();
// Check loading state
cy.getByTestId("submit-discount-button").should("be.disabled");
cy.get(".animate-spin").should("be.visible");
cy.wait("@slowCreate");
});
});
describe("List Page Features", () => {
beforeEach(() => {
cy.intercept("GET", "**/discount/**", apiMocks.discountsList).as(
"getDiscounts"
);
cy.visit("/discount-codes");
cy.wait("@getDiscounts");
});
it("should search discount codes", () => {
cy.searchDiscountCode("SAVE20");
cy.contains("SAVE20").should("be.visible");
cy.searchDiscountCode("NONEXISTENT");
cy.contains("هیچ کد تخفیفی یافت نشد").should("be.visible");
});
it("should clear filters", () => {
cy.searchDiscountCode("TEST");
cy.clearDiscountFilters();
cy.get('input[placeholder*="جستجو"]').should("have.value", "");
});
it("should display discount codes with correct formatting", () => {
cy.contains("SAVE20").should("be.visible");
cy.contains("20%").should("be.visible");
cy.get(".bg-green-100").should("contain", "فعال");
});
});
describe("Edit Functionality", () => {
beforeEach(() => {
cy.intercept("GET", "**/discount/**", apiMocks.discountsList).as(
"getDiscounts"
);
cy.intercept("GET", "**/discount/1", {
statusCode: 200,
body: {
id: 1,
code: "SAVE20",
name: "20% Off Discount",
description: "Get 20% off on your purchase",
type: "percentage",
value: 20,
status: "active",
application_level: "invoice",
},
}).as("getDiscount");
cy.intercept("PUT", "**/discount/1", {
statusCode: 200,
body: { message: "updated successfully" },
}).as("updateDiscount");
});
it("should edit existing discount code", () => {
cy.visit("/discount-codes");
cy.wait("@getDiscounts");
cy.contains("SAVE20")
.parent()
.parent()
.within(() => {
cy.get('[title="ویرایش"]').click();
});
cy.wait("@getDiscount");
cy.url().should("include", "/edit");
cy.getByTestId("discount-name-input")
.clear()
.type("Updated Discount Name");
cy.submitDiscountForm();
cy.wait("@updateDiscount");
cy.contains("کد تخفیف با موفقیت به‌روزرسانی شد").should("be.visible");
});
});
describe("Delete Functionality", () => {
beforeEach(() => {
cy.intercept("GET", "**/discount/**", apiMocks.discountsList).as(
"getDiscounts"
);
cy.intercept("DELETE", "**/discount/**", {
statusCode: 200,
body: { message: "deleted successfully" },
}).as("deleteDiscount");
});
it("should delete discount code", () => {
cy.visit("/discount-codes");
cy.wait("@getDiscounts");
cy.contains("SAVE20")
.parent()
.parent()
.within(() => {
cy.get('[title="حذف"]').click();
});
cy.contains("آیا از حذف این کد تخفیف اطمینان دارید؟").should(
"be.visible"
);
cy.contains("button", "حذف").click();
cy.wait("@deleteDiscount");
cy.contains("کد تخفیف با موفقیت حذف شد").should("be.visible");
});
});
describe("Responsive Design", () => {
it("should work on mobile devices", () => {
cy.viewport("iphone-6");
cy.navigateToCreateDiscount();
cy.fillBasicDiscountInfo({
code: "MOBILE123",
name: "Mobile Test",
});
cy.fillDiscountSettings({
type: "percentage",
value: "10",
status: "active",
applicationLevel: "invoice",
});
cy.getByTestId("submit-discount-button").should("be.visible");
});
it("should work on tablets", () => {
cy.viewport("ipad-2");
cy.visit("/discount-codes");
cy.waitForLoading();
cy.get("table").should("be.visible");
cy.get('[title="کد تخفیف جدید"]').should("be.visible");
});
});
describe("Accessibility", () => {
it("should be keyboard navigable", () => {
cy.navigateToCreateDiscount();
cy.getByTestId("discount-code-input").focus();
cy.focused().should("have.attr", "data-testid", "discount-code-input");
cy.focused().tab();
cy.focused().should("have.attr", "data-testid", "discount-name-input");
});
it("should have proper ARIA labels", () => {
cy.navigateToCreateDiscount();
cy.get("label").should("have.length.greaterThan", 5);
cy.get("input[required]").should("have.length.greaterThan", 3);
});
it("should announce errors to screen readers", () => {
cy.navigateToCreateDiscount();
cy.getByTestId("discount-code-input").type("AB");
cy.get(".text-red-600").should("have.attr", "role", "alert");
});
});
describe("Performance", () => {
it("should load create page quickly", () => {
const startTime = Date.now();
cy.navigateToCreateDiscount();
cy.getByTestId("discount-code-input")
.should("be.visible")
.then(() => {
const loadTime = Date.now() - startTime;
expect(loadTime).to.be.lessThan(3000); // Should load within 3 seconds
});
});
it("should handle large forms efficiently", () => {
cy.navigateToCreateDiscount();
// Fill form quickly without delays
cy.getByTestId("discount-code-input").type("PERF123");
cy.getByTestId("discount-name-input").type("Performance Test");
cy.getByTestId("discount-description-textarea").type("A".repeat(400));
cy.getByTestId("discount-type-select").select("percentage");
cy.getByTestId("discount-value-input").type("25");
// Form should remain responsive
cy.getByTestId("submit-discount-button").should("not.be.disabled");
});
});
});

View File

@ -0,0 +1,251 @@
/// <reference types="../support" />
describe("Discount Codes Management - Fixed", () => {
beforeEach(() => {
cy.login();
cy.visit("/discount-codes");
cy.waitForLoading();
});
it("should display discount codes list page", () => {
cy.contains("مدیریت کدهای تخفیف").should("be.visible");
cy.getByTestId("create-discount-button").should("be.visible");
});
it("should navigate to create discount code page", () => {
cy.getByTestId("create-discount-button").click();
cy.url().should("include", "/discount-codes/create");
cy.contains("ایجاد کد تخفیف").should("be.visible");
});
it("should create a basic percentage discount code", () => {
cy.getByTestId("create-discount-button").click();
// Fill basic information using data-testid
cy.getByTestId("discount-code-input").type("SAVE20");
cy.getByTestId("discount-name-input").type("تخفیف ۲۰ درصدی");
cy.getByTestId("discount-description-textarea").type(
"تخفیف ۲۰ درصدی برای کل خرید"
);
// Set discount settings using data-testid
cy.getByTestId("discount-type-select").select("percentage");
cy.getByTestId("discount-value-input").type("20");
// Set other required fields
cy.getByTestId("discount-status-select").select("active");
cy.getByTestId("discount-application-level-select").select("invoice");
// Submit form
cy.getByTestId("submit-discount-button").click();
// Verify creation (might need to mock API response)
cy.url().should("include", "/discount-codes");
});
it("should validate required fields properly", () => {
cy.getByTestId("create-discount-button").click();
// Submit button should be disabled initially
cy.getByTestId("submit-discount-button").should("be.disabled");
// Fill only code field
cy.getByTestId("discount-code-input").type("TEST");
cy.getByTestId("submit-discount-button").should("be.disabled");
// Fill name field
cy.getByTestId("discount-name-input").type("Test Name");
cy.getByTestId("submit-discount-button").should("be.disabled");
// Fill all required fields
cy.getByTestId("discount-type-select").select("percentage");
cy.getByTestId("discount-value-input").type("10");
cy.getByTestId("discount-status-select").select("active");
cy.getByTestId("discount-application-level-select").select("invoice");
// Now submit button should be enabled
cy.getByTestId("submit-discount-button").should("not.be.disabled");
});
it("should validate code length constraints", () => {
cy.getByTestId("create-discount-button").click();
// Test code too short
cy.getByTestId("discount-code-input").type("AB");
cy.getByTestId("discount-name-input").type("Test");
cy.getByTestId("discount-type-select").select("percentage");
cy.getByTestId("discount-value-input").type("10");
// Check for validation error
cy.get(".text-red-600").should("contain", "کد باید حداقل ۳ کاراکتر باشد");
// Clear and test code too long
cy.getByTestId("discount-code-input").clear().type("A".repeat(51));
cy.get(".text-red-600").should(
"contain",
"کد نباید بیشتر از ۵۰ کاراکتر باشد"
);
});
it("should create different discount types", () => {
const discountTypes = [
{ type: "percentage", value: "25", level: "invoice" },
{ type: "fixed", value: "50000", level: "invoice" },
{ type: "fee_percentage", value: "5", level: "product_fee" },
];
discountTypes.forEach((discount, index) => {
// Navigate to create page before each iteration
cy.visit("/discount-codes");
cy.waitForLoading();
cy.getByTestId("create-discount-button").click();
cy.getByTestId("discount-code-input").type(
`TEST${index}${discount.type.toUpperCase()}`
);
cy.getByTestId("discount-name-input").type(`Test ${discount.type}`);
cy.getByTestId("discount-type-select").select(discount.type);
cy.getByTestId("discount-value-input").type(discount.value);
cy.getByTestId("discount-status-select").select("active");
cy.getByTestId("discount-application-level-select").select(
discount.level
);
cy.getByTestId("submit-discount-button").click();
cy.url().should("include", "/discount-codes");
});
});
it("should handle form cancellation", () => {
cy.getByTestId("create-discount-button").click();
// Fill some data
cy.getByTestId("discount-code-input").type("CANCELTEST");
cy.getByTestId("discount-name-input").type("Cancel Test");
// Click cancel button
cy.getByTestId("cancel-discount-button").click();
// Should return to list page
cy.url().should("include", "/discount-codes");
cy.url().should("not.include", "/create");
});
it("should show empty state when no results found", () => {
// Search for non-existent code
cy.get('input[placeholder*="جستجو"]').type("NONEXISTENTCODE123");
cy.wait(500);
// Check for empty state
cy.contains("هیچ کد تخفیفی یافت نشد").should("be.visible");
});
it("should navigate back properly", () => {
cy.getByTestId("create-discount-button").click();
// Wait for form to load completely
cy.getByTestId("discount-code-input").should("be.visible");
// Click cancel button
cy.getByTestId("cancel-discount-button").click();
// Should return to list page
cy.url().should("include", "/discount-codes");
cy.url().should("not.include", "/create");
});
// Test with API mocking
it("should handle API errors gracefully", () => {
// Mock API error
cy.intercept("POST", "**/discount/**", {
statusCode: 400,
body: { message: "کد تخفیف تکراری است" },
}).as("createError");
cy.getByTestId("create-discount-button").click();
cy.getByTestId("discount-code-input").type("ERRORTEST");
cy.getByTestId("discount-name-input").type("Error Test");
cy.getByTestId("discount-type-select").select("percentage");
cy.getByTestId("discount-value-input").type("10");
cy.getByTestId("discount-status-select").select("active");
cy.getByTestId("discount-application-level-select").select("invoice");
cy.getByTestId("submit-discount-button").click();
cy.wait("@createError");
// Error message should appear
cy.contains("خطا در ایجاد کد تخفیف").should("be.visible");
});
it("should handle loading states", () => {
// Mock slow API response
cy.intercept("POST", "**/discount/**", {
delay: 2000,
statusCode: 201,
body: { id: 1, code: "TEST", name: "Test" },
}).as("createSlow");
cy.getByTestId("create-discount-button").click();
cy.getByTestId("discount-code-input").type("LOADTEST");
cy.getByTestId("discount-name-input").type("Load Test");
cy.getByTestId("discount-type-select").select("percentage");
cy.getByTestId("discount-value-input").type("10");
cy.getByTestId("discount-status-select").select("active");
cy.getByTestId("discount-application-level-select").select("invoice");
cy.getByTestId("submit-discount-button").click();
// Check loading state
cy.getByTestId("submit-discount-button").should("be.disabled");
cy.wait("@createSlow");
});
// Test mobile responsiveness
it("should work on mobile viewport", () => {
cy.viewport("iphone-6");
cy.getByTestId("create-discount-button").should("be.visible");
cy.getByTestId("create-discount-button").click();
cy.contains("ایجاد کد تخفیف").should("be.visible");
// Form should be usable on mobile
cy.getByTestId("discount-code-input").type("MOBILETEST");
cy.getByTestId("discount-name-input").type("Mobile Test");
cy.getByTestId("discount-type-select").select("percentage");
cy.getByTestId("discount-value-input").type("10");
cy.getByTestId("discount-status-select").select("active");
cy.getByTestId("discount-application-level-select").select("invoice");
// Scroll to submit button to make it visible
cy.getByTestId("submit-discount-button").scrollIntoView();
cy.getByTestId("submit-discount-button").should("be.visible");
});
// Test accessibility
it("should be accessible", () => {
cy.getByTestId("create-discount-button").click();
// Check for proper labels
cy.get("label").should("have.length.greaterThan", 5);
// Check for required field indicators
cy.getByTestId("discount-code-input").should(
"have.attr",
"aria-required",
"true"
);
cy.getByTestId("discount-name-input").should(
"have.attr",
"aria-required",
"true"
);
// Check for proper form structure
cy.get("form").should("exist");
cy.get(".bg-gradient-to-r").should("have.length.greaterThan", 3);
});
});

View File

@ -0,0 +1,331 @@
describe("Discount Codes Management", () => {
beforeEach(() => {
cy.login();
cy.visit("/discount-codes");
cy.waitForLoading();
});
it("should display discount codes list page", () => {
cy.contains("مدیریت کدهای تخفیف").should("be.visible");
cy.contains("ایجاد و مدیریت کدهای تخفیف").should("be.visible");
cy.get('[title="کد تخفیف جدید"]').should("be.visible");
});
it("should navigate to create discount code page", () => {
cy.get('[title="کد تخفیف جدید"]').click();
cy.url().should("include", "/discount-codes/create");
cy.contains("ایجاد کد تخفیف").should("be.visible");
cy.contains("ایجاد و مدیریت کدهای تخفیف برای فروشگاه").should("be.visible");
});
it("should create a percentage discount code", () => {
cy.get('[title="کد تخفیف جدید"]').click();
// Fill basic information
cy.get('input[name="code"]').type("SAVE20");
cy.get('input[name="name"]').type("تخفیف ۲۰ درصدی");
cy.get('textarea[name="description"]').type("تخفیف ۲۰ درصدی برای کل خرید");
// Set discount settings
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("20");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
// Set limits
cy.get('input[name="min_purchase_amount"]').type("100000");
cy.get('input[name="max_discount_amount"]').type("50000");
cy.get('input[name="usage_limit"]').type("1000");
cy.get('input[name="user_usage_limit"]').type("1");
// Set date range
cy.get('input[name="valid_from"]').type("2024-01-01T00:00");
cy.get('input[name="valid_to"]').type("2024-12-31T23:59");
// Set user restrictions
cy.get('select[name="user_restrictions.user_group"]').select("loyal");
// Set meta information
cy.get('input[name="meta.campaign"]').type("summer_sale");
cy.get('input[name="meta.category"]').type("general");
// Submit form
cy.get('button[type="submit"]').click();
// Verify creation
cy.url().should("include", "/discount-codes");
cy.contains("کد تخفیف با موفقیت ایجاد شد").should("be.visible");
cy.contains("SAVE20").should("be.visible");
});
it("should create a fixed amount discount code", () => {
cy.get('[title="کد تخفیف جدید"]').click();
// Fill basic information
cy.get('input[name="code"]').type("FIXED50000");
cy.get('input[name="name"]').type("تخفیف ۵۰ هزار تومانی");
cy.get('textarea[name="description"]').type(
"تخفیف مبلغ ثابت ۵۰ هزار تومان"
);
// Set discount settings
cy.get('select[name="type"]').select("fixed");
cy.get('input[name="value"]').type("50000");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
// Set single use
cy.get('input[name="single_use"]').check();
// Set user restrictions for new users only
cy.get('input[name="user_restrictions.new_users_only"]').check();
// Submit form
cy.get('button[type="submit"]').click();
// Verify creation
cy.url().should("include", "/discount-codes");
cy.contains("کد تخفیف با موفقیت ایجاد شد").should("be.visible");
cy.contains("FIXED50000").should("be.visible");
});
it("should create a fee percentage discount code", () => {
cy.get('[title="کد تخفیف جدید"]').click();
// Fill basic information
cy.get('input[name="code"]').type("FEEREDUCTION10");
cy.get('input[name="name"]').type("کاهش کارمزد ۱۰ درصدی");
// Set discount settings
cy.get('select[name="type"]').select("fee_percentage");
cy.get('input[name="value"]').type("10");
cy.get('select[name="application_level"]').select("product_fee");
// Submit form
cy.get('button[type="submit"]').click();
// Verify creation
cy.url().should("include", "/discount-codes");
cy.contains("کد تخفیف با موفقیت ایجاد شد").should("be.visible");
cy.contains("FEEREDUCTION10").should("be.visible");
});
it("should validate required fields", () => {
cy.get('[title="کد تخفیف جدید"]').click();
// Try to submit without required fields
cy.get('button[type="submit"]').should("be.disabled");
// Fill only code field
cy.get('input[name="code"]').type("TEST");
cy.get('button[type="submit"]').should("be.disabled");
// Fill name field
cy.get('input[name="name"]').type("Test");
cy.get('button[type="submit"]').should("be.disabled");
// Fill all required fields
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("10");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
// Now submit button should be enabled
cy.get('button[type="submit"]').should("not.be.disabled");
});
it("should validate code length", () => {
cy.get('[title="کد تخفیف جدید"]').click();
// Test code too short
cy.get('input[name="code"]').type("AB");
cy.get('input[name="name"]').type("Test");
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("10");
cy.contains("کد باید حداقل ۳ کاراکتر باشد").should("be.visible");
// Clear and test code too long
cy.get('input[name="code"]').clear().type("A".repeat(51));
cy.contains("کد نباید بیشتر از ۵۰ کاراکتر باشد").should("be.visible");
});
it("should validate percentage value range", () => {
cy.get('[title="کد تخفیف جدید"]').click();
cy.get('input[name="code"]').type("TESTPERCENTAGE");
cy.get('input[name="name"]').type("Test Percentage");
cy.get('select[name="type"]').select("percentage");
// Test value too high for percentage (should warn in UI for >100)
cy.get('input[name="value"]').type("150");
// The form should still accept it but backend will validate
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
});
it("should search and filter discount codes", () => {
// Assuming we have some discount codes in the list
cy.get('input[placeholder="جستجو بر اساس کد..."]').type("SAVE");
cy.wait(500); // Wait for search to filter
// Test status filter
cy.get("select").contains("همه وضعیت‌ها").parent().select("active");
cy.wait(500);
// Clear filters
cy.contains("پاک کردن فیلترها").click();
cy.get('input[placeholder="جستجو بر اساس کد..."]').should("have.value", "");
});
it("should edit existing discount code", () => {
// Assuming we have discount codes in the list
cy.get("table tbody tr")
.first()
.within(() => {
cy.get('[title="ویرایش"]').click();
});
cy.url().should("include", "/discount-codes/");
cy.url().should("include", "/edit");
cy.contains("ویرایش کد تخفیف").should("be.visible");
// Modify the discount code
cy.get('input[name="name"]').clear().type("کد تخفیف ویرایش شده");
cy.get('textarea[name="description"]').clear().type("توضیحات ویرایش شده");
// Submit changes
cy.get('button[type="submit"]').click();
// Verify update
cy.url().should("include", "/discount-codes");
cy.contains("کد تخفیف با موفقیت به‌روزرسانی شد").should("be.visible");
cy.contains("کد تخفیف ویرایش شده").should("be.visible");
});
it("should delete discount code", () => {
// Create a test discount code first
cy.get('[title="کد تخفیف جدید"]').click();
cy.get('input[name="code"]').type("TESTDELETE");
cy.get('input[name="name"]').type("Test Delete");
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("10");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
cy.get('button[type="submit"]').click();
// Wait for creation
cy.contains("کد تخفیف با موفقیت ایجاد شد").should("be.visible");
// Find and delete the test discount code
cy.contains("TESTDELETE")
.parent()
.parent()
.within(() => {
cy.get('[title="حذف"]').click();
});
// Confirm deletion in modal
cy.contains("آیا از حذف این کد تخفیف اطمینان دارید؟").should("be.visible");
cy.contains("button", "حذف").click();
// Verify deletion
cy.contains("کد تخفیف با موفقیت حذف شد").should("be.visible");
cy.contains("TESTDELETE").should("not.exist");
});
it("should display proper status badges", () => {
// Check if status badges are displayed with correct colors
cy.get("table tbody tr").each(($row) => {
cy.wrap($row).within(() => {
cy.get("span")
.contains(/فعال|غیرفعال/)
.should("exist");
});
});
});
it("should show empty state when no discount codes exist", () => {
// This test assumes a clean state or uses a filter that returns no results
cy.get('input[placeholder="جستجو بر اساس کد..."]').type("NONEXISTENTCODE");
cy.wait(500);
cy.contains("هیچ کد تخفیفی یافت نشد").should("be.visible");
cy.contains("برای شروع یک کد تخفیف ایجاد کنید").should("be.visible");
});
it("should cancel discount code creation", () => {
cy.get('[title="کد تخفیف جدید"]').click();
// Fill some data
cy.get('input[name="code"]').type("CANCELTEST");
cy.get('input[name="name"]').type("Cancel Test");
// Click cancel
cy.contains("button", "انصراف").click();
// Should return to list page
cy.url().should("include", "/discount-codes");
cy.url().should("not.include", "/create");
});
it("should handle user restrictions properly", () => {
cy.get('[title="کد تخفیف جدید"]').click();
// Fill basic fields
cy.get('input[name="code"]').type("USERRESTRICT");
cy.get('input[name="name"]').type("User Restriction Test");
cy.get('select[name="type"]').select("percentage");
cy.get('input[name="value"]').type("15");
cy.get('select[name="status"]').select("active");
cy.get('select[name="application_level"]').select("invoice");
// Set user restrictions
cy.get('select[name="user_restrictions.user_group"]').select("new");
cy.get('input[name="user_restrictions.min_purchase_count"]').type("0");
cy.get('input[name="user_restrictions.max_purchase_count"]').type("5");
cy.get('input[name="user_restrictions.referrer_user_id"]').type("123");
// Check warning about mutually exclusive options
cy.get('input[name="user_restrictions.new_users_only"]').check();
cy.get('input[name="user_restrictions.loyal_users_only"]').check();
// Should show warning
cy.contains(
"new_users_only و loyal_users_only نمی‌توانند همزمان فعال باشند"
).should("be.visible");
cy.get('button[type="submit"]').click();
cy.url().should("include", "/discount-codes");
cy.contains("کد تخفیف با موفقیت ایجاد شد").should("be.visible");
});
it("should validate form sections are properly organized", () => {
cy.get('[title="کد تخفیف جدید"]').click();
// Verify all sections exist
cy.contains("اطلاعات اصلی کد تخفیف").should("be.visible");
cy.contains("تنظیمات تخفیف").should("be.visible");
cy.contains("بازه زمانی اعتبار").should("be.visible");
cy.contains("محدودیت‌های کاربری").should("be.visible");
cy.contains("اطلاعات تکمیلی").should("be.visible");
// Verify form has proper styling
cy.get(".bg-gradient-to-r").should("have.length.greaterThan", 3);
cy.get(".rounded-xl").should("have.length.greaterThan", 3);
});
it("should handle back navigation properly", () => {
cy.get('[title="کد تخفیف جدید"]').click();
// Click back button
cy.contains("بازگشت").click();
// Should return to list page
cy.url().should("include", "/discount-codes");
cy.url().should("not.include", "/create");
cy.contains("مدیریت کدهای تخفیف").should("be.visible");
});
});

View File

@ -16,6 +16,12 @@ describe("Smoke Tests", () => {
cy.visit("/products");
cy.url().should("include", "/products");
cy.visit("/discount-codes");
cy.url().should("include", "/discount-codes");
cy.visit("/orders");
cy.url().should("include", "/orders");
cy.visit("/admin-users");
cy.url().should("include", "/admin-users");

View File

@ -31,3 +31,6 @@ Cypress.Commands.add("waitForLoading", () => {
// Wait for any loading spinner to disappear
cy.get(".animate-spin", { timeout: 1000 }).should("not.exist");
});
// Import discount codes helpers
import "./discount-codes-helpers";

View File

@ -0,0 +1,310 @@
// Helper functions for discount codes E2E tests
export interface DiscountCodeData {
code: string;
name: string;
description?: string;
type: "percentage" | "fixed" | "fee_percentage";
value: string;
status: "active" | "inactive";
applicationLevel:
| "invoice"
| "category"
| "product"
| "shipping"
| "product_fee";
minPurchaseAmount?: string;
maxDiscountAmount?: string;
usageLimit?: string;
userUsageLimit?: string;
singleUse?: boolean;
validFrom?: string;
validTo?: string;
userGroup?: "new" | "loyal" | "all";
newUsersOnly?: boolean;
loyalUsersOnly?: boolean;
campaign?: string;
category?: string;
}
declare global {
namespace Cypress {
interface Chainable {
createDiscountCode(data: DiscountCodeData): Chainable<void>;
fillBasicDiscountInfo(data: Partial<DiscountCodeData>): Chainable<void>;
fillDiscountSettings(data: Partial<DiscountCodeData>): Chainable<void>;
fillUserRestrictions(data: Partial<DiscountCodeData>): Chainable<void>;
submitDiscountForm(): Chainable<void>;
verifyDiscountCreation(): Chainable<void>;
navigateToCreateDiscount(): Chainable<void>;
searchDiscountCode(code: string): Chainable<void>;
clearDiscountFilters(): Chainable<void>;
}
}
}
// Navigate to create discount page
Cypress.Commands.add("navigateToCreateDiscount", () => {
cy.visit("/discount-codes");
cy.waitForLoading();
cy.getByTestId("create-discount-button").click();
cy.url().should("include", "/discount-codes/create");
});
// Fill basic discount information
Cypress.Commands.add(
"fillBasicDiscountInfo",
(data: Partial<DiscountCodeData>) => {
if (data.code) {
cy.getByTestId("discount-code-input").clear().type(data.code);
}
if (data.name) {
cy.getByTestId("discount-name-input").clear().type(data.name);
}
if (data.description) {
cy.getByTestId("discount-description-textarea")
.clear()
.type(data.description);
}
}
);
// Fill discount settings
Cypress.Commands.add(
"fillDiscountSettings",
(data: Partial<DiscountCodeData>) => {
if (data.type) {
cy.getByTestId("discount-type-select").select(data.type);
}
if (data.value) {
cy.getByTestId("discount-value-input").clear().type(data.value);
}
if (data.status) {
cy.getByTestId("discount-status-select").select(data.status);
}
if (data.applicationLevel) {
cy.getByTestId("discount-application-level-select").select(
data.applicationLevel
);
}
if (data.minPurchaseAmount) {
cy.get('input[name="min_purchase_amount"]')
.clear()
.type(data.minPurchaseAmount);
}
if (data.maxDiscountAmount) {
cy.get('input[name="max_discount_amount"]')
.clear()
.type(data.maxDiscountAmount);
}
if (data.usageLimit) {
cy.get('input[name="usage_limit"]').clear().type(data.usageLimit);
}
if (data.userUsageLimit) {
cy.get('input[name="user_usage_limit"]')
.clear()
.type(data.userUsageLimit);
}
if (data.singleUse) {
cy.get('input[name="single_use"]').check();
}
if (data.validFrom) {
cy.get('input[name="valid_from"]').type(data.validFrom);
}
if (data.validTo) {
cy.get('input[name="valid_to"]').type(data.validTo);
}
}
);
// Fill user restrictions
Cypress.Commands.add(
"fillUserRestrictions",
(data: Partial<DiscountCodeData>) => {
if (data.userGroup) {
cy.get('select[name="user_restrictions.user_group"]').select(
data.userGroup
);
}
if (data.newUsersOnly) {
cy.get('input[name="user_restrictions.new_users_only"]').check();
}
if (data.loyalUsersOnly) {
cy.get('input[name="user_restrictions.loyal_users_only"]').check();
}
if (data.campaign) {
cy.get('input[name="meta.campaign"]').clear().type(data.campaign);
}
if (data.category) {
cy.get('input[name="meta.category"]').clear().type(data.category);
}
}
);
// Submit discount form
Cypress.Commands.add("submitDiscountForm", () => {
cy.getByTestId("submit-discount-button").click();
});
// Verify discount creation
Cypress.Commands.add("verifyDiscountCreation", () => {
cy.url().should("include", "/discount-codes");
cy.url().should("not.include", "/create");
cy.url().should("not.include", "/edit");
});
// Create complete discount code
Cypress.Commands.add("createDiscountCode", (data: DiscountCodeData) => {
cy.navigateToCreateDiscount();
cy.fillBasicDiscountInfo(data);
cy.fillDiscountSettings(data);
cy.fillUserRestrictions(data);
cy.submitDiscountForm();
cy.verifyDiscountCreation();
});
// Search for discount code
Cypress.Commands.add("searchDiscountCode", (code: string) => {
cy.get('input[placeholder*="جستجو"]').clear().type(code);
cy.wait(500); // Wait for search to filter
});
// Clear discount filters
Cypress.Commands.add("clearDiscountFilters", () => {
cy.contains("پاک کردن فیلترها").click();
cy.get('input[placeholder*="جستجو"]').should("have.value", "");
});
// Predefined discount code templates for testing
export const discountTemplates = {
basicPercentage: {
code: "BASIC20",
name: "Basic 20% Discount",
description: "Basic percentage discount for testing",
type: "percentage" as const,
value: "20",
status: "active" as const,
applicationLevel: "invoice" as const,
},
fixedAmount: {
code: "FIXED50K",
name: "Fixed 50K Discount",
description: "Fixed amount discount for testing",
type: "fixed" as const,
value: "50000",
status: "active" as const,
applicationLevel: "invoice" as const,
minPurchaseAmount: "100000",
},
feePercentage: {
code: "FEERED10",
name: "Fee Reduction 10%",
description: "Fee percentage reduction for testing",
type: "fee_percentage" as const,
value: "10",
status: "active" as const,
applicationLevel: "product_fee" as const,
},
loyalUsers: {
code: "LOYAL25",
name: "Loyal Users 25%",
description: "Discount for loyal users only",
type: "percentage" as const,
value: "25",
status: "active" as const,
applicationLevel: "invoice" as const,
userGroup: "loyal" as const,
loyalUsersOnly: true,
},
newUsers: {
code: "WELCOME15",
name: "Welcome New Users",
description: "Welcome discount for new users",
type: "percentage" as const,
value: "15",
status: "active" as const,
applicationLevel: "invoice" as const,
userGroup: "new" as const,
newUsersOnly: true,
singleUse: true,
},
timeBasedDiscount: {
code: "SUMMER24",
name: "Summer Sale 2024",
description: "Summer sale discount with time constraints",
type: "percentage" as const,
value: "30",
status: "active" as const,
applicationLevel: "invoice" as const,
validFrom: "2024-06-01T00:00",
validTo: "2024-08-31T23:59",
usageLimit: "1000",
userUsageLimit: "1",
campaign: "summer_sale_2024",
category: "seasonal",
},
};
// API response mocks
export const apiMocks = {
successfulCreation: (data: Partial<DiscountCodeData>) => ({
statusCode: 201,
body: {
id: Math.floor(Math.random() * 1000),
code: data.code,
name: data.name,
description: data.description,
type: data.type,
value: parseFloat(data.value || "0"),
status: data.status,
application_level: data.applicationLevel,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
},
}),
validationError: {
statusCode: 400,
body: {
message: "کد تخفیف تکراری است",
errors: {
code: ["این کد قبلاً استفاده شده است"],
},
},
},
serverError: {
statusCode: 500,
body: {
message: "خطای سرور",
},
},
discountsList: {
statusCode: 200,
body: {
discount_codes: [
{
id: 1,
code: "SAVE20",
name: "20% Off Discount",
description: "Get 20% off on your purchase",
type: "percentage",
value: 20,
status: "active",
application_level: "invoice",
created_at: "2024-01-01T00:00:00Z",
},
],
total: 1,
page: 1,
limit: 20,
total_pages: 1,
},
},
};

19
cypress/support/index.d.ts vendored Normal file
View File

@ -0,0 +1,19 @@
declare namespace Cypress {
interface Chainable {
login(username?: string, password?: string): Chainable<void>;
logout(): Chainable<void>;
getByTestId(testId: string): Chainable<JQuery<HTMLElement>>;
waitForLoading(): Chainable<void>;
// Discount codes helper methods
navigateToCreateDiscount(): Chainable<void>;
fillBasicDiscountInfo(data: any): Chainable<void>;
fillDiscountSettings(data: any): Chainable<void>;
fillUserRestrictions(data: any): Chainable<void>;
submitDiscountForm(): Chainable<void>;
verifyDiscountCreation(): Chainable<void>;
createDiscountCode(data: any): Chainable<void>;
searchDiscountCode(code: string): Chainable<void>;
clearDiscountFilters(): Chainable<void>;
}
}

Binary file not shown.

View File

@ -1,7 +1,7 @@
import { clsx } from 'clsx';
import { MouseEvent } from 'react';
import { MouseEvent, ButtonHTMLAttributes } from 'react';
interface ButtonProps {
interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'type' | 'onClick'> {
children: any;
variant?: 'primary' | 'secondary' | 'danger' | 'success';
size?: 'sm' | 'md' | 'lg';
@ -21,6 +21,7 @@ export const Button = ({
onClick,
type = 'button',
className = '',
...rest
}: ButtonProps) => {
const baseClasses = 'inline-flex items-center justify-center rounded-lg font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2';
@ -53,6 +54,7 @@ export const Button = ({
disabledClasses,
className
)}
{...rest}
>
{loading && (
<svg

View File

@ -64,7 +64,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
<p className="text-xs text-gray-500 dark:text-gray-400">{helperText}</p>
)}
{error && (
<p className="text-xs text-red-600 dark:text-red-400">{error}</p>
<p className="text-xs text-red-600 dark:text-red-400" role="alert">{error}</p>
)}
</div>
);

View File

@ -127,6 +127,9 @@ const DiscountCodeFormPage = () => {
error={errors.code?.message}
{...register('code')}
className="font-mono"
data-testid="discount-code-input"
aria-required="true"
required
/>
<Input
label="نام نمایشی"
@ -134,6 +137,9 @@ const DiscountCodeFormPage = () => {
placeholder="مثال: تخفیف ۲۰ درصدی"
error={errors.name?.message}
{...register('name')}
data-testid="discount-name-input"
aria-required="true"
required
/>
<div className="lg:col-span-2">
<Label htmlFor="description">توضیحات</Label>
@ -142,6 +148,7 @@ const DiscountCodeFormPage = () => {
placeholder="توضیحات کامل درباره این کد تخفیف..."
className={`w-full px-4 py-3 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none h-24 transition-colors ${errors.description ? 'border-red-500 focus:ring-red-500 focus:border-red-500' : 'border-gray-300 dark:border-gray-600'} dark:bg-gray-700 dark:text-gray-100`}
{...register('description' as const)}
data-testid="discount-description-textarea"
/>
{errors.description && <p className="text-sm text-red-600 dark:text-red-400 mt-1">{errors.description.message as string}</p>}
</div>
@ -163,7 +170,11 @@ const DiscountCodeFormPage = () => {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="space-y-2">
<Label>نوع تخفیف</Label>
<select className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100 transition-colors" {...register('type')}>
<select
className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100 transition-colors"
{...register('type')}
data-testid="discount-type-select"
>
<option value="percentage">درصدی</option>
<option value="fixed">مبلغ ثابت</option>
<option value="fee_percentage">درصد کارمزد</option>
@ -177,25 +188,38 @@ const DiscountCodeFormPage = () => {
placeholder="20"
error={errors.value?.message as string}
{...register('value')}
data-testid="discount-value-input"
/>
<div className="space-y-2">
<Label>وضعیت</Label>
<select className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100 transition-colors" {...register('status')}>
<select
className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100 transition-colors"
{...register('status')}
data-testid="discount-status-select"
required
aria-required="true"
>
<option value="active">فعال</option>
<option value="inactive">غیرفعال</option>
</select>
{errors.status && <p className="text-sm text-red-600 dark:text-red-400">{errors.status.message as string}</p>}
{errors.status && <p className="text-sm text-red-600 dark:text-red-400" role="alert">{errors.status.message as string}</p>}
</div>
<div className="space-y-2">
<Label>سطح اعمال</Label>
<select className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100 transition-colors" {...register('application_level')}>
<select
className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-gray-100 transition-colors"
{...register('application_level')}
data-testid="discount-application-level-select"
required
aria-required="true"
>
<option value="invoice">کل سبد خرید</option>
<option value="category">دستهبندی خاص</option>
<option value="product">محصول خاص</option>
<option value="shipping">هزینه ارسال</option>
<option value="product_fee">کارمزد محصول</option>
</select>
{errors.application_level && <p className="text-sm text-red-600 dark:text-red-400">{errors.application_level.message as string}</p>}
{errors.application_level && <p className="text-sm text-red-600 dark:text-red-400" role="alert">{errors.application_level.message as string}</p>}
</div>
<Input
label="حداقل مبلغ خرید"
@ -352,6 +376,7 @@ const DiscountCodeFormPage = () => {
variant="secondary"
onClick={() => navigate('/discount-codes')}
className="sm:order-1"
data-testid="cancel-discount-button"
>
انصراف
</Button>
@ -361,6 +386,7 @@ const DiscountCodeFormPage = () => {
loading={isLoading}
disabled={!isValid}
className="sm:order-2 bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800"
data-testid="submit-discount-button"
>
{isEdit ? 'به‌روزرسانی کد تخفیف' : 'ایجاد کد تخفیف'}
</Button>

View File

@ -79,6 +79,7 @@ const DiscountCodesListPage = () => {
onClick={handleCreate}
className="flex items-center justify-center w-12 h-12 bg-primary-600 hover:bg-primary-700 rounded-full transition-colors duration-200 text-white shadow-lg hover:shadow-xl"
title="کد تخفیف جدید"
data-testid="create-discount-button"
>
<Plus className="h-5 w-5" />
</button>

View File

@ -0,0 +1,75 @@
// User Admin models and types
export interface User {
id: number;
phone_number: string;
first_name: string;
last_name: string;
email?: string;
national_code?: string;
verified: boolean;
hashed_password?: string;
avatar?: string;
created_at?: string;
updated_at?: string;
}
export interface PaginatedUsersResponse {
users: User[];
total: number;
limit: number;
offset: number;
filters?: UserFilters;
}
export interface UserFilters {
verified?: boolean;
search_text?: string;
phone_number?: string;
email?: string;
national_code?: string;
limit?: number;
offset?: number;
}
export interface CreateUserRequest {
phone_number: string;
first_name: string;
last_name: string;
email?: string;
national_code?: string;
verified?: boolean;
password?: string;
}
export interface UpdateUserRequest {
first_name: string;
last_name: string;
email?: string;
national_code?: string;
verified: boolean;
}
export interface UpdateUserProfileRequest {
first_name: string;
last_name: string;
email?: string;
national_code?: string;
}
export interface UpdateUserAvatarRequest {
avatar_url: string;
}
export interface UserStats {
total_users: number;
verified_users: number;
unverified_users: number;
recent_registrations: number;
}
export type UserStatus = 'verified' | 'unverified' | 'all';
export interface UserActionResponse {
message: string;
}