const fs = require("fs"); const os = require("os"); const path = require("path"); const vm = require("vm"); const webpack = require("webpack"); const createUserscriptConfig = require("../../webpack.userscript.config"); const userscriptMeta = require("./userscript.meta"); const ARTIFACT_PATH = path.join(__dirname, "Return Youtube Dislike.user.js"); function compile(config) { return new Promise((resolve, reject) => { const compiler = webpack(config); compiler.run((error, stats) => { const finish = (closeError) => { if (error || closeError) { reject(error || closeError); return; } if (stats.hasErrors()) { reject(new Error(stats.toString({ all: false, errors: true }))); return; } resolve(); }; compiler.close(finish); }); }); } describe("generated userscript artifact", () => { let artifact; beforeAll(() => { artifact = fs.readFileSync(ARTIFACT_PATH, "utf8"); }); it("contains the candidate metadata and required modern and legacy grants", () => { expect(artifact.startsWith("// ==UserScript==\n")).toBe(true); expect(artifact).toContain(`// @version ${userscriptMeta.version}`); expect(userscriptMeta.version).toBe("3.2.0"); expect(artifact).toContain(`// @downloadURL ${userscriptMeta.downloadURL}`); expect(artifact).toContain(`// @updateURL ${userscriptMeta.updateURL}`); for (const grant of userscriptMeta.grants) { expect(artifact).toContain(`// @grant ${grant}`); } expect(artifact).not.toContain("@grant GM.xmlHttpRequest"); expect(artifact).not.toContain("@connect"); }); it("is a standalone syntactically valid script with editable user options", () => { expect(() => new vm.Script(artifact, { filename: ARTIFACT_PATH })).not.toThrow(); expect(artifact).not.toMatch(/^\s*(?:import|export)\s/m); expect(artifact).toContain("BEGIN USER OPTIONS"); expect(artifact).toContain("disableVoteSubmission: false"); expect(artifact).not.toContain("data-ryd-userscript-version"); }); it("builds an unpublished live-test artifact with a runtime marker and no update URLs", async () => { const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "ryd-userscript-live-build-")); try { const config = createUserscriptConfig({ liveTest: "true" }, { mode: "production" }); config.output = { ...config.output, path: temporaryDirectory }; await compile(config); const liveArtifact = fs.readFileSync(path.join(temporaryDirectory, config.output.filename), "utf8"); expect(liveArtifact).toContain("data-ryd-userscript-version"); expect(liveArtifact).toContain("// @name Return YouTube Dislike [Live Test]"); expect(liveArtifact).not.toContain("// @downloadURL"); expect(liveArtifact).not.toContain("// @updateURL"); } finally { fs.rmSync(temporaryDirectory, { recursive: true, force: true }); } }); it("rebuilds deterministically", async () => { const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "ryd-userscript-build-")); try { const config = createUserscriptConfig({}, { mode: "production" }); config.output = { ...config.output, path: temporaryDirectory }; await compile(config); const rebuilt = fs.readFileSync(path.join(temporaryDirectory, config.output.filename), "utf8"); expect(rebuilt).toBe(artifact); } finally { fs.rmSync(temporaryDirectory, { recursive: true, force: true }); } }); });