-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: introduce additional components for product variation select di…
…splay (#1317) * moved existing `ProductVariationSelectComponent` rendering as standard select box to new `ProductVariationSelectDefaultComponent` * `ProductVariationSelectComponent` now contains the logic to select the fitting variation select rendering component * added `ProductVariationSelectSwatchComponent` for colorCode and swatchImage variation select rendering * added `ProductVariationSelectEnhancedComponent` for a select box rendering with color codes or swatch images and a mobile optimization BREAKING CHANGES: Changed the rendering of the `ProductVariationSelectComponent` and introduced additional product variation select rendering components (see [Migrations / 3.1 to 3.2](https://github.com/intershop/intershop-pwa/blob/develop/docs/guides/migrations.md#31-to-32) for more details). Co-authored-by: Stefan Hauke <s.hauke@intershop.de>
- Loading branch information
Showing
18 changed files
with
675 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
.../product/product-variation-select-default/product-variation-select-default.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<select | ||
class="form-control" | ||
[id]="uuid + group.id" | ||
[attr.data-testing-id]="group.id" | ||
(change)="optionChange(group.id, $event.target)" | ||
> | ||
<ng-container *ngFor="let option of group.options"> | ||
<option | ||
*ngIf="!option.alternativeCombination || multipleOptions" | ||
[value]="option.value" | ||
[selected]="option.active" | ||
[attr.data-testing-id]="group.id + '-' + option.value" | ||
> | ||
{{ option.label }} | ||
<ng-container *ngIf="option.alternativeCombination"> | ||
- {{ 'product.available_in_different_configuration' | translate }} | ||
</ng-container> | ||
</option> | ||
</ng-container> | ||
</select> |
72 changes: 72 additions & 0 deletions
72
...oduct/product-variation-select-default/product-variation-select-default.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
import { By } from '@angular/platform-browser'; | ||
import { anything, capture, spy, verify } from 'ts-mockito'; | ||
|
||
import { VariationOptionGroup } from 'ish-core/models/product-variation/variation-option-group.model'; | ||
import { findAllDataTestingIDs } from 'ish-core/utils/dev/html-query-utils'; | ||
|
||
import { ProductVariationSelectDefaultComponent } from './product-variation-select-default.component'; | ||
|
||
describe('Product Variation Select Default Component', () => { | ||
let component: ProductVariationSelectDefaultComponent; | ||
let fixture: ComponentFixture<ProductVariationSelectDefaultComponent>; | ||
let element: HTMLElement; | ||
|
||
const group = { id: 'a', options: [{ value: 'B' }, { value: 'C' }] } as VariationOptionGroup; | ||
|
||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
declarations: [ProductVariationSelectDefaultComponent], | ||
}).compileComponents(); | ||
}); | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(ProductVariationSelectDefaultComponent); | ||
component = fixture.componentInstance; | ||
element = fixture.nativeElement; | ||
component.group = group; | ||
}); | ||
|
||
it('should be created', () => { | ||
expect(component).toBeTruthy(); | ||
expect(element).toBeTruthy(); | ||
expect(() => fixture.detectChanges()).not.toThrow(); | ||
}); | ||
|
||
it('should initialize form of option group', () => { | ||
fixture.detectChanges(); | ||
|
||
expect(findAllDataTestingIDs(fixture)).toMatchInlineSnapshot(` | ||
Array [ | ||
"a", | ||
"a-B", | ||
"a-C", | ||
] | ||
`); | ||
}); | ||
|
||
it('should set active values for form', () => { | ||
fixture.detectChanges(); | ||
|
||
expect(fixture.debugElement.query(By.css('select[data-testing-id=a]')).nativeElement.value).toMatchInlineSnapshot( | ||
`"B"` | ||
); | ||
}); | ||
|
||
it('should trigger changeOption output handler if select value changes', () => { | ||
fixture.detectChanges(); | ||
const emitter = spy(component.changeOption); | ||
const select = fixture.debugElement.query(By.css('select')).nativeElement; | ||
select.value = 'C'; | ||
select.dispatchEvent(new Event('change')); | ||
|
||
verify(emitter.emit(anything())).once(); | ||
const [arg] = capture(emitter.emit).last(); | ||
expect(arg).toMatchInlineSnapshot(` | ||
Object { | ||
"group": "a", | ||
"value": "C", | ||
} | ||
`); | ||
}); | ||
}); |
20 changes: 20 additions & 0 deletions
20
...ts/product/product-variation-select-default/product-variation-select-default.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; | ||
|
||
import { VariationOptionGroup } from 'ish-core/models/product-variation/variation-option-group.model'; | ||
|
||
@Component({ | ||
selector: 'ish-product-variation-select-default', | ||
templateUrl: './product-variation-select-default.component.html', | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
}) | ||
export class ProductVariationSelectDefaultComponent { | ||
@Input() group: VariationOptionGroup; | ||
@Input() uuid: string; | ||
@Input() multipleOptions: boolean; | ||
|
||
@Output() changeOption = new EventEmitter<{ group: string; value: string }>(); | ||
|
||
optionChange(group: string, target: EventTarget) { | ||
this.changeOption.emit({ group, value: (target as HTMLDataElement).value }); | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
...roduct/product-variation-select-enhanced/product-variation-select-enhanced.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<!-- desktop: display enhanced select boxes with color codes or swatch images with labels --> | ||
<ng-container *ngIf="(deviceType$ | async) === 'desktop'; else mobile"> | ||
<div ngbDropdown> | ||
<button ngbDropdownToggle type="button" class="btn variation-select" [id]="uuid + group.id"> | ||
<ng-container *ngFor="let option of group.options"> | ||
<ng-container *ngIf="option.active"> | ||
<ng-container *ngTemplateOutlet="optionTemplate; context: { group, option }"></ng-container> | ||
</ng-container> | ||
</ng-container> | ||
</button> | ||
<div ngbDropdownMenu class="variation-options" [attr.aria-labelledby]="uuid + group.id + 'label'"> | ||
<ng-container *ngFor="let option of group.options"> | ||
<button | ||
ngbDropdownItem | ||
*ngIf="!option.alternativeCombination || multipleOptions" | ||
[value]="option.value" | ||
[attr.data-testing-id]="group.id + '-' + option.value" | ||
(click)="optionChange(group.id, option.value)" | ||
> | ||
<ng-container *ngTemplateOutlet="optionTemplate; context: { group, option }"></ng-container> | ||
</button> | ||
</ng-container> | ||
</div> | ||
</div> | ||
</ng-container> | ||
|
||
<!-- mobile/tablet: display a list of color codes or swatch images with labels --> | ||
<ng-template #mobile> | ||
<div class="mobile-variation-select"> | ||
<div *ngFor="let option of group.options" class="mobile-variation-option"> | ||
<a (click)="optionChange(group.id, option.value)"> | ||
<ng-container *ngTemplateOutlet="optionTemplate; context: { group, option }"></ng-container> | ||
</a> | ||
</div> | ||
</div> | ||
</ng-template> | ||
|
||
<!-- reusable template to render the individual options as color code or image swatch with label --> | ||
<ng-template #optionTemplate let-group="group" let-option="option"> | ||
<span | ||
*ngIf="group.attributeType === 'defaultAndColorCode'" | ||
class="color-code" | ||
[ngStyle]="{ 'background-color': '#' + option.metaData }" | ||
[ngClass]="{ 'light-color': option.label.toLowerCase() === 'white' }" | ||
></span> | ||
<img | ||
*ngIf="group.attributeType === 'defaultAndSwatchImage'" | ||
class="image-swatch" | ||
[src]="option.metaData" | ||
alt="{{ option.label }}" | ||
/> | ||
<span class="label" [ngClass]="{ selected: option.active }" | ||
>{{ option.label }} | ||
<ng-container *ngIf="option.alternativeCombination"> | ||
- {{ 'product.available_in_different_configuration' | translate }} | ||
</ng-container> | ||
</span> | ||
</ng-template> |
66 changes: 66 additions & 0 deletions
66
...roduct/product-variation-select-enhanced/product-variation-select-enhanced.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
@import 'variables'; | ||
|
||
.variation-select { | ||
width: 100%; | ||
padding-right: 12px; | ||
padding-left: 12px; | ||
overflow: hidden; | ||
text-align: left; | ||
border: 1px solid $gray-400; | ||
|
||
&.dropdown-toggle::after { | ||
position: absolute; | ||
top: 17px; | ||
right: 10px; | ||
} | ||
|
||
.label.selected { | ||
font-family: $font-family-regular; | ||
} | ||
} | ||
|
||
.variation-options { | ||
width: 100%; | ||
|
||
.dropdown-item { | ||
padding-right: 12px; | ||
padding-left: 12px; | ||
} | ||
} | ||
|
||
.mobile-variation-select { | ||
margin: $space-default * 0.5 0 $space-default 0; | ||
font-family: $font-family-regular; | ||
|
||
.mobile-variation-option { | ||
margin-bottom: $space-default * 0.5; | ||
} | ||
} | ||
|
||
span.color-code, | ||
img.image-swatch { | ||
display: inline-block; | ||
width: 24px; | ||
height: 24px; | ||
margin-right: $space-default * 0.5; | ||
vertical-align: middle; | ||
} | ||
|
||
span.color-code { | ||
border: 1px solid transparent; | ||
border-radius: 50%; | ||
|
||
&.light-color { | ||
border: 1px solid $border-color-light; | ||
} | ||
} | ||
|
||
span.label { | ||
font-family: $font-family-regular; | ||
color: $text-color-primary; | ||
text-transform: none; | ||
|
||
&.selected { | ||
font-family: $font-family-bold; | ||
} | ||
} |
134 changes: 134 additions & 0 deletions
134
...uct/product-variation-select-enhanced/product-variation-select-enhanced.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
import { By } from '@angular/platform-browser'; | ||
import { of } from 'rxjs'; | ||
import { anything, capture, instance, mock, spy, verify, when } from 'ts-mockito'; | ||
|
||
import { AppFacade } from 'ish-core/facades/app.facade'; | ||
import { VariationOptionGroup } from 'ish-core/models/product-variation/variation-option-group.model'; | ||
|
||
import { ProductVariationSelectEnhancedComponent } from './product-variation-select-enhanced.component'; | ||
|
||
describe('Product Variation Select Enhanced Component', () => { | ||
let component: ProductVariationSelectEnhancedComponent; | ||
let fixture: ComponentFixture<ProductVariationSelectEnhancedComponent>; | ||
let element: HTMLElement; | ||
let appFacade: AppFacade; | ||
|
||
const group_colorCode = { | ||
id: 'color', | ||
attributeType: 'defaultAndColorCode', | ||
options: [ | ||
{ value: 'black', label: 'Black', metaData: '000000', active: true }, | ||
{ value: 'white', label: 'White', metaData: 'FFFFFF' }, | ||
], | ||
} as VariationOptionGroup; | ||
|
||
const group_swatchImage = { | ||
id: 'swatch', | ||
attributeType: 'defaultAndSwatchImage', | ||
options: [ | ||
{ value: 'Y', label: 'yyy', metaData: 'imageY.png' }, | ||
{ value: 'Z', label: 'zzz', metaData: 'imageZ.png', active: true }, | ||
], | ||
} as VariationOptionGroup; | ||
|
||
beforeEach(async () => { | ||
appFacade = mock(AppFacade); | ||
await TestBed.configureTestingModule({ | ||
declarations: [ProductVariationSelectEnhancedComponent], | ||
providers: [{ provide: AppFacade, useFactory: () => instance(appFacade) }], | ||
}).compileComponents(); | ||
}); | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(ProductVariationSelectEnhancedComponent); | ||
component = fixture.componentInstance; | ||
element = fixture.nativeElement; | ||
component.group = group_colorCode; | ||
component.uuid = 'uuid'; | ||
}); | ||
|
||
it('should be created', () => { | ||
expect(component).toBeTruthy(); | ||
expect(element).toBeTruthy(); | ||
expect(() => fixture.detectChanges()).not.toThrow(); | ||
}); | ||
|
||
it('should render a color code select when the attribute type is "defaultAndColorCode" for mobile', () => { | ||
component.group = group_colorCode; | ||
fixture.detectChanges(); | ||
expect(element).toMatchInlineSnapshot(` | ||
<div class="mobile-variation-select"> | ||
<div class="mobile-variation-option"> | ||
<a | ||
><span class="color-code" style="background-color: rgb(0, 0, 0)"></span | ||
><span class="label selected">Black </span></a | ||
> | ||
</div> | ||
<div class="mobile-variation-option"> | ||
<a | ||
><span class="color-code light-color" style="background-color: rgb(255, 255, 255)"></span | ||
><span class="label">White </span></a | ||
> | ||
</div> | ||
</div> | ||
`); | ||
}); | ||
|
||
it('should render a swatch image select when the attribute type is "defaultAndColorCode" for desktop', () => { | ||
when(appFacade.deviceType$).thenReturn(of('desktop')); | ||
component.group = group_swatchImage; | ||
fixture.detectChanges(); | ||
expect(element).toMatchInlineSnapshot(` | ||
<div ngbdropdown=""> | ||
<button ngbdropdowntoggle="" type="button" class="btn variation-select" id="uuidswatch"> | ||
<img class="image-swatch" alt="zzz" src="imageZ.png" /><span class="label selected">zzz </span> | ||
</button> | ||
<div ngbdropdownmenu="" class="variation-options" aria-labelledby="uuidswatchlabel"> | ||
<button ngbdropdownitem="" value="Y" data-testing-id="swatch-Y"> | ||
<img class="image-swatch" alt="yyy" src="imageY.png" /><span class="label">yyy </span></button | ||
><button ngbdropdownitem="" value="Z" data-testing-id="swatch-Z"> | ||
<img class="image-swatch" alt="zzz" src="imageZ.png" /><span class="label selected" | ||
>zzz | ||
</span> | ||
</button> | ||
</div> | ||
</div> | ||
`); | ||
}); | ||
|
||
it('should trigger changeOption output handler if color code element is clicked (mobile)', () => { | ||
component.group = group_colorCode; | ||
fixture.detectChanges(); | ||
const emitter = spy(component.changeOption); | ||
const link = fixture.debugElement.query(By.css('.label.selected')).parent.nativeElement; | ||
link.dispatchEvent(new Event('click')); | ||
|
||
verify(emitter.emit(anything())).once(); | ||
const [arg] = capture(emitter.emit).last(); | ||
expect(arg).toMatchInlineSnapshot(` | ||
Object { | ||
"group": "color", | ||
"value": "black", | ||
} | ||
`); | ||
}); | ||
|
||
it('should trigger changeOption output handler if swatch image element is clicked (desktop)', () => { | ||
when(appFacade.deviceType$).thenReturn(of('desktop')); | ||
component.group = group_swatchImage; | ||
fixture.detectChanges(); | ||
const emitter = spy(component.changeOption); | ||
const link = fixture.debugElement.queryAll(By.css('.label.selected')).pop().parent.nativeElement; | ||
link.dispatchEvent(new Event('click')); | ||
|
||
verify(emitter.emit(anything())).once(); | ||
const [arg] = capture(emitter.emit).last(); | ||
expect(arg).toMatchInlineSnapshot(` | ||
Object { | ||
"group": "swatch", | ||
"value": "Z", | ||
} | ||
`); | ||
}); | ||
}); |
Oops, something went wrong.