Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
144 changes: 144 additions & 0 deletions .changeset/silent-pianos-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
"@bigcommerce/catalyst-core": patch
---

Fix WishlistDetails page from exceeding GraphQL complexity limit, and fix wishlist e2e tests.

Additionally, add the `required` prop to `core/components/wishlist/modals/new.tsx` and `core/components/wishlist/modals/rename.tsx`

## Migration

### Step 1: Update wishlist GraphQL fragments

In `core/components/wishlist/fragment.ts`, replace the `WishlistItemProductFragment` to use explicit fields instead of `ProductCardFragment`:

```typescript
export const WishlistItemProductFragment = graphql(
`
fragment WishlistItemProductFragment on Product {
entityId
name
defaultImage {
altText
url: urlTemplate(lossy: true)
}
path
brand {
name
path
}
reviewSummary {
numberOfReviews
averageRating
}
sku
showCartAction
inventory {
isInStock
}
availabilityV2 {
status
}
...PricingFragment
}
`,
[PricingFragment],
);
```

Remove `ProductCardFragment` from all fragment dependencies in the same file.

### Step 2: Update product card transformer

In `core/data-transformers/product-card-transformer.ts`:

1. Import the `WishlistItemProductFragment`:
```typescript
import { WishlistItemProductFragment } from '~/components/wishlist/fragment';
```

2. Update the `singleProductCardTransformer` function signature to accept both fragment types:
```typescript
product: ResultOf<typeof ProductCardFragment | typeof WishlistItemProductFragment>
```

3. Add a conditional check for the `inventoryMessage` field:
```typescript
inventoryMessage:
'variants' in product
? getInventoryMessage(product, outOfStockMessage, showBackorderMessage)
: undefined,
```

4. Update the `productCardTransformer` function signature similarly:
```typescript
products: Array<ResultOf<typeof ProductCardFragment | typeof WishlistItemProductFragment>>
```

### Step 3: Fix wishlist e2e tests

In `core/tests/ui/e2e/account/wishlists.spec.ts`, update label selectors to use `{ exact: true }` for specificity:

Update all locators for the wishlist name input selectors:
```diff
- page.getByLabel(t('Form.nameLabel'))
+ page.getByLabel(t('Form.nameLabel'), { exact: true })
```

### Step 4: Fix mobile wishlist e2e tests

In `core/tests/ui/e2e/account/wishlists.mobile.spec.ts`, update translation calls to use namespace prefixes:

1. Update the translation initialization:
```diff
- const t = await getTranslations('Account.Wishlist');
+ const t = await getTranslations();
```

2. Update all translation keys to include the namespace:
```diff
- await locator.getByRole('button', { name: t('actionsTitle') }).click();
- await page.getByRole('menuitem', { name: t('share') }).click();
+ await locator.getByRole('button', { name: t('Wishlist.actionsTitle') }).click();
+ await page.getByRole('menuitem', { name: t('Wishlist.share') }).click();
```

```diff
- await expect(page.getByText(t('shareSuccess'))).toBeVisible();
+ await expect(page.getByText(t('Wishlist.shareSuccess'))).toBeVisible();
```

### Step 5: Add `required` prop to wishlist modals

Update the modal forms to include the `required` prop on the name input field:

In `core/components/wishlist/modals/new.tsx`:
```diff
<Input
{...getInputProps(fields.wishlistName, { type: 'text' })}
defaultValue={defaultValue.current}
errors={fields.wishlistName.errors}
key={fields.wishlistName.id}
label={nameLabel}
onChange={(e) => {
defaultValue.current = e.target.value;
}}
+ required
/>
```

In `core/components/wishlist/modals/rename.tsx`:
```diff
<Input
{...getInputProps(fields.wishlistName, { type: 'text' })}
defaultValue={defaultValue.current}
errors={fields.wishlistName.errors}
key={fields.wishlistName.id}
label={nameLabel}
onChange={(e) => {
defaultValue.current = e.target.value;
}}
+ required
/>
```

31 changes: 23 additions & 8 deletions core/components/wishlist/fragment.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import { PaginationFragment } from '~/client/fragments/pagination';
import { PricingFragment } from '~/client/fragments/pricing';
import { graphql } from '~/client/graphql';
import { ProductCardFragment } from '~/components/product-card/fragment';

export const WishlistItemProductFragment = graphql(
`
fragment WishlistItemProductFragment on Product {
...ProductCardFragment
entityId
name
defaultImage {
altText
url: urlTemplate(lossy: true)
}
path
brand {
name
path
}
reviewSummary {
numberOfReviews
averageRating
}
sku
showCartAction
inventory {
Expand All @@ -14,9 +28,10 @@ export const WishlistItemProductFragment = graphql(
availabilityV2 {
status
}
...PricingFragment
}
`,
[ProductCardFragment],
[PricingFragment],
);

export const WishlistItemFragment = graphql(
Expand All @@ -30,7 +45,7 @@ export const WishlistItemFragment = graphql(
}
}
`,
[WishlistItemProductFragment, ProductCardFragment],
[WishlistItemProductFragment],
);

export const WishlistFragment = graphql(
Expand All @@ -52,7 +67,7 @@ export const WishlistFragment = graphql(
}
}
`,
[WishlistItemFragment, ProductCardFragment],
[WishlistItemFragment],
);

export const WishlistsFragment = graphql(
Expand All @@ -68,7 +83,7 @@ export const WishlistsFragment = graphql(
}
}
`,
[WishlistFragment, ProductCardFragment, PaginationFragment],
[WishlistFragment, PaginationFragment],
);

export const WishlistPaginatedItemsFragment = graphql(
Expand All @@ -93,7 +108,7 @@ export const WishlistPaginatedItemsFragment = graphql(
}
}
`,
[WishlistItemFragment, ProductCardFragment, PaginationFragment],
[WishlistItemFragment, PaginationFragment],
);

