From 35d6844225da3dd1e1393121b25266f6af660a55 Mon Sep 17 00:00:00 2001 From: Zac-Smucker-Bryan Date: Fri, 31 Jul 2026 09:49:48 -0500 Subject: [PATCH 1/5] docs(tests): Add specific guidance to testing standalone For Angular, React, and Vue. Includes new testing page for Vue as a starter. --- docs/angular/testing.md | 41 +++++++++++++++++++ .../testing/unit-testing/best-practices.md | 4 ++ docs/vue/testing.md | 25 +++++++++++ 3 files changed, 70 insertions(+) create mode 100644 docs/vue/testing.md diff --git a/docs/angular/testing.md b/docs/angular/testing.md index 1278551b0b9..5d2cb402e82 100644 --- a/docs/angular/testing.md +++ b/docs/angular/testing.md @@ -178,6 +178,47 @@ describe('PayrolService', () => { }); ``` +### Import `componentOnReady` for standalone projects + +When testing Ionic components, use the exported `componentOnReady` helper from `@ionic/core` instead of calling `el.componentOnReady()` directly. The helper works with both lazy-loaded and custom-element builds, making it more likely the component has finished rendering before making assertions against its rendered DOM or running accessibility tests. See an example here: + +```tsx +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { axe, toHaveNoViolations } from 'jasmine-axe'; +import { componentOnReady } from '@ionic/core'; + +import { Component } from './component'; + +describe('Component', () => { + let component: Component; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Component], + }).compileComponents(); + + fixture = TestBed.createComponent(Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should pass accessibility test', async () => { + const el = fixture.nativeElement.querySelector('ion-button'); + + await new Promise((resolve) => { + componentOnReady(el, () => resolve()); + }); + + jasmine.addMatchers(toHaveNoViolations); + + const a11y = await axe(fixture.nativeElement); + + expect(a11y).toHaveNoViolations(); + }); +}); +``` + #### Testing HTTP Data Services Most services that perform HTTP operations will use Angular's HttpClient service in order to perform those operations. For such tests, it is suggested to use Angular's `HttpClientTestingModule`. For detailed documentation of this module, please see Angular's Angular's Testing HTTP requests guide. diff --git a/docs/react/testing/unit-testing/best-practices.md b/docs/react/testing/unit-testing/best-practices.md index ce1562d391f..8991f5f3b28 100644 --- a/docs/react/testing/unit-testing/best-practices.md +++ b/docs/react/testing/unit-testing/best-practices.md @@ -49,3 +49,7 @@ test('example', async () => { ``` For more information on `user-event`, see the [user-event documentation](https://testing-library.com/docs/user-event/intro/). + +## Import `componentOnReady` for standalone projects + +When testing Ionic components, use the exported `componentOnReady` helper from `@ionic/core` instead of calling `el.componentOnReady()` directly. The helper works with both lazy-loaded and custom-element builds, making it more likely the component has finished rendering before making assertions against its rendered DOM or running accessibility tests. diff --git a/docs/vue/testing.md b/docs/vue/testing.md new file mode 100644 index 00000000000..d259a4a029d --- /dev/null +++ b/docs/vue/testing.md @@ -0,0 +1,25 @@ +--- +title: Testing +--- + + + Vue Unit and End-to-End Testing for Ionic App Components + + + +# Testing Ionic Vue + +This document provides an overview of how to test an application built with `@ionic/vue`. It covers the basics of testing with Vue, as well as the specific tools and libraries developers can use to test their applications. + +## Introduction + +Testing is an important part of the development process, and it helps to ensure that an application is working as intended. + +## Best Practices + +### Import `componentOnReady` for standalone projects + +When testing Ionic components, use the exported `componentOnReady` helper from `@ionic/core` instead of calling `el.componentOnReady()` directly. The helper works with both lazy-loaded and custom-element builds, making it more likely the component has finished rendering before making assertions against its rendered DOM or running accessibility tests. From ea7e060caf5cad543e13ae067e3654af201c3d39 Mon Sep 17 00:00:00 2001 From: Zac Smucker-Bryan <111775722+Zac-Smucker-Bryan@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:33:51 -0500 Subject: [PATCH 2/5] Apply suggestions from docs review Clarity on use of componentOnReady, conciseness of test example Co-authored-by: Maria Hutt --- docs/angular/testing.md | 40 +------------------ .../testing/unit-testing/best-practices.md | 2 +- docs/vue/testing.md | 12 ++---- 3 files changed, 7 insertions(+), 47 deletions(-) diff --git a/docs/angular/testing.md b/docs/angular/testing.md index 5d2cb402e82..9569e7451a3 100644 --- a/docs/angular/testing.md +++ b/docs/angular/testing.md @@ -178,46 +178,10 @@ describe('PayrolService', () => { }); ``` -### Import `componentOnReady` for standalone projects +### Waiting for Components -When testing Ionic components, use the exported `componentOnReady` helper from `@ionic/core` instead of calling `el.componentOnReady()` directly. The helper works with both lazy-loaded and custom-element builds, making it more likely the component has finished rendering before making assertions against its rendered DOM or running accessibility tests. See an example here: +When testing Ionic components, use the `componentOnReady` helper exported from `@ionic/core` rather than calling `el.componentOnReady()` directly. That method only exists on lazy-loaded elements. Calling it directly throws on custom-element builds, which is what standalone projects use. The helper handles both. It awaits the element's own `componentOnReady()` promise when that exists. Otherwise it waits one animation frame, giving the component's inner contents a chance to render. Wait for the callback before asserting against the rendered DOM or running accessibility tests. -```tsx -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { axe, toHaveNoViolations } from 'jasmine-axe'; -import { componentOnReady } from '@ionic/core'; - -import { Component } from './component'; - -describe('Component', () => { - let component: Component; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [Component], - }).compileComponents(); - - fixture = TestBed.createComponent(Component); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should pass accessibility test', async () => { - const el = fixture.nativeElement.querySelector('ion-button'); - - await new Promise((resolve) => { - componentOnReady(el, () => resolve()); - }); - - jasmine.addMatchers(toHaveNoViolations); - - const a11y = await axe(fixture.nativeElement); - - expect(a11y).toHaveNoViolations(); - }); -}); -``` #### Testing HTTP Data Services diff --git a/docs/react/testing/unit-testing/best-practices.md b/docs/react/testing/unit-testing/best-practices.md index 8991f5f3b28..9a1fb5ff0d4 100644 --- a/docs/react/testing/unit-testing/best-practices.md +++ b/docs/react/testing/unit-testing/best-practices.md @@ -52,4 +52,4 @@ For more information on `user-event`, see the [user-event documentation](https:/ ## Import `componentOnReady` for standalone projects -When testing Ionic components, use the exported `componentOnReady` helper from `@ionic/core` instead of calling `el.componentOnReady()` directly. The helper works with both lazy-loaded and custom-element builds, making it more likely the component has finished rendering before making assertions against its rendered DOM or running accessibility tests. +When you need to wait for an Ionic component to render before asserting against its DOM, use the `componentOnReady` helper exported from `@ionic/core`. Do not call `el.componentOnReady()` directly. `@ionic/react` uses Stencil's custom elements build, where that method does not exist on the element. The helper waits one animation frame instead, giving the component's inner contents a chance to render. diff --git a/docs/vue/testing.md b/docs/vue/testing.md index d259a4a029d..c212ea9b7a1 100644 --- a/docs/vue/testing.md +++ b/docs/vue/testing.md @@ -12,14 +12,10 @@ title: Testing # Testing Ionic Vue -This document provides an overview of how to test an application built with `@ionic/vue`. It covers the basics of testing with Vue, as well as the specific tools and libraries developers can use to test their applications. +This document provides an overview of how to test an application built with `@ionic/vue`. Applications generated with the Ionic CLI are set up for unit testing with [Vitest](https://vitest.dev) and [Vue Test Utils](https://test-utils.vuejs.org), and for end-to-end testing with [Cypress](https://www.cypress.io). -## Introduction +## Unit Testing -Testing is an important part of the development process, and it helps to ensure that an application is working as intended. +### Waiting for Components -## Best Practices - -### Import `componentOnReady` for standalone projects - -When testing Ionic components, use the exported `componentOnReady` helper from `@ionic/core` instead of calling `el.componentOnReady()` directly. The helper works with both lazy-loaded and custom-element builds, making it more likely the component has finished rendering before making assertions against its rendered DOM or running accessibility tests. +When you need to wait for an Ionic component to render before asserting against its DOM, use the `componentOnReady` helper exported from `@ionic/core`. Do not call `el.componentOnReady()` directly. `@ionic/vue` uses Stencil's custom elements build, where that method does not exist on the element. The helper waits one animation frame instead, giving the component's inner contents a chance to render. From 7147fc9ea134e5d0b8a4cbd35ef6f98ba7714604 Mon Sep 17 00:00:00 2001 From: Zac-Smucker-Bryan Date: Tue, 4 Aug 2026 20:57:17 -0500 Subject: [PATCH 3/5] docs(test): Larger Code Review Feedback Slight rephrasing of paragraph explaining componentOnReady helper Add more concise test to Angular using componentOnReady helper Move Angular to last part of Unit Testing Add example test for React, matching Angular Small formatting edits to Vue, add Vue testing page to sidebar --- docs/angular/testing.md | 33 ++++++++++++++++--- .../testing/unit-testing/best-practices.md | 20 ++++++++++- docs/vue/testing.md | 2 -- sidebars.js | 1 + 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/docs/angular/testing.md b/docs/angular/testing.md index 9569e7451a3..5fc0e2efb16 100644 --- a/docs/angular/testing.md +++ b/docs/angular/testing.md @@ -109,6 +109,34 @@ describe('TabsPage', () => { When doing component class testing, the component object is accessed using the component object defined via `component = fixture.componentInstance;`. This is an instance of the component class. When doing DOM testing, the `fixture.nativeElement` property is used. This is the actual `HTMLElement` for the component, which allows the test to use standard HTML API methods such as `HTMLElement.querySelector` in order to examine the DOM. +### Waiting for Components + +When testing Ionic components, use the `componentOnReady` helper exported from `@ionic/core` rather than calling `el.componentOnReady()` directly. The `el.componentOnReady()` method only exists on lazy-loaded elements and calling it directly throws an error on custom-element builds, which is what standalone projects use. The helper handles both. It awaits the element's own `componentOnReady()` promise when that exists. Otherwise it waits one animation frame, giving the component's inner contents a chance to render. Wait for the callback before asserting against the rendered DOM or running accessibility tests. + +```tsx +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { componentOnReady } from '@ionic/core'; +import { HomePage } from './home.page'; + +describe('HomePage', () => { + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [HomePage], + }).compileComponents(); + fixture = TestBed.createComponent(HomePage); + fixture.detectChanges(); + }); + + it('renders the submit button', async () => { + const button = fixture.nativeElement.querySelector('ion-button'); + await new Promise((resolve) => componentOnReady(button, () => resolve())); + expect(button.textContent).toContain('Submit'); + }); +}); +``` + ## Services Services often fall into one of two broad categories: utility services that perform calculations and other operations, and data services that perform primarily HTTP operations and data manipulation. @@ -178,11 +206,6 @@ describe('PayrolService', () => { }); ``` -### Waiting for Components - -When testing Ionic components, use the `componentOnReady` helper exported from `@ionic/core` rather than calling `el.componentOnReady()` directly. That method only exists on lazy-loaded elements. Calling it directly throws on custom-element builds, which is what standalone projects use. The helper handles both. It awaits the element's own `componentOnReady()` promise when that exists. Otherwise it waits one animation frame, giving the component's inner contents a chance to render. Wait for the callback before asserting against the rendered DOM or running accessibility tests. - - #### Testing HTTP Data Services Most services that perform HTTP operations will use Angular's HttpClient service in order to perform those operations. For such tests, it is suggested to use Angular's `HttpClientTestingModule`. For detailed documentation of this module, please see Angular's Angular's Testing HTTP requests guide. diff --git a/docs/react/testing/unit-testing/best-practices.md b/docs/react/testing/unit-testing/best-practices.md index 9a1fb5ff0d4..7576b13b868 100644 --- a/docs/react/testing/unit-testing/best-practices.md +++ b/docs/react/testing/unit-testing/best-practices.md @@ -50,6 +50,24 @@ test('example', async () => { For more information on `user-event`, see the [user-event documentation](https://testing-library.com/docs/user-event/intro/). -## Import `componentOnReady` for standalone projects +## Waiting for Components When you need to wait for an Ionic component to render before asserting against its DOM, use the `componentOnReady` helper exported from `@ionic/core`. Do not call `el.componentOnReady()` directly. `@ionic/react` uses Stencil's custom elements build, where that method does not exist on the element. The helper waits one animation frame instead, giving the component's inner contents a chance to render. + +```tsx +import { test, expect } from 'vitest'; +import { render } from '@testing-library/react'; +import { componentOnReady } from '@ionic/core'; + +import App from './App'; + +test('renders the submit button', async () => { + const { container } = render(); + + const button = container.querySelector('ion-button'); + + await new Promise((resolve) => componentOnReady(button!, () => resolve())); + + expect(button?.textContent).toContain('Submit'); +}); +``` diff --git a/docs/vue/testing.md b/docs/vue/testing.md index c212ea9b7a1..184ed834fda 100644 --- a/docs/vue/testing.md +++ b/docs/vue/testing.md @@ -10,8 +10,6 @@ title: Testing /> -# Testing Ionic Vue - This document provides an overview of how to test an application built with `@ionic/vue`. Applications generated with the Ionic CLI are set up for unit testing with [Vitest](https://vitest.dev) and [Vue Test Utils](https://test-utils.vuejs.org), and for end-to-end testing with [Cypress](https://www.cypress.io). ## Unit Testing diff --git a/sidebars.js b/sidebars.js index 06689adc351..82f1e873e93 100644 --- a/sidebars.js +++ b/sidebars.js @@ -183,6 +183,7 @@ module.exports = { 'vue/slides', 'vue/utility-functions', 'vue/platform', + 'vue/testing', 'vue/pwa', 'vue/storage', 'vue/troubleshooting', From 694f85c2b4f3798299b1bafe88e52d192b14a1ec Mon Sep 17 00:00:00 2001 From: Zac Smucker-Bryan <111775722+Zac-Smucker-Bryan@users.noreply.github.com> Date: Wed, 5 Aug 2026 14:19:28 -0500 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Maria Hutt --- .../testing/unit-testing/best-practices.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/docs/react/testing/unit-testing/best-practices.md b/docs/react/testing/unit-testing/best-practices.md index 7576b13b868..039914be190 100644 --- a/docs/react/testing/unit-testing/best-practices.md +++ b/docs/react/testing/unit-testing/best-practices.md @@ -54,20 +54,3 @@ For more information on `user-event`, see the [user-event documentation](https:/ When you need to wait for an Ionic component to render before asserting against its DOM, use the `componentOnReady` helper exported from `@ionic/core`. Do not call `el.componentOnReady()` directly. `@ionic/react` uses Stencil's custom elements build, where that method does not exist on the element. The helper waits one animation frame instead, giving the component's inner contents a chance to render. -```tsx -import { test, expect } from 'vitest'; -import { render } from '@testing-library/react'; -import { componentOnReady } from '@ionic/core'; - -import App from './App'; - -test('renders the submit button', async () => { - const { container } = render(); - - const button = container.querySelector('ion-button'); - - await new Promise((resolve) => componentOnReady(button!, () => resolve())); - - expect(button?.textContent).toContain('Submit'); -}); -``` From 3402e86dc34e7b7c77318172043dd97574d03801 Mon Sep 17 00:00:00 2001 From: Zac-Smucker-Bryan Date: Wed, 5 Aug 2026 15:35:18 -0500 Subject: [PATCH 5/5] docs(test): Add concise React and Vue examples From code/docs review feedback --- .../testing/unit-testing/best-practices.md | 21 +++++++++++++++ docs/vue/testing.md | 27 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/docs/react/testing/unit-testing/best-practices.md b/docs/react/testing/unit-testing/best-practices.md index 039914be190..8aa882ce754 100644 --- a/docs/react/testing/unit-testing/best-practices.md +++ b/docs/react/testing/unit-testing/best-practices.md @@ -54,3 +54,24 @@ For more information on `user-event`, see the [user-event documentation](https:/ When you need to wait for an Ionic component to render before asserting against its DOM, use the `componentOnReady` helper exported from `@ionic/core`. Do not call `el.componentOnReady()` directly. `@ionic/react` uses Stencil's custom elements build, where that method does not exist on the element. The helper waits one animation frame instead, giving the component's inner contents a chance to render. +```tsx +import { IonApp } from '@ionic/react'; +import { render } from '@testing-library/react'; +import { componentOnReady } from '@ionic/core'; + +import Example from './Example'; + +test('renders the submit button', async () => { + const { container } = render( + + + + ); + + const button = container.querySelector('ion-button'); + + await new Promise((resolve) => componentOnReady(button, () => resolve())); + + expect(button.textContent).toContain('Submit'); +}); +``` diff --git a/docs/vue/testing.md b/docs/vue/testing.md index 184ed834fda..bfc2e60bae6 100644 --- a/docs/vue/testing.md +++ b/docs/vue/testing.md @@ -17,3 +17,30 @@ This document provides an overview of how to test an application built with `@io ### Waiting for Components When you need to wait for an Ionic component to render before asserting against its DOM, use the `componentOnReady` helper exported from `@ionic/core`. Do not call `el.componentOnReady()` directly. `@ionic/vue` uses Stencil's custom elements build, where that method does not exist on the element. The helper waits one animation frame instead, giving the component's inner contents a chance to render. + +```tsx +import { mount } from '@vue/test-utils'; +import { IonApp } from '@ionic/vue'; +import { componentOnReady } from '@ionic/core'; + +import Example from './Example.vue'; + +const TestComponent = { + components: { IonApp, Example }, + template: ` + + + + `, +}; + +test('renders the submit button', async () => { + const wrapper = mount(TestComponent); + + const button = wrapper.element.querySelector('ion-button')!; + + await new Promise((resolve) => componentOnReady(button, () => resolve())); + + expect(button.textContent).toContain('Submit'); +}); +```