[web] Move UI scope to React states (#26403)

* Move `ui.view` to setState

* Move `openFile` to setState

* Move `ui.leftMenuShown` to setState

* Move `ui.miniReviewPanelVisible` to setState

* Move `ui.pdfLayout` to setState

* Move `ui.chatOpen` to setState

* Move `ui.reviewPanelOpen` to setState

* Cleanup: remove layout-context-adapter and imports

* Replace `ui` scope by mocked `LayoutProvider` in tests

* Update test

* Remove unnecessary `scopeStore.set('ui.chatOpen' ...`

GitOrigin-RevId: 81578bfdc958239eac492905f714a6074c81d0f5
This commit is contained in:
Antoine Clausse
2025-06-24 10:05:34 +02:00
committed by Copybot
parent ab140f578d
commit 9e189e7d59
17 changed files with 406 additions and 310 deletions

View File

@@ -36,8 +36,6 @@ import { DocId } from '../../../../../types/project-settings'
import { Update } from '@/features/history/services/types/update'
import { useDebugDiffTracker } from '../hooks/use-debug-diff-tracker'
import { useEditorContext } from '@/shared/context/editor-context'
import useScopeValueSetterOnly from '@/shared/hooks/use-scope-value-setter-only'
import { BinaryFile } from '@/features/file-view/types/binary-file'
import { convertFileRefToBinaryFile } from '@/features/ide-react/util/file-view'
export interface GotoOffsetOptions {
@@ -97,7 +95,7 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
const { reportError, eventEmitter, projectId } = useIdeReactContext()
const { setOutOfSync } = useEditorContext()
const { socket, closeConnection, connectionState } = useConnectionContext()
const { view, setView } = useLayoutContext()
const { view, setView, setOpenFile } = useLayoutContext()
const { showGenericMessageModal, genericModalVisible, showOutOfSyncModal } =
useModalsContext()
const { id: userId } = useUserContext()
@@ -521,8 +519,6 @@ export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
[fileTreeData, openDoc]
)
const [, setOpenFile] = useScopeValueSetterOnly<BinaryFile | null>('openFile')
const openFileWithId = useCallback(
(fileRefId: string) => {
const fileRef = findFileRefEntityById(fileTreeData, fileRefId)

View File

@@ -11,8 +11,6 @@ import {
import { useProjectContext } from '@/shared/context/project-context'
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
import useScopeValueSetterOnly from '@/shared/hooks/use-scope-value-setter-only'
import { BinaryFile } from '@/features/file-view/types/binary-file'
import {
FileTreeDocumentFindResult,
FileTreeFileRefFindResult,
@@ -22,6 +20,7 @@ import { debugConsole } from '@/utils/debugging'
import { convertFileRefToBinaryFile } from '@/features/ide-react/util/file-view'
import { sendMB } from '@/infrastructure/event-tracking'
import { FileRef } from '../../../../../types/file-ref'
import { useLayoutContext } from '@/shared/context/layout-context'
const FileTreeOpenContext = createContext<
| {
@@ -43,7 +42,7 @@ export const FileTreeOpenProvider: FC<React.PropsWithChildren> = ({
const { eventEmitter, projectJoined } = useIdeReactContext()
const { openDocWithId, currentDocumentId, openInitialDoc } =
useEditorManagerContext()
const [, setOpenFile] = useScopeValueSetterOnly<BinaryFile | null>('openFile')
const { setOpenFile } = useLayoutContext()
const [openEntity, setOpenEntity] = useState<
FileTreeDocumentFindResult | FileTreeFileRefFindResult | null
>(null)

View File

@@ -8,7 +8,6 @@ import React, {
useCallback,
} from 'react'
import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
import populateLayoutScope from '@/features/ide-react/scope-adapters/layout-context-adapter'
import { IdeProvider } from '@/shared/context/ide-context'
import {
createIdeEventEmitter,
@@ -67,7 +66,6 @@ export function createReactScopeValueStore(projectId: string) {
// necessary values in the store, but this is simpler for now
populateIdeReactScope(scopeStore)
populateEditorScope(scopeStore, projectId)
populateLayoutScope(scopeStore)
populateProjectScope(scopeStore)
populatePdfScope(scopeStore)

View File

@@ -1,14 +0,0 @@
import { ReactScopeValueStore } from '../scope-value-store/react-scope-value-store'
import getMeta from '@/utils/meta'
const reviewPanelStorageKey = `ui.reviewPanelOpen.${getMeta('ol-project_id')}`
export default function populateLayoutScope(store: ReactScopeValueStore) {
store.set('ui.view', 'editor')
store.set('openFile', null)
store.persisted('ui.chatOpen', false, 'ui.chatOpen')
store.persisted('ui.reviewPanelOpen', false, reviewPanelStorageKey)
store.set('ui.leftMenuShown', false)
store.set('ui.miniReviewPanelVisible', false)
store.set('ui.pdfLayout', 'sideBySide')
}

View File

@@ -9,7 +9,6 @@ import {
FC,
useState,
} from 'react'
import useScopeValue from '../hooks/use-scope-value'
import useDetachLayout from '../hooks/use-detach-layout'
import localStorage from '../../infrastructure/local-storage'
import getMeta from '../../utils/meta'
@@ -21,6 +20,7 @@ import useEventListener from '@/shared/hooks/use-event-listener'
import { isMac } from '@/shared/utils/os'
import { sendSearchEvent } from '@/features/event-tracking/search-events'
import { useRailContext } from '@/features/ide-redesign/contexts/rail-context'
import usePersistedState from '@/shared/hooks/use-persisted-state'
export type IdeLayout = 'sideBySide' | 'flat'
export type IdeView = 'editor' | 'file' | 'pdf' | 'history'
@@ -55,6 +55,8 @@ export type LayoutContextValue = {
pdfPreviewOpen: boolean
projectSearchIsOpen: boolean
setProjectSearchIsOpen: Dispatch<SetStateAction<boolean>>
openFile: BinaryFile | null
setOpenFile: Dispatch<SetStateAction<BinaryFile | null>>
}
const debugPdfDetach = getMeta('ol-debugPdfDetach')
@@ -70,10 +72,12 @@ function setLayoutInLocalStorage(pdfLayout: IdeLayout) {
)
}
const reviewPanelStorageKey = `ui.reviewPanelOpen.${getMeta('ol-project_id')}`
export const LayoutProvider: FC<React.PropsWithChildren> = ({ children }) => {
// what to show in the "flat" view (editor or pdf)
const [view, _setView] = useScopeValue<IdeView | null>('ui.view')
const [openFile] = useScopeValue<BinaryFile | null>('openFile')
const [view, _setView] = useState<IdeView | null>('editor')
const [openFile, setOpenFile] = useState<BinaryFile | null>(null)
const historyToggleEmitter = useScopeEventEmitter('history:toggle', true)
const { isOpen: railIsOpen, setIsOpen: setRailIsOpen } = useRailContext()
const [prevRailIsOpen, setPrevRailIsOpen] = useState(railIsOpen)
@@ -118,19 +122,23 @@ export const LayoutProvider: FC<React.PropsWithChildren> = ({ children }) => {
)
// whether the chat pane is open
const [chatIsOpen, setChatIsOpen] = useScopeValue<boolean>('ui.chatOpen')
const [chatIsOpen, setChatIsOpen] = usePersistedState<boolean>(
'ui.chatOpen',
false
)
// whether the review pane is open
const [reviewPanelOpen, setReviewPanelOpen] =
useScopeValue<boolean>('ui.reviewPanelOpen')
const [reviewPanelOpen, setReviewPanelOpen] = usePersistedState<boolean>(
reviewPanelStorageKey,
false
)
// whether the review pane is collapsed
const [miniReviewPanelVisible, setMiniReviewPanelVisible] =
useScopeValue<boolean>('ui.miniReviewPanelVisible')
useState<boolean>(false)
// whether the menu pane is open
const [leftMenuShown, setLeftMenuShown] =
useScopeValue<boolean>('ui.leftMenuShown')
const [leftMenuShown, setLeftMenuShown] = useState<boolean>(false)
// whether the project search is open
const [projectSearchIsOpen, setProjectSearchIsOpen] = useState(false)
@@ -173,7 +181,7 @@ export const LayoutProvider: FC<React.PropsWithChildren> = ({ children }) => {
)
// whether to display the editor and preview side-by-side or full-width ("flat")
const [pdfLayout, setPdfLayout] = useScopeValue<IdeLayout>('ui.pdfLayout')
const [pdfLayout, setPdfLayout] = useState<IdeLayout>('sideBySide')
// whether stylesheet on theme is loading
const [loadingStyleSheet, setLoadingStyleSheet] = useState(false)
@@ -238,6 +246,7 @@ export const LayoutProvider: FC<React.PropsWithChildren> = ({ children }) => {
changeLayout,
chatIsOpen,
leftMenuShown,
openFile,
pdfLayout,
pdfPreviewOpen,
projectSearchIsOpen,
@@ -247,6 +256,7 @@ export const LayoutProvider: FC<React.PropsWithChildren> = ({ children }) => {
loadingStyleSheet,
setChatIsOpen,
setLeftMenuShown,
setOpenFile,
setPdfLayout,
setReviewPanelOpen,
setMiniReviewPanelVisible,
@@ -262,6 +272,7 @@ export const LayoutProvider: FC<React.PropsWithChildren> = ({ children }) => {
changeLayout,
chatIsOpen,
leftMenuShown,
openFile,
pdfLayout,
pdfPreviewOpen,
projectSearchIsOpen,
@@ -271,6 +282,7 @@ export const LayoutProvider: FC<React.PropsWithChildren> = ({ children }) => {
loadingStyleSheet,
setChatIsOpen,
setLeftMenuShown,
setOpenFile,
setPdfLayout,
setReviewPanelOpen,
setMiniReviewPanelVisible,

View File

@@ -59,10 +59,6 @@ const project: Project = {
const initialScope = {
user,
project,
ui: {
chatOpen: true,
pdfLayout: 'flat',
},
settings: {
pdfViewer: 'js',
syntaxValidation: true,

View File

@@ -56,14 +56,10 @@ describe('<EditorLeftMenu />', function () {
})
it('render full menu', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders scope={scope} layoutContext={{ leftMenuShown: true }}>
<EditorLeftMenu />
</EditorProviders>
)
@@ -110,14 +106,12 @@ describe('<EditorLeftMenu />', function () {
describe('download menu', function () {
it('have a correct source & pdf download url', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -142,14 +136,13 @@ describe('<EditorLeftMenu />', function () {
},
})
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -184,14 +177,13 @@ describe('<EditorLeftMenu />', function () {
},
}).as('wordCount')
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -217,9 +209,6 @@ describe('<EditorLeftMenu />', function () {
})
const scope = mockScope({
ui: {
leftMenuShown: true,
},
project: {
members: [],
owner: {
@@ -234,7 +223,10 @@ describe('<EditorLeftMenu />', function () {
})
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -245,9 +237,6 @@ describe('<EditorLeftMenu />', function () {
it('shows git modal correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
project: {
owner: {
_id: '123',
@@ -259,7 +248,10 @@ describe('<EditorLeftMenu />', function () {
})
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -271,9 +263,6 @@ describe('<EditorLeftMenu />', function () {
it('shows git modal paywall correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
project: {
owner: {
_id: '123',
@@ -285,7 +274,10 @@ describe('<EditorLeftMenu />', function () {
})
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -304,14 +296,13 @@ describe('<EditorLeftMenu />', function () {
enabled: false,
}).as('project-status')
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -335,14 +326,13 @@ describe('<EditorLeftMenu />', function () {
describe('settings menu', function () {
it('shows compiler menu correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -369,14 +359,13 @@ describe('<EditorLeftMenu />', function () {
})
it('shows texlive version menu correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -410,14 +399,14 @@ describe('<EditorLeftMenu />', function () {
folders: [],
}
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope} rootFolder={[rootFolder as any]}>
<EditorProviders
layoutContext={{ leftMenuShown: true }}
scope={scope}
rootFolder={[rootFolder as any]}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -451,14 +440,13 @@ describe('<EditorLeftMenu />', function () {
window.metaAttributesCache.set('ol-languages', languages)
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -475,14 +463,13 @@ describe('<EditorLeftMenu />', function () {
})
it('shows dictionary modal correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -493,14 +480,13 @@ describe('<EditorLeftMenu />', function () {
})
it('shows auto-complete menu correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -517,14 +503,13 @@ describe('<EditorLeftMenu />', function () {
})
it('shows auto-close brackets menu correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -541,14 +526,13 @@ describe('<EditorLeftMenu />', function () {
})
it('shows code check menu correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -579,14 +563,13 @@ describe('<EditorLeftMenu />', function () {
legacyEditorThemes
)
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -619,14 +602,13 @@ describe('<EditorLeftMenu />', function () {
})
it('shows overall theme menu correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -643,14 +625,13 @@ describe('<EditorLeftMenu />', function () {
})
it('shows keybindings menu correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -667,14 +648,13 @@ describe('<EditorLeftMenu />', function () {
})
it('shows font size menu correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -713,14 +693,13 @@ describe('<EditorLeftMenu />', function () {
})
it('shows font family menu correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -741,14 +720,13 @@ describe('<EditorLeftMenu />', function () {
})
it('shows line height menu correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -765,14 +743,13 @@ describe('<EditorLeftMenu />', function () {
})
it('shows pdf viewer menu correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -791,14 +768,13 @@ describe('<EditorLeftMenu />', function () {
describe('help menu', function () {
it('shows hotkeys modal correctly', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -808,14 +784,13 @@ describe('<EditorLeftMenu />', function () {
})
it('shows correct url for documentation', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -828,14 +803,13 @@ describe('<EditorLeftMenu />', function () {
})
it('shows correct contact us modal', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders
scope={scope}
layoutContext={{ leftMenuShown: true }}
>
<EditorLeftMenu />
</EditorProviders>
)
@@ -848,16 +822,13 @@ describe('<EditorLeftMenu />', function () {
describe('for anonymous users', function () {
it('render minimal menu', function () {
const scope = mockScope({
ui: {
leftMenuShown: true,
},
})
const scope = mockScope()
window.metaAttributesCache.set('ol-anonymous', true)
Object.assign(getMeta('ol-ExposedSettings'), { ieeeBrandId: 123 })
cy.mount(
<EditorProviders scope={scope}>
<EditorProviders scope={scope} layoutContext={{ leftMenuShown: true }}>
<EditorLeftMenu />
</EditorProviders>
)

View File

@@ -47,10 +47,5 @@ export const mockScope = (scope?: Scope) => ({
},
},
hasLintingError: false,
ui: {
view: 'editor',
pdfLayout: 'sideBySide',
leftMenuShown: false,
},
...scope,
})

View File

@@ -17,8 +17,4 @@ export const mockScope = () => ({
}),
},
hasLintingError: false,
ui: {
view: 'editor',
pdfLayout: 'sideBySide',
},
})

View File

@@ -9,9 +9,11 @@ import * as eventTracking from '@/infrastructure/event-tracking'
describe('<LayoutDropdownButton />', function () {
let openStub
let sendMBSpy
const defaultUi = {
const defaultLayout = {
pdfLayout: 'flat',
view: 'pdf',
chatIsOpen: false,
}
beforeEach(function () {
@@ -27,7 +29,9 @@ describe('<LayoutDropdownButton />', function () {
it('should mark current layout option as selected', async function () {
// Selected is aria-label, visually we show a checkmark
renderWithEditorContext(<LayoutDropdownButton />, { ui: defaultUi })
renderWithEditorContext(<LayoutDropdownButton />, {
layoutContext: defaultLayout,
})
screen.getByRole('button', { name: 'Layout' }).click()
@@ -69,7 +73,7 @@ describe('<LayoutDropdownButton />', function () {
it('should not select any option in history view', async function () {
// Selected is aria-label, visually we show a checkmark
renderWithEditorContext(<LayoutDropdownButton />, {
ui: { ...defaultUi, view: 'history' },
layoutContext: { ...defaultLayout, view: 'history' },
})
screen.getByRole('button', { name: 'Layout' }).click()
@@ -112,9 +116,10 @@ describe('<LayoutDropdownButton />', function () {
it('should treat file and editor views the same way', async function () {
// Selected is aria-label, visually we show a checkmark
renderWithEditorContext(<LayoutDropdownButton />, {
ui: {
layoutContext: {
pdfLayout: 'flat',
view: 'file',
chatIsOpen: false,
},
})
@@ -161,7 +166,7 @@ describe('<LayoutDropdownButton />', function () {
window.BroadcastChannel = originalBroadcastChannel || true // ensure that window.BroadcastChannel is truthy
renderWithEditorContext(<LayoutDropdownButton />, {
ui: { ...defaultUi, view: 'editor' },
layoutContext: { ...defaultLayout, view: 'editor' },
})
screen.getByRole('button', { name: 'Layout' }).click()
@@ -192,7 +197,7 @@ describe('<LayoutDropdownButton />', function () {
beforeEach(async function () {
window.metaAttributesCache.set('ol-detachRole', 'detacher')
renderWithEditorContext(<LayoutDropdownButton />, {
ui: { ...defaultUi, view: 'editor' },
layoutContext: { ...defaultLayout, view: 'editor' },
})
screen.getByRole('button', { name: 'Layout' }).click()

View File

@@ -93,6 +93,8 @@ const createInitialValue = () =>
pdfPreviewOpen: false,
projectSearchIsOpen: true,
setProjectSearchIsOpen: cy.stub(),
openFile: null,
setOpenFile: cy.stub(),
}) satisfies LayoutContextValue
const LayoutProvider: FC<React.PropsWithChildren> = ({ children }) => {

View File

@@ -14,11 +14,10 @@ import { withTestContainerErrorBoundary } from '../../../helpers/error-boundary'
const TestContainerWithoutErrorBoundary: FC<{
component: React.ReactNode
scope: Record<string, unknown>
props: Record<string, unknown>
}> = ({ component, scope, props }) => {
}> = ({ component, props }) => {
return (
<EditorProviders scope={scope} {...props}>
<EditorProviders {...props}>
<HistoryProvider>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<div className="history-react">{component}</div>
@@ -34,17 +33,12 @@ const TestContainer = withTestContainerErrorBoundary(
const mountWithEditorProviders = (
component: React.ReactNode,
scope: Record<string, unknown> = {},
props: Record<string, unknown> = {}
) => {
cy.mount(<TestContainer component={component} scope={scope} props={props} />)
cy.mount(<TestContainer component={component} props={props} />)
}
describe('change list (Bootstrap 5)', function () {
const scope = {
ui: { view: 'history', pdfLayout: 'sideBySide', chatOpen: true },
}
const waitForData = () => {
cy.wait('@updates')
cy.wait('@labels')
@@ -101,7 +95,8 @@ describe('change list (Bootstrap 5)', function () {
describe('tags', function () {
it('renders tags', function () {
mountWithEditorProviders(<ChangeList />, scope, {
mountWithEditorProviders(<ChangeList />, {
layoutContext: { view: 'history' },
user: {
id: USER_ID,
email: USER_EMAIL,
@@ -173,13 +168,18 @@ describe('change list (Bootstrap 5)', function () {
})
it('deletes tag', function () {
mountWithEditorProviders(<ChangeList />, scope, {
user: {
id: USER_ID,
email: USER_EMAIL,
isAdmin: true,
},
})
mountWithEditorProviders(
<ChangeList />,
{
layoutContext: { view: 'history' },
user: {
id: USER_ID,
email: USER_EMAIL,
isAdmin: true,
},
}
)
waitForData()
cy.findByLabelText(/all history/i).click({ force: true })
@@ -235,7 +235,8 @@ describe('change list (Bootstrap 5)', function () {
})
it('verifies that selecting the same list item will not trigger a new diff', function () {
mountWithEditorProviders(<ChangeList />, scope, {
mountWithEditorProviders(<ChangeList />, {
layoutContext: { view: 'history' },
user: {
id: USER_ID,
email: USER_EMAIL,
@@ -257,13 +258,18 @@ describe('change list (Bootstrap 5)', function () {
describe('all history', function () {
beforeEach(function () {
mountWithEditorProviders(<ChangeList />, scope, {
user: {
id: USER_ID,
email: USER_EMAIL,
isAdmin: true,
},
})
mountWithEditorProviders(
<ChangeList />,
{
layoutContext: { view: 'history' },
user: {
id: USER_ID,
email: USER_EMAIL,
isAdmin: true,
},
}
)
waitForData()
})
@@ -318,13 +324,18 @@ describe('change list (Bootstrap 5)', function () {
describe('labels only', function () {
beforeEach(function () {
mountWithEditorProviders(<ChangeList />, scope, {
user: {
id: USER_ID,
email: USER_EMAIL,
isAdmin: true,
},
})
mountWithEditorProviders(
<ChangeList />,
{
layoutContext: { view: 'history' },
user: {
id: USER_ID,
email: USER_EMAIL,
isAdmin: true,
},
}
)
waitForData()
cy.findByLabelText(/labels/i).click({ force: true })
})
@@ -399,13 +410,18 @@ describe('change list (Bootstrap 5)', function () {
describe('compare mode', function () {
beforeEach(function () {
mountWithEditorProviders(<ChangeList />, scope, {
user: {
id: USER_ID,
email: USER_EMAIL,
isAdmin: true,
},
})
mountWithEditorProviders(
<ChangeList />,
{
layoutContext: { view: 'history' },
user: {
id: USER_ID,
email: USER_EMAIL,
isAdmin: true,
},
}
)
waitForData()
})
@@ -435,13 +451,18 @@ describe('change list (Bootstrap 5)', function () {
describe('dropdown', function () {
beforeEach(function () {
mountWithEditorProviders(<ChangeList />, scope, {
user: {
id: USER_ID,
email: USER_EMAIL,
isAdmin: true,
},
})
mountWithEditorProviders(
<ChangeList />,
{
layoutContext: { view: 'history' },
user: {
id: USER_ID,
email: USER_EMAIL,
isAdmin: true,
},
}
)
waitForData()
})
@@ -610,21 +631,18 @@ describe('change list (Bootstrap 5)', function () {
})
it('shows non-owner paywall', function () {
const scope = {
ui: {
view: 'history',
pdfLayout: 'sideBySide',
chatOpen: true,
},
}
mountWithEditorProviders(
<ChangeList />,
mountWithEditorProviders(<ChangeList />, scope, {
user: {
id: USER_ID,
email: USER_EMAIL,
isAdmin: false,
},
})
{
layoutContext: { view: 'history' },
user: {
id: USER_ID,
email: USER_EMAIL,
isAdmin: false,
},
}
)
waitForData()
@@ -634,15 +652,8 @@ describe('change list (Bootstrap 5)', function () {
})
it('shows owner paywall', function () {
const scope = {
ui: {
view: 'history',
pdfLayout: 'sideBySide',
chatOpen: true,
},
}
mountWithEditorProviders(<ChangeList />, scope, {
mountWithEditorProviders(<ChangeList />, {
layoutContext: { view: 'history' },
user: {
id: USER_ID,
email: USER_EMAIL,
@@ -662,15 +673,8 @@ describe('change list (Bootstrap 5)', function () {
})
it('shows all labels in free tier', function () {
const scope = {
ui: {
view: 'history',
pdfLayout: 'sideBySide',
chatOpen: true,
},
}
mountWithEditorProviders(<ChangeList />, scope, {
mountWithEditorProviders(<ChangeList />, {
layoutContext: { view: 'history' },
user: {
id: USER_ID,
email: USER_EMAIL,

View File

@@ -5,14 +5,15 @@ import { Diff } from '../../../../../frontend/js/features/history/services/types
import { EditorProviders } from '../../../helpers/editor-providers'
import { FC } from 'react'
import { withTestContainerErrorBoundary } from '../../../helpers/error-boundary'
import { LayoutContextValue } from '@/shared/context/layout-context'
const TestContainerWithoutErrorBoundary: FC<{
scope: Record<string, unknown>
layoutContext: LayoutContextValue
diff: Diff
selection: HistoryContextValue['selection']
}> = ({ scope, diff, selection }) => {
}> = ({ diff, selection, layoutContext }) => {
return (
<EditorProviders scope={scope}>
<EditorProviders layoutContext={layoutContext}>
<HistoryProvider>
<div className="history-react">
<Toolbar diff={diff} selection={selection} />
@@ -27,10 +28,6 @@ const TestContainer = withTestContainerErrorBoundary(
)
describe('history toolbar', function () {
const editorProvidersScope = {
ui: { view: 'history', pdfLayout: 'sideBySide', chatOpen: true },
}
const diff: Diff = {
binary: false,
docDiff: {
@@ -81,7 +78,7 @@ describe('history toolbar', function () {
cy.mount(
<TestContainer
scope={editorProvidersScope}
layoutContext={{ view: 'history' }}
diff={diff}
selection={selection}
/>
@@ -129,7 +126,7 @@ describe('history toolbar', function () {
cy.mount(
<TestContainer
scope={editorProvidersScope}
layoutContext={{ view: 'history' }}
diff={diff}
selection={selection}
/>

View File

@@ -4,7 +4,9 @@ import SwitchToEditorButton from '@/features/pdf-preview/components/switch-to-ed
describe('<SwitchToEditorButton />', function () {
it('shows button in full screen pdf layout', function () {
cy.mount(
<EditorProviders ui={{ view: 'pdf', pdfLayout: 'flat', chatOpen: false }}>
<EditorProviders
layoutContext={{ view: 'pdf', pdfLayout: 'flat', chatIsOpen: false }}
>
<SwitchToEditorButton />
</EditorProviders>
)
@@ -15,7 +17,11 @@ describe('<SwitchToEditorButton />', function () {
it('does not show button in split screen layout', function () {
cy.mount(
<EditorProviders
ui={{ view: 'pdf', pdfLayout: 'sideBySide', chatOpen: false }}
layoutContext={{
view: 'pdf',
pdfLayout: 'sideBySide',
chatIsOpen: false,
}}
>
<SwitchToEditorButton />
</EditorProviders>
@@ -28,7 +34,13 @@ describe('<SwitchToEditorButton />', function () {
window.metaAttributesCache.set('ol-detachRole', 'detacher')
cy.mount(
<EditorProviders ui={{ view: 'pdf', pdfLayout: 'flat', chatOpen: false }}>
<EditorProviders
layoutContext={{
view: 'pdf',
pdfLayout: 'flat',
chatIsOpen: false,
}}
>
<SwitchToEditorButton />
</EditorProviders>
)

View File

@@ -5,7 +5,7 @@ describe('<SwitchToPDFButton />', function () {
it('shows button in full screen editor layout', function () {
cy.mount(
<EditorProviders
ui={{ view: 'editor', pdfLayout: 'flat', chatOpen: false }}
layoutContext={{ view: 'editor', pdfLayout: 'flat', chatIsOpen: false }}
>
<SwitchToPDFButton />
</EditorProviders>
@@ -17,7 +17,11 @@ describe('<SwitchToPDFButton />', function () {
it('does not show button in split screen layout', function () {
cy.mount(
<EditorProviders
ui={{ view: 'editor', pdfLayout: 'sideBySide', chatOpen: false }}
layoutContext={{
view: 'editor',
pdfLayout: 'sideBySide',
chatIsOpen: false,
}}
>
<SwitchToPDFButton />
</EditorProviders>
@@ -31,7 +35,7 @@ describe('<SwitchToPDFButton />', function () {
cy.mount(
<EditorProviders
ui={{ view: 'editor', pdfLayout: 'flat', chatOpen: false }}
layoutContext={{ view: 'editor', pdfLayout: 'flat', chatIsOpen: false }}
>
<SwitchToPDFButton />
</EditorProviders>

View File

@@ -81,9 +81,6 @@ export const mockScope = (
write: true,
...permissions,
},
ui: {
reviewPanelOpen: false,
},
toggleReviewPanel: cy.stub(),
toggleTrackChangesForEveryone: cy.stub(),
refreshResolvedCommentsDropdown: cy.stub(() => sleep(1000)),

View File

@@ -3,7 +3,7 @@
import { merge } from 'lodash'
import { SocketIOMock } from '@/ide/connection/SocketIoShim'
import { IdeContext } from '@/shared/context/ide-context'
import React, { useEffect, useState } from 'react'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import {
createReactScopeValueStore,
IdeReactContext,
@@ -12,6 +12,9 @@ import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter'
import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
import { ConnectionContext } from '@/features/ide-react/context/connection-context'
import { ReactContextRoot } from '@/features/ide-react/context/react-context-root'
import useEventListener from '@/shared/hooks/use-event-listener'
import useDetachLayout from '@/shared/hooks/use-detach-layout'
import { LayoutContext } from '@/shared/context/layout-context'
// these constants can be imported in tests instead of
// using magic strings
@@ -35,6 +38,22 @@ const defaultUserSettings = {
mathPreview: true,
}
/**
* @typedef {import('@/shared/context/layout-context').LayoutContextValue} LayoutContextValue
* @type Partial<LayoutContextValue>
*/
const layoutContextDefault = {
view: 'editor',
openFile: null,
chatIsOpen: true, // false in the application, true in tests
reviewPanelOpen: false,
miniReviewPanelVisible: false,
leftMenuShown: false,
projectSearchIsOpen: false,
pdfLayout: 'sideBySide',
loadingStyleSheet: false,
}
export function EditorProviders({
user = { id: USER_ID, email: USER_EMAIL },
projectId = PROJECT_ID,
@@ -68,7 +87,8 @@ export function EditorProviders({
fileRefs: [],
},
],
ui = { view: 'editor', pdfLayout: 'sideBySide', chatOpen: true },
/** @type {Partial<LayoutContext>} */
layoutContext = layoutContextDefault,
userSettings = {},
providers = {},
}) {
@@ -110,7 +130,6 @@ export function EditorProviders({
imageName,
compiler,
},
ui,
permissionsLevel,
},
defaultScope
@@ -125,6 +144,7 @@ export function EditorProviders({
providers={{
ConnectionProvider: makeConnectionProvider(socket),
IdeReactProvider: makeIdeReactProvider(scope, socket),
LayoutProvider: makeLayoutProvider(layoutContext),
...providers,
}}
>
@@ -183,7 +203,6 @@ const makeIdeReactProvider = (scope, socket) => {
scopeStore.set(key, value)
}
scopeStore.set('editor.sharejs_doc', scope.editor.sharejs_doc)
scopeStore.set('ui.chatOpen', scope.ui.chatOpen)
const scopeEventEmitter = new ReactScopeEventEmitter(
new IdeEventEmitter()
)
@@ -215,3 +234,110 @@ const makeIdeReactProvider = (scope, socket) => {
}
return IdeReactProvider
}
const makeLayoutProvider = layoutContextOverrides => {
const layout = {
...layoutContextDefault,
...layoutContextOverrides,
}
const LayoutProvider = ({ children }) => {
const [view, setView] = useState(layout.view)
const [openFile, setOpenFile] = useState(layout.openFile)
const [chatIsOpen, setChatIsOpen] = useState(layout.chatIsOpen)
const [reviewPanelOpen, setReviewPanelOpen] = useState(
layout.reviewPanelOpen
)
const [miniReviewPanelVisible, setMiniReviewPanelVisible] = useState(
layout.miniReviewPanelVisible
)
const [leftMenuShown, setLeftMenuShown] = useState(layout.leftMenuShown)
const [projectSearchIsOpen, setProjectSearchIsOpen] = useState(
layout.projectSearchIsOpen
)
const [pdfLayout, setPdfLayout] = useState(layout.pdfLayout)
const [loadingStyleSheet, setLoadingStyleSheet] = useState(
layout.loadingStyleSheet
)
useEventListener(
'ui.toggle-review-panel',
useCallback(() => {
setReviewPanelOpen(open => !open)
}, [setReviewPanelOpen])
)
const changeLayout = useCallback(
(newLayout, newView = 'editor') => {
setPdfLayout(newLayout)
setView(newLayout === 'sideBySide' ? 'editor' : newView)
},
[setPdfLayout, setView]
)
const {
reattach,
detach,
isLinked: detachIsLinked,
role: detachRole,
} = useDetachLayout()
const pdfPreviewOpen =
pdfLayout === 'sideBySide' || view === 'pdf' || detachRole === 'detacher'
const value = useMemo(
() => ({
reattach,
detach,
detachIsLinked,
detachRole,
changeLayout,
chatIsOpen,
leftMenuShown,
openFile,
pdfLayout,
pdfPreviewOpen,
projectSearchIsOpen,
setProjectSearchIsOpen,
reviewPanelOpen,
miniReviewPanelVisible,
loadingStyleSheet,
setChatIsOpen,
setLeftMenuShown,
setOpenFile,
setPdfLayout,
setReviewPanelOpen,
setMiniReviewPanelVisible,
setLoadingStyleSheet,
setView,
view,
}),
[
reattach,
detach,
detachIsLinked,
detachRole,
changeLayout,
chatIsOpen,
leftMenuShown,
openFile,
pdfLayout,
pdfPreviewOpen,
projectSearchIsOpen,
setProjectSearchIsOpen,
reviewPanelOpen,
miniReviewPanelVisible,
loadingStyleSheet,
setChatIsOpen,
setLeftMenuShown,
setOpenFile,
setPdfLayout,
setReviewPanelOpen,
setMiniReviewPanelVisible,
setLoadingStyleSheet,
setView,
view,
]
)
return (
<LayoutContext.Provider value={value}>{children}</LayoutContext.Provider>
)
}
return LayoutProvider
}