Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ Vitest is a next-generation testing framework powered by Vite. This is a monorep
- Main docs in `docs/` directory
- Built with `pnpm docs:build`
- Local dev server: `pnpm docs`
- When adding cli options, run `pnpm -C docs run cli-table` to update the cli-generated.md file

## Dependencies and Tools

Expand Down
5 changes: 5 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ To develop and test `vitest` package:

> 💡 If you use VS Code, you can hit `⇧ ⌘ B` or `Ctrl + Shift + B` to launch all the necessary dev tasks.

### UI Development

If you want to improve Vitest Browser Mode, see the [Browser Mode development guide](./packages/ui/README.md) for setup instructions and development workflow.

## Debugging

### VS Code
Expand Down Expand Up @@ -74,6 +78,7 @@ VITE_NODE_DEPS_MODULE_DIRECTORIES=/node_modules/,/packages/

- Add accompanying test case.
- Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it.
- When adding cli options, run `pnpm -C docs run cli-table` to update the cli-generated.md file

- If fixing bug:

Expand Down
4 changes: 4 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,10 @@ export default ({ mode }: { mode: string }) => {
text: 'browser.ui',
link: '/config/browser/ui',
},
{
text: 'browser.detailsPanelPosition',
link: '/config/browser/detailspanelposition',
},
{
text: 'browser.viewport',
link: '/config/browser/viewport',
Expand Down
9 changes: 9 additions & 0 deletions docs/config/browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ List of available `browser` options:
- [`browser.screenshotDirectory`](#browser-screenshotdirectory)
- [`browser.screenshotFailures`](#browser-screenshotfailures)
- [`browser.provider`](#browser-provider)
- [`browser.detailsPanelPosition`](#browser-detailspanelposition)

Under the hood, Vitest transforms these instances into separate [test projects](/api/advanced/test-project) sharing a single Vite server for better caching performance.

Expand Down Expand Up @@ -220,6 +221,14 @@ export interface BrowserProvider {

Should Vitest UI be injected into the page. By default, injects UI iframe during development.

## browser.detailsPanelPosition

- **Type:** `'right' | 'bottom'`
- **Default:** `'right'`
- **CLI:** `--browser.detailsPanelPosition=bottom`, `--browser.detailsPanelPosition=right`

Controls the default position of the details panel in the Vitest UI when running browser tests. See [`browser.detailsPanelPosition`](/config/browser/detailspanelposition) for more details.

## browser.viewport

- **Type:** `{ width, height }`
Expand Down
28 changes: 28 additions & 0 deletions docs/config/browser/detailspanelposition.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
title: browser.detailsPanelPosition | Config
outline: deep
---

# browser.detailsPanelPosition

- **Type:** `'right' | 'bottom'`
- **Default:** `'right'`
- **CLI:** `--browser.detailsPanelPosition=bottom`, `--browser.detailsPanelPosition=right`

Controls the default position of the details panel in the Vitest UI when running browser tests.

- `'right'` - Shows the details panel on the right side with a horizontal split between the browser viewport and the details panel.
- `'bottom'` - Shows the details panel at the bottom with a vertical split between the browser viewport and the details panel.

```ts [vitest.config.ts]
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
browser: {
enabled: true,
detailsPanelPosition: 'bottom', // or 'right'
},
},
})
```
7 changes: 7 additions & 0 deletions docs/guide/cli-generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,13 @@ Run every browser test file in isolation. To disable isolation, use `--browser.i

Show Vitest UI when running tests (default: `!process.env.CI`)

### browser.detailsPanelPosition

- **CLI:** `--browser.detailsPanelPosition <position>`
- **Config:** [browser.detailsPanelPosition](/config/browser/detailspanelposition)

Default position for the details panel in browser mode. Either `right` (horizontal split) or `bottom` (vertical split) (default: `right`)

### browser.fileParallelism

- **CLI:** `--browser.fileParallelism`
Expand Down
4 changes: 3 additions & 1 deletion packages/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ nr test --api=3200

Open the browser at the URL printed by the first command. For example, `http://localhost:5173/`. If you see a connection error, it means the port is specified incorrectly.

To preview the browser tab, uncomment the "browser-dev-preview" plugin in `vite.config.ts`. To configure the browser state, update the `__vitest_browser_runner__` object in `browser.dev.js`.
To preview the browser tab, uncomment the "browser-dev-preview" plugin in `vite.config.ts`.

To configure the browser state, update the `__vitest_browser_runner__` object in `browser.dev.js`.
20 changes: 6 additions & 14 deletions packages/ui/client/components/BrowserIframe.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { computed } from 'vue'
import { viewport } from '~/composables/browser'
import { browserState } from '~/composables/client'
import {
hideRightPanel,
detailsPanelVisible,
detailsPosition,
panels,
showNavigationPanel,
showRightPanel,
updateBrowserPanel,
} from '~/composables/navigation'
import IconButton from './IconButton.vue'
Expand Down Expand Up @@ -86,19 +86,11 @@ const marginLeft = computed(() => {
<div class="i-carbon-content-delivery-network" />
<span pl-1 font-bold text-sm flex-auto ws-nowrap overflow-hidden truncate>Browser UI</span>
<IconButton
v-show="panels.details.main > 0"
v-tooltip.bottom="'Hide Right Panel'"
title="Hide Right Panel"
v-show="detailsPosition === 'right' && !detailsPanelVisible"
v-tooltip.bottom="'Show Details Panel'"
title="Show Details Panel"
icon="i-carbon:side-panel-close"
rotate-180
@click="hideRightPanel()"
/>
<IconButton
v-show="panels.details.main === 0"
v-tooltip.bottom="'Show Right Panel'"
title="Show Right Panel"
icon="i-carbon:side-panel-close"
@click="showRightPanel()"
@click="detailsPanelVisible = true"
/>
</div>
<div p="l3 y2 r2" flex="~ gap-2" items-center bg-header border="b-2 base">
Expand Down
16 changes: 16 additions & 0 deletions packages/ui/client/components/ClosedDetailsHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script setup lang="ts">
import DetailsHeaderButtons from '~/components/DetailsHeaderButtons.vue'
</script>

<template>
<div
p="2"
flex="~ gap-2"
items-center
bg-header
border="b base"
>
<div flex-1 />
<DetailsHeaderButtons />
</div>
</template>
4 changes: 4 additions & 0 deletions packages/ui/client/components/Coverage.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<script setup lang="ts">
import DetailsHeaderButtons from '~/components/DetailsHeaderButtons.vue'
import { browserState } from '~/composables/client'

defineProps<{
src: string
}>()
Expand All @@ -9,6 +12,7 @@ defineProps<{
<div p="3" h-10 flex="~ gap-2" items-center bg-header border="b base">
<div class="i-carbon:folder-details-reference" />
<span pl-1 font-bold text-sm flex-auto ws-nowrap overflow-hidden truncate>Coverage</span>
<DetailsHeaderButtons v-if="browserState" />
</div>
<div flex-auto py-1 bg-white>
<iframe id="vitest-ui-coverage" :src="src" />
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/client/components/Dashboard.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script setup lang="ts">
import DetailsHeaderButtons from '~/components/DetailsHeaderButtons.vue'
import { browserState } from '~/composables/client'
import TestsFilesContainer from './dashboard/TestsFilesContainer.vue'
</script>

Expand All @@ -7,6 +9,7 @@ import TestsFilesContainer from './dashboard/TestsFilesContainer.vue'
<div p="3" h-10 flex="~ gap-2" items-center bg-header border="b base">
<div class="i-carbon-dashboard" />
<span pl-1 font-bold text-sm flex-auto ws-nowrap overflow-hidden truncate>Dashboard</span>
<DetailsHeaderButtons v-if="browserState" />
</div>
<div class="scrolls" flex-auto py-1>
<TestsFilesContainer />
Expand Down
46 changes: 46 additions & 0 deletions packages/ui/client/components/DetailsHeaderButtons.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script setup lang="ts">
import IconButton from '~/components/IconButton.vue'
import {
detailsPanelVisible,
detailsPosition,
hideDetailsPanel,
showDetailsPanel,
toggleDetailsPosition,
} from '~/composables/navigation'

function getDetailsPanelToggleRotation(action: 'show' | 'hide') {
// `i-carbon:side-panel-close` is treated as "pointing right" by default.
// We rotate it based on where the details panel is positioned.
if (detailsPosition.value === 'right') {
return action === 'hide' ? 'rotate-180' : ''
}
// detailsPosition === 'bottom'
return action === 'hide' ? '-rotate-90' : 'rotate-90'
}
</script>

<template>
<IconButton
v-tooltip.bottom="`Switch panel position (${detailsPosition === 'bottom' ? 'right' : 'bottom'})`"
:title="`Switch panel position (${detailsPosition === 'bottom' ? 'right' : 'bottom'})`"
icon="i-carbon-split-screen"
:class="{ 'rotate-90': detailsPosition === 'right' }"
@click="toggleDetailsPosition"
/>
<IconButton
v-if="detailsPanelVisible"
v-tooltip.bottom="detailsPosition === 'right' ? 'Hide Right Panel' : 'Hide Bottom Panel'"
:title="detailsPosition === 'right' ? 'Hide Right Panel' : 'Hide Bottom Panel'"
icon="i-carbon:side-panel-close"
:class="getDetailsPanelToggleRotation('hide')"
@click="hideDetailsPanel"
/>
<IconButton
v-show="!detailsPanelVisible"
v-tooltip.bottom="detailsPosition === 'right' ? 'Show Right Panel' : 'Show Bottom Panel'"
:title="detailsPosition === 'right' ? 'Show Right Panel' : 'Show Bottom Panel'"
icon="i-carbon:side-panel-close"
:class="getDetailsPanelToggleRotation('show')"
@click="showDetailsPanel"
/>
</template>
2 changes: 2 additions & 0 deletions packages/ui/client/components/FileDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Params } from '~/composables/params'
import { debouncedWatch } from '@vueuse/core'
import { toJSON } from 'flatted'
import { computed, nextTick, ref } from 'vue'
import DetailsHeaderButtons from '~/components/DetailsHeaderButtons.vue'
import {
browserState,
client,
Expand Down Expand Up @@ -241,6 +242,7 @@ const tags = computed(() => {
:disabled="!current?.filepath"
@click="open"
/>
<DetailsHeaderButtons v-if="browserState" />
</div>
</div>
<div flex="~" items-center bg-header border="b-2 base" text-sm h-41px>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ const open = ref(true)
<template>
<div
:open="open"
class="details-panel"
data-testid="details-panel"
class="results-panel"
data-testid="results-panel"
@toggle="open = ($event.target as any).open"
>
<div
Expand All @@ -38,7 +38,7 @@ const open = ref(true)
</template>

<style>
.details-panel {
.results-panel {
user-select: none;
width: 100%;
}
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/client/components/explorer/Explorer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { availableProjects, config } from '~/composables/client'
import { useSearch } from '~/composables/explorer/search'
import { ALL_PROJECTS, projectSort } from '~/composables/explorer/state'
import { activeFileId, selectedTest } from '~/composables/params'
import DetailsPanel from '../DetailsPanel.vue'
import FilterStatus from '../FilterStatus.vue'
import IconButton from '../IconButton.vue'
import ResultsPanel from '../ResultsPanel.vue'
import ExplorerItem from './ExplorerItem.vue'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'

Expand Down Expand Up @@ -236,7 +236,7 @@ useResizeObserver(() => testExplorerRef.value, ([{ contentRect }]) => {
</div>
</div>
<div class="scrolls" flex-auto py-1 @scroll.passive="hideAllPoppers">
<DetailsPanel>
<ResultsPanel>
<template v-if="initialized" #summary>
<div grid="~ items-center gap-x-1 cols-[auto_min-content_auto] rows-[min-content_min-content]">
<span text-red5>
Expand Down Expand Up @@ -342,7 +342,7 @@ useResizeObserver(() => testExplorerRef.value, ([{ contentRect }]) => {
</template>
</RecycleScroller>
</template>
</DetailsPanel>
</ResultsPanel>
</div>
</div>
</template>
51 changes: 35 additions & 16 deletions packages/ui/client/composables/navigation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { File, Task } from '@vitest/runner'
import type { Params } from './params'
import { useLocalStorage, watchOnce } from '@vueuse/core'
import { computed, reactive, ref, watch } from 'vue'
import { computed, nextTick, reactive, ref, watch } from 'vue'
import { viewport } from './browser'
import { browserState, client, config, findById } from './client'
import { testRunState } from './client/state'
Expand Down Expand Up @@ -35,6 +35,31 @@ export const detailSizes = useLocalStorage<[left: number, right: number]>(
],
)

export const detailsPanelVisible = useLocalStorage<boolean>(
'vitest-ui_details-panel-visible',
true,
)

export const detailsPosition = ref<'right' | 'bottom'>('right')

nextTick(() => {
watch(config, () => {
if (config.value?.browser?.detailsPanelPosition) {
detailsPosition.value = config.value.browser.detailsPanelPosition
}
})
})

export function hideDetailsPanel() {
// setTimeout is used to avoid splitpanes throwing a race condition error
setTimeout(() => {
detailsPanelVisible.value = false
}, 0)
}
export function showDetailsPanel() {
detailsPanelVisible.value = true
}

// live sizes of panels in percentage
export const panels = reactive({
navigation: mainSizes.value[0],
Expand Down Expand Up @@ -147,12 +172,6 @@ export function showCoverage() {
activeFileId.value = ''
}

export function hideRightPanel() {
panels.details.browser = 100
panels.details.main = 0
detailSizes.value = [100, 0]
}

function calculateBrowserPanel() {
// we don't scale webdriverio provider because it doesn't support scaling
// TODO: find a way to make this universal - maybe show browser separately(?)
Expand All @@ -165,15 +184,6 @@ function calculateBrowserPanel() {
return 33
}

export function showRightPanel() {
panels.details.browser = calculateBrowserPanel()
panels.details.main = 100 - panels.details.browser
detailSizes.value = [
panels.details.browser,
panels.details.main,
]
}

export function showNavigationPanel() {
panels.navigation = 33
panels.details.size = 67
Expand All @@ -195,3 +205,12 @@ export function updateBrowserPanel() {
panels.details.main,
]
}

export function toggleDetailsPosition() {
detailsPosition.value = detailsPosition.value === 'right' ? 'bottom' : 'right'
// Reset to default sizes when changing orientation
const defaultSize = detailsPosition.value === 'bottom' ? 33 : 50
detailSizes.value = [defaultSize, 100 - defaultSize]
panels.details.browser = defaultSize
panels.details.main = 100 - defaultSize
}
4 changes: 1 addition & 3 deletions packages/ui/client/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// @ts-expect-error not typed global
const browserState = window.__vitest_browser_runner__
export const PORT = import.meta.hot && !browserState ? (import.meta.env.VITE_PORT || '51204') : location.port
export const PORT = import.meta.hot ? (import.meta.env.VITE_PORT || '51204') : location.port
export const HOST = [location.hostname, PORT].filter(Boolean).join(':')
export const ENTRY_URL = `${
location.protocol === 'https:' ? 'wss:' : 'ws:'
Expand Down
Loading
Loading