diff --git a/libs/core/segmented-button/segmented-button.component.spec.ts b/libs/core/segmented-button/segmented-button.component.spec.ts index 6720b6d1d17..d6d5638d78d 100644 --- a/libs/core/segmented-button/segmented-button.component.spec.ts +++ b/libs/core/segmented-button/segmented-button.component.spec.ts @@ -1,12 +1,10 @@ import { Component, ElementRef, ViewChild } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - import { ButtonComponent, ButtonModule } from '@fundamental-ngx/core/button'; import { runValueAccessorTests } from 'ngx-cva-test-suite'; - -import { RtlService } from '@fundamental-ngx/cdk/utils'; import { SegmentedButtonComponent } from './segmented-button.component'; import { SegmentedButtonModule } from './segmented-button.module'; +import { SimpleChange } from '@angular/core'; const isSelectedClass = 'fd-button--toggled'; @@ -21,17 +19,10 @@ const isSelectedClass = 'fd-button--toggled'; ` }) export class HostComponent { - @ViewChild('first', { read: ElementRef }) - firstButton: ElementRef; - - @ViewChild('second', { read: ButtonComponent }) - secondButton: ButtonComponent; - - @ViewChild('third', { read: ElementRef }) - thirdButton: ElementRef; - - @ViewChild(SegmentedButtonComponent) - segmentedButton: SegmentedButtonComponent; + @ViewChild('first', { read: ElementRef }) firstButton: ElementRef; + @ViewChild('second', { read: ButtonComponent }) secondButton: ButtonComponent; + @ViewChild('third', { read: ElementRef }) thirdButton: ElementRef; + @ViewChild(SegmentedButtonComponent) segmentedButton: SegmentedButtonComponent; toggle = false; } @@ -43,7 +34,6 @@ describe('SegmentedButtonComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [HostComponent], - providers: [RtlService], imports: [SegmentedButtonModule, ButtonModule] }).compileComponents(); })); @@ -59,103 +49,87 @@ describe('SegmentedButtonComponent', () => { expect(component).toBeTruthy(); }); - it('should select button, when value is changed', () => { - component.segmentedButton.writeValue('first'); + // Default Example + it('should correctly select and deselect single value in non-toggle mode', () => { + component.segmentedButton.writeValue('second'); fixture.detectChanges(); + expect(component.secondButton.elementRef.nativeElement.classList.contains(isSelectedClass)).toBe(true); + component.segmentedButton.writeValue('first'); + fixture.detectChanges(); expect(component.firstButton.nativeElement.classList.contains(isSelectedClass)).toBe(true); + expect(component.secondButton.elementRef.nativeElement.classList.contains(isSelectedClass)).toBe(false); }); - it('should select all buttons button, when value is changed', () => { + // Toggle Example + it('should correctly handle multiple selections in toggle mode', () => { component.toggle = true; - component.segmentedButton.writeValue(['first']); fixture.detectChanges(); - component.segmentedButton.writeValue(['first', 'second', 'third']); - fixture.detectChanges(); - - expect(component.firstButton.nativeElement.classList.contains(isSelectedClass)).toBe(true); - expect(component.secondButton.elementRef.nativeElement.classList.contains(isSelectedClass)).toBe(true); - expect(component.thirdButton.nativeElement.classList.contains(isSelectedClass)).toBe(true); - }); - - it('should select button, when trigger event is performed', () => { component.firstButton.nativeElement.dispatchEvent(new MouseEvent('click')); fixture.detectChanges(); - expect(component.firstButton.nativeElement.classList.contains(isSelectedClass)).toBe(true); - }); - it('should select button and deselect other button, when trigger event is performed on non-toggle mode', () => { - component.firstButton.nativeElement.dispatchEvent(new MouseEvent('click')); + component.secondButton.elementRef.nativeElement.dispatchEvent(new MouseEvent('click')); fixture.detectChanges(); + expect(component.secondButton.elementRef.nativeElement.classList.contains(isSelectedClass)).toBe(true); - expect(component.firstButton.nativeElement.classList.contains(isSelectedClass)).toBe(true); - expect(component.segmentedButton['_currentValue']).toEqual('first'); - - component.secondButton.elementRef.nativeElement.dispatchEvent(new MouseEvent('click')); + component.thirdButton.nativeElement.dispatchEvent(new MouseEvent('click')); fixture.detectChanges(); + expect(component.thirdButton.nativeElement.classList.contains(isSelectedClass)).toBe(true); + // Deselect + component.firstButton.nativeElement.dispatchEvent(new MouseEvent('click')); + fixture.detectChanges(); expect(component.firstButton.nativeElement.classList.contains(isSelectedClass)).toBe(false); - expect(component.secondButton.elementRef.nativeElement.classList.contains(isSelectedClass)).toBe(true); - expect(component.segmentedButton['_currentValue']).toEqual('second'); }); - it('should select buttons, when trigger event is performed on toggle mode', () => { - component.segmentedButton.toggle = true; - component.firstButton.nativeElement.dispatchEvent(new MouseEvent('click')); + // Form Example + it('should update form value correctly', () => { + component.segmentedButton.writeValue('first'); fixture.detectChanges(); - expect(component.firstButton.nativeElement.classList.contains(isSelectedClass)).toBe(true); - expect(component.segmentedButton['_currentValue']).toEqual(['first']); - - component.secondButton.elementRef.nativeElement.dispatchEvent(new MouseEvent('click')); + + component.segmentedButton.writeValue('second'); fixture.detectChanges(); - - expect(component.firstButton.nativeElement.classList.contains(isSelectedClass)).toBe(true); expect(component.secondButton.elementRef.nativeElement.classList.contains(isSelectedClass)).toBe(true); - expect(component.segmentedButton['_currentValue']).toEqual(['first', 'second']); }); - it('should ignore trigger event on disabled', () => { - component.firstButton.nativeElement.dispatchEvent(new MouseEvent('click')); + // Disabled State Check + it('should detect disabled state', () => { + component.segmentedButton.setDisabledState(true); fixture.detectChanges(); + + expect(component.firstButton.nativeElement.hasAttribute('disabled')).toBe(true); + expect(component.secondButton.elementRef.nativeElement.hasAttribute('disabled')).toBe(true); + expect(component.thirdButton.nativeElement.hasAttribute('disabled')).toBe(true); - expect(component.firstButton.nativeElement.classList.contains(isSelectedClass)).toBe(true); - expect(component.segmentedButton['_currentValue']).toEqual('first'); - - component.secondButton.disabled = true; - fixture.detectChanges(); - component.secondButton.elementRef.nativeElement.dispatchEvent(new MouseEvent('click')); + component.segmentedButton.setDisabledState(false); fixture.detectChanges(); - expect(component.secondButton.elementRef.nativeElement.classList.contains(isSelectedClass)).toBe(false); - expect(component.segmentedButton['_currentValue']).toEqual('first'); + expect(component.firstButton.nativeElement.hasAttribute('disabled')).toBe(false); + expect(component.secondButton.elementRef.nativeElement.hasAttribute('disabled')).toBe(false); + expect(component.thirdButton.nativeElement.hasAttribute('disabled')).toBe(false); }); + // Event Handling Test it('should handle all trigger events', () => { component.segmentedButton.toggle = true; - component.firstButton.nativeElement.dispatchEvent(new MouseEvent('click')); fixture.detectChanges(); + component.firstButton.nativeElement.dispatchEvent(new MouseEvent('click')); + fixture.detectChanges(); expect(component.firstButton.nativeElement.classList.contains(isSelectedClass)).toBe(true); - expect(component.segmentedButton['_currentValue']).toEqual(['first']); component.secondButton.elementRef.nativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); fixture.detectChanges(); - - expect(component.firstButton.nativeElement.classList.contains(isSelectedClass)).toBe(true); expect(component.secondButton.elementRef.nativeElement.classList.contains(isSelectedClass)).toBe(true); - expect(component.segmentedButton['_currentValue']).toEqual(['first', 'second']); component.thirdButton.nativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' })); fixture.detectChanges(); - - expect(component.firstButton.nativeElement.classList.contains(isSelectedClass)).toBe(true); - expect(component.secondButton.elementRef.nativeElement.classList.contains(isSelectedClass)).toBe(true); expect(component.thirdButton.nativeElement.classList.contains(isSelectedClass)).toBe(true); - expect(component.segmentedButton['_currentValue']).toEqual(['first', 'second', 'third']); }); + }); describe('Segmented button component CVA', () => { @@ -169,18 +143,15 @@ describe('Segmented button component CVA', () => { }, testModuleMetadata: { declarations: [HostComponent], - providers: [RtlService], - imports: [SegmentedButtonModule, ButtonModule] // <= importing the module for app-select + imports: [SegmentedButtonModule, ButtonModule] }, hostTemplate: { - // specify that "AppSelectComponent" should not be tested directly hostComponent: HostComponent, - // specify the way to access "AppSelectComponent" from the host template getTestingComponent: (fixture) => fixture.componentInstance.segmentedButton }, supportsOnBlur: false, internalValueChangeSetter: null, - getComponentValue: (fixture) => (fixture.componentInstance.segmentedButton as any)._currentValue, - getValues: () => [1, 2, 3] // <= setting the same values as select options in host template + getComponentValue: (fixture) => fixture.componentInstance.segmentedButton['_currentValue'], + getValues: () => ['first', 'second', 'third'] }); }); diff --git a/libs/core/segmented-button/segmented-button.component.ts b/libs/core/segmented-button/segmented-button.component.ts index 72faa899ce6..78dc4f86023 100644 --- a/libs/core/segmented-button/segmented-button.component.ts +++ b/libs/core/segmented-button/segmented-button.component.ts @@ -22,13 +22,11 @@ import { } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { FocusableListDirective, KeyUtil, Nullable, RtlService, destroyObservable } from '@fundamental-ngx/cdk/utils'; +import { FocusableItemDirective, FocusableListDirective, KeyUtil, Nullable, RtlService, destroyObservable } from '@fundamental-ngx/cdk/utils'; import { ButtonComponent, FD_BUTTON_COMPONENT } from '@fundamental-ngx/core/button'; import { Subject, asyncScheduler, fromEvent, merge } from 'rxjs'; import { filter, observeOn, startWith, takeUntil, tap } from 'rxjs/operators'; -export const isDisabledClass = 'is-disabled'; - export type SegmentedButtonValue = string | (string | null)[] | null; /** @@ -79,6 +77,10 @@ export class SegmentedButtonComponent implements AfterViewInit, ControlValueAcce @ContentChildren(FD_BUTTON_COMPONENT) _buttons: QueryList; + /** @hidden */ + @ContentChildren(FocusableItemDirective) + _focusableItems: QueryList; + /** * Value of segmented button can have 2 types: * - string, when there is no toggle mode and only 1 value can be chosen. @@ -166,12 +168,13 @@ export class SegmentedButtonComponent implements AfterViewInit, ControlValueAcce setDisabledState(isDisabled: boolean): void { this._isDisabled = isDisabled; this._toggleDisableButtons(isDisabled); + this._onRefresh$.next(); this._changeDetRef.detectChanges(); } /** @hidden */ private _listenToButtonChanges(): void { - this._buttons.changes + merge(this._buttons.changes, this._focusableItems.changes) .pipe(startWith(1), observeOn(asyncScheduler), takeUntilDestroyed(this._destroyRef)) .subscribe(() => { this._onRefresh$.next(); @@ -207,14 +210,12 @@ export class SegmentedButtonComponent implements AfterViewInit, ControlValueAcce /** @hidden */ private _handleTriggerOnButton(buttonComponent: ButtonComponent): void { if (!this._isButtonDisabled(buttonComponent)) { - if (!this._isButtonSelected(buttonComponent) && !this.toggle) { + if (!this.toggle) { this._buttons.forEach((button) => this._deselectButton(button)); this._selectButton(buttonComponent); this._propagateChange(); this._changeDetRef.markForCheck(); - } - - if (this.toggle) { + } else { this._toggleButton(buttonComponent); this._propagateChange(); this._changeDetRef.markForCheck(); @@ -224,8 +225,9 @@ export class SegmentedButtonComponent implements AfterViewInit, ControlValueAcce /** @hidden */ private _propagateChange(): void { - this.onChange(this._getValuesBySelected()); - this._currentValue = this._getValuesBySelected(); + const selectedValue = this._getValuesBySelected(); + this.onChange(selectedValue); + this._currentValue = selectedValue; } /** @hidden */ @@ -282,14 +284,23 @@ export class SegmentedButtonComponent implements AfterViewInit, ControlValueAcce /** @hidden */ private _toggleDisableButtons(disable: boolean): void { - if (!this._buttons) { + if (!this._buttons || !this._focusableItems) { return; } - this._buttons.forEach((button) => (button.disabled = disable)); + this._buttons.forEach((button) => button.disabled = disable); + + this._focusableItems.forEach((focusableItemDirective) => { + focusableItemDirective.setTabbable(!disable); + focusableItemDirective.fdkFocusableItem = !disable; + }); if (disable) { - this._buttons.forEach((button) => button.elementRef.nativeElement.setAttribute('disabled', 'true')); + this._buttons.forEach((button) => { + button.elementRef.nativeElement.role = 'option'; + this._listenToTriggerEvents(button); + }); } + this._changeDetRef.markForCheck(); } diff --git a/libs/docs/core/segmented-button/e2e/segmented-button.e2e-spec.ts b/libs/docs/core/segmented-button/e2e/segmented-button.e2e-spec.ts deleted file mode 100644 index c0fe521732f..00000000000 --- a/libs/docs/core/segmented-button/e2e/segmented-button.e2e-spec.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { - acceptAlert, - click, - getAttributeByName, - getElementClass, - getText, - refreshPage, - waitForElDisplayed -} from '../../../../../e2e'; -import { SegmentedButtonPo } from './segmented-button.po'; - -describe('Select component:', () => { - const segmentedButtonPage = new SegmentedButtonPo(); - const { - toggleExample, - defaultExample, - complexExample, - formExample, - button, - firstDefaulSegment, - secondDefaulSegment, - firstFormSegment, - firstFormButtonsSection, - secondFormButtonsSection, - toggled, - chosenValue - } = segmentedButtonPage; - - beforeAll(async () => { - await segmentedButtonPage.open(); - }, 1); - - afterEach(async () => { - await refreshPage(); - await segmentedButtonPage.waitForRoot(); - await waitForElDisplayed(segmentedButtonPage.title); - }, 2); - - describe('Select modes', () => { - it('should check choosing value in Default Example', async () => { - await checkDefaultExample(secondDefaulSegment, firstDefaulSegment); - await checkDefaultExample(firstDefaulSegment, secondDefaulSegment); - }); - - it('should check choosing value in Toogle Example', async () => { - for (let i = 1; i < 3; i++) { - await click(toggleExample + button, i); - await expect(await getElementClass(toggleExample + button, i)).toContain( - toggled, - `button with index ${i} is not toggled` - ); - switch (i) { - case 0: - await expect(await getText(toggleExample + chosenValue)).toContain('first'); - break; - case 1: - await expect(await getText(toggleExample + chosenValue)).toContain('first,second'); - break; - case 2: - await expect(await getText(toggleExample + chosenValue)).toContain('first,second,third'); - break; - } - } - for (let i = 0; i < 3; i++) { - await click(toggleExample + button, i); - await expect(await getElementClass(toggleExample + button, i)).not.toContain( - toggled, - `button with index ${i} is not toggled` - ); - } - }); - - it('should check choosing value in Complex Example', async () => { - for (let i = 0; i < 3; i++) { - await click(complexExample + button, i); - await acceptAlert(); - await expect(await getElementClass(complexExample + button, i)).toContain( - toggled, - `button with index ${i} is not toggled` - ); - switch (i) { - case 0: - await expect(await getText(complexExample + chosenValue)).toEqual('value: first'); - break; - case 1: - await expect(await getText(complexExample + chosenValue)).toEqual('value: second'); - break; - case 2: - await expect(await getText(complexExample + chosenValue)).toEqual('value: third'); - break; - } - } - }); - - it('should check choosing value in Form Example', async () => { - for (let i = 0; i < 3; i++) { - await click(firstFormSegment + button, i); - await expect(await getElementClass(firstFormSegment + button, i)).toContain( - toggled, - `button with index ${i} is not toggled` - ); - switch (i) { - case 0: - await expect( - ( - await getText(formExample + firstFormButtonsSection + chosenValue + ':nth-child(4)') - ).trim() - ).toEqual('Value: first'); - break; - case 1: - await expect( - ( - await getText(formExample + firstFormButtonsSection + chosenValue + ':nth-child(4)') - ).trim() - ).toEqual('Value: second'); - break; - case 2: - await expect( - ( - await getText(formExample + firstFormButtonsSection + chosenValue + ':nth-child(4)') - ).trim() - ).toEqual('Value: third'); - break; - } - } - }); - - it('should check disabled form in Form Example', async () => { - for (let i = 0; i < 3; i++) { - await expect( - await getAttributeByName(formExample + secondFormButtonsSection + button, 'disabled', i) - ).toBe('true'); - } - }); - - describe('check orientation', () => { - it('should check RTL and LTR orientation', async () => { - await segmentedButtonPage.checkRtlSwitch(); - }); - }); - }); - - async function checkDefaultExample(mainSection: string, secondarySection): Promise { - for (let i = 0; i < 3; i++) { - await click(mainSection + button, i); - await expect(await getElementClass(mainSection + button, i)).toContain( - toggled, - `button with index ${i} is not toggled` - ); - await expect(await getElementClass(secondarySection + button, i)).toContain( - toggled, - `button with index ${i} is not toggled` - ); - switch (i) { - case 0: - await expect(await getText(defaultExample + chosenValue)).toEqual('value: first'); - break; - case 1: - await expect(await getText(defaultExample + chosenValue)).toEqual('value: second'); - break; - case 2: - await expect(await getText(defaultExample + chosenValue)).toEqual('value: third'); - break; - } - } - } -}); diff --git a/libs/docs/core/segmented-button/e2e/segmented-button.po.ts b/libs/docs/core/segmented-button/e2e/segmented-button.po.ts deleted file mode 100644 index 3109efec3ab..00000000000 --- a/libs/docs/core/segmented-button/e2e/segmented-button.po.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { CoreBaseComponentPo, waitForElDisplayed } from '../../../../../e2e'; - -export class SegmentedButtonPo extends CoreBaseComponentPo { - url = '/segmented-button'; - - defaultExample = 'fd-segmented-button-default-example '; - toggleExample = 'fd-segmented-button-toggle-example '; - complexExample = 'fd-segmented-button-complex-example '; - formExample = 'fd-segmented-button-form-example '; - - firstDefaulSegment = this.defaultExample + '.fd-segmented-button:nth-child(2) '; - secondDefaulSegment = this.defaultExample + '.fd-segmented-button:nth-child(4) '; - firstFormSegment = this.formExample + 'form div:nth-child(1) fd-segmented-button '; - firstFormButtonsSection = 'div:nth-child(1) '; - secondFormButtonsSection = 'div:nth-child(2) '; - button = '.fd-button'; - - toggled = 'fd-button--toggled'; - chosenValue = 'small'; - - async open(): Promise { - await super.open(this.url); - await this.waitForRoot(); - await waitForElDisplayed(this.title); - } - - async getScreenshotFolder(): Promise> { - return super.getScreenshotFolder(this.url); - } - - async saveExampleBaselineScreenshot(specName: string = 'segmented-button'): Promise { - await super.saveExampleBaselineScreenshot(specName, this.getScreenshotFolder()); - } - - async compareWithBaseline(specName: string = 'segmented-button'): Promise { - return super.compareWithBaseline(specName, await this.getScreenshotFolder()); - } -} diff --git a/libs/docs/core/segmented-button/e2e/tsconfig.json b/libs/docs/core/segmented-button/e2e/tsconfig.json deleted file mode 100644 index 2d345834a6d..00000000000 --- a/libs/docs/core/segmented-button/e2e/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../../../../../e2e/tsconfig.json", - "include": ["./**/*.e2e-spec.ts"] -} diff --git a/libs/docs/core/segmented-button/examples/segmented-button-complex-example/segmented-button-complex-example.component.ts b/libs/docs/core/segmented-button/examples/segmented-button-complex-example/segmented-button-complex-example.component.ts index 39526f14b1f..46436c15e73 100644 --- a/libs/docs/core/segmented-button/examples/segmented-button-complex-example/segmented-button-complex-example.component.ts +++ b/libs/docs/core/segmented-button/examples/segmented-button-complex-example/segmented-button-complex-example.component.ts @@ -15,7 +15,12 @@ export class SegmentedButtonComplexExampleComponent { currentValue = ''; handleValueChange(value: string): void { - this.currentValue = value; - alert(`Current value changed to ${value}`); + const index = this.currentValue.indexOf(value); + if (index === -1) { + this.currentValue = this.currentValue ? `${this.currentValue},${value}` : value; + } else { + this.currentValue = this.currentValue.split(',').filter(v => v !== value).join(','); + } + alert(`Current value changed to ${this.currentValue}`); } } diff --git a/libs/docs/core/segmented-button/examples/segmented-button-form-example/segmented-button-form-example.component.ts b/libs/docs/core/segmented-button/examples/segmented-button-form-example/segmented-button-form-example.component.ts index eecd583c86e..215243e2d84 100644 --- a/libs/docs/core/segmented-button/examples/segmented-button-form-example/segmented-button-form-example.component.ts +++ b/libs/docs/core/segmented-button/examples/segmented-button-form-example/segmented-button-form-example.component.ts @@ -18,4 +18,13 @@ export class SegmentedButtonFormExampleComponent { disabled: true }) }); + + constructor() { + setTimeout(() => { + this.customForm.controls['basic'].disable(); + }, 1000); + setTimeout(() => { + this.customForm.controls['basic'].enable(); + }, 2000); + } }