From 8df822d317c67821dca4c47a27fc94f28e7c44d7 Mon Sep 17 00:00:00 2001 From: Rish Date: Fri, 12 Jun 2020 13:51:00 +0530 Subject: [PATCH] Refactered App structure and naming no issue - Moved `ParentContainer.js` to redundant empty top level `App.js` to remove extra nesting and improved naming - Renamed `ParentContext` to `AppContext` and exported as default - Fixed imports in all the components and test-utils with new structure - Passed `siteUrl` to api setup from main App to allow easier configuration - Updated App tests --- ghost/portal/src/App.js | 286 +++++++++++++++++- ghost/portal/src/App.test.js | 34 ++- .../ParentContext.js => AppContext.js} | 6 +- .../portal/src/components/ParentContainer.js | 275 ----------------- ghost/portal/src/components/PopupModal.js | 5 +- ghost/portal/src/components/TriggerButton.js | 4 +- .../src/components/pages/AccountHomePage.js | 8 +- .../src/components/pages/AccountPlanPage.js | 4 +- .../components/pages/AccountProfilePage.js | 4 +- .../src/components/pages/MagicLinkPage.js | 4 +- .../portal/src/components/pages/SigninPage.js | 4 +- .../portal/src/components/pages/SignupPage.js | 4 +- ghost/portal/src/utils/api.js | 5 +- ghost/portal/src/utils/test-utils.js | 6 +- 14 files changed, 332 insertions(+), 317 deletions(-) rename ghost/portal/src/{components/ParentContext.js => AppContext.js} (70%) delete mode 100644 ghost/portal/src/components/ParentContainer.js diff --git a/ghost/portal/src/App.js b/ghost/portal/src/App.js index 6e5a1a6a3e..7979ecf87b 100644 --- a/ghost/portal/src/App.js +++ b/ghost/portal/src/App.js @@ -1,13 +1,279 @@ -import React from 'react'; +import TriggerButton from './components/TriggerButton'; +import PopupModal from './components/PopupModal'; +import setupGhostApi from './utils/api'; +import AppContext from './AppContext'; import './App.css'; -import ParentContainer from './components/ParentContainer'; -function App(props) { - return ( -
- -
- ); +const React = require('react'); + +export default class App extends React.Component { + constructor(props) { + super(props); + // Setup custom trigger button handling + this.setupCustomTriggerButton(); + + // testState is used by App.test to pass custom default state for testing + this.state = props.testState || { + site: null, + member: null, + page: 'loading', + showPopup: false, + action: 'init:running', + initStatus: 'running', + lastPage: null + }; + } + + componentDidMount() { + this.fetchData(); + } + + componentDidUpdate(prevProps, prevState) { + if (prevState.showPopup !== this.state.showPopup) { + this.handleCustomTriggerClassUpdate(); + } + } + + componentWillUnmount() { + this.customTriggerButtons.forEach((customTriggerButton) => { + customTriggerButton.addEventListener('click', this.clickHandler); + }); + } + + handleCustomTriggerClassUpdate() { + const popupOpenClass = 'gh-members-popup-open'; + const popupCloseClass = 'gh-members-popup-close'; + this.customTriggerButtons.forEach((customButton) => { + const elAddClass = this.state.showPopup ? popupOpenClass : popupCloseClass; + const elRemoveClass = this.state.showPopup ? popupCloseClass : popupOpenClass; + customButton.classList.add(elAddClass); + customButton.classList.remove(elRemoveClass); + }); + } + + getStripeUrlParam() { + const url = new URL(window.location); + return url.searchParams.get('stripe'); + } + + getDefaultPage({member = this.state.member, stripeParam} = {}) { + // Loads default page and popup state for local UI testing + if (process.env.NODE_ENV === 'development') { + return { + page: 'signup', + showPopup: true + }; + } + if (!member && stripeParam === 'success') { + return {page: 'magiclink', showPopup: true}; + } + if (member) { + return { + page: 'accountHome' + }; + } + return { + page: 'signup' + }; + } + + // Fetch site and member session data with Ghost Apis + async fetchData() { + const {siteUrl} = this.props; + try { + this.GhostApi = setupGhostApi({siteUrl}); + const {site, member} = await this.GhostApi.init(); + site.isStripeConfigured = (site.isStripeConfigured === undefined) || site.isStripeConfigured; + const stripeParam = this.getStripeUrlParam(); + const {page, showPopup = false} = this.getDefaultPage({member, stripeParam}); + this.setState({ + site, + member, + page, + showPopup, + action: 'init:success', + initStatus: 'success' + }); + } catch (e) { + /* eslint-disable no-console */ + console.error(`[Members.js] Failed to initialize`); + /* eslint-enable no-console */ + this.setState({ + action: 'init:failed', + initStatus: 'failed' + }); + } + } + + setupCustomTriggerButton() { + // Handler for custom buttons + this.clickHandler = (event) => { + const target = event.currentTarget; + const page = target && target.dataset.membersTriggerButton; + + event.preventDefault(); + this.onAction('openPopup', {page}); + }; + const customTriggerSelector = '[data-members-trigger-button]'; + const popupCloseClass = 'gh-members-popup-close'; + this.customTriggerButtons = document.querySelectorAll(customTriggerSelector) || []; + this.customTriggerButtons.forEach((customTriggerButton) => { + customTriggerButton.classList.add(popupCloseClass); + customTriggerButton.addEventListener('click', this.clickHandler); + }); + } + + getActionData(action) { + const [type, status, reason] = action.split(':'); + return {type, status, reason}; + } + + getBrandColor() { + return (this.state.site && this.state.site.brand && this.state.site.brand.primaryColor) || '#3db0ef'; + } + + async onAction(action, data) { + this.setState({ + action: `${action}:running` + }); + try { + if (action === 'switchPage') { + this.setState({ + page: data.page, + lastPage: data.lastPage || null + }); + } else if (action === 'togglePopup') { + this.setState({ + showPopup: !this.state.showPopup + }); + } else if (action === 'openPopup') { + this.setState({ + showPopup: true, + page: data.page + }); + } else if (action === 'back') { + if (this.state.lastPage) { + this.setState({ + page: this.state.lastPage + }); + } + } else if (action === 'closePopup') { + const {page: defaultPage} = this.getDefaultPage(); + this.setState({ + showPopup: false, + page: this.state.page === 'magiclink' ? defaultPage : this.state.page + }); + } else if (action === 'signout') { + await this.GhostApi.member.signout(); + this.setState({ + action: 'signout:success' + }); + } else if (action === 'signin') { + await this.GhostApi.member.sendMagicLink(data); + this.setState({ + action: 'signin:success', + page: 'magiclink' + }); + } else if (action === 'signup') { + const {plan, email, name} = data; + if (plan.toLowerCase() === 'free') { + await this.GhostApi.member.sendMagicLink(data); + } else { + await this.GhostApi.member.checkoutPlan({plan, email, name}); + } + this.setState({ + action: 'signup:success', + page: 'magiclink' + }); + } else if (action === 'updateEmail') { + await this.GhostApi.member.sendMagicLink(data); + this.setState({ + action: 'updateEmail:success' + }); + } else if (action === 'checkoutPlan') { + const {plan} = data; + await this.GhostApi.member.checkoutPlan({ + plan + }); + } else if (action === 'updateSubscription') { + const {plan, subscriptionId, cancelAtPeriodEnd} = data; + await this.GhostApi.member.updateSubscription({ + planName: plan, subscriptionId, cancelAtPeriodEnd + }); + const member = await this.GhostApi.member.sessionData(); + this.setState({ + action: 'updateSubscription:success', + page: 'accountHome', + member: member + }); + } else if (action === 'editBilling') { + await this.GhostApi.member.editBilling(); + } else if (action === 'updateMember') { + const {name, subscribed} = data; + const member = await this.GhostApi.member.update({name, subscribed}); + if (!member) { + this.setState({ + action: 'updateMember:failed' + }); + } else { + this.setState({ + action: 'updateMember:success', + member: member + }); + } + } + setTimeout(() => { + this.setState({ + action: '' + }); + }, 5000); + } catch (e) { + this.setState({ + action: `${action}:failed` + }); + } + } + + renderPopup() { + if (this.state.showPopup) { + return ( + + ); + } + return null; + } + + renderTriggerButton() { + if (!this.customTriggerButtons || this.customTriggerButtons.length === 0) { + return ( + + ); + } + + return null; + } + + render() { + if (this.state.initStatus === 'success') { + const {site, member, action, page, lastPage} = this.state; + + return ( + this.onAction(_action, data) + }}> + {this.renderPopup()} + {this.renderTriggerButton()} + + ); + } + return null; + } } - -export default App; diff --git a/ghost/portal/src/App.test.js b/ghost/portal/src/App.test.js index 0c558f1f56..b12caef4e5 100644 --- a/ghost/portal/src/App.test.js +++ b/ghost/portal/src/App.test.js @@ -1,13 +1,35 @@ import React from 'react'; import {render} from '@testing-library/react'; +import {site, member} from './utils/fixtures'; import App from './App'; -test('renders App', () => { - const {container} = render( - +const setup = (overrides) => { + const testState = { + site, + member: member.free, + action: 'init:success', + brandColor: site.brand.primaryColor, + page: 'signup', + initStatus: 'success', + showPopup: true + }; + const {...utils} = render( + ); + const triggerButtonFrame = utils.getByTitle(/membersjs-trigger/i); + const popupFrame = utils.getByTitle(/membersjs-popup/i); + return { + popupFrame, + triggerButtonFrame, + ...utils + }; +}; - // dashboard component should be rendered on root route - const element = container.querySelector('.App'); - expect(element).toBeInTheDocument(); +describe('App', () => { + test('renders popup and trigger frames', () => { + const {popupFrame, triggerButtonFrame} = setup(); + + expect(popupFrame).toBeInTheDocument(); + expect(triggerButtonFrame).toBeInTheDocument(); + }); }); \ No newline at end of file diff --git a/ghost/portal/src/components/ParentContext.js b/ghost/portal/src/AppContext.js similarity index 70% rename from ghost/portal/src/components/ParentContext.js rename to ghost/portal/src/AppContext.js index bb26e584cd..579d964db0 100644 --- a/ghost/portal/src/components/ParentContext.js +++ b/ghost/portal/src/AppContext.js @@ -1,10 +1,12 @@ // Ref: https://reactjs.org/docs/context.html const React = require('react'); -export const ParentContext = React.createContext({ +const AppContext = React.createContext({ site: {}, member: {}, action: '', brandColor: '', onAction: () => {} -}); \ No newline at end of file +}); + +export default AppContext; \ No newline at end of file diff --git a/ghost/portal/src/components/ParentContainer.js b/ghost/portal/src/components/ParentContainer.js deleted file mode 100644 index 82d119eb7d..0000000000 --- a/ghost/portal/src/components/ParentContainer.js +++ /dev/null @@ -1,275 +0,0 @@ -import TriggerButton from './TriggerButton'; -import PopupModal from './PopupModal'; -import setupGhostApi from '../utils/api'; -import {ParentContext} from './ParentContext'; - -const React = require('react'); - -export default class ParentContainer extends React.Component { - constructor(props) { - super(props); - - // Setup custom trigger button handling - this.setupCustomTriggerButton(); - - this.state = { - page: 'loading', - showPopup: false, - action: 'init:running', - initStatus: 'running', - lastPage: null - }; - } - - componentDidMount() { - this.fetchData(); - } - - componentDidUpdate(prevProps, prevState) { - if (prevState.showPopup !== this.state.showPopup) { - this.handleCustomTriggerClassUpdate(); - } - } - - componentWillUnmount() { - this.customTriggerButtons.forEach((customTriggerButton) => { - customTriggerButton.addEventListener('click', this.clickHandler); - }); - } - - handleCustomTriggerClassUpdate() { - const popupOpenClass = 'gh-members-popup-open'; - const popupCloseClass = 'gh-members-popup-close'; - this.customTriggerButtons.forEach((customButton) => { - const elAddClass = this.state.showPopup ? popupOpenClass : popupCloseClass; - const elRemoveClass = this.state.showPopup ? popupCloseClass : popupOpenClass; - customButton.classList.add(elAddClass); - customButton.classList.remove(elRemoveClass); - }); - } - - getStripeUrlParam() { - const url = new URL(window.location); - return url.searchParams.get('stripe'); - } - - getDefaultPage({member = this.state.member, stripeParam} = {}) { - // Loads default page and popup state for local UI testing - if (process.env.NODE_ENV === 'development') { - return { - page: 'signup', - showPopup: true - }; - } - if (!member && stripeParam === 'success') { - return {page: 'magiclink', showPopup: true}; - } - if (member) { - return { - page: 'accountHome' - }; - } - return { - page: 'signup' - }; - } - - // Fetch site and member session data with Ghost Apis - async fetchData() { - try { - this.GhostApi = setupGhostApi(); - const {site, member} = await this.GhostApi.init(); - site.isStripeConfigured = (site.isStripeConfigured === undefined) || site.isStripeConfigured; - const stripeParam = this.getStripeUrlParam(); - const {page, showPopup = false} = this.getDefaultPage({member, stripeParam}); - this.setState({ - site, - member, - page, - showPopup, - action: 'init:success', - initStatus: 'success' - }); - } catch (e) { - /* eslint-disable no-console */ - console.error(`[Members.js] Failed to initialize`); - /* eslint-enable no-console */ - this.setState({ - action: 'init:failed', - initStatus: 'failed' - }); - } - } - - setupCustomTriggerButton() { - // Handler for custom buttons - this.clickHandler = (event) => { - const target = event.currentTarget; - const page = target && target.dataset.membersTriggerButton; - - event.preventDefault(); - this.onAction('openPopup', {page}); - }; - const customTriggerSelector = '[data-members-trigger-button]'; - const popupCloseClass = 'gh-members-popup-close'; - this.customTriggerButtons = document.querySelectorAll(customTriggerSelector) || []; - this.customTriggerButtons.forEach((customTriggerButton) => { - customTriggerButton.classList.add(popupCloseClass); - customTriggerButton.addEventListener('click', this.clickHandler); - }); - } - - getActionData(action) { - const [type, status, reason] = action.split(':'); - return {type, status, reason}; - } - - getBrandColor() { - return (this.state.site && this.state.site.brand && this.state.site.brand.primaryColor) || '#3db0ef'; - } - - async onAction(action, data) { - this.setState({ - action: `${action}:running` - }); - try { - if (action === 'switchPage') { - this.setState({ - page: data.page, - lastPage: data.lastPage || null - }); - } else if (action === 'togglePopup') { - this.setState({ - showPopup: !this.state.showPopup - }); - } else if (action === 'openPopup') { - this.setState({ - showPopup: true, - page: data.page - }); - } else if (action === 'back') { - if (this.state.lastPage) { - this.setState({ - page: this.state.lastPage - }); - } - } else if (action === 'closePopup') { - const {page: defaultPage} = this.getDefaultPage(); - this.setState({ - showPopup: false, - page: this.state.page === 'magiclink' ? defaultPage : this.state.page - }); - } else if (action === 'signout') { - await this.GhostApi.member.signout(); - this.setState({ - action: 'signout:success' - }); - } else if (action === 'signin') { - await this.GhostApi.member.sendMagicLink(data); - this.setState({ - action: 'signin:success', - page: 'magiclink' - }); - } else if (action === 'signup') { - const {plan, email, name} = data; - if (plan.toLowerCase() === 'free') { - await this.GhostApi.member.sendMagicLink(data); - } else { - await this.GhostApi.member.checkoutPlan({plan, email, name}); - } - this.setState({ - action: 'signup:success', - page: 'magiclink' - }); - } else if (action === 'updateEmail') { - await this.GhostApi.member.sendMagicLink(data); - this.setState({ - action: 'updateEmail:success' - }); - } else if (action === 'checkoutPlan') { - const {plan} = data; - await this.GhostApi.member.checkoutPlan({ - plan - }); - } else if (action === 'updateSubscription') { - const {plan, subscriptionId, cancelAtPeriodEnd} = data; - await this.GhostApi.member.updateSubscription({ - planName: plan, subscriptionId, cancelAtPeriodEnd - }); - const member = await this.GhostApi.member.sessionData(); - this.setState({ - action: 'updateSubscription:success', - page: 'accountHome', - member: member - }); - } else if (action === 'editBilling') { - await this.GhostApi.member.editBilling(); - } else if (action === 'updateMember') { - const {name, subscribed} = data; - const member = await this.GhostApi.member.update({name, subscribed}); - if (!member) { - this.setState({ - action: 'updateMember:failed' - }); - } else { - this.setState({ - action: 'updateMember:success', - member: member - }); - } - } - setTimeout(() => { - this.setState({ - action: '' - }); - }, 5000); - } catch (e) { - this.setState({ - action: `${action}:failed` - }); - } - } - - renderPopup() { - if (this.state.showPopup) { - return ( - - ); - } - return null; - } - - renderTriggerButton() { - if (!this.customTriggerButtons || this.customTriggerButtons.length === 0) { - return ( - - ); - } - - return null; - } - - render() { - if (this.state.initStatus === 'success') { - const {site, member} = this.state; - - return ( - this.onAction(action, data) - }}> - {this.renderPopup()} - {this.renderTriggerButton()} - - ); - } - return null; - } -} diff --git a/ghost/portal/src/components/PopupModal.js b/ghost/portal/src/components/PopupModal.js index 6467128426..fdf3487d7d 100644 --- a/ghost/portal/src/components/PopupModal.js +++ b/ghost/portal/src/components/PopupModal.js @@ -5,7 +5,7 @@ import AccountHomePage from './pages/AccountHomePage'; import MagicLinkPage from './pages/MagicLinkPage'; import LoadingPage from './pages/LoadingPage'; import {ReactComponent as CloseIcon} from '../images/icons/close.svg'; -import {ParentContext} from './ParentContext'; +import AppContext from '../AppContext'; import FrameStyle from './Frame.styles'; import AccountPlanPage from './pages/AccountPlanPage'; import AccountProfilePage from './pages/AccountProfilePage'; @@ -42,6 +42,7 @@ const StylesWrapper = ({member}) => { width: '100%', height: '100%', overflow: 'auto', + textAlign: 'center', backgroundColor: 'rgba(128,128,128,0.5)' }, frame: { @@ -123,7 +124,7 @@ const Pages = { }; export default class PopupModal extends React.Component { - static contextType = ParentContext; + static contextType = AppContext; renderCurrentPage(page) { const PageComponent = Pages[page]; diff --git a/ghost/portal/src/components/TriggerButton.js b/ghost/portal/src/components/TriggerButton.js index 30c183b70e..d54808a666 100644 --- a/ghost/portal/src/components/TriggerButton.js +++ b/ghost/portal/src/components/TriggerButton.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import Frame from './Frame'; import MemberGravatar from './common/MemberGravatar'; -import {ParentContext} from './ParentContext'; +import AppContext from '../AppContext'; import {ReactComponent as UserIcon} from '../images/icons/user.svg'; import {ReactComponent as CloseIcon} from '../images/icons/close.svg'; const React = require('react'); @@ -62,7 +62,7 @@ const Styles = ({brandColor}) => { }; export default class TriggerButton extends React.Component { - static contextType = ParentContext; + static contextType = AppContext; onToggle() { this.context.onAction('togglePopup'); diff --git a/ghost/portal/src/components/pages/AccountHomePage.js b/ghost/portal/src/components/pages/AccountHomePage.js index 9bbf9b543b..2a9b391a1b 100644 --- a/ghost/portal/src/components/pages/AccountHomePage.js +++ b/ghost/portal/src/components/pages/AccountHomePage.js @@ -1,4 +1,4 @@ -import {ParentContext} from '../ParentContext'; +import AppContext from '../../AppContext'; import MemberAvatar from '../common/MemberGravatar'; import ActionButton from '../common/ActionButton'; import Switch from '../common/Switch'; @@ -59,7 +59,7 @@ const UserHeader = ({member}) => { }; class FreeAccountHomePage extends React.Component { - static contextType = ParentContext; + static contextType = AppContext; handleSignout(e) { e.preventDefault(); @@ -120,7 +120,7 @@ class FreeAccountHomePage extends React.Component { } class PaidAccountHomePage extends React.Component { - static contextType = ParentContext; + static contextType = AppContext; handleSignout(e) { e.preventDefault(); @@ -254,7 +254,7 @@ class PaidAccountHomePage extends React.Component { } } export default class AccountHomePage extends React.Component { - static contextType = ParentContext; + static contextType = AppContext; render() { const {member} = this.context; diff --git a/ghost/portal/src/components/pages/AccountPlanPage.js b/ghost/portal/src/components/pages/AccountPlanPage.js index 2966a66b6a..193764fae8 100644 --- a/ghost/portal/src/components/pages/AccountPlanPage.js +++ b/ghost/portal/src/components/pages/AccountPlanPage.js @@ -1,11 +1,11 @@ -import {ParentContext} from '../ParentContext'; +import AppContext from '../../AppContext'; import ActionButton from '../common/ActionButton'; import PlansSection from '../common/PlansSection'; const React = require('react'); export default class AccountPlanPage extends React.Component { - static contextType = ParentContext; + static contextType = AppContext; constructor(props, context) { super(props, context); diff --git a/ghost/portal/src/components/pages/AccountProfilePage.js b/ghost/portal/src/components/pages/AccountProfilePage.js index 40eebf1d60..e5642307b5 100644 --- a/ghost/portal/src/components/pages/AccountProfilePage.js +++ b/ghost/portal/src/components/pages/AccountProfilePage.js @@ -1,4 +1,4 @@ -import {ParentContext} from '../ParentContext'; +import AppContext from '../../AppContext'; import MemberAvatar from '../common/MemberGravatar'; import ActionButton from '../common/ActionButton'; import InputField from '../common/InputField'; @@ -7,7 +7,7 @@ import Switch from '../common/Switch'; const React = require('react'); export default class AccountProfilePage extends React.Component { - static contextType = ParentContext; + static contextType = AppContext; constructor(props, context) { super(props, context); diff --git a/ghost/portal/src/components/pages/MagicLinkPage.js b/ghost/portal/src/components/pages/MagicLinkPage.js index 46e18f0f2a..dbe92343fb 100644 --- a/ghost/portal/src/components/pages/MagicLinkPage.js +++ b/ghost/portal/src/components/pages/MagicLinkPage.js @@ -1,9 +1,9 @@ import ActionButton from '../common/ActionButton'; -import {ParentContext} from '../ParentContext'; +import AppContext from '../../AppContext'; const React = require('react'); export default class MagicLinkPage extends React.Component { - static contextType = ParentContext; + static contextType = AppContext; renderFormHeader() { return ( diff --git a/ghost/portal/src/components/pages/SigninPage.js b/ghost/portal/src/components/pages/SigninPage.js index 296fdc2bcd..4b14451494 100644 --- a/ghost/portal/src/components/pages/SigninPage.js +++ b/ghost/portal/src/components/pages/SigninPage.js @@ -1,11 +1,11 @@ import ActionButton from '../common/ActionButton'; import InputField from '../common/InputField'; -import {ParentContext} from '../ParentContext'; +import AppContext from '../../AppContext'; const React = require('react'); export default class SigninPage extends React.Component { - static contextType = ParentContext; + static contextType = AppContext; constructor(props) { super(props); diff --git a/ghost/portal/src/components/pages/SignupPage.js b/ghost/portal/src/components/pages/SignupPage.js index 702e31e28f..4eebc972bb 100644 --- a/ghost/portal/src/components/pages/SignupPage.js +++ b/ghost/portal/src/components/pages/SignupPage.js @@ -1,12 +1,12 @@ import ActionButton from '../common/ActionButton'; import InputField from '../common/InputField'; -import {ParentContext} from '../ParentContext'; +import AppContext from '../../AppContext'; import PlansSection from '../common/PlansSection'; const React = require('react'); class SignupPage extends React.Component { - static contextType = ParentContext; + static contextType = AppContext; constructor(props) { super(props); diff --git a/ghost/portal/src/utils/api.js b/ghost/portal/src/utils/api.js index be20af66a0..a3c5da03d3 100644 --- a/ghost/portal/src/utils/api.js +++ b/ghost/portal/src/utils/api.js @@ -1,12 +1,11 @@ import * as Fixtures from './fixtures'; -function setupGhostApi() { +function setupGhostApi({siteUrl = window.location.origin}) { const apiPath = 'members/api'; - const siteUrl = window.location.origin; function endpointFor({type, resource}) { if (type === 'members') { - return `${siteUrl}/${apiPath}/${resource}/`; + return `${siteUrl.replace(/\/$/, '')}/${apiPath}/${resource}/`; } } diff --git a/ghost/portal/src/utils/test-utils.js b/ghost/portal/src/utils/test-utils.js index 7160067f58..1a9abb546e 100644 --- a/ghost/portal/src/utils/test-utils.js +++ b/ghost/portal/src/utils/test-utils.js @@ -1,15 +1,15 @@ // Common test setup util - Ref: https://testing-library.com/docs/react-testing-library/setup#custom-render import React from 'react'; import {render} from '@testing-library/react'; -import {ParentContext} from '../components/ParentContext'; +import AppContext from '../AppContext'; import {site, member} from './fixtures'; const setupProvider = (context) => { return ({children}) => { return ( - + {children} - + ); }; };