Skip to content
This repository has been archived by the owner on Mar 3, 2023. It is now read-only.

Commit

Permalink
Migrate cartlinequantityadjustbutton (#180)
Browse files Browse the repository at this point in the history
* Start migration of CartLineQuantityAdjustButton

Co-Authored-By: Jason Kurian <2642545+JaKXz@users.noreply.github.com>

* Get CartLineQuantity typescript tests passing

* Update tests for CartLineQuantityAdjustButton to use RTL

Fix a couple instances of unnecessary "data-testId" camel casing.

Update BuyNowButton tests to use a shared utility

Co-Authored-By: Jason Kurian <2642545+JaKXz@users.noreply.github.com>

* Add attributes to linesUpdate() so that they're not lost

When adjusting the quantity.

* Add documentation for the components

* Update docs icons

* Small update to PR template

* Allow dev to disable manually if they want.

* Make the typing DX better for these keys

---------

Co-authored-by: Jason Kurian <2642545+JaKXz@users.noreply.github.com>
  • Loading branch information
frehner and JaKXz committed Feb 14, 2023
1 parent ee2d134 commit 2bb8c81
Show file tree
Hide file tree
Showing 20 changed files with 902 additions and 214 deletions.
13 changes: 13 additions & 0 deletions .changeset/sharp-walls-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@shopify/hydrogen-react': patch
---

Adding `<CartLineQuantity />` and `<CartLineQuantityAdjustButton />`

The `<CartLineQuantity />` and `<CartLineQuantityAdjustButton />` components have been added / migrated over from Hydrogen v1.

Additionally, fixed a bug when using `<CartLineQuantityAdjustButton />` that caused CartLine Attributes to be erased. CartLine Attributes should now be persisted when using that component.

## `useCartLine()` TypeScript types update

`useCartLine()`'s TypeScript type originally returned a `CartLine`. It has now been updated to be `PartialDeep<CartLine>`, which makes all the properties optional instead of required. This matches with the rest of hydrogen-react in that we can't know or guarnatee what properties exist on certain objects so we reflect that state in the TypeScript types.
4 changes: 2 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

### Before submitting the PR, please make sure you do the following:

- [ ] Read the [Contributing Guidelines](https://github.com/shopify/hydrogen-react/blob/main/contributing.md)
- [ ] Read the [Contributing Guidelines](https://github.com/Shopify/hydrogen-react/blob/main/CONTRIBUTING.md)
- [ ] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`)
- [ ] Update docs in this repository according to your change
- [ ] Update docs in this repository according to your change, and run `yarn build-docs` in the `packages/react` folder.
- [ ] Run `yarn changeset add` if this PR cause a version bump based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). If you have a breaking change, it will need to wait until the next major version release. Otherwise, use patch updates even for new features. Read [more about Hydrogen React's versioning](https://github.com/shopify/hydrogen-react/blob/main/readme.md#versioning).
491 changes: 312 additions & 179 deletions packages/react/docs/generated/generated_docs_data.json

Large diffs are not rendered by default.

34 changes: 11 additions & 23 deletions packages/react/src/BuyNowButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,10 @@ import {render, screen} from '@testing-library/react';
import {vi} from 'vitest';
import userEvent from '@testing-library/user-event';
import {BuyNowButton} from './BuyNowButton.js';
import {getCartWithActionsMock} from './CartProvider.test.helpers.js';

vi.mock('./CartProvider');

const defaultCart = {
buyerIdentityUpdate: vi.fn(),
cartAttributesUpdate: vi.fn(),
cartCreate: vi.fn(),
cartFragment: '',
checkoutUrl: '',
discountCodesUpdate: vi.fn(),
linesAdd: vi.fn(),
linesRemove: vi.fn(),
linesUpdate: vi.fn(),
noteUpdate: vi.fn(),
status: 'idle' as const,
totalQuantity: 0,
};

describe('<BuyNowButton/>', () => {
it('renders a button', () => {
render(<BuyNowButton variantId="1">Buy now</BuyNowButton>, {
Expand Down Expand Up @@ -59,10 +45,11 @@ describe('<BuyNowButton/>', () => {
it('uses useCartCreateCallback with the correct arguments', async () => {
const mockCartCreate = vi.fn();

vi.mocked(useCart).mockImplementation(() => ({
...defaultCart,
cartCreate: mockCartCreate,
}));
vi.mocked(useCart).mockImplementation(() =>
getCartWithActionsMock({
cartCreate: mockCartCreate,
})
);

const user = userEvent.setup();

Expand Down Expand Up @@ -133,10 +120,11 @@ describe('<BuyNowButton/>', () => {
});

it('redirects to checkout', () => {
vi.mocked(useCart).mockImplementation(() => ({
...defaultCart,
checkoutUrl: '/checkout?id=123',
}));
vi.mocked(useCart).mockImplementation(() =>
getCartWithActionsMock({
checkoutUrl: '/checkout?id=123',
})
);

render(<BuyNowButton variantId="1">Buy now</BuyNowButton>, {
wrapper: CartProvider,
Expand Down
5 changes: 1 addition & 4 deletions packages/react/src/CartLineProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {renderHook} from '@testing-library/react';
import {CartLine} from './storefront-api-types.js';
import {useCartLine, CartLineProvider} from './CartLineProvider.js';
import {getCartLineMock} from './CartProvider.test.helpers.js';

Expand All @@ -8,9 +7,7 @@ it('provides a hook to access cart line data', () => {

const {result} = renderHook(() => useCartLine(), {
wrapper: ({children}) => (
<CartLineProvider line={cartLine as CartLine}>
{children}
</CartLineProvider>
<CartLineProvider line={cartLine}>{children}</CartLineProvider>
),
});

Expand Down
9 changes: 6 additions & 3 deletions packages/react/src/CartLineProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import {useContext, createContext, type ReactNode} from 'react';
import type {CartLine} from './storefront-api-types.js';
import type {PartialDeep} from 'type-fest';

export const CartLineContext = createContext<CartLine | null>(null);
type CartLinePartialDeep = PartialDeep<CartLine, {recurseIntoArrays: true}>;

export const CartLineContext = createContext<CartLinePartialDeep | null>(null);

/**
* The `useCartLine` hook provides access to the [CartLine object](https://shopify.dev/api/storefront/unstable/objects/cartline) from the Storefront API. It must be a descendent of a `CartProvider` component.
*/
export function useCartLine(): CartLine {
export function useCartLine(): CartLinePartialDeep {
const context = useContext(CartLineContext);

if (context == null) {
Expand All @@ -20,7 +23,7 @@ type CartLineProviderProps = {
/** Any `ReactNode` elements. */
children: ReactNode;
/** A cart line object. */
line: CartLine;
line: CartLinePartialDeep;
};

/**
Expand Down
50 changes: 50 additions & 0 deletions packages/react/src/CartLineQuantity.doc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';

const data: ReferenceEntityTemplateSchema = {
name: 'CartLineQuantity',
category: 'components',
isVisualComponent: false,
related: [
{
name: 'useCartLine',
type: 'gear',
url: '/api/hydrogen-react/hooks/useCartLine',
},
{
name: 'CartLineQuantityAdjustButton',
type: 'component',
url: '/api/hydrogen-react/components/CartLineQuantityAdjustButton',
},
],
description: `
The \`<CartLineQuantity/>\` component renders a \`span\` (or another element / component that can be customized by the \`as\` prop) with the cart line's quantity.\n\nIt must be a descendent of a \`<CartLineProvider/>\` component, and uses the \`useCartLine()\` hook internally.
`,
type: 'component',
defaultExample: {
description: 'I am the default example',
codeblock: {
tabs: [
{
title: 'JavaScript',
code: './CartLineQuantity.example.jsx',
language: 'jsx',
},
{
title: 'TypeScript',
code: './CartLineQuantity.example.tsx',
language: 'tsx',
},
],
title: 'Example code',
},
},
definitions: [
{
title: 'Props',
type: 'CartLineQuantityBaseProps',
description: '',
},
],
};

export default data;
9 changes: 9 additions & 0 deletions packages/react/src/CartLineQuantity.example.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {CartLineQuantity, CartLineProvider} from '@shopify/hydrogen-react';

export function Example({line}) {
return (
<CartLineProvider line={line}>
<CartLineQuantity />
</CartLineProvider>
);
}
10 changes: 10 additions & 0 deletions packages/react/src/CartLineQuantity.example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {CartLineQuantity, CartLineProvider} from '@shopify/hydrogen-react';
import type {CartLine} from '@shopify/hydrogen-react/storefront-api-types';

export function Example({line}: {line: CartLine}) {
return (
<CartLineProvider line={line}>
<CartLineQuantity />
</CartLineProvider>
);
}
59 changes: 59 additions & 0 deletions packages/react/src/CartLineQuantity.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {render, screen} from '@testing-library/react';
import {CartLineProvider} from './CartLineProvider.js';
import {CartLineQuantity} from './CartLineQuantity.js';
import {CART_LINE} from './CartProvider.test.helpers.js';

describe('<CartLineQuantity />', () => {
it('displays the quantity', () => {
render(
<CartLineProvider line={CART_LINE}>
<CartLineQuantity />
</CartLineProvider>
);

expect(screen.getByText(CART_LINE?.quantity ?? '')).toBeInTheDocument();
});

it('allows a custom tag', () => {
render(
<CartLineProvider line={CART_LINE}>
<CartLineQuantity as="p" />
</CartLineProvider>
);

const quantity = screen.getByText(CART_LINE?.quantity ?? '');

expect(quantity).toBeInTheDocument();
expect(quantity.tagName).toBe('P');
});

describe(`typescript validation`, () => {
it.skip(`validates props for a component passed to the 'as' prop`, () => {
expect.assertions(0);
render(
<CartLineProvider line={CART_LINE}>
<CartLineQuantity as={FakeComponentWithRequiredProp} testing />
</CartLineProvider>
);
});

it.skip(`typescript validation: validates props for a component passed to the 'as' prop`, () => {
expect.assertions(0);
render(
<CartLineProvider line={CART_LINE}>
<CartLineQuantity
as={FakeComponentWithRequiredProp}
// @ts-expect-error Testing should be a boolean
testing="alsdkjf"
/>
</CartLineProvider>
);
});
});
});

const FAKECOMPONENTID = 'fake-component';

function FakeComponentWithRequiredProp({testing}: {testing: boolean}) {
return <div data-testid={FAKECOMPONENTID}>{testing}</div>;
}
32 changes: 32 additions & 0 deletions packages/react/src/CartLineQuantity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type {ComponentPropsWithoutRef, ElementType} from 'react';
import {useCartLine} from './CartLineProvider.js';

interface CartLineQuantityBaseProps<
ComponentGeneric extends ElementType = 'span'
> {
/** An HTML tag or React Component to be rendered as the base element wrapper. The default is `span`. */
as?: ComponentGeneric;
}

export type CartLineQuantityProps<ComponentGeneric extends ElementType> =
CartLineQuantityBaseProps<ComponentGeneric> &
Omit<
ComponentPropsWithoutRef<ComponentGeneric>,
keyof CartLineQuantityBaseProps<ComponentGeneric>
>;

/**
* The `<CartLineQuantity/>` component renders a `span` (or another element / component that can be customized by the `as` prop) with the cart line's quantity.
*
* It must be a descendent of a `<CartLineProvider/>` component, and uses the `useCartLine()` hook internally.
*/
export function CartLineQuantity<ComponentGeneric extends ElementType = 'span'>(
props: CartLineQuantityProps<ComponentGeneric>
): JSX.Element {
const cartLine = useCartLine();
const {as, ...passthroughProps} = props;

const Wrapper = as ? as : 'span';

return <Wrapper {...passthroughProps}>{cartLine.quantity}</Wrapper>;
}
50 changes: 50 additions & 0 deletions packages/react/src/CartLineQuantityAdjustButton.doc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';

const data: ReferenceEntityTemplateSchema = {
name: 'CartLineQuantityAdjustButton',
category: 'components',
isVisualComponent: false,
related: [
{
name: 'useCartLine',
type: 'gear',
url: '/api/hydrogen-react/hooks/useCartLine',
},
{
name: 'CartLineQuantity',
type: 'component',
url: '/api/hydrogen-react/components/CartLineQuantity',
},
],
description: `
The \`<CartLineQuantityAdjustButton/>\` component renders a \`span\` (or another element / component that can be customized by the \`as\` prop) with the cart line's quantity.\n\nIt must be a descendent of a \`<CartLineProvider/>\` component, and uses the \`useCartLine()\` hook internally.
`,
type: 'component',
defaultExample: {
description: 'I am the default example',
codeblock: {
tabs: [
{
title: 'JavaScript',
code: './CartLineQuantityAdjustButton.example.jsx',
language: 'jsx',
},
{
title: 'TypeScript',
code: './CartLineQuantityAdjustButton.example.tsx',
language: 'tsx',
},
],
title: 'Example code',
},
},
definitions: [
{
title: 'Props',
type: 'CartLineQuantityAdjustButtonBaseProps',
description: '',
},
],
};

export default data;
23 changes: 23 additions & 0 deletions packages/react/src/CartLineQuantityAdjustButton.example.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {
CartLineQuantityAdjustButton,
CartLineProvider,
CartProvider,
} from '@shopify/hydrogen-react';

export function Example({line}) {
return (
<CartProvider>
<CartLineProvider line={line}>
<CartLineQuantityAdjustButton adjust="increase">
Increase
</CartLineQuantityAdjustButton>
<CartLineQuantityAdjustButton adjust="decrease">
Decrease
</CartLineQuantityAdjustButton>
<CartLineQuantityAdjustButton adjust="remove">
Remove
</CartLineQuantityAdjustButton>
</CartLineProvider>
</CartProvider>
);
}
24 changes: 24 additions & 0 deletions packages/react/src/CartLineQuantityAdjustButton.example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
CartLineQuantityAdjustButton,
CartLineProvider,
CartProvider,
} from '@shopify/hydrogen-react';
import type {CartLine} from '@shopify/hydrogen-react/storefront-api-types';

export function Example({line}: {line: CartLine}) {
return (
<CartProvider>
<CartLineProvider line={line}>
<CartLineQuantityAdjustButton adjust="increase">
Increase
</CartLineQuantityAdjustButton>
<CartLineQuantityAdjustButton adjust="decrease">
Decrease
</CartLineQuantityAdjustButton>
<CartLineQuantityAdjustButton adjust="remove">
Remove
</CartLineQuantityAdjustButton>
</CartLineProvider>
</CartProvider>
);
}

0 comments on commit 2bb8c81

Please sign in to comment.