Skip to content

Commit

Permalink
Merge pull request #70 from mbti-nf-team/feat/react-hook-useGeolocation
Browse files Browse the repository at this point in the history
feat(@nf-team/react): useGeolocation hook 구현
  • Loading branch information
saseungmin authored Feb 24, 2024
2 parents 3f6d2bd + 86be66f commit 392e42a
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/slimy-ravens-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nf-team/react": minor
---

feat(@nf-team/react): useGeolocation hook 구현
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions packages/react/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { default as useActionKeyEvent } from './useActionKeyEvent';
export { default as useBoolean } from './useBoolean';
export { default as useDebounce } from './useDebounce';
export { default as useEffectOnce } from './useEffectOnce';
export { default as useGeolocation } from './useGeolocation';
export { default as useIsFirstRender } from './useIsFirstRender';
export { default as useIsMounted } from './useIsMounted';
export { default as useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
Expand Down
105 changes: 105 additions & 0 deletions packages/react/src/hooks/useGeolocation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { act, renderHook } from '@testing-library/react';

import useGeoLocation from './useGeolocation';

describe('useGeoLocation', () => {
const mockNavigatorGeolocation = (hasGeolocation: boolean) => {
const getCurrentPositionMock = jest.fn();

const geolocation = hasGeolocation ? {
getCurrentPosition: getCurrentPositionMock,
} : undefined;

Object.defineProperty(global.navigator, 'geolocation', {
value: geolocation,
writable: true,
});

return { getCurrentPositionMock };
};

beforeEach(() => {
jest.clearAllMocks();
});

const useGeoLocationHook = () => renderHook(() => useGeoLocation());

context('에러가 발생한 경우', () => {
context('나머지 에러인 경우', () => {
const errorMessage = 'errorMessage';
const { getCurrentPositionMock } = mockNavigatorGeolocation(true);
getCurrentPositionMock.mockImplementation((_, rejected) => rejected({
code: 1,
message: errorMessage,
PERMISSION_DENIED: '',
POSITION_UNAVAILABLE: '',
TIMEOUT: '',
}));

it('에러 상태가 반환되어야만 한다', () => {
const { result } = useGeoLocationHook();

act(() => {
result.current[1]();
});

expect(getCurrentPositionMock).toHaveBeenCalled();
expect(result.current[0]).toEqual({
loading: false,
error: {
code: 1,
message: errorMessage,
},
});
});
});

context('지원하지 않는 브라우저인 경우', () => {
it('에러 상태가 반환되어야만 한다', () => {
const { getCurrentPositionMock } = mockNavigatorGeolocation(false);
const { result } = useGeoLocationHook();

act(() => {
result.current[1]();
});

expect(getCurrentPositionMock).not.toHaveBeenCalled();
expect(result.current[0]).toEqual({
loading: false,
error: {
code: 1,
message: 'Geolocation not supported',
},
});
});
});
});

context('성공한 경우', () => {
const position = {
coords: {
latitude: 55,
longitude: 44,
},
};

it('위치 정보가 반환되어야만 한다', () => {
const { getCurrentPositionMock } = mockNavigatorGeolocation(true);
getCurrentPositionMock.mockImplementation((success) => success(position));
const { result } = useGeoLocationHook();

act(() => {
result.current[1]();
});

expect(getCurrentPositionMock).toHaveBeenCalled();
expect(result.current[0]).toEqual({
loading: false,
coordinates: {
lat: position.coords.latitude,
lng: position.coords.longitude,
},
});
});
});
});
63 changes: 63 additions & 0 deletions packages/react/src/hooks/useGeolocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useCallback, useState } from 'react';

type GeoLocation = {
loading: boolean;
coordinates?: {
lat: number | undefined;
lng: number | undefined;
};
error?: {
code: number;
message: string;
}
};

const useGeoLocation = (): [GeoLocation, () => void] => {
const [location, setLocation] = useState<GeoLocation>({
loading: false,
coordinates: { lat: undefined, lng: undefined },
});

const onSuccess = (position: GeolocationPosition) => {
setLocation({
loading: false,
coordinates: {
lat: position.coords.latitude,
lng: position.coords.longitude,
},
});
};

const onError = (error: GeolocationPositionError) => {
setLocation({
loading: false,
error: {
code: error.code,
message: error.message,
},
});
};

const onGetGeoLocation = useCallback(() => {
if (navigator?.geolocation) {
setLocation((prev) => ({
...prev,
loading: true,
}));
navigator.geolocation.getCurrentPosition(onSuccess, onError);
return;
}

onError({
code: 1,
message: 'Geolocation not supported',
PERMISSION_DENIED: 1,
POSITION_UNAVAILABLE: 2,
TIMEOUT: 3,
});
}, []);

return [location, onGetGeoLocation];
};

export default useGeoLocation;

0 comments on commit 392e42a

Please sign in to comment.