Skip to content

Commit

Permalink
Merge pull request #12 from shopgate/CCP-2192
Browse files Browse the repository at this point in the history
Add product context around product cards
  • Loading branch information
alexbridge committed Jun 25, 2020
2 parents 97c8e99 + 2a2027a commit 0fcc7a0
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 177 deletions.
3 changes: 3 additions & 0 deletions frontend/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "@shopgate/eslint-config"
}
30 changes: 21 additions & 9 deletions frontend/components/Item/index.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { ThemeContext } from '@shopgate/engage/core';
import { Card } from '@shopgate/engage/components';
import { ProductCard as ProductCardBase } from '@shopgate/engage/product';
import PlaceholderCard from './components/PlaceholderCard';
Expand All @@ -22,6 +23,8 @@ const Item = ({
showPrice,
titleRows,
}) => {
const { contexts: { ProductContext } } = useContext(ThemeContext);

let ProductCard = ProductCardBase;

// Show a placeholder if product is not yet available.
Expand All @@ -32,20 +35,29 @@ const Item = ({
return (
<div className={styles.defaultSliderItem}>
<Card className={styles.defaultSliderCard}>
<ProductCard
product={product}
hideName={!showName}
hidePrice={!showPrice}
hideRating
titleRows={titleRows}
/>
{/** Add context with current product. Image component and image badges using it */}
<ProductContext.Provider
value={{
productId: product ? product.id : null,
}}
>
<ProductCard
product={product}
hideName={!showName}
hidePrice={!showPrice}
hideRating
titleRows={titleRows}
/>
</ProductContext.Provider>
</Card>
</div>
);
};

Item.propTypes = {
product: PropTypes.shape({}),
product: PropTypes.shape({
id: PropTypes.string.isRequired,
}),
showName: PropTypes.bool,
showPrice: PropTypes.bool,
titleRows: PropTypes.number,
Expand Down
68 changes: 29 additions & 39 deletions frontend/components/PDPSheet/index.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import React from 'react';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';

let mockedCurrentBaseProductId = null;
jest.mock('@shopgate/pwa-common-commerce/product/selectors/product', () => ({
getBaseProductId: () => mockedCurrentBaseProductId,
import PDPSheet from './index';

let mockedType = '';
let mockedHeadline = '';
jest.mock('../../helpers/getConfig', () => () => ({
productPageAddToCart: {
get type() { return mockedType; },
get headline() { return mockedHeadline; },
},
}));

const mockedConfig = {
productPageAddToCart: {},
};
jest.mock('../../helpers/getConfig', () => () => mockedConfig);

// eslint-disable-next-line require-jsdoc
const MockedSheet = () => (<div>Hello world</div>);
jest.mock('../Sheet', () => MockedSheet);
jest.mock('../Sheet', () => function Sheet() {
return null;
});

const mockedPDPAddToCartSuccessSubscribe = jest.fn();
jest.mock('../../streams', () => ({
Expand All @@ -37,33 +34,26 @@ jest.mock('@shopgate/pwa-common-commerce/product/actions/fetchProductRelations',
return { type: 'MOCKED_GET_PRODUCT_RELATIONS' };
});

jest.mock('../connectors', () => ({
makeConnectProductWithRelations: () => PDPSheetComponent => PDPSheetComponent,
}));

describe('PDPSheet', () => {
// eslint-disable-next-line require-jsdoc
const makeComponent = () => {
const PDPSheet = require.requireActual('./index').default;
return mount((
<Provider store={configureStore()({})}>
<PDPSheet />
</Provider>
));
};
const dispatch = jest.fn();

beforeEach(() => {
mockedPDPAddToCartSuccessSubscribe.mockReset();
mockedRouteDidChangeSubscribe.mockReset();
});

it('should do nothing when productId is missing', () => {
mockedCurrentBaseProductId = null;
mockedConfig.productPageAddToCart = {
type: 'mockedType',
headline: 'mockedHeadline',
};
mockedType = 'mockedType';
mockedHeadline = 'mockedHeadline';

const component = makeComponent();
const component = mount(<PDPSheet dispatch={dispatch} />);
const instance = component.find('PDPSheet').instance();

expect(component.html()).toBe('');
expect(component.html()).toBe(null);
expect(instance.state.disabled).toBe(false);
expect(instance.state.isOpen).toBe(false);
expect(mockedPDPAddToCartSuccessSubscribe).toHaveBeenCalled();
Expand All @@ -83,12 +73,11 @@ describe('PDPSheet', () => {
});

it('should do nothing when disabled', () => {
mockedCurrentBaseProductId = 'mockedId';
mockedConfig.productPageAddToCart.type = null;
const component = makeComponent();
mockedType = null;
const component = mount(<PDPSheet productId="mockedId" dispatch={dispatch} />);
const instance = component.find('PDPSheet').instance();

expect(component.html()).toBe('');
expect(component.html()).toBe(null);
expect(instance.state.disabled).toBe(true);
expect(instance.state.isOpen).toBe(false);
expect(mockedPDPAddToCartSuccessSubscribe).not.toHaveBeenCalled();
Expand All @@ -109,12 +98,13 @@ describe('PDPSheet', () => {
let addToCartSuccessFunc;

it('should render Sheet and update when product changes', () => {
mockedCurrentBaseProductId = 'mockedId';
mockedConfig.productPageAddToCart.type = 'mockedType';
component = makeComponent();
mockedType = 'mockedType';
mockedHeadline = 'mockedHeadline';

component = mount(<PDPSheet productId="mockedId" dispatch={dispatch} />);
instance = component.find('PDPSheet').instance();

expect(component.find('MockedSheet').exists()).toBe(true);
expect(component.find('Sheet').exists()).toBe(true);
expect(instance.state.disabled).toBe(false);
expect(instance.state.isOpen).toBe(false);
expect(mockedGetProductRelations).toHaveBeenCalled();
Expand Down
100 changes: 22 additions & 78 deletions frontend/components/PDPSlider/__snapshots__/index.spec.jsx.snap
Original file line number Diff line number Diff line change
@@ -1,87 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`PDPSlider should render with price and names 1`] = `
<Provider
store={
Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
}
}
<PDPSlider
productId="mockedProductId"
>
<Component>
<PDPSlider
productId="mockedProductId"
>
<Component
headline="mockedHeadline"
productId="mockedProductId"
showName={true}
showPrice={true}
titleRows={2}
type="mockedType"
>
<MockedDummyComponent
headline="mockedHeadline"
productId="mockedProductId"
showName={true}
showPrice={true}
titleRows={2}
type="mockedType"
>
<div>
Mocked Slider
</div>
</MockedDummyComponent>
</Component>
</PDPSlider>
</Component>
</Provider>
<Slider
headline="mockedHeadline"
productId="mockedProductId"
showName={true}
showPrice={true}
titleRows={2}
type="mockedType"
/>
</PDPSlider>
`;

exports[`PDPSlider should render without price and names as default 1`] = `
<Provider
store={
Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
}
}
<PDPSlider
productId="mockedProductId"
>
<Component>
<PDPSlider
productId="mockedProductId"
>
<Component
headline="mockedHeadline"
productId="mockedProductId"
showName={false}
showPrice={false}
titleRows={2}
type="mockedType"
>
<MockedDummyComponent
headline="mockedHeadline"
productId="mockedProductId"
showName={false}
showPrice={false}
titleRows={2}
type="mockedType"
>
<div>
Mocked Slider
</div>
</MockedDummyComponent>
</Component>
</PDPSlider>
</Component>
</Provider>
<Slider
headline="mockedHeadline"
productId="mockedProductId"
showName={false}
showPrice={false}
titleRows={2}
type="mockedType"
/>
</PDPSlider>
`;
77 changes: 26 additions & 51 deletions frontend/components/PDPSlider/index.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,50 @@
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import { mount } from 'enzyme';
import getConfig from '../../helpers/getConfig';
import PDPSlider from './index';

const mockedConfig = {
jest.mock('react', () => ({
...jest.requireActual('react'),
memo: cmp => cmp,
}));
let mockedShowPrice = true;
let mockedShowName = true;
jest.mock('../../helpers/getConfig', () => () => ({
productPage: {
type: 'mockedType',
headline: 'mockedHeadline',
showPrice: true,
showName: true,
get showPrice() { return mockedShowPrice; },
get showName() { return mockedShowName; },
},
};

jest.mock('../../helpers/getConfig', () => () => mockedConfig);
// eslint-disable-next-line require-jsdoc, react/prop-types
const MockedDummyComponent = props => (<div>{props.children}</div>);

jest.mock('../Slider', () => props => ((
<MockedDummyComponent {...props}>
Mocked Slider
</MockedDummyComponent>
)));

let mockedProductId = 'mockedProductId';
jest.mock('@shopgate/pwa-common-commerce/product/selectors/product', () => ({
getCurrentBaseProductId: () => mockedProductId,
}));

jest.mock('@shopgate-ps/pwa-extension-kit/connectors', () => ({
withPageProductId: ({ WrappedComponent }) => props =>
<WrappedComponent productId={mockedProductId} {...props} />,
jest.mock('../Slider', () => function Slider() {
return null;
});
jest.mock('../connectors', () => ({
makeConnectProductWithRelations: () => PDPSheetComponent => PDPSheetComponent,
}));

describe('PDPSlider', () => {
// eslint-disable-next-line global-require
const PDPSlider = require('./index').default;
// eslint-disable-next-line require-jsdoc
const makeComponent = () => mount((
<Provider store={configureStore()({})}>
<PDPSlider />
</Provider>
));

it('should render with price and names', () => {
const component = makeComponent();
const component = mount(<PDPSlider productId="mockedProductId" />);

expect(component.find('PDPSlider').props().productId).toBe(mockedProductId);
expect(component.find('MockedDummyComponent').props()).toMatchObject(mockedConfig.productPage);
// Component should never update once rendered something.
expect(component.find('PDPSlider').instance().shouldComponentUpdate()).toBe(false);
expect(component.find('PDPSlider').props().productId).toBe('mockedProductId');
expect(component.find('Slider').props()).toMatchObject(getConfig().productPage);
expect(component).toMatchSnapshot();
});

it('should render without price and names as default', () => {
delete mockedConfig.productPage.showName;
delete mockedConfig.productPage.showPrice;
const component = makeComponent();
mockedShowPrice = null;
mockedShowName = null;
const component = mount(<PDPSlider productId="mockedProductId" />);

expect(component.find('PDPSlider').props().productId).toBe(mockedProductId);
expect(component.find('MockedDummyComponent').props()).toMatchObject(mockedConfig.productPage);
expect(component.find('MockedDummyComponent').props().showPrice).toBe(false);
expect(component.find('MockedDummyComponent').props().showName).toBe(false);
expect(component.find('Slider').props().showPrice).toBe(false);
expect(component.find('Slider').props().showName).toBe(false);
expect(component).toMatchSnapshot();
});

it('should render nothing when productId is not ready', () => {
mockedProductId = undefined;
const component = makeComponent();

expect(component.html()).toBe('');
expect(component.find('PDPSlider').instance().shouldComponentUpdate()).toBe(true);
expect(component.find('PDPSlider').instance().shouldComponentUpdate()).toBe(true);
const component = mount(<PDPSlider />);
expect(component.html()).toBe(null);
});
});

0 comments on commit 0fcc7a0

Please sign in to comment.