Skip to content
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

[PWA-1124] Browsing and Viewing Virtual product #3052

Merged
merged 13 commits into from
Mar 22, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ const jestConfig = {
path.join('<rootDir>', 'scripts', 'jest-backend-setup.js')
]
})),
configureProject(
'extensions/venia-virtual-products',
'Venia Virtual Products',
inPackage => testReactComponents(inPackage)
),
configureProject(
'extensions/venia-sample-payments-checkmo',
'Check Money Order Payment',
Expand Down
26 changes: 26 additions & 0 deletions packages/extensions/venia-virtual-products/intercept.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = targets => {
targets.of('@magento/pwa-buildpack').specialFeatures.tap(features => {
features[targets.name] = {
cssModules: true,
esModules: true,
graphqlQueries: true
};
});

const { talons } = targets.of('@magento/peregrine');

talons.tap(hooks => {
const useCategory = hooks.RootComponents.Category.useCategory;
useCategory.wrapWith(
'@magento/venia-virtual-products/src/wrapUseCategory'
);
});

const { categoryListProductAttributes } = targets.of('@magento/venia-ui');
categoryListProductAttributes.tap(target => {
target.insertAfterJSX({
matcher: 'Link className={classes.name}',
importStatement: `import SubTypeAttribute from '@magento/venia-virtual-products/src/components/SubTypeAttribute'`
});
});
};
25 changes: 25 additions & 0 deletions packages/extensions/venia-virtual-products/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@magento/venia-virtual-products",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this implementation is not going to be the final core implementation it would make sense to follow the sample naming convention and not use up the logical name.

Suggested change
"name": "@magento/venia-virtual-products",
"name": "@magento/venia-sample-virtual-products",

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call out, I've gone ahead and made this change since this is really for reference given our specific sample data.

"version": "0.0.1",
"publishConfig": {
"access": "public"
},
"description": "Adds support for additional fields when rendering virtual products",
"main": "./intercept.js",
"scripts": {
"clean": " "
},
"repository": "github:magento/pwa-studio",
"license": "(OSL-3.0 OR AFL-3.0)",
"peerDependencies": {
"@apollo/client": "~3.1.2",
"@magento/peregrine": "~9.0.0",
"@magento/pwa-buildpack": "~8.0.1",
"@magento/venia-ui": "~6.0.1"
},
"pwa-studio": {
"targets": {
"intercept": "./intercept"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import wrapUseCategory from '../wrapUseCategory';

jest.mock('../category.gql', () => ({
GET_CATEGORY: jest.fn().mockName('GET_CATEGORY')
}));

test('injects custom query into talon props', () => {
const mockTalon = jest.fn();
wrapUseCategory(mockTalon)({ talonProp: 'should be untouched' });

expect(mockTalon.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"operations": Object {
"getCategoryQuery": [MockFunction GET_CATEGORY],
},
"talonProp": "should be untouched",
}
`);
});
35 changes: 35 additions & 0 deletions packages/extensions/venia-virtual-products/src/category.gql.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { gql } from '@apollo/client';

import {
CategoryFragment,
ProductsFragment
} from '@magento/peregrine/lib/talons/RootComponents/Category/categoryFragments.gql';

export const GET_CATEGORY = gql`
query GetCategoriesWithSubType(
$id: Int!
$pageSize: Int!
$currentPage: Int!
$filters: ProductAttributeFilterInput!
$sort: ProductAttributeSortInput
) {
category(id: $id) {
id
...CategoryFragment
}
products(
pageSize: $pageSize
currentPage: $currentPage
filter: $filters
sort: $sort
) {
items {
id
sub_type
}
...ProductsFragment
}
}
${CategoryFragment}
${ProductsFragment}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import { useQuery } from '@apollo/client';
import createTestInstance from '@magento/peregrine/lib/util/createTestInstance';

import SubTypeAttribute from '../subTypeAttribute';

jest.mock('@apollo/client', () => ({
gql: jest.fn(),
useQuery: jest.fn().mockReturnValue({})
}));

test('renders null without sub_type', () => {
const tree = createTestInstance(<SubTypeAttribute item={{ id: 1 }} />);

expect(tree.toJSON()).toMatchInlineSnapshot(`null`);
});

test('renders skeleton while fetching attribute metadata', () => {
const tree = createTestInstance(
<SubTypeAttribute item={{ id: 1, sub_type: 123 }} />
);

expect(tree.toJSON()).toMatchInlineSnapshot(`
<span
className="root"
/>
`);
});

test('renders attribute label with metadata', () => {
useQuery.mockReturnValue({
data: {
customAttributeMetadata: {
items: [
{
attribute_options: [
{ label: 'Consultation', value: 456 },
{ label: 'Service', value: 123 },
{ label: 'Donation', value: 789 }
]
}
]
}
}
});

const tree = createTestInstance(
<SubTypeAttribute item={{ id: 1, sub_type: 123 }} />
);

expect(tree.toJSON()).toMatchInlineSnapshot(`
<span
className="root"
>
Service
</span>
`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './subTypeAttribute';
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.root {
color: rgb(var(--venia-global-color-text-hint));
font-size: var(--venia-typography-detail-L-fontSize);
}

.root:empty {
background-color: rgb(var(--venia-global-color-gray));
min-height: 1rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { gql } from '@apollo/client';

const GET_ATTRIBUTE_METADATA = gql`
query GetSubtypeMetadata {
customAttributeMetadata(
attributes: [
{ attribute_code: "sub_type", entity_type: "catalog_product" }
]
) {
items {
attribute_options {
label
value
}
}
}
}
`;

export default {
getAttributeMetadataQuery: GET_ATTRIBUTE_METADATA
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useMemo } from 'react';

import classes from './subTypeAttribute.css';
import operations from './subTypeAttribute.gql';
import { useQuery } from '@apollo/client';

const SubTypeAttribute = props => {
const { item } = props;
const { sub_type } = item;

const { getAttributeMetadataQuery } = operations;
const { data } = useQuery(getAttributeMetadataQuery, {
skip: !sub_type
});

const attributeLabel = useMemo(() => {
if (!sub_type || !data) return null;

const attributeOptions =
data.customAttributeMetadata.items[0].attribute_options;
const attributeOption = attributeOptions.find(
attribute => attribute.value == sub_type
);

return attributeOption.label;
}, [data, sub_type]);

if (!sub_type) return null;

return <span className={classes.root}>{attributeLabel}</span>;
};

export default SubTypeAttribute;
14 changes: 14 additions & 0 deletions packages/extensions/venia-virtual-products/src/wrapUseCategory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { GET_CATEGORY } from './category.gql';

const wrapUseCategory = original => props => {
const newProps = {
...props,
operations: {
getCategoryQuery: GET_CATEGORY
supernova-at marked this conversation as resolved.
Show resolved Hide resolved
}
};

return original(newProps);
};

export default wrapUseCategory;
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,24 @@ test('sets isAddToCartDisabled true if add simple mutation is loading', () => {

expect(talonProps.isAddToCartDisabled).toBe(true);
});

test('returns correct value for supported product type', () => {
const tree = createTestInstance(<Component {...defaultProps} />);

const { root } = tree;
const { talonProps: talonProps1 } = root.findByType('i').props;

tree.update(
<Component
product={{
...defaultProps.product,
__typename: 'Unsupported Type'
}}
/>
);

const { talonProps: talonProps2 } = root.findByType('i').props;

expect(talonProps1.isSupportedProductType).toBe(true);
expect(talonProps2.isSupportedProductType).toBe(false);
});
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ const SUPPORTED_PRODUCT_TYPES = ['SimpleProduct', 'ConfigurableProduct'];
* handleSelectionChange: func,
* handleSetQuantity: func,
* isAddToCartDisabled: boolean,
* isSupportedProductType: boolean,
* mediaGalleryEntries: array,
* productDetails: object,
* quantity: number
Expand Down Expand Up @@ -315,10 +316,8 @@ export const useProductFullDetail = props => {
handleAddToCart,
handleSelectionChange,
isAddToCartDisabled:
!isSupportedProductType ||
isMissingOptions ||
isAddConfigurableLoading ||
isAddSimpleLoading,
isMissingOptions || isAddConfigurableLoading || isAddSimpleLoading,
isSupportedProductType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can list of supported product types be made a target in venia? Extensions can then add to this list.

supernova-at marked this conversation as resolved.
Show resolved Hide resolved
mediaGalleryEntries,
productDetails
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { gql } from '@apollo/client';

import { CategoryFragment, ProductsFragment } from './categoryFragments.gql';

export const GET_CATEGORY = gql`
query GetCategories(
$id: Int!
Expand All @@ -10,46 +12,19 @@ export const GET_CATEGORY = gql`
) {
category(id: $id) {
id
description
name
product_count
meta_title
meta_keywords
meta_description
...CategoryFragment
}
products(
pageSize: $pageSize
currentPage: $currentPage
filter: $filters
sort: $sort
) {
items {
# id is always required, even if the fragment includes it.
id
# TODO: Once this issue is resolved we can use a
# GalleryItemFragment here:
# https://github.com/magento/magento2/issues/28584
name
price {
regularPrice {
amount {
currency
value
}
}
}
small_image {
url
}
url_key
url_suffix
}
page_info {
total_pages
}
total_count
...ProductsFragment
}
}
${CategoryFragment}
${ProductsFragment}
`;

export const GET_FILTER_INPUTS = gql`
Expand Down
Loading