From 8e7d5b29f602e2e00d37c8334d7cf69e5eaa1495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bengt=20Wei=C3=9Fe?= Date: Tue, 13 Jan 2026 10:18:32 +0100 Subject: [PATCH 1/3] fix(textarea): add reflect option to disabled and readonly props To be consistent with ion-input and could be useful for styling purposes, since there is no "readonly" css class --- core/src/components/textarea/textarea.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/components/textarea/textarea.tsx b/core/src/components/textarea/textarea.tsx index 89646f6a247..ea5fa464186 100644 --- a/core/src/components/textarea/textarea.tsx +++ b/core/src/components/textarea/textarea.tsx @@ -132,7 +132,7 @@ export class Textarea implements ComponentInterface { /** * If `true`, the user cannot interact with the textarea. */ - @Prop() disabled = false; + @Prop({ reflect: true }) disabled = false; /** * The fill for the item. If `"solid"` the item will have a background. If @@ -177,7 +177,7 @@ export class Textarea implements ComponentInterface { /** * If `true`, the user cannot modify the value. */ - @Prop() readonly = false; + @Prop({ reflect: true }) readonly = false; /** * If `true`, the user must fill in a value before submitting a form. From a6a284fb8e9c0e271c9a8123507a4f4cb326e1b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bengt=20Wei=C3=9Fe?= Date: Thu, 15 Jan 2026 08:40:00 +0100 Subject: [PATCH 2/3] fix(textarea): update api.txt --- core/api.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/api.txt b/core/api.txt index 745d82786af..a43e79a733b 100644 --- a/core/api.txt +++ b/core/api.txt @@ -1873,7 +1873,7 @@ ion-textarea,prop,cols,number | undefined,undefined,false,true ion-textarea,prop,counter,boolean,false,false,false ion-textarea,prop,counterFormatter,((inputLength: number, maxLength: number) => string) | undefined,undefined,false,false ion-textarea,prop,debounce,number | undefined,undefined,false,false -ion-textarea,prop,disabled,boolean,false,false,false +ion-textarea,prop,disabled,boolean,false,false,true ion-textarea,prop,enterkeyhint,"done" | "enter" | "go" | "next" | "previous" | "search" | "send" | undefined,undefined,false,false ion-textarea,prop,errorText,string | undefined,undefined,false,false ion-textarea,prop,fill,"outline" | "solid" | undefined,undefined,false,false @@ -1886,7 +1886,7 @@ ion-textarea,prop,minlength,number | undefined,undefined,false,false ion-textarea,prop,mode,"ios" | "md",undefined,false,false ion-textarea,prop,name,string,this.inputId,false,false ion-textarea,prop,placeholder,string | undefined,undefined,false,false -ion-textarea,prop,readonly,boolean,false,false,false +ion-textarea,prop,readonly,boolean,false,false,true ion-textarea,prop,required,boolean,false,false,false ion-textarea,prop,rows,number | undefined,undefined,false,false ion-textarea,prop,shape,"round" | undefined,undefined,false,false From b333cb461796d813a65da7780996e145b9643bdf Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 5 Feb 2026 13:33:14 -0800 Subject: [PATCH 3/3] test(angular, react, vue): add reflect tests for inputs --- .../test/base/e2e/src/lazy/inputs.spec.ts | 211 ++++++++++-------- .../base/e2e/src/standalone/inputs.spec.ts | 28 +++ .../src/app/lazy/inputs/inputs.component.html | 28 ++- .../src/app/lazy/inputs/inputs.component.ts | 17 ++ .../standalone/app-standalone/app.routes.ts | 1 + .../home-page/home-page.component.html | 5 + .../standalone/inputs/inputs.component.html | 79 +++++++ .../app/standalone/inputs/inputs.component.ts | 80 +++++++ packages/react/test/base/src/pages/Inputs.tsx | 34 ++- .../tests/e2e/specs/components/inputs.cy.ts | 103 +++++---- packages/vue/test/base/src/views/Inputs.vue | 46 ++-- .../test/base/tests/e2e/specs/inputs.cy.js | 98 ++++---- 12 files changed, 528 insertions(+), 202 deletions(-) create mode 100644 packages/angular/test/base/e2e/src/standalone/inputs.spec.ts create mode 100644 packages/angular/test/base/src/app/standalone/inputs/inputs.component.html create mode 100644 packages/angular/test/base/src/app/standalone/inputs/inputs.component.ts diff --git a/packages/angular/test/base/e2e/src/lazy/inputs.spec.ts b/packages/angular/test/base/e2e/src/lazy/inputs.spec.ts index fdf2b38adc4..d8b8b4919bd 100644 --- a/packages/angular/test/base/e2e/src/lazy/inputs.spec.ts +++ b/packages/angular/test/base/e2e/src/lazy/inputs.spec.ts @@ -5,105 +5,126 @@ test.describe('Inputs', () => { await page.goto('/lazy/inputs'); }); - test('should have default values', async ({ page }) => { - // Check primary elements for default values - await expect(page.locator('ion-checkbox').first()).toHaveJSProperty('checked', true); - await expect(page.locator('ion-radio-group').first()).toHaveJSProperty('value', 'nes'); - await expect(page.locator('ion-toggle').first()).toHaveJSProperty('checked', true); - await expect(page.locator('ion-input').first()).toHaveJSProperty('value', 'some text'); - await expect(page.locator('ion-input-otp').first()).toHaveJSProperty('value', '1234'); - await expect(page.locator('ion-datetime').first()).toHaveJSProperty('value', '1994-03-15'); - await expect(page.locator('ion-select').first()).toHaveJSProperty('value', 'nes'); - await expect(page.locator('ion-range').first()).toHaveJSProperty('value', 50); - }); + test.describe('basic functionality', () => { + test('should have default values', async ({ page }) => { + // Check primary elements for default values + await expect(page.locator('ion-checkbox').first()).toHaveJSProperty('checked', true); + await expect(page.locator('ion-radio-group').first()).toHaveJSProperty('value', 'nes'); + await expect(page.locator('ion-toggle').first()).toHaveJSProperty('checked', true); + await expect(page.locator('ion-input').first()).toHaveJSProperty('value', 'some text'); + await expect(page.locator('ion-input-otp').first()).toHaveJSProperty('value', '1234'); + await expect(page.locator('ion-datetime').first()).toHaveJSProperty('value', '1994-03-15'); + await expect(page.locator('ion-select').first()).toHaveJSProperty('value', 'nes'); + await expect(page.locator('ion-range').first()).toHaveJSProperty('value', 50); + }); - test('should reset values', async ({ page }) => { - await page.locator('#reset-button').click(); - - // Check primary elements after reset - await expect(page.locator('ion-checkbox').first()).toHaveJSProperty('checked', false); - await expect(page.locator('ion-radio-group').first()).toHaveJSProperty('value', undefined); - await expect(page.locator('ion-toggle').first()).toHaveJSProperty('checked', false); - /** - * The `value` property gets set to undefined - * for these components, so we need to check - * that the value property is undefined. - */ - await expect(page.locator('ion-input').first()).toHaveJSProperty('value', undefined); - await expect(page.locator('ion-input-otp').first()).toHaveJSProperty('value', undefined); - await expect(page.locator('ion-datetime').first()).toHaveJSProperty('value', undefined); - await expect(page.locator('ion-select').first()).toHaveJSProperty('value', undefined); - await expect(page.locator('ion-range').first()).toHaveJSProperty('value', undefined); - }); + test('should reset values', async ({ page }) => { + await page.locator('#reset-button').click(); + + // Check primary elements after reset + await expect(page.locator('ion-checkbox').first()).toHaveJSProperty('checked', false); + await expect(page.locator('ion-radio-group').first()).toHaveJSProperty('value', undefined); + await expect(page.locator('ion-toggle').first()).toHaveJSProperty('checked', false); + /** + * The `value` property gets set to undefined + * for these components, so we need to check + * that the value property is undefined. + */ + await expect(page.locator('ion-input').first()).toHaveJSProperty('value', undefined); + await expect(page.locator('ion-input-otp').first()).toHaveJSProperty('value', undefined); + await expect(page.locator('ion-datetime').first()).toHaveJSProperty('value', undefined); + await expect(page.locator('ion-select').first()).toHaveJSProperty('value', undefined); + await expect(page.locator('ion-range').first()).toHaveJSProperty('value', undefined); + }); - test('should set values', async ({ page }) => { - await page.locator('#reset-button').click(); - await page.locator('#set-button').click(); - - // Check primary elements after setting values - await expect(page.locator('ion-checkbox').first()).toHaveJSProperty('checked', true); - await expect(page.locator('ion-radio-group').first()).toHaveJSProperty('value', 'nes'); - await expect(page.locator('ion-toggle').first()).toHaveJSProperty('checked', true); - await expect(page.locator('ion-input').first()).toHaveJSProperty('value', 'some text'); - await expect(page.locator('ion-input-otp').first()).toHaveJSProperty('value', '1234'); - await expect(page.locator('ion-datetime').first()).toHaveJSProperty('value', '1994-03-15'); - await expect(page.locator('ion-select').first()).toHaveJSProperty('value', 'nes'); - await expect(page.locator('ion-range').first()).toHaveJSProperty('value', 50); - }); + test('should set values', async ({ page }) => { + await page.locator('#reset-button').click(); + await page.locator('#set-button').click(); + + // Check primary elements after setting values + await expect(page.locator('ion-checkbox').first()).toHaveJSProperty('checked', true); + await expect(page.locator('ion-radio-group').first()).toHaveJSProperty('value', 'nes'); + await expect(page.locator('ion-toggle').first()).toHaveJSProperty('checked', true); + await expect(page.locator('ion-input').first()).toHaveJSProperty('value', 'some text'); + await expect(page.locator('ion-input-otp').first()).toHaveJSProperty('value', '1234'); + await expect(page.locator('ion-datetime').first()).toHaveJSProperty('value', '1994-03-15'); + await expect(page.locator('ion-select').first()).toHaveJSProperty('value', 'nes'); + await expect(page.locator('ion-range').first()).toHaveJSProperty('value', 50); + }); - test('should update angular when values change', async ({ page }) => { - await page.locator('#reset-button').click(); - - await page.locator('ion-checkbox#first-checkbox').click(); - await page.locator('ion-radio').first().click(); - await page.locator('ion-toggle').first().click(); - - await page.locator('ion-input').nth(0).locator('input').fill('hola'); - await page.locator('ion-input').nth(0).locator('input').blur(); - - await page.locator('ion-input-otp input').nth(0).fill('1'); - await page.locator('ion-input-otp input').nth(1).fill('2'); - await page.locator('ion-input-otp input').nth(2).fill('3'); - await page.locator('ion-input-otp input').nth(3).fill('4'); - await page.locator('ion-input-otp input').nth(3).blur(); - - // Set date to 1994-03-14 - await page.locator('ion-datetime').first().click(); - await page.locator('ion-datetime').first().locator('.calendar-day:not([disabled])').first().click(); - - await page.locator('ion-select#game-console').click(); - await expect(page.locator('ion-alert')).toBeVisible(); - // Playstation option - await page.locator('ion-alert .alert-radio-button').nth(3).click(); - // Click confirm button - await page.locator('ion-alert .alert-button:not(.alert-button-role-cancel)').click(); - - // Check note text (Angular binding updates) - await expect(page.locator('#checkbox-note')).toHaveText('true'); - await expect(page.locator('#radio-note')).toHaveText('nes'); - await expect(page.locator('#toggle-note')).toHaveText('true'); - await expect(page.locator('#input-note')).toHaveText('hola'); - await expect(page.locator('#input-otp-note')).toHaveText('1234'); - await expect(page.locator('#datetime-note')).toHaveText('1994-03-14'); - await expect(page.locator('#select-note')).toHaveText('ps'); - }); + test('should update angular when values change', async ({ page }) => { + await page.locator('#reset-button').click(); - test('should update values when erasing input', async ({ page }) => { - // Focus the input and press backspace to remove last character - await page.locator('ion-input').nth(0).locator('input').click(); - await page.locator('ion-input').nth(0).locator('input').press('Backspace'); - // Check mirror element reflects the change - await expect(page.locator('ion-input').nth(1)).toHaveJSProperty('value', 'some tex'); - // Check note text (Angular binding) - await expect(page.locator('#input-note')).toHaveText('some tex'); - - // Focus the last OTP input and press backspace - await page.locator('ion-input-otp input').last().click(); - await page.locator('ion-input-otp input').last().press('Backspace'); - // Check mirror element reflects the change - await expect(page.locator('ion-input-otp').nth(1)).toHaveJSProperty('value', '123'); - // Check note text (Angular binding) - await expect(page.locator('#input-otp-note')).toHaveText('123'); + await page.locator('ion-checkbox#first-checkbox').click(); + await page.locator('ion-radio').first().click(); + await page.locator('ion-toggle').first().click(); + + await page.locator('ion-input').nth(0).locator('input').fill('hola'); + await page.locator('ion-input').nth(0).locator('input').blur(); + + await page.locator('ion-input-otp input').nth(0).fill('1'); + await page.locator('ion-input-otp input').nth(1).fill('2'); + await page.locator('ion-input-otp input').nth(2).fill('3'); + await page.locator('ion-input-otp input').nth(3).fill('4'); + await page.locator('ion-input-otp input').nth(3).blur(); + + // Set date to 1994-03-14 + await page.locator('ion-datetime').first().click(); + await page.locator('ion-datetime').first().locator('.calendar-day:not([disabled])').first().click(); + + await page.locator('ion-select#game-console').click(); + await expect(page.locator('ion-alert')).toBeVisible(); + // Playstation option + await page.locator('ion-alert .alert-radio-button').nth(3).click(); + // Click confirm button + await page.locator('ion-alert .alert-button:not(.alert-button-role-cancel)').click(); + + // Check note text (Angular binding updates) + await expect(page.locator('#checkbox-note')).toHaveText('true'); + await expect(page.locator('#radio-note')).toHaveText('nes'); + await expect(page.locator('#toggle-note')).toHaveText('true'); + await expect(page.locator('#input-note')).toHaveText('hola'); + await expect(page.locator('#input-otp-note')).toHaveText('1234'); + await expect(page.locator('#datetime-note')).toHaveText('1994-03-14'); + await expect(page.locator('#select-note')).toHaveText('ps'); + }); + + test('should update values when erasing input', async ({ page }) => { + // Focus the input and press backspace to remove last character + await page.locator('ion-input').nth(0).locator('input').click(); + await page.locator('ion-input').nth(0).locator('input').press('Backspace'); + // Check mirror element reflects the change + await expect(page.locator('ion-input').nth(1)).toHaveJSProperty('value', 'some tex'); + // Check note text (Angular binding) + await expect(page.locator('#input-note')).toHaveText('some tex'); + + // Focus the last OTP input and press backspace + await page.locator('ion-input-otp input').last().click(); + await page.locator('ion-input-otp input').last().press('Backspace'); + // Check mirror element reflects the change + await expect(page.locator('ion-input-otp').nth(1)).toHaveJSProperty('value', '123'); + // Check note text (Angular binding) + await expect(page.locator('#input-otp-note')).toHaveText('123'); + }); + + test('should reflect props when component has a default value', async ({ page }) => { + // Disable inputs + await page.locator('#disable-button').click(); + + // Disabled prop + await expect(page.locator('ion-input').first()).toHaveAttribute('disabled', ''); + await expect(page.locator('ion-input-otp').first()).toHaveAttribute('disabled', ''); + await expect(page.locator('ion-textarea').first()).toHaveAttribute('disabled', ''); + + // Reset disabled state and set readonly state + await page.locator('#disable-button').click(); + await page.locator('#readonly-button').click(); + + // Readonly prop + await expect(page.locator('ion-input').first()).toHaveAttribute('readonly', ''); + await expect(page.locator('ion-input-otp').first()).toHaveAttribute('readonly', ''); + await expect(page.locator('ion-textarea').first()).toHaveAttribute('readonly', ''); + }); }); test.describe('updating text input refs', () => { diff --git a/packages/angular/test/base/e2e/src/standalone/inputs.spec.ts b/packages/angular/test/base/e2e/src/standalone/inputs.spec.ts new file mode 100644 index 00000000000..b5b17496ee2 --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/inputs.spec.ts @@ -0,0 +1,28 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Inputs', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/standalone/inputs'); + }); + + test.describe('basic functionality', () => { + test('should reflect props when component has a default value', async ({ page }) => { + // Disable inputs + await page.locator('#disable-button').click(); + + // Disabled prop + await expect(page.locator('ion-input')).toHaveAttribute('disabled', ''); + await expect(page.locator('ion-input-otp')).toHaveAttribute('disabled', ''); + await expect(page.locator('ion-textarea')).toHaveAttribute('disabled', ''); + + // Reset disabled state and set readonly state + await page.locator('#disable-button').click(); + await page.locator('#readonly-button').click(); + + // Readonly prop + await expect(page.locator('ion-input')).toHaveAttribute('readonly', ''); + await expect(page.locator('ion-input-otp')).toHaveAttribute('readonly', ''); + await expect(page.locator('ion-textarea')).toHaveAttribute('readonly', ''); + }); + }); +}); diff --git a/packages/angular/test/base/src/app/lazy/inputs/inputs.component.html b/packages/angular/test/base/src/app/lazy/inputs/inputs.component.html index c63e1c2792e..28381edc619 100644 --- a/packages/angular/test/base/src/app/lazy/inputs/inputs.component.html +++ b/packages/angular/test/base/src/app/lazy/inputs/inputs.component.html @@ -11,18 +11,18 @@ DateTime - + {{datetime}} DateTime Mirror - + {{datetime}} - + No Game Console NES Nintendo64 @@ -48,7 +48,7 @@ - + Toggle {{toggle}} @@ -62,27 +62,27 @@ - + {{input}} - + {{input}} - Input OTP + Input OTP {{inputOtp}} - Input OTP Mirror + Input OTP Mirror {{inputOtp}} - + Checkbox {{checkbox}} @@ -97,7 +97,7 @@ - Radio + Radio {{radio}} @@ -110,14 +110,20 @@ - + Range + + + +

Set values Reset values + Toggle Disabled + Toggle Readonly

diff --git a/packages/angular/test/base/src/app/lazy/inputs/inputs.component.ts b/packages/angular/test/base/src/app/lazy/inputs/inputs.component.ts index e53f73948c3..fb04b7d1467 100644 --- a/packages/angular/test/base/src/app/lazy/inputs/inputs.component.ts +++ b/packages/angular/test/base/src/app/lazy/inputs/inputs.component.ts @@ -16,6 +16,11 @@ export class InputsComponent { select? = 'nes'; changes = 0; range? = 50; + textarea? = 'some text'; + + // States + isDisabled = false; + isReadonly = false; setValues() { console.log('set values'); @@ -27,6 +32,7 @@ export class InputsComponent { this.toggle = true; this.select = 'nes'; this.range = 50; + this.textarea = 'some text'; } resetValues() { @@ -39,6 +45,17 @@ export class InputsComponent { this.toggle = false; this.select = undefined; this.range = undefined; + this.textarea = undefined; + } + + toggleDisable() { + console.log(`toggle disable to ${!this.isDisabled}`); + this.isDisabled = !this.isDisabled; + } + + toggleReadonly() { + console.log(`toggle readonly to ${!this.isReadonly}`); + this.isReadonly = !this.isReadonly; } counter() { diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index 667ef672e8b..46508b144ce 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -7,6 +7,7 @@ export const routes: Routes = [ component: AppComponent, children: [ { path: '', loadComponent: () => import('../home-page/home-page.component').then(c => c.HomePageComponent) }, + { path: 'inputs', loadComponent: () => import('../inputs/inputs.component').then(c => c.InputsComponent) }, { path: 'menu-controller', loadComponent: () => import('../menu-controller/menu-controller.component').then(c => c.MenuControllerComponent) }, { path: 'action-sheet-controller', loadComponent: () => import('../action-sheet-controller/action-sheet-controller.component').then(c => c.ActionSheetControllerComponent) }, { path: 'popover', loadComponent: () => import('../popover/popover.component').then(c => c.PopoverComponent) }, diff --git a/packages/angular/test/base/src/app/standalone/home-page/home-page.component.html b/packages/angular/test/base/src/app/standalone/home-page/home-page.component.html index 6dbad643eb2..3aa3062f9ba 100644 --- a/packages/angular/test/base/src/app/standalone/home-page/home-page.component.html +++ b/packages/angular/test/base/src/app/standalone/home-page/home-page.component.html @@ -28,6 +28,11 @@ Icon Test + + + Inputs Test + + Reorder Group Test diff --git a/packages/angular/test/base/src/app/standalone/inputs/inputs.component.html b/packages/angular/test/base/src/app/standalone/inputs/inputs.component.html new file mode 100644 index 00000000000..d1334c479c2 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/inputs/inputs.component.html @@ -0,0 +1,79 @@ + + + + Inputs test + + + + +
+ + + DateTime + + {{form.value.datetime}} + + + + + No Game Console + NES + Nintendo64 + PlayStation + Sega Genesis + Sega Saturn + SNES + + {{form.value.select}} + + + + + Toggle + + {{form.value.toggle}} + + + + + {{form.value.input}} + + + + Input OTP + {{form.value.inputOtp}} + + + + + Checkbox + + {{form.value.checkbox}} + + + + + Radio + + {{form.value.radio}} + + + + + Range + + + + + + + + +
+

+ Set values + Reset values + Toggle Disabled + Toggle Readonly +

+
diff --git a/packages/angular/test/base/src/app/standalone/inputs/inputs.component.ts b/packages/angular/test/base/src/app/standalone/inputs/inputs.component.ts new file mode 100644 index 00000000000..7273fa5926a --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/inputs/inputs.component.ts @@ -0,0 +1,80 @@ +import { Component } from '@angular/core'; +import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { + IonHeader, IonToolbar, IonTitle, IonContent, IonList, IonItem, + IonLabel, IonDatetime, IonNote, IonSelect, IonSelectOption, + IonToggle, IonInput, IonInputOtp, IonCheckbox, IonRadioGroup, + IonRadio, IonRange, IonTextarea, IonButton +} from '@ionic/angular/standalone'; + +@Component({ + selector: 'app-inputs', + templateUrl: './inputs.component.html', + standalone: true, + imports: [ + ReactiveFormsModule, + IonHeader, IonToolbar, IonTitle, IonContent, IonList, IonItem, + IonLabel, IonDatetime, IonNote, IonSelect, IonSelectOption, + IonToggle, IonInput, IonInputOtp, IonCheckbox, IonRadioGroup, + IonRadio, IonRange, IonTextarea, IonButton + ], +}) +export class InputsComponent { + // Create the FormGroup + form = new FormGroup({ + datetime: new FormControl('1994-03-15'), + input: new FormControl('some text'), + inputOtp: new FormControl('1234'), + checkbox: new FormControl(true), + radio: new FormControl('nes'), + toggle: new FormControl(true), + select: new FormControl('nes'), + range: new FormControl(50), + textarea: new FormControl('some text'), + }); + + // States + isDisabled = false; + isReadonly = false; + + setValues() { + console.log('set values'); + this.form.patchValue({ + datetime: '1994-03-15', + input: 'some text', + inputOtp: '1234', + checkbox: true, + radio: 'nes', + toggle: true, + select: 'nes', + range: 50, + textarea: 'some text', + }); + } + + resetValues() { + console.log('reset values'); + // reset them each + this.form.patchValue({ + datetime: undefined, + input: undefined, + inputOtp: undefined, + checkbox: false, + radio: undefined, + toggle: false, + select: undefined, + range: undefined, + textarea: undefined, + }); + } + + toggleDisable() { + console.log(`toggle disable to ${!this.isDisabled}`); + this.isDisabled = !this.isDisabled; + } + + toggleReadonly() { + console.log(`toggle readonly to ${!this.isReadonly}`); + this.isReadonly = !this.isReadonly; + } +} diff --git a/packages/react/test/base/src/pages/Inputs.tsx b/packages/react/test/base/src/pages/Inputs.tsx index 7e5db11b5de..47ac3ac5328 100644 --- a/packages/react/test/base/src/pages/Inputs.tsx +++ b/packages/react/test/base/src/pages/Inputs.tsx @@ -66,6 +66,10 @@ const Inputs: React.FC = () => { const [segment, setSegment] = useState('dogs'); const [select, setSelect] = useState('apples'); + // States + const [isDisabled, setIsDisabled] = useState(false); + const [isReadonly, setIsReadonly] = useState(false); + const reset = () => { setCheckbox(false); setToggle(false); @@ -94,6 +98,14 @@ const Inputs: React.FC = () => { setSelect('bananas'); }; + const toggleDisable = () => { + setIsDisabled(!isDisabled); + }; + + const toggleReadonly = () => { + setIsReadonly(!isReadonly); + }; + return ( @@ -109,6 +121,7 @@ const Inputs: React.FC = () => { onIonChange={(e: IonSegmentCustomEvent) => { if (typeof e.detail.value === 'string') setSegment(e.detail.value); }} + disabled={isDisabled} > Dogs @@ -122,6 +135,7 @@ const Inputs: React.FC = () => { ) => setSearchbar(e.detail.value!)} + disabled={isDisabled} > @@ -137,6 +151,7 @@ const Inputs: React.FC = () => { ) => setCheckbox(e.detail.checked)} + disabled={isDisabled} > Checkbox @@ -146,6 +161,7 @@ const Inputs: React.FC = () => { ) => setToggle(e.detail.checked)} + disabled={isDisabled} > Toggle @@ -156,6 +172,8 @@ const Inputs: React.FC = () => { value={input} onIonInput={(e: IonInputCustomEvent) => setInput(e.detail.value!)} label="Input" + disabled={isDisabled} + readonly={isReadonly} > @@ -163,6 +181,8 @@ const Inputs: React.FC = () => { ) => setInputOtp(e.detail.value ?? '')} + disabled={isDisabled} + readonly={isReadonly} > @@ -174,6 +194,7 @@ const Inputs: React.FC = () => { max={100} value={range} onIonChange={(e: IonRangeCustomEvent) => setRange(e.detail.value as { lower: number; upper: number })} + disabled={isDisabled} > @@ -182,6 +203,8 @@ const Inputs: React.FC = () => { value={textarea} onIonInput={(e: IonTextareaCustomEvent) => setTextarea(e.detail.value!)} label="Textarea" + disabled={isDisabled} + readonly={isReadonly} > @@ -195,6 +218,8 @@ const Inputs: React.FC = () => { setDatetime(value); } }} + disabled={isDisabled} + readonly={isReadonly} > @@ -203,13 +228,13 @@ const Inputs: React.FC = () => { onIonChange={(e: IonRadioGroupCustomEvent) => setRadio(e.detail.value)} > - Red + Red - Green + Green - Blue + Blue @@ -218,6 +243,7 @@ const Inputs: React.FC = () => { value={select} onIonChange={(e: IonSelectCustomEvent>) => setSelect(e.detail.value)} label="Select" + disabled={isDisabled} > Apples Bananas @@ -241,6 +267,8 @@ const Inputs: React.FC = () => { Reset Values Set Values + Toggle Disabled + Toggle Readonly diff --git a/packages/react/test/base/tests/e2e/specs/components/inputs.cy.ts b/packages/react/test/base/tests/e2e/specs/components/inputs.cy.ts index b32468bd96e..55732f89cae 100644 --- a/packages/react/test/base/tests/e2e/specs/components/inputs.cy.ts +++ b/packages/react/test/base/tests/e2e/specs/components/inputs.cy.ts @@ -3,48 +3,69 @@ describe('Inputs', () => { cy.visit('/inputs') }) - it('should have default value', () => { - cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', false); - cy.get('ion-toggle').should('have.prop', 'checked').and('eq', false); - cy.get('ion-input').should('have.prop', 'value').and('eq', ''); - cy.get('ion-input-otp').should('have.prop', 'value').and('eq', ''); - cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 30, upper: 70 }); - cy.get('ion-textarea').should('have.prop', 'value').and('eq', ''); - cy.get('ion-searchbar').should('have.prop', 'value').and('eq', ''); - cy.get('ion-datetime').should('have.prop', 'value').and('eq', ''); - cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'red'); - cy.get('ion-segment').should('have.prop', 'value').and('eq', 'dogs'); - cy.get('ion-select').should('have.prop', 'value').and('eq', 'apples'); - }); + describe('basic functionality', () => { + it('should have default value', () => { + cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', false); + cy.get('ion-toggle').should('have.prop', 'checked').and('eq', false); + cy.get('ion-input').should('have.prop', 'value').and('eq', ''); + cy.get('ion-input-otp').should('have.prop', 'value').and('eq', ''); + cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 30, upper: 70 }); + cy.get('ion-textarea').should('have.prop', 'value').and('eq', ''); + cy.get('ion-searchbar').should('have.prop', 'value').and('eq', ''); + cy.get('ion-datetime').should('have.prop', 'value').and('eq', ''); + cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'red'); + cy.get('ion-segment').should('have.prop', 'value').and('eq', 'dogs'); + cy.get('ion-select').should('have.prop', 'value').and('eq', 'apples'); + }); + + it('should set/reset values', () => { + cy.get('ion-button#set').click(); - it('should set/reset values', () => { - cy.get('ion-button#set').click(); - - cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', true); - cy.get('ion-toggle').should('have.prop', 'checked').and('eq', true); - cy.get('ion-input').should('have.prop', 'value').and('eq', 'Hello World'); - cy.get('ion-input-otp').should('have.prop', 'value').and('eq', '1234'); - cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 10, upper: 90 }); - cy.get('ion-textarea').should('have.prop', 'value').and('eq', 'Lorem Ipsum'); - cy.get('ion-searchbar').should('have.prop', 'value').and('eq', 'Search Query'); - cy.get('ion-datetime').should('have.prop', 'value').and('eq', '2019-01-31'); - cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'blue'); - cy.get('ion-segment').should('have.prop', 'value').and('eq', 'cats'); - cy.get('ion-select').should('have.prop', 'value').and('eq', 'bananas'); - - cy.get('ion-button#reset').click(); - - cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', false); - cy.get('ion-toggle').should('have.prop', 'checked').and('eq', false); - cy.get('ion-input').should('have.prop', 'value').and('eq', ''); - cy.get('ion-input-otp').should('have.prop', 'value').and('eq', ''); - cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 30, upper: 70 }); - cy.get('ion-textarea').should('have.prop', 'value').and('eq', ''); - cy.get('ion-searchbar').should('have.prop', 'value').and('eq', ''); - cy.get('ion-datetime').should('have.prop', 'value').and('eq', ''); - cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'red'); - cy.get('ion-segment').should('have.prop', 'value').and('eq', 'dogs'); - cy.get('ion-select').should('have.prop', 'value').and('eq', 'apples'); + cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', true); + cy.get('ion-toggle').should('have.prop', 'checked').and('eq', true); + cy.get('ion-input').should('have.prop', 'value').and('eq', 'Hello World'); + cy.get('ion-input-otp').should('have.prop', 'value').and('eq', '1234'); + cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 10, upper: 90 }); + cy.get('ion-textarea').should('have.prop', 'value').and('eq', 'Lorem Ipsum'); + cy.get('ion-searchbar').should('have.prop', 'value').and('eq', 'Search Query'); + cy.get('ion-datetime').should('have.prop', 'value').and('eq', '2019-01-31'); + cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'blue'); + cy.get('ion-segment').should('have.prop', 'value').and('eq', 'cats'); + cy.get('ion-select').should('have.prop', 'value').and('eq', 'bananas'); + + cy.get('ion-button#reset').click(); + + cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', false); + cy.get('ion-toggle').should('have.prop', 'checked').and('eq', false); + cy.get('ion-input').should('have.prop', 'value').and('eq', ''); + cy.get('ion-input-otp').should('have.prop', 'value').and('eq', ''); + cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 30, upper: 70 }); + cy.get('ion-textarea').should('have.prop', 'value').and('eq', ''); + cy.get('ion-searchbar').should('have.prop', 'value').and('eq', ''); + cy.get('ion-datetime').should('have.prop', 'value').and('eq', ''); + cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'red'); + cy.get('ion-segment').should('have.prop', 'value').and('eq', 'dogs'); + cy.get('ion-select').should('have.prop', 'value').and('eq', 'apples'); + }); + + it('should reflect props when component has a default value', () => { + // Disable inputs + cy.get('ion-button#disable').click(); + + // Disabled prop + cy.get('ion-input').should('have.attr', 'disabled'); + cy.get('ion-input-otp').should('have.attr', 'disabled'); + cy.get('ion-textarea').should('have.attr', 'disabled'); + + // Reset disabled state and set readonly state + cy.get('ion-button#disable').click(); + cy.get('ion-button#readonly').click(); + + // Readonly prop + cy.get('ion-input').should('have.attr', 'readonly'); + cy.get('ion-input-otp').should('have.attr', 'readonly'); + cy.get('ion-textarea').should('have.attr', 'readonly'); + }); }); describe('updating text input refs', () => { diff --git a/packages/vue/test/base/src/views/Inputs.vue b/packages/vue/test/base/src/views/Inputs.vue index 28108f4f0dd..6bfe69984b2 100644 --- a/packages/vue/test/base/src/views/Inputs.vue +++ b/packages/vue/test/base/src/views/Inputs.vue @@ -8,7 +8,7 @@ Inputs - + Dogs @@ -18,7 +18,7 @@ - + @@ -30,48 +30,48 @@ - Checkbox + Checkbox - Toggle + Toggle - + - + - + - + Datetime - + - Red + Red - Green + Green - Blue + Blue - + Apples Bananas @@ -94,6 +94,8 @@ Reset Values Set Values + Toggle Disabled + Toggle Readonly @@ -171,6 +173,10 @@ export default defineComponent({ const segment = ref('dogs'); const select = ref('apples'); + // States + const isDisabled = ref(false); + const isReadonly = ref(false); + const reset = () => { checkbox.value = false; toggle.value = false; @@ -205,6 +211,14 @@ export default defineComponent({ select.value = 'bananas'; } + const toggleDisable = () => { + isDisabled.value = !isDisabled.value; + }; + + const toggleReadonly = () => { + isReadonly.value = !isReadonly.value; + }; + return { checkbox, toggle, @@ -217,9 +231,13 @@ export default defineComponent({ radio, segment, select, + isDisabled, + isReadonly, reset, - set + set, + toggleDisable, + toggleReadonly } } }); diff --git a/packages/vue/test/base/tests/e2e/specs/inputs.cy.js b/packages/vue/test/base/tests/e2e/specs/inputs.cy.js index 26c26f8ef95..f836496a5d0 100644 --- a/packages/vue/test/base/tests/e2e/specs/inputs.cy.js +++ b/packages/vue/test/base/tests/e2e/specs/inputs.cy.js @@ -2,48 +2,70 @@ describe('Inputs', () => { beforeEach(() => { cy.visit('/inputs') }) - it('should have default value', () => { - cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', false); - cy.get('ion-toggle').should('have.prop', 'checked').and('eq', false); - cy.get('ion-input').should('have.prop', 'value').and('eq', ''); - cy.get('ion-input-otp').should('have.prop', 'value').and('eq', ''); - cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 30, upper: 70 }); - cy.get('ion-textarea').should('have.prop', 'value').and('eq', ''); - cy.get('ion-searchbar').should('have.prop', 'value').and('eq', ''); - cy.get('ion-datetime').should('have.prop', 'value').and('eq', ''); - cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'red'); - cy.get('ion-segment').should('have.prop', 'value').and('eq', 'dogs'); - cy.get('ion-select').should('have.prop', 'value').and('eq', 'apples'); - }); + + describe('basic functionality', () => { + it('should have default value', () => { + cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', false); + cy.get('ion-toggle').should('have.prop', 'checked').and('eq', false); + cy.get('ion-input').should('have.prop', 'value').and('eq', ''); + cy.get('ion-input-otp').should('have.prop', 'value').and('eq', ''); + cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 30, upper: 70 }); + cy.get('ion-textarea').should('have.prop', 'value').and('eq', ''); + cy.get('ion-searchbar').should('have.prop', 'value').and('eq', ''); + cy.get('ion-datetime').should('have.prop', 'value').and('eq', ''); + cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'red'); + cy.get('ion-segment').should('have.prop', 'value').and('eq', 'dogs'); + cy.get('ion-select').should('have.prop', 'value').and('eq', 'apples'); + }); + + it('should set/reset values', () => { + cy.get('ion-button#set').click(); + + cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', true); + cy.get('ion-toggle').should('have.prop', 'checked').and('eq', true); + cy.get('ion-input').should('have.prop', 'value').and('eq', 'Hello World'); + cy.get('ion-input-otp').should('have.prop', 'value').and('eq', '1234'); + cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 10, upper: 90 }); + cy.get('ion-textarea').should('have.prop', 'value').and('eq', 'Lorem Ipsum'); + cy.get('ion-searchbar').should('have.prop', 'value').and('eq', 'Search Query'); + cy.get('ion-datetime').should('have.prop', 'value').and('eq', '2019-01-31'); + cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'blue'); + cy.get('ion-segment').should('have.prop', 'value').and('eq', 'cats'); + cy.get('ion-select').should('have.prop', 'value').and('eq', 'bananas'); - it('should set/reset values', () => { - cy.get('ion-button#set').click(); + cy.get('ion-button#reset').click(); - cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', true); - cy.get('ion-toggle').should('have.prop', 'checked').and('eq', true); - cy.get('ion-input').should('have.prop', 'value').and('eq', 'Hello World'); - cy.get('ion-input-otp').should('have.prop', 'value').and('eq', '1234'); - cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 10, upper: 90 }); - cy.get('ion-textarea').should('have.prop', 'value').and('eq', 'Lorem Ipsum'); - cy.get('ion-searchbar').should('have.prop', 'value').and('eq', 'Search Query'); - cy.get('ion-datetime').should('have.prop', 'value').and('eq', '2019-01-31'); - cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'blue'); - cy.get('ion-segment').should('have.prop', 'value').and('eq', 'cats'); - cy.get('ion-select').should('have.prop', 'value').and('eq', 'bananas'); + cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', false); + cy.get('ion-toggle').should('have.prop', 'checked').and('eq', false); + cy.get('ion-input').should('have.prop', 'value').and('eq', ''); + cy.get('ion-input-otp').should('have.prop', 'value').and('eq', ''); + cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 30, upper: 70 }); + cy.get('ion-textarea').should('have.prop', 'value').and('eq', ''); + cy.get('ion-searchbar').should('have.prop', 'value').and('eq', ''); + cy.get('ion-datetime').should('have.prop', 'value').and('eq', ''); + cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'red'); + cy.get('ion-segment').should('have.prop', 'value').and('eq', 'dogs'); + cy.get('ion-select').should('have.prop', 'value').and('eq', 'apples'); + }); + + it('should reflect props when component has a default value', () => { + // Disable inputs + cy.get('ion-button#disable').click(); - cy.get('ion-button#reset').click(); + // Disabled prop + cy.get('ion-input').should('have.attr', 'disabled'); + cy.get('ion-input-otp').should('have.attr', 'disabled'); + cy.get('ion-textarea').should('have.attr', 'disabled'); - cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', false); - cy.get('ion-toggle').should('have.prop', 'checked').and('eq', false); - cy.get('ion-input').should('have.prop', 'value').and('eq', ''); - cy.get('ion-input-otp').should('have.prop', 'value').and('eq', ''); - cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 30, upper: 70 }); - cy.get('ion-textarea').should('have.prop', 'value').and('eq', ''); - cy.get('ion-searchbar').should('have.prop', 'value').and('eq', ''); - cy.get('ion-datetime').should('have.prop', 'value').and('eq', ''); - cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'red'); - cy.get('ion-segment').should('have.prop', 'value').and('eq', 'dogs'); - cy.get('ion-select').should('have.prop', 'value').and('eq', 'apples'); + // Reset disabled state and set readonly state + cy.get('ion-button#disable').click(); + cy.get('ion-button#readonly').click(); + + // Readonly prop + cy.get('ion-input').should('have.attr', 'readonly'); + cy.get('ion-input-otp').should('have.attr', 'readonly'); + cy.get('ion-textarea').should('have.attr', 'readonly'); + }); }); describe('updating text input refs', () => {