cart; return [ $this->expectedField( 'cart.subtotal', \wc_graphql_price( $cart->get_subtotal() ) ), $this->expectedField( 'cart.subtotalTax', \wc_graphql_price( $cart->get_subtotal_tax() ) ), $this->expectedField( 'cart.discountTotal', \wc_graphql_price( $cart->get_discount_total() ) ), $this->expectedField( 'cart.discountTax', \wc_graphql_price( $cart->get_discount_tax() ) ), $this->expectedField( 'cart.shippingTotal', \wc_graphql_price( $cart->get_shipping_total() ) ), $this->expectedField( 'cart.shippingTax', \wc_graphql_price( $cart->get_shipping_tax() ) ), $this->expectedField( 'cart.contentsTotal', \wc_graphql_price( $cart->get_cart_contents_total() ) ), $this->expectedField( 'cart.contentsTax', \wc_graphql_price( $cart->get_cart_contents_tax() ) ), $this->expectedField( 'cart.feeTotal', \wc_graphql_price( $cart->get_fee_total() ) ), $this->expectedField( 'cart.feeTax', \wc_graphql_price( $cart->get_fee_tax() ) ), $this->expectedField( 'cart.total', \wc_graphql_price( $cart->get_totals()['total'] ) ), $this->expectedField( 'cart.totalTax', \wc_graphql_price( $cart->get_total_tax() ) ), $this->expectedField( 'cart.isEmpty', $cart->is_empty() ), $this->expectedField( 'cart.displayPricesIncludeTax', $cart->display_prices_including_tax() ), $this->expectedField( 'cart.needsShippingAddress', $cart->needs_shipping_address() ), $this->expectedField( 'cart.rawSubtotal', static::NOT_NULL ), $this->expectedField( 'cart.rawSubtotalTax', static::NOT_NULL ), $this->expectedField( 'cart.rawDiscountTotal', static::NOT_NULL ), $this->expectedField( 'cart.rawDiscountTax', static::NOT_NULL ), $this->expectedField( 'cart.rawShippingTotal', static::NOT_NULL ), $this->expectedField( 'cart.rawShippingTax', static::NOT_NULL ), $this->expectedField( 'cart.rawContentsTotal', static::NOT_NULL ), $this->expectedField( 'cart.rawContentsTax', static::NOT_NULL ), $this->expectedField( 'cart.rawFeeTotal', static::NOT_NULL ), $this->expectedField( 'cart.rawFeeTax', static::NOT_NULL ), $this->expectedField( 'cart.rawtotal', static::NOT_NULL ), $this->expectedField( 'cart.rawTotalTax', static::NOT_NULL ), ]; } public function getExpectedCartItemData( $path, $cart_item_key ) { $cart = WC()->cart; $item = $cart->get_cart_item( $cart_item_key ); return [ $this->expectedObject( $path, [ $this->expectedField( 'key', $item['key'] ), $this->expectedField( 'product.node.id', $this->toRelayId( 'post', $item['product_id'] ) ), $this->expectedField( 'product.node.databaseId', $item['product_id'] ), $this->expectedField( 'variation.node.id', ! empty( $item['variation_id'] ) ? $this->toRelayId( 'post', $item['variation_id'] ) : 'NULL' ), $this->expectedField( 'variation.node.databaseId', ! empty( $item['variation_id'] ) ? $item['variation_id'] : 'NULL' ), $this->expectedField( 'quantity', $item['quantity'] ), $this->expectedField( 'subtotal', \wc_graphql_price( $item['line_subtotal'] ) ), $this->expectedField( 'subtotalTax', \wc_graphql_price( $item['line_subtotal_tax'] ) ), $this->expectedField( 'total', \wc_graphql_price( $item['line_total'] ) ), $this->expectedField( 'tax', \wc_graphql_price( $item['line_tax'] ) ), ] ), ]; } // tests public function testCartQuery() { $cart = WC()->cart; $this->factory->cart->add( [ 'product_id' => $this->factory->product->createSimple(), 'quantity' => 2, ], [ 'product_id' => $this->factory->product->createSimple(), 'quantity' => 1, ] ); $query = ' query { cart { subtotal subtotalTax discountTotal discountTax shippingTotal shippingTax contentsTotal contentsTax feeTotal feeTax total totalTax isEmpty displayPricesIncludeTax needsShippingAddress totalTaxes { id isCompound amount label } rawSubtotal: subtotal(format: RAW) rawSubtotalTax: subtotalTax(format: RAW) rawDiscountTotal: discountTotal(format: RAW) rawDiscountTax: discountTax(format: RAW) rawShippingTotal: shippingTotal(format:RAW) rawShippingTax: shippingTax(format: RAW) rawContentsTotal: contentsTotal(format: RAW) rawContentsTax: contentsTax(format: RAW) rawFeeTotal: feeTotal(format: RAW) rawFeeTax: feeTax(format: RAW) rawtotal: total(format: RAW) rawTotalTax: totalTax(format: RAW) } } '; /** * Assertion One */ $response = $this->graphql( compact( 'query' ) ); $this->assertQuerySuccessful( $response, $this->getExpectedCartData() ); } public function testCartItemQuery() { $cart = \WC()->cart; $variations = $this->factory->product_variation->createSome(); $key = $cart->add_to_cart( $variations['product'], 3, $variations['variations'][0], [ 'attribute_pa_color' => 'red' ] ); $query = ' query ($key: ID!) { cartItem(key: $key) { key product { node { id databaseId } } variation { attributes { id attributeId name label value } node { id databaseId } } quantity subtotal subtotalTax total tax } } '; /** * Assertion One */ $variables = [ 'key' => $key ]; $response = $this->graphql( compact( 'query', 'variables' ) ); $this->assertQuerySuccessful( $response, $this->getExpectedCartItemData( 'cartItem', $key ) ); } // Resolves a cart item's variation when its node loads as a base // \WPGraphQL\Model\Post (no get_type()) instead of a Product_Variation — // the case on sites where the WC model upgrade doesn't run for the // product_variation post type (e.g. Polylang). Without the fallback in // Post_Types::resolve_product_variation_type() this query fatals. public function testCartItemVariationResolvesWhenLoadedAsPostModel() { $cart = \WC()->cart; $variations = $this->factory->product_variation->createSome(); $key = $cart->add_to_cart( $variations['product'], 1, $variations['variations'][0], [ 'attribute_pa_color' => 'red' ] ); // Drop the WC model upgrade so the variation node arrives as a base Post. remove_filter( 'graphql_dataloader_pre_get_model', [ '\WPGraphQL\WooCommerce\Data\Loader\WC_CPT_Loader', 'inject_post_loader_models' ], 10 ); $query = ' query ($key: ID!) { cartItem(key: $key) { variation { node { __typename ... on SimpleProductVariation { databaseId } } } } } '; $response = $this->graphql( [ 'query' => $query, 'variables' => [ 'key' => $key ] ] ); // Restore the filter before asserting so its removal can't leak into other tests. add_filter( 'graphql_dataloader_pre_get_model', [ '\WPGraphQL\WooCommerce\Data\Loader\WC_CPT_Loader', 'inject_post_loader_models' ], 10, 3 ); $this->assertQuerySuccessful( $response, [ $this->expectedField( 'cartItem.variation.node.__typename', 'SimpleProductVariation' ), $this->expectedField( 'cartItem.variation.node.databaseId', $variations['variations'][0] ), ] ); } public function testCartItemConnection() { $keys = $this->factory->cart->add( [ 'product_id' => $this->factory->product->createSimple( [ 'virtual' => true ] ), 'quantity' => 2, ], [ 'product_id' => $this->factory->product->createSimple(), 'quantity' => 1, ], [ 'product_id' => $this->factory->product->createSimple(), 'quantity' => 10, ] ); $code = \wc_get_coupon_code_by_id( $this->factory->coupon->create( [ 'amount' => 45.50, 'discount_type' => 'fixed_cart', ] ) ); $cart = \WC()->cart; $cart->apply_coupon( $code ); $query = ' query($needsShipping: Boolean) { cart { contents (where: {needsShipping: $needsShipping}) { nodes { key } } } } '; /** * Assertion One */ $response = $this->graphql( compact( 'query' ) ); $expected = []; foreach ( $keys as $key ) { $expected[] = $this->expectedNode( 'cart.contents.nodes', [ 'key' => $key ] ); } $this->assertQuerySuccessful( $response, $expected ); /** * Assertion Two * * Tests "needsShipping" parameter. */ $variables = [ 'needsShipping' => true ]; $response = $this->graphql( compact( 'query', 'variables' ) ); $this->assertQuerySuccessful( $response, [ $this->expectedNode( 'cart.contents.nodes', [ 'key' => $keys[1] ] ), $this->expectedNode( 'cart.contents.nodes', [ 'key' => $keys[2] ] ), ] ); /** * Assertion Three * * Tests "needsShipping" parameter reversed. */ $variables = [ 'needsShipping' => false ]; $response = $this->graphql( compact( 'query', 'variables' ) ); $this->assertQuerySuccessful( $response, [ $this->expectedNode( 'cart.contents.nodes', [ 'key' => $keys[0] ] ), ] ); } public function testCartFeeQuery() { $product_id = $this->factory->product->createSimple(); WC()->cart->add_to_cart( $product_id, 3 ); WC()->cart->add_fee( 'Test fee', 30.50 ); $fee_ids = array_keys( WC()->cart->get_fees() ); $query = ' query ($id: ID!) { cartFee(id: $id) { id name taxClass taxable amount total } } '; /** * Assertion One */ $variables = [ 'id' => $fee_ids[0] ]; $response = $this->graphql( compact( 'query', 'variables' ) ); $fee = ( \WC()->cart->get_fees() )[ $fee_ids[0] ]; $this->assertQuerySuccessful( $response, [ $this->expectedField( 'cartFee.id', $fee->id ), $this->expectedField( 'cartFee.name', $fee->name ), $this->expectedField( 'cartFee.taxClass', $this->maybe( $fee->tax_class ) ), $this->expectedField( 'cartFee.taxable', $fee->taxable ), $this->expectedField( 'cartFee.amount', (float) $fee->amount ), $this->expectedField( 'cartFee.total', (float) $fee->total ), ] ); } public function testCartToCartFeeQuery() { $product_id = $this->factory->product->createSimple(); WC()->cart->add_to_cart( $product_id, 3 ); WC()->cart->add_fee( 'Test fee', 30.50 ); $query = ' query { cart { fees { id } } } '; /** * Assertion One */ $response = $this->graphql( compact( 'query' ) ); $expected = []; foreach ( \WC()->cart->get_fees() as $fee_id => $value ) { $expected[] = $this->expectedNode( 'cart.fees', [ 'id' => $fee_id ] ); } $this->assertQuerySuccessful( $response, $expected ); } public function testCartItemPagination() { $cart_items = $this->factory->cart->add( [ 'product_id' => $this->factory->product->createSimple(), 'quantity' => 2, ], [ 'product_id' => $this->factory->product->createSimple(), 'quantity' => 1, ], [ 'product_id' => $this->factory->product->createSimple(), 'quantity' => 1, ], [ 'product_id' => $this->factory->product->createSimple(), 'quantity' => 1, ], [ 'product_id' => $this->factory->product->createSimple(), 'quantity' => 1, ] ); $query = ' query ($first: Int, $last: Int, $before: String, $after: String) { cart { contents(first: $first, last: $last, before: $before, after: $after) { itemCount productCount pageInfo { hasNextPage hasPreviousPage } edges { cursor node { key } } } } } '; /** * Assertion One * * Tests "first" parameter. */ $variables = [ 'first' => 2, 'after' => '', ]; $response = $this->graphql( compact( 'query', 'variables' ) ); $expected = [ $this->expectedField( 'cart.contents.itemCount', 6 ), $this->expectedField( 'cart.contents.productCount', 5 ), $this->expectedField( 'cart.contents.pageInfo.hasNextPage', true ), $this->expectedField( 'cart.contents.pageInfo.hasPreviousPage', false ), $this->expectedEdge( 'cart.contents.edges', [ $this->expectedField( 'key', $cart_items[0] ) ], 0 ), $this->expectedEdge( 'cart.contents.edges', [ $this->expectedField( 'key', $cart_items[1] ) ], 1 ), $this->expectedField( 'cart.contents.edges.0.cursor', $this->key_to_cursor( $cart_items[0] ) ), $this->expectedField( 'cart.contents.edges.1.cursor', $this->key_to_cursor( $cart_items[1] ) ), ]; $this->assertQuerySuccessful( $response, $expected ); /** * Assertion Two * * Tests "after" parameter. */ $variables = [ 'first' => 2, 'after' => $this->key_to_cursor( $cart_items[1] ), ]; $response = $this->graphql( compact( 'query', 'variables' ) ); $expected = [ $this->expectedField( 'cart.contents.pageInfo.hasNextPage', true ), $this->expectedField( 'cart.contents.pageInfo.hasPreviousPage', true ), $this->expectedField( 'cart.contents.edges.0.cursor', $this->key_to_cursor( $cart_items[2] ) ), $this->expectedField( 'cart.contents.edges.1.cursor', $this->key_to_cursor( $cart_items[3] ) ), $this->expectedEdge( 'cart.contents.edges', [ $this->expectedField( 'key', $cart_items[2] ) ], 0 ), $this->expectedEdge( 'cart.contents.edges', [ $this->expectedField( 'key', $cart_items[3] ) ], 1 ), ]; $this->assertQuerySuccessful( $response, $expected ); /** * Assertion Three * * Tests "last" parameter. */ $variables = [ 'last' => 2 ]; $response = $this->graphql( compact( 'query', 'variables' ) ); $expected = [ $this->expectedField( 'cart.contents.pageInfo.hasNextPage', false ), $this->expectedField( 'cart.contents.pageInfo.hasPreviousPage', true ), $this->expectedEdge( 'cart.contents.edges', [ $this->expectedField( 'key', $cart_items[3] ) ], 0 ), $this->expectedEdge( 'cart.contents.edges', [ $this->expectedField( 'key', $cart_items[4] ) ], 1 ), $this->expectedField( 'cart.contents.edges.0.cursor', $this->key_to_cursor( $cart_items[3] ) ), $this->expectedField( 'cart.contents.edges.1.cursor', $this->key_to_cursor( $cart_items[4] ) ), ]; $this->assertQuerySuccessful( $response, $expected ); /** * Assertion Four * * Tests "before" parameter. */ $variables = [ 'last' => 4, 'before' => $this->key_to_cursor( $cart_items[3] ), ]; $response = $this->graphql( compact( 'query', 'variables' ) ); $expected = [ $this->expectedField( 'cart.contents.pageInfo.hasNextPage', true ), $this->expectedField( 'cart.contents.pageInfo.hasPreviousPage', false ), $this->expectedField( 'cart.contents.edges.0.cursor', $this->key_to_cursor( $cart_items[0] ) ), $this->expectedField( 'cart.contents.edges.1.cursor', $this->key_to_cursor( $cart_items[1] ) ), $this->expectedField( 'cart.contents.edges.2.cursor', $this->key_to_cursor( $cart_items[2] ) ), $this->expectedEdge( 'cart.contents.edges', [ $this->expectedField( 'key', $cart_items[0] ) ], 0 ), $this->expectedEdge( 'cart.contents.edges', [ $this->expectedField( 'key', $cart_items[1] ) ], 1 ), $this->expectedEdge( 'cart.contents.edges', [ $this->expectedField( 'key', $cart_items[2] ) ], 2 ), ]; $this->assertQuerySuccessful( $response, $expected ); } public function testCartQueryWithFees() { $product_id = $this->factory->product->createSimple(); WC()->cart->add_to_cart( $product_id, 3 ); $query = ' query($fees: [FeeInput]) { cart(fees: $fees) { total feeTotal feeTax fees { name amount total } } } '; $variables = [ 'fees' => [ [ 'name' => 'Test Fee', 'amount' => 10.50, 'taxable' => false, 'taxClass' => 'STANDARD', ], ], ]; $response = $this->graphql( compact( 'query', 'variables' ) ); $this->assertQuerySuccessful( $response, [ $this->expectedField( 'cart.total', self::NOT_FALSY ), $this->expectedField( 'cart.feeTotal', self::NOT_FALSY ), $this->expectedField( 'cart.fees.#.name', 'Test Fee' ), $this->expectedField( 'cart.fees.#.amount', floatval( 10.50 ) ), $this->expectedField( 'cart.fees.#.total', floatval( 10.50 ) ), ] ); } /** * Test cart taxLines field returns itemized tax data. */ public function testCartTaxLines() { // Enable taxes and set itemized display. update_option( 'woocommerce_calc_taxes', 'yes' ); update_option( 'woocommerce_tax_total_display', 'itemized' ); // Create a tax rate. $tax_rate_id = $this->factory->tax_rate->create( [ 'country' => '', 'rate' => '10.0000', 'name' => 'Test Tax', 'priority' => 1, 'compound' => 0, 'shipping' => 1, 'class' => '', ] ); // Create a product and add to cart. $product_id = $this->factory->product->createSimple( [ 'regular_price' => '100', 'virtual' => true ] ); WC()->cart->add_to_cart( $product_id ); WC()->cart->calculate_totals(); $query = ' query { cart { taxLines { name price rate } } } '; $response = $this->graphql( compact( 'query' ) ); $expected = [ $this->expectedNode( 'cart.taxLines', [ $this->expectedField( 'name', 'Test Tax' ), $this->expectedField( 'price', '$10.00' ), $this->expectedField( 'rate', '10%' ), ] ), ]; $this->assertQuerySuccessful( $response, $expected ); } /** * Test cart taxLines returns empty when display is not itemized. */ public function testCartTaxLinesEmptyWhenNotItemized() { update_option( 'woocommerce_calc_taxes', 'yes' ); update_option( 'woocommerce_tax_total_display', 'single' ); $tax_rate_id = $this->factory->tax_rate->create( [ 'country' => '', 'rate' => '10.0000', 'name' => 'Test Tax', 'priority' => 1, 'compound' => 0, 'shipping' => 1, 'class' => '', ] ); $product_id = $this->factory->product->createSimple( [ 'regular_price' => '100', 'virtual' => true ] ); WC()->cart->add_to_cart( $product_id ); WC()->cart->calculate_totals(); $query = ' query { cart { taxLines { name price rate } } } '; $response = $this->graphql( compact( 'query' ) ); $expected = [ $this->expectedField( 'cart.taxLines', static::IS_FALSY ), ]; $this->assertQuerySuccessful( $response, $expected ); } /** * Test shipping rate cost includes tax when woocommerce_tax_display_cart is incl. */ public function testShippingRateCostIncludesTax() { // Enable taxes and set display to include tax. update_option( 'woocommerce_calc_taxes', 'yes' ); update_option( 'woocommerce_tax_display_cart', 'incl' ); // Create a tax rate that applies to shipping. $this->factory->tax_rate->create( [ 'country' => '', 'rate' => '10.0000', 'name' => 'Shipping Tax', 'priority' => 1, 'compound' => 0, 'shipping' => 1, 'class' => '', ] ); // Create a physical product and add to cart. $product_id = $this->factory->product->createSimple( [ 'regular_price' => '50' ] ); WC()->cart->add_to_cart( $product_id ); // Set shipping address so rates are calculated. WC()->customer->set_shipping_country( 'GB' ); WC()->customer->set_shipping_postcode( 'CB23 1AB' ); WC()->cart->calculate_totals(); $query = ' query { cart { availableShippingMethods { rates { id label cost subtotal taxTotal } } } } '; $response = $this->graphql( compact( 'query' ) ); // Flat rate is $10, tax is 10% = $1. Cost should be $11 (incl tax), subtotal $10. $expected = [ $this->expectedObject( 'cart.availableShippingMethods.0.rates.#', [ $this->expectedField( 'cost', '$11.00' ), $this->expectedField( 'subtotal', '$10.00' ), $this->expectedField( 'taxTotal', '$1.00' ), ] ), ]; $this->assertQuerySuccessful( $response, $expected ); } /** * Test shipping rate cost excludes tax when woocommerce_tax_display_cart is excl. */ public function testShippingRateCostExcludesTax() { // Enable taxes and set display to exclude tax. update_option( 'woocommerce_calc_taxes', 'yes' ); update_option( 'woocommerce_tax_display_cart', 'excl' ); $this->factory->tax_rate->create( [ 'country' => '', 'rate' => '10.0000', 'name' => 'Shipping Tax', 'priority' => 1, 'compound' => 0, 'shipping' => 1, 'class' => '', ] ); $product_id = $this->factory->product->createSimple( [ 'regular_price' => '50' ] ); WC()->cart->add_to_cart( $product_id ); WC()->customer->set_shipping_country( 'GB' ); WC()->customer->set_shipping_postcode( 'CB23 1AB' ); WC()->cart->calculate_totals(); $query = ' query { cart { availableShippingMethods { rates { id label cost subtotal taxTotal } } } } '; $response = $this->graphql( compact( 'query' ) ); // Cost and subtotal should both be $10 (excl tax), taxTotal still $1. $expected = [ $this->expectedObject( 'cart.availableShippingMethods.0.rates.#', [ $this->expectedField( 'cost', '$10.00' ), $this->expectedField( 'subtotal', '$10.00' ), $this->expectedField( 'taxTotal', '$1.00' ), ] ), ]; $this->assertQuerySuccessful( $response, $expected ); } }