Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions core/src/components/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export class Checkbox implements ComponentInterface {
private inputLabelId = `${this.inputId}-lbl`;
private helperTextId = `${this.inputId}-helper-text`;
private errorTextId = `${this.inputId}-error-text`;
private focusEl?: HTMLElement;
private inheritedAttributes: Attributes = {};

@Element() el!: HTMLIonCheckboxElement;
Expand Down Expand Up @@ -147,9 +146,7 @@ export class Checkbox implements ComponentInterface {
/** @internal */
@Method()
async setFocus() {
if (this.focusEl) {
this.focusEl.focus();
}
this.el.focus();
}

/**
Expand All @@ -169,7 +166,6 @@ export class Checkbox implements ComponentInterface {
private toggleChecked = (ev: Event) => {
ev.preventDefault();

this.setFocus();
this.setChecked(!this.checked);
this.indeterminate = false;
};
Expand Down Expand Up @@ -285,6 +281,9 @@ export class Checkbox implements ComponentInterface {
aria-disabled={disabled ? 'true' : null}
tabindex={disabled ? undefined : 0}
onKeyDown={this.onKeyDown}
onFocus={this.onFocus}
onBlur={this.onBlur}
onClick={this.onClick}
class={createColorClasses(color, {
[mode]: true,
'in-item': hostContext('ion-item', el),
Expand All @@ -296,7 +295,6 @@ export class Checkbox implements ComponentInterface {
[`checkbox-alignment-${alignment}`]: alignment !== undefined,
[`checkbox-label-placement-${labelPlacement}`]: true,
})}
onClick={this.onClick}
>
<label class="checkbox-wrapper" htmlFor={inputId}>
{/*
Expand All @@ -309,9 +307,6 @@ export class Checkbox implements ComponentInterface {
disabled={disabled}
id={inputId}
onChange={this.toggleChecked}
onFocus={() => this.onFocus()}
onBlur={() => this.onBlur()}
ref={(focusEl) => (this.focusEl = focusEl)}
required={required}
{...inheritedAttributes}
/>
Expand Down
196 changes: 195 additions & 1 deletion core/src/components/checkbox/test/basic/checkbox.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ configs().forEach(({ title, screenshot, config }) => {
});
});

configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
/**
* This behavior does not vary across modes/directions
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('checkbox: ionChange'), () => {
test('should fire ionChange when interacting with checkbox', async ({ page }) => {
await page.setContent(
Expand Down Expand Up @@ -133,4 +136,195 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
expect(clickCount).toBe(1);
});
});

test.describe(title('checkbox: ionFocus'), () => {
test('should not have visual regressions', async ({ page, pageUtils }) => {
await page.setContent(
`
<style>
#container {
display: inline-block;
padding: 10px;
}
</style>

<div id="container">
<ion-checkbox>Unchecked</ion-checkbox>
</div>
`,
config
);

await pageUtils.pressKeys('Tab');

const container = page.locator('#container');

await expect(container).toHaveScreenshot(screenshot(`checkbox-focus`));
});

test('should not have visual regressions when interacting with checkbox in item', async ({ page, pageUtils }) => {
await page.setContent(
`
<ion-item class="ion-focused">
<ion-checkbox>Unchecked</ion-checkbox>
</ion-item>
`,
config
);

// Test focus with keyboard navigation
await pageUtils.pressKeys('Tab');

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

await expect(item).toHaveScreenshot(screenshot(`checkbox-in-item-focus`));
});

test('should fire ionFocus when checkbox is focused', async ({ page, pageUtils }) => {
await page.setContent(
`
<ion-checkbox aria-label="checkbox" value="my-checkbox"></ion-checkbox>
`,
config
);

const ionFocus = await page.spyOnEvent('ionFocus');

// Test focus with keyboard navigation
await pageUtils.pressKeys('Tab');

expect(ionFocus).toHaveReceivedEventTimes(1);

// Reset focus
const checkbox = page.locator('ion-checkbox');
const checkboxBoundingBox = (await checkbox.boundingBox())!;
await page.mouse.click(0, checkboxBoundingBox.height + 1);

// Test focus with click
await checkbox.click();

expect(ionFocus).toHaveReceivedEventTimes(2);
});

test('should fire ionFocus when interacting with checkbox in item', async ({ page, pageUtils }) => {
await page.setContent(
`
<ion-item>
<ion-checkbox aria-label="checkbox" value="my-checkbox"></ion-checkbox>
</ion-item>
`,
config
);

const ionFocus = await page.spyOnEvent('ionFocus');

// Test focus with keyboard navigation
await pageUtils.pressKeys('Tab');

expect(ionFocus).toHaveReceivedEventTimes(1);

// Verify that the event target is the checkbox and not the item
const eventByKeyboard = ionFocus.events[0];
expect((eventByKeyboard.target as HTMLElement).tagName.toLowerCase()).toBe('ion-checkbox');

// Reset focus
const checkbox = page.locator('ion-checkbox');
const checkboxBoundingBox = (await checkbox.boundingBox())!;
await page.mouse.click(0, checkboxBoundingBox.height + 1);

// Test focus with click
const item = page.locator('ion-item');
await item.click();

expect(ionFocus).toHaveReceivedEventTimes(2);

// Verify that the event target is the checkbox and not the item
const eventByClick = ionFocus.events[0];
expect((eventByClick.target as HTMLElement).tagName.toLowerCase()).toBe('ion-checkbox');
});

test('should not fire when programmatically setting a value', async ({ page }) => {
await page.setContent(
`
<ion-checkbox aria-label="checkbox" value="my-checkbox"></ion-checkbox>
`,
config
);

const ionFocus = await page.spyOnEvent('ionFocus');
const checkbox = page.locator('ion-checkbox');

await checkbox.evaluate((el: HTMLIonCheckboxElement) => (el.checked = true));
expect(ionFocus).not.toHaveReceivedEvent();
});
});

test.describe(title('checkbox: ionBlur'), () => {
test('should fire ionBlur when checkbox is blurred', async ({ page, pageUtils }) => {
await page.setContent(
`
<ion-checkbox aria-label="checkbox" value="my-checkbox"></ion-checkbox>
`,
config
);

const ionBlur = await page.spyOnEvent('ionBlur');

// Test blur with keyboard navigation
// Focus the checkbox
await pageUtils.pressKeys('Tab');
// Blur the checkbox
await pageUtils.pressKeys('Tab');

expect(ionBlur).toHaveReceivedEventTimes(1);

// Test blur with click
const checkbox = page.locator('ion-checkbox');
// Focus the checkbox
await checkbox.click();
// Blur the checkbox by clicking outside of it
const checkboxBoundingBox = (await checkbox.boundingBox())!;
await page.mouse.click(0, checkboxBoundingBox.height + 1);

expect(ionBlur).toHaveReceivedEventTimes(2);
});

test('should fire ionBlur after interacting with checkbox in item', async ({ page, pageUtils }) => {
await page.setContent(
`
<ion-item>
<ion-checkbox aria-label="checkbox" value="my-checkbox"></ion-checkbox>
</ion-item>
`,
config
);

const ionBlur = await page.spyOnEvent('ionBlur');

// Test blur with keyboard navigation
// Focus the checkbox
await pageUtils.pressKeys('Tab');
// Blur the checkbox
await pageUtils.pressKeys('Tab');

expect(ionBlur).toHaveReceivedEventTimes(1);

// Verify that the event target is the checkbox and not the item
const event = ionBlur.events[0];
expect((event.target as HTMLElement).tagName.toLowerCase()).toBe('ion-checkbox');

// Test blur with click
const item = page.locator('ion-item');
await item.click();
// Blur the checkbox by clicking outside of it
const itemBoundingBox = (await item.boundingBox())!;
await page.mouse.click(0, itemBoundingBox.height + 1);

expect(ionBlur).toHaveReceivedEventTimes(2);

// Verify that the event target is the checkbox and not the item
const eventByClick = ionBlur.events[0];
expect((eventByClick.target as HTMLElement).tagName.toLowerCase()).toBe('ion-checkbox');
});
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions core/src/components/checkbox/test/basic/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@
<ion-checkbox checked style="width: 200px">Specified width</ion-checkbox><br />
<ion-checkbox checked style="width: 100%">Full-width</ion-checkbox><br />
</ion-content>

<script>
document.addEventListener('ionBlur', (ev) => {
console.log('ionBlur', ev);
});

document.addEventListener('ionChange', (ev) => {
console.log('ionChange', ev);
});

document.addEventListener('ionFocus', (ev) => {
console.log('ionFocus', ev);
});
</script>
</ion-app>
</body>
</html>
14 changes: 14 additions & 0 deletions core/src/components/checkbox/test/item/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,20 @@ <h1>Multiline Label</h1>
</div>
</div>
</ion-content>

<script>
document.addEventListener('ionBlur', (ev) => {
console.log('ionBlur', ev);
});

document.addEventListener('ionChange', (ev) => {
console.log('ionChange', ev);
});

document.addEventListener('ionFocus', (ev) => {
console.log('ionFocus', ev);
});
</script>
</ion-app>
</body>
</html>
14 changes: 14 additions & 0 deletions core/src/components/toggle/test/basic/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@
<ion-toggle style="width: 100%"> Full-width </ion-toggle><br />
<ion-toggle> Long Label Long Label Long Label Long Label Long Label Long Label </ion-toggle><br />
</ion-content>

<script>
document.addEventListener('ionBlur', (ev) => {
console.log('ionBlur', ev);
});

document.addEventListener('ionChange', (ev) => {
console.log('ionChange', ev);
});

document.addEventListener('ionFocus', (ev) => {
console.log('ionFocus', ev);
});
</script>
</ion-app>
</body>
</html>
Loading
Loading