From 03ade52f28b8a64e718228ce243f7d3557afaa1b Mon Sep 17 00:00:00 2001 From: Wyatt Bai <53634020+congweibai@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:46:29 +0930 Subject: [PATCH] feat: useBattery hook --- src/hooks/index.ts | 1 + src/hooks/useBattery/index.ts | 1 + src/hooks/useBattery/useBattery.test.tsx | 78 ++++++++++++++++++++++++ src/hooks/useBattery/useBattery.tsx | 67 ++++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 src/hooks/useBattery/index.ts create mode 100644 src/hooks/useBattery/useBattery.test.tsx create mode 100644 src/hooks/useBattery/useBattery.tsx diff --git a/src/hooks/index.ts b/src/hooks/index.ts index c8d7bb4..b0b688f 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -13,3 +13,4 @@ export * from "./useVisibilityChange"; export * from "./useObjectState"; export * from "./useDebounce"; export * from "./useHistoryState"; +export * from "./useBattery"; diff --git a/src/hooks/useBattery/index.ts b/src/hooks/useBattery/index.ts new file mode 100644 index 0000000..362cacb --- /dev/null +++ b/src/hooks/useBattery/index.ts @@ -0,0 +1 @@ +export * from "./useBattery"; diff --git a/src/hooks/useBattery/useBattery.test.tsx b/src/hooks/useBattery/useBattery.test.tsx new file mode 100644 index 0000000..6c594b5 --- /dev/null +++ b/src/hooks/useBattery/useBattery.test.tsx @@ -0,0 +1,78 @@ +import { renderHook, act } from "@testing-library/react-hooks"; +import useBattery from "./useBattery"; // Adjust the path as needed +import { afterEach, beforeEach, describe, expect, it, Mock, vi } from "vitest"; + +type MockBattery = { + level: number; + charging: boolean; + chargingTime: number; + dischargingTime: number; + addEventListener: Mock; + removeEventListener: Mock; +}; + +describe("useBattery", () => { + let mockBattery: MockBattery; + let getBatteryMock: Mock; + + beforeEach(() => { + // mock + mockBattery = { + level: 0.5, + charging: true, + chargingTime: 100, + dischargingTime: 200, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + }; + + getBatteryMock = vi.fn().mockResolvedValue(mockBattery); + Object.defineProperty(navigator, "getBattery", { + value: getBatteryMock, + writable: true, + }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("should initialize with loading state", () => { + // act + const { result } = renderHook(() => useBattery()); + + // assert + expect(result.current.loading).toBe(true); + expect(result.current.supported).toBe(true); + }); + + it("should update battery state when getBattery is resolved", async () => { + // act + const { result, waitForNextUpdate } = renderHook(() => useBattery()); + + await waitForNextUpdate(); + + // assert + expect(result.current.loading).toBe(false); + expect(result.current.supported).toBe(true); + expect(result.current.level).toBe(0.5); + expect(result.current.charging).toBe(true); + expect(result.current.chargingTime).toBe(100); + expect(result.current.dischargingTime).toBe(200); + }); + + it("should update state when battery properties change", async () => { + // act + const { result, waitForNextUpdate } = renderHook(() => useBattery()); + + await waitForNextUpdate(); + + act(() => { + mockBattery.level = 0.7; + mockBattery.addEventListener.mock.calls[0][1](); // Trigger the event listener + }); + + // assert + expect(result.current.level).toBe(0.7); + }); +}); diff --git a/src/hooks/useBattery/useBattery.tsx b/src/hooks/useBattery/useBattery.tsx new file mode 100644 index 0000000..d6562a1 --- /dev/null +++ b/src/hooks/useBattery/useBattery.tsx @@ -0,0 +1,67 @@ +import { useEffect, useState } from "react"; + +type BatteryState = { + supported: boolean; + loading: boolean; + level: number | null; + charging: boolean | null; + chargingTime: number | null; + dischargingTime: number | null; +}; + +export default function useBattery() { + const [batteryState, setBatteryState] = useState({ + supported: true, + loading: true, + level: null, + charging: null, + chargingTime: null, + dischargingTime: null, + }); + useEffect(() => { + if (!navigator?.getBattery) { + setBatteryState((s) => ({ + ...s, + loading: false, + supported: false, + })); + + return; + } + let battery: BatteryManager | null = null; + + const handleBatteryChange = () => { + setBatteryState({ + supported: true, + loading: false, + level: battery.level, + charging: battery.charging, + chargingTime: battery.chargingTime, + dischargingTime: battery.dischargingTime, + }); + }; + + navigator.getBattery().then((b) => { + battery = b; + handleBatteryChange(); + b.addEventListener("levelchange", handleBatteryChange); + b.addEventListener("chargingchange", handleBatteryChange); + b.addEventListener("chargingtimechange", handleBatteryChange); + b.addEventListener("dischargingtimechange", handleBatteryChange); + }); + + return () => { + if (battery) { + battery.removeEventListener("levelchange", handleBatteryChange); + battery.removeEventListener("chargingchange", handleBatteryChange); + battery.removeEventListener("chargingtimechange", handleBatteryChange); + battery.removeEventListener( + "dischargingtimechange", + handleBatteryChange + ); + } + }; + }, []); + + return batteryState; +}