tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit c14367dcaf72b3d880a474e8bc05382954e045d2
parent 6ab54c0b573e3e5ddd300bb3876ca8c757b22ceb
Author: Cristian Tuns <ctuns@mozilla.com>
Date:   Wed, 31 Dec 2025 02:24:15 -0500

Revert "Bug 2007031 - Update ModalOverlay component to use native HTML dialog element. r=home-newtab-reviewers,nina-py" for causing bc failures in browser_topsites_section.js

This reverts commit b6323dc4976f88b3c9100c4a25bda0236a05b5a9.

Diffstat:
Mbrowser/extensions/newtab/content-src/components/DiscoveryStreamComponents/TopicSelection/TopicSelection.jsx | 23++++++++++++++++++++---
Mbrowser/extensions/newtab/content-src/components/DiscoveryStreamComponents/TopicSelection/_TopicSelection.scss | 22++++++++++++++++++++++
Mbrowser/extensions/newtab/content-src/components/ModalOverlay/ModalOverlay.jsx | 62+++++++++++++++++++++++++++++++++-----------------------------
Mbrowser/extensions/newtab/content-src/components/ModalOverlay/_ModalOverlay.scss | 103++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mbrowser/extensions/newtab/content-src/components/TopSites/TopSiteForm.jsx | 45++++++++++++++++++++++-----------------------
Mbrowser/extensions/newtab/content-src/components/TopSites/_TopSites.scss | 15++++++++++++---
Mbrowser/extensions/newtab/css/activity-stream.css | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mbrowser/extensions/newtab/data/content/activity-stream.bundle.js | 98++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mbrowser/extensions/newtab/test/unit/content-src/components/ModalOverlay.test.jsx | 69++++++++++++++++++++++++++++++++++++---------------------------------
Mbrowser/extensions/newtab/test/unit/content-src/components/TopSites.test.jsx | 110++++++++++++++++---------------------------------------------------------------
10 files changed, 435 insertions(+), 237 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,13 +132,30 @@ function TopicSelection({ supportUrl }) { }, [inputRef]); const handleFocus = useCallback(e => { - const isArrowPressed = e.key === "ArrowUp" || e.key === "ArrowDown"; + // 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 ( + 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) ) { - 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,9 +1,15 @@ /* 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; @@ -58,6 +64,22 @@ 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,9 +2,11 @@ * 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, useRef } from "react"; +import React, { useEffect, useCallback, useRef } from "react"; function ModalOverlayWrapper({ + // eslint-disable-next-line no-shadow + document = globalThis.document, unstyled, innerClassName, onClose, @@ -12,48 +14,50 @@ function ModalOverlayWrapper({ headerId, id, }) { - const dialogRef = useRef(null); + const modalRef = useRef(null); - let className = unstyled ? "" : "modalOverlayInner"; + let className = unstyled ? "" : "modalOverlayInner active"; if (innerClassName) { className += ` ${innerClassName}`; } - useEffect(() => { - const dialogElement = dialogRef.current; - if (dialogElement && !dialogElement.open) { - dialogElement.showModal(); - } - - const handleCancel = e => { - e.preventDefault(); - onClose(e); - }; + // 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] + ); - dialogElement?.addEventListener("cancel", handleCancel); + useEffect(() => { + document.addEventListener("keydown", onKeyDown); + document.body.classList.add("modal-open"); return () => { - dialogElement?.removeEventListener("cancel", handleCancel); - if (dialogElement && dialogElement.open) { - dialogElement.close(); - } + document.removeEventListener("keydown", onKeyDown); + document.body.classList.remove("modal-open"); }; - }, [onClose]); + }, [document, onKeyDown]); return ( - <dialog - ref={dialogRef} - className="modalOverlayOuter" - onClick={e => { - if (e.target === dialogRef.current) { - onClose(e); - } - }} + <div + className="modalOverlayOuter active" + onKeyDown={onKeyDown} + role="presentation" > - <div className={className} aria-labelledby={headerId} id={id}> + <div + className={className} + aria-labelledby={headerId} + id={id} + role="dialog" + ref={modalRef} + > {children} </div> - </dialog> + </div> ); } diff --git a/browser/extensions/newtab/content-src/components/ModalOverlay/_ModalOverlay.scss b/browser/extensions/newtab/content-src/components/ModalOverlay/_ModalOverlay.scss @@ -1,10 +1,101 @@ +// Variable for the about:welcome modal scrollbars +$modal-scrollbar-z-index: 1100; + +.activity-stream { + &.modal-open { + overflow: hidden; + } +} + .modalOverlayOuter { - border: none; - box-shadow: var(--box-shadow-popup); - padding: 0; - border-radius: var(--border-radius-medium); + 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; - &::backdrop { - background: var(--newtab-overlay-color); + &:focus, + &.active, + &:hover { + @include fade-in-card; + } + } } } diff --git a/browser/extensions/newtab/content-src/components/TopSites/TopSiteForm.jsx b/browser/extensions/newtab/content-src/components/TopSites/TopSiteForm.jsx @@ -307,30 +307,29 @@ export class TopSiteForm extends React.PureComponent { </div> </div> <section className="actions"> - <moz-button-group className="button-group"> - <moz-button - type="default" - data-l10n-id="newtab-topsites-cancel-button" - onClick={this.onCancelButtonClick} + <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" /> - {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> + ) : ( + <button + className="done" + type="submit" + data-l10n-id={ + showAsAdd + ? "newtab-topsites-add-button" + : "newtab-topsites-save-button" + } + /> + )} </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,9 +456,14 @@ $calculated-max-width-twice-widest: $break-point-widest + 2 * $card-width; } .modal { - border: 0; - min-height: fit-content; + box-shadow: $shadow-secondary; + inset-inline-start: 0; + margin: 0 auto; + max-height: calc(100% - 40px); + position: fixed; + inset-inline-end: 0; inset-block-start: var(--space-xxlarge); + width: $wrapper-default-width; @media (min-width: $break-point-medium) { width: $wrapper-max-width-medium; @@ -649,7 +654,11 @@ $calculated-max-width-twice-widest: $break-point-widest + 2 * $card-width; .actions { justify-content: flex-end; - padding: var(--space-large) var(--space-xlarge); + + button { + margin-inline-start: var(--space-small); + margin-inline-end: 0; + } } @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,9 +1092,14 @@ main section { border: 1px solid var(--border-color); } .edit-topsites-wrapper .modal { - border: 0; - min-height: fit-content; + 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; inset-block-start: var(--space-xxlarge); + width: 274px; } @media (min-width: 610px) { .edit-topsites-wrapper .modal { @@ -1248,7 +1253,10 @@ main section { } .topsite-form .actions { justify-content: flex-end; - padding: var(--space-large) var(--space-xlarge); +} +.topsite-form .actions button { + margin-inline-start: var(--space-small); + margin-inline-end: 0; } @media (max-width: 610px) { .topsite-form .fields-and-preview { @@ -4100,14 +4108,95 @@ dialog:dir(rtl)::after { text-decoration: none; } -.modalOverlayOuter { - border: none; - box-shadow: var(--box-shadow-popup); - padding: 0; - border-radius: var(--border-radius-medium); +.activity-stream.modal-open { + overflow: hidden; } -.modalOverlayOuter::backdrop { + +.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; +} +.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; } .notification-wrapper { @@ -8165,9 +8254,15 @@ 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; @@ -8215,6 +8310,18 @@ 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,6 +6151,8 @@ class MoreRecommendations extends (external_React_default()).PureComponent { function ModalOverlayWrapper({ + // eslint-disable-next-line no-shadow + document = globalThis.document, unstyled, innerClassName, onClose, @@ -6158,40 +6160,37 @@ function ModalOverlayWrapper({ headerId, id }) { - const dialogRef = (0,external_React_namespaceObject.useRef)(null); - let className = unstyled ? "" : "modalOverlayInner"; + const modalRef = (0,external_React_namespaceObject.useRef)(null); + let className = unstyled ? "" : "modalOverlayInner active"; if (innerClassName) { className += ` ${innerClassName}`; } - (0,external_React_namespaceObject.useEffect)(() => { - const dialogElement = dialogRef.current; - if (dialogElement && !dialogElement.open) { - dialogElement.showModal(); + + // 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); } - const handleCancel = e => { - e.preventDefault(); - onClose(e); - }; - dialogElement?.addEventListener("cancel", handleCancel); + }, [onClose]); + (0,external_React_namespaceObject.useEffect)(() => { + document.addEventListener("keydown", onKeyDown); + document.body.classList.add("modal-open"); return () => { - dialogElement?.removeEventListener("cancel", handleCancel); - if (dialogElement && dialogElement.open) { - dialogElement.close(); - } + document.removeEventListener("keydown", onKeyDown); + document.body.classList.remove("modal-open"); }; - }, [onClose]); - return /*#__PURE__*/external_React_default().createElement("dialog", { - ref: dialogRef, - className: "modalOverlayOuter", - onClick: e => { - if (e.target === dialogRef.current) { - onClose(e); - } - } + }, [document, onKeyDown]); + return /*#__PURE__*/external_React_default().createElement("div", { + className: "modalOverlayOuter active", + onKeyDown: onKeyDown, + role: "presentation" }, /*#__PURE__*/external_React_default().createElement("div", { className: className, "aria-labelledby": headerId, - id: id + id: id, + role: "dialog", + ref: modalRef }, children)); } @@ -9301,21 +9300,20 @@ class TopSiteForm extends (external_React_default()).PureComponent { title: this.state.label }))), /*#__PURE__*/external_React_default().createElement("section", { className: "actions" - }, /*#__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 - })))); + }, /*#__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" + }))); } } TopSiteForm.defaultProps = { @@ -15208,9 +15206,23 @@ function TopicSelection({ inputRef?.current?.focus(); }, [inputRef]); const handleFocus = (0,external_React_namespaceObject.useCallback)(e => { - const isArrowPressed = e.key === "ArrowUp" || e.key === "ArrowDown"; - if (isArrowPressed && checkboxWrapperRef.current.contains(document.activeElement)) { - e.preventDefault(); + // 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 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,64 +3,67 @@ 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 render a dialog element", async () => { - const wrapper = mount(<ModalOverlayWrapper />); - assert.equal(wrapper.find("dialog").length, 1); + 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 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 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 props.onClose on an Escape key via cancel event", async () => { + it("should call props.onClose on an Escape key", async () => { const onClose = sandbox.stub(); - const wrapper = mount(<ModalOverlayWrapper onClose={onClose} />); + mount(<ModalOverlayWrapper document={fakeDoc} onClose={onClose} />); - // 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); + // Simulate onkeydown being called + const [, callback] = fakeDoc.addEventListener.firstCall.args; + callback({ key: "Escape" }); assert.calledOnce(onClose); }); - it("should call props.onClose when clicked on dialog backdrop", async () => { + it("should not call props.onClose on other keys than Escape", async () => { const onClose = sandbox.stub(); - const wrapper = mount(<ModalOverlayWrapper onClose={onClose} />); + mount(<ModalOverlayWrapper document={fakeDoc} onClose={onClose} />); - // Simulate clicking on the dialog itself (backdrop) - const dialog = wrapper.find("dialog"); - dialog.simulate("click", { target: dialog.getDOMNode() }); + // Simulate onkeydown being called + const [, callback] = fakeDoc.addEventListener.firstCall.args; + callback({ key: "Ctrl" }); - assert.calledOnce(onClose); + assert.notCalled(onClose); }); - it("should not call props.onClose when clicked inside dialog content", async () => { + it("should not call props.onClose when clicked outside dialog", async () => { const onClose = sandbox.stub(); const wrapper = mount( - <ModalOverlayWrapper onClose={onClose}> - <div className="content">Content</div> - </ModalOverlayWrapper> + <ModalOverlayWrapper document={fakeDoc} onClose={onClose} /> ); - - // Simulate clicking on inner content (not the dialog backdrop) - const innerDiv = wrapper.find("div.modalOverlayInner"); - wrapper.find("dialog").simulate("click", { target: innerDiv.getDOMNode() }); - + wrapper.find("div.modalOverlayOuter.active").simulate("click"); 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,42 +1020,22 @@ describe("<TopSiteForm>", () => { ); it("should render the preview button on invalid urls", () => { - assert.equal( - 0, - wrapper.find( - 'moz-button[data-l10n-id="newtab-topsites-preview-button"]' - ).length - ); + assert.equal(0, wrapper.find(".preview").length); wrapper.setState({ customScreenshotUrl: " " }); - assert.equal( - 1, - wrapper.find( - 'moz-button[data-l10n-id="newtab-topsites-preview-button"]' - ).length - ); + assert.equal(1, wrapper.find(".preview").length); }); it("should render the preview button when input value updated", () => { - assert.equal( - 0, - wrapper.find( - 'moz-button[data-l10n-id="newtab-topsites-preview-button"]' - ).length - ); + assert.equal(0, wrapper.find(".preview").length); wrapper.setState({ customScreenshotUrl: "http://baz.com", screenshotPreview: null, }); - assert.equal( - 1, - wrapper.find( - 'moz-button[data-l10n-id="newtab-topsites-preview-button"]' - ).length - ); + assert.equal(1, wrapper.find(".preview").length); }); }); @@ -1070,18 +1050,14 @@ describe("<TopSiteForm>", () => { it("shouldn't dispatch a request for invalid urls", () => { wrapper.setState({ customScreenshotUrl: " ", url: "foo" }); - wrapper - .find('moz-button[data-l10n-id="newtab-topsites-preview-button"]') - .simulate("click"); + wrapper.find(".preview").simulate("click"); assert.notCalled(wrapper.props().dispatch); }); it("should dispatch a PREVIEW_REQUEST", () => { wrapper.setState({ customScreenshotUrl: "screenshot" }); - wrapper - .find('moz-button[data-l10n-id="newtab-topsites-preview-button"]') - .simulate("click"); + wrapper.find(".preview").simulate("submit"); assert.calledTwice(wrapper.props().dispatch); assert.calledWith( @@ -1178,31 +1154,23 @@ describe("<TopSiteForm>", () => { assert.equal(0, wrapper.find(".custom-image-input-container").length); }); it("should call onClose if Cancel button is clicked", () => { - wrapper - .find('moz-button[data-l10n-id="newtab-topsites-cancel-button"]') - .simulate("click"); + wrapper.find(".cancel").simulate("click"); assert.calledOnce(wrapper.instance().props.onClose); }); it("should set validationError if url is empty", () => { assert.equal(wrapper.state().validationError, false); - wrapper - .find('moz-button[data-l10n-id="newtab-topsites-add-button"]') - .simulate("click"); + wrapper.find(".done").simulate("submit"); 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('moz-button[data-l10n-id="newtab-topsites-add-button"]') - .simulate("click"); + wrapper.find(".done").simulate("submit"); 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('moz-button[data-l10n-id="newtab-topsites-add-button"]') - .simulate("click"); + wrapper.find(".done").simulate("submit"); assert.calledOnce(wrapper.instance().props.onClose); assert.calledWith(wrapper.instance().props.dispatch, { data: { @@ -1224,9 +1192,7 @@ describe("<TopSiteForm>", () => { }); it("should not pass empty string label in dispatch data", () => { wrapper.setState({ url: "valid.com", label: "" }); - wrapper - .find('moz-button[data-l10n-id="newtab-topsites-add-button"]') - .simulate("click"); + wrapper.find(".done").simulate("submit"); assert.calledWith(wrapper.instance().props.dispatch, { data: { site: { url: "http://valid.com" }, index: -1 }, meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" }, @@ -1280,17 +1246,13 @@ describe("<TopSiteForm>", () => { ); }); it("should call onClose if Cancel button is clicked", () => { - wrapper - .find('moz-button[data-l10n-id="newtab-topsites-cancel-button"]') - .simulate("click"); + wrapper.find(".cancel").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('moz-button[data-l10n-id="newtab-topsites-save-button"]') - .simulate("click"); + wrapper.find(".done").simulate("submit"); assert.equal(wrapper.state().validationError, true); assert.notCalled(wrapper.instance().props.onClose); assert.notCalled(wrapper.instance().props.dispatch); @@ -1298,17 +1260,13 @@ 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('moz-button[data-l10n-id="newtab-topsites-save-button"]') - .simulate("click"); + wrapper.find(".done").simulate("submit"); 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('moz-button[data-l10n-id="newtab-topsites-save-button"]') - .simulate("click"); + wrapper.find(".done").simulate("submit"); assert.calledOnce(wrapper.instance().props.onClose); assert.calledTwice(wrapper.instance().props.dispatch); assert.calledWith(wrapper.instance().props.dispatch, { @@ -1338,9 +1296,7 @@ describe("<TopSiteForm>", () => { it("should set customScreenshotURL to null if it was removed", () => { wrapper.setState({ customScreenshotUrl: "" }); - wrapper - .find('moz-button[data-l10n-id="newtab-topsites-save-button"]') - .simulate("click"); + wrapper.find(".done").simulate("submit"); assert.calledWith(wrapper.instance().props.dispatch, { data: { @@ -1357,9 +1313,7 @@ describe("<TopSiteForm>", () => { }); it("should call onClose and dispatch with right args if URL is valid (negative index)", () => { wrapper.setProps({ index: -1 }); - wrapper - .find('moz-button[data-l10n-id="newtab-topsites-save-button"]') - .simulate("click"); + wrapper.find(".done").simulate("submit"); assert.calledOnce(wrapper.instance().props.onClose); assert.calledTwice(wrapper.instance().props.dispatch); assert.calledWith(wrapper.instance().props.dispatch, { @@ -1377,9 +1331,7 @@ describe("<TopSiteForm>", () => { }); it("should not pass empty string label in dispatch data", () => { wrapper.setState({ label: "" }); - wrapper - .find('moz-button[data-l10n-id="newtab-topsites-save-button"]') - .simulate("click"); + wrapper.find(".done").simulate("submit"); assert.calledWith(wrapper.instance().props.dispatch, { data: { site: { url: "https://foo.bar", customScreenshotURL: "http://foo" }, @@ -1394,32 +1346,14 @@ describe("<TopSiteForm>", () => { customScreenshotUrl: "foo", screenshotPreview: "custom", }); - 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 - ); + assert.equal(0, wrapper.find(".preview").length); + assert.equal(1, wrapper.find(".done").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( - '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 - ); + assert.equal(0, wrapper.find(".preview").length); + assert.equal(1, wrapper.find(".done").length); }); });