mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 23:24:52 +09:00
Refactor how dep resolution errors are displayed
This commit is contained in:
6 files changed
+234
-213
No files matched your search
@@ -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();
|
||||
})
|
||||
|
||||
+1
-3
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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<typeof lib.ModPortalGetAllRequest.Response>["mods"][number];
|
||||
type ModPortalReleaseType = NonNullable<ModPortalModType["releases"]>[number];
|
||||
const dependencyRequirements = new Map<string, lib.ModVersionRange>();
|
||||
const optionalRequirements = new Map<string, lib.ModVersionRange>();
|
||||
const candidateReleases = new Map<string, lib.ModInfo>();
|
||||
const incompatible = new Set<string>();
|
||||
const invalid = new Set<string>();
|
||||
const errors = new Map<string, lib.ModDependencyResolveErrors>();
|
||||
|
||||
const _modPortalReleases = new Map<string, ModPortalModType | null>();
|
||||
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<ModPortalReleaseType | undefined>((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<ModPortalReleaseType | undefined>((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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<typeof ModDependencyResolveErrorsSchema>;
|
||||
|
||||
/**
|
||||
* 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<string, ModDependencyResolveErrors>,
|
||||
) {}
|
||||
|
||||
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<typeof this.jsonSchema>) {
|
||||
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<typeof ModDependencyResolveResponse.jsonSchema> {
|
||||
return {
|
||||
dependencies: this.dependencies.map(d => d.toJSON()),
|
||||
errors: Object.fromEntries(this.errors),
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -268,16 +268,28 @@ function DownloadDependenciesButton(props: DownloadDependenciesProps) {
|
||||
|
||||
// State for the modal and dependencies
|
||||
const [open, setOpen] = useState(false);
|
||||
const [missing, setMissing] = useState<string[]>([]);
|
||||
const [builtins, setBuiltins] = useState<string[]>([]);
|
||||
const [incompatible, setIncompatible] = useState<string[]>([]);
|
||||
const [factorioVersion, setFactorioVersion] = useState<lib.ApiVersion | null>(null);
|
||||
|
||||
// State for all mods fetched from backend
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [allMods, setAllMods] = useState<lib.ModInfo[]>([]);
|
||||
const [mods, setMods] = useState<lib.ModInfo[]>([]);
|
||||
const [factorioVersion, setFactorioVersion] = useState<lib.ApiVersion | null>(null);
|
||||
|
||||
const [mods, setMods] = useState<lib.ModInfo[]>([]); // All resolved mods and dependencies
|
||||
const [missing, setMissing] = useState<lib.ModInfo[]>([]); // Missing mods and dependencies, excluding builtin
|
||||
|
||||
const [modErrors, setModErrors] = useState({
|
||||
notFound: new Array<string>(), // Mods which were not found on the mod portal, can include builtins
|
||||
incompatible: new Array<string>(), // Mods which are required, but are marked incompatible by another
|
||||
unsatisfiable: new Array<string>(), // Mods which can not have their version range satisfied
|
||||
invalidDependency: new Array<string>(), // 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<typeof modErrors>((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 && <Button
|
||||
@@ -415,8 +438,11 @@ function DownloadDependenciesButton(props: DownloadDependenciesProps) {
|
||||
<Button
|
||||
key="start"
|
||||
type="primary"
|
||||
disabled={loading || mods.length === 0}
|
||||
onClick={() => { setOpen(false); handleControllerDownload(); }}
|
||||
disabled={loading || missing.length === 0}
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
fixModErrors();
|
||||
}}
|
||||
>Start Download</Button>,
|
||||
]}
|
||||
>
|
||||
@@ -429,30 +455,26 @@ function DownloadDependenciesButton(props: DownloadDependenciesProps) {
|
||||
style={{ marginBottom: 16 }}
|
||||
/>}
|
||||
|
||||
{/* Display warning message if dependencies could not be found */}
|
||||
{!loading && missing.length > 0 && <Alert
|
||||
message="Failed to Find Dependencies:"
|
||||
description={<ul>
|
||||
{missing.map(name => <li>{name}</li>)}
|
||||
</ul>}
|
||||
type="warning"
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
/>}
|
||||
|
||||
{/* Display warning message if dependencies are incompatible */}
|
||||
{!loading && incompatible.length > 0 && <Alert
|
||||
message="Incompatible Dependencies Found:"
|
||||
description={<ul>
|
||||
{incompatible.map(name => <li>{name}</li>)}
|
||||
</ul>}
|
||||
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 : <Alert
|
||||
showIcon
|
||||
type="warning"
|
||||
message={message}
|
||||
description={<ul>{modNames.map(name => <li>{name}</li>)}</ul>}
|
||||
style={{ marginBottom: 14 }}
|
||||
/>;
|
||||
})}
|
||||
|
||||
{/* Display a table of the missing mods that need to be downloaded */}
|
||||
<Table
|
||||
dataSource={mods}
|
||||
dataSource={missing}
|
||||
rowKey={record => record.name}
|
||||
loading={loading}
|
||||
pagination={{
|
||||
|
||||
+60
-88
@@ -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}`);
|
||||
}
|
||||
]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user