Skip to content

Commit

Permalink
feat(browser): do not reload the page during watch mode (#5810)
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Jun 1, 2024
1 parent 3796dd7 commit e5b9a0b
Show file tree
Hide file tree
Showing 18 changed files with 87 additions and 30 deletions.
9 changes: 8 additions & 1 deletion packages/browser/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { parse, stringify } from 'flatted'
import type { VitestBrowserClientMocker } from './mocker'
import { getBrowserState } from './utils'

const PAGE_TYPE = getBrowserState().type

export const PORT = import.meta.hot ? '51204' : location.port
export const HOST = [location.hostname, PORT].filter(Boolean).join(':')
export const SESSION_ID = crypto.randomUUID()
export const ENTRY_URL = `${
location.protocol === 'https:' ? 'wss:' : 'ws:'
}//${HOST}/__vitest_browser_api__?type=${getBrowserState().type}&sessionId=${SESSION_ID}`
}//${HOST}/__vitest_browser_api__?type=${PAGE_TYPE}&sessionId=${SESSION_ID}`

let setCancel = (_: CancelReason) => {}
export const onCancel = new Promise<CancelReason>((resolve) => {
Expand Down Expand Up @@ -51,6 +53,11 @@ function createClient() {
const exports = await mocker.resolve(id)
return Object.keys(exports)
},
async createTesters(files: string[]) {
if (PAGE_TYPE !== 'orchestrator')
return
getBrowserState().createTesters?.(files)
},
}, {
post: msg => ctx.ws.send(msg),
on: fn => (onMessage = fn),
Expand Down
22 changes: 18 additions & 4 deletions packages/browser/src/client/orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ const ID_ALL = '__vitest_all__'

const iframes = new Map<string, HTMLIFrameElement>()

let promiseTesters: Promise<void> | undefined
getBrowserState().createTesters = async (files) => {
await promiseTesters
promiseTesters = createTesters(files).finally(() => {
promiseTesters = undefined
})
await promiseTesters
}

function debug(...args: unknown[]) {
const debug = getConfig().env.VITEST_BROWSER_DEBUG
if (debug && debug !== 'false')
Expand All @@ -20,7 +29,7 @@ function debug(...args: unknown[]) {

function createIframe(container: HTMLDivElement, file: string) {
if (iframes.has(file)) {
container.removeChild(iframes.get(file)!)
iframes.get(file)!.remove()
iframes.delete(file)
}

Expand Down Expand Up @@ -75,7 +84,6 @@ async function getContainer(config: ResolvedConfig): Promise<HTMLDivElement> {
}

client.ws.addEventListener('open', async () => {
const config = getConfig()
const testFiles = getBrowserState().files

debug('test files', testFiles.join(', '))
Expand All @@ -86,7 +94,6 @@ client.ws.addEventListener('open', async () => {
return
}

const container = await getContainer(config)
const runningFiles = new Set<string>(testFiles)

channel.addEventListener('message', async (e: MessageEvent<IframeChannelEvent>): Promise<void> => {
Expand Down Expand Up @@ -137,6 +144,13 @@ client.ws.addEventListener('open', async () => {
}
})

await createTesters(testFiles)
})

async function createTesters(testFiles: string[]) {
const config = getConfig()
const container = await getContainer(config)

if (config.browser.ui) {
container.className = ''
container.textContent = ''
Expand Down Expand Up @@ -174,7 +188,7 @@ client.ws.addEventListener('open', async () => {
})
}
}
})
}

function generateFileId(file: string) {
const config = getConfig()
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/client/tester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async function prepareTestEnvironment(files: string[]) {
worker: './browser.js',
workerId: 1,
config,
projectName: config.name,
projectName: config.name || '',
files,
environment: {
name: 'browser',
Expand Down
3 changes: 2 additions & 1 deletion packages/browser/src/client/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ interface BrowserRunnerState {
config: ResolvedConfig
type: 'tester' | 'orchestrator'
wrapModule: <T>(module: () => T) => T
runTests: (tests: string[]) => Promise<void>
runTests?: (tests: string[]) => Promise<void>
createTesters?: (files: string[]) => Promise<void>
}

export function getBrowserState(): BrowserRunnerState {
Expand Down
2 changes: 1 addition & 1 deletion packages/runner/src/types/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export interface Suite extends TaskBase {

export interface File extends Suite {
filepath: string
projectName: string
projectName: string | undefined
collectDuration?: number
setupDuration?: number
}
Expand Down
11 changes: 9 additions & 2 deletions packages/ui/client/components/FileDetails.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { ModuleGraphData } from 'vitest'
import { client, current, currentLogs, isReport, browserState } from '~/composables/client'
import { client, current, currentLogs, isReport, browserState, config } from '~/composables/client'
import type { Params } from '~/composables/params'
import { viewMode } from '~/composables/params'
import type { ModuleGraph } from '~/composables/module-graph'
Expand Down Expand Up @@ -43,6 +43,13 @@ const consoleCount = computed(() => {
function onDraft(value: boolean) {
draft.value = value
}
function relativeToRoot(path?: string) {
if (!path) return ''
if (path.startsWith(config.root))
return path.slice(config.root.length)
return path
}
</script>

<template>
Expand All @@ -54,7 +61,7 @@ function onDraft(value: boolean) {
[{{ current?.file.projectName || '' }}]
</div>
<div flex-1 font-light op-50 ws-nowrap truncate text-sm>
{{ current?.filepath }}
{{ relativeToRoot(current?.filepath) }}
</div>
<div class="flex text-lg">
<IconButton
Expand Down
6 changes: 1 addition & 5 deletions packages/ui/client/components/TasksList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,7 @@ const filteredTests: ComputedRef<File[]> = computed(() => isFiltered.value ? fil
const failed = computed(() => filtered.value.filter(task => task.result?.state === 'fail'))
const success = computed(() => filtered.value.filter(task => task.result?.state === 'pass'))
const skipped = computed(() => filtered.value.filter(task => task.mode === 'skip' || task.mode === 'todo'))
const running = computed(() => filtered.value.filter(task =>
!failed.value.includes(task)
&& !success.value.includes(task)
&& !skipped.value.includes(task),
))
const running = computed(() => filtered.value.filter(task => !task.result || task.result.state === 'run'))
const disableClearSearch = computed(() => search.value === '')
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/client/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function resizeMain() {
<transition v-else>
<Splitpanes key="detail" @resized="onModuleResized">
<Pane :size="detailSizes[0]">
<BrowserIframe />
<BrowserIframe v-once />
</Pane>
<Pane :size="detailSizes[1]">
<Dashboard v-if="dashboardVisible" key="summary" />
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/api/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export function setup(ctx: Vitest, _server?: ViteDevServer) {
async getTestFiles() {
const spec = await ctx.globTestFiles()
return spec.map(([project, file]) => [{
name: project.getName(),
name: project.config.name,
root: project.config.root,
}, file])
},
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export interface WebSocketEvents extends Pick<Reporter, 'onCollected' | 'onFinis
export interface WebSocketBrowserEvents {
onCancel: (reason: CancelReason) => void
startMocking: (id: string) => Promise<string[]>
createTesters: (files: string[]) => Promise<void>
}

export type WebSocketRPC = BirpcReturn<WebSocketEvents, WebSocketHandlers>
Expand Down
6 changes: 3 additions & 3 deletions packages/vitest/src/node/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,13 @@ export class Vitest {

public getProjectByTaskId(taskId: string): WorkspaceProject {
const task = this.state.idMap.get(taskId)
const projectName = (task as File).projectName || task?.file?.projectName
const projectName = (task as File).projectName || task?.file?.projectName || ''
return this.projects.find(p => p.getName() === projectName)
|| this.getCoreWorkspaceProject()
|| this.projects[0]
}

public getProjectByName(name: string) {
public getProjectByName(name: string = '') {
return this.projects.find(p => p.getName() === name)
|| this.getCoreWorkspaceProject()
|| this.projects[0]
Expand Down Expand Up @@ -609,7 +609,7 @@ export class Vitest {
await this.report('onPathsCollected', filepaths)
await this.report('onSpecsCollected', specs.map(
([project, file]) =>
[{ name: project.getName(), root: project.config.root }, file] as SerializableSpec,
[{ name: project.config.name, root: project.config.root }, file] as SerializableSpec,
))

// previous run
Expand Down
11 changes: 10 additions & 1 deletion packages/vitest/src/node/pools/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,16 @@ export function createBrowserPool(ctx: Vitest): ProcessPool {

const promise = waitForTests(project, files)

await provider.openPage(new URL('/', origin).toString())
const orchestrators = project.browserRpc.orchestrators
if (orchestrators.size) {
orchestrators.forEach((orchestrator) => {
orchestrator.createTesters(files)
})
}
else {
await provider.openPage(new URL('/', origin).toString())
}

await promise
}

Expand Down
6 changes: 3 additions & 3 deletions packages/vitest/src/node/reporters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,8 @@ export abstract class BaseReporter implements Reporter {
const hasStr = i[0]?.stackStr === error.stackStr
if (!hasStr)
return false
const currentProjectName = (task as File)?.projectName || task.file?.projectName
const projectName = (i[1][0] as File)?.projectName || i[1][0].file?.projectName
const currentProjectName = (task as File)?.projectName || task.file?.projectName || ''
const projectName = (i[1][0] as File)?.projectName || i[1][0].file?.projectName || ''
return projectName === currentProjectName
})
if (errorItem)
Expand All @@ -397,7 +397,7 @@ export abstract class BaseReporter implements Reporter {
for (const [error, tasks] of errorsQueue) {
for (const task of tasks) {
const filepath = (task as File)?.filepath || ''
const projectName = (task as File)?.projectName || task.file?.projectName
const projectName = (task as File)?.projectName || task.file?.projectName || ''
let name = getFullName(task, c.dim(' > '))
if (filepath)
name = `${name} ${c.dim(`[ ${this.relative(filepath)} ]`)}`
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/node/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class StateManager {
}

// this file is reused by ws-client, and shoult not rely on heavy dependencies like workspace
clearFiles(_project: { config: { name: string; root: string } }, paths: string[] = []) {
clearFiles(_project: { config: { name: string | undefined; root: string } }, paths: string[] = []) {
const project = _project as WorkspaceProject
paths.forEach((path) => {
const files = this.filesMap.get(path)
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/types/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ export interface Reporter {
}

export type { Vitest }
export type SerializableSpec = [project: { name: string; root: string }, file: string]
export type SerializableSpec = [project: { name: string | undefined; root: string }, file: string]
17 changes: 17 additions & 0 deletions test/browser/src/button.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.wrapper {
padding: 12px;
font-family: Readex Pro,sans-serif;
font-size: large;
font-weight: bold;
text-align: center;
}

.node {
background-color: #3f3f3f;
border-radius: 12px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
color: white;
}
4 changes: 2 additions & 2 deletions test/browser/src/createNode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export function createNode() {
const div = document.createElement('div')
div.textContent = 'Hello World'
document.body.appendChild(div)
div.className = 'node'
div.textContent = 'Hello World!'
return div
}
9 changes: 7 additions & 2 deletions test/browser/test/dom.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { expect, test } from 'vitest'
import { createNode } from '#src/createNode'
import '../src/button.css'

test('renders div', () => {
const div = createNode()
document.body.style.background = '#f3f3f3'
expect(div.textContent).toBe('Hello World')
const wrapper = document.createElement('div')
wrapper.className = 'wrapper'
document.body.appendChild(wrapper)
const div = createNode()
wrapper.appendChild(div)
expect(div.textContent).toBe('Hello World!')
})

0 comments on commit e5b9a0b

Please sign in to comment.