rea/src/domain/javascript/electronStaticAnalysisOptio...

158 lines
5.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
describe.each(["webPreferences", "serviceName"] as const)(
"Electron %s option lookup",
(name) => {
const value =
name === "webPreferences"
? '{ preload: "./kept.js", sandbox: true }'
: '"kept"';
const explicit = `${name}: ${value}`;
const method = `${name}() { return ${value}; }`;
const getter = `get ${name}() { return ${value}; }`;
const setter = `set ${name}(value) {}`;
const explicitCases = [
["plain key", explicit],
["quoted key", `"${name}": ${value}`],
["computed string key", `["${name}"]: ${value}`],
["duplicate key", `${name}: null, ${explicit}`],
["computed duplicate key", `${name}: null, ["${name}"]: ${value}`],
["earlier spread", `...options, ${explicit}`],
["earlier computed identifier", `[key]: null, ${explicit}`],
["earlier computed expression", `[getKey()]: null, ${explicit}`],
["earlier getter", `${getter}, ${explicit}`],
["earlier method", `${method}, ${explicit}`],
["unrelated later key", `${explicit}, title: "title"`],
["unrelated computed literal", `${explicit}, ["title"]: null`],
["unrelated numeric key", `${explicit}, [1]: null`],
["unrelated empty key", `${explicit}, [""]: null`],
["unrelated getter", `${explicit}, get title() { return "title"; }`],
];
it.each(explicitCases)(
"retains the explicit value with %s",
(_, members) => {
const analysis = analyzeOption(name, `{ ${members} }`);
expect(analysis.parse_status).toBe("complete");
if (name === "webPreferences") {
expect(analysis.electron.browser_windows).toEqual([
expect.objectContaining({
options_status: "object-literal",
web_preferences_status: "object-literal",
preload_path: "./kept.js",
preload_resolution_context: "module-specifier",
web_preferences: [
{ name: "preload", value: literal("./kept.js") },
{ name: "sandbox", value: literal(true) },
],
}),
]);
} else {
expect(analysis.electron.utility_processes).toEqual([
expect.objectContaining({ service_name: "kept" }),
]);
}
},
);
const dynamicCases = [
["same-spelled computed binding", `[${name}]: ${value}`],
["different computed binding", `[key]: ${value}`],
["computed expression", `[getKey()]: ${value}`],
["unevaluated template key", `[\`${name}\`]: ${value}`],
["spread only", "...options"],
["later spread", `${explicit}, ...options`],
["later computed binding", `${explicit}, [key]: null`],
["later computed expression", `${explicit}, [getKey()]: null`],
["exact getter", getter],
["exact setter", setter],
["exact method", method],
["computed string method", `["${name}"]() { return ${value}; }`],
["overriding getter", `${explicit}, ${getter}`],
["overriding setter", `${explicit}, ${setter}`],
["overriding method", `${explicit}, ${method}`],
["dynamic explicit value", `${name}: dynamicValue`],
["null explicit value", `${name}: null`],
["duplicate null value", `${explicit}, ${name}: null`],
];
it.each(dynamicCases)("preserves uncertainty for %s", (_, members) => {
expectUnknownOption(name, `{ ${members} }`, "object-literal");
});
it.each(["null", "options"])(
"preserves nonliteral options %s",
(options) => {
expectUnknownOption(name, options, "dynamic");
},
);
it.each(["", "{}", '{ title: "title", ["other"]: null }'])(
"reports an omitted option in %s without inventing a default",
(options) => {
const analysis = analyzeOption(name, options);
expect(analysis.parse_status).toBe("complete");
if (name === "webPreferences")
expect(analysis.electron.browser_windows[0]).toMatchObject({
options_status: options === "" ? "missing" : "object-literal",
web_preferences_status: "missing",
web_preferences: [],
preload_path: null,
preload_resolution_context: null,
});
else
expect(
analysis.electron.utility_processes[0]?.service_name,
).toBeNull();
},
);
},
);
type OptionName = "webPreferences" | "serviceName";
const analyzeOption = (name: OptionName, options: string) =>
analyzeJavaScriptStaticSource(`
const webPreferences = "title";
const serviceName = "env";
${
name === "webPreferences"
? `new BrowserWindow(${options});`
: `utilityProcess.fork("./worker.js", []${options === "" ? "" : `, ${options}`});`
}
`);
const literal = (value: string | boolean) => ({
status: "literal",
value,
expression: null,
});
const expectUnknownOption = (
name: OptionName,
options: string,
optionsStatus: "object-literal" | "dynamic",
): void => {
const analysis = analyzeOption(name, options);
expect(analysis.parse_status).toBe("partial");
expect(analysis.parse_error_count).toBe(0);
expect(analysis.limitations.join(" ")).toContain("remain unknown");
if (name === "webPreferences")
expect(analysis.electron.browser_windows[0]).toMatchObject({
options_status: optionsStatus,
web_preferences_status: "dynamic",
web_preferences: [],
preload_path: null,
preload_resolution_context: null,
});
else
expect(analysis.electron.utility_processes[0]).toMatchObject({
module_path: "./worker.js",
module_expression: null,
service_name: null,
});
};