diff --git a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts index e961ba9d37b..634aeb4ebfd 100644 --- a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts +++ b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.spec.ts @@ -1,6 +1,6 @@ import type { SinonStub } from 'sinon'; import userEvent from '@testing-library/user-event'; -import { screen, waitFor } from '@testing-library/dom'; +import { screen, waitFor, fireEvent } from '@testing-library/dom'; import { useSandbox, useDefineProperty } from '@18f/identity-test-helpers'; import '@18f/identity-spinner-button/spinner-button-element'; import { CAPTCHA_EVENT_NAME } from './captcha-submit-button-element'; @@ -35,58 +35,30 @@ describe('CaptchaSubmitButtonElement', () => { `; }); - it('submits the form', async () => { + it('does not prevent default form submission', async () => { const button = screen.getByRole('button', { name: 'Submit' }); const form = document.querySelector('form')!; - sandbox.stub(form, 'submit'); + let didSubmit = false; + form.addEventListener('submit', (event) => { + expect(event.defaultPrevented).to.equal(false); + event.preventDefault(); + didSubmit = true; + }); await userEvent.click(button); - await waitFor(() => expect((form.submit as SinonStub).called).to.be.true()); + await waitFor(() => expect(didSubmit).to.be.true()); }); - context('with form validation errors', () => { - beforeEach(() => { - document.body.innerHTML = ` -
- - - - - - -
- `; - }); - - it('does not submit the form and reports validity', async () => { - const button = screen.getByRole('button', { name: 'Submit' }); - const form = document.querySelector('form')!; - const input = document.querySelector('input')!; - - let didSubmit = false; - form.addEventListener('submit', (event) => { - event.preventDefault(); - didSubmit = true; - }); - - let didReportInvalid = false; - input.addEventListener('invalid', () => { - didReportInvalid = true; - }); + it('unbinds form events when disconnected', () => { + const submitButton = document.querySelector('lg-captcha-submit-button')!; + const form = submitButton.form!; + form.removeChild(submitButton); - await userEvent.click(button); + sandbox.spy(submitButton, 'shouldInvokeChallenge'); + fireEvent.submit(form); - expect(didSubmit).to.be.false(); - expect(didReportInvalid).to.be.true(); - }); - - it('stops or otherwise prevents the spinner button from spinning', async () => { - const button = screen.getByRole('button', { name: 'Submit' }); - await userEvent.click(button); - - expect(document.querySelector('.spinner-button--spinner-active')).to.not.exist(); - }); + expect(submitButton.shouldInvokeChallenge).not.to.have.been.called(); }); context('with configured recaptcha', () => { @@ -130,7 +102,7 @@ describe('CaptchaSubmitButtonElement', () => { expect(grecaptcha.execute).to.have.been.calledWith(RECAPTCHA_SITE_KEY, { action: RECAPTCHA_ACTION_NAME, }); - expect(Object.fromEntries(new FormData(form))).to.deep.equal({ + expect(Object.fromEntries(new window.FormData(form))).to.deep.equal({ recaptcha_token: RECAPTCHA_TOKEN_VALUE, }); }); @@ -145,10 +117,14 @@ describe('CaptchaSubmitButtonElement', () => { const button = screen.getByRole('button', { name: 'Submit' }); const form = document.querySelector('form')!; - sandbox.stub(form, 'submit'); + let didSubmit = false; + form.addEventListener('submit', (event) => { + event.preventDefault(); + didSubmit = true; + }); await userEvent.click(button); - await waitFor(() => expect((form.submit as SinonStub).called).to.be.true()); + await waitFor(() => expect(didSubmit).to.be.true()); expect(grecaptcha.ready).not.to.have.been.called(); }); diff --git a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts index 3828d484d39..7f5eecde1c1 100644 --- a/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts +++ b/app/javascript/packages/captcha-submit-button/captcha-submit-button-element.ts @@ -1,8 +1,16 @@ export const CAPTCHA_EVENT_NAME = 'lg:captcha-submit-button:challenge'; class CaptchaSubmitButtonElement extends HTMLElement { + form: HTMLFormElement | null; + connectedCallback() { - this.button.addEventListener('click', (event) => this.handleButtonClick(event)); + this.form = this.closest('form'); + + this.form?.addEventListener('submit', this.handleFormSubmit); + } + + disconnectedCallback() { + this.form?.removeEventListener('submit', this.handleFormSubmit); } get button(): HTMLButtonElement { @@ -13,10 +21,6 @@ class CaptchaSubmitButtonElement extends HTMLElement { return this.querySelector('[type=hidden]')!; } - get form(): HTMLFormElement | null { - return this.closest('form'); - } - get recaptchaSiteKey(): string | null { return this.getAttribute('recaptcha-site-key'); } @@ -48,21 +52,12 @@ class CaptchaSubmitButtonElement extends HTMLElement { return !event.defaultPrevented; } - handleButtonClick(event: MouseEvent) { - event.preventDefault(); - - if (this.form && !this.form.reportValidity()) { - // Prevent any associated custom click handling, e.g. spinner button spinning - event.stopImmediatePropagation(); - return; - } - + handleFormSubmit = (event: SubmitEvent) => { if (this.shouldInvokeChallenge()) { + event.preventDefault(); this.invokeChallenge(); - } else { - this.submit(); } - } + }; } declare global { diff --git a/app/javascript/packages/spinner-button/spinner-button-element.spec.ts b/app/javascript/packages/spinner-button/spinner-button-element.spec.ts index ed2678340bc..12e132e0d1b 100644 --- a/app/javascript/packages/spinner-button/spinner-button-element.spec.ts +++ b/app/javascript/packages/spinner-button/spinner-button-element.spec.ts @@ -3,7 +3,6 @@ import { getByRole, fireEvent, screen } from '@testing-library/dom'; import type { SinonStub } from 'sinon'; import { useSandbox } from '@18f/identity-test-helpers'; import './spinner-button-element'; -import type { SpinnerButtonElement } from './spinner-button-element'; describe('SpinnerButtonElement', () => { const sandbox = useSandbox({ useFakeTimers: true }); @@ -14,20 +13,37 @@ describe('SpinnerButtonElement', () => { interface WrapperOptions { actionMessage?: string; - tagName?: string; - spinOnClick?: boolean; + inForm?: boolean; + isButtonTo?: boolean; } - function createWrapper({ actionMessage, tagName = 'a', spinOnClick }: WrapperOptions = {}) { - document.body.innerHTML = ` + function createWrapper({ + actionMessage, + tagName = 'a', + spinOnClick, + inForm, + isButtonTo, + }: WrapperOptions = {}) { + let tag; + if (tagName === 'a') { + tag = 'Click Me'; + } else { + tag = ''; + } + + if (isButtonTo) { + tag = `
${tag}
`; + } + + let html = `
- ${tagName === 'a' ? 'Click Me' : ''} + ${tag}