commit b6323dc4976f88b3c9100c4a25bda0236a05b5a9 parent 2a64929749cab47d242785f48eadce3225415000 Author: Reem H <42309026+reemhamz@users.noreply.github.com> Date: Tue, 30 Dec 2025 23:15:48 +0000 Bug 2007031 - Update ModalOverlay component to use native HTML dialog element. r=home-newtab-reviewers,nina-py Differential Revision: https://phabricator.services.mozilla.com/D277131 Diffstat:
10 files changed, 237 insertions(+), 435 deletions(-)
diff --git a/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/TopicSelection/TopicSelection.jsx b/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/TopicSelection/TopicSelection.jsx @@ -132,30 +132,13 @@ function TopicSelection({ supportUrl }) { }, [inputRef]); const handleFocus = useCallback(e => { - // this list will have to be updated with other reusable components that get used inside of this modal - const tabbableElements = modalRef.current.querySelectorAll( - 'a[href], button, moz-button, input[tabindex="0"]' - ); - const [firstTabableEl] = tabbableElements; - const lastTabbableEl = tabbableElements[tabbableElements.length - 1]; - - let isTabPressed = e.key === "Tab" || e.keyCode === 9; - let isArrowPressed = e.key === "ArrowUp" || e.key === "ArrowDown"; + const isArrowPressed = e.key === "ArrowUp" || e.key === "ArrowDown"; - if (isTabPressed) { - if (e.shiftKey) { - if (document.activeElement === firstTabableEl) { - lastTabbableEl.focus(); - e.preventDefault(); - } - } else if (document.activeElement === lastTabbableEl) { - firstTabableEl.focus(); - e.preventDefault(); - } - } else if ( + if ( isArrowPressed && checkboxWrapperRef.current.contains(document.activeElement) ) { + e.preventDefault(); const checkboxElements = checkboxWrapperRef.current.querySelectorAll("input"); const [firstInput] = checkboxElements; diff --git a/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/TopicSelection/_TopicSelection.scss b/browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/TopicSelection/_TopicSelection.scss @@ -1,15 +1,9 @@ /* stylelint-disable max-nesting-depth */ -.modalOverlayOuter.active:has(.topic-selection-container) { - background-color: rgba(21, 20, 26, 50%); -} - .topic-selection-container { --transition: 0.6s opacity, 0.6s scale, 0.6s rotate, 0.6s translate; position: relative; - border-radius: var(--border-radius-medium); - box-shadow: $shadow-large; padding: var(--space-xxlarge); max-width: 745px; height: auto; @@ -64,22 +58,6 @@ justify-content: space-between; align-items: center; - > a { - color: var(--link-color); - - &:hover { - color: var(--link-color-hover); - } - - &:hover:active { - color: var(--link-color-active); - } - - &:visited { - color: var(--link-color-visited); - } - } - .button-group { gap: var(--space-medium); display: flex; diff --git a/browser/extensions/newtab/content-src/components/ModalOverlay/ModalOverlay.jsx b/browser/extensions/newtab/content-src/components/ModalOverlay/ModalOverlay.jsx @@ -2,11 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React, { useEffect, useCallback, useRef } from "react"; +import React, { useEffect, useRef } from "react"; function ModalOverlayWrapper({ - // eslint-disable-next-line no-shadow - document = globalThis.document, unstyled, innerClassName, onClose, @@ -14,50 +12,48 @@ function ModalOverlayWrapper({ headerId, id, }) { - const modalRef = useRef(null); + const dialogRef = useRef(null); - let className = unstyled ? "" : "modalOverlayInner active"; + let className = unstyled ? "" : "modalOverlayInner"; if (innerClassName) { className += ` ${innerClassName}`; } - // The intended behaviour is to listen for an escape key - // but not for a click; see Bug 1582242 - const onKeyDown = useCallback( - event => { - if (event.key === "Escape") { - onClose(event); - } - }, - [onClose] - ); - useEffect(() => { - document.addEventListener("keydown", onKeyDown); - document.body.classList.add("modal-open"); + const dialogElement = dialogRef.current; + if (dialogElement && !dialogElement.open) { + dialogElement.showModal(); + } + + const handleCancel = e => { + e.preventDefault(); + onClose(e); + }; + + dialogElement?.addEventListener("cancel", handleCancel); return () => { - document.removeEventListener("keydown", onKeyDown); - document.body.classList.remove("modal-open"); + dialogElement?.removeEventListener("cancel", handleCancel); + if (dialogElement && dialogElement.open) { + dialogElement.close(); + } }; - }, [document, onKeyDown]); + }, [onClose]); return ( - <div - className="modalOverlayOuter active" - onKeyDown={onKeyDown} - role="presentation" + <dialog + ref={dialogRef} + className="modalOverlayOuter" + onClick={e => { + if (e.target === dialogRef.current) { + onClose(e); + } + }} > - <div - className={className} - aria-labelledby={headerId} - id={id} - role="dialog" - ref={modalRef} - > + <div className={className} aria-labelledby={headerId} id={id}> {children} </div> - </div> + </dialog> ); } diff --git a/browser/extensions/newtab/content-src/components/ModalOverlay/_ModalOverlay.scss b/browser/extensions/newtab/content-src/components/ModalOverlay/_ModalOverlay.scss @@ -1,101 +1,10 @@ -// Variable for the about:welcome modal scrollbars -$modal-scrollbar-z-index: 1100; - -.activity-stream { - &.modal-open { - overflow: hidden; - } -} - .modalOverlayOuter { - background: var(--newtab-overlay-color); - height: 100%; - position: fixed; - inset-block-start: 0; - inset-inline-start: 0; - width: 100%; - display: none; - z-index: $modal-scrollbar-z-index; - overflow: auto; - - &.active { - display: flex; - } -} - -.modalOverlayInner { - min-width: min-content; - width: 100%; - max-width: 960px; - position: relative; - margin: auto; - background: var(--newtab-background-color-secondary); - box-shadow: $shadow-large; - border-radius: var(--border-radius-small); - display: none; - z-index: $modal-scrollbar-z-index; - - // modal takes over entire screen - @media(width <= 960px) { - height: 100%; - inset-block-start: 0; - inset-inline-start: 0; - box-shadow: none; - border-radius: 0; - } - - &.active { - display: block; - } - - h2 { - color: var(--newtab-text-primary-color); - text-align: center; - margin-block-start: var(--space-xxlarge); - font-size: var(--font-size-xxlarge); - - @media(width <= 960px) { - // Bug 1967304 - Large number (96px) - margin-block-start: calc(var(--space-xlarge) * 4); - } - - @media(width <= 850px) { - margin-block-start: var(--space-xxlarge); - } - } - - .footer { - border-block-start: 1px solid var(--border-color); - border-radius: var(--border-radius-small); - height: 70px; - width: 100%; - position: absolute; - inset-block-end: 0; - text-align: center; - background-color: $white; - - // if modal is short enough, footer becomes sticky - @media(width <= 850px) and (height <= 730px) { - position: sticky; - } - - // if modal is narrow enough, footer becomes sticky - @media(width <= 650px) and (height <= 600px) { - position: sticky; - } - - .modalButton { - margin-block-start: var(--space-large); - min-width: 150px; - height: 30px; - padding: var(--space-xsmall) var(--space-xxlarge); - font-size: inherit; + border: none; + box-shadow: var(--box-shadow-popup); + padding: 0; + border-radius: var(--border-radius-medium); - &:focus, - &.active, - &:hover { - @include fade-in-card; - } - } + &::backdrop { + background: var(--newtab-overlay-color); } } diff --git a/browser/extensions/newtab/content-src/components/TopSites/TopSiteForm.jsx b/browser/extensions/newtab/content-src/components/TopSites/TopSiteForm.jsx @@ -307,29 +307,30 @@ export class TopSiteForm extends React.PureComponent { </div> </div> <section className="actions"> - <button - className="cancel" - type="button" - onClick={this.onCancelButtonClick} - data-l10n-id="newtab-topsites-cancel-button" - /> - {previewMode ? ( - <button - className="done preview" - type="submit" - data-l10n-id="newtab-topsites-preview-button" - /> - ) : ( - <button - className="done" - type="submit" - data-l10n-id={ - showAsAdd - ? "newtab-topsites-add-button" - : "newtab-topsites-save-button" - } + <moz-button-group className="button-group"> + <moz-button + type="default" + data-l10n-id="newtab-topsites-cancel-button" + onClick={this.onCancelButtonClick} /> - )} + {previewMode ? ( + <moz-button + type="primary" + data-l10n-id="newtab-topsites-preview-button" + onClick={this.onPreviewButtonClick} + /> + ) : ( + <moz-button + type="primary" + data-l10n-id={ + showAsAdd + ? "newtab-topsites-add-button" + : "newtab-topsites-save-button" + } + onClick={this.onDoneButtonClick} + /> + )} + </moz-button-group> </section> </form> ); diff --git a/browser/extensions/newtab/content-src/components/TopSites/_TopSites.scss b/browser/extensions/newtab/content-src/components/TopSites/_TopSites.scss @@ -456,14 +456,9 @@ $calculated-max-width-twice-widest: $break-point-widest + 2 * $card-width; } .modal { - box-shadow: $shadow-secondary; - inset-inline-start: 0; - margin: 0 auto; - max-height: calc(100% - 40px); - position: fixed; - inset-inline-end: 0; + border: 0; + min-height: fit-content; inset-block-start: var(--space-xxlarge); - width: $wrapper-default-width; @media (min-width: $break-point-medium) { width: $wrapper-max-width-medium; @@ -654,11 +649,7 @@ $calculated-max-width-twice-widest: $break-point-widest + 2 * $card-width; .actions { justify-content: flex-end; - - button { - margin-inline-start: var(--space-small); - margin-inline-end: 0; - } + padding: var(--space-large) var(--space-xlarge); } @media (max-width: $break-point-medium) { diff --git a/browser/extensions/newtab/css/activity-stream.css b/browser/extensions/newtab/css/activity-stream.css @@ -1092,14 +1092,9 @@ main section { border: 1px solid var(--border-color); } .edit-topsites-wrapper .modal { - box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2); - inset-inline-start: 0; - margin: 0 auto; - max-height: calc(100% - 40px); - position: fixed; - inset-inline-end: 0; + border: 0; + min-height: fit-content; inset-block-start: var(--space-xxlarge); - width: 274px; } @media (min-width: 610px) { .edit-topsites-wrapper .modal { @@ -1253,10 +1248,7 @@ main section { } .topsite-form .actions { justify-content: flex-end; -} -.topsite-form .actions button { - margin-inline-start: var(--space-small); - margin-inline-end: 0; + padding: var(--space-large) var(--space-xlarge); } @media (max-width: 610px) { .topsite-form .fields-and-preview { @@ -4108,95 +4100,14 @@ dialog:dir(rtl)::after { text-decoration: none; } -.activity-stream.modal-open { - overflow: hidden; -} - .modalOverlayOuter { - background: var(--newtab-overlay-color); - height: 100%; - position: fixed; - inset-block-start: 0; - inset-inline-start: 0; - width: 100%; - display: none; - z-index: 1100; - overflow: auto; -} -.modalOverlayOuter.active { - display: flex; -} - -.modalOverlayInner { - min-width: min-content; - width: 100%; - max-width: 960px; - position: relative; - margin: auto; - background: var(--newtab-background-color-secondary); - box-shadow: 0 2px 14px 0 rgba(0, 0, 0, 0.2); - border-radius: var(--border-radius-small); - display: none; - z-index: 1100; -} -@media (width <= 960px) { - .modalOverlayInner { - height: 100%; - inset-block-start: 0; - inset-inline-start: 0; - box-shadow: none; - border-radius: 0; - } -} -.modalOverlayInner.active { - display: block; -} -.modalOverlayInner h2 { - color: var(--newtab-text-primary-color); - text-align: center; - margin-block-start: var(--space-xxlarge); - font-size: var(--font-size-xxlarge); -} -@media (width <= 960px) { - .modalOverlayInner h2 { - margin-block-start: calc(var(--space-xlarge) * 4); - } -} -@media (width <= 850px) { - .modalOverlayInner h2 { - margin-block-start: var(--space-xxlarge); - } -} -.modalOverlayInner .footer { - border-block-start: 1px solid var(--border-color); - border-radius: var(--border-radius-small); - height: 70px; - width: 100%; - position: absolute; - inset-block-end: 0; - text-align: center; - background-color: var(--color-white); -} -@media (width <= 850px) and (height <= 730px) { - .modalOverlayInner .footer { - position: sticky; - } -} -@media (width <= 650px) and (height <= 600px) { - .modalOverlayInner .footer { - position: sticky; - } -} -.modalOverlayInner .footer .modalButton { - margin-block-start: var(--space-large); - min-width: 150px; - height: 30px; - padding: var(--space-xsmall) var(--space-xxlarge); - font-size: inherit; + border: none; + box-shadow: var(--box-shadow-popup); + padding: 0; + border-radius: var(--border-radius-medium); } -.modalOverlayInner .footer .modalButton:focus, .modalOverlayInner .footer .modalButton.active, .modalOverlayInner .footer .modalButton:hover { - box-shadow: 0 0 0 5px var(--newtab-element-secondary-color); - transition: box-shadow 150ms; +.modalOverlayOuter::backdrop { + background: var(--newtab-overlay-color); } .notification-wrapper { @@ -8254,15 +8165,9 @@ dialog:dir(rtl)::after { } /* stylelint-disable max-nesting-depth */ -.modalOverlayOuter.active:has(.topic-selection-container) { - background-color: rgba(21, 20, 26, 0.5); -} - .topic-selection-container { --transition: 0.6s opacity, 0.6s scale, 0.6s rotate, 0.6s translate; position: relative; - border-radius: var(--border-radius-medium); - box-shadow: 0 2px 14px 0 rgba(0, 0, 0, 0.2); padding: var(--space-xxlarge); max-width: 745px; height: auto; @@ -8310,18 +8215,6 @@ dialog:dir(rtl)::after { justify-content: space-between; align-items: center; } -.topic-selection-container .modal-footer > a { - color: var(--link-color); -} -.topic-selection-container .modal-footer > a:hover { - color: var(--link-color-hover); -} -.topic-selection-container .modal-footer > a:hover:active { - color: var(--link-color-active); -} -.topic-selection-container .modal-footer > a:visited { - color: var(--link-color-visited); -} .topic-selection-container .modal-footer .button-group { gap: var(--space-medium); display: flex; diff --git a/browser/extensions/newtab/data/content/activity-stream.bundle.js b/browser/extensions/newtab/data/content/activity-stream.bundle.js @@ -6151,8 +6151,6 @@ class MoreRecommendations extends (external_React_default()).PureComponent { function ModalOverlayWrapper({ - // eslint-disable-next-line no-shadow - document = globalThis.document, unstyled, innerClassName, onClose, @@ -6160,37 +6158,40 @@ function ModalOverlayWrapper({ headerId, id }) { - const modalRef = (0,external_React_namespaceObject.useRef)(null); - let className = unstyled ? "" : "modalOverlayInner active"; + const dialogRef = (0,external_React_namespaceObject.useRef)(null); + let className = unstyled ? "" : "modalOverlayInner"; if (innerClassName) { className += ` ${innerClassName}`; } - - // The intended behaviour is to listen for an escape key - // but not for a click; see Bug 1582242 - const onKeyDown = (0,external_React_namespaceObject.useCallback)(event => { - if (event.key === "Escape") { - onClose(event); - } - }, [onClose]); (0,external_React_namespaceObject.useEffect)(() => { - document.addEventListener("keydown", onKeyDown); - document.body.classList.add("modal-open"); + const dialogElement = dialogRef.current; + if (dialogElement && !dialogElement.open) { + dialogElement.showModal(); + } + const handleCancel = e => { + e.preventDefault(); + onClose(e); + }; + dialogElement?.addEventListener("cancel", handleCancel); return () => { - document.removeEventListener("keydown", onKeyDown); - document.body.classList.remove("modal-open"); + dialogElement?.removeEventListener("cancel", handleCancel); + if (dialogElement && dialogElement.open) { + dialogElement.close(); + } }; - }, [document, onKeyDown]); - return /*#__PURE__*/external_React_default().createElement("div", { - className: "modalOverlayOuter active", - onKeyDown: onKeyDown, - role: "presentation" + }, [onClose]); + return /*#__PURE__*/external_React_default().createElement("dialog", { + ref: dialogRef, + className: "modalOverlayOuter", + onClick: e => { + if (e.target === dialogRef.current) { + onClose(e); + } + } }, /*#__PURE__*/external_React_default().createElement("div", { className: className, "aria-labelledby": headerId, - id: id, - role: "dialog", - ref: modalRef + id: id }, children)); } @@ -9300,20 +9301,21 @@ class TopSiteForm extends (external_React_default()).PureComponent { title: this.state.label }))), /*#__PURE__*/external_React_default().createElement("section", { className: "actions" - }, /*#__PURE__*/external_React_default().createElement("button", { - className: "cancel", - type: "button", - onClick: this.onCancelButtonClick, - "data-l10n-id": "newtab-topsites-cancel-button" - }), previewMode ? /*#__PURE__*/external_React_default().createElement("button", { - className: "done preview", - type: "submit", - "data-l10n-id": "newtab-topsites-preview-button" - }) : /*#__PURE__*/external_React_default().createElement("button", { - className: "done", - type: "submit", - "data-l10n-id": showAsAdd ? "newtab-topsites-add-button" : "newtab-topsites-save-button" - }))); + }, /*#__PURE__*/external_React_default().createElement("moz-button-group", { + className: "button-group" + }, /*#__PURE__*/external_React_default().createElement("moz-button", { + type: "default", + "data-l10n-id": "newtab-topsites-cancel-button", + onClick: this.onCancelButtonClick + }), previewMode ? /*#__PURE__*/external_React_default().createElement("moz-button", { + type: "primary", + "data-l10n-id": "newtab-topsites-preview-button", + onClick: this.onPreviewButtonClick + }) : /*#__PURE__*/external_React_default().createElement("moz-button", { + type: "primary", + "data-l10n-id": showAsAdd ? "newtab-topsites-add-button" : "newtab-topsites-save-button", + onClick: this.onDoneButtonClick + })))); } } TopSiteForm.defaultProps = { @@ -15206,23 +15208,9 @@ function TopicSelection({ inputRef?.current?.focus(); }, [inputRef]); const handleFocus = (0,external_React_namespaceObject.useCallback)(e => { - // this list will have to be updated with other reusable components that get used inside of this modal - const tabbableElements = modalRef.current.querySelectorAll('a[href], button, moz-button, input[tabindex="0"]'); - const [firstTabableEl] = tabbableElements; - const lastTabbableEl = tabbableElements[tabbableElements.length - 1]; - let isTabPressed = e.key === "Tab" || e.keyCode === 9; - let isArrowPressed = e.key === "ArrowUp" || e.key === "ArrowDown"; - if (isTabPressed) { - if (e.shiftKey) { - if (document.activeElement === firstTabableEl) { - lastTabbableEl.focus(); - e.preventDefault(); - } - } else if (document.activeElement === lastTabbableEl) { - firstTabableEl.focus(); - e.preventDefault(); - } - } else if (isArrowPressed && checkboxWrapperRef.current.contains(document.activeElement)) { + const isArrowPressed = e.key === "ArrowUp" || e.key === "ArrowDown"; + if (isArrowPressed && checkboxWrapperRef.current.contains(document.activeElement)) { + e.preventDefault(); const checkboxElements = checkboxWrapperRef.current.querySelectorAll("input"); const [firstInput] = checkboxElements; const lastInput = checkboxElements[checkboxElements.length - 1]; diff --git a/browser/extensions/newtab/test/unit/content-src/components/ModalOverlay.test.jsx b/browser/extensions/newtab/test/unit/content-src/components/ModalOverlay.test.jsx @@ -3,67 +3,64 @@ import { mount } from "enzyme"; import React from "react"; describe("ModalOverlayWrapper", () => { - let fakeDoc; let sandbox; - let header; beforeEach(() => { sandbox = sinon.createSandbox(); - header = document.createElement("div"); - - fakeDoc = { - addEventListener: sandbox.stub(), - removeEventListener: sandbox.stub(), - body: { classList: { add: sandbox.stub(), remove: sandbox.stub() } }, - getElementById() { - return header; - }, - }; }); afterEach(() => { sandbox.restore(); }); - it("should add eventListener and a class on mount", async () => { - mount(<ModalOverlayWrapper document={fakeDoc} />); - assert.calledOnce(fakeDoc.addEventListener); - assert.calledWith(fakeDoc.body.classList.add, "modal-open"); + it("should render a dialog element", async () => { + const wrapper = mount(<ModalOverlayWrapper />); + assert.equal(wrapper.find("dialog").length, 1); }); - it("should remove eventListener on unmount", async () => { - const wrapper = mount(<ModalOverlayWrapper document={fakeDoc} />); - wrapper.unmount(); - assert.calledOnce(fakeDoc.addEventListener); - assert.calledOnce(fakeDoc.removeEventListener); - assert.calledWith(fakeDoc.body.classList.remove, "modal-open"); + it("should call showModal on mount", async () => { + const wrapper = mount(<ModalOverlayWrapper />); + const showModalStub = sandbox.stub(); + sandbox.stub(React, "useRef").returns({ + current: { showModal: showModalStub, open: false }, + }); + mount(<ModalOverlayWrapper />); + // Dialog showModal is called via useEffect + assert.ok(wrapper.find("dialog").exists()); }); - it("should call props.onClose on an Escape key", async () => { + it("should call props.onClose on an Escape key via cancel event", async () => { const onClose = sandbox.stub(); - mount(<ModalOverlayWrapper document={fakeDoc} onClose={onClose} />); + const wrapper = mount(<ModalOverlayWrapper onClose={onClose} />); - // Simulate onkeydown being called - const [, callback] = fakeDoc.addEventListener.firstCall.args; - callback({ key: "Escape" }); + // Simulate cancel event (fired when Escape is pressed on dialog) + const dialog = wrapper.find("dialog").getDOMNode(); + const cancelEvent = new Event("cancel", { cancelable: true }); + dialog.dispatchEvent(cancelEvent); assert.calledOnce(onClose); }); - it("should not call props.onClose on other keys than Escape", async () => { + it("should call props.onClose when clicked on dialog backdrop", async () => { const onClose = sandbox.stub(); - mount(<ModalOverlayWrapper document={fakeDoc} onClose={onClose} />); + const wrapper = mount(<ModalOverlayWrapper onClose={onClose} />); - // Simulate onkeydown being called - const [, callback] = fakeDoc.addEventListener.firstCall.args; - callback({ key: "Ctrl" }); + // Simulate clicking on the dialog itself (backdrop) + const dialog = wrapper.find("dialog"); + dialog.simulate("click", { target: dialog.getDOMNode() }); - assert.notCalled(onClose); + assert.calledOnce(onClose); }); - it("should not call props.onClose when clicked outside dialog", async () => { + it("should not call props.onClose when clicked inside dialog content", async () => { const onClose = sandbox.stub(); const wrapper = mount( - <ModalOverlayWrapper document={fakeDoc} onClose={onClose} /> + <ModalOverlayWrapper onClose={onClose}> + <div className="content">Content</div> + </ModalOverlayWrapper> ); - wrapper.find("div.modalOverlayOuter.active").simulate("click"); + + // Simulate clicking on inner content (not the dialog backdrop) + const innerDiv = wrapper.find("div.modalOverlayInner"); + wrapper.find("dialog").simulate("click", { target: innerDiv.getDOMNode() }); + assert.notCalled(onClose); }); }); diff --git a/browser/extensions/newtab/test/unit/content-src/components/TopSites.test.jsx b/browser/extensions/newtab/test/unit/content-src/components/TopSites.test.jsx @@ -1020,22 +1020,42 @@ describe("<TopSiteForm>", () => { ); it("should render the preview button on invalid urls", () => { - assert.equal(0, wrapper.find(".preview").length); + assert.equal( + 0, + wrapper.find( + 'moz-button[data-l10n-id="newtab-topsites-preview-button"]' + ).length + ); wrapper.setState({ customScreenshotUrl: " " }); - assert.equal(1, wrapper.find(".preview").length); + assert.equal( + 1, + wrapper.find( + 'moz-button[data-l10n-id="newtab-topsites-preview-button"]' + ).length + ); }); it("should render the preview button when input value updated", () => { - assert.equal(0, wrapper.find(".preview").length); + assert.equal( + 0, + wrapper.find( + 'moz-button[data-l10n-id="newtab-topsites-preview-button"]' + ).length + ); wrapper.setState({ customScreenshotUrl: "http://baz.com", screenshotPreview: null, }); - assert.equal(1, wrapper.find(".preview").length); + assert.equal( + 1, + wrapper.find( + 'moz-button[data-l10n-id="newtab-topsites-preview-button"]' + ).length + ); }); }); @@ -1050,14 +1070,18 @@ describe("<TopSiteForm>", () => { it("shouldn't dispatch a request for invalid urls", () => { wrapper.setState({ customScreenshotUrl: " ", url: "foo" }); - wrapper.find(".preview").simulate("click"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-preview-button"]') + .simulate("click"); assert.notCalled(wrapper.props().dispatch); }); it("should dispatch a PREVIEW_REQUEST", () => { wrapper.setState({ customScreenshotUrl: "screenshot" }); - wrapper.find(".preview").simulate("submit"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-preview-button"]') + .simulate("click"); assert.calledTwice(wrapper.props().dispatch); assert.calledWith( @@ -1154,23 +1178,31 @@ describe("<TopSiteForm>", () => { assert.equal(0, wrapper.find(".custom-image-input-container").length); }); it("should call onClose if Cancel button is clicked", () => { - wrapper.find(".cancel").simulate("click"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-cancel-button"]') + .simulate("click"); assert.calledOnce(wrapper.instance().props.onClose); }); it("should set validationError if url is empty", () => { assert.equal(wrapper.state().validationError, false); - wrapper.find(".done").simulate("submit"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-add-button"]') + .simulate("click"); assert.equal(wrapper.state().validationError, true); }); it("should set validationError if url is invalid", () => { wrapper.setState({ url: "not valid" }); assert.equal(wrapper.state().validationError, false); - wrapper.find(".done").simulate("submit"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-add-button"]') + .simulate("click"); assert.equal(wrapper.state().validationError, true); }); it("should call onClose and dispatch with right args if URL is valid", () => { wrapper.setState({ url: "valid.com", label: "a label" }); - wrapper.find(".done").simulate("submit"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-add-button"]') + .simulate("click"); assert.calledOnce(wrapper.instance().props.onClose); assert.calledWith(wrapper.instance().props.dispatch, { data: { @@ -1192,7 +1224,9 @@ describe("<TopSiteForm>", () => { }); it("should not pass empty string label in dispatch data", () => { wrapper.setState({ url: "valid.com", label: "" }); - wrapper.find(".done").simulate("submit"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-add-button"]') + .simulate("click"); assert.calledWith(wrapper.instance().props.dispatch, { data: { site: { url: "http://valid.com" }, index: -1 }, meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" }, @@ -1246,13 +1280,17 @@ describe("<TopSiteForm>", () => { ); }); it("should call onClose if Cancel button is clicked", () => { - wrapper.find(".cancel").simulate("click"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-cancel-button"]') + .simulate("click"); assert.calledOnce(wrapper.instance().props.onClose); }); it("should show error and not call onClose or dispatch if URL is empty", () => { wrapper.setState({ url: "" }); assert.equal(wrapper.state().validationError, false); - wrapper.find(".done").simulate("submit"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-save-button"]') + .simulate("click"); assert.equal(wrapper.state().validationError, true); assert.notCalled(wrapper.instance().props.onClose); assert.notCalled(wrapper.instance().props.dispatch); @@ -1260,13 +1298,17 @@ describe("<TopSiteForm>", () => { it("should show error and not call onClose or dispatch if URL is invalid", () => { wrapper.setState({ url: "not valid" }); assert.equal(wrapper.state().validationError, false); - wrapper.find(".done").simulate("submit"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-save-button"]') + .simulate("click"); assert.equal(wrapper.state().validationError, true); assert.notCalled(wrapper.instance().props.onClose); assert.notCalled(wrapper.instance().props.dispatch); }); it("should call onClose and dispatch with right args if URL is valid", () => { - wrapper.find(".done").simulate("submit"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-save-button"]') + .simulate("click"); assert.calledOnce(wrapper.instance().props.onClose); assert.calledTwice(wrapper.instance().props.dispatch); assert.calledWith(wrapper.instance().props.dispatch, { @@ -1296,7 +1338,9 @@ describe("<TopSiteForm>", () => { it("should set customScreenshotURL to null if it was removed", () => { wrapper.setState({ customScreenshotUrl: "" }); - wrapper.find(".done").simulate("submit"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-save-button"]') + .simulate("click"); assert.calledWith(wrapper.instance().props.dispatch, { data: { @@ -1313,7 +1357,9 @@ describe("<TopSiteForm>", () => { }); it("should call onClose and dispatch with right args if URL is valid (negative index)", () => { wrapper.setProps({ index: -1 }); - wrapper.find(".done").simulate("submit"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-save-button"]') + .simulate("click"); assert.calledOnce(wrapper.instance().props.onClose); assert.calledTwice(wrapper.instance().props.dispatch); assert.calledWith(wrapper.instance().props.dispatch, { @@ -1331,7 +1377,9 @@ describe("<TopSiteForm>", () => { }); it("should not pass empty string label in dispatch data", () => { wrapper.setState({ label: "" }); - wrapper.find(".done").simulate("submit"); + wrapper + .find('moz-button[data-l10n-id="newtab-topsites-save-button"]') + .simulate("click"); assert.calledWith(wrapper.instance().props.dispatch, { data: { site: { url: "https://foo.bar", customScreenshotURL: "http://foo" }, @@ -1346,14 +1394,32 @@ describe("<TopSiteForm>", () => { customScreenshotUrl: "foo", screenshotPreview: "custom", }); - assert.equal(0, wrapper.find(".preview").length); - assert.equal(1, wrapper.find(".done").length); + assert.equal( + 0, + wrapper.find( + 'moz-button[data-l10n-id="newtab-topsites-preview-button"]' + ).length + ); + assert.equal( + 1, + wrapper.find('moz-button[data-l10n-id="newtab-topsites-save-button"]') + .length + ); }); it("should render the save button if custom screenshot url was cleared", () => { wrapper.setState({ customScreenshotUrl: "" }); wrapper.setProps({ site: { customScreenshotURL: "foo" } }); - assert.equal(0, wrapper.find(".preview").length); - assert.equal(1, wrapper.find(".done").length); + assert.equal( + 0, + wrapper.find( + 'moz-button[data-l10n-id="newtab-topsites-preview-button"]' + ).length + ); + assert.equal( + 1, + wrapper.find('moz-button[data-l10n-id="newtab-topsites-save-button"]') + .length + ); }); });