From e9fa30002bd5dec4f2f56a15c84eec1b3e794942 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Wed, 9 Aug 2023 09:35:11 -0500 Subject: [PATCH] fix(button): hidden button is added when form is set async (#27955) Issue number: resolves #27952 --------- ## What is the current behavior? The hidden button in `ion-button` that is responsible for submitting the form does not get added when the `form` property is set async. ## What is the new behavior? - `ion-button` now checks to see if it needs to render a hidden button whenever it re-renders. This allows it to account for changes to the `type` property, `form` property, etc. Since this code can potentially run multiple times I added an extra check so we don't add multiple buttons to the form. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information Dev build: `7.2.3-dev.11691523847.1760ab58` --- core/src/components/button/button.tsx | 37 +++++++++++++------ .../button/test/form-reference/button.e2e.ts | 23 ++++++++++++ .../button/test/form-reference/button.spec.ts | 31 ++++++++++++++++ 3 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 core/src/components/button/test/form-reference/button.spec.ts diff --git a/core/src/components/button/button.tsx b/core/src/components/button/button.tsx index facb64f8d0..73fe417fca 100644 --- a/core/src/components/button/button.tsx +++ b/core/src/components/button/button.tsx @@ -153,19 +153,27 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf */ @Event() ionBlur!: EventEmitter; - connectedCallback(): void { - // Allow form to be submitted through `ion-button` - if (this.type !== 'button' && hasShadowDom(this.el)) { - this.formEl = this.findForm(); - if (this.formEl) { - // Create a hidden native button inside of the form - this.formButtonEl = document.createElement('button'); - this.formButtonEl.type = this.type; - this.formButtonEl.style.display = 'none'; - // Only submit if the button is not disabled. - this.formButtonEl.disabled = this.disabled; - this.formEl.appendChild(this.formButtonEl); + private renderHiddenButton() { + const formEl = (this.formEl = this.findForm()); + if (formEl) { + const { formButtonEl } = this; + + /** + * If the form already has a rendered form button + * then do not append a new one again. + */ + if (formButtonEl !== null && formEl.contains(formButtonEl)) { + return; } + + // Create a hidden native button inside of the form + const newFormButtonEl = (this.formButtonEl = document.createElement('button')); + newFormButtonEl.type = this.type; + newFormButtonEl.style.display = 'none'; + // Only submit if the button is not disabled. + newFormButtonEl.disabled = this.disabled; + + formEl.appendChild(newFormButtonEl); } } @@ -314,6 +322,11 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf if (fill == null) { fill = this.inToolbar || this.inListHeader ? 'clear' : 'solid'; } + + { + type !== 'button' && this.renderHiddenButton(); + } + return ( expect(submitEvent).not.toHaveReceivedEvent(); }); + + test('should submit the form by id when form is set async', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/27952', + }); + await page.setContent( + ` +
+ Submit + `, + config + ); + + const submitEvent = await page.spyOnEvent('submit'); + const button = page.locator('ion-button'); + + await button.evaluate((el: HTMLIonButtonElement) => (el.form = 'myForm')); + + await page.click('ion-button'); + + expect(submitEvent).toHaveReceivedEvent(); + }); }); test.describe(title('should throw a warning if the form cannot be found'), () => { diff --git a/core/src/components/button/test/form-reference/button.spec.ts b/core/src/components/button/test/form-reference/button.spec.ts new file mode 100644 index 0000000000..810f46c846 --- /dev/null +++ b/core/src/components/button/test/form-reference/button.spec.ts @@ -0,0 +1,31 @@ +import { newSpecPage } from '@stencil/core/testing'; +import { Button } from '../../button'; + +describe('Button: Hidden Form Button', () => { + it('should not add multiple buttons to the form', async () => { + const page = await newSpecPage({ + components: [Button], + html: ` +
+ Submit + `, + }); + + const getButtons = () => { + return page.body.querySelectorAll('form button'); + }; + + const form = page.body.querySelectorAll('form'); + const button = page.body.querySelector('ion-button'); + + await page.waitForChanges(); + + expect(getButtons().length).toEqual(1); + + // Re-render the component + button.color = 'danger'; + await page.waitForChanges(); + + expect(getButtons().length).toEqual(1); + }); +});