// Developer mode: the shell's syntax gate before a changed plugin is reloaded (the watcher's // debounce and reload order are tested natively in src-tauri/src/devmode.rs). import assert from 'node:assert/strict'; import { readdirSync, readFileSync } from 'node:fs'; import test from 'node:test'; import { checkPluginSyntax } from '../../../waybetter/packages/shell/src/frame/devModel.ts'; test('a plugin with a syntax error is reported with its file line and column, not reloaded', async () => { const broken = '/**\n * @name Broken\n */\nmodule.exports = class {\n start() {\n const x = ;\n }\n};\n'; const problem = await checkPluginSyntax(broken); assert.deepEqual({ line: problem?.line, column: problem?.column }, { line: 6, column: 15 }); assert.match(problem!.message, /^SyntaxError: Unexpected token/); const unclosed = await checkPluginSyntax('module.exports = class {\n start() {\n'); assert.ok(unclosed && unclosed.line >= 2, 'unexpected end of input'); }); test('valid plugins parse the way the host evaluates them (a CommonJS function body)', async () => { // Top-level return, modern syntax and a BOM are fine: the host wraps the file in a function. assert.equal(await checkPluginSyntax('if (window.x) return;\nmodule.exports = class { static A = 1; #p = 2; start() { this.a?.b ?? 0; } stop() {} };'), null); const dir = new URL('../src-tauri/bundled-plugins/', import.meta.url); for (const file of readdirSync(dir).filter(name => name.endsWith('.plugin.js'))) { assert.equal(await checkPluginSyntax(readFileSync(new URL(file, dir), 'utf8')), null, file); } });