Skip to content

Commit 076794c

Browse files
committed
feat: add a button to toggle the browser preview in browser mode
1 parent c39080a commit 076794c

File tree

6 files changed

+150
-3
lines changed

6 files changed

+150
-3
lines changed

package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,23 @@
207207
"markdownDescription": "The arguments to pass to the shell executable. This is applied only when `vitest.shellType` is `terminal`.",
208208
"type": "array",
209209
"scope": "resource"
210+
},
211+
"vitest.previewBrowser": {
212+
"markdownDescription": "Open the visible browser when running tests in the Browser Mode.",
213+
"type": "boolean",
214+
"default": false,
215+
"scope": "resource"
210216
}
211217
}
218+
},
219+
"views": {
220+
"test": [
221+
{
222+
"type": "webview",
223+
"id": "vitest.webviewSettings",
224+
"name": "Vitest"
225+
}
226+
]
212227
}
213228
},
214229
"scripts": {

src/api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ export class VitestFolderAPI {
177177
}
178178

179179
async cancelRun() {
180+
if (this.process.closed)
181+
return
180182
await this.meta.rpc.cancelRun()
181183
}
182184

src/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,13 @@ export function getConfig(workspaceFolder?: WorkspaceFolder) {
5959
const shellType = get<'child_process' | 'terminal'>('shellType', 'child_process')
6060
const nodeExecArgs = get<string[] | undefined>('nodeExecArgs')
6161

62+
const previewBrowser = get<boolean>('previewBrowser', false)
63+
6264
return {
6365
env: get<null | Record<string, string>>('nodeEnv', null),
6466
debugExclude: get<string[]>('debugExclude', []),
6567
filesWatcherInclude,
68+
previewBrowser,
6669
terminalShellArgs,
6770
terminalShellPath,
6871
shellType,

src/extension.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import { TagsManager } from './tagsManager'
1616
import { coverageContext } from './coverage'
1717
import { debugTests } from './debug/api'
1818
import { VitestTerminalProcess } from './api/terminal'
19+
import { SettingsWebview } from './settingsWebview'
1920

2021
export async function activate(context: vscode.ExtensionContext) {
21-
const extension = new VitestExtension()
22+
const extension = new VitestExtension(context)
2223
context.subscriptions.push(extension)
2324
await extension.activate()
2425
}
@@ -37,7 +38,7 @@ class VitestExtension {
3738

3839
private disposables: vscode.Disposable[] = []
3940

40-
constructor() {
41+
constructor(private context: vscode.ExtensionContext) {
4142
log.info(`[v${version}] Vitest extension is activated because Vitest is installed or there is a Vite/Vitest config file in the workspace.`)
4243

4344
this.testController = vscode.tests.createTestController(testControllerId, 'Vitest')
@@ -279,6 +280,7 @@ class VitestExtension {
279280
'vitest.terminalShellPath',
280281
]
281282

283+
const settingsWebview = new SettingsWebview(this.context.extensionUri)
282284
this.disposables = [
283285
vscode.workspace.onDidChangeConfiguration((event) => {
284286
if (reloadConfigNames.some(x => event.affectsConfiguration(x)))
@@ -330,6 +332,7 @@ class VitestExtension {
330332
const tokenSource = new vscode.CancellationTokenSource()
331333
await profile.runHandler(request, tokenSource.token)
332334
}),
335+
settingsWebview,
333336
]
334337

335338
// if the config changes, re-define all test profiles

src/runner/runner.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,14 @@ export class TestRunner extends vscode.Disposable {
225225
log.verbose?.('Test run was cancelled manually for', join(request.include))
226226
})
227227

228-
await this.runTestItems(request)
228+
try {
229+
await this.runTestItems(request)
230+
}
231+
catch (err) {
232+
this.endTestRun()
233+
// TODO: do we need to show the error to the user?
234+
log.error('Failed to run tests', err)
235+
}
229236

230237
this.nonContinuousRequest = undefined
231238
this._onRequestsExhausted.fire()

src/settingsWebview.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import * as vscode from 'vscode'
2+
import { getConfig } from './config'
3+
import { nanoid } from './utils'
4+
5+
export class SettingsWebview implements vscode.WebviewViewProvider, vscode.Disposable {
6+
private disposables: vscode.Disposable[]
7+
private view: vscode.WebviewView | undefined
8+
9+
constructor(
10+
private extensionUri: vscode.Uri,
11+
) {
12+
this.disposables = [
13+
vscode.window.registerWebviewViewProvider('vitest.webviewSettings', this),
14+
]
15+
}
16+
17+
resolveWebviewView(webviewView: vscode.WebviewView, _context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken): Thenable<void> | void {
18+
this.view = webviewView
19+
20+
webviewView.webview.options = {
21+
enableScripts: true,
22+
localResourceRoots: [this.extensionUri],
23+
}
24+
25+
webviewView.webview.html = createHtmlView(webviewView.webview)
26+
27+
this.disposables.push(
28+
// when we get the message from the view, process it
29+
webviewView.webview.onDidReceiveMessage((message) => {
30+
if (message.method === 'toggle') {
31+
const settings = vscode.workspace.getConfiguration('vitest')
32+
settings.update(message.args.setting, !settings.get(message.args.setting))
33+
}
34+
}),
35+
// when the user changes the configuration manually, update the view
36+
vscode.workspace.onDidChangeConfiguration((e) => {
37+
if (e.affectsConfiguration('vitest')) {
38+
this.updateSettings()
39+
}
40+
}),
41+
// when the weview is opened, make sure it's up to date
42+
webviewView.onDidChangeVisibility(() => {
43+
if (!webviewView.visible)
44+
return
45+
this.updateSettings()
46+
}),
47+
)
48+
49+
this.updateSettings()
50+
}
51+
52+
updateSettings() {
53+
this.view?.webview.postMessage({
54+
method: 'settings',
55+
args: {
56+
settings: getConfig(),
57+
},
58+
})
59+
}
60+
61+
dispose() {
62+
this.disposables.forEach(d => d.dispose())
63+
this.disposables = []
64+
}
65+
}
66+
67+
// based on
68+
// https://github.com/microsoft/playwright-vscode/blob/4454e6876bfde1b4a8570dbaeca1ad14e8cd37c8/src/settingsView.ts
69+
function createHtmlView(webview: vscode.Webview) {
70+
// <link href="${styleUri}" rel="stylesheet">
71+
const nonce = nanoid()
72+
return `
73+
<!DOCTYPE html>
74+
<html lang="en">
75+
<head>
76+
<meta charset="UTF-8">
77+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
78+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
79+
<title>Vitest</title>
80+
</head>
81+
<body>
82+
<div class="list">
83+
<div>
84+
<label title="${vscode.l10n.t('Show the browser when running tests in the Browser Mode. This will disable parallel execution.')}">
85+
<input type="checkbox" data-setting="previewBrowser"></input>
86+
${vscode.l10n.t('Show browser')}
87+
</label>
88+
</div>
89+
</div>
90+
<script nonce="${nonce}">
91+
const vscode = acquireVsCodeApi();
92+
for (const input of document.querySelectorAll('input[type=checkbox]')) {
93+
console.log('input', input)
94+
input.addEventListener('change', event => {
95+
vscode.postMessage({ method: 'toggle', args: { setting: event.target.dataset.setting } });
96+
});
97+
}
98+
window.addEventListener('message', event => {
99+
const { method, args } = event.data;
100+
if (method === 'settings') {
101+
for (const [key, value] of Object.entries(args.settings)) {
102+
const input = document.querySelector('input[data-setting=' + key + ']');
103+
console.log('input', input, key, value)
104+
if (!input)
105+
continue;
106+
if (typeof value === 'boolean')
107+
input.checked = value;
108+
else
109+
input.value = value;
110+
}
111+
}
112+
})
113+
</script>
114+
</body>
115+
</html>
116+
`
117+
}

0 commit comments

Comments
 (0)