Skip to content

Commit 5ebb3ab

Browse files
authored
fix(browser): restore the original viewport when unselecting the preset viewport (#5821)
1 parent 2e874f8 commit 5ebb3ab

File tree

12 files changed

+114
-125
lines changed

12 files changed

+114
-125
lines changed

packages/browser/context.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,5 @@ export const page: {
8787
/**
8888
* Change the size of iframe's viewport.
8989
*/
90-
viewport: (width: number | string, height: number | string) => Promise<void>
90+
viewport: (width: number, height: number) => Promise<void>
9191
}

packages/browser/src/client/orchestrator.ts

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,10 @@ function createIframe(container: HTMLDivElement, file: string) {
3838
iframe.setAttribute('src', `${url.pathname}__vitest_test__/__test__/${encodeURIComponent(file)}`)
3939
iframe.setAttribute('data-vitest', 'true')
4040

41-
const config = getConfig().browser
42-
iframe.style.width = `${config.viewport.width}px`
43-
iframe.style.height = `${config.viewport.height}px`
44-
4541
iframe.style.display = 'block'
4642
iframe.style.border = 'none'
43+
iframe.style.zIndex = '1'
44+
iframe.style.position = 'relative'
4745
iframe.setAttribute('allowfullscreen', 'true')
4846
iframe.setAttribute('allow', 'clipboard-write;')
4947

@@ -71,8 +69,8 @@ interface IframeErrorEvent {
7169

7270
interface IframeViewportEvent {
7371
type: 'viewport'
74-
width: number | string
75-
height: number | string
72+
width: number
73+
height: number
7674
id: string
7775
}
7876

@@ -111,8 +109,6 @@ client.ws.addEventListener('open', async () => {
111109
switch (e.data.type) {
112110
case 'viewport': {
113111
const { width, height, id } = e.data
114-
const widthStr = typeof width === 'number' ? `${width}px` : width
115-
const heightStr = typeof height === 'number' ? `${height}px` : height
116112
const iframe = iframes.get(id)
117113
if (!iframe) {
118114
const error = new Error(`Cannot find iframe with id ${id}`)
@@ -123,13 +119,7 @@ client.ws.addEventListener('open', async () => {
123119
}, 'Teardown Error')
124120
return
125121
}
126-
iframe.style.width = widthStr
127-
iframe.style.height = heightStr
128-
const ui = getUiAPI()
129-
if (ui) {
130-
await new Promise(r => requestAnimationFrame(r))
131-
ui.recalculateDetailPanels()
132-
}
122+
await setIframeViewport(iframe, width, height)
133123
channel.postMessage({ type: 'viewport:done', id })
134124
break
135125
}
@@ -143,7 +133,7 @@ client.ws.addEventListener('open', async () => {
143133
// so we only select it when the run is done
144134
if (ui && filenames.length > 1) {
145135
const id = generateFileId(filenames[filenames.length - 1])
146-
ui.setCurrentById(id)
136+
ui.setCurrentFileId(id)
147137
}
148138
await done()
149139
}
@@ -189,37 +179,26 @@ async function createTesters(testFiles: string[]) {
189179
container.className = 'scrolls'
190180
container.textContent = ''
191181
}
182+
const { width, height } = config.browser.viewport
192183

193184
if (config.isolate === false) {
194-
createIframe(
185+
const iframe = createIframe(
195186
container,
196187
ID_ALL,
197188
)
198189

199-
const ui = getUiAPI()
200-
201-
if (ui) {
202-
await new Promise(r => requestAnimationFrame(r))
203-
ui.recalculateDetailPanels()
204-
}
190+
await setIframeViewport(iframe, width, height)
205191
}
206192
else {
207193
// otherwise, we need to wait for each iframe to finish before creating the next one
208194
// this is the most stable way to run tests in the browser
209195
for (const file of testFiles) {
210-
const ui = getUiAPI()
211-
212-
createIframe(
196+
const iframe = createIframe(
213197
container,
214198
file,
215199
)
216200

217-
if (ui) {
218-
const id = generateFileId(file)
219-
ui.setCurrentById(id)
220-
await new Promise(r => requestAnimationFrame(r))
221-
ui.recalculateDetailPanels()
222-
}
201+
await setIframeViewport(iframe, width, height)
223202

224203
await new Promise<void>((resolve) => {
225204
channel.addEventListener('message', function handler(e: MessageEvent<IframeChannelEvent>) {
@@ -240,3 +219,14 @@ function generateFileId(file: string) {
240219
const path = relative(config.root, file)
241220
return generateHash(`${path}${project}`)
242221
}
222+
223+
async function setIframeViewport(iframe: HTMLIFrameElement, width: number, height: number) {
224+
const ui = getUiAPI()
225+
if (ui) {
226+
await ui.setIframeViewport(width, height)
227+
}
228+
else {
229+
iframe.style.width = `${width}px`
230+
iframe.style.height = `${height}px`
231+
}
232+
}

packages/browser/src/client/ui.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
1-
import type { File } from '@vitest/runner'
1+
import type { BrowserUI } from 'vitest'
22

3-
interface UiAPI {
4-
currentModule: File
5-
setCurrentById: (fileId: string) => void
6-
resetDetailSizes: () => void
7-
recalculateDetailPanels: () => void
8-
}
9-
10-
export function getUiAPI(): UiAPI | undefined {
3+
export function getUiAPI(): BrowserUI | undefined {
114
// @ts-expect-error not typed global
125
return window.__vitest_ui_api__
136
}

packages/browser/src/node/plugins/pluginContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const page = {
5959
viewport(width, height) {
6060
const id = __vitest_browser_runner__.iframeId
6161
channel.postMessage({ type: 'viewport', width, height, id })
62-
return new Promise((resolve) => {
62+
return new Promise((resolve, reject) => {
6363
channel.addEventListener('message', function handler(e) {
6464
if (e.data.type === 'viewport:done' && e.data.id === id) {
6565
channel.removeEventListener('message', handler)

packages/ui/client/auto-imports.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ declare global {
3535
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
3636
const currentModule: typeof import('./composables/navigation')['currentModule']
3737
const customRef: typeof import('vue')['customRef']
38+
const customViewport: typeof import('./composables/browser')['customViewport']
3839
const dashboardVisible: typeof import('./composables/navigation')['dashboardVisible']
3940
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
4041
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
@@ -53,6 +54,7 @@ declare global {
5354
const filesSuccess: typeof import('./composables/summary')['filesSuccess']
5455
const filesTodo: typeof import('./composables/summary')['filesTodo']
5556
const finished: typeof import('./composables/summary')['finished']
57+
const getCurrentBrowserIframe: typeof import('./composables/api')['getCurrentBrowserIframe']
5658
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
5759
const getCurrentScope: typeof import('vue')['getCurrentScope']
5860
const getModuleGraph: typeof import('./composables/module-graph')['getModuleGraph']
@@ -76,6 +78,7 @@ declare global {
7678
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
7779
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
7880
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
81+
const onBrowserPanelResizing: typeof import('./composables/browser')['onBrowserPanelResizing']
7982
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
8083
const onDeactivated: typeof import('vue')['onDeactivated']
8184
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
@@ -115,6 +118,7 @@ declare global {
115118
const resolveComponent: typeof import('vue')['resolveComponent']
116119
const resolveRef: typeof import('@vueuse/core')['resolveRef']
117120
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
121+
const setIframeViewport: typeof import('./composables/api')['setIframeViewport']
118122
const shallowReactive: typeof import('vue')['shallowReactive']
119123
const shallowReadonly: typeof import('vue')['shallowReadonly']
120124
const shallowRef: typeof import('vue')['shallowRef']
@@ -315,6 +319,7 @@ declare global {
315319
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
316320
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
317321
const viewMode: typeof import('./composables/params')['viewMode']
322+
const viewport: typeof import('./composables/browser')['viewport']
318323
const watch: typeof import('vue')['watch']
319324
const watchArray: typeof import('@vueuse/core')['watchArray']
320325
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']

packages/ui/client/components/BrowserIframe.vue

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,34 @@
11
<script setup lang="ts">
2-
import { useResizing } from '~/composables/browser'
2+
import { viewport, customViewport } from '~/composables/browser'
3+
import type { ViewportSize } from '~/composables/browser'
4+
import { setIframeViewport, getCurrentBrowserIframe } from '~/composables/api'
35
4-
type ViewportSize = 'small-mobile' | 'large-mobile' | 'tablet' | 'custom'
5-
6-
const sizes: Record<ViewportSize, [width: string, height: string]> = {
6+
const sizes: Record<ViewportSize, [width: string, height: string] | null> = {
77
'small-mobile': ['320px', '568px'],
88
'large-mobile': ['414px', '896px'],
99
tablet: ['834px', '1112px'],
10-
custom: ['100%', '100%'],
10+
full: ['100%', '100%'],
11+
// should not be used manually, this is just
12+
// a fallback for the case when the viewport is not set correctly
13+
custom: null,
1114
}
1215
13-
const testerRef = ref<HTMLDivElement | undefined>()
14-
const viewport = ref<ViewportSize>('custom')
15-
16-
const { recalculateDetailPanels } = useResizing(testerRef)
17-
1816
async function changeViewport(name: ViewportSize) {
1917
if (viewport.value === name) {
20-
viewport.value = 'custom'
18+
viewport.value = customViewport.value ? 'custom' : 'full'
2119
} else {
2220
viewport.value = name
2321
}
2422
25-
const iframe = document.querySelector<HTMLIFrameElement>('#tester-ui iframe[data-vitest]')
23+
const iframe = getCurrentBrowserIframe()
2624
if (!iframe) {
2725
console.warn('Iframe not found')
2826
return
2927
}
3028
31-
const [width, height] = sizes[viewport.value]
32-
33-
iframe.style.width = width
34-
iframe.style.height = height
29+
const [width, height] = sizes[viewport.value] || customViewport.value || sizes.full
3530
36-
await new Promise(r => requestAnimationFrame(r))
37-
38-
recalculateDetailPanels()
31+
await setIframeViewport(width, height)
3932
}
4033
</script>
4134

@@ -68,6 +61,13 @@ async function changeViewport(name: ViewportSize) {
6861
border="b-2 base"
6962
>
7063
<!-- TODO: these are only for preview (thank you Storybook!), we need to support more different and custom sizes (as a dropdown) -->
64+
<IconButton
65+
v-tooltip.bottom="'Flexible'"
66+
title="Flexible"
67+
icon="i-carbon:fit-to-screen"
68+
:active="viewport === 'full'"
69+
@click="changeViewport('full')"
70+
/>
7171
<IconButton
7272
v-tooltip.bottom="'Small mobile'"
7373
title="Small mobile"
@@ -91,7 +91,7 @@ async function changeViewport(name: ViewportSize) {
9191
/>
9292
</div>
9393
<div flex-auto class="scrolls">
94-
<div id="tester-ui" ref="testerRef" class="flex h-full justify-center items-center font-light op70" style="overflow: auto; width: 100%; height: 100%">
94+
<div id="tester-ui" class="flex h-full justify-center items-center font-light op70" style="overflow: auto; width: 100%; height: 100%">
9595
Select a test to run
9696
</div>
9797
</div>

packages/ui/client/composables/api.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { BrowserUI } from 'vitest'
2+
import { findById } from './client'
3+
import { customViewport, viewport } from './browser'
4+
import { detailSizes } from '~/composables/navigation'
5+
6+
const ui: BrowserUI = {
7+
setCurrentFileId(fileId: string) {
8+
activeFileId.value = fileId
9+
currentModule.value = findById(fileId)
10+
showDashboard(false)
11+
},
12+
async setIframeViewport(width: number, height: number) {
13+
// reset the button before setting a custom viewport
14+
viewport.value = 'custom'
15+
customViewport.value = [width, height]
16+
await setIframeViewport(width, height)
17+
},
18+
}
19+
20+
// @ts-expect-error not typed global
21+
window.__vitest_ui_api__ = ui
22+
23+
function recalculateDetailPanels() {
24+
const iframe = getCurrentBrowserIframe()
25+
const panel = document.querySelector<HTMLDivElement>('#details-splitpanes')!
26+
const panelWidth = panel.clientWidth
27+
const iframeWidth = iframe.clientWidth
28+
const iframePercent = Math.min((iframeWidth / panelWidth) * 100, 95)
29+
const detailsPercent = 100 - iframePercent
30+
detailSizes.value = [iframePercent, detailsPercent]
31+
}
32+
33+
export function getCurrentBrowserIframe() {
34+
return document.querySelector<HTMLIFrameElement>('#tester-ui iframe[data-vitest]')!
35+
}
36+
37+
export async function setIframeViewport(width: number | string, height: number | string) {
38+
const iframe = getCurrentBrowserIframe()
39+
// change the viewport of the iframe
40+
iframe.style.width = typeof width === 'string' ? width : `${width}px`
41+
iframe.style.height = typeof height === 'string' ? height : `${height}px`
42+
// wait until it renders the new size and resize the panel to make the iframe visible
43+
// this will not make it fully visible if viewport is too wide, but it's better than nothing
44+
await new Promise(r => requestAnimationFrame(r))
45+
recalculateDetailPanels()
46+
}
Lines changed: 8 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,11 @@
1-
import type { Ref } from 'vue'
2-
import { detailSizes } from '~/composables/navigation'
1+
export type ViewportSize = 'small-mobile' | 'large-mobile' | 'tablet' | 'full' | 'custom'
2+
export const viewport = ref<ViewportSize>('full')
3+
export const customViewport = ref<[number, number]>()
34

4-
type ResizingListener = (isResizing: boolean) => void
5+
export function onBrowserPanelResizing(isResizing: boolean) {
6+
const tester = document.querySelector<HTMLDivElement>('#tester-ui')
7+
if (!tester)
8+
return
59

6-
const resizingListeners = new Set<ResizingListener>()
7-
8-
export function recalculateDetailPanels() {
9-
const iframe = document.querySelector('#tester-ui iframe[data-vitest]')!
10-
const panel = document.querySelector('#details-splitpanes')!
11-
const panelWidth = panel.clientWidth
12-
const iframeWidth = iframe.clientWidth
13-
const iframePercent = Math.min((iframeWidth / panelWidth) * 100, 95)
14-
const detailsPercent = 100 - iframePercent
15-
detailSizes.value = [iframePercent, detailsPercent]
16-
}
17-
18-
export function useResizing(testerRef: Ref<HTMLDivElement | undefined>) {
19-
function onResizing(isResizing: boolean) {
20-
const tester = testerRef.value
21-
if (!tester)
22-
return
23-
24-
tester.style.pointerEvents = isResizing ? 'none' : ''
25-
}
26-
27-
onMounted(() => {
28-
resizingListeners.add(onResizing)
29-
})
30-
31-
onUnmounted(() => {
32-
resizingListeners.delete(onResizing)
33-
})
34-
35-
return { recalculateDetailPanels }
36-
}
37-
38-
export function useNotifyResizing() {
39-
function notifyResizing(isResizing: boolean) {
40-
for (const listener of resizingListeners)
41-
listener(isResizing)
42-
}
43-
44-
return { notifyResizing }
10+
tester.style.pointerEvents = isResizing ? 'none' : ''
4511
}

packages/ui/client/composables/navigation.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,6 @@ export const detailSizes = useLocalStorage<[left: number, right: number]>('vites
1616
initOnMounted: true,
1717
})
1818

19-
// @ts-expect-error not typed global
20-
window.__vitest_ui_api__ = {
21-
get currentModule() {
22-
return currentModule.value
23-
},
24-
setCurrentById(fileId: string) {
25-
activeFileId.value = fileId
26-
currentModule.value = findById(fileId)
27-
showDashboard(false)
28-
},
29-
resetDetailSizes() {
30-
detailSizes.value = [33, 67]
31-
},
32-
recalculateDetailPanels,
33-
}
3419
export const openedTreeItems = useLocalStorage<string[]>('vitest-ui_task-tree-opened', [])
3520
// TODO
3621
// For html report preview, "coverage.reportsDirectory" must be explicitly set as a subdirectory of html report.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy