diff --git a/src/liquid/components/ld-button/ld-button.tsx b/src/liquid/components/ld-button/ld-button.tsx index 6fc512cbfe..f68c59b557 100644 --- a/src/liquid/components/ld-button/ld-button.tsx +++ b/src/liquid/components/ld-button/ld-button.tsx @@ -76,8 +76,19 @@ export class LdButton implements InnerFocusable { }) } + private clickFakeButton( + form: HTMLFormElement, + buttonType: 'submit' | 'reset' + ) { + const btnFake = document.createElement('button') + btnFake.type = buttonType + btnFake.style.display = 'none' + form.appendChild(btnFake) + btnFake.click() + btnFake.remove() + } + private handleClick = (event: MouseEvent) => { - console.log(event.target) const ariaDisabled = this.button.getAttribute('aria-disabled') if (this.disabled || (ariaDisabled && ariaDisabled !== 'false')) { @@ -85,6 +96,20 @@ export class LdButton implements InnerFocusable { event.stopImmediatePropagation() return } + + if (!this.href) { + const form = this.el.closest('form') + if (form) { + switch (this.el.getAttribute('type')) { + case 'reset': + this.clickFakeButton(form, 'reset') + break + case 'submit': + default: + this.clickFakeButton(form, 'submit') + } + } + } } componentWillLoad() { diff --git a/src/liquid/components/ld-button/test/ld-button.e2e.ts b/src/liquid/components/ld-button/test/ld-button.e2e.ts index 01f040ade8..7422a737ff 100644 --- a/src/liquid/components/ld-button/test/ld-button.e2e.ts +++ b/src/liquid/components/ld-button/test/ld-button.e2e.ts @@ -4,14 +4,6 @@ import { LdButton } from '../ld-button' jest.useRealTimers() -const themes = [ - 'ocean', - // 'bubblegum', - // 'shake', - // 'solvent', - 'tea', -] - const modes = [ '', 'highlight', @@ -35,318 +27,300 @@ const allowableMismatchedRatio = 0.02 describe('ld-button', () => { for (const mode of modes) { - const modeStr = mode ? ` ${mode}` : '' - describe(`themed${modeStr}`, () => { - for (const theme of themes) { - // Themed - it(`default theme-${theme}${modeStr}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`hover theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - await page.hover('ld-button') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`focus theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - await page.keyboard.press('Tab') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`active theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - await page.keyboard.press('Tab') - await page.keyboard.down('Space') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) + const modeStr = mode ? `mode ${mode}` : 'mode none' + describe(modeStr, () => { + it(`default ${modeStr}`, async () => { + const page = await getPageWithContent( + `Text` + ) + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`hover`, async () => { + const page = await getPageWithContent( + `Text` + ) + await page.hover('ld-button') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`focus`, async () => { + const page = await getPageWithContent( + `Text` + ) + await page.keyboard.press('Tab') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`active`, async () => { + const page = await getPageWithContent( + `Text` + ) + await page.keyboard.press('Tab') + await page.keyboard.down('Space') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) - // Disabled - it(`disabled theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`disabled hover theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - await page.hover('ld-button') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`disabled focus theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - await page.keyboard.press('Tab') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`disabled active theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - await page.keyboard.press('Tab') - await page.keyboard.down('Space') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) + // Disabled + it(`disabled`, async () => { + const page = await getPageWithContent( + `Text` + ) + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`disabled hover`, async () => { + const page = await getPageWithContent( + `Text` + ) + await page.hover('ld-button') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`disabled focus`, async () => { + const page = await getPageWithContent( + `Text` + ) + await page.keyboard.press('Tab') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`disabled active`, async () => { + const page = await getPageWithContent( + `Text` + ) + await page.keyboard.press('Tab') + await page.keyboard.down('Space') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) - // Aria-disabled - it(`aria-disabled theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`aria-disabled hover theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - await page.hover('ld-button') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`aria-disabled focus theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - await page.keyboard.press('Tab') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`aria-disabled active theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - await page.keyboard.press('Tab') - await page.keyboard.down('Space') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) + // Aria-disabled + it(`aria-disabled`, async () => { + const page = await getPageWithContent( + `Text` + ) + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`aria-disabled hover`, async () => { + const page = await getPageWithContent( + `Text` + ) + await page.hover('ld-button') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`aria-disabled focus`, async () => { + const page = await getPageWithContent( + `Text` + ) + await page.keyboard.press('Tab') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`aria-disabled active`, async () => { + const page = await getPageWithContent( + `Text` + ) + await page.keyboard.press('Tab') + await page.keyboard.down('Space') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) - // Progress button - it(`progress button theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) + // Progress button + it(`progress button`, async () => { + const page = await getPageWithContent( + `Text` + ) + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) - it(`progress button secondary theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) + it(`progress button secondary`, async () => { + const page = await getPageWithContent( + `Text` + ) + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) - it(`progress button ghost theme-${theme}`, async () => { - const page = await getPageWithContent( - `Text`, - theme - ) - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) + it(`progress button ghost`, async () => { + const page = await getPageWithContent( + `Text` + ) + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) - // Themed CSS component - const modeModifier = mode ? ` ld-button--${mode}` : '' - it(`css component default theme-${theme}${modeStr}`, async () => { - const page = await getPageWithContent( - ``, - theme, - [LdButton, LdIcon] - ) - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`css component hover theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - [LdButton, LdIcon] - ) - await page.hover('.ld-button') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`css component focus theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - [LdButton, LdIcon] - ) - await page.keyboard.press('Tab') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`css component active theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - [LdButton, LdIcon] - ) - await page.keyboard.press('Tab') - await page.keyboard.down('Space') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) + // CSS component + const modeModifier = mode ? ` ld-button--${mode}` : '' + it(`css component default ${modeStr}`, async () => { + const page = await getPageWithContent( + ``, + undefined, + [LdButton, LdIcon] + ) + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`css component hover`, async () => { + const page = await getPageWithContent( + ``, + undefined, + [LdButton, LdIcon] + ) + await page.hover('.ld-button') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`css component focus`, async () => { + const page = await getPageWithContent( + ``, + undefined, + [LdButton, LdIcon] + ) + await page.keyboard.press('Tab') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`css component active`, async () => { + const page = await getPageWithContent( + ``, + undefined, + [LdButton, LdIcon] + ) + await page.keyboard.press('Tab') + await page.keyboard.down('Space') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) - // Disabled CSS component - it(`css component disabled theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - [LdButton, LdIcon] - ) - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`css component disabled hover theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - [LdButton, LdIcon] - ) - await page.hover('.ld-button') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`css component disabled focus theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - [LdButton, LdIcon] - ) - await page.keyboard.press('Tab') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`css component disabled active theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - [LdButton, LdIcon] - ) - await page.keyboard.press('Tab') - await page.keyboard.down('Space') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) + // Disabled CSS component + it(`css component disabled`, async () => { + const page = await getPageWithContent( + ``, + undefined, + [LdButton, LdIcon] + ) + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`css component disabled hover`, async () => { + const page = await getPageWithContent( + ``, + undefined, + [LdButton, LdIcon] + ) + await page.hover('.ld-button') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`css component disabled focus`, async () => { + const page = await getPageWithContent( + ``, + undefined, + [LdButton, LdIcon] + ) + await page.keyboard.press('Tab') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`css component disabled active`, async () => { + const page = await getPageWithContent( + ``, + undefined, + [LdButton, LdIcon] + ) + await page.keyboard.press('Tab') + await page.keyboard.down('Space') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) - // Aria-disabled CSS component - it(`css component aria-disabled theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - [LdButton, LdIcon] - ) - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`css component aria-disabled hover theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - [LdButton, LdIcon] - ) - await page.hover('.ld-button') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`css component aria-disabled focus theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - [LdButton, LdIcon] - ) - await page.keyboard.press('Tab') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - it(`css component aria-disabled active theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - [LdButton, LdIcon] - ) - await page.keyboard.press('Tab') - await page.keyboard.down('Space') - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) + // Aria-disabled CSS component + it(`css component aria-disabled`, async () => { + const page = await getPageWithContent( + ``, + undefined, + [LdButton, LdIcon] + ) + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`css component aria-disabled hover`, async () => { + const page = await getPageWithContent( + ``, + undefined, + [LdButton, LdIcon] + ) + await page.hover('.ld-button') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`css component aria-disabled focus`, async () => { + const page = await getPageWithContent( + ``, + undefined, + [LdButton, LdIcon] + ) + await page.keyboard.press('Tab') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) + it(`css component aria-disabled active`, async () => { + const page = await getPageWithContent( + ``, + undefined, + [LdButton, LdIcon] + ) + await page.keyboard.press('Tab') + await page.keyboard.down('Space') + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) - // Progress button CSS component - it(`css component progress button theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - LdButton - ) - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) + // Progress button CSS component + it(`css component progress button`, async () => { + const page = await getPageWithContent( + ``, + undefined, + LdButton + ) + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) - it(`css component progress button secondary theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - LdButton - ) - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) + it(`css component progress button secondary`, async () => { + const page = await getPageWithContent( + ``, + undefined, + LdButton + ) + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) - it(`css component progress button ghost theme-${theme}`, async () => { - const page = await getPageWithContent( - ``, - theme, - LdButton - ) - const results = await page.compareScreenshot() - expect(results).toMatchScreenshot({ allowableMismatchedRatio }) - }) - } + it(`css component progress button ghost`, async () => { + const page = await getPageWithContent( + ``, + undefined, + LdButton + ) + const results = await page.compareScreenshot() + expect(results).toMatchScreenshot({ allowableMismatchedRatio }) + }) }) } @@ -455,4 +429,21 @@ describe('ld-button', () => { expect(results).toMatchScreenshot({ allowableMismatchedRatio }) }) }) + + describe('implicit form submission', () => { + it('submits form implicitly', async () => { + const page = await getPageWithContent( + `
Text
` + ) + const form = await page.find('form') + expect(form).not.toBeNull() + const formSubmitSpy = await form.spyOnEvent('submit') + + // Using ldButton.click here leads to Error: Node is either not visible or not an HTMLElement + await page.evaluate(() => document.querySelector('ld-button').click()) + page.waitForChanges() + + expect(formSubmitSpy).toHaveReceivedEvent() + }) + }) }) diff --git a/src/liquid/components/ld-button/test/ld-button.spec.ts b/src/liquid/components/ld-button/test/ld-button.spec.ts index 479f559177..0a94011825 100644 --- a/src/liquid/components/ld-button/test/ld-button.spec.ts +++ b/src/liquid/components/ld-button/test/ld-button.spec.ts @@ -176,4 +176,87 @@ describe('ld-button', () => { expect(button.focus).toHaveBeenCalled() }) + + describe('implicit form submission', () => { + beforeAll(() => { + // Mock clickFakeButton (actual implementation tested in e2e test). + jest + .spyOn( + (LdButton.prototype as unknown) as { clickFakeButton }, + 'clickFakeButton' + ) + .mockImplementation( + (form: HTMLFormElement, buttonType: 'submit' | 'reset') => { + form.dispatchEvent(new Event(buttonType)) + } + ) + }) + + afterAll(() => { + jest.restoreAllMocks() + }) + + it('submits a form implicitly', async () => { + const page = await newSpecPage({ + components: [LdButton], + html: `
Text
`, + }) + const form = page.body.querySelector('form') + + const ldButton = page.body.querySelector('ld-button') + const submitHandler = jest.fn() + const resetHandler = jest.fn() + + form.addEventListener('submit', submitHandler) + form.addEventListener('reset', resetHandler) + ldButton.dispatchEvent( + new MouseEvent('click', { bubbles: true, cancelable: true }) + ) + + expect(submitHandler).toHaveBeenCalled() + expect(resetHandler).not.toHaveBeenCalled() + }) + + it('does not submit a form as an anchor', async () => { + const page = await newSpecPage({ + components: [LdButton], + html: `
Text
`, + }) + const form = page.body.querySelector('form') + + const ldButton = page.body.querySelector('ld-button') + const submitHandler = jest.fn() + const resetHandler = jest.fn() + + form.addEventListener('submit', submitHandler) + form.addEventListener('reset', resetHandler) + ldButton.dispatchEvent( + new MouseEvent('click', { bubbles: true, cancelable: true }) + ) + + expect(submitHandler).not.toHaveBeenCalled() + expect(resetHandler).not.toHaveBeenCalled() + }) + + it('resets a form', async () => { + const page = await newSpecPage({ + components: [LdButton], + html: `
Text
`, + }) + const form = page.body.querySelector('form') + + const ldButton = page.body.querySelector('ld-button') + const submitHandler = jest.fn() + const resetHandler = jest.fn() + + form.addEventListener('submit', submitHandler) + form.addEventListener('reset', resetHandler) + ldButton.dispatchEvent( + new MouseEvent('click', { bubbles: true, cancelable: true }) + ) + + expect(submitHandler).not.toHaveBeenCalled() + expect(resetHandler).toHaveBeenCalled() + }) + }) })