/** * @file native-dialog.test.js * @description Unit tests for the server-side native folder/file picker: the * argv each platform gets, the fallback from zenity to kdialog, Cancel versus a * chosen path, and the "no dialog program" answer the client falls back on. * @author Nguyễn Ngọc Trí Vĩ */ const { describe, it, afterEach } = require("node:test"); const assert = require("node:assert/strict"); const os = require("node:os"); const nd = require("../lib/native-dialog"); const desktop = require("../lib/desktop-env"); const linux = process.platform === "linux"; describe("native-dialog", () => { afterEach(() => nd.__setExecImpl(null)); it("starts in the given folder, a file's folder, or home", () => { assert.equal(nd.startDir(os.tmpdir()), os.tmpdir()); assert.equal(nd.startDir("/definitely/not/here/x"), os.homedir()); assert.equal(nd.startDir("~"), os.homedir()); assert.equal(nd.startDir(undefined), os.homedir()); }); it("builds zenity then kdialog on Linux, the request's folder one argv element", () => { const [[z, zargs], [k, kargs]] = nd.dialogCommands("dir", "/tmp/a b", "linux"); assert.equal(z, "zenity"); assert.deepEqual(zargs, [ "--file-selection", "--title=Select a folder", "--filename=/tmp/a b/", "--directory", ]); assert.equal(k, "kdialog"); assert.deepEqual(kargs.slice(0, 2), ["--getexistingdirectory", "/tmp/a b"]); assert.ok(!nd.dialogCommands("file", "/tmp", "linux")[0][1].includes("--directory")); }); it("passes the folder to osascript as an argument, never inside the script", () => { const [[cmd, args]] = nd.dialogCommands("file", '/x"; do shell script "id', "darwin"); assert.equal(cmd, "osascript"); assert.equal(args.at(-1), '/x"; do shell script "id'); assert.ok(!args.slice(0, -2).join(" ").includes("shell script")); }); it("brings osascript forward before the macOS dialog, so it does not open behind the browser", () => { const [[, args]] = nd.dialogCommands("dir", "/x", "darwin"); const script = args.filter((_, i) => args[i - 1] === "-e"); assert.ok(script.indexOf("activate") > -1); assert.ok(script.indexOf("activate") < script.findIndex((s) => s.includes("choose folder"))); }); it("gives the Windows dialog a foreground TopMost owner, so it does not open behind the browser", () => { for (const kind of ["dir", "file"]) { const [[cmd, args]] = nd.dialogCommands(kind, "C:\\x", "win32"); assert.equal(cmd, "powershell.exe"); assert.equal(args.at(-2), "-EncodedCommand"); const script = Buffer.from(args.at(-1), "base64").toString("utf16le"); assert.match( script, /\$o\.TopMost = \$true;[\s\S]*ShowWindow\(\$o\.Handle, 1\)[\s\S]*\$o\.Show\(\)[\s\S]*Front\(\$o\.Handle\)[\s\S]*\$d\.ShowDialog\(\$o\)/ ); assert.match(script, /AttachThreadInput/); assert.ok(!script.includes("C:\\x"), "the folder travels in env, never the script"); } }); it( "answers the chosen path, null on Cancel, and falls through a missing program", { skip: !linux, }, async () => { desktop.__setExecImpl(() => "DISPLAY=:0\n"); const tried = []; nd.__setExecImpl(async (cmd) => { tried.push(cmd); return cmd === "zenity" ? { missing: true } : { code: 0, stdout: "/home/u/proj\n" }; }); assert.equal(await nd.pickPath("dir"), "/home/u/proj"); assert.deepEqual(tried, ["zenity", "kdialog"]); nd.__setExecImpl(async () => ({ code: 1, stdout: "" })); assert.equal(await nd.pickPath("file"), null); nd.__setExecImpl(async () => ({ missing: true })); await assert.rejects(nd.pickPath("dir"), { code: "ENODIALOG" }); } ); it( "answers ENODIALOG, not a Cancel, when the program could not reach the desktop", { skip: !linux }, async () => { desktop.__setExecImpl(() => "DISPLAY=:0\n"); nd.__setExecImpl(async () => ({ code: 1, stdout: "", stderr: "(zenity:1): Gtk-WARNING **: cannot open display: :0\n", })); await assert.rejects(nd.pickPath("dir"), { code: "ENODIALOG" }); } ); it("passes the abort signal through to the dialog program", { skip: !linux }, async () => { desktop.__setExecImpl(() => "DISPLAY=:0\n"); const ac = new AbortController(); let seen; nd.__setExecImpl(async (cmd, args, env, signal) => { seen = signal; return { code: 1, stdout: "", stderr: "" }; }); assert.equal(await nd.pickPath("dir", undefined, ac.signal), null); assert.equal(seen, ac.signal); }); it("tells a Cancel from a failure per program", () => { assert.equal(nd.isCancel("zenity", ""), true); assert.equal( nd.isCancel("zenity", "Gtk-Message: GtkDialog mapped without a transient parent"), true ); assert.equal(nd.isCancel("kdialog", "qt.qpa.xcb: could not connect to display :1"), false); assert.equal(nd.isCancel("osascript", "execution error: User canceled. (-128)"), true); assert.equal( nd.isCancel("osascript", "execution error: No user interaction allowed. (-1713)"), false ); assert.equal(nd.isCancel("powershell.exe", ""), true); assert.equal(nd.isCancel("powershell.exe", "Add-Type : Cannot add type"), false); }); });