Skip to content

Checkout options #319

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 35 commits into from
Nov 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f05a4f0
Add kebab submenu to items in cart, add option to remove item from cart
Serunde Sep 12, 2018
c6443c0
Code cleanup, fix zeroed price bug in recently emptied cart
Serunde Sep 12, 2018
81c2e71
Refactor, additional code cleanup, and unit tests
Serunde Sep 17, 2018
e5ad03b
Merge changes from styling branch
Serunde Sep 20, 2018
b7020db
Add `edit item` and `favorite` to cart + additional styling
Serunde Sep 21, 2018
df815ee
Add storybook and product options stories to Venia
pcvonz Sep 24, 2018
f2546b0
Extract logic from `Kebab`, add reusable component `Section`
Serunde Sep 25, 2018
8f682a1
Merge branch 'master' of https://github.com/bargreenellingson/pwa-stu…
Serunde Sep 25, 2018
5349e0e
Merge branch 'storybook' of https://github.com/bargreenellingson/pwa-…
Serunde Sep 25, 2018
3659cd0
Enzyme and Storybook tests, prettier pass
Serunde Sep 28, 2018
5e4fb8d
Remove unnecessary prop types and irrelevant Storybook items
Serunde Sep 28, 2018
a08fa9c
Merge branch 'release/2.0' of https://github.com/magento-research/pwa…
Serunde Oct 1, 2018
172757b
Integrate all features and tests with release 2.0 changes
Serunde Oct 2, 2018
dd72dcb
Extract conditional logic from checkout and options confirmation comp…
Serunde Oct 2, 2018
84f34fe
Linter pass
Serunde Oct 2, 2018
1abd10f
Add action/reducer tests for `removeItemFromCart()`
Serunde Oct 4, 2018
ff193ab
Remove merge conflicts in package.json
Serunde Oct 5, 2018
6ed920a
Merge branch 'release/2.0' into checkout-options
Serunde Oct 5, 2018
a3c21cf
Simplify test for cart reset on 404 error
Serunde Oct 5, 2018
c8dc9cb
Merge branch 'release/2.0' into checkout-options
Oct 22, 2018
6a461bb
Merge branch 'release/2.0' into checkout-options
Oct 23, 2018
476d3db
Merge branch 'release/2.0' into checkout-options
zetlen Nov 1, 2018
4d3df9b
Merge branch 'release/2.0' of https://github.com/magento-research/pwa…
Serunde Nov 9, 2018
fd898c8
Better CSS organization, minor code cleanup
Serunde Nov 9, 2018
30e57e4
Pull for current package.json
Serunde Nov 9, 2018
4e642e3
Prettier pass, test updates
Serunde Nov 9, 2018
29c6c5d
npm i
Serunde Nov 9, 2018
7e236a9
Merge branch 'release/2.0' into checkout-options
Serunde Nov 9, 2018
6cac7dc
Add classname to `section` stylesheet
Serunde Nov 10, 2018
85393d9
Merge branch 'checkout-options' of https://github.com/bargreenellings…
Serunde Nov 10, 2018
feffe61
Merge & merge conflicts
Serunde Nov 16, 2018
2291efc
Merge branch 'release/2.0' into checkout-options
jimbo Nov 19, 2018
d658d05
Merge branch 'release/2.0' into checkout-options
jimbo Nov 19, 2018
7fef2c3
Fix package lock conflicts
jimbo Nov 19, 2018
dc43f99
Fix package lock file again
jimbo Nov 19, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"prettier": "prettier --write '@(packages|scripts)/**/*.@(js|css)' '*.js'",
"prettier:check": "prettier --list-different '@(packages|scripts)/**/*.@(js|css)' '*.js'",
"stage:venia": "cd packages/venia-concept && npm start; cd - >/dev/null",
"storybook:venia": "cd packages/venia-concept && npm run storybook",
"test": "jest",
"test:ci": "npm run -s test -- -i --json --outputFile=test-results.json",
"test:debug": "node --inspect-brk node_modules/.bin/jest -i",
Expand Down
8 changes: 8 additions & 0 deletions packages/venia-concept/.storybook/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { configure } from '@storybook/react';

function loadStories() {
const context = require.context('../src', true, /__stories__\/.+\.js$/);
context.keys().forEach(context);
}

configure(loadStories, module);
63 changes: 63 additions & 0 deletions packages/venia-concept/.storybook/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const path = require('path');

const configureBabel = require('../babel.config.js');
const babelOptions = configureBabel('development');
console.log(babelOptions);

const base_config = require('./webpack.config.js');

const themePaths = {
src: path.resolve(__dirname, '../src'),
assets: path.resolve(__dirname, '../web'),
output: path.resolve(__dirname, '../web/js'),
node: path.resolve(__dirname, '../../../')
};

