From 1141984c2d50e4e652ff970ac1518fbf827456d9 Mon Sep 17 00:00:00 2001 From: Ramon Date: Sun, 20 Mar 2022 09:34:50 -0300 Subject: [PATCH 01/11] Ensure $all_meta is an array on wc_get_product_variation_attributes --- plugins/woocommerce/includes/wc-product-functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/wc-product-functions.php b/plugins/woocommerce/includes/wc-product-functions.php index 7a4811d9816..d3c715a09c9 100644 --- a/plugins/woocommerce/includes/wc-product-functions.php +++ b/plugins/woocommerce/includes/wc-product-functions.php @@ -686,7 +686,7 @@ function wc_get_product_id_by_sku( $sku ) { */ function wc_get_product_variation_attributes( $variation_id ) { // Build variation data from meta. - $all_meta = get_post_meta( $variation_id ); + $all_meta = array_filter( (array) get_post_meta( $variation_id ) ); $parent_id = wp_get_post_parent_id( $variation_id ); $parent_attributes = array_filter( (array) get_post_meta( $parent_id, '_product_attributes', true ) ); $found_parent_attributes = array(); From e6d412093a6716ca67e844974af70a645d9d6758 Mon Sep 17 00:00:00 2001 From: Ramon Date: Sun, 3 Apr 2022 19:46:12 -0300 Subject: [PATCH 02/11] Update to check if the return of get_post_meta is an array --- plugins/woocommerce/includes/wc-product-functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/includes/wc-product-functions.php b/plugins/woocommerce/includes/wc-product-functions.php index d3c715a09c9..bced2009235 100644 --- a/plugins/woocommerce/includes/wc-product-functions.php +++ b/plugins/woocommerce/includes/wc-product-functions.php @@ -686,7 +686,7 @@ function wc_get_product_id_by_sku( $sku ) { */ function wc_get_product_variation_attributes( $variation_id ) { // Build variation data from meta. - $all_meta = array_filter( (array) get_post_meta( $variation_id ) ); + $all_meta = is_array( get_post_meta( $variation_id ) ) ? get_post_meta( $variation_id ) : array(); $parent_id = wp_get_post_parent_id( $variation_id ); $parent_attributes = array_filter( (array) get_post_meta( $parent_id, '_product_attributes', true ) ); $found_parent_attributes = array(); From bbd0abf849323a6863bc3d667de89d307d8ec86d Mon Sep 17 00:00:00 2001 From: Ramon Date: Sat, 30 Sep 2023 11:17:13 -0300 Subject: [PATCH 03/11] add changelog --- plugins/woocommerce/changelog/pr-32288 | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugins/woocommerce/changelog/pr-32288 diff --git a/plugins/woocommerce/changelog/pr-32288 b/plugins/woocommerce/changelog/pr-32288 new file mode 100644 index 00000000000..7665551be4a --- /dev/null +++ b/plugins/woocommerce/changelog/pr-32288 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fixed warning on wc_get_product_variation_attributes when product does not exist From e695f42e852046c1125f17d4af4787c9cc906a4a Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Thu, 2 Nov 2023 15:42:26 -0300 Subject: [PATCH 04/11] Add stalebot schedules to allow processing of all issues --- .github/workflows/scripts/stalebot.js | 25 +++++++++++++++++++ .github/workflows/stalebot.yml | 21 +++++++++++++--- .../changelog/add-stalebot-schedules | 4 +++ 3 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/scripts/stalebot.js create mode 100644 plugins/woocommerce/changelog/add-stalebot-schedules diff --git a/.github/workflows/scripts/stalebot.js b/.github/workflows/scripts/stalebot.js new file mode 100644 index 00000000000..af3c5e26c37 --- /dev/null +++ b/.github/workflows/scripts/stalebot.js @@ -0,0 +1,25 @@ +/** + * Set the stalebot start date given a cron schedule. + */ + +// You need to install this dependency as part of your workflow. +const core = require( '@actions/core' ); + +const ScheduleStartDate = () => { + let scheduleStartDate; + + switch ( process.env.CRON_SCHEDULE ) { + case '21 1 * * *': + scheduleStartDate = '2022-01-01'; + case '31 2 * * *': + scheduleStartDate = '2023-01-01'; + case '41 3 * * *': + scheduleStartDate = '2023-08-01'; + default: + scheduleStartDate = '2018-01-01'; + } + + core.setOutput( 'stale-start-date', scheduleStartDate ); +}; + +ScheduleStartDate(); diff --git a/.github/workflows/stalebot.yml b/.github/workflows/stalebot.yml index c3e9f03bee9..719213623f1 100644 --- a/.github/workflows/stalebot.yml +++ b/.github/workflows/stalebot.yml @@ -1,7 +1,10 @@ -name: 'Close stale needs-feedback issues' +name: 'Process stale needs-feedback issues' on: schedule: - - cron: '21 0 * * *' + - cron: '11 0 * * *' + - cron: '21 1 * * *' + - cron: '31 2 * * *' + - cron: '41 3 * * *' permissions: {} @@ -13,10 +16,20 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v8 + - name: Install Actions Core + run: npm --prefix .github/workflows/scripts install @actions/core + + - name: Get start date + id: startdate + run: node .github/workflows/scripts/stalebot.js + env: + CRON_SCHEDULE: ${{ github.event.schedule }} + - name: Scan issues + uses: actions/stale@v8 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - operations-per-run: 40 + operations-per-run: 8 + start-date: steps.startdate.outputs.stale-start-date stale-issue-message: "As a part of this repository's maintenance, this issue is being marked as stale due to inactivity. Please feel free to comment on it in case we missed something.\n\n###### After 7 days with no activity this issue will be automatically be closed." close-issue-message: 'This issue was closed because it has been 14 days with no activity.' days-before-issue-stale: 7 diff --git a/plugins/woocommerce/changelog/add-stalebot-schedules b/plugins/woocommerce/changelog/add-stalebot-schedules new file mode 100644 index 00000000000..49268dd5210 --- /dev/null +++ b/plugins/woocommerce/changelog/add-stalebot-schedules @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Add stalebot schedules to allow processing of all issues From c738977c21e938fa24ee5a62cfdd7893f2c25e5d Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Thu, 2 Nov 2023 15:57:13 -0300 Subject: [PATCH 05/11] include the switch breaks --- .github/workflows/scripts/stalebot.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/scripts/stalebot.js b/.github/workflows/scripts/stalebot.js index af3c5e26c37..83b28292d49 100644 --- a/.github/workflows/scripts/stalebot.js +++ b/.github/workflows/scripts/stalebot.js @@ -11,12 +11,16 @@ const ScheduleStartDate = () => { switch ( process.env.CRON_SCHEDULE ) { case '21 1 * * *': scheduleStartDate = '2022-01-01'; + break; case '31 2 * * *': scheduleStartDate = '2023-01-01'; + break; case '41 3 * * *': scheduleStartDate = '2023-08-01'; + break; default: scheduleStartDate = '2018-01-01'; + break; } core.setOutput( 'stale-start-date', scheduleStartDate ); From 15d46e478ca4bde6850b1115f290fdc6f3a8cf30 Mon Sep 17 00:00:00 2001 From: Matt Sherman Date: Mon, 30 Oct 2023 10:52:00 -0400 Subject: [PATCH 06/11] Add PostTypeContext --- .../js/product-editor/src/components/editor/editor.tsx | 9 +++++++-- .../src/contexts/post-type-context/index.ts | 6 ++++++ packages/js/product-editor/src/index.ts | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 packages/js/product-editor/src/contexts/post-type-context/index.ts diff --git a/packages/js/product-editor/src/components/editor/editor.tsx b/packages/js/product-editor/src/components/editor/editor.tsx index 042b0afb4ae..ecb1bc121e0 100644 --- a/packages/js/product-editor/src/components/editor/editor.tsx +++ b/packages/js/product-editor/src/components/editor/editor.tsx @@ -37,6 +37,7 @@ import { FullscreenMode, InterfaceSkeleton } from '@wordpress/interface'; */ import { Header } from '../header'; import { BlockEditor } from '../block-editor'; +import { PostTypeContext } from '../../contexts/post-type-context'; import { ValidationProvider } from '../../contexts/validation-context'; export type ProductEditorSettings = Partial< @@ -90,8 +91,12 @@ export function Editor( { postId: product.id, } } /> - { /* @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated. */ } - + + { /* @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated. */ } + + } /> diff --git a/packages/js/product-editor/src/contexts/post-type-context/index.ts b/packages/js/product-editor/src/contexts/post-type-context/index.ts new file mode 100644 index 00000000000..968e2962631 --- /dev/null +++ b/packages/js/product-editor/src/contexts/post-type-context/index.ts @@ -0,0 +1,6 @@ +/** + * External dependencies + */ +import { createContext } from '@wordpress/element'; + +export const PostTypeContext = createContext( 'product' ); diff --git a/packages/js/product-editor/src/index.ts b/packages/js/product-editor/src/index.ts index 878a63d638a..ad5ae2a28fb 100644 --- a/packages/js/product-editor/src/index.ts +++ b/packages/js/product-editor/src/index.ts @@ -20,5 +20,6 @@ export * from './utils'; * Hooks */ export * from './hooks'; +export { PostTypeContext } from './contexts/post-type-context'; export { useValidation, useValidations } from './contexts/validation-context'; export * from './contexts/validation-context/types'; From fc2fa8598bf5ca8a06e9725241446e60b31ef135 Mon Sep 17 00:00:00 2001 From: Matt Sherman Date: Mon, 30 Oct 2023 16:43:08 -0400 Subject: [PATCH 07/11] Changelog --- .../changelog/update-add-post-type-context-product-editor | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 packages/js/product-editor/changelog/update-add-post-type-context-product-editor diff --git a/packages/js/product-editor/changelog/update-add-post-type-context-product-editor b/packages/js/product-editor/changelog/update-add-post-type-context-product-editor new file mode 100644 index 00000000000..2ad018f462b --- /dev/null +++ b/packages/js/product-editor/changelog/update-add-post-type-context-product-editor @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Add PostTypeContext so that plugins can get post type. From afbe61a4f7da62e44e014d35a84d2d3ee5f7149d Mon Sep 17 00:00:00 2001 From: Matt Sherman Date: Wed, 1 Nov 2023 08:54:46 -0400 Subject: [PATCH 08/11] Move PluginArea into BlockEditor so plugins can get block info --- .../src/components/block-editor/block-editor.tsx | 7 +++++++ .../js/product-editor/src/components/editor/editor.tsx | 8 -------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/js/product-editor/src/components/block-editor/block-editor.tsx b/packages/js/product-editor/src/components/block-editor/block-editor.tsx index f58e44659e1..526c6507cbb 100644 --- a/packages/js/product-editor/src/components/block-editor/block-editor.tsx +++ b/packages/js/product-editor/src/components/block-editor/block-editor.tsx @@ -5,6 +5,7 @@ import { synchronizeBlocksWithTemplate, Template } from '@wordpress/blocks'; import { createElement, useMemo, useLayoutEffect } from '@wordpress/element'; import { useDispatch, useSelect, select as WPSelect } from '@wordpress/data'; import { uploadMedia } from '@wordpress/media-utils'; +import { PluginArea } from '@wordpress/plugins'; import { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore No types for this exist yet. @@ -32,6 +33,7 @@ import { */ import { useConfirmUnsavedProductChanges } from '../../hooks/use-confirm-unsaved-product-changes'; import { ProductEditorContext } from '../../types'; +import { PostTypeContext } from '../../contexts/post-type-context'; type BlockEditorProps = { context: Partial< ProductEditorContext >; @@ -120,6 +122,11 @@ export function BlockEditor( { + { /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ } + + { /* @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated. */ } + + diff --git a/packages/js/product-editor/src/components/editor/editor.tsx b/packages/js/product-editor/src/components/editor/editor.tsx index ecb1bc121e0..e5b3f177871 100644 --- a/packages/js/product-editor/src/components/editor/editor.tsx +++ b/packages/js/product-editor/src/components/editor/editor.tsx @@ -7,7 +7,6 @@ import { Fragment, useState, } from '@wordpress/element'; -import { PluginArea } from '@wordpress/plugins'; import { LayoutContextProvider, useExtendLayout, @@ -37,7 +36,6 @@ import { FullscreenMode, InterfaceSkeleton } from '@wordpress/interface'; */ import { Header } from '../header'; import { BlockEditor } from '../block-editor'; -import { PostTypeContext } from '../../contexts/post-type-context'; import { ValidationProvider } from '../../contexts/validation-context'; export type ProductEditorSettings = Partial< @@ -91,12 +89,6 @@ export function Editor( { postId: product.id, } } /> - - { /* @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated. */ } - - } /> From 18faf14db7ab4d858831eb238298f0a0c45c732d Mon Sep 17 00:00:00 2001 From: Matt Sherman Date: Wed, 1 Nov 2023 19:51:40 -0400 Subject: [PATCH 09/11] Update changelog --- .../changelog/update-add-post-type-context-product-editor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/product-editor/changelog/update-add-post-type-context-product-editor b/packages/js/product-editor/changelog/update-add-post-type-context-product-editor index 2ad018f462b..a0ef51e308b 100644 --- a/packages/js/product-editor/changelog/update-add-post-type-context-product-editor +++ b/packages/js/product-editor/changelog/update-add-post-type-context-product-editor @@ -1,4 +1,4 @@ Significance: minor Type: update -Add PostTypeContext so that plugins can get post type. +Allow plugins to access PostTypeContext and blocks (through core/block-editor data store). From 6ff4b13b632109db77cd30000d4b4f9a3c319c98 Mon Sep 17 00:00:00 2001 From: Jonathan Lane Date: Fri, 3 Nov 2023 09:14:27 -0700 Subject: [PATCH 10/11] Add tests for tax display for the shopper (#40178) * Add tests for tax display for the shopper * Add changelog * Renamed .spec file * Fixes for re-testability * Fix a locator problem --------- Co-authored-by: Jon Lane --- .../e2e-display-correct-tax-checkout | 4 + .../shopper/checkout-calculate-tax.spec.js | 695 ++++++++++++++++++ 2 files changed, 699 insertions(+) create mode 100644 plugins/woocommerce/changelog/e2e-display-correct-tax-checkout create mode 100644 plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-calculate-tax.spec.js diff --git a/plugins/woocommerce/changelog/e2e-display-correct-tax-checkout b/plugins/woocommerce/changelog/e2e-display-correct-tax-checkout new file mode 100644 index 00000000000..59f51148725 --- /dev/null +++ b/plugins/woocommerce/changelog/e2e-display-correct-tax-checkout @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Adds e2e tests for tax display in store, cart and checkout diff --git a/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-calculate-tax.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-calculate-tax.spec.js new file mode 100644 index 00000000000..41132ac0dc9 --- /dev/null +++ b/plugins/woocommerce/tests/e2e-pw/tests/shopper/checkout-calculate-tax.spec.js @@ -0,0 +1,695 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; +const { admin, customer } = require( '../../test-data/data' ); + +const productName = 'Taxed products are awesome'; +const productPrice = '100.00'; +const messyProductPrice = '13.47'; +const secondProductName = 'Other products are also awesome'; + +let productId, productId2, nastyTaxId, seventeenTaxId, sixTaxId, countryTaxId, stateTaxId, cityTaxId, zipTaxId, shippingTaxId, shippingZoneId, shippingMethodId; + +test.describe( 'Shopper Tax Display Tests', () => { + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/general/woocommerce_calc_taxes', { + value: 'yes', + } ); + await api.post( 'products', { + name: productName, + type: 'simple', + regular_price: productPrice, + } ) + .then( ( response ) => { + productId = response.data.id; + } ); + await api.post( 'taxes', { + "country": "US", + "state": "*", + "cities": "*", + "postcodes": "*", + "rate": "25", + "name": "Nasty Tax", + "shipping": false + } ) + .then( ( response ) => { + nastyTaxId = response.data.id; + } ); + } ); + + test.beforeEach( async ( { page, context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + await context.clearCookies(); + + // all tests use the first product + await page.goto( `/shop/?add-to-cart=${ productId }`, { waitUntil: 'networkidle' } ); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_cart', { + value: 'excl', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_shop', { + value: 'excl' + } ); + await api.put( 'settings/tax/woocommerce_price_display_suffix', { + value: '', + } ); + await api.put( 'settings/general/woocommerce_calc_taxes', { + value: 'no', + } ); + await api.put( 'settings/tax/woocommerce_tax_total_display', { + value: 'itemized', + } ); + await api.delete( `products/${ productId }`, { + force: true, + } ); + await api.delete( `taxes/${ nastyTaxId }`, { + force: true, + } ); + } ); + + test( 'checks that taxes are calculated properly on totals, inclusive tax displayed properly', async ( { page, baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_cart', { + value: 'incl', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_shop', { + value: 'incl' + } ); + + await test.step( 'Load shop page and confirm price display', async() => { + await page.goto( '/shop/' ); + await expect( page.getByRole( 'heading', { name: 'Shop' } ) ).toBeVisible(); + await expect( page.getByRole('link', { name: 'Placeholder Taxed products are awesome $125.00' }).first() ).toBeVisible(); + } ); + + await test.step( 'Load cart page and confirm price display', async() => { + await page.goto( '/cart/' ); + await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible(); + await expect( page.getByRole( 'cell', { name: '$125.00 (incl. tax)' } ) ).toHaveCount(2); + await expect( page.getByRole( 'row', { name: 'Subtotal $125.00 (incl. tax)'} ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Total $125.00 (includes $25.00 Nasty Tax)' } ) ).toBeVisible(); + } ); + + await test.step( 'Load checkout page and confirm price display', async() => { + await page.goto( '/checkout/' ); + await expect( page.getByRole( 'heading', { name: 'Checkout' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Taxed products are awesome × 1 $125.00 (incl. tax)' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Subtotal $125.00 (incl. tax)' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Total $125.00 (includes $25.00 Nasty Tax)'} ) ).toBeVisible(); + } ); + } ); + + test( 'checks that taxes are calculated and displayed correctly exclusive on shop, cart and checkout', async ( { page, baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_cart', { + value: 'excl', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_shop', { + value: 'excl' + } ); + + await test.step( 'Load shop page and confirm price display', async() => { + await page.goto( '/shop/' ); + await expect( page.getByRole( 'heading', { name: 'Shop' } ) ).toBeVisible(); + await expect( page.getByRole('link', { name: 'Placeholder Taxed products are awesome $100.00' }).first() ).toBeVisible(); + } ); + + await test.step( 'Load cart page and confirm price display', async() => { + await page.goto( '/cart/' ); + await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible(); + await expect( page.getByRole( 'cell', { name: '$100.00' } ) ).toHaveCount(3); + await expect( page.getByRole( 'row', { name: 'Subtotal $100.00'} ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Tax $25.00' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Total $125.00' } ) ).toBeVisible(); + } ); + + await test.step( 'Load checkout page and confirm price display', async() => { + await page.goto( '/checkout/' ); + await expect( page.getByRole( 'heading', { name: 'Checkout' } ) ).toBeVisible(); + + await page.locator( '#billing_first_name' ).fill( customer.billing.us.first_name ); + await page.locator( '#billing_last_name' ).fill( customer.billing.us.last_name ); + await page.locator( '#billing_address_1' ).fill( customer.billing.us.address ); + await page.locator( '#billing_city' ).fill( customer.billing.us.city ); + await page.locator( '#billing_country' ).selectOption( customer.billing.us.country ); + await page.locator( '#billing_state' ).selectOption( customer.billing.us.state ); + await page.locator( '#billing_postcode' ).fill( customer.billing.us.zip ); + await page.locator( '#billing_phone' ).fill( customer.billing.us.phone ); + await page.locator( '#billing_email' ).fill( customer.billing.us.email ); + + await expect( page.getByRole( 'row', { name: 'Taxed products are awesome × 1 $100.00' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Subtotal $100.00' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Tax $25.00' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Total $125.00' } ) ).toBeVisible(); + } ); + } ); + + test( 'checks that display suffix is shown', async ( { page, baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_cart', { + value: 'excl', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_shop', { + value: 'excl', + } ); + await api.put( 'settings/tax/woocommerce_price_display_suffix', { + value: 'excluding VAT', + } ); + + await test.step( 'Load shop page and confirm price suffix display', async() => { + await page.goto( '/shop/' ); + await expect( page.getByRole( 'heading', { name: 'Shop' } ) ).toBeVisible(); + await expect( page.getByRole('link', { name: 'Placeholder Taxed products are awesome $100.00 excluding VAT' }).first() ).toBeVisible(); + } ); + } ); +} ); + +test.describe( 'Shopper Tax Rounding', () => { + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/general/woocommerce_calc_taxes', { + value: 'yes', + } ); + await api.post( 'products', { + name: productName, + type: 'simple', + regular_price: messyProductPrice, + } ) + .then( ( response ) => { + productId = response.data.id; + } ); + await api.post( 'products', { + name: secondProductName, + type: 'simple', + regular_price: messyProductPrice, + } ) + .then( ( response ) => { + productId2 = response.data.id; + } ); + await api.post( 'taxes', { + "country": "US", + "state": "*", + "cities": "*", + "postcodes": "*", + "rate": "17", + "name": "Seventeen Tax", + "shipping": false, + "compound": true, + "priority": 1 + } ) + .then( ( response ) => { + seventeenTaxId = response.data.id; + } ); + await api.post( 'taxes', { + "country": "US", + "state": "*", + "cities": "*", + "postcodes": "*", + "rate": "6", + "name": "Six Tax", + "shipping": false, + "compound": true, + "priority": 2 + } ) + .then( ( response ) => { + sixTaxId = response.data.id; + } ); + } ); + + test.beforeEach( async ( { page, context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + await context.clearCookies(); + + // all tests use the first product + await page.goto( `/shop/?add-to-cart=${ productId }`, { waitUntil: 'networkidle' } ); + await page.goto( `/shop/?add-to-cart=${ productId2 }`, { waitUntil: 'networkidle' } ); + await page.goto( `/shop/?add-to-cart=${ productId2 }`, { waitUntil: 'networkidle' } ); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_cart', { + value: 'excl', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_shop', { + value: 'excl' + } ); + await api.put( 'settings/tax/woocommerce_tax_round_at_subtotal', { + value: 'no', + } ); + await api.put( 'settings/general/woocommerce_calc_taxes', { + value: 'no', + } ); + await api.put( 'settings/tax/woocommerce_tax_total_display', { + value: 'itemized' + } ); + await api.delete( `products/${ productId }`, { + force: true, + } ); + await api.delete( `products/${ productId2 }`, { + force: true, + } ); + await api.delete( `taxes/${ seventeenTaxId }`, { + force: true, + } ); + await api.delete( `taxes/${ sixTaxId }`, { + force: true, + } ); + } ); + + test( 'checks rounding at subtotal level', async ( { page, baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_cart', { + value: 'excl', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_shop', { + value: 'excl', + } ); + await api.put( 'settings/tax/woocommerce_tax_round_at_subtotal', { + value: 'yes', + } ); + await api.put( 'settings/tax/woocommerce_tax_total_display', { + value: 'single', + } ); + + await test.step( 'Load shop page and confirm price display', async() => { + await page.goto( '/shop/' ); + await expect( page.getByRole( 'heading', { name: 'Shop' } ) ).toBeVisible(); + await expect( page.getByRole('link', { name: 'Placeholder Taxed products are awesome $13.47' }).first() ).toBeVisible(); + } ); + + await test.step( 'Load cart page and confirm price display', async() => { + await page.goto( '/cart/' ); + await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible(); + await expect( page.getByRole( 'cell', { name: '$13.47' } ) ).toHaveCount(3); + await expect( page.getByRole( 'row', { name: 'Subtotal $40.41'} ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Tax $9.71 ' } ) ).toBeVisible() + await expect( page.getByRole( 'row', { name: 'Total $50.12 ' } ) ).toBeVisible(); + } ); + } ); + + test( 'checks rounding off at subtotal level', async ( { page, baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_cart', { + value: 'excl', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_shop', { + value: 'excl', + } ); + await api.put( 'settings/tax/woocommerce_tax_round_at_subtotal', { + value: 'no', + } ); + await api.put( 'settings/tax/woocommerce_tax_total_display', { + value: 'itemized', + } ); + + await test.step( 'Load shop page and confirm price display', async() => { + await page.goto( '/shop/' ); + await expect( page.getByRole( 'heading', { name: 'Shop' } ) ).toBeVisible(); + await expect( page.getByRole('link', { name: 'Placeholder Taxed products are awesome $13.47' }).first() ).toBeVisible(); + } ); + + await test.step( 'Load cart page and confirm price display', async() => { + await page.goto( '/cart/' ); + await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible(); + await expect( page.getByRole( 'cell', { name: '$13.47' } ) ).toHaveCount(3); + await expect( page.getByRole( 'row', { name: 'Subtotal $40.41'} ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Seventeen Tax $6.87 ' } ) ).toBeVisible() + await expect( page.getByRole( 'row', { name: 'Six Tax $2.84 ' } ) ).toBeVisible() + await expect( page.getByRole( 'row', { name: 'Total $50.12 ' } ) ).toBeVisible(); + } ); + } ); +} ); + +test.describe( 'Shopper Tax Levels', () => { + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/general/woocommerce_calc_taxes', { + value: 'yes', + } ); + await api.put( 'settings/tax/woocommerce_tax_display_cart', { + value: 'excl', + } ); + await api.post( 'products', { + name: productName, + type: 'simple', + regular_price: productPrice, + } ) + .then( ( response ) => { + productId = response.data.id; + } ); + await api.post( 'taxes', { + "country": "US", + "state": "*", + "cities": "*", + "postcodes": "*", + "rate": "10", + "name": "Country Tax", + "shipping": false, + "priority": 1 + } ) + .then( ( response ) => { + countryTaxId = response.data.id; + } ); + await api.post( 'taxes', { + "country": "*", + "state": "CA", + "cities": "*", + "postcodes": "*", + "rate": "5", + "name": "State Tax", + "shipping": false, + "priority": 2 + } ) + .then( ( response ) => { + stateTaxId = response.data.id; + } ); + await api.post( 'taxes', { + "country": "*", + "state": "*", + "cities": "Sacramento", + "postcodes": "*", + "rate": "2.5", + "name": "City Tax", + "shipping": false, + "priority": 3 + } ) + .then( ( response ) => { + cityTaxId = response.data.id; + } ); + await api.post( 'taxes', { + "country": "*", + "state": "*", + "cities": "*", + "postcodes": "55555", + "rate": "1.25", + "name": "Zip Tax", + "shipping": false, + "priority": 4 + } ) + .then( ( response ) => { + zipTaxId = response.data.id; + } ); + } ); + + test.beforeEach( async ( { page, context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + await context.clearCookies(); + + // all tests use the first product + await page.goto( `/shop/?add-to-cart=${ productId }`, { waitUntil: 'networkidle' } ); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/tax/woocommerce_tax_total_display', { + value: 'itemized' + } ); + await api.put( 'settings/tax/woocommerce_tax_display_cart', { + value: 'excl', + } ); + await api.delete( `products/${ productId }`, { + force: true, + } ); + + await api.delete( `taxes/${ countryTaxId }`, { + force: true, + } ); + await api.delete( `taxes/${ stateTaxId }`, { + force: true, + } ); + await api.delete( `taxes/${ cityTaxId }`, { + force: true, + } ); + await api.delete( `taxes/${ zipTaxId }`, { + force: true, + } ); + } ); + + test( 'checks applying taxes of 4 different levels', async ( { page, baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/tax/woocommerce_tax_total_display', { + value: 'itemized', + } ); + + await test.step( 'Load cart page and confirm price display', async() => { + await page.goto( '/cart/' ); + await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible(); + await expect( page.getByRole( 'cell', { name: '$100.00' } ) ).toHaveCount(3); + await expect( page.getByRole( 'row', { name: 'Subtotal $100.00'} ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Country Tax $10.00 ' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'State Tax $5.00 ' } ) ).toBeVisible() + await expect( page.getByRole( 'row', { name: 'Total $115.00 ' } ) ).toBeVisible(); + } ); + + await test.step( 'Load checkout page and confirm taxes displayed', async() => { + await page.goto( '/checkout/' ); + await expect( page.getByRole( 'heading', { name: 'Checkout', exact: true } ) ).toBeVisible(); + + await page.getByLabel('First name *').first().fill( customer.billing.us.first_name ); + await page.getByLabel('Last name *').first().fill( customer.billing.us.last_name ); + await page.getByPlaceholder('House number and street name').first().fill( customer.billing.us.address ); + await page.getByLabel('Town / City *').first().pressSequentially( 'Sacramento' ); + await page.getByLabel('ZIP Code *').first().pressSequentially( '55555' ); + await page.getByLabel('Phone *').first().fill( customer.billing.us.phone ); + await page.getByLabel('Email address *').first().fill( customer.billing.us.email ); + + await expect( page.getByRole( 'row', { name: 'Subtotal $100.00'} ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Country Tax $10.00' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'State Tax $5.00' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'City Tax $2.50' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Zip Tax $1.25' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Total $118.75 ' } ) ).toBeVisible(); + } ); + } ); + + test( 'checks applying taxes of 2 different levels (2 excluded)', async ( { page, baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/tax/woocommerce_tax_total_display', { + value: 'itemized', + } ); + + await test.step( 'Load cart page and confirm price display', async() => { + await page.goto( '/cart/' ); + await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible(); + await expect( page.getByRole( 'cell', { name: '$100.00' } ) ).toHaveCount(3); + await expect( page.getByRole( 'row', { name: 'Subtotal $100.00'} ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Country Tax $10.00 ' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'State Tax $5.00 ' } ) ).toBeVisible() + await expect( page.getByRole( 'row', { name: 'Total $115.00 ' } ) ).toBeVisible(); + } ); + + await test.step( 'Load checkout page and confirm taxes displayed', async() => { + await page.goto( '/checkout/' ); + await expect( page.getByRole( 'heading', { name: 'Checkout', exact: true } ) ).toBeVisible(); + + await page.getByLabel('First name *').first().fill( customer.billing.us.first_name ); + await page.getByLabel('Last name *').first().fill( customer.billing.us.last_name ); + await page.getByPlaceholder('House number and street name').first().fill( customer.billing.us.address ); + await page.getByLabel('Town / City *').first().pressSequentially( customer.billing.us.city ); + await page.getByLabel('ZIP Code *').first().pressSequentially( customer.billing.us.zip ); + await page.getByLabel('Phone *').first().fill( customer.billing.us.phone ); + await page.getByLabel('Email address *').first().fill( customer.billing.us.email ); + + await expect( page.getByRole( 'row', { name: 'Subtotal $100.00'} ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Country Tax $10.00' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'State Tax $5.00' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'City Tax $2.50' } ) ).not.toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Zip Tax $1.25' } ) ).not.toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Total $115.00 ' } ) ).toBeVisible(); + } ); + } ); +} ); + +test.describe( 'Shipping Tax', () => { + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/general/woocommerce_calc_taxes', { + value: 'yes', + } ); + await api.post( 'products', { + name: productName, + type: 'simple', + regular_price: productPrice, + } ) + .then( ( response ) => { + productId = response.data.id; + } ); + await api.post( 'taxes', { + "country": "US", + "state": "*", + "cities": "*", + "postcodes": "*", + "rate": "15", + "name": "Shipping Tax", + "shipping": true + } ) + .then( ( response ) => { + shippingTaxId = response.data.id; + } ); + await api.post( 'shipping/zones', { + name: 'All', + } ) + .then( ( response ) => { + shippingZoneId = response.data.id; + } ); + await api.post( `shipping/zones/${ shippingZoneId }/methods`, { + method_id: 'flat_rate', + } ) + .then( ( response ) => { + shippingMethodId = response.data.id; + } ); + await api.put( `shipping/zones/${ shippingZoneId }/methods/${ shippingMethodId }`, { + settings: { + cost: '20.00', + } + } ); + await api.put( 'payment_gateways/cod' , { + enabled: true + } ); + await api.put( 'settings/tax/woocommerce_tax_display_cart', { + value: 'incl', + } ); + } ); + + test.beforeEach( async ( { page, context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + await context.clearCookies(); + + // all tests use the first product + await page.goto( `/shop/?add-to-cart=${ productId }`, { waitUntil: 'networkidle' } ); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/general/woocommerce_calc_taxes', { + value: 'no', + } ); + await api.delete( `products/${ productId }`, { + force: true, + } ); + await api.delete( `taxes/${ shippingTaxId }`, { + force: true, + } ); + await api.put( 'payment_gateways/cod' , { + enabled: false + } ); + await api.delete( `shipping/zones/${ shippingZoneId }`, { + force: true, + } ); + } ); + + test( 'checks that tax is applied to shipping as well as order', async ( { page, baseURL } ) => { + + await test.step( 'Load cart page and confirm price display', async() => { + await page.goto( '/cart/' ); + await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible(); + await expect( page.getByRole( 'cell', { name: '$115.00 (incl. tax)' } ) ).toHaveCount(2); + await expect( page.getByRole( 'row', { name: 'Subtotal $115.00 (incl. tax)'} ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Shipping Flat rate: $23.00 (incl. tax) Shipping to CA.' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Total $138.00 (includes $18.00 Shipping Tax)' } ) ).toBeVisible(); + } ); + + await test.step( 'Load checkout page and confirm price display', async() => { + await page.goto( '/checkout/' ); + await expect( page.getByRole( 'heading', { name: 'Checkout' } ) ).toBeVisible(); + + await page.getByRole('textbox', { name: 'First name *' }).fill( customer.billing.us.first_name ); + await page.getByRole('textbox', { name: 'Last name *' }).fill( customer.billing.us.last_name ); + await page.getByRole('textbox', { name: 'Street address *' }).fill( customer.billing.us.address ); + await page.getByRole('textbox', { name: 'Town / City *' }).type( customer.billing.us.city ); + await page.getByRole('textbox', { name: 'ZIP Code *' }).type( customer.billing.us.zip ); + await page.getByLabel('Phone *').fill( customer.billing.us.phone ); + await page.getByLabel('Email address *').fill( customer.billing.us.email ); + + await expect( page.getByRole( 'row', { name: 'Taxed products are awesome × 1 $115.00 (incl. tax)' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Subtotal $115.00 (incl. tax)' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Shipping Flat rate: $23.00 (incl. tax)' } ) ).toBeVisible(); + await expect( page.getByRole( 'row', { name: 'Total $138.00 (includes $18.00 Shipping Tax)'} ) ).toBeVisible(); + } ); + + } ); + +} ); From 0a11d03ce5d533bb50f9de6134486023df44f1f6 Mon Sep 17 00:00:00 2001 From: jonathansadowski Date: Fri, 3 Nov 2023 13:22:07 -0500 Subject: [PATCH 11/11] Remove milestone from unmerged PRs (#41216) * Remove milestone from unmerged PRs * Add pr write permission to milestone manager --- .github/workflows/milestoned.yml | 26 +++++++++++++++++++ .../pull-request-post-merge-processing.yml | 4 +-- 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/milestoned.yml diff --git a/.github/workflows/milestoned.yml b/.github/workflows/milestoned.yml new file mode 100644 index 00000000000..1098ba24068 --- /dev/null +++ b/.github/workflows/milestoned.yml @@ -0,0 +1,26 @@ +name: Milestone Manager + +on: + pull_request_target: + types: [milestoned] + +permissions: {} + +jobs: + remove-milestone-from-unmerged-prs: + name: "Remove Milestone from Unmerged PRs" + if: github.event.pull_request.merged != true + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/github-script@v6 + with: + script: | + github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + milestone: null, + }); diff --git a/.github/workflows/pull-request-post-merge-processing.yml b/.github/workflows/pull-request-post-merge-processing.yml index 7e8073f6a1c..dea0dab0de6 100644 --- a/.github/workflows/pull-request-post-merge-processing.yml +++ b/.github/workflows/pull-request-post-merge-processing.yml @@ -38,9 +38,7 @@ jobs: with: php-version: '7.4' - name: 'Run the script to assign a milestone' - if: | - !github.event.pull_request.milestone && - github.event.pull_request.base.ref == 'trunk' + if: github.event.pull_request.base.ref == 'trunk' run: php assign-milestone-to-merged-pr.php env: PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }}