refactor: app category metadata to use Record<AppCategories, AppCategoryEntry> for exhaustive type safety (#29558)

* refactor: derive category metadata from single Record source of truth

* test: add test for category order and entries sync

* fix: check for duplicates
This commit is contained in:
Anirban Singha authored and GitHub committed 2026-06-29 20:25:39 +05:30
1 parent b97cd6203d
commit 5d6d4d30cc
3 files changed
+70 -76

No files matched your search

@@ -0,0 +1,18 @@
import { describe, it, expect } from "vitest";
import { APP_CATEGORY_ENTRIES, CATEGORY_ORDER } from "./getAppCategories";
import type { ActiveAppCategoryKeys } from "./getAppCategories"
describe("getAppCategories", () => {
it("should have CATEGORY_ORDER that includes all ActiveAppCategoryKeys", () => {
const categoryEntriesKeys = Object.keys(APP_CATEGORY_ENTRIES) as ActiveAppCategoryKeys[]
const categoryOrderKeys = CATEGORY_ORDER
const missingInOrder = categoryEntriesKeys.filter((key) => !categoryOrderKeys.includes(key))
const missingInEntries = categoryOrderKeys.filter((key) => !categoryEntriesKeys.includes(key))
expect(categoryOrderKeys).toHaveLength(categoryEntriesKeys.length)
expect(missingInEntries).toHaveLength(0)
expect(missingInOrder).toHaveLength(0)
})
})
+31 -54
View File
@@ -8,6 +8,8 @@ function getHref(baseURL: string, category: string, useQueryParam: boolean) {
return useQueryParam ? `${baseUrlParsed.toString()}` : `${baseURL}/${category}`;
}
export type ActiveAppCategoryKeys = Exclude<AppCategories, "video" | "web3">;
type AppCategoryEntry = {
name: AppCategories;
href: string;
@@ -15,59 +17,34 @@ type AppCategoryEntry = {
"data-testid": string;
};
const getAppCategories = (baseURL: string, useQueryParam: boolean): AppCategoryEntry[] => {
// Manually sorted alphabetically, but leaving "Other" at the end
// TODO: Refactor and type with Record<AppCategories, AppCategoryEntry> to enforce consistency
return [
{
name: "analytics",
href: getHref(baseURL, "analytics", useQueryParam),
icon: "chart-bar",
"data-testid": "analytics",
},
{
name: "automation",
href: getHref(baseURL, "automation", useQueryParam),
icon: "share-2",
"data-testid": "automation",
},
{
name: "calendar",
href: getHref(baseURL, "calendar", useQueryParam),
icon: "calendar",
"data-testid": "calendar",
},
{
name: "conferencing",
href: getHref(baseURL, "conferencing", useQueryParam),
icon: "video",
"data-testid": "conferencing",
},
{
name: "crm",
href: getHref(baseURL, "crm", useQueryParam),
icon: "contact",
"data-testid": "crm",
},
{
name: "messaging",
href: getHref(baseURL, "messaging", useQueryParam),
icon: "mail",
"data-testid": "messaging",
},
{
name: "payment",
href: getHref(baseURL, "payment", useQueryParam),
icon: "credit-card",
"data-testid": "payment",
},
{
name: "other",
href: getHref(baseURL, "other", useQueryParam),
icon: "grid-3x3",
"data-testid": "other",
},
];
export const APP_CATEGORY_ENTRIES: Record<ActiveAppCategoryKeys, Omit<AppCategoryEntry, "name">> = {
analytics: { href: "", icon: "chart-bar", "data-testid": "analytics" },
automation: { href: "", icon: "share-2", "data-testid": "automation" },
calendar: { href: "", icon: "calendar", "data-testid": "calendar" },
conferencing: { href: "", icon: "video", "data-testid": "conferencing" },
crm: { href: "", icon: "contact", "data-testid": "crm" },
messaging: { href: "", icon: "mail", "data-testid": "messaging" },
payment: { href: "", icon: "credit-card", "data-testid": "payment" },
other: { href: "", icon: "grid-3x3", "data-testid": "other" },
};
export default getAppCategories;
export const CATEGORY_ORDER = [
"analytics",
"automation",
"calendar",
"conferencing",
"crm",
"messaging",
"payment",
"other",
] as const satisfies readonly ActiveAppCategoryKeys[];
const getAppCategories = (baseURL: string, useQueryParam: boolean): AppCategoryEntry[] =>
CATEGORY_ORDER.map((name): AppCategoryEntry => ({
name,
...APP_CATEGORY_ENTRIES[name],
href: getHref(baseURL, name, useQueryParam),
}));
export default getAppCategories;