Skip to content

Commit

Permalink
Change: SKU Selector logic to prioritize available SKUs
Browse files Browse the repository at this point in the history
  • Loading branch information
beatrizmaselli authored Nov 6, 2024
1 parent 1a09940 commit 15c6c2e
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 56 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Changed

- Default selection logic in `SKU Selector` component to prioritize available SKUs.

## [3.175.1] - 2024-09-11

### Fixed
Expand Down
8 changes: 8 additions & 0 deletions react/__mocks__/vtex.render-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ const runtime = {
culture: { currency: 'USD' },
query: 'foo',
navigate: jest.fn(),
getSettings: () => ({
'vtex.store': {
storeName: 'Store Theme - VTEX Base Store',
titleTag: 'Store Theme - VTEX Base Store',
metaTagDescription: 'Store Theme - VTEX Base Store',
metaTagKeywords: 'store theme, vtex, store, vtex io, base store, vtex',
},
}),
}

export const withRuntimeContext = Comp =>
Expand Down
65 changes: 61 additions & 4 deletions react/__tests__/components/SKUSelector.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useProduct, ProductContext } from 'vtex.product-context'
import { getSKU } from 'sku-helper'

import SKUSelector from '../../components/SKUSelector/Wrapper'
import { orderItemsByAvailability } from '../../components/SKUSelector/components/SKUSelector'

