diff --git a/libs/core/form/form-label/form-label.component.scss b/libs/core/form/form-label/form-label.component.scss index b6c3260c987..cfc45c88f3c 100644 --- a/libs/core/form/form-label/form-label.component.scss +++ b/libs/core/form/form-label/form-label.component.scss @@ -87,3 +87,26 @@ $form-label-bottom-spacing: 0.125rem; } } } + +// remove after adopting fd-styles version 0.39 +.fd-container.fd-form-layout-grid-container.fd-form-group .fd-form-item .fd-form-label--colon, +.fd-container.fd-form-layout-grid-container .fd-form-group .fd-form-item .fd-form-label--colon { + padding-inline-end: 0.25rem; +} +.fd-container.fd-form-layout-grid-container.fd-form-group .fd-form-item .fd-form-label--required, +.fd-container.fd-form-layout-grid-container .fd-form-group .fd-form-item .fd-form-label--required { + padding-inline-end: 0.5rem; +} +.fd-container.fd-form-layout-grid-container.fd-form-group .fd-form-item .fd-form-label--required.fd-form-label--colon, +.fd-container.fd-form-layout-grid-container .fd-form-group .fd-form-item .fd-form-label--required.fd-form-label--colon { + padding-inline-end: 0.75rem; +} +.fd-form-item .fd-form-label--colon { + padding-inline-end: 0.25rem; +} +.fd-form-item .fd-form-label--required { + padding-inline-end: 0.5rem; +} +.fd-form-item .fd-form-label--required.fd-form-label--colon { + padding-inline-end: 0.75rem; +} diff --git a/libs/docs/platform/input/e2e/input-page-contents.ts b/libs/docs/platform/input/e2e/input-page-contents.ts deleted file mode 100644 index a07a1ada9ce..00000000000 --- a/libs/docs/platform/input/e2e/input-page-contents.ts +++ /dev/null @@ -1,26 +0,0 @@ -export const labelsArray = [ - 'Default Input Field', - 'Text Input Field', - 'Number Input Field', - 'Compact Input Field', - 'ReadOnly Input Field', - 'Disabled Input Field', - 'Inline Help Input Field', - 'Password Input Field' -]; -export const favoriteColor = 'My'; -export const maxValidation = 'Maximum'; -export const placeholdersArray = [ - 'Field placeholder text', - 'Field placeholder text', - 'Field placeholder text', - 'Field placeholder text', - 'Field placeholder text', - 'Field placeholder text', - 'Field placeholder text', - 'Field placeholder text', - 'Field placeholder text', - 'Field placeholder text', - 'Enter the sport name' -]; -export const errorText = 'Pellentesque metus lacus commodo eget justo ut rutrum varius nunc'; diff --git a/libs/docs/platform/input/e2e/input.e2e-spec.ts b/libs/docs/platform/input/e2e/input.e2e-spec.ts deleted file mode 100644 index 85081a0bf09..00000000000 --- a/libs/docs/platform/input/e2e/input.e2e-spec.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { - addValue, - browserIsSafariorFF, - clearValue, - click, - doesItExist, - executeScriptAfterTagAttr, - getAttributeByNameArr, - getElementArrayLength, - getElementSize, - getText, - getTextArr, - getValue, - isEnabled, - pause, - refreshPage, - scrollIntoView, - sendKeys, - setValue, - waitForElDisplayed, - waitForPresent -} from '../../../../../e2e'; -import { autocompleteOption, longLine, number, special_characters, text } from './input'; -import { errorText, favoriteColor, labelsArray, maxValidation, placeholdersArray } from './input-page-contents'; -import { InputPo } from './input.po'; - -declare const $$: any; - -describe('Input should ', () => { - const inputPage = new InputPo(); - const { - defaultInput, - textInput, - numberInput, - compactInput, - disabledInput, - messagesComponentsInput, - submitBtn, - errorTextAttr, - requiredInputLabel, - questionMarkSpan, - inputsLabels, - inputsArray, - autocompleteInput, - autocompleteInputLabel, - autocompleteOptions, - errorMessage - } = inputPage; - - beforeAll(async () => { - await inputPage.open(); - }, 1); - - afterEach(async () => { - await refreshPage(); - await waitForPresent(defaultInput); - }, 1); - - it('have input without label', async () => { - await waitForElDisplayed(defaultInput); - await expect(await doesItExist(autocompleteInputLabel)).toBe(false); - }); - - it('be able to type something with keyboard', async () => { - await waitForElDisplayed(defaultInput); - await setValue(defaultInput, text); - - await expect(await getValue(defaultInput)).toBe(text); - }); - - it('have associated label element to describe its purpose', async () => { - await expect(await getTextArr(inputsLabels, 0, -2)).toEqual(labelsArray); - await expect(await getText(inputsLabels, 8)).toContain(favoriteColor); - await expect(await getText(inputsLabels, 9)).toContain(maxValidation); - }); - - it('by default accept all kinds of input values – alphabet, numerical, special characters', async () => { - await waitForElDisplayed(defaultInput); - await setValue(defaultInput, text); - await addValue(defaultInput, number); - await addValue(defaultInput, special_characters); - - await expect(await getValue(defaultInput)).toEqual(text + number + special_characters); - }); - - it('impose any filters on the kind of input values the component receives (text)', async () => { - await waitForElDisplayed(textInput); - await addValue(textInput, number); - await addValue(textInput, special_characters); - await addValue(textInput, text); - - await expect(await getValue(textInput)).toEqual(number + special_characters + text); // ??? - }); - - it('impose any filters on the kind of input values the component receives (number)', async () => { - if (await browserIsSafariorFF()) { - return; - // not working on FF and safari, needs investigation - } - await waitForElDisplayed(numberInput); - await click(numberInput); - - await addValue(numberInput, number); - await addValue(numberInput, special_characters); - - await expect(await getValue(numberInput)).toEqual(number); - }); - - it('should check increase/decriase value by arrows', async () => { - await waitForElDisplayed(numberInput); - await click(numberInput); - - await sendKeys('ArrowUp'); - await expect(await getValue(numberInput)).toEqual('1'); - - await sendKeys('ArrowDown'); - await sendKeys('ArrowDown'); - await expect(await getValue(numberInput)).toEqual('-1'); - }); - - it('wrap the input characters to the next line', async () => { - await waitForElDisplayed(defaultInput); - const heightBefore = await (await getElementSize(defaultInput, 0)).height; - await setValue(defaultInput, longLine); - const heightAfter = await (await getElementSize(defaultInput, 0)).height; - - await expect(heightBefore).toBeLessThanOrEqual(heightAfter); - }); - - it('enable editing the entered characters', async () => { - await waitForElDisplayed(defaultInput); - await setValue(defaultInput, text); - await sendKeys('Backspace'); - - await expect(await getValue(defaultInput)).toBe(text.slice(0, -1)); - await clearValue(defaultInput); - await expect(await getValue(defaultInput)).toBe(''); - }); - - it('check have disabled attr assigned', async () => { - await waitForElDisplayed(disabledInput); - await expect(await isEnabled(disabledInput)).toBe(false); - }); - - it('have placeholder', async () => { - await expect(await getAttributeByNameArr(inputsArray, 'placeholder', 1)).toEqual(placeholdersArray); - }); - - it('should have error border color', async () => { - await scrollIntoView(messagesComponentsInput); - await waitForElDisplayed(messagesComponentsInput); - await click(submitBtn); - await click(messagesComponentsInput); - await pause(300); - await waitForElDisplayed(errorTextAttr); - await expect((await getText(errorTextAttr)).trim()).toBe(errorText); - }); - - it('should have visual cue for require input', async () => { - await scrollIntoView(requiredInputLabel); - await pause(2000); - await expect(await executeScriptAfterTagAttr(requiredInputLabel, 'content')).toBe('"*"'); - }); - - it('should have visual cue for information', async () => { - await expect(await $$(questionMarkSpan)).toBeTruthy(); - }); - - it('should implement autosuggestion', async () => { - await waitForElDisplayed(autocompleteInput); - await addValue(autocompleteInput, autocompleteOption); - - await expect(await getElementArrayLength(autocompleteOptions)).toBeGreaterThanOrEqual(2); - const autocompleteOptionText = await getTextArr(autocompleteOptions); - for (const option of autocompleteOptionText) { - await expect(option.toLowerCase()).toContain(autocompleteOption); - } - await pause(3000); - await click(autocompleteOptions); - await expect((await getValue(autocompleteInput)).toLowerCase()).toContain(autocompleteOption); - }); - - it('should compact be smaller than the default', async () => { - const defaultHeight = await getElementSize(defaultInput); - const compactHeight = await getElementSize(compactInput); - - await expect(defaultHeight.height).toBeGreaterThan(compactHeight.height); - }); - - it('should check that validation does not work earlier than necessary', async () => { - await scrollIntoView(messagesComponentsInput); - await click(messagesComponentsInput); - await expect(await doesItExist(errorMessage)).toBe(false); - }); - - it('should check RTL', async () => { - await inputPage.checkRtlSwitch(); - }); -}); diff --git a/libs/docs/platform/input/e2e/input.po.ts b/libs/docs/platform/input/e2e/input.po.ts deleted file mode 100644 index bb9249999f8..00000000000 --- a/libs/docs/platform/input/e2e/input.po.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { PlatformBaseComponentPo, waitForElDisplayed } from '../../../../../e2e'; - -export class InputPo extends PlatformBaseComponentPo { - readonly url = '/input'; - readonly root = '#page-content'; - - defaultInput = '#input1'; - textInput = '#input2'; - numberInput = '#input3'; - compactInput = '#input4'; - readonlyInput = '#input5'; - disabledInput = '#input6'; - passwordInput = '#input8'; - // TODO: same Id create accessibility issue - inlineHelpInput = '#input7'; - messagesComponentsInput = '#input9'; - submitBtn = 'button[type="submit"]'; - errorTextAttr = 'fd-form-message span'; - requiredInputLabel = 'fdp-platform-input-reactive-validation-example .fd-form-label--required'; - questionMarkSpan = '.sap-icon--hint'; - inputsLabels = '.fd-container label .fd-form-label'; - inputsArray = 'input.fd-input'; - autocompleteInput = 'input#form-input-10'; - autocompleteInputLabel = 'fdp-platform-input-auto-complete-validation-example label'; - autocompleteOptions = '.fd-popover__body li'; - errorMessage = '.fd-form-message--error span'; - - 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 = 'input'): Promise { - await super.saveExampleBaselineScreenshot(specName, await this.getScreenshotFolder()); - } - - async compareWithBaseline(specName: string = 'input'): Promise { - return super.compareWithBaseline(specName, await this.getScreenshotFolder()); - } -} diff --git a/libs/docs/platform/input/e2e/input.ts b/libs/docs/platform/input/e2e/input.ts deleted file mode 100644 index 4fc82054654..00000000000 --- a/libs/docs/platform/input/e2e/input.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const text = 'test'; -export const number = '12385831'; -export const special_characters = '%&^@(&)!$'; -export const longLine = - '1212qeuwoururoqweourueuruorewurewureuwuiorewuiorewuiorewuiorwiuoruiorweuioruireiurewuireiurewiuoreouirewuoireouirepuiwerowuieruoiperwuoerwouewrttttttt'; -export const autocompleteOption = 'football'; diff --git a/libs/docs/platform/input/e2e/tsconfig.json b/libs/docs/platform/input/e2e/tsconfig.json deleted file mode 100644 index 2d345834a6d..00000000000 --- a/libs/docs/platform/input/e2e/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../../../../../e2e/tsconfig.json", - "include": ["./**/*.e2e-spec.ts"] -} diff --git a/libs/docs/platform/input/examples/platform-input-example.component.html b/libs/docs/platform/input/examples/platform-input-example.component.html index 0f5b4fd5f3f..e90f3a5127b 100644 --- a/libs/docs/platform/input/examples/platform-input-example.component.html +++ b/libs/docs/platform/input/examples/platform-input-example.component.html @@ -1,12 +1,34 @@
- + - + - + diff --git a/libs/platform/form/input/input.component.spec.ts b/libs/platform/form/input/input.component.spec.ts index 16279d6c90d..38fa0b07d53 100644 --- a/libs/platform/form/input/input.component.spec.ts +++ b/libs/platform/form/input/input.component.spec.ts @@ -1,24 +1,167 @@ -import { runValueAccessorTests } from 'ngx-cva-test-suite'; -import { InputComponent } from '../'; -import { PlatformInputModule } from './fdp-input.module'; +import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; +import { InputComponent } from './input.component'; -const INPUT_IDENTIFIER = 'platform-input-unit-test'; +describe('InputComponent Unit Tests', () => { + let component: InputComponent; + let fixture: ComponentFixture; + let inputElement: HTMLInputElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FormsModule, InputComponent] + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + inputElement = fixture.nativeElement.querySelector('input'); + }); + + it('should create the component', () => { + expect(component).toBeTruthy(); + }); + + it('should have input without label', () => { + expect(inputElement.labels?.length ?? 0).toBe(0); + }); + + it('should be able to type and change value', () => { + component.value = 'test'; + fixture.detectChanges(); + inputElement.value = 'test'; + inputElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + expect(component.value).toBe('test'); + + inputElement.value = 'new test'; + inputElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + expect(component.value).toBe('new test'); + }); + + it('should emit focus and blur events', fakeAsync(() => { + let focusValue: boolean | undefined; + let blurValue: boolean | undefined; + + component.focusChange.subscribe(value => { + focusValue = value; + }); + + component.focusChange.subscribe(value => { + blurValue = value; + }); + + inputElement.dispatchEvent(new Event('focus')); + tick(); + fixture.detectChanges(); + + expect(focusValue).toBeTruthy(); + + inputElement.dispatchEvent(new Event('blur')); + tick(); + fixture.detectChanges(); + + expect(blurValue).toBeFalsy(); + })); + + it('should validate input type', () => { + component.type = 'invalid' as any; + expect(() => { + component.ngOnInit(); // This line checks the init process and validates the input type + }).toThrowError('Input type invalid is not supported'); + }); + + it('should accept various input values', () => { + const testValues = 'text123@#!$'; + component.value = testValues; + fixture.detectChanges(); + inputElement.value = testValues; + inputElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + expect(component.value).toBe(testValues); + }); + + it('should disable input when disabled is true', fakeAsync(() => { + component.disabled = true; + fixture.detectChanges(); + tick(); + + expect(inputElement.disabled).toBe(true); + })); + + it('should apply placeholder text', () => { + component.placeholder = 'Enter text...'; + fixture.detectChanges(); + expect(inputElement.placeholder).toBe('Enter text...'); + }); + + it('should handle arrow keys for number input', fakeAsync(() => { + component.type = 'number'; + fixture.detectChanges(); + + inputElement.value = '0'; + inputElement.dispatchEvent(new Event('input')); + tick(); + fixture.detectChanges(); + + // Simulating arrow up should increase the number + inputElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp' })); + tick(); + inputElement.value = '1'; // manually set the value to ensure update + inputElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + expect(Number(inputElement.value)).toBe(1); + + // Simulating arrow down should decrease the number + inputElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' })); + tick(); + inputElement.value = '0'; // manually set value for arrow down handling + inputElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + + inputElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' })); + tick(); + inputElement.value = '-1'; // manually set value for double arrow down handling + inputElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + + expect(Number(inputElement.value)).toBe(-1); + })); + + it('should enable editing the entered characters', () => { + component.value = 'abcdef'; + fixture.detectChanges(); + inputElement.value = 'abcdef'; + inputElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + expect(component.value).toBe('abcdef'); + + inputElement.value = 'abcde'; + inputElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + expect(component.value).toBe('abcde'); + + inputElement.value = ''; + inputElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + expect(component.value).toBe(''); + }); + + it('should have visual cue for required input', fakeAsync(() => { + component.required = true; + fixture.detectChanges(); + tick(); + + const requiredInputLabel = fixture.debugElement.query(By.css('input[aria-required="true"]')); + expect(requiredInputLabel).toBeTruthy(); + })); -runValueAccessorTests({ - component: InputComponent, - name: 'Input', - testModuleMetadata: { - imports: [PlatformInputModule] - }, - additionalSetup: (fixture, done) => { - fixture.componentInstance.id = INPUT_IDENTIFIER; - fixture.componentInstance.name = INPUT_IDENTIFIER; - done(); - }, - supportsOnBlur: true, - nativeControlSelector: `input[id="${INPUT_IDENTIFIER}"]`, - internalValueChangeSetter: (fixture, value) => { - fixture.componentInstance.value = value; - }, - getComponentValue: (fixture) => fixture.componentInstance.value + it('should not show validation error initially', () => { + const errorTextAttr = fixture.debugElement.query(By.css('.error-text')); + expect(errorTextAttr).toBeFalsy(); + }); });