From ad3c66b36eb7dd869c3573f26c983cf02fadaebb Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Wed, 16 Jun 2021 10:32:38 +0100 Subject: [PATCH] Add IdeProvider (#4161) GitOrigin-RevId: cab09354cf4b325a1ea3814a8c4c49fac7c831be --- .../components/share-project-modal.js | 6 +- .../react-share-project-modal-controller.js | 3 +- .../js/shared/context/compile-context.js | 17 +- .../js/shared/context/editor-context.js | 52 ++-- .../frontend/js/shared/context/ide-context.js | 30 ++ .../js/shared/context/layout-context.js | 30 +- .../js/shared/context/root-context.js | 25 +- .../shared/context/util/scope-value-hook.js | 11 +- .../stories/preview-logs-pane.stories.js | 17 +- .../stories/share-project-modal.stories.js | 72 +---- .../stories/utils/with-context-root.js | 22 ++ .../components/share-project-modal.test.js | 286 ++++++++---------- .../frontend/helpers/render-with-context.js | 64 ++-- 13 files changed, 302 insertions(+), 333 deletions(-) create mode 100644 services/web/frontend/js/shared/context/ide-context.js create mode 100644 services/web/frontend/stories/utils/with-context-root.js diff --git a/services/web/frontend/js/features/share-project-modal/components/share-project-modal.js b/services/web/frontend/js/features/share-project-modal/components/share-project-modal.js index c93c51631c..b46d5f0363 100644 --- a/services/web/frontend/js/features/share-project-modal/components/share-project-modal.js +++ b/services/web/frontend/js/features/share-project-modal/components/share-project-modal.js @@ -84,12 +84,11 @@ export default function ShareProjectModal({ show, animation = true, isAdmin, - ide, }) { const [inFlight, setInFlight] = useState(false) const [error, setError] = useState() - const [project, setProject] = useScopeValue('project', ide.$scope, true) + const [project, setProject] = useScopeValue('project', true) // reset error when the modal is opened useEffect(() => { @@ -167,8 +166,5 @@ ShareProjectModal.propTypes = { animation: PropTypes.bool, handleHide: PropTypes.func.isRequired, isAdmin: PropTypes.bool.isRequired, - ide: PropTypes.shape({ - $scope: PropTypes.object.isRequired, - }).isRequired, show: PropTypes.bool.isRequired, } diff --git a/services/web/frontend/js/features/share-project-modal/controllers/react-share-project-modal-controller.js b/services/web/frontend/js/features/share-project-modal/controllers/react-share-project-modal-controller.js index 5fcc87d96b..11060a0e9c 100644 --- a/services/web/frontend/js/features/share-project-modal/controllers/react-share-project-modal-controller.js +++ b/services/web/frontend/js/features/share-project-modal/controllers/react-share-project-modal-controller.js @@ -9,8 +9,7 @@ App.component( 'shareProjectModal', react2angular( rootContext.use(ShareProjectModal), - Object.keys(ShareProjectModal.propTypes), - ['ide'] + Object.keys(ShareProjectModal.propTypes) ) ) diff --git a/services/web/frontend/js/shared/context/compile-context.js b/services/web/frontend/js/shared/context/compile-context.js index 7efd2b907e..7ab63ecf48 100644 --- a/services/web/frontend/js/shared/context/compile-context.js +++ b/services/web/frontend/js/shared/context/compile-context.js @@ -13,11 +13,11 @@ CompileContext.Provider.propTypes = { }), } -export function CompileProvider({ children, $scope }) { - const [pdfUrl] = useScopeValue('pdf.url', $scope) - const [pdfDownloadUrl] = useScopeValue('pdf.downloadUrl', $scope) - const [logEntries] = useScopeValue('pdf.logEntries', $scope) - const [uncompiled] = useScopeValue('pdf.uncompiled', $scope) +export function CompileProvider({ children }) { + const [pdfUrl] = useScopeValue('pdf.url') + const [pdfDownloadUrl] = useScopeValue('pdf.downloadUrl') + const [logEntries] = useScopeValue('pdf.logEntries') + const [uncompiled] = useScopeValue('pdf.uncompiled') const value = { pdfUrl, @@ -27,17 +27,12 @@ export function CompileProvider({ children, $scope }) { } return ( - <> - - {children} - - + {children} ) } CompileProvider.propTypes = { children: PropTypes.any, - $scope: PropTypes.any.isRequired, } export function useCompileContext(propTypes) { diff --git a/services/web/frontend/js/shared/context/editor-context.js b/services/web/frontend/js/shared/context/editor-context.js index 740b1cb95d..13a6829f43 100644 --- a/services/web/frontend/js/shared/context/editor-context.js +++ b/services/web/frontend/js/shared/context/editor-context.js @@ -2,6 +2,7 @@ import React, { createContext, useCallback, useContext, useEffect } from 'react' import PropTypes from 'prop-types' import useScopeValue from './util/scope-value-hook' import useBrowserWindow from '../hooks/use-browser-window' +import { useIdeContext } from './ide-context' export const EditorContext = createContext() @@ -30,7 +31,9 @@ EditorContext.Provider.propTypes = { }), } -export function EditorProvider({ children, ide, settings }) { +export function EditorProvider({ children, settings }) { + const ide = useIdeContext() + const cobranding = window.brandVariation ? { logoImgUrl: window.brandVariation.logo_url, @@ -45,26 +48,12 @@ export function EditorProvider({ children, ide, settings }) { } : undefined - const ownerId = - ide.$scope.project && ide.$scope.project.owner - ? ide.$scope.project.owner._id - : null - - const [loading] = useScopeValue('state.loading', ide.$scope) - - const [projectRootDocId] = useScopeValue('project.rootDoc_id', ide.$scope) - - const [projectName, setProjectName] = useScopeValue( - 'project.name', - ide.$scope - ) - - const [compileGroup] = useScopeValue( - 'project.features.compileGroup', - ide.$scope - ) - - const [rootFolder] = useScopeValue('rootFolder', ide.$scope) + const [loading] = useScopeValue('state.loading') + const [projectRootDocId] = useScopeValue('project.rootDoc_id') + const [projectName, setProjectName] = useScopeValue('project.name') + const [compileGroup] = useScopeValue('project.features.compileGroup') + const [rootFolder] = useScopeValue('rootFolder') + const [ownerId] = useScopeValue('project.owner.id') const renameProject = useCallback( newName => { @@ -112,22 +101,25 @@ export function EditorProvider({ children, ide, settings }) { } return ( - <> - - {children} - - + + {children} + ) } EditorProvider.propTypes = { children: PropTypes.any, - ide: PropTypes.any.isRequired, settings: PropTypes.any.isRequired, } export function useEditorContext(propTypes) { - const data = useContext(EditorContext) - PropTypes.checkPropTypes(propTypes, data, 'data', 'EditorContext.Provider') - return data + const context = useContext(EditorContext) + + if (!context) { + throw new Error('useEditorContext is only available inside EditorProvider') + } + + PropTypes.checkPropTypes(propTypes, context, 'data', 'EditorContext.Provider') + + return context } diff --git a/services/web/frontend/js/shared/context/ide-context.js b/services/web/frontend/js/shared/context/ide-context.js new file mode 100644 index 0000000000..f28ea7989a --- /dev/null +++ b/services/web/frontend/js/shared/context/ide-context.js @@ -0,0 +1,30 @@ +import React, { createContext, useContext } from 'react' +import PropTypes from 'prop-types' + +const IdeContext = createContext() + +IdeContext.Provider.propTypes = { + value: PropTypes.shape({ + $scope: PropTypes.object.isRequired, + }), +} + +export function useIdeContext() { + const context = useContext(IdeContext) + + if (!context) { + throw new Error('useIdeContext is only available inside IdeProvider') + } + + return context +} + +export function IdeProvider({ ide, children }) { + return {children} +} +IdeProvider.propTypes = { + children: PropTypes.any.isRequired, + ide: PropTypes.shape({ + $scope: PropTypes.object.isRequired, + }).isRequired, +} diff --git a/services/web/frontend/js/shared/context/layout-context.js b/services/web/frontend/js/shared/context/layout-context.js index ee15a93907..79ba58ebb9 100644 --- a/services/web/frontend/js/shared/context/layout-context.js +++ b/services/web/frontend/js/shared/context/layout-context.js @@ -1,6 +1,7 @@ import React, { createContext, useContext, useCallback } from 'react' import PropTypes from 'prop-types' import useScopeValue from './util/scope-value-hook' +import { useIdeContext } from './ide-context' export const LayoutContext = createContext() @@ -18,8 +19,11 @@ LayoutContext.Provider.propTypes = { }).isRequired, } -export function LayoutProvider({ children, $scope }) { - const [view, _setView] = useScopeValue('ui.view', $scope) +export function LayoutProvider({ children }) { + const { $scope } = useIdeContext() + + const [view, _setView] = useScopeValue('ui.view') + const setView = useCallback( value => { _setView(value) @@ -30,19 +34,14 @@ export function LayoutProvider({ children, $scope }) { [$scope, _setView] ) - const [chatIsOpen, setChatIsOpen] = useScopeValue('ui.chatOpen', $scope) + const [chatIsOpen, setChatIsOpen] = useScopeValue('ui.chatOpen') const [reviewPanelOpen, setReviewPanelOpen] = useScopeValue( - 'ui.reviewPanelOpen', - $scope - ) - const [leftMenuShown, setLeftMenuShown] = useScopeValue( - 'ui.leftMenuShown', - $scope + 'ui.reviewPanelOpen' ) + const [leftMenuShown, setLeftMenuShown] = useScopeValue('ui.leftMenuShown') + const [pdfLayout] = useScopeValue('ui.pdfLayout') - const [pdfLayout] = useScopeValue('ui.pdfLayout', $scope) - - const layoutContextValue = { + const value = { view, setView, chatIsOpen, @@ -55,17 +54,12 @@ export function LayoutProvider({ children, $scope }) { } return ( - - {children} - + {children} ) } LayoutProvider.propTypes = { children: PropTypes.any, - $scope: PropTypes.shape({ - toggleHistory: PropTypes.func.isRequired, - }).isRequired, } export function useLayoutContext(propTypes) { diff --git a/services/web/frontend/js/shared/context/root-context.js b/services/web/frontend/js/shared/context/root-context.js index e7e0033a11..ea2a8c7071 100644 --- a/services/web/frontend/js/shared/context/root-context.js +++ b/services/web/frontend/js/shared/context/root-context.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types' import createSharedContext from 'react2angular-shared-context' import { ApplicationProvider } from './application-context' +import { IdeProvider } from './ide-context' import { EditorProvider } from './editor-context' import { CompileProvider } from './compile-context' import { LayoutProvider } from './layout-context' @@ -13,17 +14,19 @@ export function ContextRoot({ children, ide, settings }) { return ( - - - - {isAnonymousUser ? ( - children - ) : ( - {children} - )} - - - + + + + + {isAnonymousUser ? ( + children + ) : ( + {children} + )} + + + + ) } diff --git a/services/web/frontend/js/shared/context/util/scope-value-hook.js b/services/web/frontend/js/shared/context/util/scope-value-hook.js index c1e6b8fcbc..3418b4bfc7 100644 --- a/services/web/frontend/js/shared/context/util/scope-value-hook.js +++ b/services/web/frontend/js/shared/context/util/scope-value-hook.js @@ -1,17 +1,22 @@ import { useCallback, useEffect, useState } from 'react' +import PropTypes from 'prop-types' import _ from 'lodash' +import { useIdeContext } from '../ide-context' /** * Binds a property in an Angular scope making it accessible in a React * component. The interface is compatible with React.useState(), including * the option of passing a function to the setter. * - * @param {string} path - dot '.' path of a property in `sourceScope`. - * @param {object} $scope - Angular $scope containing the value to bind. + * @param {string} path - dot '.' path of a property in the Angular scope. * @param {boolean} deep * @returns {[any, function]} - Binded value and setter function tuple. */ -export default function useScopeValue(path, $scope, deep = false) { +export default function useScopeValue(path, deep = false) { + const { $scope } = useIdeContext({ + $scope: PropTypes.object.isRequired, + }) + const [value, setValue] = useState(() => _.get($scope, path)) useEffect(() => { diff --git a/services/web/frontend/stories/preview-logs-pane.stories.js b/services/web/frontend/stories/preview-logs-pane.stories.js index 0ea3701c57..066cbe161b 100644 --- a/services/web/frontend/stories/preview-logs-pane.stories.js +++ b/services/web/frontend/stories/preview-logs-pane.stories.js @@ -3,6 +3,7 @@ import PreviewLogsPane from '../js/features/preview/components/preview-logs-pane import { EditorProvider } from '../js/shared/context/editor-context' import { ApplicationProvider } from '../js/shared/context/application-context' import useFetchMock from './hooks/use-fetch-mock' +import { IdeProvider } from '../js/shared/context/ide-context' export const TimedOutError = args => { useFetchMock(fetchMock => { @@ -25,9 +26,11 @@ export const TimedOutError = args => { return ( - - - + + + + + ) } @@ -58,9 +61,11 @@ export const TimedOutErrorWithPriorityCompile = args => { return ( - - - + + + + + ) } diff --git a/services/web/frontend/stories/share-project-modal.stories.js b/services/web/frontend/stories/share-project-modal.stories.js index 7a595c8a99..d0e9b82fa4 100644 --- a/services/web/frontend/stories/share-project-modal.stories.js +++ b/services/web/frontend/stories/share-project-modal.stories.js @@ -1,7 +1,7 @@ import React, { useEffect } from 'react' import ShareProjectModal from '../js/features/share-project-modal/components/share-project-modal' -import { ContextRoot } from '../js/shared/context/root-context' import useFetchMock from './hooks/use-fetch-mock' +import { withContextRoot } from './utils/with-context-root' export const LinkSharingOff = args => { useFetchMock(setupFetchMock) @@ -11,9 +11,7 @@ export const LinkSharingOff = args => { publicAccesLevel: 'private', } - return renderWithContext( - - ) + return withContextRoot(, { project }) } export const LinkSharingOn = args => { @@ -24,9 +22,7 @@ export const LinkSharingOn = args => { publicAccesLevel: 'tokenBased', } - return renderWithContext( - - ) + return withContextRoot(, { project }) } export const LinkSharingLoading = args => { @@ -38,9 +34,7 @@ export const LinkSharingLoading = args => { tokens: undefined, } - return renderWithContext( - - ) + return withContextRoot(, { project }) } export const NonAdminLinkSharingOff = args => { @@ -49,13 +43,9 @@ export const NonAdminLinkSharingOff = args => { publicAccesLevel: 'private', } - return renderWithContext( - - ) + return withContextRoot(, { + project, + }) } export const NonAdminLinkSharingOn = args => { @@ -64,13 +54,9 @@ export const NonAdminLinkSharingOn = args => { publicAccesLevel: 'tokenBased', } - return renderWithContext( - - ) + return withContextRoot(, { + project, + }) } export const RestrictedTokenMember = args => { @@ -91,9 +77,7 @@ export const RestrictedTokenMember = args => { publicAccesLevel: 'tokenBased', } - return renderWithContext( - - ) + return withContextRoot(, { project }) } export const LegacyLinkSharingReadAndWrite = args => { @@ -104,9 +88,7 @@ export const LegacyLinkSharingReadAndWrite = args => { publicAccesLevel: 'readAndWrite', } - return renderWithContext( - - ) + return withContextRoot(, { project }) } export const LegacyLinkSharingReadOnly = args => { @@ -117,9 +99,7 @@ export const LegacyLinkSharingReadOnly = args => { publicAccesLevel: 'readOnly', } - return renderWithContext( - - ) + return withContextRoot(, { project }) } export const LimitedCollaborators = args => { @@ -133,9 +113,7 @@ export const LimitedCollaborators = args => { }, } - return renderWithContext( - - ) + return withContextRoot(, { project }) } const project = { @@ -199,18 +177,6 @@ export default { }, } -// Unfortunately, we cannot currently use decorators here, since we need to -// set a value on window, before the contexts are rendered. -// When using decorators, the contexts are rendered before the story, so we -// don't have the opportunity to set the window value first. -function renderWithContext(Story) { - return ( - - {Story} - - ) -} - const contacts = [ // user with edited name { @@ -282,13 +248,3 @@ function setupFetchMock(fetchMock) { // send analytics event .post('express:/event/:key', 200) } - -function ideWithProject(project) { - return { - $scope: { - $watch: () => () => {}, - $applyAsync: () => {}, - project, - }, - } -} diff --git a/services/web/frontend/stories/utils/with-context-root.js b/services/web/frontend/stories/utils/with-context-root.js new file mode 100644 index 0000000000..9b037f058f --- /dev/null +++ b/services/web/frontend/stories/utils/with-context-root.js @@ -0,0 +1,22 @@ +import React from 'react' +import { ContextRoot } from '../../js/shared/context/root-context' + +// Unfortunately, we cannot currently use decorators here, since we need to +// set a value on window, before the contexts are rendered. +// When using decorators, the contexts are rendered before the story, so we +// don't have the opportunity to set the window value first. +export function withContextRoot(Story, scope) { + const ide = { + ...window._ide, + $scope: { + ...window._ide.$scope, + ...scope, + }, + } + + return ( + + {Story} + + ) +} diff --git a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.js b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.js index c6fe61b342..9652b7596e 100644 --- a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.js +++ b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.js @@ -4,16 +4,17 @@ import React from 'react' import { screen, fireEvent, + render, waitFor, waitForElementToBeRemoved, } from '@testing-library/react' import fetchMock from 'fetch-mock' -import { get } from 'lodash' import ShareProjectModal from '../../../../../frontend/js/features/share-project-modal/components/share-project-modal' import { renderWithEditorContext, cleanUpContext, + EditorProviders, } from '../../../helpers/render-with-context' import * as locationModule from '../../../../../frontend/js/features/share-project-modal/utils/location' @@ -73,23 +74,7 @@ describe('', function () { }, ] - const ideWithProject = project => { - const scope = { project } - - return { - $scope: { - $watch: (path, callback) => { - callback(get(scope, path)) - return () => null - }, - $applyAsync: () => {}, - ...scope, - }, - } - } - const modalProps = { - ide: ideWithProject(project), show: true, isAdmin: true, handleHide: sinon.stub(), @@ -105,7 +90,9 @@ describe('', function () { }) it('renders the modal', async function () { - renderWithEditorContext() + renderWithEditorContext(, { + scope: { project }, + }) await screen.findByText('Share Project') }) @@ -114,7 +101,8 @@ describe('', function () { const handleHide = sinon.stub() renderWithEditorContext( - + , + { scope: { project } } ) const [ @@ -129,12 +117,9 @@ describe('', function () { }) it('handles access level "private"', async function () { - renderWithEditorContext( - - ) + renderWithEditorContext(, { + scope: { project: { ...project, publicAccesLevel: 'private' } }, + }) await screen.findByText( 'Link sharing is off, only invited users can view this project.' @@ -148,12 +133,9 @@ describe('', function () { }) it('handles access level "tokenBased"', async function () { - renderWithEditorContext( - - ) + renderWithEditorContext(, { + scope: { project: { ...project, publicAccesLevel: 'tokenBased' } }, + }) await screen.findByText('Link sharing is on') await screen.findByRole('button', { name: 'Turn off link sharing' }) @@ -165,12 +147,9 @@ describe('', function () { }) it('handles legacy access level "readAndWrite"', async function () { - renderWithEditorContext( - - ) + renderWithEditorContext(, { + scope: { project: { ...project, publicAccesLevel: 'readAndWrite' } }, + }) await screen.findByText( 'This project is public and can be edited by anyone with the URL.' @@ -179,12 +158,9 @@ describe('', function () { }) it('handles legacy access level "readOnly"', async function () { - renderWithEditorContext( - - ) + renderWithEditorContext(, { + scope: { project: { ...project, publicAccesLevel: 'readOnly' } }, + }) await screen.findByText( 'This project is public and can be viewed but not edited by anyone with the URL' @@ -202,16 +178,18 @@ describe('', function () { ] // render as admin: actions should be present - const { rerender } = renderWithEditorContext( - + const { rerender } = render( + + + ) await screen.findByRole('button', { name: 'Turn off link sharing' }) @@ -219,15 +197,17 @@ describe('', function () { // render as non-admin (non-owner), link sharing on: actions should be missing and message should be present rerender( - + + + ) await screen.findByText( @@ -242,15 +222,17 @@ describe('', function () { // render as non-admin (non-owner), link sharing off: actions should be missing and message should be present rerender( - + + + ) await screen.findByText( @@ -265,13 +247,10 @@ describe('', function () { }) it('only shows read-only token link to restricted token members', async function () { - renderWithEditorContext( - , - { isRestrictedTokenMember: true } - ) + renderWithEditorContext(, { + isRestrictedTokenMember: true, + scope: { project: { ...project, publicAccesLevel: 'tokenBased' } }, + }) // no buttons expect(screen.queryByRole('button', { name: 'Turn on link sharing' })).to.be @@ -312,17 +291,16 @@ describe('', function () { }, ] - renderWithEditorContext( - , { + scope: { + project: { ...project, members, invites, publicAccesLevel: 'tokenBased', - })} - /> - ) + }, + }, + }) expect(screen.queryAllByText('project-owner@example.com')).to.have.length(1) expect(screen.queryAllByText('member-author@example.com')).to.have.length(1) @@ -356,16 +334,15 @@ describe('', function () { }, ] - renderWithEditorContext( - , { + scope: { + project: { ...project, invites, publicAccesLevel: 'tokenBased', - })} - /> - ) + }, + }, + }) const [, closeButton] = screen.getAllByRole('button', { name: 'Close', @@ -391,16 +368,15 @@ describe('', function () { }, ] - renderWithEditorContext( - , { + scope: { + project: { ...project, invites, publicAccesLevel: 'tokenBased', - })} - /> - ) + }, + }, + }) const [, closeButton] = screen.getAllByRole('button', { name: 'Close', @@ -425,16 +401,15 @@ describe('', function () { }, ] - renderWithEditorContext( - , { + scope: { + project: { ...project, members, publicAccesLevel: 'tokenBased', - })} - /> - ) + }, + }, + }) const [, closeButton] = await screen.getAllByRole('button', { name: 'Close', @@ -468,16 +443,15 @@ describe('', function () { }, ] - renderWithEditorContext( - , { + scope: { + project: { ...project, members, publicAccesLevel: 'tokenBased', - })} - /> - ) + }, + }, + }) expect(screen.queryAllByText('member-viewer@example.com')).to.have.length(1) @@ -508,16 +482,15 @@ describe('', function () { }, ] - renderWithEditorContext( - , { + scope: { + project: { ...project, members, publicAccesLevel: 'tokenBased', - })} - /> - ) + }, + }, + }) expect(screen.queryAllByText('member-viewer@example.com')).to.have.length(1) @@ -551,15 +524,14 @@ describe('', function () { }) it('sends invites to input email addresses', async function () { - renderWithEditorContext( - , { + scope: { + project: { ...project, publicAccesLevel: 'tokenBased', - })} - /> - ) + }, + }, + }) const [inputElement] = await screen.findAllByLabelText( 'Share with your collaborators' @@ -641,24 +613,21 @@ describe('', function () { it('displays a message when the collaborator limit is reached', async function () { fetchMock.post('/event/project-sharing-paywall-prompt', {}) - renderWithEditorContext( - , { + user: { + id: '123abd', + allowedFreeTrial: true, + }, + scope: { + project: { ...project, publicAccesLevel: 'tokenBased', features: { collaborators: 0, }, - })} - />, - { - user: { - id: '123abd', - allowedFreeTrial: true, }, - } - ) + }, + }) expect(screen.queryByLabelText('Share with your collaborators')).to.be.null @@ -668,15 +637,14 @@ describe('', function () { }) it('handles server error responses', async function () { - renderWithEditorContext( - , { + scope: { + project: { ...project, publicAccesLevel: 'tokenBased', - })} - /> - ) + }, + }, + }) // loading contacts await waitFor(() => { @@ -738,25 +706,19 @@ describe('', function () { const watchCallbacks = {} - const ideWithProject = project => { + const scopeWithProject = project => { return { - $scope: { - $watch: (path, callback, deep) => { - watchCallbacks[path] = callback - return () => {} - }, - $applyAsync: () => {}, - project, + $watch: (path, callback) => { + watchCallbacks[path] = callback + return () => {} }, + project, } } - renderWithEditorContext( - - ) + renderWithEditorContext(, { + scope: scopeWithProject({ ...project, publicAccesLevel: 'private' }), + }) await screen.findByText( 'Link sharing is off, only invited users can view this project.' @@ -799,7 +761,9 @@ describe('', function () { }) it('avoids selecting unmatched contact', async function () { - renderWithEditorContext() + renderWithEditorContext(, { + scope: { project }, + }) const [inputElement] = await screen.findAllByLabelText( 'Share with your collaborators' diff --git a/services/web/test/frontend/helpers/render-with-context.js b/services/web/test/frontend/helpers/render-with-context.js index f705889dde..229311a954 100644 --- a/services/web/test/frontend/helpers/render-with-context.js +++ b/services/web/test/frontend/helpers/render-with-context.js @@ -8,6 +8,8 @@ import { ApplicationProvider } from '../../../frontend/js/shared/context/applica import { EditorProvider } from '../../../frontend/js/shared/context/editor-context' import { LayoutProvider } from '../../../frontend/js/shared/context/layout-context' import { ChatProvider } from '../../../frontend/js/features/chat/context/chat-context' +import { IdeProvider } from '../../../frontend/js/shared/context/ide-context' +import { get } from 'lodash' export function EditorProviders({ user = { id: '123abd' }, @@ -17,6 +19,7 @@ export function EditorProviders({ removeListener: sinon.stub(), }, isRestrictedTokenMember = false, + scope, children, }) { window.user = user || window.user @@ -24,38 +27,44 @@ export function EditorProviders({ window.project_id = projectId != null ? projectId : window.project_id window.isRestrictedTokenMember = isRestrictedTokenMember - window._ide = { - $scope: { - project: { - owner: { - _id: '124abd', - }, + const $scope = { + project: { + owner: { + _id: '124abd', }, - ui: { - chatOpen: true, - pdfLayout: 'flat', - }, - $watch: () => {}, - toggleHistory: () => {}, }, - socket, + ui: { + chatOpen: true, + pdfLayout: 'flat', + }, + $watch: (path, callback) => { + callback(get($scope, path)) + return () => null + }, + $applyAsync: () => {}, + toggleHistory: () => {}, + ...scope, } + + window._ide = { $scope, socket } + return ( - - {children} - + + + {children} + + ) } export function renderWithEditorContext(component, contextProps) { - return render(component, { - // eslint-disable-next-line react/display-name - wrapper: ({ children }) => ( - {children} - ), - }) + const EditorProvidersWrapper = ({ children }) => ( + {children} + ) + + return render(component, { wrapper: EditorProvidersWrapper }) } export function ChatProviders({ children, ...props }) { @@ -67,12 +76,11 @@ export function ChatProviders({ children, ...props }) { } export function renderWithChatContext(component, props) { - return render(component, { - // eslint-disable-next-line react/display-name - wrapper: ({ children }) => ( - {children} - ), - }) + const ChatProvidersWrapper = ({ children }) => ( + {children} + ) + + return render(component, { wrapper: ChatProvidersWrapper }) } export function cleanUpContext() {