From 5a26d2708e071d9e586403918aeb25596422648e Mon Sep 17 00:00:00 2001 From: Rua Haszard Date: Fri, 6 Mar 2020 08:11:39 +1300 Subject: [PATCH] ensure cart totals update when items are removed or quantity changed (https://github.com/woocommerce/woocommerce-blocks/pull/1840) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ensure cart totals update when items are removed (prototype): - return complete cart object from DELETE cart/items/:key - use receiveCart on client to update whole cart including total * move API endpoint for removing cart items to cart controller: - returns full cart schema so should be part of cart controller - is now a POST request with param - not strict REST - fix up client action to use new API * move API test for removing cart items (API has moved) * use correct path for remove API in tests (doh!) * add extra API test for remove_cart_item with bad key => 404 (experiment) * experiment: delete test_remove_cart_item, does test_remove_bad_cart_item work? * reinstate test_remove_cart_item with single valid request * remove unnecessary newline * tidy comments in PHP api tests, rerun travis? * remove test_remove_cart_item which may be causing problems * reinstate troublesome remove cart item api test (experiment): - see if this works now travis issue is resolved * whitespace * show correct total when changing cart item quantity: - move update cart item quantity API to cart controller - & return full cart response - update js action to new API route & receive full cart response * simplify test_remove_cart_item API test - now just tests a valid remove succeeds * remove test_update_item (API has moved) + + experimentally remove test_remove_bad_cart_item - testing if cart remove tests interact with each other * fix tests (🤞) - pass params in body, not as query params * reinstate test for re-deleting same item * update API docs - update/delete cart item have moved to /cart * add response data checks to new cart API tests: - extra protection against expected 404 "false positives" * reinstate API test for changing cart item quantity * fix remove cart item body tests - MIA items return error code, not cart * fix test - quantity param is int not string * attempt fix 404ing test_update_item: - only allow POST, remove trailing `/` - align array equals for good measure :) * fix action for update-item - method=POST, now takes body params * fix response body asserts in test_update_item * reinstate update_item and delete_item on CartItems controller * typos + examples in new cart items API docs * reorder previous cart item docs to minimise diff/churn * reinstate tabs in docs response example --- .../assets/js/data/cart/actions.js | 22 ++--- .../src/RestApi/StoreApi/Controllers/Cart.php | 86 +++++++++++++++++++ .../src/RestApi/StoreApi/README.md | 39 +++++++++ .../php/RestApi/StoreApi/Controllers/Cart.php | 66 ++++++++++++++ 4 files changed, 203 insertions(+), 10 deletions(-) diff --git a/plugins/woocommerce-blocks/assets/js/data/cart/actions.js b/plugins/woocommerce-blocks/assets/js/data/cart/actions.js index f0dd8d09227..82d32aec441 100644 --- a/plugins/woocommerce-blocks/assets/js/data/cart/actions.js +++ b/plugins/woocommerce-blocks/assets/js/data/cart/actions.js @@ -199,13 +199,13 @@ export function* removeItemFromCart( cartItemKey ) { yield itemQuantityPending( cartItemKey, true ); try { - yield apiFetch( { - path: `/wc/store/cart/items/${ cartItemKey }`, - method: 'DELETE', + const cart = yield apiFetch( { + path: `/wc/store/cart/remove-item/?key=${ cartItemKey }`, + method: 'POST', cache: 'no-store', } ); - yield receiveRemovedItem( cartItemKey ); + yield receiveCart( cart ); } catch ( error ) { yield receiveError( error ); } @@ -227,15 +227,17 @@ export function* changeCartItemQuantity( cartItemKey, quantity ) { yield itemQuantityPending( cartItemKey, true ); try { - const result = yield apiFetch( { - path: `/wc/store/cart/items/${ cartItemKey }?quantity=${ quantity }`, - method: 'PUT', + const cart = yield apiFetch( { + path: `/wc/store/cart/update-item`, + method: 'POST', + data: { + key: cartItemKey, + quantity, + }, cache: 'no-store', } ); - if ( result ) { - yield receiveCartItem( result ); - } + yield receiveCart( cart ); } catch ( error ) { yield receiveError( error ); } diff --git a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Controllers/Cart.php b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Controllers/Cart.php index c49e42be620..9c894735068 100644 --- a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Controllers/Cart.php +++ b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Controllers/Cart.php @@ -103,6 +103,44 @@ class Cart extends RestController { 'schema' => [ $this, 'get_public_item_schema' ], ] ); + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/remove-item', + [ + [ + 'methods' => 'POST', + 'callback' => [ $this, 'remove_cart_item' ], + 'args' => [ + 'key' => [ + 'description' => __( 'Unique identifier (key) for the cart item.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + ], + ], + ], + 'schema' => [ $this, 'get_public_item_schema' ], + ] + ); + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/update-item', + [ + [ + 'methods' => 'POST', + 'callback' => [ $this, 'update_cart_item' ], + 'args' => [ + 'key' => [ + 'description' => __( 'Unique identifier (key) for the cart item to update.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + ], + 'quantity' => [ + 'description' => __( 'New quantity of the item in the cart.', 'woo-gutenberg-products-block' ), + 'type' => 'integer', + ], + ], + ], + 'schema' => [ $this, 'get_public_item_schema' ], + ] + ); } /** @@ -193,6 +231,54 @@ class Cart extends RestController { return $response; } + /** + * Delete a single cart item. + * + * @param \WP_Rest_Request $request Full data about the request. + * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. + */ + public function remove_cart_item( $request ) { + $controller = new CartController(); + $cart = $controller->get_cart_instance(); + $cart_item = $controller->get_cart_item( $request['key'] ); + + if ( ! $cart_item ) { + return new RestError( 'woocommerce_rest_cart_invalid_key', __( 'Cart item does not exist.', 'woo-gutenberg-products-block' ), array( 'status' => 404 ) ); + } + + $cart->remove_cart_item( $request['key'] ); + + $data = $this->prepare_item_for_response( $cart, $request ); + $response = rest_ensure_response( $data ); + + return $response; + } + + /** + * Change quantity for specified cart item. + * + * @param \WP_Rest_Request $request Full data about the request. + * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. + */ + public function update_cart_item( $request ) { + $controller = new CartController(); + $cart = $controller->get_cart_instance(); + $cart_item = $controller->get_cart_item( $request['key'] ); + + if ( ! $cart_item ) { + return new RestError( 'woocommerce_rest_cart_invalid_key', __( 'Cart item does not exist.', 'woo-gutenberg-products-block' ), array( 'status' => 404 ) ); + } + + if ( isset( $request['quantity'] ) ) { + $cart->set_quantity( $request['key'], $request['quantity'] ); + } + + $data = $this->prepare_item_for_response( $cart, $request ); + $response = rest_ensure_response( $data ); + + return $response; + } + /** * Cart item schema. * diff --git a/plugins/woocommerce-blocks/src/RestApi/StoreApi/README.md b/plugins/woocommerce-blocks/src/RestApi/StoreApi/README.md index a7e0e51467b..fa40f5eed45 100644 --- a/plugins/woocommerce-blocks/src/RestApi/StoreApi/README.md +++ b/plugins/woocommerce-blocks/src/RestApi/StoreApi/README.md @@ -308,6 +308,8 @@ Example response: ## Cart API +### Get the cart + ```http GET /cart ``` @@ -455,6 +457,43 @@ Example response: } ``` +### Edit single cart item + +Change the quantity of a cart item. + +```http +PUT /cart/update-item/ +``` + +| Attribute | Type | Required | Description | +| :--------- | :------ | :------: | :--------------------------------- | +| `key` | string | Yes | The key of the cart item to edit. | +| `quantity` | integer | Yes | Quantity of this item in the cart. | + +Returns the full cart object (same response as `GET /cart`). + +```http +curl --request POST "https://example-store.com/wp-json/wc/store/cart/update-item?key=1ff1de774005f8da13f42943881c655f&quantity=3" +``` + +### Delete single cart item + +Delete/remove an item from the cart. + +```http +POST /cart/remove-item/ +``` + +| Attribute | Type | Required | Description | +| :-------- | :----- | :------: | :-------------------------------- | +| `key` | string | Yes | The key of the cart item to edit. | + +Returns the full cart object (same response as `GET /cart`). + +```http +curl --request POST "https://example-store.com/wp-json/wc/store/cart/remove-item?key=1ff1de774005f8da13f42943881c655f" +``` + ## Cart items API ### List cart items diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Cart.php b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Cart.php index 6e152307fd3..7505457cc1c 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Cart.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Cart.php @@ -85,6 +85,72 @@ class Cart extends TestCase { $this->assertEquals( '2900', $data['totals']->total_price ); } + /** + * Test removing a nonexistent cart item. + */ + public function test_remove_bad_cart_item() { + // Test removing a bad cart item - should return 404. + $request = new WP_REST_Request( 'POST', '/wc/store/cart/remove-item' ); + $request->set_body_params( + array( + 'key' => 'bad_item_key_123', + ) + ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 404, $response->get_status() ); + $this->assertEquals( 'woocommerce_rest_cart_invalid_key', $data['code'] ); + } + + /** + * Test remove cart item. + */ + public function test_remove_cart_item() { + // Test removing a valid cart item - should return updated cart. + $request = new WP_REST_Request( 'POST', '/wc/store/cart/remove-item' ); + $request->set_body_params( + array( + 'key' => $this->keys[0], + ) + ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, $data['items_count'] ); + $this->assertEquals( 1, count( $data['items'] ) ); + $this->assertEquals( '10', $data['items_weight'] ); + $this->assertEquals( '1000', $data['totals']->total_items ); + + // Test removing same item again - should return 404 (item is already removed). + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 404, $response->get_status() ); + $this->assertEquals( 'woocommerce_rest_cart_invalid_key', $data['code'] ); + } + + /** + * Test changing the quantity of a cart item. + */ + public function test_update_item() { + $request = new WP_REST_Request( 'POST', '/wc/store/cart/update-item' ); + $request->set_body_params( + array( + 'key' => $this->keys[0], + 'quantity' => 10, + ) + ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 10, $data['items'][0]['quantity'] ); + $this->assertEquals( 11, $data['items_count'] ); + $this->assertEquals( '11000', $data['totals']->total_items ); + } + /** * Test applying coupon to cart. */