Skip to content

Commit

Permalink
fix(input, textarea): clearOnInput ignores key modifiers (#28639)
Browse files Browse the repository at this point in the history
Issue number: resolves #28633

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

In #28005 I introduced
a fix that causes "clearOnEdit" to not clear input/textarea when the Tab
key was pressed. However, there were several edge cases I missed. For
instance, pressing the "shift" key and then the "tab" key would still
clear the input because "shift" was not explicitly excluded.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Input and textarea now explicitly ignores modifier keys that do not
change the value of the input
- `didInputClearOnEdit` is not set to `true` when an ignored key is
pressed. Otherwise, pressing an ignored key and then an accepted key
would not cause the input to be cleared. For example, pressing "shift",
releasing "shift", then pressing "A" should cause the input to still get
cleared.
- Added test coverage

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

Playwright bug report for
a9f34a5:
microsoft/playwright#28495
  • Loading branch information
liamdebeasi committed Dec 11, 2023
1 parent fc88613 commit 8f7d87c
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 12 deletions.
26 changes: 24 additions & 2 deletions core/src/components/input/input.tsx
Expand Up @@ -549,15 +549,37 @@ export class Input implements ComponentInterface {
if (!this.shouldClearOnEdit()) {
return;
}

/**
* The following keys do not modify the
* contents of the input. As a result, pressing
* them should not edit the input.
*
* We can't check to see if the value of the input
* was changed because we call checkClearOnEdit
* in a keydown listener, and the key has not yet
* been added to the input.
*/
const IGNORED_KEYS = ['Enter', 'Tab', 'Shift', 'Meta', 'Alt', 'Control'];
const pressedIgnoredKey = IGNORED_KEYS.includes(ev.key);

/**
* Clear the input if the control has not been previously cleared during focus.
* Do not clear if the user hitting enter to submit a form.
*/
if (!this.didInputClearOnEdit && this.hasValue() && ev.key !== 'Enter' && ev.key !== 'Tab') {
if (!this.didInputClearOnEdit && this.hasValue() && !pressedIgnoredKey) {
this.value = '';
this.emitInputChange(ev);
}
this.didInputClearOnEdit = true;

/**
* Pressing an IGNORED_KEYS first and
* then an allowed key will cause the input to not
* be cleared.
*/
if (!pressedIgnoredKey) {
this.didInputClearOnEdit = true;
}
}

private onCompositionStart = () => {
Expand Down
47 changes: 39 additions & 8 deletions core/src/components/input/test/clear-on-edit/input.e2e.ts
@@ -1,6 +1,8 @@
import { expect } from '@playwright/test';
import { test, configs } from '@utils/test/playwright';

const IGNORED_KEYS = ['Enter', 'Tab', 'Shift', 'Meta', 'Alt', 'Control'];

configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('input: clearOnEdit'), () => {
test('should clear when typed into', async ({ page }) => {
Expand All @@ -16,28 +18,57 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await expect(input).toHaveJSProperty('value', 'h');
});

test('should not clear when enter is pressed', async ({ page }) => {
await page.setContent(`<ion-input value="abc" clear-on-edit="true" aria-label="input"></ion-input>`, config);
test('should not clear the input if it does not have an initial value when typing', async ({ page }) => {
await page.setContent(`<ion-input label="input" value="" clear-on-edit="true"></ion-input>`, config);

const input = page.locator('ion-input');
await input.locator('input').focus();

await page.keyboard.press('Enter');
await page.waitForChanges();
await input.click();
await input.type('hello world');

await expect(input).toHaveJSProperty('value', 'abc');
await expect(input).toHaveJSProperty('value', 'hello world');
});

IGNORED_KEYS.forEach((ignoredKey: string) => {
test(`should not clear when ${ignoredKey} is pressed`, async ({ page, skip }) => {
skip.browser(
(browserName: string) => browserName === 'firefox' && ignoredKey === 'Meta',
'Firefox incorrectly adds "OS" to the input when pressing the Meta key on Linux'
);
await page.setContent(`<ion-input value="abc" clear-on-edit="true" aria-label="input"></ion-input>`, config);

const input = page.locator('ion-input');
await input.locator('input').focus();

await page.keyboard.press(ignoredKey);
await page.waitForChanges();

await expect(input).toHaveJSProperty('value', 'abc');
});
});

test('should not clear when tab is pressed', async ({ page }) => {
test('should clear after when pressing valid key after pressing ignored key', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/28633',
});

await page.setContent(`<ion-input value="abc" clear-on-edit="true" aria-label="input"></ion-input>`, config);

const input = page.locator('ion-input');
await input.locator('input').focus();

await page.keyboard.press('Tab');
// ignored
await page.keyboard.press('Shift');
await page.waitForChanges();

await expect(input).toHaveJSProperty('value', 'abc');

// allowed
await page.keyboard.press('a');
await page.waitForChanges();

await expect(input).toHaveJSProperty('value', 'a');
});
});
});
61 changes: 61 additions & 0 deletions core/src/components/textarea/test/clear-on-edit/textarea.e2e.ts
@@ -1,6 +1,8 @@
import { expect } from '@playwright/test';
import { test, configs } from '@utils/test/playwright';

const IGNORED_KEYS = ['Tab', 'Shift', 'Meta', 'Alt', 'Control'];

configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('textarea: clearOnEdit'), () => {
test('should clear when typed into', async ({ page }) => {
Expand Down Expand Up @@ -33,5 +35,64 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>

await expect(textarea).toHaveJSProperty('value', 'abc');
});

test('should not clear the textarea if it does not have an initial value when typing', async ({ page }) => {
await page.setContent(`<ion-textarea label="textarea" value="" clear-on-edit="true"></ion-textarea>`, config);

const textarea = page.locator('ion-textarea');

await textarea.click();
await textarea.type('hello world');

await expect(textarea).toHaveJSProperty('value', 'hello world');
});

IGNORED_KEYS.forEach((ignoredKey: string) => {
test(`should not clear when ${ignoredKey} is pressed`, async ({ page, skip }) => {
skip.browser(
(browserName: string) => browserName === 'firefox' && ignoredKey === 'Meta',
'Firefox incorrectly adds "OS" to the textarea when pressing the Meta key on Linux'
);
await page.setContent(
`<ion-textarea value="abc" clear-on-edit="true" aria-label="textarea"></ion-textarea>`,
config
);

const textarea = page.locator('ion-textarea');
await textarea.locator('textarea').focus();

await page.keyboard.press(ignoredKey);
await page.waitForChanges();

await expect(textarea).toHaveJSProperty('value', 'abc');
});
});

test('should clear after when pressing valid key after pressing ignored key', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/28633',
});

await page.setContent(
`<ion-textarea value="abc" clear-on-edit="true" aria-label="textarea"></ion-textarea>`,
config
);

const textarea = page.locator('ion-textarea');
await textarea.locator('textarea').focus();

// ignored
await page.keyboard.press('Shift');
await page.waitForChanges();

await expect(textarea).toHaveJSProperty('value', 'abc');

// allowed
await page.keyboard.press('a');
await page.waitForChanges();

await expect(textarea).toHaveJSProperty('value', 'a');
});
});
});
30 changes: 28 additions & 2 deletions core/src/components/textarea/textarea.tsx
Expand Up @@ -459,15 +459,41 @@ export class Textarea implements ComponentInterface {
if (!this.clearOnEdit) {
return;
}

/**
* The following keys do not modify the
* contents of the input. As a result, pressing
* them should not edit the textarea.
*
* We can't check to see if the value of the textarea
* was changed because we call checkClearOnEdit
* in a keydown listener, and the key has not yet
* been added to the textarea.
*
* Unlike ion-input, the "Enter" key does modify the
* textarea by adding a new line, so "Enter" is not
* included in the IGNORED_KEYS array.
*/
const IGNORED_KEYS = ['Tab', 'Shift', 'Meta', 'Alt', 'Control'];
const pressedIgnoredKey = IGNORED_KEYS.includes(ev.key);

/**
* Clear the textarea if the control has not been previously cleared
* during focus.
*/
if (!this.didTextareaClearOnEdit && this.hasValue() && ev.key !== 'Tab') {
if (!this.didTextareaClearOnEdit && this.hasValue() && !pressedIgnoredKey) {
this.value = '';
this.emitInputChange(ev);
}
this.didTextareaClearOnEdit = true;

/**
* Pressing an IGNORED_KEYS first and
* then an allowed key will cause the input to not
* be cleared.
*/
if (!pressedIgnoredKey) {
this.didTextareaClearOnEdit = true;
}
}

private focusChange() {
Expand Down

0 comments on commit 8f7d87c

Please sign in to comment.