Skip to content

Commit

Permalink
šŸ› Fix decoder bug when option value encoding does not end with a contā€¦
Browse files Browse the repository at this point in the history
ā€¦rol character (#2721)

* Bugfix for cases where option value encoding does not end with a control character

* Add changeset
  • Loading branch information
lhoffbeck authored Jan 27, 2025
1 parent f25a0d4 commit 08b7fa5
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/witty-baboons-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/hydrogen-react': patch
---

Update `decodeEncodedVariant` utility to fix bug if encoding ends with index.
50 changes: 40 additions & 10 deletions packages/hydrogen-react/src/optionValueDecoder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,16 @@ describe('decodeEncodedVariant', () => {
describe('v1', () => {
const VERSION_PREFIX = 'v1_';

it('it correctly decodes a set of 1-dimensional arrays with no gaps in the number sequence', () => {
// occurs if a product has no available variants
it('returns an empty array if the encoding contains no option values', () => {
expect(decodeEncodedVariant(`${VERSION_PREFIX}`)).toStrictEqual([]);
});

it('decodes a single option value', () => {
expect(decodeEncodedVariant(`${VERSION_PREFIX}0`)).toStrictEqual([[0]]);
});

it('decodes a set of 1-dimensional arrays with no gaps in the number sequence', () => {
expect(decodeEncodedVariant(`${VERSION_PREFIX}0-9`)).toStrictEqual([
[0],
[1],
Expand All @@ -70,13 +79,21 @@ describe('decodeEncodedVariant', () => {
]);
});

it('it correctly decodes a set of 1-dimensional arrays with gaps in the number sequence', () => {
it('decodes a set of 1-dimensional arrays with gaps in the number sequence', () => {
expect(
decodeEncodedVariant(`${VERSION_PREFIX}0-2 4-6 8-9`),
).toStrictEqual([[0], [1], [2], [4], [5], [6], [8], [9]]);
decodeEncodedVariant(`${VERSION_PREFIX}0-2 4-6 8-10`),
).toStrictEqual([[0], [1], [2], [4], [5], [6], [8], [9], [10]]);
});

// this can only occur for an availability encoding, not a existence encoding
it('decodes a set of 1-dimensional arrays with gaps and no ranges in the number sequence', () => {
expect(decodeEncodedVariant(`${VERSION_PREFIX}0 2`)).toStrictEqual([
[0],
[2],
]);
});

it('it correctly decodes a set of 2-dimensional arrays with no gaps in the number sequence', () => {
it('decodes a set of 2-dimensional arrays with no gaps in the number sequence', () => {
expect(
decodeEncodedVariant(`${VERSION_PREFIX}0:0-2,1:0-2,2:0-2,`),
).toStrictEqual([
Expand All @@ -92,7 +109,7 @@ describe('decodeEncodedVariant', () => {
]);
});

it('it correctly decodes a set of 2-dimensional arrays with gaps in the number sequence', () => {
it('decodes a set of 2-dimensional arrays with gaps in the number sequence', () => {
expect(
decodeEncodedVariant(`${VERSION_PREFIX}0:0-1,1:0 2,2:1-2,`),
).toStrictEqual([
Expand All @@ -105,7 +122,20 @@ describe('decodeEncodedVariant', () => {
]);
});

it('it correctly decodes a set of 3-dimensional arrays with no gaps in the number sequence', () => {
it('decodes a set of 2-dimensional arrays with gaps and no rangesin the number sequence', () => {
expect(
decodeEncodedVariant(`${VERSION_PREFIX}0:0-1,1:0 2,2:1 3,`),
).toStrictEqual([
[0, 0],
[0, 1],
[1, 0],
[1, 2],
[2, 1],
[2, 3],
]);
});

it('decodes a set of 3-dimensional arrays with no gaps in the number sequence', () => {
const output = generateCombinations(3, 3);
expect(
decodeEncodedVariant(
Expand All @@ -114,7 +144,7 @@ describe('decodeEncodedVariant', () => {
).toStrictEqual(output);
});

it('it correctly decodes a set of 3-dimensional arrays with gaps in the number sequence', () => {
it('decodes a set of 3-dimensional arrays with gaps in the number sequence', () => {
const output = generateCombinations(3, 3, [
[1, 0, 1],
[2, 2, 2],
Expand All @@ -127,7 +157,7 @@ describe('decodeEncodedVariant', () => {
).toStrictEqual(output);
});

it('it correctly decodes a set of 4-dimensional arrays with no gaps in the number sequence', () => {
it('decodes a set of 4-dimensional arrays with no gaps in the number sequence', () => {
const output = generateCombinations(4, 3);
expect(
decodeEncodedVariant(
Expand All @@ -136,7 +166,7 @@ describe('decodeEncodedVariant', () => {
).toStrictEqual(output);
});

it('it correctly decodes a set of 4-dimensional arrays with gaps in the number sequence', () => {
it('decodes a set of 4-dimensional arrays with gaps in the number sequence', () => {
const output = generateCombinations(4, 3, [
[1, 0, 1, 0],
[2, 2, 2, 0],
Expand Down
23 changes: 13 additions & 10 deletions packages/hydrogen-react/src/optionValueDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,16 +172,19 @@ function v1Decoder(encodedVariantField: string): number[][] {
index = tokenizer.lastIndex;
}

// Because we iterate over control characters and the range processing happens in the while,
// if the last control char is a range we need to manually add the final range to the option list.
const lastRangeStartIndex = encodedVariantField.lastIndexOf('-');
if (rangeStart != null && lastRangeStartIndex > 0) {
const finalValueIndex = parseInt(
encodedVariantField.substring(lastRangeStartIndex + 1),
);
for (; rangeStart <= finalValueIndex; rangeStart++) {
currentOptionValue[depth] = rangeStart;
options.push([...currentOptionValue]);
// The while loop only iterates control characters, meaning if an encoded string ends with an index it will not be processed.
const encodingEndsWithIndex = encodedVariantField.match(/\d+$/g);
if (encodingEndsWithIndex) {
const finalValueIndex = parseInt(encodingEndsWithIndex[0]);
if (rangeStart != null) {
// process final range
for (; rangeStart <= finalValueIndex; rangeStart++) {
currentOptionValue[depth] = rangeStart;
options.push([...currentOptionValue]);
}
} else {
// process final index
options.push([finalValueIndex]);
}
}

Expand Down

0 comments on commit 08b7fa5

Please sign in to comment.