Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: set up sharing of react via module federation in studio #31129

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/app/cypress/e2e/cypress-in-cypress-component.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ describe('Cypress In Cypress CT', { viewportWidth: 1500, defaultCommandTimeout:

// Temporarily removed from CT since it doesn't work. Invert this assertion when completing https://github.com/cypress-io/cypress/issues/24549
cy.get('.hook-open-in-ide').should('not.exist')

cy.get('#unified-runner').should('have.attr', 'style', 'width: 500px; height: 500px; transform: scale(1); position: absolute; margin-left: 225px;')
})

it('navigation between specs and other parts of the app works', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/app/cypress/e2e/cypress-in-cypress-e2e.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ describe('Cypress In Cypress E2E', { viewportWidth: 1500, defaultCommandTimeout:
})

cy.get('.hook-open-in-ide').should('exist')

cy.get('#unified-runner').should('have.attr', 'style', 'width: 1000px; height: 660px; transform: scale(0.769697); position: absolute; margin-left: -25px;')
})

it('navigation between specs and other parts of the app works', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@headlessui/vue": "1.4.0",
"@iconify-json/mdi": "1.1.63",
"@intlify/unplugin-vue-i18n": "4.0.0",
"@module-federation/runtime": "^0.8.11",
"@module-federation/vite": "^1.2.2",
"@packages/data-context": "0.0.0-development",
"@packages/frontend-shared": "0.0.0-development",
Expand Down
21 changes: 20 additions & 1 deletion packages/app/src/runner/ResizablePanels.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const slotContents = {
panel1: () => <div class="h-full bg-emerald-100">panel1</div>,
panel2: () => <div class="h-full bg-purple-300">panel2</div>,
panel3: () => <div class="grow h-full bg-indigo-100">panel3</div>,
panel4: () => <div class="h-full bg-yellow-100">panel4</div>,
}

describe('<ResizablePanels />', { viewportWidth: 1500, defaultCommandTimeout: 4000 }, () => {
Expand Down Expand Up @@ -169,7 +170,7 @@ describe('<ResizablePanels />', { viewportWidth: 1500, defaultCommandTimeout: 40
assertWidth('panel3', 550)
})

it('Panel 3 resizes correctly when both panels are hidden', () => {
it('Panel 3 resizes correctly when all panels are hidden', () => {
cy.mount(() => (
<div class="h-screen">
<ResizablePanels
Expand All @@ -184,5 +185,23 @@ describe('<ResizablePanels />', { viewportWidth: 1500, defaultCommandTimeout: 40
cy.contains('panel2').should('not.be.visible')
assertWidth('panel3', 1500)
})

it('Panel 3 resizes correctly when panels 1 and 2 are hidden and panel 4 is shown', () => {
cy.mount(() => (
<div class="h-screen">
<ResizablePanels
maxTotalWidth={1500}
v-slots={slotContents}
showPanel1={false}
showPanel2={false}
showPanel4={true}
/>
</div>))

cy.contains('panel1').should('not.be.visible')
cy.contains('panel2').should('not.be.visible')
cy.contains('panel4').should('be.visible')
assertWidth('panel3', 1200)
})
})
})
34 changes: 31 additions & 3 deletions packages/app/src/runner/ResizablePanels.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@
:width="panel3width"
/>
</div>

<div
v-show="showPanel4"
data-cy="studio-panel"
class="h-full bg-gray-100 relative"
:style="{width: `${panel4Width}px`}"
>
<slot
name="panel4"
:width="panel4Width"
/>
</div>
</div>
</template>

Expand All @@ -72,6 +84,7 @@ import type { DraggablePanel } from './useRunnerStyle'
const props = withDefaults(defineProps<{
showPanel1?: boolean // specsList in runner
showPanel2?: boolean // reporter in runner
showPanel4?: boolean // studio in runner
initialPanel1Width?: number
initialPanel2Width?: number
minPanel1Width?: number
Expand All @@ -82,6 +95,7 @@ const props = withDefaults(defineProps<{
}>(), {
showPanel1: true,
showPanel2: true,
showPanel4: false,
initialPanel1Width: runnerConstants.defaultSpecListWidth,
initialPanel2Width: runnerConstants.defaultReporterWidth,
minPanel1Width: 200,
Expand Down Expand Up @@ -146,6 +160,14 @@ const maxPanel1Width = computed(() => {
return props.maxTotalWidth - unavailableWidth
})

const panel4Width = computed(() => {
if (!props.showPanel4) {
return 0
}

return runnerConstants.defaultStudioWidth
})

const panel1Width = computed(() => {
if (!props.showPanel1) {
return 0
Expand All @@ -155,13 +177,13 @@ const panel1Width = computed(() => {
})

const maxPanel2Width = computed(() => {
const unavailableWidth = panel1Width.value + props.minPanel3Width
const unavailableWidth = panel1Width.value + props.minPanel3Width + panel4Width.value

return props.maxTotalWidth - unavailableWidth
})

const panel3width = computed(() => {
const panel3SpaceAvailable = props.maxTotalWidth - panel1Width.value - panel2Width.value
const panel3SpaceAvailable = props.maxTotalWidth - panel1Width.value - panel2Width.value - panel4Width.value

// minimumWithMargin - if panel 3 would end up below the minimum allowed size
// due to window resizing, forcing the minimum width will create a horizontal scroll
Expand All @@ -176,7 +198,7 @@ function handleResizeEnd (panel: DraggablePanel) {
}

function isNewWidthAllowed (mouseClientX: number, panel: DraggablePanel) {
const isMaxWidthSmall = props.maxTotalWidth < (panel1Width.value + panel2Width.value + props.minPanel3Width)
const isMaxWidthSmall = props.maxTotalWidth < (panel1Width.value + panel2Width.value + props.minPanel3Width + panel4Width.value)
const fallbackWidth = 50

if (panel === 'panel1') {
Expand Down Expand Up @@ -206,6 +228,12 @@ watchEffect(() => {
} else if (props.showPanel1) {
emit('panelWidthUpdated', { panel: 'panel1', width: cachedPanel1Width.value })
}

if (!props.showPanel4) {
emit('panelWidthUpdated', { panel: 'panel4', width: 0 })
} else if (props.showPanel4) {
emit('panelWidthUpdated', { panel: 'panel4', width: panel4Width.value })
}
})

</script>
25 changes: 12 additions & 13 deletions packages/app/src/runner/SpecRunnerOpenMode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
:min-panel3-width="minWidths.aut"
:show-panel1="runnerUiStore.isSpecsListOpen && !screenshotStore.isScreenshotting"
:show-panel2="!screenshotStore.isScreenshotting && !hideCommandLog"
:show-panel4="studioStatus === 'INITIALIZED' && studioStore.isActive"
@resize-end="handleResizeEnd"
@panel-width-updated="handlePanelWidthUpdated"
>
Expand Down Expand Up @@ -96,12 +97,15 @@
/>
<ScreenshotHelperPixels />
</template>
<template #panel4>
<StudioPanel v-if="studioStatus === 'INITIALIZED' && studioStore.isActive" />
</template>
</ResizablePanels>
</AdjustRunnerStyleDuringScreenshot>
</template>

<script lang="ts" setup>
import { computed, onBeforeUnmount, onMounted, watchEffect } from 'vue'
import { computed, onBeforeUnmount, onMounted } from 'vue'
import { REPORTER_ID, RUNNER_ID } from './utils'
import InlineSpecList from '../specs/InlineSpecList.vue'
import { getAutIframeModel, getEventManager } from '.'
Expand Down Expand Up @@ -130,6 +134,7 @@ import { runnerConstants } from './runner-constants'
import StudioInstructionsModal from './studio/StudioInstructionsModal.vue'
import StudioSaveModal from './studio/StudioSaveModal.vue'
import { useStudioStore } from '../store/studio-store'
import StudioPanel from '../studio/StudioPanel.vue'

const {
preferredMinimumPanelWidth,
Expand All @@ -148,6 +153,7 @@ fragment SpecRunner_Preferences on Query {
autoScrollingEnabled
reporterWidth
specListWidth
studioWidth
}
}
}
Expand Down Expand Up @@ -220,6 +226,10 @@ const reporterWidthPreferences = computed(() => {
return props.gql.localSettings.preferences.reporterWidth ?? runnerUiStore.reporterWidth
})

const studioWidthPreferences = computed(() => {
return props.gql.localSettings.preferences.studioWidth ?? runnerUiStore.studioWidth
})

const isSpecsListOpenPreferences = computed(() => {
return props.gql.localSettings.preferences.isSpecsListOpen ?? false
})
Expand All @@ -245,6 +255,7 @@ if (!hideCommandLog) {
preferences.update('isSpecsListOpen', isSpecsListOpenPreferences.value)
preferences.update('reporterWidth', reporterWidthPreferences.value)
preferences.update('specListWidth', specsListWidthPreferences.value)
preferences.update('studioWidth', studioWidthPreferences.value)
// 👆 we must update these preferences before calling useRunnerStyle, to make sure that values from GQL
// will be available during the initial calculation that useRunnerStyle does
}
Expand Down Expand Up @@ -300,18 +311,6 @@ function openFile () {
})
}

watchEffect(() => {
if (studioStatus.value === 'INITIALIZED') {
import('app-studio').then(({ mountTestGenerationPanel }) => {
// eslint-disable-next-line no-console
console.log('Studio loaded', mountTestGenerationPanel)
}).catch((err) => {
// eslint-disable-next-line no-console
console.error('Error loading Studio', err)
})
}
})

onMounted(() => {
const eventManager = getEventManager()

Expand Down
1 change: 1 addition & 0 deletions packages/app/src/runner/runner-constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const runnerConstants = {
defaultSpecListWidth: 280,
defaultReporterWidth: 450,
defaultStudioWidth: 300,
preferredMinimumPanelWidth: 200,
absoluteAutMinimum: 100,
absoluteSpecListMinimum: 50,
Expand Down
12 changes: 9 additions & 3 deletions packages/app/src/runner/useRunnerStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useAutStore, useRunnerUiStore } from '../store'
import { useScreenshotStore } from '../store/screenshot-store'
import { runnerConstants } from './runner-constants'

export type ResizablePanelName = 'panel1' | 'panel2' | 'panel3'
export type ResizablePanelName = 'panel1' | 'panel2' | 'panel3' | 'panel4'

export type DraggablePanel = Exclude<ResizablePanelName, 'panel3'>

Expand All @@ -17,6 +17,7 @@ const autMargin = 16
// so that we only save to GQL when the resizing has ended
const reporterWidth = ref<number>(0)
const specListWidth = ref<number>(0)
const studioWidth = ref<number>(0)

export const useRunnerStyle = () => {
const { width: windowWidth, height: windowHeight } = useWindowSize()
Expand All @@ -26,10 +27,11 @@ export const useRunnerStyle = () => {
const screenshotStore = useScreenshotStore()
const autStore = useAutStore()

const { reporterWidth: initialReporterWidth, specListWidth: initialSpecsListWidth } = runnerUIStore
const { reporterWidth: initialReporterWidth, specListWidth: initialSpecsListWidth, studioWidth: initialStudioWidth } = runnerUIStore

reporterWidth.value = initialReporterWidth
specListWidth.value = initialSpecsListWidth
studioWidth.value = initialStudioWidth

const containerWidth = computed(() => {
const miscBorders = 4
Expand All @@ -47,7 +49,7 @@ export const useRunnerStyle = () => {
nonAutWidth += (autMargin * 2)
}

nonAutWidth += reporterWidth.value + specListWidth.value + miscBorders
nonAutWidth += reporterWidth.value + specListWidth.value + studioWidth.value + miscBorders
}

const containerWidth = windowWidth.value - nonAutWidth
Expand Down Expand Up @@ -117,6 +119,8 @@ export function useResizablePanels () {
const handleResizeEnd = async (panel: DraggablePanel) => {
if (panel === 'panel1') {
await preferences.update('specListWidth', specListWidth.value)
} else if (panel === 'panel4') {
await preferences.update('studioWidth', studioWidth.value)
} else {
await preferences.update('reporterWidth', reporterWidth.value)
}
Expand All @@ -125,6 +129,8 @@ export function useResizablePanels () {
const handlePanelWidthUpdated = ({ panel, width }: { panel: DraggablePanel, width: number }) => {
if (panel === 'panel1') {
specListWidth.value = width
} else if (panel === 'panel4') {
studioWidth.value = width
} else {
reporterWidth.value = width
}
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/store/runner-ui-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface RunnerUiState {
isSpecsListOpen: boolean
specListWidth: number
reporterWidth: number
studioWidth: number
automationStatus: AutomationStatus
randomString: string
hideCommandLog: boolean
Expand All @@ -40,6 +41,7 @@ export const useRunnerUiStore = defineStore({
isSpecsListOpen: false,
specListWidth: runnerConstants.defaultSpecListWidth,
reporterWidth: runnerConstants.defaultReporterWidth,
studioWidth: runnerConstants.defaultStudioWidth,
automationStatus: automation.CONNECTING,
randomString: `${Math.random()}`,
hideCommandLog: window.__CYPRESS_CONFIG__.hideCommandLog,
Expand Down
78 changes: 78 additions & 0 deletions packages/app/src/studio/StudioPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<template>
<div v-if="error">
Error loading the panel
</div>
<div
v-else
ref="root"
>
Loading the panel...
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUpdated, onBeforeUnmount } from 'vue'
import { init, loadRemote } from '@module-federation/runtime'

const root = ref<HTMLElement | null>(null)
const error = ref<string | null>(null)
const Panel = ref<ReturnType<any> | null>(null)

const updateComponent = () => {
if (!Panel.value || !!error.value) {
return
}

const panel = window.UnifiedRunner.React.createElement(Panel.value)

window.UnifiedRunner.ReactDOM.createRoot(root.value).render(panel)
}

const unmountReactComponent = () => {
if (!Panel.value || !root.value) {
return
}

window.UnifiedRunner.ReactDOM.unmountComponentAtNode(root.value)
}

init({
remotes: [{
alias: 'app-studio',
type: 'module',
name: 'app-studio',
entryGlobalName: 'app-studio',
entry: '/__cypress-studio/app-studio.js',
shareScope: 'default',
}],
shared: {
react: {
scope: 'default',
version: '18.3.1',
lib: () => window.UnifiedRunner.React,
shareConfig: {
singleton: true,
requiredVersion: '^18.3.1',
},
},
},
name: 'app',
})

onMounted(updateComponent)
onUpdated(updateComponent)
onBeforeUnmount(unmountReactComponent)

loadRemote<typeof import('app-studio')>('app-studio').then((module) => {
if (!module?.default) {
error.value = 'The panel was not loaded successfully'

return
}

Panel.value = module.default
updateComponent()
}).catch((e) => {
error.value = e.message
})

</script>
Loading
Loading