export const PublicWishlistFragment = graphql(
Expand All @@ -117,5 +132,5 @@ export const PublicWishlistFragment = graphql(
}
}
`,
[WishlistItemFragment, ProductCardFragment, PaginationFragment],
[WishlistItemFragment, PaginationFragment],
);
1 change: 1 addition & 0 deletions core/components/wishlist/modals/new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const NewWishlistModal = ({
onChange={(e) => {
defaultValue.current = e.target.value;
}}
required
/>
{state.lastResult?.status === 'error' && (
<div className="mt-4">
Expand Down
1 change: 1 addition & 0 deletions core/components/wishlist/modals/rename.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const RenameWishlistModal = ({
onChange={(e) => {
defaultValue.current = e.target.value;
}}
required
/>
{state.lastResult?.status === 'error' && (
<div className="mt-4">
Expand Down
10 changes: 7 additions & 3 deletions core/data-transformers/product-card-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getFormatter } from 'next-intl/server';
import { Product } from '@/vibes/soul/primitives/product-card';
import { ExistingResultType } from '~/client/util';
import { ProductCardFragment } from '~/components/product-card/fragment';
import { WishlistItemProductFragment } from '~/components/wishlist/fragment';

import { pricesTransformer } from './prices-transformer';

Expand Down Expand Up @@ -46,7 +47,7 @@ const getInventoryMessage = (
};

export const singleProductCardTransformer = (
product: ResultOf<typeof ProductCardFragment>,
product: ResultOf<typeof ProductCardFragment | typeof WishlistItemProductFragment>,
format: ExistingResultType<typeof getFormatter>,
outOfStockMessage?: string,
showBackorderMessage?: boolean,
Expand All @@ -62,12 +63,15 @@ export const singleProductCardTransformer = (
subtitle: product.brand?.name ?? undefined,
rating: product.reviewSummary.averageRating,
numberOfReviews: product.reviewSummary.numberOfReviews,
inventoryMessage: getInventoryMessage(product, outOfStockMessage, showBackorderMessage),
inventoryMessage:
'variants' in product
? getInventoryMessage(product, outOfStockMessage, showBackorderMessage)
: undefined,
};
};

export const productCardTransformer = (
products: Array<ResultOf<typeof ProductCardFragment>>,
products: Array<ResultOf<typeof ProductCardFragment | typeof WishlistItemProductFragment>>,
format: ExistingResultType<typeof getFormatter>,
outOfStockMessage?: string,
showBackorderMessage?: boolean,
Expand Down
9 changes: 4 additions & 5 deletions core/tests/ui/e2e/account/wishlists.mobile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,21 @@ test('Share button calls navigator.share with the correct URL', async ({ page, c
};
});

const t = await getTranslations('Wishlist');
const t = await getTranslations();
const { id: customerId } = await customer.login();
const { name, token } = await customer.createWishlist({
customerId,
isPublic: true,
});

await page.goto('/account/wishlists/');
await expect(page.getByRole('heading', { name: t('title'), exact: true })).toBeVisible();

const locator = page.getByRole('region', { name });

await locator.getByRole('button', { name: t('actionsTitle') }).click();
await page.getByRole('menuitem', { name: t('share') }).click();
await locator.getByRole('button', { name: t('Wishlist.actionsTitle') }).click();
await page.getByRole('menuitem', { name: t('Wishlist.share') }).click();

await expect(page.getByText(t('shareSuccess'))).toBeVisible();
await expect(page.getByText(t('Wishlist.shareSuccess'))).toBeVisible();

const expectedUrl = `${testEnv.PLAYWRIGHT_TEST_BASE_URL}/wishlist/${token}`;

Expand Down
8 changes: 4 additions & 4 deletions core/tests/ui/e2e/account/wishlists.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,11 @@ test.describe('Wishlist actions menu', () => {
page.getByRole('heading', { name: t('Modal.renameTitle', { name }) }),
).toBeVisible();

await expect(page.getByLabel(t('Form.nameLabel'))).toHaveValue(name);
await expect(page.getByLabel(t('Form.nameLabel'), { exact: true })).toHaveValue(name);

const newName = `${name} (renamed)`;

await page.getByLabel(t('Form.nameLabel')).fill(newName);
await page.getByLabel(t('Form.nameLabel'), { exact: true }).fill(newName);
await page.getByRole('button', { name: t('Modal.save') }).click();

await expect(page.getByText(t('Result.updateSuccess'))).toBeVisible();
Expand All @@ -258,7 +258,7 @@ test.describe('Wishlist actions menu', () => {
await locator.getByRole('button', { name: t('actionsTitle') }).click();
await page.getByRole('menuitem', { name: t('rename') }).click();

await page.getByLabel(t('Form.nameLabel')).fill('');
await page.getByLabel(t('Form.nameLabel'), { exact: true }).fill('');
await page.getByRole('button', { name: t('Modal.save') }).click();

await expect(page.getByText(t('Errors.nameRequired'))).toBeVisible();
Expand All @@ -282,7 +282,7 @@ test.describe('Wishlist actions menu', () => {

await locator.getByRole('button', { name: t('actionsTitle') }).click();
await page.getByRole('menuitem', { name: t('rename') }).click();
await page.getByLabel(t('Form.nameLabel')).fill(`${name} (renamed)`);
await page.getByLabel(t('Form.nameLabel'), { exact: true }).fill(`${name} (renamed)`);
await page.getByRole('button', { name: t('Modal.save') }).click();

await expect(page.getByText(t('Errors.unauthorized'))).toBeVisible();
Expand Down