describe('<SKUSelector />', () => {
const renderComponent = (customProps = {}) => {
Expand Down Expand Up @@ -33,7 +34,10 @@ describe('<SKUSelector />', () => {
})

it('should render the options an select one', async () => {
const defaultSeller = { commertialOffer: { Price: 15 } }
const defaultSeller = {
commertialOffer: { Price: 15, AvailableQuantity: 1 },
}

const skuItems = [
{
itemId: '1',
Expand Down Expand Up @@ -108,8 +112,48 @@ describe('<SKUSelector />', () => {
expect(getByText('41')).toBeDefined()
})

it('should be able to order by availability', async () => {
const defaultSeller1 = {
sellerDefault: true,
commertialOffer: { Price: 15, AvailableQuantity: 0, ListPrice: 200 },
}

const defaultSeller2 = {
sellerDefault: true,
commertialOffer: { Price: 15, AvailableQuantity: 1, ListPrice: 200 },
}

const skuItems = [
{
itemId: '1',
name: 'Gray Shoe',
variations: ['Size', 'Color'],
variationValues: { Size: '41', Color: 'Gray' },
sellers: [defaultSeller1],
images: [],
},
{
itemId: '2',
name: 'Gray Shoe',
variations: ['Size', 'Color'],
variationValues: { Size: '41', Color: 'Gray' },
sellers: [defaultSeller2],
images: [],
},
]

const possibleItemsOrderedByAvailability = skuItems.sort(
orderItemsByAvailability
)

expect(possibleItemsOrderedByAvailability[0].itemId).toEqual('2')
})

it('should render only three main variations', async () => {
const defaultSeller = { commertialOffer: { Price: 15 } }
const defaultSeller = {
commertialOffer: { Price: 15, AvailableQuantity: 1 },
}

const skuItems = [
{
itemId: '1',
Expand Down Expand Up @@ -173,7 +217,10 @@ describe('<SKUSelector />', () => {
})

it('should render show 8 items for variation and see more button', async () => {
const defaultSeller = { commertialOffer: { Price: 15 } }
const defaultSeller = {
commertialOffer: { Price: 15, AvailableQuantity: 1 },
}

const skuItems = [
{
itemId: '1',
Expand Down Expand Up @@ -637,7 +684,10 @@ describe('<SKUSelector />', () => {
})

it('should show all variations when count is inside threshold', async () => {
const defaultSeller = { commertialOffer: { Price: 15 } }
const defaultSeller = {
commertialOffer: { Price: 15, AvailableQuantity: 1 },
}

const skuItems = [
{
itemId: '1',
Expand Down Expand Up @@ -1410,6 +1460,10 @@ describe('<SKUSelector />', () => {
})

it('must order sku specification to be sorted in alphabetical order', async () => {
const defaultSeller = {
commertialOffer: { Price: 15, AvailableQuantity: 1 },
}

const skuItems = [
{
itemId: '1',
Expand All @@ -1419,6 +1473,7 @@ describe('<SKUSelector />', () => {
{ name: 'Color', values: ['Gray'] },
],
images: [],
sellers: [defaultSeller],
},
{
itemId: '4',
Expand All @@ -1431,6 +1486,7 @@ describe('<SKUSelector />', () => {
{ name: 'Color', values: ['Gray'] },
],
images: [],
sellers: [defaultSeller],
},
{
itemId: '4',
Expand All @@ -1443,6 +1499,7 @@ describe('<SKUSelector />', () => {
{ name: 'Color', values: ['Gray'] },
],
images: [],
sellers: [defaultSeller],
},
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ exports[`<InfoCard /> should render with image and text side by side 1`] = `
alt="CLICK HERE"
class="infoCardImage"
data-testid="half-image"
fetchpriority="auto"
src="my-image.com/image.png"
style="object-fit: cover;"
/>
Expand Down Expand Up @@ -315,6 +316,7 @@ exports[`<InfoCard /> should wrap half image in a link with imageActionUrl 1`] =
alt="CLICK HERE"
class="infoCardImage"
data-testid="half-image"
fetchpriority="auto"
src="my-image.com/image.png"
style="object-fit: cover;"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ exports[`<SKUSelector /> should consider order from skuSpecifications 1`] = `
class="skuSelectorOptionsList w-100 inline-flex flex-wrap ml2 items-center"
>
<div
class="skuSelectorItem skuSelectorItem--black relative di pointer flex items-center outline-0 ma2 skuSelectorItemImage"
class="skuSelectorItem skuSelectorItem--black relative di pointer flex items-center outline-0 ma2 skuSelectorItemImage valueWrapper"
role="button"
tabindex="0"
>
Expand All @@ -45,7 +45,7 @@ exports[`<SKUSelector /> should consider order from skuSpecifications 1`] = `
</div>
</div>
<div
class="skuSelectorItem skuSelectorItem--gray skuSelectorItem--selected relative di pointer flex items-center outline-0 ma2 skuSelectorItemImage"
class="skuSelectorItem skuSelectorItem--gray skuSelectorItem--selected relative di pointer flex items-center outline-0 ma2 skuSelectorItemImage valueWrapper"
role="button"
tabindex="0"
>
Expand All @@ -66,7 +66,7 @@ exports[`<SKUSelector /> should consider order from skuSpecifications 1`] = `
</div>
</div>
<div
class="skuSelectorItem skuSelectorItem--blue relative di pointer flex items-center outline-0 ma2 skuSelectorItemImage"
class="skuSelectorItem skuSelectorItem--blue relative di pointer flex items-center outline-0 ma2 skuSelectorItemImage valueWrapper"
role="button"
tabindex="0"
>
Expand Down Expand Up @@ -108,7 +108,7 @@ exports[`<SKUSelector /> should consider order from skuSpecifications 1`] = `
class="skuSelectorOptionsList w-100 inline-flex flex-wrap ml2 items-center"
>
<div
class="skuSelectorItem skuSelectorItem--42 relative di pointer flex items-center outline-0 ma2"
class="skuSelectorItem skuSelectorItem--42 relative di pointer flex items-center outline-0 ma2 valueWrapper"
role="button"
tabindex="0"
>
Expand All @@ -129,7 +129,7 @@ exports[`<SKUSelector /> should consider order from skuSpecifications 1`] = `
</div>
</div>
<div
class="skuSelectorItem skuSelectorItem--41 skuSelectorItem--selected relative di pointer flex items-center outline-0 ma2"
class="skuSelectorItem skuSelectorItem--41 skuSelectorItem--selected relative di pointer flex items-center outline-0 ma2 valueWrapper"
role="button"
tabindex="0"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ exports[`<SearchBar /> should match snapshot 1`] = `
class="suffixWrapper flex h-100"
>
<button
aria-label="Search Products"
class=" searchBarIcon searchBarIcon--search flex items-center pointer bn bg-transparent outline-0 pv0 pl0 pr3"
>
<div
Expand Down
45 changes: 42 additions & 3 deletions react/components/SKUSelector/components/SKUSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,35 @@ interface AvailableVariationParams {
sortVariationsByLabel: boolean
}

export const orderItemsByAvailability = (
item1: SelectorProductItem,
item2: SelectorProductItem
) => {
const isAvailableItem1 = isSkuAvailable(item1)
const isAvailableItem2 = isSkuAvailable(item2)

if (isAvailableItem1 && !isAvailableItem2) {
return -1
}

if (!isAvailableItem1 && isAvailableItem2) {
return 1
}

const defaultSellerItem1 = getDefaultSeller(item1.sellers)
const defaultSellerItem2 = getDefaultSeller(item2.sellers)

const availabilityItem1 = defaultSellerItem1
? defaultSellerItem1.commertialOffer.AvailableQuantity
: 0

const availabilityItem2 = defaultSellerItem2
? defaultSellerItem2.commertialOffer.AvailableQuantity
: 0

return availabilityItem2 - availabilityItem1
}

const parseOptionNameToDisplayOption =
({
selectedVariations,
Expand Down Expand Up @@ -161,13 +190,20 @@ const parseOptionNameToDisplayOption =

if (possibleItems.length > 0) {
// This is a valid combination option
const [item] = possibleItems

const possibleItemsOrderedByAvailability =
possibleItems.length > 1
? possibleItems.sort(orderItemsByAvailability)
: possibleItems

const [item] = possibleItemsOrderedByAvailability

const callbackFn = onSelectItemMemo({
name: variationName,
value: variationValue.name,
skuId: item.itemId,
isMainAndImpossible: false,
possibleItems,
possibleItems: possibleItemsOrderedByAvailability,
})

return {
Expand All @@ -176,7 +212,7 @@ const parseOptionNameToDisplayOption =
onSelectItem: callbackFn,
image,
available: showItemAsAvailable({
possibleItems,
possibleItems: possibleItemsOrderedByAvailability,
selectedVariations,
variationCount,
isSelected,
Expand Down Expand Up @@ -248,13 +284,16 @@ const variationNameToDisplayVariation =
const allNumbers = options.every(
(option: any) => !Number.isNaN(option.label)
)

options.sort((a: any, b: any) => {
if (allNumbers) {
return a.label - b.label
}

return a.label < b.label ? -1 : a.label > b.label ? 1 : 0
})
}

return { name, originalName, options }
}

Expand Down
23 changes: 12 additions & 11 deletions react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@
"apollo-cache-inmemory": "^1.4.3",
"babel-eslint": "^10.1.0",
"typescript": "3.9.7",
"vtex.address-form": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.address-form@4.22.8/public/@types/vtex.address-form",
"vtex.apps-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.apps-graphql@3.15.0/public/@types/vtex.apps-graphql",
"vtex.checkout-resources": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.checkout-resources@0.49.0/public/@types/vtex.checkout-resources",
"vtex.address-form": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.address-form@4.25.5/public/@types/vtex.address-form",
"vtex.apps-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.apps-graphql@3.17.4/public/@types/vtex.apps-graphql",
"vtex.checkout-resources": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.checkout-resources@0.50.0/public/@types/vtex.checkout-resources",
"vtex.css-handles": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.css-handles@1.0.1/public/@types/vtex.css-handles",
"vtex.device-detector": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.device-detector@0.2.6/public/@types/vtex.device-detector",
"vtex.format-currency": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.format-currency@0.4.1/public/@types/vtex.format-currency",
Expand All @@ -61,17 +61,18 @@
"vtex.pixel-manager": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.pixel-manager@1.9.0/public/@types/vtex.pixel-manager",
"vtex.product-context": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.product-context@0.10.1/public/@types/vtex.product-context",
"vtex.react-portal": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.react-portal@0.4.1/public/@types/vtex.react-portal",
"vtex.render-runtime": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.render-runtime@8.134.2/public/@types/vtex.render-runtime",
"vtex.render-runtime": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.render-runtime@8.134.11/public/@types/vtex.render-runtime",
"vtex.responsive-values": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.responsive-values@0.4.2/public/@types/vtex.responsive-values",
"vtex.rich-text": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.rich-text@0.16.0/public/@types/vtex.rich-text",
"vtex.search-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.search-graphql@0.58.0/public/@types/vtex.search-graphql",
"vtex.shipping-estimate-translator": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.shipping-estimate-translator@2.2.3/public/@types/vtex.shipping-estimate-translator",
"vtex.slider-layout": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.slider-layout@0.24.4/public/@types/vtex.slider-layout",
"vtex.store-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.store-graphql@2.170.1/public/@types/vtex.store-graphql",
"vtex.search-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.search-graphql@0.64.0/public/@types/vtex.search-graphql",
"vtex.shipping-estimate-translator": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.shipping-estimate-translator@2.3.0/public/@types/vtex.shipping-estimate-translator",
"vtex.slider-layout": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.slider-layout@0.24.6/public/@types/vtex.slider-layout",
"vtex.store-components": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.store-components@3.175.1/public/@types/vtex.store-components",
"vtex.store-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.store-graphql@2.172.1/public/@types/vtex.store-graphql",
"vtex.store-icons": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.store-icons@0.18.0/public/@types/vtex.store-icons",
"vtex.store-image": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.store-image@0.20.0/public/@types/vtex.store-image",
"vtex.store-resources": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.store-resources@0.93.0/public/@types/vtex.store-resources",
"vtex.styleguide": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.styleguide@9.146.9/public/@types/vtex.styleguide"
"vtex.store-image": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.store-image@0.22.3/public/@types/vtex.store-image",
"vtex.store-resources": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.store-resources@0.100.0/public/@types/vtex.store-resources",
"vtex.styleguide": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.styleguide@9.146.13/public/@types/vtex.styleguide"
},
"resolutions": {
"json-schema": "^0.4.0",
Expand Down
Loading

0 comments on commit 15c6c2e

Please sign in to comment.