IKHSAN
EN Read in EnglishID

homeio

Publik

Fork dari doctor-io/homeio

Branch
main
Commit terakhir
958d74616 Oktober 2026

useUserLocation.test.tsx

Buka raw
/* @vitest-environment jsdom */

import { act, renderHook, waitFor } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
  resetUserLocationReverseGeocodeStateForTests,
  useUserLocation,
} from "@/modules/system/hooks/useUserLocation";

const USER_LOCATION_STORAGE_KEY = "weather.user-location.v1";

function mockGeolocation(
  implementation: Geolocation["getCurrentPosition"],
) {
  const geolocation = {
    getCurrentPosition: vi.fn(implementation),
  } satisfies Pick<Geolocation, "getCurrentPosition">;

  Object.defineProperty(navigator, "geolocation", {
    configurable: true,
    value: geolocation,
  });

  return geolocation.getCurrentPosition;
}

function mockPermissions(state: PermissionState = "prompt") {
  const permission = {
    state,
    onchange: null,
  } as unknown as PermissionStatus;

  const permissions = {
    query: vi.fn().mockResolvedValue(permission),
  } satisfies Pick<Permissions, "query">;

  Object.defineProperty(navigator, "permissions", {
    configurable: true,
    value: permissions,
  });

  return { permission, query: permissions.query };
}

describe("useUserLocation", () => {
  beforeEach(() => {
    localStorage.clear();
    vi.clearAllMocks();
    resetUserLocationReverseGeocodeStateForTests();
  });

  afterEach(() => {
    vi.unstubAllGlobals();
  });

  it("loads saved coordinates from localStorage", async () => {
    localStorage.setItem(
      USER_LOCATION_STORAGE_KEY,
      JSON.stringify({ latitude: 36.8, longitude: 10.1 }),
    );
    const getCurrentPosition = mockGeolocation(() => {});

    const { result } = renderHook(() => useUserLocation());

    await waitFor(() => {
      expect(result.current.location).toEqual({
        latitude: 36.8,
        longitude: 10.1,
        city: undefined,
        country: undefined,
      });
    });

    expect(result.current.loading).toBe(false);
    expect(getCurrentPosition).toHaveBeenCalledTimes(1);
  });

  it("persists coordinates when geolocation succeeds", async () => {
    vi.stubGlobal(
      "fetch",
      vi.fn().mockResolvedValue({
        ok: true,
        json: async () => ({
          address: {
            city: "Sousse",
            country_code: "tn",
          },
        }),
      }),
    );

    mockGeolocation((success) => {
      success({
        coords: {
          latitude: 35.2,
          longitude: 9.4,
        },
      } as GeolocationPosition);
    });

    const { result } = renderHook(() => useUserLocation());

    await waitFor(() => {
      expect(result.current.location).toEqual({
        latitude: 35.2,
        longitude: 9.4,
        city: "Sousse",
        country: "TN",
      });
    });

    expect(fetch).toHaveBeenCalledWith(
      "https://nominatim.openstreetmap.org/reverse?lat=35.2&lon=9.4&format=json",
      { headers: { "User-Agent": "Homeio Dashboard" } },
    );
    expect(result.current.error).toBeNull();
    expect(localStorage.getItem(USER_LOCATION_STORAGE_KEY)).toBe(
      JSON.stringify({
        latitude: 35.2,
        longitude: 9.4,
        city: "Sousse",
        country: "TN",
      }),
    );
  });

  it("tracks geolocation permission status changes", async () => {
    mockGeolocation(() => {});
    const { permission } = mockPermissions("prompt");

    const { result } = renderHook(() => useUserLocation());

    await waitFor(() => {
      expect(result.current.permissionStatus).toBe("prompt");
    });

    act(() => {
      (permission as { state: PermissionState }).state = "granted";
      permission.onchange?.(new Event("change"));
    });

    await waitFor(() => {
      expect(result.current.permissionStatus).toBe("granted");
    });
  });

  it("handles missing browser geolocation support", async () => {
    Object.defineProperty(navigator, "geolocation", {
      configurable: true,
      value: undefined,
    });

    const { result } = renderHook(() => useUserLocation());

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    expect(result.current.location).toBeNull();
    expect(result.current.error).toBe("Geolocation not supported");
  });

  it("backs off reverse geocoding after a 429 rate-limit response", async () => {
    vi.stubGlobal(
      "fetch",
      vi.fn().mockResolvedValue({
        ok: false,
        status: 429,
      }),
    );

    mockGeolocation((success) => {
      success({
        coords: {
          latitude: 35.2,
          longitude: 9.4,
        },
      } as GeolocationPosition);
    });

    const { result } = renderHook(() => useUserLocation());

    await waitFor(() => {
      expect(result.current.location).toEqual({
        latitude: 35.2,
        longitude: 9.4,
        city: undefined,
        country: undefined,
      });
    });

    expect(fetch).toHaveBeenCalledTimes(1);

    act(() => {
      result.current.requestLocation();
    });

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    expect(fetch).toHaveBeenCalledTimes(1);
  });
});
byte
5.053
baris
205
Jenis
tsx
Commit terakhir
16 Oktober 2026