Improve test-helper (#2820)

This commit is contained in:
Leedehai 2023-12-20 06:03:09 -05:00 committed by GitHub
parent 3dc4eb6bcd
commit 6eb6e877ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 266 additions and 96 deletions

View File

@ -1,87 +1,225 @@
const vscode = require('vscode')
const cp = require('child_process')
function activate(context) {
let panel = null
class TestHelper {
constructor() {
/** @type {vscode.Uri?} */ this.sourceUriOfActivePanel = null
/** @type {Map<vscode.Uri, vscode.WebviewPanel>} */ this.panels = new Map()
function refreshPanel(stdout, stderr) {
const uri = vscode.window.activeTextEditor.document.uri
const { pngPath, refPath } = getPaths(uri)
/** @type {vscode.StatusBarItem} */ this.testRunningStatusBarItem =
vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right)
this.testRunningStatusBarItem.text = "$(loading~spin) Running"
this.testRunningStatusBarItem.backgroundColor =
new vscode.ThemeColor('statusBarItem.warningBackground')
}
/**
* The caller should ensure when this function is called, a text editor is active.
* Note the fake "editor" for the extension's WebView panel is not one.
* @returns {vscode.Uri}
*/
static getActiveDocumentUri() {
const editor = vscode.window.activeTextEditor
if (!editor) {
throw new Error('vscode.window.activeTextEditor is undefined.')
}
return editor.document.uri
}
/**
* The caller should ensure when this function is called, a WebView panel is active.
* @returns {vscode.Uri}
*/
getSourceUriOfActivePanel() {
// If this function is invoked when user clicks the button from within a WebView
// panel, then the active panel is this panel, and sourceUriOfActivePanel is
// guaranteed to have been updated by that panel's onDidChangeViewState listener.
if (!this.sourceUriOfActivePanel) {
throw new Error('sourceUriOfActivePanel is falsy; is there a focused panel?')
}
return this.sourceUriOfActivePanel
}
/** @param {vscode.Uri} uri */
static getImageUris(uri) {
const png = vscode.Uri.file(uri.path
.replace("tests/typ", "tests/png")
.replace(".typ", ".png"))
const ref = vscode.Uri.file(uri.path
.replace("tests/typ", "tests/ref")
.replace(".typ", ".png"))
return {png, ref}
}
/**
* @param {vscode.Uri} uri
* @param {string} stdout
* @param {string} stderr
*/
refreshTestPreviewImpl_(uri, stdout, stderr) {
const {png, ref} = TestHelper.getImageUris(uri)
const panel = this.panels.get(uri)
if (panel && panel.visible) {
console.log('Refreshing WebView')
const pngSrc = panel.webview.asWebviewUri(pngPath)
const refSrc = panel.webview.asWebviewUri(refPath)
console.log(`Refreshing WebView for ${uri.fsPath}`)
const webViewSrcs = {
png: panel.webview.asWebviewUri(png),
ref: panel.webview.asWebviewUri(ref),
}
panel.webview.html = ''
// Make refresh notable.
setTimeout(() => {
panel.webview.html = getWebviewContent(pngSrc, refSrc, stdout, stderr)
if (!panel) {
throw new Error('panel to refresh is falsy after waiting')
}
panel.webview.html = getWebviewContent(webViewSrcs, stdout, stderr)
}, 50)
}
}
const openCmd = vscode.commands.registerCommand("ShortcutMenuBar.testOpen", () => {
panel = vscode.window.createWebviewPanel(
'testOutput',
'Test output',
/** @param {vscode.Uri} uri */
openTestPreview(uri) {
if (this.panels.has(uri)) {
this.panels.get(uri)?.reveal()
return
}
const newPanel = vscode.window.createWebviewPanel(
'Typst.test-helper.preview',
uri.path.split('/').pop()?.replace('.typ', '.png') ?? 'Test output',
vscode.ViewColumn.Beside,
{}
)
newPanel.onDidChangeViewState(() => {
if (newPanel && newPanel.active && newPanel.visible) {
console.log(`Set sourceUriOfActivePanel to ${uri}`)
this.sourceUriOfActivePanel = uri
} else {
console.log(`Set sourceUriOfActivePanel to null`)
this.sourceUriOfActivePanel = null
}
})
newPanel.onDidDispose(() => {
console.log(`Delete panel ${uri}`)
this.panels.delete(uri)
if (this.sourceUriOfActivePanel === uri) {
this.sourceUriOfActivePanel = null
}
})
this.panels.set(uri, newPanel)
refreshPanel("", "")
})
this.refreshTestPreviewImpl_(uri, "", "")
}
const refreshCmd = vscode.commands.registerCommand("ShortcutMenuBar.testRefresh", () => {
refreshPanel("", "")
})
const rerunCmd = vscode.commands.registerCommand("ShortcutMenuBar.testRerun", () => {
const uri = vscode.window.activeTextEditor.document.uri
/** @param {vscode.Uri} uri */
runTest(uri) {
const components = uri.fsPath.split(/tests[\/\\]/)
const dir = components[0]
const subPath = components[1]
this.testRunningStatusBarItem.show()
cp.exec(
`cargo test --manifest-path ${dir}/Cargo.toml --all --test tests -- ${subPath}`,
(err, stdout, stderr) => {
console.log('Ran tests')
refreshPanel(stdout, stderr)
this.testRunningStatusBarItem.hide()
console.log(`Ran tests ${uri.fsPath}`)
this.refreshTestPreviewImpl_(uri, stdout, stderr)
}
)
})
}
const updateCmd = vscode.commands.registerCommand("ShortcutMenuBar.testUpdate", () => {
const uri = vscode.window.activeTextEditor.document.uri
const { pngPath, refPath } = getPaths(uri)
/** @param {vscode.Uri} uri */
refreshTestPreview(uri) {
const panel = this.panels.get(uri)
if (panel) {
panel.reveal()
this.refreshTestPreviewImpl_(uri, "", "")
}
}
vscode.workspace.fs.copy(pngPath, refPath, { overwrite: true }).then(() => {
console.log('Copied to reference file')
cp.exec(`oxipng -o max -a ${refPath.fsPath}`, (err, stdout, stderr) => {
refreshPanel(stdout, stderr)
/** @param {vscode.Uri} uri */
updateTestReference(uri) {
const {png, ref} = TestHelper.getImageUris(uri)
vscode.workspace.fs.copy(png, ref, {overwrite: true})
.then(() => {
cp.exec(`oxipng -o max -a ${ref.fsPath}`, (err, stdout, stderr) => {
console.log(`Copied to reference file for ${uri.fsPath}`)
this.refreshTestPreviewImpl_(uri, stdout, stderr)
})
})
})
})
}
context.subscriptions.push(openCmd)
context.subscriptions.push(refreshCmd)
context.subscriptions.push(rerunCmd)
context.subscriptions.push(updateCmd)
/**
* @param {vscode.Uri} uri
* @param {string} webviewSection
*/
copyFilePathToClipboard(uri, webviewSection) {
const {png, ref} = TestHelper.getImageUris(uri)
switch (webviewSection) {
case 'png':
vscode.env.clipboard.writeText(png.fsPath)
break
case 'ref':
vscode.env.clipboard.writeText(ref.fsPath)
break
default:
break
}
}
}
function getPaths(uri) {
const pngPath = vscode.Uri.file(uri.path
.replace("tests/typ", "tests/png")
.replace(".typ", ".png"))
/** @param {vscode.ExtensionContext} context */
function activate(context) {
const manager = new TestHelper();
context.subscriptions.push(manager.testRunningStatusBarItem)
const refPath = vscode.Uri.file(uri.path
.replace("tests/typ", "tests/ref")
.replace(".typ", ".png"))
return { pngPath, refPath }
context.subscriptions.push(vscode.commands.registerCommand(
"Typst.test-helper.openFromSource", () => {
manager.openTestPreview(TestHelper.getActiveDocumentUri())
}))
context.subscriptions.push(vscode.commands.registerCommand(
"Typst.test-helper.refreshFromSource", () => {
manager.refreshTestPreview(TestHelper.getActiveDocumentUri())
}))
context.subscriptions.push(vscode.commands.registerCommand(
"Typst.test-helper.refreshFromPreview", () => {
manager.refreshTestPreview(manager.getSourceUriOfActivePanel())
}))
context.subscriptions.push(vscode.commands.registerCommand(
"Typst.test-helper.runFromSource", () => {
manager.runTest(TestHelper.getActiveDocumentUri())
}))
context.subscriptions.push(vscode.commands.registerCommand(
"Typst.test-helper.runFromPreview", () => {
manager.runTest(manager.getSourceUriOfActivePanel())
}))
context.subscriptions.push(vscode.commands.registerCommand(
"Typst.test-helper.updateFromSource", () => {
manager.updateTestReference(TestHelper.getActiveDocumentUri())
}))
context.subscriptions.push(vscode.commands.registerCommand(
"Typst.test-helper.updateFromPreview", () => {
manager.updateTestReference(manager.getSourceUriOfActivePanel())
}))
// Context menu: the drop-down menu after right-click.
context.subscriptions.push(vscode.commands.registerCommand(
"Typst.test-helper.copyImageFilePathFromPreviewContext", (e) => {
manager.copyFilePathToClipboard(
manager.getSourceUriOfActivePanel(), e.webviewSection)
}))
}
function getWebviewContent(pngSrc, refSrc, stdout, stderr) {
/**
* @param {{png: vscode.Uri, ref: vscode.Uri}} webViewSrcs
* @param {string} stdout
* @param {string} stderr
* @returns {string}
*/
function getWebviewContent(webViewSrcs, stdout, stderr) {
const escape = (text) => text.replace(/</g, "&lt;").replace(/>/g, "&gt;")
return `
<!DOCTYPE html>
<html lang="en">
@ -118,15 +256,15 @@ function getWebviewContent(pngSrc, refSrc, stdout, stderr) {
</style>
</head>
<body>
<div class="flex">
<div class="flex" data-vscode-context='{"preventDefaultContextMenuItems": true}'>
<div>
<h1>Output</h1>
<img src="${pngSrc}"/>
<img data-vscode-context='{"webviewSection":"png"}' src="${webViewSrcs.png}"/>
</div>
<div>
<h1>Reference</h1>
<img src="${refSrc}"/>
<img data-vscode-context='{"webviewSection":"ref"}' src="${webViewSrcs.ref}"/>
</div>
</div>
@ -140,10 +278,6 @@ function getWebviewContent(pngSrc, refSrc, stdout, stderr) {
`
}
function escape(text) {
return text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function deactivate() {}
module.exports = { activate, deactivate }
module.exports = {activate, deactivate}

View File

@ -1,81 +1,117 @@
{
"name": "typst-test-helper",
"name": "test-helper",
"publisher": "typst",
"displayName": "Typst Test Helper",
"description": "Helps to run, compare and update Typst tests.",
"version": "0.0.1",
"engines": {
"vscode": "^1.53.0"
"vscode": "^1.71.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:ShortcutMenuBar.testOpen",
"onCommand:ShortcutMenuBar.testRefresh",
"onCommand:ShortcutMenuBar.testRerun",
"onCommand:ShortcutMenuBar.testUpdate"
"onCommand:Typst.test-helper.openFromSource",
"onCommand:Typst.test-helper.refreshFromSource",
"onCommand:Typst.test-helper.refreshFromPreview",
"onCommand:Typst.test-helper.runFromSource",
"onCommand:Typst.test-helper.runFromPreview",
"onCommand:Typst.test-helper.updateFromSource",
"onCommand:Typst.test-helper.updateFromPreview",
"onCommand:Typst.test-helper.copyImageFilePathFromPreviewContext"
],
"main": "./extension.js",
"contributes": {
"commands": [
{
"command": "ShortcutMenuBar.testOpen",
"command": "Typst.test-helper.openFromSource",
"title": "Open test output",
"category": "ShortcutMenuBar",
"icon": {
"light": "images/open-light.svg",
"dark": "images/open-dark.svg"
}
"category": "Typst.test-helper",
"icon": "$(plus)"
},
{
"command": "ShortcutMenuBar.testRefresh",
"command": "Typst.test-helper.refreshFromSource",
"title": "Refresh preview",
"category": "ShortcutMenuBar",
"icon": {
"light": "images/refresh-light.svg",
"dark": "images/refresh-dark.svg"
}
"category": "Typst.test-helper",
"icon": "$(refresh)"
},
{
"command": "ShortcutMenuBar.testRerun",
"title": "Rerun test",
"category": "ShortcutMenuBar",
"icon": {
"light": "images/rerun-light.svg",
"dark": "images/rerun-dark.svg"
}
"command": "Typst.test-helper.refreshFromPreview",
"title": "Refresh preview",
"category": "Typst.test-helper",
"icon": "$(refresh)"
},
{
"command": "ShortcutMenuBar.testUpdate",
"command": "Typst.test-helper.runFromSource",
"title": "Run test",
"category": "Typst.test-helper",
"icon": "$(debug-start)"
},
{
"command": "Typst.test-helper.runFromPreview",
"title": "Run test",
"category": "Typst.test-helper",
"icon": "$(debug-start)"
},
{
"command": "Typst.test-helper.updateFromSource",
"title": "Update reference image",
"category": "ShortcutMenuBar",
"icon": {
"light": "images/update-light.svg",
"dark": "images/update-dark.svg"
}
"category": "Typst.test-helper",
"icon": "$(save)"
},
{
"command": "Typst.test-helper.updateFromPreview",
"title": "Update reference image",
"category": "Typst.test-helper",
"icon": "$(save)"
},
{
"command": "Typst.test-helper.copyImageFilePathFromPreviewContext",
"title": "Copy image file path"
}
],
"menus": {
"editor/title": [
{
"when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
"command": "ShortcutMenuBar.testOpen",
"group": "navigation@0"
},
{
"when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
"command": "ShortcutMenuBar.testRefresh",
"command": "Typst.test-helper.openFromSource",
"group": "navigation@1"
},
{
"when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
"command": "ShortcutMenuBar.testRerun",
"command": "Typst.test-helper.refreshFromSource",
"group": "navigation@2"
},
{
"when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
"command": "ShortcutMenuBar.testUpdate",
"command": "Typst.test-helper.runFromSource",
"group": "navigation@3"
},
{
"when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
"command": "Typst.test-helper.updateFromSource",
"group": "navigation@4"
},
{
"when": "activeWebviewPanelId == Typst.test-helper.preview",
"command": "Typst.test-helper.refreshFromPreview",
"group": "navigation@1"
},
{
"when": "activeWebviewPanelId == Typst.test-helper.preview",
"command": "Typst.test-helper.runFromPreview",
"group": "navigation@2"
},
{
"when": "activeWebviewPanelId == Typst.test-helper.preview",
"command": "Typst.test-helper.updateFromPreview",
"group": "navigation@3"
}
],
"webview/context": [
{
"command": "Typst.test-helper.copyImageFilePathFromPreviewContext",
"when": "webviewId == Typst.test-helper.preview && (webviewSection == png || webviewSection == ref)"
}
]
}
@ -84,4 +120,4 @@
"@types/vscode": "^1.53.0",
"@types/node": "^12.11.7"
}
}
}