Skip to content
Open
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
53 changes: 53 additions & 0 deletions src/services/__tests__/firefox-api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { afterAll, beforeEach, describe, expect, it, spyOn } from "bun:test";

import { createFirefoxApi } from "../firefox-api";

const fetchSpy = spyOn(globalThis, "fetch");

const addonResponse = (overrides: Record<string, unknown> = {}) => ({
id: 123,
slug: "example-addon",
guid: "example-addon@example.com",
icon_url: "https://addons.mozilla.org/user-media/addon_icons/0/123-64.png",
last_updated: "2026-09-30T00:00:00Z",
description: { "en-US": "Long description" },
name: { "en-US": "Example Addon" },
summary: { "en-US": "Short description" },
ratings: { average: 5, bayesian_average: 5, count: 1, text_count: 1 },
url: "https://addons.mozilla.org/en-US/firefox/addon/example-addon/",
current_version: { version: "1.0.0" },
average_daily_users: 3,
previews: [],
...overrides,
});

describe("Firefox API", () => {
beforeEach(() => {
fetchSpy.mockReset();
});

afterAll(() => {
fetchSpy.mockRestore();
});

it("should return the first localized value for each text field", async () => {
fetchSpy.mockResolvedValue(Response.json(addonResponse()));

const addon = await createFirefoxApi().getAddon("example-addon");

expect(addon.name).toBe("Example Addon");
expect(addon.shortDescription).toBe("Short description");
expect(addon.longDescription).toBe("Long description");
});

// AMO returns `null` instead of a locale map when the listing leaves a field empty
it("should return an empty string when a localized field is null", async () => {
fetchSpy.mockResolvedValue(Response.json(addonResponse({ description: null, summary: null })));

const addon = await createFirefoxApi().getAddon("example-addon");

expect(addon.name).toBe("Example Addon");
expect(addon.shortDescription).toBe("");
expect(addon.longDescription).toBe("");
});
});
14 changes: 9 additions & 5 deletions src/services/firefox-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,21 @@ export interface FirefoxApi {
}

export function createFirefoxApi(): FirefoxApi {
// AMO returns `null` instead of a locale map when a listing leaves a field empty
const firstLocalizedValue = (value: { [locale: string]: string } | null): string =>
Object.values(value ?? {})[0] ?? "";

const toGqlFirefoxAddon = (res: GetAddon200Response): Gql.FirefoxAddon => ({
id: String(res.id),
slug: res.slug,
guid: res.guid,
iconUrl: res.icon_url,
lastUpdated: res.last_updated,
longDescription: Object.values<string>(res.description)[0]!,
name: Object.values<string>(res.name)[0]!,
longDescription: firstLocalizedValue(res.description),
name: firstLocalizedValue(res.name),
rating: res.ratings.average,
reviewCount: res.ratings.count,
shortDescription: Object.values<string>(res.summary)[0]!,
shortDescription: firstLocalizedValue(res.summary),
storeUrl: res.url,
version: res.current_version.version,
users: res.average_daily_users,
Expand Down Expand Up @@ -105,7 +109,7 @@ type GetAddon200Response = {
default_locale: string;
description: {
[locale: string]: string;
};
} | null;
developer_comments: string | null;
edit_url: string;
guid: string;
Expand Down Expand Up @@ -153,7 +157,7 @@ type GetAddon200Response = {
status: string;
summary: {
[locale: string]: string;
};
} | null;
support_email: {
[locale: string]: string;
};
Expand Down
Loading