From acf711fbb05256e1031003643019340ce0eab028 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Mon, 9 Feb 2026 21:35:54 +0000 Subject: [PATCH] Refactor how dep resolution errors are displayed --- clean.js | 2 +- package.json | 4 +- packages/controller/src/ControlConnection.ts | 107 +++++++----- packages/lib/src/data/messages_mod.ts | 22 ++- .../web_ui/src/components/ModPackViewPage.tsx | 164 ++++++++++-------- test/messages/mod.test.js | 148 +++++++--------- 6 files changed, 234 insertions(+), 213 deletions(-) diff --git a/clean.js b/clean.js index 8e15dcba..1282c22f 100644 --- a/clean.js +++ b/clean.js @@ -104,7 +104,7 @@ async function main() { removeBuildArtifacts(); removeTestArtifacts(); }) - .command("fast", "Remove all build artifacts", () => {}, argv => { + .command("$0", "Remove all build artifacts", () => {}, argv => { DRY = argv.dry; removeBuildArtifacts(); }) diff --git a/package.json b/package.json index 672fabe8..fffb638e 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,7 @@ "ci-cover": "nyc --reporter=lcovonly pnpm run-script test", "lint": "eslint packages plugins test", "lint-fix": "eslint --fix packages plugins test", - "clean": "node ./clean.js fast", - "clean-tests": "node ./clean.js tests", - "clean-all": "node ./clean.js all", + "clean": "node ./clean.js", "docs": "typedoc", "bundle-dependencies": "bundle-dependencies" }, diff --git a/packages/controller/src/ControlConnection.ts b/packages/controller/src/ControlConnection.ts index 0a101b9b..260963dd 100644 --- a/packages/controller/src/ControlConnection.ts +++ b/packages/controller/src/ControlConnection.ts @@ -614,22 +614,21 @@ export default class ControlConnection extends BaseConnection { return modInfos; } + // eslint-disable-next-line complexity async handleModDependencyResolveRequest(request: lib.ModDependencyResolveRequest) { const {factorioVersion, checkForUpdates} = request; const searchQueue = [...request.mods]; const localInfos = [...this._controller.modStore.mods()]; - const _builtInModNames = lib.ModPack.getBuiltinModNames(factorioVersion); - const builtInModNames = new Set(_builtInModNames); - const skipSearch = new Set(_builtInModNames); + const builtInModNames = lib.ModPack.getBuiltinModNames(factorioVersion); + const skipSearch = new Set(builtInModNames); type ModPortalModType = InstanceType["mods"][number]; type ModPortalReleaseType = NonNullable[number]; const dependencyRequirements = new Map(); const optionalRequirements = new Map(); const candidateReleases = new Map(); - const incompatible = new Set(); - const invalid = new Set(); + const errors = new Map(); const _modPortalReleases = new Map(); async function fetchModReleases(modName: string) { @@ -650,10 +649,44 @@ export default class ControlConnection extends BaseConnection { return releases; } + function selectModRelease(releases: ModPortalModType | null, versionRange: lib.ModVersionRange) { + if (!releases || !releases.releases) { + return null; + } + + // Select the latest matching version + const release = releases.releases + .filter(info => versionRange.testVersion(info.version)) + .reduce((max, cur) => ( + max && lib.integerFullVersion(max.version) > lib.integerFullVersion(cur.version) ? max : cur + ), undefined); + + if (!release) { + return null; + } + + // Create the mod info class from the selected + try { + return lib.ModInfo.fromJSON({ + ...release.info_json, // includes dependencies + sha1: release.sha1, + version: release.version, + name: releases.name, + author: releases.owner, + title: releases.title, + }); + } catch { + // Can error if there are invalid dependencies + errors.set(releases.name, "invalidDependency"); + return null; + } + } + while (searchQueue.length > 0) { + // Dequeue the next mod, skip if incompatible const mod = searchQueue.shift()!; if (mod.incompatible) { - incompatible.add(mod.name); + errors.set(mod.name, "incompatible"); continue; } @@ -674,13 +707,13 @@ export default class ControlConnection extends BaseConnection { versionRange.combineVersion(mod.version); if (!versionRange.valid) { candidateReleases.delete(mod.name); - invalid.add(mod.name); + errors.set(mod.name, "unsatisfiable"); } } - // Do not add dependencies for invalids, optionals, skips, or valid candidates + // Do not add dependencies for errors, skips, optionals, or valid candidates const existingCandidate = candidateReleases.get(mod.name); - if (invalid.has(mod.name) || optionalRequirements.has(mod.name) || skipSearch.has(mod.name) || ( + if (errors.has(mod.name) || skipSearch.has(mod.name) || optionalRequirements.has(mod.name) || ( existingCandidate && versionRange.testVersion(existingCandidate.version) )) { continue; @@ -695,35 +728,11 @@ export default class ControlConnection extends BaseConnection { // Check if a mod poral version fits the requirements if (!candidate || checkForUpdates) { - const modReleases = await fetchModReleases(mod.name); - if (modReleases && modReleases.releases) { - const release = modReleases.releases - .filter(info => ( - info.info_json.factorio_version === factorioVersion - && versionRange.testVersion(info.version) - )) - .reduce((max, cur) => ( - max && lib.integerFullVersion(max.version) > lib.integerFullVersion(cur.version) ? max : cur - ), undefined); - - // We only select the mod portal release if it is more recent than the local candidate - if (release && (!candidate || lib.integerFullVersion(release.version) > candidate.integerVersion)) { - // eslint-disable-next-line max-depth - try { - candidate = lib.ModInfo.fromJSON({ - ...release.info_json, // Also includes dependencies - sha1: release.sha1, - version: release.version, - name: modReleases.name, - author: modReleases.owner, - title: modReleases.title, - }); - } catch { - // Can error with invalid dependencies, in which case we highlight it as invalid - // We modify the name to expose the error to the user. - invalid.add(`${modReleases.name} (Has invalid dependency)`); - } - } + const releases = await fetchModReleases(mod.name); + const release = selectModRelease(releases, versionRange); + // We only select the mod portal release if it is more recent than the local candidate + if (release && (!candidate || release.integerVersion > candidate.integerVersion)) { + candidate = release; } } @@ -739,14 +748,22 @@ export default class ControlConnection extends BaseConnection { } } + // Add the not found dependencies as errors + for (const modName of dependencyRequirements.keys()) { + if (!candidateReleases.has(modName) && !errors.has(modName)) { + errors.set(modName, "notFound"); + } + } + + // Remove incompatible dependencies if they aren't required by others + for (const [modName, error] of errors) { + if (error === "incompatible" && !dependencyRequirements.has(modName)) { + errors.delete(modName); + } + } + return new lib.ModDependencyResolveRequest.Response( - [...candidateReleases.values()], - [...invalid.values(), ...[...incompatible.values()].filter(mod => ( - candidateReleases.has(mod) || builtInModNames.has(mod) - ))], - [...dependencyRequirements.keys()].filter(mod => ( - !candidateReleases.has(mod) && !incompatible.has(mod) && !invalid.has(mod) - )), + [...candidateReleases.values()], errors, ); } diff --git a/packages/lib/src/data/messages_mod.ts b/packages/lib/src/data/messages_mod.ts index dc147b3d..096eace5 100644 --- a/packages/lib/src/data/messages_mod.ts +++ b/packages/lib/src/data/messages_mod.ts @@ -403,6 +403,10 @@ export class ModPortalDownloadRequest { } } +const ModDependencyResolveErrors = ["incompatible", "unsatisfiable", "invalidDependency", "notFound"] as const; +export const ModDependencyResolveErrorsSchema = Type.Union(ModDependencyResolveErrors.map(v => Type.Literal(v))); +export type ModDependencyResolveErrors = Static; + /** * Request mods dependencies to be resolved using the Factorio mod portal. * @@ -456,18 +460,26 @@ export class ModDependencyResolveRequest { static Response = class ModDependencyResolveResponse { constructor( public dependencies: ModInfo[], - public incompatible: string[], - public missing: string[], + public errors: Map, ) {} static jsonSchema = Type.Object({ "dependencies": Type.Array(ModInfo.jsonSchema), - "incompatible": Type.Array(Type.String()), - "missing": Type.Array(Type.String()), + "errors": Type.Record(Type.String(), ModDependencyResolveErrorsSchema), }); static fromJSON(json: Static) { - return new this(json.dependencies.map(d => ModInfo.fromJSON(d)), json.incompatible, json.missing); + return new this( + json.dependencies.map(d => ModInfo.fromJSON(d)), + new Map(Object.entries(json.errors)) + ); + } + + toJSON(): Static { + return { + dependencies: this.dependencies.map(d => d.toJSON()), + errors: Object.fromEntries(this.errors), + }; } }; } diff --git a/packages/web_ui/src/components/ModPackViewPage.tsx b/packages/web_ui/src/components/ModPackViewPage.tsx index fbf75bdf..8cccaf24 100644 --- a/packages/web_ui/src/components/ModPackViewPage.tsx +++ b/packages/web_ui/src/components/ModPackViewPage.tsx @@ -268,16 +268,28 @@ function DownloadDependenciesButton(props: DownloadDependenciesProps) { // State for the modal and dependencies const [open, setOpen] = useState(false); - const [missing, setMissing] = useState([]); - const [builtins, setBuiltins] = useState([]); - const [incompatible, setIncompatible] = useState([]); - const [factorioVersion, setFactorioVersion] = useState(null); - - // State for all mods fetched from backend const [error, setError] = useState(null); const [loading, setLoading] = useState(false); - const [allMods, setAllMods] = useState([]); - const [mods, setMods] = useState([]); + const [factorioVersion, setFactorioVersion] = useState(null); + + const [mods, setMods] = useState([]); // All resolved mods and dependencies + const [missing, setMissing] = useState([]); // Missing mods and dependencies, excluding builtin + + const [modErrors, setModErrors] = useState({ + notFound: new Array(), // Mods which were not found on the mod portal, can include builtins + incompatible: new Array(), // Mods which are required, but are marked incompatible by another + unsatisfiable: new Array(), // Mods which can not have their version range satisfied + invalidDependency: new Array(), // Mods with invalid dependency specs + }); + + function resetModErrors() { + setModErrors({ + notFound: [], + incompatible: [], + unsatisfiable: [], + invalidDependency: [], + }); + } // Get a valid factorio version useEffect(() => { @@ -294,10 +306,8 @@ function DownloadDependenciesButton(props: DownloadDependenciesProps) { if (!open || !factorioVersion) { // Clear if closed or no version setMods([]); - setAllMods([]); setMissing([]); - setBuiltins([]); - setIncompatible([]); + resetModErrors(); return; } @@ -309,21 +319,29 @@ function DownloadDependenciesButton(props: DownloadDependenciesProps) { lib.ModDependencyResolveRequest.fromModPackEnabled(props.modPack, props.checkForUpdates) ).then(response => { if (canceled) { return; } - setMods(response.dependencies.filter(dep => !modInfos.has(`${dep.name}_${dep.version}`))); - setIncompatible(response.incompatible); - setAllMods(response.dependencies); - setMissing(response.missing.filter(name => !props.builtInModNames.includes(name))); - setBuiltins(response.missing.filter(name => props.builtInModNames.includes(name))); + + const newModErrors = [...response.errors.entries()] + .reduce((acc, [modName, modError]) => { + acc[modError].push(modName); + return acc; + }, { + notFound: [], + incompatible: [], + unsatisfiable: [], + invalidDependency: [], + }); + + setModErrors(newModErrors); + setMods(response.dependencies); + setMissing(response.dependencies.filter(dep => !modInfos.has(`${dep.name}_${dep.version}`))); setLoading(false); }).catch(err => { if (canceled) { return; } notifyErrorHandler("Error fetching mods dependencies")(err); setError(err); setMods([]); - setAllMods([]); setMissing([]); - setBuiltins([]); - setIncompatible([]); + resetModErrors(); setLoading(false); }); @@ -334,51 +352,56 @@ function DownloadDependenciesButton(props: DownloadDependenciesProps) { // Re-fetch when modal opens or factorio version changes }, [open, factorioVersion, control]); - // Function to handle download from Mod Portal to Controller - function handleControllerDownload() { - if (!factorioVersion || !mods) { + // Fix any mod errors found after resolving dependencies + function fixModErrors() { + if (!factorioVersion) { return; } - // Enable any required builtins - for (const name of builtins) { - props.onChange({ - type: "mods.set", - name: name, - value: { ...props.modPack.mods.get(name)!, enabled: true }, - }); + // Enable any required builtins, they will be within the not found list + for (const modName of modErrors.notFound) { + if (props.builtInModNames.includes(modName)) { + props.onChange({ + type: "mods.set", + name: modName, + value: { ...props.modPack.mods.get(modName)!, enabled: true }, + }); + } } - // Disable any incompatible mods present - for (const name of incompatible) { - const modRecord = props.modPack.mods.get(name); + // Disable any incompatible mods + for (const modName of modErrors.incompatible) { + const modRecord = props.modPack.mods.get(modName); if (modRecord) { props.onChange({ type: "mods.set", - name: name, + name: modName, value: { ...modRecord, enabled: false }, }); } } // Add all dependencies to the mod pack - for (const mod of allMods) { - props.onChange({ - type: "mods.set", - name: mod.name, - value: { + for (const mod of mods) { + const existingMod = props.modPack.mods.get(mod.name); + if (!existingMod || existingMod.version !== mod.version) { + props.onChange({ + type: "mods.set", name: mod.name, - version: mod.version, - sha1: mod.sha1, - enabled: true, - }, - }); + value: { + name: mod.name, + version: mod.version, + sha1: mod.sha1, + enabled: true, + }, + }); + } } - // Download the missing dependencies + // Download any missing mods control.send( new lib.ModPortalDownloadRequest( - mods.map(mod => ({ + missing.map(mod => ({ name: mod.name, version: new lib.ModVersionEquality("=", mod.version), })), factorioVersion, @@ -386,12 +409,12 @@ function DownloadDependenciesButton(props: DownloadDependenciesProps) { ).then(() => { notification.success({ message: "Download complete", - description: `${mods.length} mods have been downloaded to the controller.`, + description: `${missing.length} mods have been downloaded to the controller.`, }); }).catch( notifyErrorHandler("Error starting mod download") ); - }; + } return <> {props.checkForUpdates && , ]} > @@ -429,30 +455,26 @@ function DownloadDependenciesButton(props: DownloadDependenciesProps) { style={{ marginBottom: 16 }} />} - {/* Display warning message if dependencies could not be found */} - {!loading && missing.length > 0 && - {missing.map(name =>
  • {name}
  • )} - } - type="warning" - showIcon - style={{ marginBottom: 16 }} - />} - - {/* Display warning message if dependencies are incompatible */} - {!loading && incompatible.length > 0 && - {incompatible.map(name =>
  • {name}
  • )} - } - type="warning" - showIcon - style={{ marginBottom: 16 }} - />} + {/* Display warning message for mod errors */} + {([ + ["notFound", "Failed to find dependencies:"], + ["incompatible", "Dependencies are incompatible:"], + ["unsatisfiable", "Dependencies could not be satisfied:"], + ["invalidDependency", "Dependencies has invalid specifications:"], + ] as const).map(([prop, message]) => { + const modNames = modErrors[prop]; + return modNames.length === 0 ? null : {modNames.map(name =>
  • {name}
  • )}} + style={{ marginBottom: 14 }} + />; + })} + {/* Display a table of the missing mods that need to be downloaded */} record.name} loading={loading} pagination={{ diff --git a/test/messages/mod.test.js b/test/messages/mod.test.js index 42b0d787..47bbaafb 100644 --- a/test/messages/mod.test.js +++ b/test/messages/mod.test.js @@ -168,19 +168,21 @@ describe("messages/mod", function() { ModInfo.fromJSON({ name: "bar", version: "2.0.0" }), ], ], - [[], ["foo-i"], ["foo-i", "bar-i"]], // incompatible - [[], ["foo-m"], ["foo-m", "bar-m"]], // missing + [ // errors + new Map([]), + new Map([["foo", "notFound"]]), + new Map([["foo", "unsatisfiable"], ["bar", "incompatible"], ["baz", "invalidDependency"]]), + ], ); it("should be round trip json serialisable", function() { testRoundTripJsonSerialisable(ModDependencyResolveRequest.Response, tests); }); it("should be construable", function() { - for (const [dependencies, incompatible, missing] of tests) { - const test = JSON.stringify([dependencies, missing, incompatible]); - const request = new ModDependencyResolveRequest.Response(dependencies, incompatible, missing); + for (const [dependencies, errors] of tests) { + const test = JSON.stringify([dependencies, errors]); + const request = new ModDependencyResolveRequest.Response(dependencies, errors); assert.deepEqual(request.dependencies, dependencies, test); - assert.deepEqual(request.incompatible, incompatible, test); - assert.deepEqual(request.missing, missing, test); + assert.deepEqual(request.errors, errors, test); } }); }); @@ -204,19 +206,15 @@ describe("messages/mod", function() { ) ); - assert.deepEqual(result.missing, ["base"]); - assert.deepEqual(result.incompatible, []); - - const expectedIds = [ - "root_1.0.0", "foo_1.0.0", "foo-dep_1.0.0", "bar_1.0.0", - "root-2_1.0.0", "bar-2_1.0.0", - ]; + assert.deepEqual(result.errors, new Map([ + ["base", "notFound"], + ])); const depIds = new Set(result.dependencies.map(mod => mod.id)); - assert.equal(depIds.size, expectedIds.length); - for (const modId of expectedIds) { - assert(depIds.has(modId), `Missing ${modId}`); - } + assert.deepEqual(depIds, new Set([ + "root_1.0.0", "foo_1.0.0", "foo-dep_1.0.0", "bar_1.0.0", + "root-2_1.0.0", "bar-2_1.0.0", + ])); } }); it("highlights dependencies with no suitable version", async function() { @@ -228,8 +226,10 @@ describe("messages/mod", function() { new lib.ModDependencyResolveRequest([new ModDependency("root")], factorioVersion) ); - assert.deepEqual(result.missing, ["base", "bar"]); - assert.deepEqual(result.incompatible, []); + assert.deepEqual(result.errors, new Map([ + ["base", "notFound"], + ["bar", "notFound"], + ])); assert.equal(result.dependencies.length, 1); assert.equal(result.dependencies[0].id, "root_1.0.0"); @@ -243,8 +243,10 @@ describe("messages/mod", function() { new lib.ModDependencyResolveRequest([new ModDependency("root")], factorioVersion) ); - assert.deepEqual(result.missing, ["base", "foo"]); - assert.deepEqual(result.incompatible, []); + assert.deepEqual(result.errors, new Map([ + ["base", "notFound"], + ["foo", "notFound"], + ])); assert.equal(result.dependencies.length, 1); assert.equal(result.dependencies[0].id, "root_1.0.0"); @@ -260,16 +262,13 @@ describe("messages/mod", function() { new lib.ModDependencyResolveRequest([new ModDependency("root")], factorioVersion) ); - assert.deepEqual(result.missing, ["base"]); - assert.deepEqual(result.incompatible, ["foo-dep"]); - - const expectedIds = ["root_1.0.0", "foo_1.0.0", "foo-dep_1.0.0"]; + assert.deepEqual(result.errors, new Map([ + ["base", "notFound"], + ["foo-dep", "incompatible"], + ])); const depIds = new Set(result.dependencies.map(mod => mod.id)); - assert.equal(depIds.size, expectedIds.length); - for (const modId of expectedIds) { - assert(depIds.has(modId), `Missing ${modId}`); - } + assert.deepEqual(depIds, new Set(["root_1.0.0", "foo_1.0.0"])); } }); it("highlights dependencies with conflicting versions", async function() { @@ -282,16 +281,13 @@ describe("messages/mod", function() { new lib.ModDependencyResolveRequest([new ModDependency("root")], factorioVersion) ); - assert.deepEqual(result.missing, ["base"]); - assert.deepEqual(result.incompatible, ["dep"]); - - const expectedIds = ["root_1.0.0", "foo_1.0.0", "bar_1.0.0"]; + assert.deepEqual(result.errors, new Map([ + ["base", "notFound"], + ["dep", "unsatisfiable"], + ])); const depIds = new Set(result.dependencies.map(mod => mod.id)); - assert.equal(depIds.size, expectedIds.length); - for (const modId of expectedIds) { - assert(depIds.has(modId), `Missing ${modId}`); - } + assert.deepEqual(depIds, new Set(["root_1.0.0", "foo_1.0.0", "bar_1.0.0"])); } }); it("highlights incompatible builtin mods", async function() { @@ -303,16 +299,12 @@ describe("messages/mod", function() { new lib.ModDependencyResolveRequest([new ModDependency("root")], factorioVersion) ); - assert.deepEqual(result.missing, []); - assert.deepEqual(result.incompatible, ["base"]); - - const expectedIds = ["root_1.0.0", "foo_1.0.0"]; + assert.deepEqual(result.errors, new Map([ + ["base", "incompatible"], + ])); const depIds = new Set(result.dependencies.map(mod => mod.id)); - assert.equal(depIds.size, expectedIds.length); - for (const modId of expectedIds) { - assert(depIds.has(modId), `Missing ${modId}`); - } + assert.deepEqual(depIds, new Set(["root_1.0.0", "foo_1.0.0"])); } }); it("does not ignore optional dependencies when required by another", async function() { @@ -325,16 +317,12 @@ describe("messages/mod", function() { new lib.ModDependencyResolveRequest([new ModDependency("root")], factorioVersion) ); - assert.deepEqual(result.missing, ["base"]); - assert.deepEqual(result.incompatible, []); - - const expectedIds = ["root_1.0.0", "foo_1.0.0", "baz_1.0.0"]; + assert.deepEqual(result.errors, new Map([ + ["base", "notFound"], + ])); const depIds = new Set(result.dependencies.map(mod => mod.id)); - assert.equal(depIds.size, expectedIds.length); - for (const modId of expectedIds) { - assert(depIds.has(modId), `Missing ${modId}`); - } + assert.deepEqual(depIds, new Set(["root_1.0.0", "foo_1.0.0", "baz_1.0.0"])); } }); it("prefers the locally installed version if matching", async function() { @@ -347,16 +335,12 @@ describe("messages/mod", function() { new lib.ModDependencyResolveRequest([new ModDependency("root")], factorioVersion) ); - assert.deepEqual(result.missing, ["base"]); - assert.deepEqual(result.incompatible, []); - - const expectedIds = ["root_1.0.0", "foo_1.0.0"]; + assert.deepEqual(result.errors, new Map([ + ["base", "notFound"], + ])); const depIds = new Set(result.dependencies.map(mod => mod.id)); - assert.equal(depIds.size, expectedIds.length); - for (const modId of expectedIds) { - assert(depIds.has(modId), `Missing ${modId}`); - } + assert.deepEqual(depIds, new Set(["root_1.0.0", "foo_1.0.0"])); } }); it("prefers the mod portal version when checking for updates", async function() { @@ -369,16 +353,12 @@ describe("messages/mod", function() { new lib.ModDependencyResolveRequest([new ModDependency("root")], factorioVersion, true) ); - assert.deepEqual(result.missing, ["base"]); - assert.deepEqual(result.incompatible, []); - - const expectedIds = ["root_1.0.0", "foo_2.0.0"]; + assert.deepEqual(result.errors, new Map([ + ["base", "notFound"], + ])); const depIds = new Set(result.dependencies.map(mod => mod.id)); - assert.equal(depIds.size, expectedIds.length); - for (const modId of expectedIds) { - assert(depIds.has(modId), `Missing ${modId}`); - } + assert.deepEqual(depIds, new Set(["root_1.0.0", "foo_2.0.0"])); } }); it("prefers the latest mod portal version when using the mod portal", async function() { @@ -409,16 +389,12 @@ describe("messages/mod", function() { new lib.ModDependencyResolveRequest([new ModDependency("root")], factorioVersion) ); - assert.deepEqual(result.missing, ["base"]); - assert.deepEqual(result.incompatible, []); - - const expectedIds = ["root_1.0.0", "foo_2.0.0", "bar_1.5.0"]; + assert.deepEqual(result.errors, new Map([ + ["base", "notFound"], + ])); const depIds = new Set(result.dependencies.map(mod => mod.id)); - assert.equal(depIds.size, expectedIds.length); - for (const modId of expectedIds) { - assert(depIds.has(modId), `Missing ${modId}`); - } + assert.deepEqual(depIds, new Set(["root_1.0.0", "foo_2.0.0", "bar_1.5.0"])); } }); it("rejects when a network error occurs", async function() { @@ -440,22 +416,18 @@ describe("messages/mod", function() { new lib.ModDependencyResolveRequest([new ModDependency("pymodpack = 3.0.0")], "2.0") ); - assert.deepEqual(result.missing, ["base"]); - assert.deepEqual(result.incompatible, ["space-age"]); + assert.deepEqual(result.errors, new Map([ + ["base", "notFound"], + ])); - const expectedNames = [ // Can't use ids as the versions may change + const depNames = new Set(result.dependencies.map(mod => mod.name)); + assert.deepEqual(depNames, new Set([ // Can't use ids as the versions may change "pymodpack", "pypostprocessing", "pyalternativeenergy", "pyalternativeenergygraphics", "pyalienlife", "pyalienlifegraphics", "pyalienlifegraphics2", "pyalienlifegraphics3", "pycoalprocessing", "pycoalprocessinggraphics", "pyfusionenergy", "pyfusionenergygraphics", "pypetroleumhandling", "pypetroleumhandlinggraphics", "pyrawores", "pyraworesgraphics", "pyhightech", "pyhightechgraphics", "pyindustry", "pyindustrygraphics", - ]; - - const depNames = new Set(result.dependencies.map(mod => mod.name)); - assert.equal(depNames.size, expectedNames.length); - for (const modName of expectedNames) { - assert(depNames.has(modName), `Missing ${modName}`); - } + ])); }); }); });