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
15 changes: 5 additions & 10 deletions src/context/StacApiProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,11 @@ import { useStacApiContext } from './useStacApiContext';

// Mock fetch for testing - returns a successful response
beforeEach(() => {
(global.fetch as jest.Mock) = jest.fn((url: string) =>
Promise.resolve({
ok: true,
url, // Return the requested URL
json: () =>
Promise.resolve({
links: [],
}),
})
);
(global.fetch as jest.Mock) = jest.fn((url: string) => {
const response = new Response(JSON.stringify({ links: [] }));
Object.defineProperty(response, 'url', { value: url });
return Promise.resolve(response);
});
});

// Component to test that hooks work inside StacApiProvider
Expand Down
4 changes: 2 additions & 2 deletions src/context/context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createContext } from 'react';
import StacApi from '../stac-api';

export type StacApiContextType = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
stacApi?: any;
stacApi?: StacApi;
};

export const StacApiContext = createContext<StacApiContextType>({} as StacApiContextType);
35 changes: 20 additions & 15 deletions src/hooks/useCollection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fetch from 'jest-fetch-mock';
import { renderHook, act, waitFor } from '@testing-library/react';
import useCollection from './useCollection';
import wrapper from './wrapper';
import { ApiError } from '../utils/ApiError';

describe('useCollection', () => {
beforeEach(() => {
Expand Down Expand Up @@ -31,11 +32,14 @@ describe('useCollection', () => {

const { result } = renderHook(() => useCollection('nonexistent'), { wrapper });
await waitFor(() =>
expect(result.current.error).toEqual({
status: 404,
statusText: 'Not Found',
detail: { error: 'Collection not found' },
})
expect(result.current.error).toEqual(
new ApiError(
'Not Found',
404,
{ error: 'Collection not found' },
'https://fake-stac-api.net/collections/nonexistent'
)
)
);
});

Expand All @@ -49,11 +53,14 @@ describe('useCollection', () => {

const { result } = renderHook(() => useCollection('abc'), { wrapper });
await waitFor(() =>
expect(result.current.error).toEqual({
status: 400,
statusText: 'Bad Request',
detail: { error: 'Wrong query' },
})
expect(result.current.error).toEqual(
new ApiError(
'Bad Request',
400,
{ error: 'Wrong query' },
'https://fake-stac-api.net/search'
)
)
);
});

Expand All @@ -64,11 +71,9 @@ describe('useCollection', () => {

const { result } = renderHook(() => useCollection('abc'), { wrapper });
await waitFor(() =>
expect(result.current.error).toEqual({
status: 400,
statusText: 'Bad Request',
detail: 'Wrong query',
})
expect(result.current.error).toEqual(
new ApiError('Bad Request', 400, 'Wrong query', 'https://fake-stac-api.net/search')
)
);
});

Expand Down
28 changes: 1 addition & 27 deletions src/hooks/useCollection.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useQuery, type QueryObserverResult } from '@tanstack/react-query';
import type { ApiErrorType } from '../types';
import type { Collection } from '../types/stac';
import { ApiError } from '../utils/ApiError';
import { generateCollectionQueryKey } from '../utils/queryKeys';
import { useStacApiContext } from '../context/useStacApiContext';

Expand All @@ -16,31 +15,6 @@ type StacCollectionHook = {
function useCollection(collectionId: string): StacCollectionHook {
const { stacApi } = useStacApiContext();

const fetchCollection = async (): Promise<Collection> => {
if (!stacApi) throw new Error('No STAC API configured');
const response: Response = await stacApi.getCollection(collectionId);
if (!response.ok) {
let detail;
try {
detail = await response.json();
} catch {
detail = await response.text();
}

throw new ApiError(response.statusText, response.status, detail, response.url);
}
try {
return await response.json();
} catch (error) {
throw new ApiError(
'Invalid JSON Response',
response.status,
`Response is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
response.url
);
}
};

const {
data: collection,
error,
Expand All @@ -49,7 +23,7 @@ function useCollection(collectionId: string): StacCollectionHook {
refetch,
} = useQuery<Collection, ApiErrorType>({
queryKey: generateCollectionQueryKey(collectionId),
queryFn: fetchCollection,
queryFn: () => stacApi!.getCollection(collectionId),
enabled: !!stacApi,
retry: false,
});
Expand Down
22 changes: 12 additions & 10 deletions src/hooks/useCollections.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fetch from 'jest-fetch-mock';
import { renderHook, act, waitFor } from '@testing-library/react';
import useCollections from './useCollections';
import wrapper from './wrapper';
import { ApiError } from '../utils/ApiError';

describe('useCollections', () => {
beforeEach(() => {
Expand Down Expand Up @@ -50,11 +51,14 @@ describe('useCollections', () => {
const { result } = renderHook(() => useCollections(), { wrapper });

await waitFor(() =>
expect(result.current.error).toEqual({
status: 400,
statusText: 'Bad Request',
detail: { error: 'Wrong query' },
})
expect(result.current.error).toEqual(
new ApiError(
'Bad Request',
400,
{ error: 'Wrong query' },
'https://fake-stac-api.net/search'
)
)
);
});

Expand All @@ -65,11 +69,9 @@ describe('useCollections', () => {

const { result } = renderHook(() => useCollections(), { wrapper });
await waitFor(() =>
expect(result.current.error).toEqual({
status: 400,
statusText: 'Bad Request',
detail: 'Wrong query',
})
expect(result.current.error).toEqual(
new ApiError('Bad Request', 400, 'Wrong query', 'https://fake-stac-api.net/search')
)
);
});
});
28 changes: 1 addition & 27 deletions src/hooks/useCollections.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useQuery, type QueryObserverResult } from '@tanstack/react-query';
import { type ApiErrorType } from '../types';
import type { CollectionsResponse } from '../types/stac';
import { ApiError } from '../utils/ApiError';
import { generateCollectionsQueryKey } from '../utils/queryKeys';
import { useStacApiContext } from '../context/useStacApiContext';

Expand All @@ -16,31 +15,6 @@ type StacCollectionsHook = {
function useCollections(): StacCollectionsHook {
const { stacApi } = useStacApiContext();

const fetchCollections = async (): Promise<CollectionsResponse> => {
if (!stacApi) throw new Error('No STAC API configured');
const response: Response = await stacApi.getCollections();
if (!response.ok) {
let detail;
try {
detail = await response.json();
} catch {
detail = await response.text();
}

throw new ApiError(response.statusText, response.status, detail, response.url);
}
try {
return await response.json();
} catch (error) {
throw new ApiError(
'Invalid JSON Response',
response.status,
`Response is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
response.url
);
}
};

const {
data: collections,
error,
Expand All @@ -49,7 +23,7 @@ function useCollections(): StacCollectionsHook {
refetch,
} = useQuery<CollectionsResponse, ApiErrorType>({
queryKey: generateCollectionsQueryKey(),
queryFn: fetchCollections,
queryFn: () => stacApi!.getCollections(),
enabled: !!stacApi,
retry: false,
});
Expand Down
22 changes: 12 additions & 10 deletions src/hooks/useItem.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fetch from 'jest-fetch-mock';
import { renderHook, act, waitFor } from '@testing-library/react';
import useItem from './useItem';
import wrapper from './wrapper';
import { ApiError } from '../utils/ApiError';

describe('useItem', () => {
beforeEach(() => {
Expand Down Expand Up @@ -32,11 +33,14 @@ describe('useItem', () => {
wrapper,
});
await waitFor(() =>
expect(result.current.error).toEqual({
status: 400,
statusText: 'Bad Request',
detail: { error: 'Wrong query' },
})
expect(result.current.error).toEqual(
new ApiError(
'Bad Request',
400,
{ error: 'Wrong query' },
'https://fake-stac-api.net/search'
)
)
);
});

Expand All @@ -49,11 +53,9 @@ describe('useItem', () => {
wrapper,
});
await waitFor(() =>
expect(result.current.error).toEqual({
status: 400,
statusText: 'Bad Request',
detail: 'Wrong query',
})
expect(result.current.error).toEqual(
new ApiError('Bad Request', 400, 'Wrong query', 'https://fake-stac-api.net/search')
)
);
});

Expand Down
28 changes: 1 addition & 27 deletions src/hooks/useItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useQuery, type QueryObserverResult } from '@tanstack/react-query';
import { Item } from '../types/stac';
import { type ApiErrorType } from '../types';
import { useStacApiContext } from '../context/useStacApiContext';
import { ApiError } from '../utils/ApiError';
import { generateItemQueryKey } from '../utils/queryKeys';

type ItemHook = {
Expand All @@ -16,31 +15,6 @@ type ItemHook = {
function useItem(url: string): ItemHook {
const { stacApi } = useStacApiContext();

const fetchItem = async (): Promise<Item> => {
if (!stacApi) throw new Error('No STAC API configured');
const response: Response = await stacApi.get(url);
if (!response.ok) {
let detail;
try {
detail = await response.json();
} catch {
detail = await response.text();
}

throw new ApiError(response.statusText, response.status, detail, response.url);
}
try {
return await response.json();
} catch (error) {
throw new ApiError(
'Invalid JSON Response',
response.status,
`Response is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
response.url
);
}
};

const {
data: item,
error,
Expand All @@ -49,7 +23,7 @@ function useItem(url: string): ItemHook {
refetch,
} = useQuery<Item, ApiErrorType>({
queryKey: generateItemQueryKey(url),
queryFn: fetchItem,
queryFn: () => stacApi!.get<Item>(url),
enabled: !!stacApi,
retry: false,
});
Expand Down
21 changes: 6 additions & 15 deletions src/hooks/useStacApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,23 @@ function useStacApi(url: string, options?: GenericObject): StacApiHook {
const { data, isSuccess, isLoading, isError } = useQuery({
queryKey: generateStacApiQueryKey(url, options),
queryFn: async () => {
let searchMode = SearchMode.GET;
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
});
const baseUrl = response.url;
let json;
try {
json = await response.json();
} catch (error) {
throw new Error(
`Invalid JSON response from STAC API: ${error instanceof Error ? error.message : String(error)}`
);
}
const doesPost = json.links?.find(
const stacData = await StacApi.handleResponse<{ links?: Link[] }>(response);

const doesPost = stacData.links?.find(
({ rel, method }: Link) => rel === 'search' && method === 'POST'
);
if (doesPost) {
searchMode = SearchMode.POST;
}
return new StacApi(baseUrl, searchMode, options);

return new StacApi(response.url, doesPost ? SearchMode.POST : SearchMode.GET, options);
},
staleTime: Infinity,
});

return { stacApi: isSuccess ? data : undefined, isLoading, isError };
}

Expand Down
Loading