Skip to content

Commit

Permalink
Clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
wizardlyhel committed Dec 3, 2024
1 parent fe6f8ce commit 5ddc8fb
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 293 deletions.
1 change: 1 addition & 0 deletions packages/hydrogen-react/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
plugins: ['eslint-plugin-tsdoc'],
ignorePatterns: [
'**/storefront-api-types.d.ts',
'**/customer-account-api-types.d.ts',
'**/codegen.ts',
'**/dist/**',
'**/coverage/**',
Expand Down
1 change: 1 addition & 0 deletions packages/hydrogen-react/src/getProductOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ describe('getAdjacentAndFirstAvailableVariants', () => {

describe('checkProductParam', () => {
beforeEach(() => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
vi.spyOn(console, 'error').mockImplementation(() => {});
});

Expand Down
161 changes: 60 additions & 101 deletions packages/hydrogen-react/src/getProductOptions.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import {validate} from 'graphql';
import {
decodeEncodedVariant,
isOptionValueCombinationInEncodedVariant,
} from './optionValueDecoder';
import {isOptionValueCombinationInEncodedVariant} from './optionValueDecoder.js';
import type {
Maybe,
Product,
ProductOption,
ProductOptionValue,
ProductVariant,
SelectedOption,
} from './storefront-api-types';
import {PartialDeep} from 'type-fest';

export type RecursivePartial<T> = {
[P in keyof T]?: RecursivePartial<T[P]>;
Expand All @@ -32,96 +26,94 @@ type MappedProductOptionValue = ProductOptionValue & ProductOptionValueState;
* Creates a mapping of product options to their index for matching encoded values
* For example, a product option of
* [
* {
* \{
* name: 'Color',
* optionValues: [{name: 'Red'}, {name: 'Blue'}]
* },
* {
* optionValues: [\{name: 'Red'\}, \{name: 'Blue'\}]
* \},
* \{
* name: 'Size',
* optionValues: [{name: 'Small'}, {name: 'Medium'}, {name: 'Large'}]
* }
* optionValues: [\{name: 'Small'\}, \{name: 'Medium'\}, \{name: 'Large'\}]
* \}
* ]
* Would return
* [
* {Red: 0, Blue: 1},
* {Small: 0, Medium: 1, Large: 2}
* \{Red: 0, Blue: 1\},
* \{Small: 0, Medium: 1, Large: 2\}
* ]
* @param options
* @returns
*/
function mapProductOptions(options: ProductOption[]): ProductOptionsMapping[] {
return options.map((option) => {
return options.map((option: ProductOption) => {
return Object.assign(
{},
...option?.optionValues.map((value, index) => {
return {[value.name]: index};
}),
);
...(option?.optionValues
? option.optionValues.map((value, index) => {
return {[value.name]: index};
})
: []),
) as ProductOptionsMapping;
});
}

/**
* Converts the product option into an Object<key, value> for building query params
* Converts the product option into an Object\<key, value\> for building query params
* For example, a selected product option of
* [
* {
* \{
* name: 'Color',
* value: 'Red',
* },
* {
* \},
* \{
* name: 'Size',
* value: 'Medium',
* }
* \}
* ]
* Would return
* {
* \{
* Color: 'Red',
* Size: 'Medium',
* }
* \}
*/
function mapSelectedProductOptionToObject(
export function mapSelectedProductOptionToObject(
options: Pick<SelectedOption, 'name' | 'value'>[],
) {
): Record<string, string> {
return Object.assign(
{},
...options.map((key) => {
return {[key.name]: key.value};
}),
);
) as Record<string, string>;
}

/**
*
* @param options Returns selected options as a JSON string
* @returns
* Returns the JSON stringify result of mapSelectedProductOptionToObject
*/
function mapSelectedProductOptionToObjectAsString(
options: Pick<SelectedOption, 'name' | 'value'>[],
) {
): string {
return JSON.stringify(mapSelectedProductOptionToObject(options));
}

/**
* Encode the selected product option as a key for mapping to the encoded variants
* For example, a selected product option of
* [
* {
* \{
* name: 'Color',
* value: 'Red',
* },
* {
* \},
* \{
* name: 'Size',
* value: 'Medium',
* }
* \}
* ]
* Would return
* [0,1]
*
* Also works with the result of mapSelectedProductOption. For example:
* {
* \{
* Color: 'Red',
* Size: 'Medium',
* }
* \}
* Would return
* [0,1]
*
Expand All @@ -134,7 +126,7 @@ function encodeSelectedProductOptionAsKey(
| Pick<SelectedOption, 'name' | 'value'>[]
| Record<string, string>,
productOptionMappings: ProductOptionsMapping[],
) {
): string {
if (Array.isArray(selectedOption)) {
return JSON.stringify(
selectedOption.map((key, index) => {
Expand All @@ -154,34 +146,31 @@ function encodeSelectedProductOptionAsKey(
* Takes an array of product variants and maps them to an object with the encoded selected option values as the key.
* For example, a product variant of
* [
* {
* \{
* id: 1,
* selectedOptions: [
* {name: 'Color', value: 'Red'},
* {name: 'Size', value: 'Small'},
* \{name: 'Color', value: 'Red'\},
* \{name: 'Size', value: 'Small'\},
* ],
* },
* {
* \},
* \{
* id: 2,
* selectedOptions: [
* {name: 'Color', value: 'Red'},
* {name: 'Size', value: 'Medium'},
* \{name: 'Color', value: 'Red'\},
* \{name: 'Size', value: 'Medium'\},
* ],
* }
* \}
* ]
* Would return
* {
* '[0,0]': {id: 1, selectedOptions: [{name: 'Color', value: 'Red'}, {name: 'Size', value: 'Small'}]},
* '[0,1]': {id: 2, selectedOptions: [{name: 'Color', value: 'Red'}, {name: 'Size', value: 'Medium'}]},
* }
* @param variants
* @param productOptionMappings
* @returns
* \{
* '[0,0]': \{id: 1, selectedOptions: [\{name: 'Color', value: 'Red'\}, \{name: 'Size', value: 'Small'\}]\},
* '[0,1]': \{id: 2, selectedOptions: [\{name: 'Color', value: 'Red'\}, \{name: 'Size', value: 'Medium'\}]\},
* \}
*/
function mapVariants(
variants: ProductVariant[],
productOptionMappings: ProductOptionsMapping[],
) {
): Record<string, ProductVariant> {
return Object.assign(
{},
...variants.map((variant) => {
Expand All @@ -191,7 +180,7 @@ function mapVariants(
);
return {[variantKey]: variant};
}),
);
) as Record<string, ProductVariant>;
}

export type MappedProductOptions = Omit<ProductOption, 'optionValues'> & {
Expand All @@ -210,7 +199,7 @@ const PRODUCT_INPUTS_EXTRA = [
'encodedVariantAvailability',
];

function logError(key: string) {
function logError(key: string): boolean {
console.error(
`[h2:error:getProductOptions] product.${key} is missing. Make sure you query for this field from the Storefront API.`,
);
Expand Down Expand Up @@ -314,12 +303,10 @@ function checkProductVariantParam(
/**
* Finds all the variants provided by adjacentVariants, options.optionValues.firstAvailableVariant,
* and selectedOrFirstAvailableVariant and return them in a single array
* @param product
* @returns
*/
export function getAdjacentAndFirstAvailableVariants(
product: RecursivePartial<Product>,
) {
): ProductVariant[] {
// Checks for valid product input
const checkedProduct = checkProductParam(product);

Expand Down Expand Up @@ -355,6 +342,10 @@ export function getAdjacentAndFirstAvailableVariants(
return Object.values(availableVariants);
}

/**
* Returns a product options array with its relevant information
* about the variant
*/
export function getProductOptions(
product: RecursivePartial<Product>,
): MappedProductOptions[] {
Expand Down Expand Up @@ -401,7 +392,10 @@ export function getProductOptions(
);

// Top-down option check for existence and availability
const topDownKey = JSON.parse(targetKey).slice(0, optionIndex + 1);
const topDownKey = (JSON.parse(targetKey) as number[]).slice(
0,
optionIndex + 1,
);
const exists = isOptionValueCombinationInEncodedVariant(
topDownKey,
encodedVariantExistence || '',
Expand Down Expand Up @@ -436,40 +430,5 @@ export function getProductOptions(
};
});

// Workaround for bug in encodedVariantAvailability
// Remove once https://github.com/Shopify/combined-listings-app/issues/2377 is resolved
const optionsAvailable: boolean[] = [];
productOptions.reverse().map((option, optionIndex) => {
let optionAvailable = false;
if (optionIndex === 0) {
// Make sure leaf option values always reflect the variant's availableForSale attribute
option.optionValues.map((value, valueIndex) => {
if (!value.isDifferentProduct) {
const available = value.variant.availableForSale;
productOptions[optionIndex].optionValues[valueIndex].available =
available;
if (available) optionAvailable = true;
}
});
} else {
// If not the last option, for selected optionValue, check for previous option for availability
const selectedValue = option.optionValues.filter(
(value) => value.selected,
);
const previousOptionAvailable = optionsAvailable[optionIndex - 1];
if (
selectedValue &&
selectedValue.length === 1 &&
!selectedValue[0].isDifferentProduct
) {
selectedValue[0].available = previousOptionAvailable;
if (previousOptionAvailable) optionAvailable = true;
}
}

// Keep track of availability of the current option
optionsAvailable.push(optionAvailable);
});

return productOptions.reverse();
return productOptions;
}
1 change: 1 addition & 0 deletions packages/hydrogen-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export {
getAdjacentAndFirstAvailableVariants,
getProductOptions,
type MappedProductOptions,
mapSelectedProductOptionToObject,
} from './getProductOptions.js';
export {Image, IMAGE_FRAGMENT} from './Image.js';
export {useLoadScript} from './load-script.js';
Expand Down
1 change: 1 addition & 0 deletions packages/hydrogen/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export {
decodeEncodedVariant,
getProductOptions,
getAdjacentAndFirstAvailableVariants,
mapSelectedProductOptionToObject,
} from '@shopify/hydrogen-react';
export {RichText} from './RichText';

Expand Down
Loading

0 comments on commit 5ddc8fb

Please sign in to comment.