diff --git a/.gitignore b/.gitignore
index e2f07cd6e..ae7d194bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +1,18 @@
+# General
.vscode
_things
+# Tests and benchmarks
+tests/png
+tests/pdf
+tarpaulin-report.html
+
+# Rust
/target
bench/target
**/*.rs.bk
Cargo.lock
-tests/png
-tests/pdf
-tests/playground.*
-
-tarpaulin-report.html
+# Node
+node_modules
+package-lock.json
diff --git a/tools/test-helper/README.md b/tools/test-helper/README.md
new file mode 100644
index 000000000..8494edeb0
--- /dev/null
+++ b/tools/test-helper/README.md
@@ -0,0 +1,9 @@
+# Test helper
+
+This is a small VS Code extension that helps with managing Typst's test suite.
+When installed, three new buttons appear in the
+menubar for all `.typ` files in the `tests` folder.
+
+- Open test output: Opens the output and reference images of a test to the side.
+- Refresh test output: Re-runs the test and reloads the preview.
+- Approve test output: Copies the output into the reference folder and optimizes it with `oxipng`.
diff --git a/tools/test-helper/extension.js b/tools/test-helper/extension.js
new file mode 100644
index 000000000..775b2894b
--- /dev/null
+++ b/tools/test-helper/extension.js
@@ -0,0 +1,113 @@
+const vscode = require('vscode')
+const cp = require('child_process')
+
+function activate(context) {
+ let panel = null
+
+ function refreshPanel() {
+ const uri = vscode.window.activeTextEditor.document.uri
+ const { pngPath, refPath } = getPaths(uri)
+
+ if (panel && panel.visible) {
+ console.log('Refreshing WebView')
+ const pngSrc = panel.webview.asWebviewUri(pngPath)
+ const refSrc = panel.webview.asWebviewUri(refPath)
+ panel.webview.html = ''
+ panel.webview.html = getWebviewContent(pngSrc, refSrc)
+ }
+ }
+
+ const openCmd = vscode.commands.registerCommand("ShortcutMenuBar.openTestOutput", () => {
+ panel = vscode.window.createWebviewPanel(
+ 'testOutput',
+ 'Test output',
+ vscode.ViewColumn.Beside,
+ {}
+ )
+
+ refreshPanel()
+ })
+
+ const refreshCmd = vscode.commands.registerCommand("ShortcutMenuBar.refreshTestOutput", () => {
+ const uri = vscode.window.activeTextEditor.document.uri
+ const components = uri.fsPath.split('tests')
+ const dir = components[0]
+ const subPath = components[1]
+
+ cp.exec(
+ `cargo test --manifest-path ${dir}/Cargo.toml --test typeset ${subPath}`,
+ (err, stdout, stderr) => {
+ console.log(stdout)
+ console.log(stderr)
+ refreshPanel()
+ }
+ )
+ })
+
+ const approveCmd = vscode.commands.registerCommand("ShortcutMenuBar.approveTestOutput", () => {
+ const uri = vscode.window.activeTextEditor.document.uri
+ const { pngPath, refPath } = getPaths(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) => {
+ console.log(stdout)
+ console.log(stderr)
+ refreshPanel()
+ })
+ })
+ })
+
+ context.subscriptions.push(openCmd)
+ context.subscriptions.push(refreshCmd)
+ context.subscriptions.push(approveCmd)
+}
+
+function getPaths(uri) {
+ const pngPath = vscode.Uri.file(uri.path
+ .replace("tests/typ", "tests/png")
+ .replace(".typ", ".png"))
+
+ const refPath = vscode.Uri.file(uri.path
+ .replace("tests/typ", "tests/ref")
+ .replace(".typ", ".png"))
+
+ return { pngPath, refPath }
+}
+
+function getWebviewContent(pngSrc, refSrc) {
+ return `
+
+
+
+
+
+ Test output
+
+
+
+ Output image
+
+
+ Reference image
+
+
+
+ `
+}
+
+function deactivate() {}
+
+module.exports = { activate, deactivate }
diff --git a/tools/test-helper/images/approve-dark.svg b/tools/test-helper/images/approve-dark.svg
new file mode 100644
index 000000000..027ea6251
--- /dev/null
+++ b/tools/test-helper/images/approve-dark.svg
@@ -0,0 +1,3 @@
+
diff --git a/tools/test-helper/images/approve-light.svg b/tools/test-helper/images/approve-light.svg
new file mode 100644
index 000000000..288b65df1
--- /dev/null
+++ b/tools/test-helper/images/approve-light.svg
@@ -0,0 +1,3 @@
+
diff --git a/tools/test-helper/images/open-dark.svg b/tools/test-helper/images/open-dark.svg
new file mode 100644
index 000000000..e8631499b
--- /dev/null
+++ b/tools/test-helper/images/open-dark.svg
@@ -0,0 +1,3 @@
+
diff --git a/tools/test-helper/images/open-light.svg b/tools/test-helper/images/open-light.svg
new file mode 100644
index 000000000..1301f7113
--- /dev/null
+++ b/tools/test-helper/images/open-light.svg
@@ -0,0 +1,3 @@
+
diff --git a/tools/test-helper/images/refresh-dark.svg b/tools/test-helper/images/refresh-dark.svg
new file mode 100644
index 000000000..ccd525704
--- /dev/null
+++ b/tools/test-helper/images/refresh-dark.svg
@@ -0,0 +1,3 @@
+
diff --git a/tools/test-helper/images/refresh-light.svg b/tools/test-helper/images/refresh-light.svg
new file mode 100644
index 000000000..a041c7c6b
--- /dev/null
+++ b/tools/test-helper/images/refresh-light.svg
@@ -0,0 +1,3 @@
+
diff --git a/tools/test-helper/package.json b/tools/test-helper/package.json
new file mode 100644
index 000000000..55065838c
--- /dev/null
+++ b/tools/test-helper/package.json
@@ -0,0 +1,72 @@
+{
+ "name": "typst-test-helper",
+ "displayName": "Typst Test Helper",
+ "description": "Helps to run, compare and approve Typst tests.",
+ "version": "0.0.1",
+ "engines": {
+ "vscode": "^1.53.0"
+ },
+ "categories": [
+ "Other"
+ ],
+ "activationEvents": [
+ "onCommand:ShortcutMenuBar.openTestOutput",
+ "onCommand:ShortcutMenuBar.approveTestOutput",
+ "onCommand:ShortcutMenuBar.refreshTestOutput"
+ ],
+ "main": "./extension.js",
+ "contributes": {
+ "commands": [
+ {
+ "command": "ShortcutMenuBar.openTestOutput",
+ "title": "Open test output",
+ "category": "ShortcutMenuBar",
+ "icon": {
+ "light": "images/open-light.svg",
+ "dark": "images/open-dark.svg"
+ }
+ },
+ {
+ "command": "ShortcutMenuBar.refreshTestOutput",
+ "title": "Refresh test output",
+ "category": "ShortcutMenuBar",
+ "icon": {
+ "light": "images/refresh-light.svg",
+ "dark": "images/refresh-dark.svg"
+ }
+ },
+ {
+ "command": "ShortcutMenuBar.approveTestOutput",
+ "title": "Approve test output",
+ "category": "ShortcutMenuBar",
+ "icon": {
+ "light": "images/approve-light.svg",
+ "dark": "images/approve-dark.svg"
+ }
+ }
+ ],
+ "menus": {
+ "editor/title": [
+ {
+ "when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
+ "command": "ShortcutMenuBar.openTestOutput",
+ "group": "navigation@0"
+ },
+ {
+ "when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
+ "command": "ShortcutMenuBar.refreshTestOutput",
+ "group": "navigation@2"
+ },
+ {
+ "when": "resourceExtname == .typ && resourcePath =~ /.*tests.*/",
+ "command": "ShortcutMenuBar.approveTestOutput",
+ "group": "navigation@3"
+ }
+ ]
+ }
+ },
+ "devDependencies": {
+ "@types/vscode": "^1.53.0",
+ "@types/node": "^12.11.7"
+ }
+}