diff --git a/blocks/subscriptions-block/_index.scss b/blocks/subscriptions-block/_index.scss index 5827ec0bcf..88f015e8a4 100644 --- a/blocks/subscriptions-block/_index.scss +++ b/blocks/subscriptions-block/_index.scss @@ -204,3 +204,45 @@ @include scss.block-components("offer"); @include scss.block-properties("offer"); } + +.b-paywall { + &__overlay { + &-content { + @include scss.block-components("paywall-overlay-content"); + @include scss.block-properties("paywall-overlay-content"); + } + + @include scss.block-components("paywall-overlay"); + @include scss.block-properties("paywall-overlay"); + } + + &__subscription-dialog { + &-link-prompt { + @include scss.block-components("paywall-subscription-dialog-reason-prompt"); + @include scss.block-properties("paywall-subscription-dialog-reason-prompt"); + + &-pre-link { + @include scss.block-components("paywall-subscription-dialog-link-prompt-pre-link"); + @include scss.block-properties("paywall-subscription-dialog-link-prompt-pre-link"); + } + + &-link { + @include scss.block-components("paywall-subscription-dialog-link-prompt-link"); + @include scss.block-properties("paywall-subscription-dialog-link-prompt-link"); + } + } + + &-offer-info{ + @include scss.block-components("paywall-subscription-dialog-offer-info"); + @include scss.block-properties("paywall-subscription-dialog-offer-info"); + } + + &-subheadline{ + @include scss.block-components("paywall-subscription-dialog-subheadline"); + @include scss.block-properties("paywall-subscription-dialog-subheadline"); + } + + @include scss.block-components("paywall-subscription-dialog"); + @include scss.block-properties("paywall-subscription-dialog"); + } +} diff --git a/blocks/subscriptions-block/components/PaywallOffer/index.jsx b/blocks/subscriptions-block/components/PaywallOffer/index.jsx new file mode 100644 index 0000000000..582a18edb7 --- /dev/null +++ b/blocks/subscriptions-block/components/PaywallOffer/index.jsx @@ -0,0 +1,109 @@ +import React, { useEffect, useRef, useState } from "react"; +import { isServerSide } from "@wpmedia/arc-themes-components"; +import isUrl from "is-url"; +import useOffer from "../useOffer"; +import SubscriptionOverlay from "../SubscriptionOverlay"; +import SubscriptionDialog from "../SubscriptionDialog"; + +const isPaywallCampaignURL = (payWallCode) => payWallCode && isUrl(payWallCode); + +const PaywallOffer = ({ + isLoggedIn, + actionText, + actionUrl, + campaignCode = null, + displayMode, + linkPrompt, + linkText, + linkUrl, + reasonPrompt, + usePortal = true, + className +}) => { + // the paywall code (otherwise known as a campaign code) + const [payWallCode, setPayWallCode] = useState(); + + const { offer, fetchOffer } = useOffer({ campaignCode }); + + /** + * payWallOffer is the most updated offer that is returned from + * the fetchOffer method in the useOffer hook. + * We can't use the offer object that is returned from the useOffer + * directly as that causes a recursive call to the second useEffect. + * + * When the payWallOffer is updated in the second hook it's current + * value will be applied to the selectedOffer state to update the dom. + */ + const payWallOffer = useRef(offer); + + const [selectedOffer, setSelectedOffer] = useState(payWallOffer.current); + + /** + * If campaignCode is passed in as a prop, + * use that. Otherwise use what is returned from + * usePaywall hook and at the very least, set it + * to "default" + */ + useEffect(() => { + const campaign = campaignCode || "default"; + setPayWallCode(campaign); + }, [campaignCode]); + + // This will grab the offer corresponding to the paywall code + useEffect(() => { + const fetchNewOffer = async () => { + payWallOffer.current = await fetchOffer(payWallCode); + if (payWallOffer.current) { + setSelectedOffer(payWallOffer.current); + } + }; + if ( + payWallCode && + !isUrl(payWallCode) && + (!payWallOffer.current || payWallOffer.current.pw !== payWallCode) + ) { + fetchNewOffer(); + } + return () => { + payWallOffer.current = null; + }; + }, [payWallCode, fetchOffer]); + + /** + * Return null if server side, not paywalled, doesn't have an offer to show + * or if the user is logged in. + */ + if (isServerSide() || !selectedOffer) { + return null; + } + + /** + * Need to determine the campaign code. + * First we see if we have been provided with a campaignCode prop. + * If not then we check if the payWallCode is not a url, if its + * not, then we use that. If it a url (Why would it be a URL??) then + * we just set the campaign code to "default" + */ + const campaign = campaignCode || (!isPaywallCampaignURL(payWallCode) ? payWallCode : "default"); + const actionUrlFinal = + !campaign || campaign === "default" ? actionUrl : `${actionUrl}?campaign=${campaign}`; + + return ( + + + + ); +}; + +export default PaywallOffer; \ No newline at end of file diff --git a/blocks/subscriptions-block/components/PaywallOffer/index.test.js b/blocks/subscriptions-block/components/PaywallOffer/index.test.js new file mode 100644 index 0000000000..285cdc3802 --- /dev/null +++ b/blocks/subscriptions-block/components/PaywallOffer/index.test.js @@ -0,0 +1,122 @@ +import React from "react"; +import { render } from "@testing-library/react"; +import isUrl from "is-url"; + +import { isServerSide } from "@wpmedia/arc-themes-components"; + +import PaywallOffer from "."; +import usePaywall from "../usePaywall"; +import useOffer from "../useOffer"; + +jest.mock("@wpmedia/arc-themes-components", () => ({ + __esModule: true, + isServerSide: jest.fn(() => false), +})); + +jest.mock("is-url", () => ({ + __esModule: true, + default: jest.fn(() => false), +})); + +jest.mock("../../components/useOffer"); +jest.mock("../../../identity-block"); +jest.mock("../../components/usePaywall"); + +useOffer.mockReturnValue({ + offer: { + pageTitle: "this the offer title", + pageSubTitle: "this the offer subtitle", + }, + fetchOffer: () => ({ + pageTitle: "this the offer title", + pageSubTitle: "this the offer subtitle", + }), +}); + +usePaywall.mockReturnValue({ + campaignCode: "default", + isPaywalled: true, + isRegisterwalled: false, +}); + +/** + * Below I pass usePortal to false that will in return + * pass this down to the Subscription Overlay. + * This boolean prevents ReactDOM.createPortal from being used. + * Just checking with isServerSide doesn't work. Jest and Enzyme + * still have poor support for ReactDOM.createPortal, so we need a way + * to conditionally render ReactDOM.createPortal. + */ +describe("The PaywallOffer component ", () => { + it("returns null if serverSide", () => { + isServerSide.mockReturnValue(true); + render( + + ); + expect(screen.html()).toBe(null); + isServerSide.mockReset(); + }); + + it("renders with correct markup", () => { + render( + + ); + + expect(screen.getByText("Subscribe to continue reading.")).not.toBeNull(); + expect(screen.getByText("Already a subscriber?")).not.toBeNull(); + expect(screen.getByText("Log In.").closest("a")).toHaveAttribute("href", "/account/login"); + + expect(screen.getByText("this the offer title")).not.toBeNull(); + expect(screen.getByText("this the offer subtitle")).not.toBeNull(); + expect(screen.getByText("Subscribe")).not.toBeNull(); + expect(screen.getByText("Subscribe").closest("a")).toHaveAttribute( + "href", + "/offer/?campaign=defaultish" + ); + }); + + it("renders campaignCode if its a url", () => { + isUrl.mockReturnValue(true); + render( + + ); + expect(screen.getByText("Subscribe")).not.toBeNull(); + expect(screen.getByText("Subscribe").closest("a")).toHaveAttribute( + "href", + "/offer/?campaign=./" + ); + isUrl.mockReset(); + }); + + it("renders without a query param if campaignCode is not passed", () => { + isUrl.mockReturnValue(true); + render(); + expect(screen.getByRole("button")).not.toBeNull(); + expect(screen.getByText("Subscribe")).not.toBeNull(); + expect(screen.getByRole("button").toHaveAttribute("href", "/offer/")); + isUrl.mockReset(); + }); +}); diff --git a/blocks/subscriptions-block/components/RegwallOffer/index.jsx b/blocks/subscriptions-block/components/RegwallOffer/index.jsx new file mode 100644 index 0000000000..a67ffe546a --- /dev/null +++ b/blocks/subscriptions-block/components/RegwallOffer/index.jsx @@ -0,0 +1,41 @@ +import React from "react"; + +import { isServerSide } from "@wpmedia/arc-themes-components"; +import SubscriptionOverlay from "../SubscriptionOverlay"; +import SubscriptionDialog from "../SubscriptionDialog"; + +const RegwallOffer = ({ + actionText, + actionUrl, + displayMode, + headlineText, + linkPrompt, + linkText, + linkUrl, + reasonPrompt, + subheadlineText, + usePortal = true, + className, +}) => { + if (isServerSide()) { + return null; + } + + return ( + + + + ); +}; + +export default RegwallOffer; \ No newline at end of file diff --git a/blocks/subscriptions-block/components/RegwallOffer/index.test.js b/blocks/subscriptions-block/components/RegwallOffer/index.test.js new file mode 100644 index 0000000000..5e9f2c920d --- /dev/null +++ b/blocks/subscriptions-block/components/RegwallOffer/index.test.js @@ -0,0 +1,69 @@ +import React from "react"; +import { render } from "@testing-library/react"; + +import { isServerSide } from "@wpmedia/arc-themes-components"; + +import RegwallOffer from "."; + +jest.mock("@wpmedia/arc-themes-components", () => ({ + __esModule: true, + isServerSide: jest.fn(() => false), +})); + +jest.mock("is-url", () => ({ + __esModule: true, + default: jest.fn(() => false), +})); + +/** + * Below I pass usePortal to false that will in return + * pass this down to the Subscription Overlay. + * This boolean prevents ReactDOM.createPortal from being used. + * Just checking with isServerSide doesn't work. Jest and Enzyme + * still have poor support for ReactDOM.createPortal, so we need a way + * to conditionally render ReactDOM.createPortal. + */ +describe("The RegwallOffer component ", () => { + it("returns null if serverSide", () => { + isServerSide.mockReturnValue(true); + render( + + ); + expect(screen.html()).toBe(null); + isServerSide.mockReset(); + }); + + it("renders with correct markup", () => { + render( + + ); + + expect(screen.getByText("Subscribe to continue reading.")).not.toBeNull(); + expect(screen.getByText("Already a subscriber?")).not.toBeNull(); + expect(screen.getByText("Log In.").closest("a")).toHaveAttribute("href", "/account/login"); + + expect(screen.getByText("Headline")).not.toBeNull(); + expect(screen.getByText("Subheadline")).not.toBeNull(); + expect(screen.getByText("Subscribe").closest("a")).toHaveAttribute("href", "/account/signup"); + }); +}); diff --git a/blocks/subscriptions-block/components/SubscriptionDialog/index.jsx b/blocks/subscriptions-block/components/SubscriptionDialog/index.jsx new file mode 100644 index 0000000000..2cde99708c --- /dev/null +++ b/blocks/subscriptions-block/components/SubscriptionDialog/index.jsx @@ -0,0 +1,72 @@ +import React from "react"; + +import PropTypes from "@arc-fusion/prop-types"; +import { Heading, Link, Button, Stack } from "@wpmedia/arc-themes-components"; + +const SubscriptionDialog = ({ + isLoggedIn, + actionText, + actionUrl, + reasonPrompt, + headline, + linkText, + linkPrompt, + linkUrl, + subHeadline, + className, +}) => { + + return ( + + + {reasonPrompt ? ( +
+ ) : null} + {!isLoggedIn ? ( +
+ {linkPrompt ? ( + + ) : null} + + {linkText} + +
+ ) : null} + + + {headline ? : null} + {subHeadline ? ( +
+ ) : null} + + {actionUrl && actionText ? ( + + ) : null} + + ); +}; + +SubscriptionDialog.propTypes = { + isLoggedIn: PropTypes.boolean, + actionText: PropTypes.string, + actionUrl: PropTypes.string, + reasonPrompt: PropTypes.oneOfType([PropTypes.string, PropTypes.element, PropTypes.node]), + headline: PropTypes.oneOfType([PropTypes.string, PropTypes.element, PropTypes.node]), + linkText: PropTypes.string.isRequired, + linkPrompt: PropTypes.string, + linkUrl: PropTypes.string.isRequired, + subHeadline: PropTypes.oneOfType([PropTypes.string, PropTypes.element, PropTypes.node]), +}; + +export default SubscriptionDialog; diff --git a/blocks/subscriptions-block/components/SubscriptionDialog/index.story.jsx b/blocks/subscriptions-block/components/SubscriptionDialog/index.story.jsx new file mode 100644 index 0000000000..1f14769d2b --- /dev/null +++ b/blocks/subscriptions-block/components/SubscriptionDialog/index.story.jsx @@ -0,0 +1,51 @@ +import React from "react"; + +import SubscriptionDialog from "."; + +export default { + title: "Blocks/Subscriptions/Components/Subscription Dialog", + parameters: { + chromatic: { viewports: [320, 1023, 1200] }, + }, +}; + +export const allFields = () => ( + +); + +export const minimalFields = () => ; + +export const withButton = () => ( + +); + +export const withReason = () => ( + +); + +export const withLinkPrompt = () => ( + +); + +export const withHeadline = () => ( + +); + +export const withSubheadline = () => ( + +); \ No newline at end of file diff --git a/blocks/subscriptions-block/components/SubscriptionDialog/index.test.js b/blocks/subscriptions-block/components/SubscriptionDialog/index.test.js new file mode 100644 index 0000000000..4f9d2cb4d6 --- /dev/null +++ b/blocks/subscriptions-block/components/SubscriptionDialog/index.test.js @@ -0,0 +1,66 @@ +import React from "react"; +import { render } from "@testing-library/react"; + +import SubscriptionDialog from "."; + +describe("SubscriptionDialog", () => { + it("renders with minimal required properties", () => { + render(); + + expect(screen.getByText('Log In.').closest('a')).toHaveAttribute('href', '/'); + }); + + it("renders the button as a link", () => { + render( + + ); + + expect(screen.getByText('Press Me!').closest('a')).toHaveAttribute('href', '/'); + }); + + it("does not render if required action part is missing", () => { + render( + + ); + + expect(screen.getByText('Log in').closest('a')).toHaveAttribute('href', '/'); + }); + + it("does not render if other required action part is missing", () => { + render(); + + expect(screen.getByText('Log in').closest('a')).toHaveAttribute('href', '/'); + }); + + it("renders the headline", () => { + render( + + ); + + expect(screen.getByText("Headline")).not.toBeNull(); + }); + + it("renders the subheadlines", () => { + render( + + ); + + expect(screen.getByText("Headline 2")).not.toBeNull(); + }); + + it("renders the reason", () => { + render( + + ); + + expect(screen.getByText("You need to do this.")).not.toBeNull(); + }); + + it("renders the link prompt text", () => { + render( + + ); + + expect(screen.getByText("You should log in.")).not.toBeNull(); + }); +}); \ No newline at end of file diff --git a/blocks/subscriptions-block/components/SubscriptionOverlay/index.jsx b/blocks/subscriptions-block/components/SubscriptionOverlay/index.jsx new file mode 100644 index 0000000000..f6a76de787 --- /dev/null +++ b/blocks/subscriptions-block/components/SubscriptionOverlay/index.jsx @@ -0,0 +1,103 @@ +import React, { useEffect, useState, useRef } from "react"; +import * as ReactDOM from "react-dom"; + +import { isServerSide } from "@wpmedia/arc-themes-components"; + +export const Portal = ({ children }) => { + if (isServerSide()) return null; + + return ReactDOM.createPortal(children, document.body); +}; + +/** + * The usePortal param should always be true, with the exception + * of unit testing where portal needs to be false to prevent + * jest and enzyme errors because + * Portals are not currently supported by the server renderer. + * + * Just checking for isServerSide is not enough. + * + * `displayMode` param is unused at this time. + * We implemented 'bottomHalf' below. + * It can be ['bottomHalf', 'full', 'modal']. + * + */ +const SubscriptionOverlay = ({ children, usePortal = true, className }) => { + const overlayRef = useRef(); + const contentRef = useRef(); + const [scrollDelta, setScrollDelta] = useState(0); + const [overlayTouchLast, setOverlayTouchLast] = useState(0); + const [contentTouchLast, setContentTouchLast] = useState(0); + + useEffect(() => { + const disableScroll = (event) => event.preventDefault(); + const scrollElement = overlayRef.current.ownerDocument.scrollingElement; + const { overflow } = scrollElement.style; + + scrollElement.addEventListener("scroll", disableScroll); + scrollElement.style.overflow = "hidden"; + scrollElement.style.maxHeight = "100vh"; + + return () => { + scrollElement.removeEventListener("scroll", disableScroll); + scrollElement.style.overflow = overflow; + scrollElement.style.maxHeight = "auto"; + }; + }, [overlayRef]); + + useEffect(() => { + const contentTopFactor = overlayRef.current.scrollTop / overlayRef.current.clientHeight; + + if (contentRef.current.scrollTop >= 0 && contentTopFactor < 0.25) { + overlayRef.current.scrollTop += scrollDelta; + } else { + contentRef.current.scrollTop += scrollDelta; + } + }, [contentRef, overlayRef, scrollDelta]); + + const renderInternalOverlay = () => ( +
{ + setScrollDelta(event.deltaY); + }} + onTouchMove={(event) => { + setScrollDelta(overlayTouchLast - event.changedTouches[0].clientY); + setOverlayTouchLast(event.changedTouches[0].clientY); + }} + onTouchStart={(event) => { + setOverlayTouchLast(event.changedTouches[0].clientY); + }} + role="alert" + > +
{ + setScrollDelta(event.deltaY); + }} + onTouchMove={(event) => { + setScrollDelta(contentTouchLast - event.changedTouches[0].clientY); + setContentTouchLast(event.changedTouches[0].clientY); + }} + onTouchStart={(event) => { + setContentTouchLast(event.changedTouches[0].clientY); + }} + ref={contentRef} + > + {children} +
+
+ ); + + const renderOverlay = () => { + if (!usePortal || isServerSide()) { + return <>{renderInternalOverlay()}; + } + return {renderInternalOverlay()}; + }; + + return renderOverlay(); +}; + +export default SubscriptionOverlay; diff --git a/blocks/subscriptions-block/components/SubscriptionOverlay/index.story.jsx b/blocks/subscriptions-block/components/SubscriptionOverlay/index.story.jsx new file mode 100644 index 0000000000..711ccb3e9b --- /dev/null +++ b/blocks/subscriptions-block/components/SubscriptionOverlay/index.story.jsx @@ -0,0 +1,140 @@ +import React from "react"; + +import SubscriptionOverlay from "."; + +export default { + title: "Blocks/Subscriptions/Components/Subscription Overlay", + parameters: { + chromatic: { viewports: [320, 1200] }, + }, +}; + +const LongArticle = () => { + const textStyle = { + margin: "0.5em auto", + padding: "0.5em", + maxWidth: "60vw", + }; + return ( +
+ +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce elementum tincidunt placerat. + Integer bibendum, libero sed rhoncus dapibus, ipsum nulla condimentum est, in gravida sapien + risus nec libero. Nunc consectetur purus urna, id vehicula ante hendrerit at. Vivamus ac leo + id lorem commodo scelerisque. Vestibulum rhoncus sed lacus eget tempus. Quisque odio elit, + euismod a ipsum eu, posuere aliquet justo. Ut finibus est sapien, vitae mattis neque + tincidunt et. Etiam tincidunt congue sem. Curabitur tempus, nisl eget ornare gravida, magna + tortor ultricies elit, eu commodo dolor dui vel quam. Suspendisse potenti. Vestibulum + dignissim elit tortor, vel scelerisque mauris consectetur eget. +

+

+ Vestibulum blandit vel neque sollicitudin venenatis. Maecenas erat velit, eleifend at nisi + sed, vulputate fermentum nibh. Vestibulum ut elementum ex. Vivamus convallis dolor eget + dictum facilisis. Duis maximus tortor vitae nisi viverra luctus. Vivamus ultrices laoreet + lectus, non dictum massa efficitur nec. Integer vitae nunc elementum, imperdiet metus ut, + mattis lacus. Cras finibus lorem sed risus accumsan, et tempus nulla cursus. +

+

+ In in massa vitae mauris sagittis cursus. Donec fringilla elit a lacus placerat varius. + Nulla aliquet pulvinar eros eu egestas. Ut sed odio sed risus scelerisque efficitur. Nullam + at pretium est. Integer tristique tristique magna ut tempus. Fusce sagittis tincidunt + tristique. Praesent ac interdum diam. Etiam rutrum, dui eu iaculis volutpat, nisl nunc + vehicula lacus, vitae rhoncus metus leo sit amet nibh. Praesent aliquet erat id fermentum + ultrices. Integer ac arcu sit amet mauris aliquam aliquet. Integer et nunc vel lectus congue + luctus non in tellus. Mauris quis neque gravida, mollis quam id, auctor odio. Donec + placerat, sapien eget rhoncus varius, nulla eros aliquet neque, vel maximus nibh libero id + erat. +

+

+ Integer et dolor ut nulla mattis lobortis vitae sed enim. In quis dolor nec ex gravida + sollicitudin. Fusce cursus eleifend fringilla. Donec magna risus, laoreet pulvinar justo ut, + dignissim vulputate velit. Nulla vehicula, tellus eu condimentum hendrerit, sem leo + facilisis purus, pellentesque viverra turpis mi a odio. Maecenas blandit diam tincidunt, + volutpat magna sed, viverra justo. Quisque non mollis sem, eget fringilla felis. Aliquam + maximus urna vitae velit sollicitudin, vel fringilla enim scelerisque. Vestibulum varius + lacinia dui. Praesent fringilla metus id enim fringilla, nec finibus dolor condimentum. + Donec non lorem purus. Donec sit amet dolor libero. Aliquam egestas facilisis dapibus. +

+

+ Maecenas non eros eget nulla efficitur pulvinar. Duis varius bibendum tellus vitae feugiat. + In vel ipsum non lorem tincidunt aliquet non vitae purus. Vivamus nec dolor id mauris + efficitur accumsan. Vestibulum ullamcorper convallis odio. Fusce rhoncus nibh sed metus + sodales, vel efficitur metus fringilla. Pellentesque ut erat ex. +

+

+ Phasellus ac mattis enim. Sed non massa ut nulla aliquet pellentesque et vel est. Proin + consequat massa a ipsum placerat luctus. Nullam ultrices, tortor sit amet mattis vestibulum, + nulla justo pretium purus, eu lacinia lacus libero eu nibh. In tellus ex, molestie ut enim + a, pharetra convallis arcu. Duis sit amet ultricies sem, eget rhoncus erat. Nullam volutpat, + risus cursus tincidunt dapibus, libero ligula tempus tortor, eget fringilla diam eros et mi. + Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis + egestas. Donec sagittis, lorem ut porta mollis, magna quam luctus mi, id condimentum odio + ligula sed odio. Duis sagittis consequat metus, nec convallis nisl consequat a. Duis at + turpis cursus, gravida ipsum in, fermentum sem. Donec venenatis, lacus nec molestie + pulvinar, magna sem hendrerit ex, ac efficitur augue magna nec lorem. Vivamus et vehicula + mi. Vestibulum blandit lorem et massa semper dignissim. Aliquam vehicula et urna at finibus. + Nam non ante tempus, sodales odio in, pretium justo. +

+

+ Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. + Praesent interdum ultrices odio sed vestibulum. Quisque iaculis turpis sed tincidunt + pellentesque. Vestibulum molestie dui purus, quis maximus felis scelerisque ut. Donec sit + amet diam faucibus dui blandit viverra. Pellentesque ultricies lorem orci, ut tempor eros + finibus a. Fusce ac venenatis nisi. Etiam quis elit lacinia, tincidunt ligula sed, aliquet + libero. Donec eu elit ut ipsum maximus dignissim et vitae libero. Suspendisse potenti. Nulla + ullamcorper molestie nulla, sed accumsan nibh vulputate in. Phasellus pharetra accumsan + lacus vitae congue. +

+

+ Pellentesque non pellentesque velit. Nullam id enim erat. In eu condimentum mi. Proin sit + amet blandit tortor. Morbi vehicula vehicula magna, in pretium arcu hendrerit a. Vivamus a + sem sem. Nam vestibulum lectus id augue facilisis tincidunt. Nullam finibus semper nisi. + Duis vehicula tellus vitae sem facilisis ultricies. Integer et nunc at tortor ultrices + congue at vitae risus. Aenean ut nunc id quam pulvinar vehicula eu et libero. Ut nec erat et + ex vulputate vestibulum. Nam mattis pretium felis, sit amet pellentesque justo ultricies et. + Donec consectetur eleifend fermentum. +

+

+ Proin in ex quam. Quisque eget tellus tellus. Donec ac tortor feugiat enim condimentum + euismod. Mauris ac maximus leo. Nullam at tempor leo. Praesent vitae diam in diam iaculis + pharetra. Suspendisse a nulla quis quam dictum pharetra viverra ac nulla. Vestibulum + sollicitudin nunc vel odio blandit scelerisque. Nam eget sem et diam molestie tempor eget ut + lorem. Pellentesque pretium rutrum vulputate. Cras fermentum sapien eu mollis tristique. + Quisque pulvinar massa est, sodales sodales felis sodales at. Fusce aliquet, massa ac + posuere tincidunt, erat lacus ullamcorper nunc, eget scelerisque augue nibh eu dui. Donec + sodales neque bibendum arcu lacinia, sit amet sollicitudin neque tincidunt. +

+

+ Suspendisse vel egestas orci, vitae dictum velit. Donec imperdiet ipsum mauris, eget mollis + lectus gravida congue. Vestibulum porttitor id dolor id sagittis. Sed diam ligula, dictum + ullamcorper tincidunt quis, vehicula eget ex. Aliquam hendrerit dapibus pharetra. Duis ut + metus convallis, placerat magna id, lobortis purus. Vestibulum ut massa id dolor placerat + consectetur. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac + turpis egestas. Nam at interdum arcu, id feugiat dolor. Etiam aliquet vestibulum dolor vel + fermentum. +

+
+ ); +}; + +export const simpleContent = () => ( + +
This is some overlay content
+
+); + +export const multiPageContent = () => ( + + + +); + +export const scrollDisabledBackground = () => ( + <> + + + + + +); \ No newline at end of file diff --git a/blocks/subscriptions-block/components/SubscriptionOverlay/index.test.jsx b/blocks/subscriptions-block/components/SubscriptionOverlay/index.test.jsx new file mode 100644 index 0000000000..aa3394f999 --- /dev/null +++ b/blocks/subscriptions-block/components/SubscriptionOverlay/index.test.jsx @@ -0,0 +1,117 @@ +import React, { useState } from "react"; +import { render } from "@testing-library/react"; + +import SubscriptionOverlay from "."; + +Object.defineProperty(global.document, "ownerDocument", { + value: global.document, +}); +Object.defineProperty(global.document, "scrollingElement", { + value: global.document, +}); +Object.defineProperty(global.document, "style", { + value: { overflow: "auto" }, +}); + +describe("SubscriptionOverlay", () => { + it("renders with minimal required properties", () => { + render(); + + expect(screen.find(".xpmedia-subscription-overlay").at(0)).toExist(); + }); + + it("renders required a11y properties", () => { + render(); + + expect(screen.getByRole("alert").length).toEqual(1); + }); + + it("renders a child in the appropriate container", () => { + render( + +
+ + ); + + expect(screen.find(".xpmedia-subscription-overlay-content > .findThis").at(0)).toExist(); + }); + + it("handles scrolling", () => { + // Scrolling is difficult to test functionality in Enzyme jsDoc so this just test the events + render( + +
Some Text
+
+ ); + const contentContainer = screen.find(".xpmedia-subscription-overlay-content").at(0); + + const eventUp = { + deltaY: 1, + }; + contentContainer.simulate("wheel", eventUp); + + const eventDown = { + deltaY: -1, + }; + contentContainer.simulate("wheel", eventDown); + + expect(contentContainer).toExist(); + }); + + it("handles touch events", () => { + // Scrolling is difficult to test functionality in Enzyme jsDoc so this just test the events + render( + +
Some Text
+
+ ); + const contentContainer = screen.find(".xpmedia-subscription-overlay-content").at(0); + + const eventStart = { + changedTouches: [{ clientY: 9 }], + }; + contentContainer.simulate("touchstart", eventStart); + + const eventUp = { + changedTouches: [{ clientY: 10 }], + }; + contentContainer.simulate("touchmove", eventUp); + + const eventDown = { + changedTouches: [{ clientY: 9 }], + }; + contentContainer.simulate("touchmove", eventDown); + + expect(contentContainer).toExist(); + }); + + it("cleans itself up", () => { + const ShowHide = () => { + const [visible, setVisible] = useState(false); + const Button = () => ( +
+ +
+ ); + return ( + <> +