Skip to content

Commit

Permalink
feat: add a button to toggle the browser preview in browser mode
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Oct 9, 2024
1 parent c39080a commit 076794c
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 3 deletions.
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,23 @@
"markdownDescription": "The arguments to pass to the shell executable. This is applied only when `vitest.shellType` is `terminal`.",
"type": "array",
"scope": "resource"
},
"vitest.previewBrowser": {
"markdownDescription": "Open the visible browser when running tests in the Browser Mode.",
"type": "boolean",
"default": false,
"scope": "resource"
}
}
},
"views": {
"test": [
{
"type": "webview",
"id": "vitest.webviewSettings",
"name": "Vitest"
}
]
}
},
"scripts": {
Expand Down
2 changes: 2 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ export class VitestFolderAPI {
}

async cancelRun() {
if (this.process.closed)
return
await this.meta.rpc.cancelRun()
}

Expand Down
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,13 @@ export function getConfig(workspaceFolder?: WorkspaceFolder) {
const shellType = get<'child_process' | 'terminal'>('shellType', 'child_process')
const nodeExecArgs = get<string[] | undefined>('nodeExecArgs')

const previewBrowser = get<boolean>('previewBrowser', false)

return {
env: get<null | Record<string, string>>('nodeEnv', null),
debugExclude: get<string[]>('debugExclude', []),
filesWatcherInclude,
previewBrowser,
terminalShellArgs,
terminalShellPath,
shellType,
Expand Down
7 changes: 5 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import { TagsManager } from './tagsManager'
import { coverageContext } from './coverage'
import { debugTests } from './debug/api'
import { VitestTerminalProcess } from './api/terminal'
import { SettingsWebview } from './settingsWebview'

export async function activate(context: vscode.ExtensionContext) {
const extension = new VitestExtension()
const extension = new VitestExtension(context)
context.subscriptions.push(extension)
await extension.activate()
}
Expand All @@ -37,7 +38,7 @@ class VitestExtension {

private disposables: vscode.Disposable[] = []

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

this.testController = vscode.tests.createTestController(testControllerId, 'Vitest')
Expand Down Expand Up @@ -279,6 +280,7 @@ class VitestExtension {
'vitest.terminalShellPath',
]

const settingsWebview = new SettingsWebview(this.context.extensionUri)
this.disposables = [
vscode.workspace.onDidChangeConfiguration((event) => {
if (reloadConfigNames.some(x => event.affectsConfiguration(x)))
Expand Down Expand Up @@ -330,6 +332,7 @@ class VitestExtension {
const tokenSource = new vscode.CancellationTokenSource()
await profile.runHandler(request, tokenSource.token)
}),
settingsWebview,
]

// if the config changes, re-define all test profiles
Expand Down
9 changes: 8 additions & 1 deletion src/runner/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,14 @@ export class TestRunner extends vscode.Disposable {
log.verbose?.('Test run was cancelled manually for', join(request.include))
})

await this.runTestItems(request)
try {
await this.runTestItems(request)
}
catch (err) {
this.endTestRun()
// TODO: do we need to show the error to the user?
log.error('Failed to run tests', err)
}

this.nonContinuousRequest = undefined
this._onRequestsExhausted.fire()
Expand Down
117 changes: 117 additions & 0 deletions src/settingsWebview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import * as vscode from 'vscode'
import { getConfig } from './config'
import { nanoid } from './utils'

export class SettingsWebview implements vscode.WebviewViewProvider, vscode.Disposable {
private disposables: vscode.Disposable[]
private view: vscode.WebviewView | undefined

constructor(
private extensionUri: vscode.Uri,
) {
this.disposables = [
vscode.window.registerWebviewViewProvider('vitest.webviewSettings', this),
]
}

resolveWebviewView(webviewView: vscode.WebviewView, _context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken): Thenable<void> | void {
this.view = webviewView

webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [this.extensionUri],
}

webviewView.webview.html = createHtmlView(webviewView.webview)

this.disposables.push(
// when we get the message from the view, process it
webviewView.webview.onDidReceiveMessage((message) => {
if (message.method === 'toggle') {
const settings = vscode.workspace.getConfiguration('vitest')
settings.update(message.args.setting, !settings.get(message.args.setting))
}
}),
// when the user changes the configuration manually, update the view
vscode.workspace.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration('vitest')) {
this.updateSettings()
}
}),
// when the weview is opened, make sure it's up to date
webviewView.onDidChangeVisibility(() => {
if (!webviewView.visible)
return
this.updateSettings()
}),
)

this.updateSettings()
}

updateSettings() {
this.view?.webview.postMessage({
method: 'settings',
args: {
settings: getConfig(),
},
})
}

dispose() {
this.disposables.forEach(d => d.dispose())
this.disposables = []
}
}

// based on
// https://github.com/microsoft/playwright-vscode/blob/4454e6876bfde1b4a8570dbaeca1ad14e8cd37c8/src/settingsView.ts
function createHtmlView(webview: vscode.Webview) {
// <link href="${styleUri}" rel="stylesheet">
const nonce = nanoid()
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vitest</title>
</head>
<body>
<div class="list">
<div>
<label title="${vscode.l10n.t('Show the browser when running tests in the Browser Mode. This will disable parallel execution.')}">
<input type="checkbox" data-setting="previewBrowser"></input>
${vscode.l10n.t('Show browser')}
</label>
</div>
</div>
<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
for (const input of document.querySelectorAll('input[type=checkbox]')) {
console.log('input', input)
input.addEventListener('change', event => {
vscode.postMessage({ method: 'toggle', args: { setting: event.target.dataset.setting } });
});
}
window.addEventListener('message', event => {
const { method, args } = event.data;
if (method === 'settings') {
for (const [key, value] of Object.entries(args.settings)) {
const input = document.querySelector('input[data-setting=' + key + ']');
console.log('input', input, key, value)
if (!input)
continue;
if (typeof value === 'boolean')
input.checked = value;
else
input.value = value;
}
}
})
</script>
</body>
</html>
`
}

0 comments on commit 076794c

Please # to comment.