Skip to content

Commit

Permalink
feat: handle complex values of variation attributes (#1600)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhhyi authored Mar 22, 2024
1 parent ff21e54 commit 59d15c6
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 60 deletions.
161 changes: 110 additions & 51 deletions src/app/core/models/product-variation/product-variation.helper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const productVariations = [
variableVariationAttributes: [
{ name: 'Attr 1', value: 'A', variationAttributeId: 'a1' },
{ name: 'Attr 2', value: 'A', variationAttributeId: 'a2' },
{ name: 'Attr 3', value: { value: 3, unit: 'm' }, variationAttributeId: 'a3' },
],
},
{
Expand All @@ -20,6 +21,7 @@ const productVariations = [
variableVariationAttributes: [
{ name: 'Attr 1', value: 'A', variationAttributeId: 'a1' },
{ name: 'Attr 2', value: 'B', variationAttributeId: 'a2' },
{ name: 'Attr 3', value: { value: 1, unit: 'm' }, variationAttributeId: 'a3' },
],
},
{
Expand All @@ -28,6 +30,7 @@ const productVariations = [
variableVariationAttributes: [
{ name: 'Attr 1', value: 'B', variationAttributeId: 'a1' },
{ name: 'Attr 2', value: 'A', variationAttributeId: 'a2' },
{ name: 'Attr 3', value: { value: 2, unit: 'm' }, variationAttributeId: 'a3' },
],
},
{
Expand All @@ -36,6 +39,7 @@ const productVariations = [
variableVariationAttributes: [
{ name: 'Attr 1', value: 'B', variationAttributeId: 'a1' },
{ name: 'Attr 2', value: 'B', variationAttributeId: 'a2' },
{ name: 'Attr 3', value: { value: 3, unit: 'm' }, variationAttributeId: 'a3' },
],
},
{
Expand All @@ -44,6 +48,7 @@ const productVariations = [
variableVariationAttributes: [
{ name: 'Attr 1', value: 'B', variationAttributeId: 'a1' },
{ name: 'Attr 2', value: 'C', variationAttributeId: 'a2' },
{ name: 'Attr 3', value: { value: 3, unit: 'm' }, variationAttributeId: 'a3' },
],
},
] as VariationProduct[];
Expand All @@ -56,6 +61,9 @@ const productMaster = {
{ name: 'Attr 2', value: 'A', variationAttributeId: 'a2' },
{ name: 'Attr 2', value: 'B', variationAttributeId: 'a2' },
{ name: 'Attr 2', value: 'C', variationAttributeId: 'a2' },
{ name: 'Attr 3', value: '1', variationAttributeId: 'a3' },
{ name: 'Attr 3', value: '2', variationAttributeId: 'a3' },
{ name: 'Attr 3', value: '3', variationAttributeId: 'a3' },
],
} as VariationProductMaster;

Expand All @@ -73,58 +81,96 @@ const masterProductView = {
describe('Product Variation Helper', () => {
describe('buildVariationOptionGroups', () => {
it('should build variation option groups for variation product', () => {
const expectedGroups = [
{
id: 'a1',
label: 'Attr 1',
options: [
{
label: 'A',
value: 'A',
type: 'a1',
alternativeCombination: false,
active: true,
},
{
label: 'B',
value: 'B',
type: 'a1',
alternativeCombination: false,
active: false,
},
],
},
{
id: 'a2',
label: 'Attr 2',
options: [
{
label: 'A',
value: 'A',
type: 'a2',
alternativeCombination: false,
active: true,
},
{
label: 'B',
value: 'B',
type: 'a2',
alternativeCombination: false,
active: false,
},
{
label: 'C',
value: 'C',
type: 'a2',
alternativeCombination: true,
active: false,
},
],
},
];

const result = ProductVariationHelper.buildVariationOptionGroups(variationProductView);
expect(result).toEqual(expectedGroups);
expect(result).toMatchInlineSnapshot(`
[
{
"attributeType": undefined,
"id": "a1",
"label": "Attr 1",
"options": [
{
"active": true,
"alternativeCombination": true,
"label": "A",
"metaData": undefined,
"type": "a1",
"value": "A",
},
{
"active": false,
"alternativeCombination": true,
"label": "B",
"metaData": undefined,
"type": "a1",
"value": "B",
},
],
},
{
"attributeType": undefined,
"id": "a2",
"label": "Attr 2",
"options": [
{
"active": true,
"alternativeCombination": true,
"label": "A",
"metaData": undefined,
"type": "a2",
"value": "A",
},
{
"active": false,
"alternativeCombination": true,
"label": "B",
"metaData": undefined,
"type": "a2",
"value": "B",
},
{
"active": false,
"alternativeCombination": true,
"label": "C",
"metaData": undefined,
"type": "a2",
"value": "C",
},
],
},
{
"attributeType": undefined,
"id": "a3",
"label": "Attr 3",
"options": [
{
"active": false,
"alternativeCombination": true,
"label": "1",
"metaData": undefined,
"type": "a3",
"value": "1",
},
{
"active": false,
"alternativeCombination": true,
"label": "2",
"metaData": undefined,
"type": "a3",
"value": "2",
},
{
"active": true,
"alternativeCombination": true,
"label": "3",
"metaData": undefined,
"type": "a3",
"value": "3",
},
],
},
]
`);
});
});

Expand Down Expand Up @@ -188,6 +234,19 @@ describe('Product Variation Helper', () => {
expect(ProductVariationHelper.productVariationCount(masterProductView, filters)).toEqual(2);
});

it('should filter for products matching complex value attributes', () => {
const filters = {
filter: [
{
id: 'a1',
facets: [{ selected: true, name: 'a3=3' }],
},
],
} as FilterNavigation;

expect(ProductVariationHelper.productVariationCount(masterProductView, filters)).toEqual(3);
});

it('should filter for products matching multiple selected attributes', () => {
const filters = {
filter: [
Expand Down
34 changes: 27 additions & 7 deletions src/app/core/models/product-variation/product-variation.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ export class ProductVariationHelper {
// each with information about alternative combinations and active status (active status comes from currently selected variation)
const options: VariationSelectOption[] = (product.productMaster?.variationAttributeValues || [])
.map(attr => ({
label: attr.value,
value: attr.value,
label: ProductVariationHelper.toDisplayValue(attr.value),
value: ProductVariationHelper.toValue(attr.value)?.toString(),
type: attr.variationAttributeId,
metaData: attr.metaData,
active: currentSettings?.[attr.variationAttributeId]?.value === attr.value,
active: ProductVariationHelper.isEqual(currentSettings?.[attr.variationAttributeId]?.value, attr.value),
}))
.map(option => ({
...option,
Expand Down Expand Up @@ -65,7 +65,9 @@ export class ProductVariationHelper {

const candidates = product.variations
.filter(variation =>
variation.variableVariationAttributes.some(attr => attr.variationAttributeId === name && attr.value === value)
variation.variableVariationAttributes.some(
attr => attr.variationAttributeId === name && ProductVariationHelper.isEqual(attr.value, value)
)
)
.map(variation => ({
sku: variation.sku,
Expand Down Expand Up @@ -113,7 +115,9 @@ export class ProductVariationHelper {
// attribute is not selected
!selectedFacets.find(([key]) => key === attr.variationAttributeId) ||
// selection is variation
selectedFacets.find(([key, val]) => key === attr.variationAttributeId && val === attr.value.toString())
selectedFacets.find(
([key, val]) => key === attr.variationAttributeId && ProductVariationHelper.isEqual(val, attr.value)
)
)
).length;
}
Expand Down Expand Up @@ -144,14 +148,17 @@ export class ProductVariationHelper {
// increment quality if variation attribute matches selected product attribute.
if (
attribute.variationAttributeId === selectedAttribute.variationAttributeId &&
attribute.value === selectedAttribute.value
ProductVariationHelper.isEqual(attribute.value, selectedAttribute.value)
) {
quality += 1;
continue;
}

// increment quality if variation attribute matches currently checked option.
if (attribute.variationAttributeId === option.type && attribute.value === option.value) {
if (
attribute.variationAttributeId === option.type &&
ProductVariationHelper.isEqual(attribute.value, option.value)
) {
quality += 1;
continue;
}
Expand All @@ -168,6 +175,19 @@ export class ProductVariationHelper {
return true;
}

private static toValue(input: VariationAttribute['value']): string | number {
return typeof input === 'object' ? input.value : input;
}

private static toDisplayValue(input: VariationAttribute['value']): string {
return typeof input === 'object' ? `${input.value} ${input.unit}` : input.toString();
}

private static isEqual(obj1: VariationAttribute['value'], obj2: VariationAttribute['value']): boolean {
// eslint-disable-next-line eqeqeq -- needed for comparison of string, integers and floats
return ProductVariationHelper.toValue(obj1) == ProductVariationHelper.toValue(obj2);
}

private static simplifyVariableVariationAttributes(attrs: VariationAttribute[]): { [name: string]: string } {
return attrs
.map(attr => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export interface VariationAttribute {
variationAttributeId: string;
name: string;
value: string;
value: string | number | { value: number; unit: string };
attributeType: VariationAttributeType;
metaData?: string;
}
Expand Down
2 changes: 2 additions & 0 deletions src/app/core/pipes.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { HtmlEncodePipe } from './pipes/html-encode.pipe';
import { MakeHrefPipe } from './pipes/make-href.pipe';
import { SanitizePipe } from './pipes/sanitize.pipe';
import { ServerSettingPipe } from './pipes/server-setting.pipe';
import { VariationAttributePipe } from './pipes/variation-attribute.pipe';
import { CategoryRoutePipe } from './routing/category/category-route.pipe';
import { ContentPageRoutePipe } from './routing/content-page/content-page-route.pipe';
import { ProductRoutePipe } from './routing/product/product-route.pipe';
Expand All @@ -26,6 +27,7 @@ const pipes = [
ProductRoutePipe,
SanitizePipe,
ServerSettingPipe,
VariationAttributePipe,
];

@NgModule({
Expand Down
67 changes: 67 additions & 0 deletions src/app/core/pipes/variation-attribute.pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { registerLocaleData } from '@angular/common';
import localeDe from '@angular/common/locales/de';
import { TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';

import { VariationAttribute } from 'ish-core/models/product-variation/variation-attribute.model';

import { VariationAttributePipe } from './variation-attribute.pipe';

describe('Variation Attribute Pipe', () => {
let variationAttributePipe: VariationAttributePipe;
let translateService: TranslateService;

beforeEach(() => {
registerLocaleData(localeDe);

TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
providers: [VariationAttributePipe],
});

variationAttributePipe = TestBed.inject(VariationAttributePipe);
translateService = TestBed.inject(TranslateService);
translateService.setDefaultLang('en');
translateService.use('en');
});

it('should be created', () => {
expect(variationAttributePipe).toBeTruthy();
});

it('should transform undefined to undefined', () => {
expect(variationAttributePipe.transform(undefined)).toMatchInlineSnapshot(`"undefined"`);
});

it('should transform string attribute to string', () => {
const attr = { value: 'test' } as VariationAttribute;
expect(variationAttributePipe.transform(attr)).toMatchInlineSnapshot(`"test"`);
});

it('should transform number attribute to number', () => {
const attr = { value: 123 } as VariationAttribute;
expect(variationAttributePipe.transform(attr)).toMatchInlineSnapshot(`"123"`);
});

it('should transform float attribute to formatted locale en', () => {
const attr = { value: 123.4 } as VariationAttribute;
expect(variationAttributePipe.transform(attr)).toMatchInlineSnapshot(`"123.4"`);
});

it('should transform float attribute to formatted locale de', () => {
translateService.use('de');
const attr = { value: 123.4 } as VariationAttribute;
expect(variationAttributePipe.transform(attr)).toMatchInlineSnapshot(`"123,4"`);
});

it('should transform object attribute to formatted locale en', () => {
const attr = { value: { value: 123.4, unit: 'mm' } } as VariationAttribute;
expect(variationAttributePipe.transform(attr)).toMatchInlineSnapshot(`"123.4\xA0mm"`);
});

it('should transform object attribute to formatted locale de', () => {
translateService.use('de');
const attr = { value: { value: 123.4, unit: 'mm' } } as VariationAttribute;
expect(variationAttributePipe.transform(attr)).toMatchInlineSnapshot(`"123,4\xA0mm"`);
});
});
Loading

0 comments on commit 59d15c6

Please sign in to comment.