mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Improve test-helper (#2820)
This commit is contained in:
parent
3dc4eb6bcd
commit
6eb6e877ae
@ -1,87 +1,225 @@
|
|||||||
const vscode = require('vscode')
|
const vscode = require('vscode')
|
||||||
const cp = require('child_process')
|
const cp = require('child_process')
|
||||||
|
|
||||||
function activate(context) {
|
class TestHelper {
|
||||||
let panel = null
|
constructor() {
|
||||||
|
/** @type {vscode.Uri?} */ this.sourceUriOfActivePanel = null
|
||||||
|
/** @type {Map<vscode.Uri, vscode.WebviewPanel>} */ this.panels = new Map()
|
||||||
|
|
||||||
function refreshPanel(stdout, stderr) {
|
/** @type {vscode.StatusBarItem} */ this.testRunningStatusBarItem =
|
||||||
const uri = vscode.window.activeTextEditor.document.uri
|
vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right)
|
||||||
const { pngPath, refPath } = getPaths(uri)
|
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) {
|
if (panel && panel.visible) {
|
||||||
console.log('Refreshing WebView')
|
console.log(`Refreshing WebView for ${uri.fsPath}`)
|
||||||
const pngSrc = panel.webview.asWebviewUri(pngPath)
|
const webViewSrcs = {
|
||||||
const refSrc = panel.webview.asWebviewUri(refPath)
|
png: panel.webview.asWebviewUri(png),
|
||||||
|
ref: panel.webview.asWebviewUri(ref),
|
||||||
|
}
|
||||||
panel.webview.html = ''
|
panel.webview.html = ''
|
||||||
|
|
||||||
// Make refresh notable.
|
// Make refresh notable.
|
||||||
setTimeout(() => {
|
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)
|
}, 50)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openCmd = vscode.commands.registerCommand("ShortcutMenuBar.testOpen", () => {
|
/** @param {vscode.Uri} uri */
|
||||||
panel = vscode.window.createWebviewPanel(
|
openTestPreview(uri) {
|
||||||
'testOutput',
|
if (this.panels.has(uri)) {
|
||||||
'Test output',
|
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,
|
vscode.ViewColumn.Beside,
|
||||||
{}
|
|
||||||
)
|
)
|
||||||
|
newPanel.onDidChangeViewState(() => {
|
||||||
refreshPanel("", "")
|
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(() => {
|
||||||
const refreshCmd = vscode.commands.registerCommand("ShortcutMenuBar.testRefresh", () => {
|
console.log(`Delete panel ${uri}`)
|
||||||
refreshPanel("", "")
|
this.panels.delete(uri)
|
||||||
|
if (this.sourceUriOfActivePanel === uri) {
|
||||||
|
this.sourceUriOfActivePanel = null
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
this.panels.set(uri, newPanel)
|
||||||
|
|
||||||
const rerunCmd = vscode.commands.registerCommand("ShortcutMenuBar.testRerun", () => {
|
this.refreshTestPreviewImpl_(uri, "", "")
|
||||||
const uri = vscode.window.activeTextEditor.document.uri
|
}
|
||||||
|
|
||||||
|
/** @param {vscode.Uri} uri */
|
||||||
|
runTest(uri) {
|
||||||
const components = uri.fsPath.split(/tests[\/\\]/)
|
const components = uri.fsPath.split(/tests[\/\\]/)
|
||||||
const dir = components[0]
|
const dir = components[0]
|
||||||
const subPath = components[1]
|
const subPath = components[1]
|
||||||
|
|
||||||
|
this.testRunningStatusBarItem.show()
|
||||||
cp.exec(
|
cp.exec(
|
||||||
`cargo test --manifest-path ${dir}/Cargo.toml --all --test tests -- ${subPath}`,
|
`cargo test --manifest-path ${dir}/Cargo.toml --all --test tests -- ${subPath}`,
|
||||||
(err, stdout, stderr) => {
|
(err, stdout, stderr) => {
|
||||||
console.log('Ran tests')
|
this.testRunningStatusBarItem.hide()
|
||||||
refreshPanel(stdout, stderr)
|
console.log(`Ran tests ${uri.fsPath}`)
|
||||||
|
this.refreshTestPreviewImpl_(uri, stdout, stderr)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
||||||
const updateCmd = vscode.commands.registerCommand("ShortcutMenuBar.testUpdate", () => {
|
/** @param {vscode.Uri} uri */
|
||||||
const uri = vscode.window.activeTextEditor.document.uri
|
refreshTestPreview(uri) {
|
||||||
const { pngPath, refPath } = getPaths(uri)
|
const panel = this.panels.get(uri)
|
||||||
|
if (panel) {
|
||||||
|
panel.reveal()
|
||||||
|
this.refreshTestPreviewImpl_(uri, "", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vscode.workspace.fs.copy(pngPath, refPath, { overwrite: true }).then(() => {
|
/** @param {vscode.Uri} uri */
|
||||||
console.log('Copied to reference file')
|
updateTestReference(uri) {
|
||||||
cp.exec(`oxipng -o max -a ${refPath.fsPath}`, (err, stdout, stderr) => {
|
const {png, ref} = TestHelper.getImageUris(uri)
|
||||||
refreshPanel(stdout, stderr)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context.subscriptions.push(openCmd)
|
vscode.workspace.fs.copy(png, ref, {overwrite: true})
|
||||||
context.subscriptions.push(refreshCmd)
|
.then(() => {
|
||||||
context.subscriptions.push(rerunCmd)
|
cp.exec(`oxipng -o max -a ${ref.fsPath}`, (err, stdout, stderr) => {
|
||||||
context.subscriptions.push(updateCmd)
|
console.log(`Copied to reference file for ${uri.fsPath}`)
|
||||||
|
this.refreshTestPreviewImpl_(uri, stdout, stderr)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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) {
|
/** @param {vscode.ExtensionContext} context */
|
||||||
const pngPath = vscode.Uri.file(uri.path
|
function activate(context) {
|
||||||
.replace("tests/typ", "tests/png")
|
const manager = new TestHelper();
|
||||||
.replace(".typ", ".png"))
|
context.subscriptions.push(manager.testRunningStatusBarItem)
|
||||||
|
|
||||||
const refPath = vscode.Uri.file(uri.path
|
context.subscriptions.push(vscode.commands.registerCommand(
|
||||||
.replace("tests/typ", "tests/ref")
|
"Typst.test-helper.openFromSource", () => {
|
||||||
.replace(".typ", ".png"))
|
manager.openTestPreview(TestHelper.getActiveDocumentUri())
|
||||||
|
}))
|
||||||
return { pngPath, refPath }
|
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, "<").replace(/>/g, ">")
|
||||||
return `
|
return `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@ -118,15 +256,15 @@ function getWebviewContent(pngSrc, refSrc, stdout, stderr) {
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="flex">
|
<div class="flex" data-vscode-context='{"preventDefaultContextMenuItems": true}'>
|
||||||
<div>
|
<div>
|
||||||
<h1>Output</h1>
|
<h1>Output</h1>
|
||||||
<img src="${pngSrc}"/>
|
<img data-vscode-context='{"webviewSection":"png"}' src="${webViewSrcs.png}"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1>Reference</h1>
|
<h1>Reference</h1>
|
||||||
<img src="${refSrc}"/>
|
<img data-vscode-context='{"webviewSection":"ref"}' src="${webViewSrcs.ref}"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -140,10 +278,6 @@ function getWebviewContent(pngSrc, refSrc, stdout, stderr) {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
function escape(text) {
|
|
||||||
return text.replace(/</g, "<").replace(/>/g, ">");
|
|
||||||
}
|
|
||||||
|
|
||||||
function deactivate() {}
|
function deactivate() {}
|
||||||
|
|
||||||
module.exports = { activate, deactivate }
|
module.exports = {activate, deactivate}
|
||||||
|
@ -1,81 +1,117 @@
|
|||||||
{
|
{
|
||||||
"name": "typst-test-helper",
|
"name": "test-helper",
|
||||||
|
"publisher": "typst",
|
||||||
"displayName": "Typst Test Helper",
|
"displayName": "Typst Test Helper",
|
||||||
"description": "Helps to run, compare and update Typst tests.",
|
"description": "Helps to run, compare and update Typst tests.",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.53.0"
|
"vscode": "^1.71.0"
|
||||||
},
|
},
|
||||||
"categories": [
|
"categories": [
|
||||||
"Other"
|
"Other"
|
||||||
],
|
],
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
"onCommand:ShortcutMenuBar.testOpen",
|
"onCommand:Typst.test-helper.openFromSource",
|
||||||
"onCommand:ShortcutMenuBar.testRefresh",
|
"onCommand:Typst.test-helper.refreshFromSource",
|
||||||
"onCommand:ShortcutMenuBar.testRerun",
|
"onCommand:Typst.test-helper.refreshFromPreview",
|
||||||
"onCommand:ShortcutMenuBar.testUpdate"
|
"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",
|
"main": "./extension.js",
|
||||||
"contributes": {
|
"contributes": {
|
||||||
"commands": [
|
"commands": [
|
||||||
{
|
{
|
||||||
"command": "ShortcutMenuBar.testOpen",
|
"command": "Typst.test-helper.openFromSource",
|
||||||
"title": "Open test output",
|
"title": "Open test output",
|
||||||
"category": "ShortcutMenuBar",
|
"category": "Typst.test-helper",
|
||||||
"icon": {
|
"icon": "$(plus)"
|
||||||
"light": "images/open-light.svg",
|
|
||||||
"dark": "images/open-dark.svg"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "ShortcutMenuBar.testRefresh",
|
"command": "Typst.test-helper.refreshFromSource",
|
||||||
"title": "Refresh preview",
|
"title": "Refresh preview",
|
||||||
"category": "ShortcutMenuBar",
|
"category": "Typst.test-helper",
|
||||||
"icon": {
|
"icon": "$(refresh)"
|
||||||
"light": "images/refresh-light.svg",
|
|
||||||
"dark": "images/refresh-dark.svg"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "ShortcutMenuBar.testRerun",
|
"command": "Typst.test-helper.refreshFromPreview",
|
||||||
"title": "Rerun test",
|
"title": "Refresh preview",
|
||||||
"category": "ShortcutMenuBar",
|
"category": "Typst.test-helper",
|
||||||
"icon": {
|
"icon": "$(refresh)"
|
||||||
"light": "images/rerun-light.svg",
|
|
||||||
"dark": "images/rerun-dark.svg"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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",
|
"title": "Update reference image",
|
||||||
"category": "ShortcutMenuBar",
|
"category": "Typst.test-helper",
|
||||||
"icon": {
|
"icon": "$(save)"
|
||||||
"light": "images/update-light.svg",
|
},
|
||||||
"dark": "images/update-dark.svg"
|
{
|
||||||
}
|
"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": {
|
"menus": {
|
||||||
"editor/title": [
|
"editor/title": [
|
||||||
{
|
{
|
||||||
"when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
|
"when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
|
||||||
"command": "ShortcutMenuBar.testOpen",
|
"command": "Typst.test-helper.openFromSource",
|
||||||
"group": "navigation@0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
|
|
||||||
"command": "ShortcutMenuBar.testRefresh",
|
|
||||||
"group": "navigation@1"
|
"group": "navigation@1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
|
"when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
|
||||||
"command": "ShortcutMenuBar.testRerun",
|
"command": "Typst.test-helper.refreshFromSource",
|
||||||
"group": "navigation@2"
|
"group": "navigation@2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
|
"when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
|
||||||
"command": "ShortcutMenuBar.testUpdate",
|
"command": "Typst.test-helper.runFromSource",
|
||||||
"group": "navigation@3"
|
"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)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user