Compare commits

...

8 Commits

Author SHA1 Message Date
Nicolò Pretto
a1b72327ca revert renderSdkHook as we don't need it 2025-03-21 11:20:53 +01:00
Nicolò Pretto
88b5e0948a use renderHookWithProviders 2025-03-21 11:19:27 +01:00
Nicolò Pretto
e20ad59c10 trying something simpler 2025-03-21 11:18:39 +01:00
Oisin Coveney
29e7b3c0ca Remove a cast that isn't needeD 2025-03-21 11:18:39 +01:00
Oisin Coveney
d49e1623d5 Make some more changes to the tests 2025-03-21 11:18:39 +01:00
Oisin Coveney
94c0c34e8b Improve tests just a tad 2025-03-21 11:18:39 +01:00
Oisin Coveney
ff2815f3e4 Make some tests that ideally work 2025-03-21 11:18:39 +01:00
Oisin Coveney
8f8bad519e A much simpler way to solve the same problem 2025-03-21 11:18:39 +01:00
5 changed files with 209 additions and 15 deletions

View File

@@ -0,0 +1 @@
export * from "./use-validated-entity-id";

View File

@@ -0,0 +1 @@
export * from "./use-validated-entity-id";

View File

@@ -17,6 +17,29 @@ type SUPPORTED_ENTITIES = {
collection: CollectionId;
};
export interface UseTranslateEntityIdProps<
TEntity extends keyof SUPPORTED_ENTITIES = keyof SUPPORTED_ENTITIES,
> {
type: TEntity;
id: BaseEntityId | string | number | null | undefined;
}
export type UseTranslateEntityIdReturned<
TEntity extends keyof SUPPORTED_ENTITIES = keyof SUPPORTED_ENTITIES,
TReturnedId = SUPPORTED_ENTITIES[TEntity],
> =
| { id: TReturnedId; isLoading: false; isError: false }
| {
id: null;
isLoading: true;
isError: false;
}
| {
id: null;
isLoading: false;
isError: true;
};
/**
* A hook that validates and potentially translates an entity ID.
*
@@ -35,21 +58,10 @@ export const useValidatedEntityId = <
>({
type,
id,
}: {
type: TEntity;
id: BaseEntityId | string | number | null | undefined;
}):
| { id: TReturnedId; isLoading: false; isError: false }
| {
id: null;
isLoading: true;
isError: false;
}
| {
id: null;
isLoading: false;
isError: true;
} => {
}: UseTranslateEntityIdProps<TEntity>): UseTranslateEntityIdReturned<
TEntity,
TReturnedId
> => {
const isEntityId = isBaseEntityID(id);
const {
data: entity_ids,

View File

@@ -0,0 +1,158 @@
import { waitFor } from "@testing-library/react";
import {
callsToTranslateEntityIdEndpoint,
setupTranslateEntityIdEndpoint,
} from "__support__/server-mocks/entity-ids";
import { renderHookWithProviders } from "__support__/ui";
import type { BaseEntityId } from "metabase-types/api";
import { useValidatedEntityId } from "./use-validated-entity-id";
describe("useValidatedEntityId", () => {
it("should pass through numeric IDs without calling the translation endpoint", () => {
const { result } = renderHookWithProviders(() =>
useValidatedEntityId({ type: "card", id: 123 }),
);
expect(result.current).toEqual({
id: 123,
isLoading: false,
isError: false,
});
expect(callsToTranslateEntityIdEndpoint()).toHaveLength(0);
});
it("should handle undefined ID", () => {
const { result } = renderHookWithProviders(() =>
useValidatedEntityId({ type: "card", id: undefined }),
);
expect(result.current).toEqual({
id: null,
isLoading: false,
isError: true,
});
});
it("should handle null ID", () => {
const { result } = renderHookWithProviders(() =>
useValidatedEntityId({ type: "card", id: null }),
);
expect(result.current).toEqual({
id: null,
isLoading: false,
isError: true,
});
});
it("should handle invalid entity ID strings", () => {
const { result } = renderHookWithProviders(() =>
useValidatedEntityId({ type: "card", id: "not-an-entity-id" }),
);
expect(result.current).toEqual({
id: null,
isLoading: false,
isError: true,
});
});
it("should translate card entity IDs correctly", async () => {
const validEntityId = "2".repeat(21) as BaseEntityId;
setupTranslateEntityIdEndpoint({
[validEntityId]: {
type: "card",
status: "ok",
id: 123,
},
});
const { result } = renderHookWithProviders(() =>
useValidatedEntityId({ type: "card", id: validEntityId }),
);
await waitFor(() => expect(result.current.isLoading).toBe(false));
expect(result.current).toEqual({
id: 123,
isLoading: false,
isError: false,
});
});
it("should translate dashboard entity IDs correctly", async () => {
const validEntityId = "3".repeat(21) as BaseEntityId;
setupTranslateEntityIdEndpoint({
[validEntityId]: {
type: "dashboard",
status: "ok",
id: 456,
},
});
const { result } = renderHookWithProviders(() =>
useValidatedEntityId({ type: "dashboard", id: validEntityId }),
);
await waitFor(() => expect(result.current.isLoading).toBe(false));
expect(result.current).toEqual({
id: 456,
isLoading: false,
isError: false,
});
});
it("should translate collection entity IDs correctly", async () => {
const validEntityId = "4".repeat(21) as BaseEntityId;
setupTranslateEntityIdEndpoint({
[validEntityId]: {
type: "collection",
status: "ok",
id: 789,
},
});
const { result } = renderHookWithProviders(() =>
useValidatedEntityId({ type: "collection", id: validEntityId }),
);
await waitFor(() => expect(result.current.isLoading).toBe(false));
expect(result.current).toEqual({
id: 789,
isLoading: false,
isError: false,
});
});
it("should handle failed translations", async () => {
const invalidEntityId = "5".repeat(21) as BaseEntityId;
setupTranslateEntityIdEndpoint({
[invalidEntityId]: {
type: "card",
status: "not-found",
id: null,
},
});
const { result } = renderHookWithProviders(() =>
useValidatedEntityId({ type: "card", id: invalidEntityId }),
);
await waitFor(() => expect(result.current.isLoading).toBe(false));
expect(result.current).toEqual({
id: null,
isLoading: false,
isError: true,
});
});
});

View File

@@ -0,0 +1,22 @@
import fetchMock from "fetch-mock";
import type {
TranslateEntityIdResponseNotFound,
TranslateEntityIdResponseSuccess,
} from "metabase/api";
export const setupTranslateEntityIdEndpoint = (
mockResponse: Record<
string, // this should be BaseEntityId, but in tests we probably want to use plain strings
TranslateEntityIdResponseSuccess | TranslateEntityIdResponseNotFound
>,
) => {
fetchMock.post("path:/api/util/entity_id", {
body: { entity_ids: mockResponse },
status: 200,
});
};
export const callsToTranslateEntityIdEndpoint = () => {
return fetchMock.calls("path:/api/util/entity_id");
};