/** * @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( "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); }); });