diff --git a/plugins/woocommerce-blocks/tests/e2e/specs/shopper/checkout-single-use-coupon.test.js b/plugins/woocommerce-blocks/tests/e2e/specs/shopper/checkout-single-use-coupon.test.js new file mode 100644 index 00000000000..a1f5bd79776 --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/specs/shopper/checkout-single-use-coupon.test.js @@ -0,0 +1,71 @@ +/** + * External dependencies + */ +import { withRestApi } from '@woocommerce/e2e-utils'; + +/** + * Internal dependencies + */ +import { shopper } from '../../../utils'; +import { createCoupon } from '../../utils'; +import { SIMPLE_PRODUCT_NAME } from '../../../utils/constants'; + +if ( process.env.WOOCOMMERCE_BLOCKS_PHASE < 2 ) + // eslint-disable-next-line jest/no-focused-tests + test.only( `Skipping checkout tests`, () => {} ); + +let coupon; + +beforeAll( async () => { + coupon = await createCoupon( { usageLimit: 1 } ); + await shopper.block.emptyCart(); +} ); + +afterAll( async () => { + await withRestApi.deleteCoupon( coupon.id ); + await shopper.block.emptyCart(); +} ); + +describe( 'Shopper → Checkout → Can apply single-use coupon once', () => { + it( 'allows checkout to apply single-use coupon once', async () => { + await shopper.goToShop(); + await shopper.addToCartFromShopPage( SIMPLE_PRODUCT_NAME ); + await shopper.block.goToCheckout(); + await shopper.block.applyCouponFromCheckout( coupon.code ); + + const discountBlockSelector = '.wc-block-components-totals-discount'; + const discountAppliedCouponCodeSelector = + '.wc-block-components-totals-discount__coupon-list-item span.wc-block-components-chip__text'; + const discountValueSelector = + '.wc-block-components-totals-discount .wc-block-components-totals-item__value'; + + // Verify that the discount had been applied correctly on the checkout page. + await page.waitForSelector( discountBlockSelector ); + await expect( page ).toMatchElement( discountValueSelector, { + text: coupon.amount, + } ); + await expect( page ).toMatchElement( + discountAppliedCouponCodeSelector, + { + text: coupon.code, + } + ); + + await shopper.block.placeOrder(); + await expect( page ).toMatch( 'Your order has been received.' ); + + // Verify that the discount had been applied correctly on the order confirmation page. + await expect( page ).toMatchElement( `th`, { text: 'Discount' } ); + await expect( page ).toMatchElement( `span.woocommerce-Price-amount`, { + text: coupon.amount, + } ); + } ); + + it( 'Prevents checkout applying single-use coupon twice', async () => { + await shopper.goToShop(); + await shopper.addToCartFromShopPage( SIMPLE_PRODUCT_NAME ); + await shopper.block.goToCheckout(); + await shopper.block.applyCouponFromCheckout( coupon.code ); + await expect( page ).toMatch( 'Coupon usage limit has been reached.' ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/utils.js b/plugins/woocommerce-blocks/tests/e2e/utils.js index 699f804af47..63852c3bdb5 100644 --- a/plugins/woocommerce-blocks/tests/e2e/utils.js +++ b/plugins/woocommerce-blocks/tests/e2e/utils.js @@ -1,6 +1,7 @@ /** * External dependencies */ +import { Coupon, HTTPClientFactory } from '@woocommerce/api'; import config from 'config'; import { activateTheme, @@ -29,6 +30,11 @@ import { elementExists, getElementData, getTextContent } from './page-utils'; */ export const BASE_URL = config.get( 'url' ); +export const adminUsername = config.get( 'users.admin.username' ); +export const adminPassword = config.get( 'users.admin.password' ); +export const client = HTTPClientFactory.build( BASE_URL ) + .withBasicAuth( adminUsername, adminPassword ) + .create(); export const GUTENBERG_EDITOR_CONTEXT = process.env.GUTENBERG_EDITOR_CONTEXT || 'core'; export const DEFAULT_TIMEOUT = 30000; @@ -352,3 +358,46 @@ export const addBlockToFSEArea = async ( blockName ) => { ); await insertButton.click(); }; + +/** + * Creates a basic coupon with the provided coupon amount. Returns the coupon code. + * + * @param {Object} [coupon] Coupon object. Default to fixed cart type and amount = 5. + * @param {string} [coupon.amount] Amount to be applied. Defaults to 5. + * @param {string} [coupon.discountType] Type of a coupon. Defaults to Fixed cart discount. + * @param {number} [coupon.usageLimit] How many times the coupon can be used in total. Defaults to -1. + */ +export const createCoupon = async ( coupon ) => { + const { + amount = '5', + discountType = 'Fixed cart discount', + usageLimit = -1, + } = coupon || { amount: '5', discountType: 'Fixed cart discount' }; + + let couponType; + switch ( discountType ) { + case 'Fixed cart discount': + couponType = 'fixed_cart'; + break; + case 'Fixed product discount': + couponType = 'fixed_product'; + break; + case 'Percentage discount': + couponType = 'percent'; + break; + default: + couponType = discountType; + } + + // Fill in coupon code + const couponCode = 'code-' + couponType + new Date().getTime().toString(); + const repository = Coupon.restRepository( client ); + const createdCoupon = await repository.create( { + code: couponCode, + discountType: couponType, + amount, + usageLimit, + } ); + + return createdCoupon; +}; diff --git a/plugins/woocommerce-blocks/tests/utils/shopper.js b/plugins/woocommerce-blocks/tests/utils/shopper.js index d797d84e3b9..b3bf188667b 100644 --- a/plugins/woocommerce-blocks/tests/utils/shopper.js +++ b/plugins/woocommerce-blocks/tests/utils/shopper.js @@ -390,6 +390,20 @@ export const shopper = { await expect( page.$x( cartItemXPath ) ).resolves.toHaveLength( 1 ); }, + + applyCouponFromCheckout: async ( couponCode ) => { + const couponInputSelector = + '#wc-block-components-totals-coupon__input-0'; + const couponApplyButtonSelector = + '.wc-block-components-totals-coupon__button'; + const couponExpandButtonSelector = + '.wc-block-components-totals-coupon button'; + + await expect( page ).toClick( couponExpandButtonSelector ); + await expect( page ).toFill( couponInputSelector, couponCode ); + await expect( page ).toClick( couponApplyButtonSelector ); + await page.waitForNetworkIdle(); + }, }, isLoggedIn: async () => {