Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import CloudVersionTargetForm from "renderer/components/CloudVersionTargetForm";
import { VersionFilters } from "renderer/components/VersionTargetForm";
import { SelectedFlags } from "shared/backend/types";
import { compareFirmwareReleases } from "shared/firmwareVersions";

type Props = {
onChanged: (values: {
Expand Down Expand Up @@ -62,7 +63,7 @@ const CloudFirmwareReleasesPicker: React.FC<Props> = ({

const releases = targetsQuery.data?.cloudTargets.releases
.filter(({ isPrerelease }) => !isPrerelease || filters.includePrereleases)
.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
.sort(compareFirmwareReleases);
const selectedRelease = releases?.find(({ id }) => id === version);

// Select first release if none is selected or current version not in releases.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import VersionTargetForm, {
VersionFilters,
} from "renderer/components/VersionTargetForm";
import { useTranslation } from "react-i18next";
import { compareFirmwareReleases } from "shared/firmwareVersions";

type Props = {
onChanged: (values: {
Expand Down Expand Up @@ -68,8 +69,7 @@ const FirmwareReleasesPicker: React.FC<Props> = ({
(release) => release.id === version
)?.isPrerelease;

// TODO: sort releases by date, need to add date to schema
const sortedReleases = useSorted(releases, () => 0);
const sortedReleases = useSorted(releases, compareFirmwareReleases);
const selectedFirmware = releases?.find((release) => release.id === version);
const targets =
releaseTargetsQuery.data?.edgeTxRelease?.firmwareBundle.targets.map(
Expand Down
69 changes: 69 additions & 0 deletions src/shared/__tests__/firmwareVersions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { describe, it, expect } from "vitest";
import { compareFirmwareReleases } from "shared/firmwareVersions";
import { exampleReleasesList } from "test-utils/data";

describe("compareFirmwareReleases", () => {
it("sorts an interleaved list into descending semver order", () => {
const releases = [
{ id: "v2.12.0" },
{ id: "v2.11.6" },
{ id: "v2.12.1" },
{ id: "v2.11.5" },
{ id: "v2.12.2" },
];

expect(releases.sort(compareFirmwareReleases).map((r) => r.id)).toEqual([
"v2.12.2",
"v2.12.1",
"v2.12.0",
"v2.11.6",
"v2.11.5",
]);
});

it("always sorts non-semver ids like 'nightly' first", () => {
const releases = [{ id: "v2.12.2" }, { id: "nightly" }, { id: "v2.12.0" }];

expect(releases.sort(compareFirmwareReleases).map((r) => r.id)).toEqual([
"nightly",
"v2.12.2",
"v2.12.0",
]);
});

it("sorts prereleases below their corresponding final release", () => {
const releases = [
{ id: "v2.5.0-rc1" },
{ id: "v2.5.0" },
{ id: "v2.5.0-rc3" },
{ id: "v2.5.0-rc2" },
];

expect(releases.sort(compareFirmwareReleases).map((r) => r.id)).toEqual([
"v2.5.0",
"v2.5.0-rc3",
"v2.5.0-rc2",
"v2.5.0-rc1",
]);
});

it("reproduces the canonical order of the example releases mock data", () => {
const shuffled = [...exampleReleasesList].reverse();

expect(shuffled.sort(compareFirmwareReleases).map((r) => r.id)).toEqual(
exampleReleasesList.map((r) => r.id)
);
});

it("does not throw on empty, single-element, or already-sorted arrays", () => {
expect([].sort(compareFirmwareReleases)).toEqual([]);
expect([{ id: "v1.0.0" }].sort(compareFirmwareReleases)).toEqual([
{ id: "v1.0.0" },
]);
expect(
[{ id: "v2.0.0" }, { id: "v1.0.0" }]
.sort(compareFirmwareReleases)
.map((r) => r.id)
).toEqual(["v2.0.0", "v1.0.0"]);
});
});
22 changes: 22 additions & 0 deletions src/shared/firmwareVersions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import semver from "semver";

/**
* Compares two firmware releases by their id (git tag name, e.g. "v2.12.2")
* for descending version order (latest first).
*
* Non-semver ids (e.g. "nightly", a rolling build) always sort first,
* ahead of every numbered release.
*/
export const compareFirmwareReleases = <T extends { id: string }>(
a: T,
b: T
): number => {
const aVersion = semver.valid(a.id);
const bVersion = semver.valid(b.id);

if (!aVersion && !bVersion) return 0;
if (!aVersion) return -1;
if (!bVersion) return 1;

return semver.rcompare(aVersion, bVersion);
};
Loading