console.log(themePaths.node);

const testPath = path.resolve('../');

module.exports = (storybookBaseConfig, configType) => {
storybookBaseConfig.module.rules.push({
include: [themePaths.src],
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: { ...babelOptions, cacheDirectory: true }
}
]
});

storybookBaseConfig.module.rules.push({
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
localIdentName: '[name]-[local]-[hash:base64:3]',
modules: true
}
}
]
});

storybookBaseConfig.module.rules.push({
test: /\.(jpg|svg)$/,
use: [
{
loader: 'file-loader',
options: {}
}
]
});

storybookBaseConfig.resolve.alias = {
src: themePaths.src
};
storybookBaseConfig.resolve.modules = ['node_modules'];

return storybookBaseConfig;
};
31 changes: 31 additions & 0 deletions packages/venia-concept/src/actions/cart/__tests__/actions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,37 @@ test('addItem.receive() returns a proper action object', () => {
});
});

test('removeItem.request.toString() returns the proper action type', () => {
expect(actions.removeItem.request.toString()).toBe(
'CART/REMOVE_ITEM/REQUEST'
);
});

test('removeItem.request() returns a proper action object', () => {
expect(actions.removeItem.request(payload)).toEqual({
type: 'CART/REMOVE_ITEM/REQUEST',
payload
});
});

test('removeItem.receive.toString() returns the proper action type', () => {
expect(actions.removeItem.receive.toString()).toBe(
'CART/REMOVE_ITEM/RECEIVE'
);
});

test('removeItem.receive() returns a proper action object', () => {
expect(actions.removeItem.receive(payload)).toEqual({
type: 'CART/REMOVE_ITEM/RECEIVE',
payload
});
expect(actions.removeItem.receive(error)).toEqual({
type: 'CART/REMOVE_ITEM/RECEIVE',
payload: error,
error: true
});
});

