Compare commits

...

3 Commits

Author SHA1 Message Date
Liam DeBeasi
243cf92600 add poc 2024-02-09 10:04:11 -05:00
Liam DeBeasi
1ca9aa5246 test(toast): reset config to avoid unnecessary setTimeouts (#29004)
Issue number: Internal

---------

<!-- 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. -->

Team members are running into unexpected errors running spec tests:

```
TypeError: Cannot read properties of undefined (reading '$instanceValues$')
```

This line is the culprit:
f885a5526a/core/src/components/toast/test/toast.spec.tsx (L108)
We set the Ionic config for toastDuration to 5000. This means that on
present a setTimeout callback will fire after 5000ms and dismiss the
toast. For this test, this is fine because we never present the toast
therefore the setTimeout is never created.

The problem is that this config is not automatically reset between
tests. As a result, when we have tests that only present the toast (and
never dismiss it) the duration is also 5000 there:
f885a5526a/core/src/components/toast/test/toast.spec.tsx (L179-L184)

This results in a bunch of setTimeouts being created. The timeout
callback runs dismiss, and the body of that function tries to access
data on the host toast. Since the toast instance has already been torn
down (since the tests are done), the undefined error occurs.


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

- Reset the Ionic config after the test that sets `toastDuration` to
`5000`

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#footer
for more information.
-->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->
2024-02-08 18:50:48 +00:00
Liam DeBeasi
e833ad4649 docs(overlays): clarify how to remove an overlay (#28989)
In https://github.com/ionic-team/ionic-framework/issues/28981 there was
some confusion surrounding how to remove an overlay from the DOM if it
was never presented. The `dismiss` method will remove the overlay from
the DOM, but only if the overlay is visible. Otherwise, it's a no-op.

This PR updates the `dismiss` method docs for each overlay component to
note that developers can use the browser's remove method to remove the
element from the DOM.
2024-02-08 18:36:24 +00:00
11 changed files with 143 additions and 7 deletions

View File

@@ -160,7 +160,7 @@ export namespace Components {
/**
* Dismiss the action sheet overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the action sheet. This can be useful in a button handler for determining which button was clicked to dismiss the action sheet. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
* @param role The role of the element that is dismissing the action sheet. This can be useful in a button handler for determining which button was clicked to dismiss the action sheet. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
"dismiss": (data?: any, role?: string) => Promise<boolean>;
/**
@@ -239,7 +239,7 @@ export namespace Components {
/**
* Dismiss the alert overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the alert. This can be useful in a button handler for determining which button was clicked to dismiss the alert. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
* @param role The role of the element that is dismissing the alert. This can be useful in a button handler for determining which button was clicked to dismiss the alert. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
"dismiss": (data?: any, role?: string) => Promise<boolean>;
/**
@@ -1527,7 +1527,7 @@ export namespace Components {
/**
* Dismiss the loading overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the loading. This can be useful in a button handler for determining which button was clicked to dismiss the loading. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
* @param role The role of the element that is dismissing the loading. This can be useful in a button handler for determining which button was clicked to dismiss the loading. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
"dismiss": (data?: any, role?: string) => Promise<boolean>;
/**
@@ -1720,7 +1720,7 @@ export namespace Components {
/**
* Dismiss the modal overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the modal. For example, 'cancel' or 'backdrop'.
* @param role The role of the element that is dismissing the modal. For example, 'cancel' or 'backdrop'. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
"dismiss": (data?: any, role?: string) => Promise<boolean>;
/**
@@ -1948,6 +1948,9 @@ export namespace Components {
*/
"mode"?: "ios" | "md";
}
interface IonPasswordStrength {
"strength"?: 'weak' | 'medium' | 'strong';
}
interface IonPicker {
/**
* If `true`, the picker will animate.
@@ -1973,7 +1976,7 @@ export namespace Components {
/**
* Dismiss the picker overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the picker. This can be useful in a button handler for determining which button was clicked to dismiss the picker. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
* @param role The role of the element that is dismissing the picker. This can be useful in a button handler for determining which button was clicked to dismiss the picker. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
"dismiss": (data?: any, role?: string) => Promise<boolean>;
/**
@@ -2110,7 +2113,7 @@ export namespace Components {
* Dismiss the popover overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the popover. For example, 'cancel' or 'backdrop'.
* @param dismissParentPopover If `true`, dismissing this popover will also dismiss a parent popover if this popover is nested. Defaults to `true`.
* @param dismissParentPopover If `true`, dismissing this popover will also dismiss a parent popover if this popover is nested. Defaults to `true`. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
"dismiss": (data?: any, role?: string, dismissParentPopover?: boolean) => Promise<boolean>;
/**
@@ -3111,7 +3114,7 @@ export namespace Components {
/**
* Dismiss the toast overlay after it has been presented.
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the toast. This can be useful in a button handler for determining which button was clicked to dismiss the toast. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
* @param role The role of the element that is dismissing the toast. This can be useful in a button handler for determining which button was clicked to dismiss the toast. Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`. This is a no-op if the overlay has not been presented yet. If you want to remove an overlay from the DOM that was never presented, use the [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
"dismiss": (data?: any, role?: string) => Promise<boolean>;
/**
@@ -4035,6 +4038,12 @@ declare global {
prototype: HTMLIonNoteElement;
new (): HTMLIonNoteElement;
};
interface HTMLIonPasswordStrengthElement extends Components.IonPasswordStrength, HTMLStencilElement {
}
var HTMLIonPasswordStrengthElement: {
prototype: HTMLIonPasswordStrengthElement;
new (): HTMLIonPasswordStrengthElement;
};
interface HTMLIonPickerElementEventMap {
"ionPickerDidPresent": void;
"ionPickerWillPresent": void;
@@ -4672,6 +4681,7 @@ declare global {
"ion-nav": HTMLIonNavElement;
"ion-nav-link": HTMLIonNavLinkElement;
"ion-note": HTMLIonNoteElement;
"ion-password-strength": HTMLIonPasswordStrengthElement;
"ion-picker": HTMLIonPickerElement;
"ion-picker-column": HTMLIonPickerColumnElement;
"ion-picker-column-internal": HTMLIonPickerColumnInternalElement;
@@ -6606,6 +6616,9 @@ declare namespace LocalJSX {
*/
"mode"?: "ios" | "md";
}
interface IonPasswordStrength {
"strength"?: 'weak' | 'medium' | 'strong';
}
interface IonPicker {
/**
* If `true`, the picker will animate.
@@ -8127,6 +8140,7 @@ declare namespace LocalJSX {
"ion-nav": IonNav;
"ion-nav-link": IonNavLink;
"ion-note": IonNote;
"ion-password-strength": IonPasswordStrength;
"ion-picker": IonPicker;
"ion-picker-column": IonPickerColumn;
"ion-picker-column-internal": IonPickerColumnInternal;
@@ -8224,6 +8238,7 @@ declare module "@stencil/core" {
"ion-nav": LocalJSX.IonNav & JSXBase.HTMLAttributes<HTMLIonNavElement>;
"ion-nav-link": LocalJSX.IonNavLink & JSXBase.HTMLAttributes<HTMLIonNavLinkElement>;
"ion-note": LocalJSX.IonNote & JSXBase.HTMLAttributes<HTMLIonNoteElement>;
"ion-password-strength": LocalJSX.IonPasswordStrength & JSXBase.HTMLAttributes<HTMLIonPasswordStrengthElement>;
"ion-picker": LocalJSX.IonPicker & JSXBase.HTMLAttributes<HTMLIonPickerElement>;
"ion-picker-column": LocalJSX.IonPickerColumn & JSXBase.HTMLAttributes<HTMLIonPickerColumnElement>;
"ion-picker-column-internal": LocalJSX.IonPickerColumnInternal & JSXBase.HTMLAttributes<HTMLIonPickerColumnInternalElement>;

View File

@@ -216,6 +216,10 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
* This can be useful in a button handler for determining which button was
* clicked to dismiss the action sheet.
* Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string): Promise<boolean> {

View File

@@ -411,6 +411,10 @@ export class Alert implements ComponentInterface, OverlayInterface {
* This can be useful in a button handler for determining which button was
* clicked to dismiss the alert.
* Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string): Promise<boolean> {

View File

@@ -268,6 +268,10 @@ export class Loading implements ComponentInterface, OverlayInterface {
* This can be useful in a button handler for determining which button was
* clicked to dismiss the loading.
* Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string): Promise<boolean> {

View File

@@ -662,6 +662,10 @@ export class Modal implements ComponentInterface, OverlayInterface {
*
* @param data Any data to emit in the dismiss events.
* @param role The role of the element that is dismissing the modal. For example, 'cancel' or 'backdrop'.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string): Promise<boolean> {

View File

@@ -0,0 +1,34 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
const progressBarValue: any = {
'weak': {
value: 0.2,
},
'medium': {
value: 0.6,
},
'strong': {
value: 1,
}
}
@Component({
tag: 'ion-password-strength',
shadow: true
})
export class PasswordStrength implements ComponentInterface {
@Prop() strength?: 'weak' | 'medium' | 'strong';
render() {
// TODO need a mode virtual prop
// TODO need to add colors
const data = this.strength !== undefined ? progressBarValue[this.strength] : undefined;
return (
<Host>
<ion-progress-bar value={data?.value || 0}></ion-progress-bar>
{ this.strength !== undefined && <slot name={this.strength}></slot> }
</Host>
);
}
}

View File

@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Password Strength - Basic</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body class="ion-padding">
<ion-input label="Password" type="password" fill="outline"></ion-input>
<br />
<ion-password-strength>
<div slot="weak">Your password is weak</div>
<div slot="medium">Your password is medium</div>
<div slot="strong">Your password is strong</div>
</ion-password-strength>
<script>
const input = document.querySelector('ion-input');
const ps = document.querySelector('ion-password-strength');
input.addEventListener('ionInput', () => {
const val = input.value;
if (val.length === 0) {
ps.strength = undefined;
} else if (val.length < 4) {
ps.strength = 'weak';
} else if (val.length < 8) {
ps.strength = 'medium';
} else {
ps.strength = 'strong';
}
});
</script>
</body>
</html>

View File

@@ -248,6 +248,10 @@ export class Picker implements ComponentInterface, OverlayInterface {
* This can be useful in a button handler for determining which button was
* clicked to dismiss the picker.
* Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string): Promise<boolean> {

View File

@@ -525,6 +525,10 @@ export class Popover implements ComponentInterface, PopoverInterface {
* @param role The role of the element that is dismissing the popover. For example, 'cancel' or 'backdrop'.
* @param dismissParentPopover If `true`, dismissing this popover will also dismiss
* a parent popover if this popover is nested. Defaults to `true`.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string, dismissParentPopover = true): Promise<boolean> {

View File

@@ -93,6 +93,19 @@ describe('toast: a11y smoke test', () => {
});
describe('toast: duration config', () => {
afterEach(() => {
/**
* Important: Reset the config
* after each test as it is not
* automatically reset.
* Otherwise, toasts in other tests
* will take on any toastDuration value
* set and timeouts will potentially run
* after tests are finished.
*/
config.reset({});
});
it('should have duration set to 0', async () => {
const page = await newSpecPage({
components: [Toast],

View File

@@ -401,6 +401,10 @@ export class Toast implements ComponentInterface, OverlayInterface {
* This can be useful in a button handler for determining which button was
* clicked to dismiss the toast.
* Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`.
*
* This is a no-op if the overlay has not been presented yet. If you want
* to remove an overlay from the DOM that was never presented, use the
* [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
*/
@Method()
async dismiss(data?: any, role?: string): Promise<boolean> {