diff --git a/cypress/downloads/downloads.html b/cypress/downloads/downloads.html new file mode 100644 index 0000000..a73e074 Binary files /dev/null and b/cypress/downloads/downloads.html differ diff --git a/cypress/e2e/discount-codes-advanced.cy.ts b/cypress/e2e/discount-codes-advanced.cy.ts new file mode 100644 index 0000000..47208d9 --- /dev/null +++ b/cypress/e2e/discount-codes-advanced.cy.ts @@ -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 + }); + }); +}); diff --git a/cypress/e2e/discount-codes-complete.cy.ts b/cypress/e2e/discount-codes-complete.cy.ts new file mode 100644 index 0000000..d052930 --- /dev/null +++ b/cypress/e2e/discount-codes-complete.cy.ts @@ -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"); + }); + }); +}); diff --git a/cypress/e2e/discount-codes-fixed.cy.ts b/cypress/e2e/discount-codes-fixed.cy.ts new file mode 100644 index 0000000..fb27880 --- /dev/null +++ b/cypress/e2e/discount-codes-fixed.cy.ts @@ -0,0 +1,251 @@ +/// + +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); + }); +}); diff --git a/cypress/e2e/discount-codes.cy.ts b/cypress/e2e/discount-codes.cy.ts new file mode 100644 index 0000000..b278eff --- /dev/null +++ b/cypress/e2e/discount-codes.cy.ts @@ -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"); + }); +}); diff --git a/cypress/e2e/smoke.cy.ts b/cypress/e2e/smoke.cy.ts index 168f199..71c84ba 100644 --- a/cypress/e2e/smoke.cy.ts +++ b/cypress/e2e/smoke.cy.ts @@ -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"); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index cbe6eb1..907b8a4 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -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"; diff --git a/cypress/support/discount-codes-helpers.ts b/cypress/support/discount-codes-helpers.ts new file mode 100644 index 0000000..df92b40 --- /dev/null +++ b/cypress/support/discount-codes-helpers.ts @@ -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; + fillBasicDiscountInfo(data: Partial): Chainable; + fillDiscountSettings(data: Partial): Chainable; + fillUserRestrictions(data: Partial): Chainable; + submitDiscountForm(): Chainable; + verifyDiscountCreation(): Chainable; + navigateToCreateDiscount(): Chainable; + searchDiscountCode(code: string): Chainable; + clearDiscountFilters(): Chainable; + } + } +} + +// 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) => { + 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) => { + 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) => { + 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) => ({ + 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, + }, + }, +}; diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts new file mode 100644 index 0000000..3a508ba --- /dev/null +++ b/cypress/support/index.d.ts @@ -0,0 +1,19 @@ +declare namespace Cypress { + interface Chainable { + login(username?: string, password?: string): Chainable; + logout(): Chainable; + getByTestId(testId: string): Chainable>; + waitForLoading(): Chainable; + + // Discount codes helper methods + navigateToCreateDiscount(): Chainable; + fillBasicDiscountInfo(data: any): Chainable; + fillDiscountSettings(data: any): Chainable; + fillUserRestrictions(data: any): Chainable; + submitDiscountForm(): Chainable; + verifyDiscountCreation(): Chainable; + createDiscountCode(data: any): Chainable; + searchDiscountCode(code: string): Chainable; + clearDiscountFilters(): Chainable; + } +} diff --git a/cypress/videos/discount-codes-fixed.cy.ts.mp4 b/cypress/videos/discount-codes-fixed.cy.ts.mp4 new file mode 100644 index 0000000..39f1d65 Binary files /dev/null and b/cypress/videos/discount-codes-fixed.cy.ts.mp4 differ diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index 9d0da0c..64b72b9 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx'; -import { MouseEvent } from 'react'; +import { MouseEvent, ButtonHTMLAttributes } from 'react'; -interface ButtonProps { +interface ButtonProps extends Omit, '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 && ( (

{helperText}

)} {error && ( -

{error}

+

{error}

)} ); diff --git a/src/pages/discount-codes/discount-code-form/DiscountCodeFormPage.tsx b/src/pages/discount-codes/discount-code-form/DiscountCodeFormPage.tsx index b458753..8479543 100644 --- a/src/pages/discount-codes/discount-code-form/DiscountCodeFormPage.tsx +++ b/src/pages/discount-codes/discount-code-form/DiscountCodeFormPage.tsx @@ -29,19 +29,19 @@ const schema = yup.object({ }); const formatDateTimeLocal = (dateString?: string): string => { - if (!dateString) return ''; - try { - const date = new Date(dateString); - if (isNaN(date.getTime())) return ''; - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - return `${year}-${month}-${day}T${hours}:${minutes}`; - } catch { - return ''; - } + if (!dateString) return ''; + try { + const date = new Date(dateString); + if (isNaN(date.getTime())) return ''; + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + return `${year}-${month}-${day}T${hours}:${minutes}`; + } catch { + return ''; + } }; const DiscountCodeFormPage = () => { @@ -120,28 +120,35 @@ const DiscountCodeFormPage = () => {
- -
-