test('getGuestCart.request.toString() returns the proper action type', () => {
expect(actions.getGuestCart.request.toString()).toBe(
'CART/GET_GUEST_CART/REQUEST'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import actions from '../actions';
import {
addItemToCart,
removeItemFromCart,
createGuestCart,
getCartDetails,
toggleCart
Expand Down Expand Up @@ -371,6 +372,85 @@ test('addItemToCart opens drawer and gets cart details on success', async () =>
expect(request).toHaveBeenCalledTimes(3);
});

test('removeItemFromCart() returns a thunk', () => {
expect(removeItemFromCart({})).toBeInstanceOf(Function);
});

test('removeItemFromCart thunk returns undefined', async () => {
const result = await removeItemFromCart({})(...thunkArgs);

expect(result).toBeUndefined();
});

test('removeItemFromCart thunk dispatches actions on success', async () => {
const payload = { item: 'ITEM' };
const cartItem = 'CART_ITEM';

request.mockResolvedValueOnce(cartItem);
await removeItemFromCart(payload)(...thunkArgs);

expect(dispatch).toHaveBeenNthCalledWith(
1,
actions.removeItem.request(payload)
);
expect(dispatch).toHaveBeenNthCalledWith(
2,
actions.removeItem.receive({ cartItem, cartItemCount: 0, ...payload })
);
expect(dispatch).toHaveBeenNthCalledWith(3, expect.any(Function));
expect(dispatch).toHaveBeenCalledTimes(3);
});

test('removeItemFromCart thunk dispatches special failure if guestCartId is not present', async () => {
const payload = { item: 'ITEM' };
const error = new Error('Missing required information: guestCartId');
error.noGuestCartId = true;
getState.mockImplementationOnce(() => ({ cart: {} }));
await removeItemFromCart(payload)(...thunkArgs);
expect(mockRemoveItem).toHaveBeenCalledWith('guestCartId');
expect(dispatch).toHaveBeenNthCalledWith(
1,
actions.removeItem.request(payload)
);
expect(dispatch).toHaveBeenNthCalledWith(
2,
actions.removeItem.receive(error)
);
expect(dispatch).toHaveBeenNthCalledWith(3, expect.any(Function));
});

test('removeItemFromCart tries to recreate a guest cart on 404 failure', async () => {
getState.mockImplementationOnce(() => ({
cart: { guestCartId: 'OLD_AND_BUSTED' }
}));
const payload = { item: 'ITEM' };
const error = new Error('ERROR');
error.response = {
status: 404
};

request.mockRejectedValueOnce(error);

await removeItemFromCart(payload)(...thunkArgs);

expect(request).toHaveBeenCalledTimes(2);
});

test('removeItemFromCart resets the guest cart when removing the last item in the cart', async () => {
getState.mockImplementationOnce(() => ({
cart: { guestCartId: 'CART', details: { items_count: 1 } }
}));
let payload = { item: 'ITEM' };

// removeItemFromCart() calls storage.removeItem() to clear the guestCartId
// but only if there's 1 item left in the cart
mockRemoveItem.mockImplementationOnce(() => {});

await removeItemFromCart(payload)(...thunkArgs);

expect(mockRemoveItem).toHaveBeenCalled();
});

test('getCartDetails() returns a thunk', () => {
expect(getCartDetails()).toBeInstanceOf(Function);
});
Expand Down
4 changes: 4 additions & 0 deletions packages/venia-concept/src/actions/cart/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ const actionMap = {
REQUEST: null,
RECEIVE: null
},
REMOVE_ITEM: {
REQUEST: null,
RECEIVE: null
},
GET_GUEST_CART: {
REQUEST: null,
RECEIVE: null
Expand Down
58 changes: 58 additions & 0 deletions packages/venia-concept/src/actions/cart/asyncActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,64 @@ export const addItemToCart = (payload = {}) => {
};
};

export const removeItemFromCart = payload => {
const { item } = payload;

return async function thunk(dispatch, getState) {
dispatch(actions.removeItem.request(payload));

try {
const { cart } = getState();
const { guestCartId } = cart;
const cartItemCount = cart.details ? cart.details.items_count : 0;

if (!guestCartId) {
const missingGuestCartError = new Error(
'Missing required information: guestCartId'
);
missingGuestCartError.noGuestCartId = true;
throw missingGuestCartError;
}

const cartItem = await request(
`/rest/V1/guest-carts/${guestCartId}/items/${item.item_id}`,
{
method: 'DELETE'
}
);
// When removing the last item in the cart, perform a reset
// to prevent a bug where the next item added to the cart has
// a price of 0
if (cartItemCount == 1) {
await clearGuestCartId();
}

dispatch(
actions.removeItem.receive({ cartItem, item, cartItemCount })
);
} catch (error) {
const { response, noGuestCartId } = error;

dispatch(actions.removeItem.receive(error));

// check if the guest cart has expired
if (noGuestCartId || (response && response.status === 404)) {
// if so, then delete the cached ID...
// in contrast to the save, make sure storage deletion is
// complete before dispatching the error--you don't want an
// upstream action to try and reuse the known-bad ID.
await clearGuestCartId();
// then create a new one
await dispatch(createGuestCart());
// then retry this operation
return thunk(...arguments);
}
}

await Promise.all([dispatch(getCartDetails({ forceRefresh: true }))]);
};
};

export const getCartDetails = (payload = {}) => {
const { forceRefresh } = payload;

Expand Down
12 changes: 6 additions & 6 deletions packages/venia-concept/src/components/Gallery/gallery.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@
grid-template-columns: repeat(3, 1fr);
}

@media (max-width: 640px) {
.items {
grid-template-columns: repeat(2, 1fr);
}
}

.pagination {
display: grid;
grid-area: pagination;
Expand All @@ -40,3 +34,9 @@
justify-content: center;
padding-top: 1rem;
}

@media (max-width: 640px) {
.items {
grid-template-columns: repeat(2, 1fr);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { storiesOf } from '@storybook/react';

import Kebab from '../kebab';
import Section from '../section';
import 'src/index.css';

const stories = storiesOf('Mini Cart/Kebab', module);

const styles = {
width: '150px',
height: '150px',
display: 'grid'
};

stories.add('Kebab Closed', () => (
<div style={styles}>
<Kebab />
</div>
));

stories.add('Kebab Open', () => (
<div style={styles}>
<Kebab isOpen={true}>
<Section icon="heart" text="Section 1" />
<Section icon="x" text="Section 2" />
<Section icon="chevron-up" text="Section 3" />
<Section text="Non-icon Section" />
</Kebab>
</div>
));
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import { storiesOf } from '@storybook/react';

import ProductList from '../productList';
import 'src/index.css';

const stories = storiesOf('Mini Cart/Product List', module);

const items = [
{
item_id: 1,
name: 'Product 1',
price: 10,
qty: 1,
sku: 'TEST1',
image: 'test.jpg'
},
{
item_id: 2,
name: 'Product 2',
price: 5,
qty: 1,
sku: 'TEST2',
image: 'test.jpg'
},
{
item_id: 3,
name: 'Product 3',
price: 15,
qty: 1,
sku: 'TEST3',
image: 'test.jpg'
}
];

stories.add('Product List With Kebab', () => (
<ProductList
items={items}
currencyCode="NZD"
removeItemFromCart={() => {}}
showEditPanel={() => {}}
/>
));
Loading