170 lines
4.9 KiB
TypeScript
170 lines
4.9 KiB
TypeScript
import { act, fireEvent, renderHook } from "@testing-library/react";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
isLikelyTabSuspensionError,
|
|
lastVisibilityTransition,
|
|
useTabVisibilitySuspension,
|
|
} from "../visibilitySuspension";
|
|
|
|
describe("visibilitySuspension", () => {
|
|
it.each([
|
|
"Load failed",
|
|
"Failed to fetch",
|
|
"NetworkError when attempting to fetch resource.",
|
|
"Connection aborted",
|
|
"Connection closed unexpectedly",
|
|
"network error",
|
|
])("matches known tab-suspension transport errors: %s", (message) => {
|
|
expect(isLikelyTabSuspensionError(message)).toBe(true);
|
|
});
|
|
|
|
it("rejects unrelated backend errors", () => {
|
|
expect(isLikelyTabSuspensionError("Request failed: 500")).toBe(false);
|
|
expect(isLikelyTabSuspensionError("Validation error: missing key")).toBe(false);
|
|
});
|
|
|
|
it("tracks recently hidden window", () => {
|
|
vi.useFakeTimers();
|
|
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "visible",
|
|
});
|
|
const { result } = renderHook(() => useTabVisibilitySuspension());
|
|
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "hidden",
|
|
});
|
|
act(() => {
|
|
fireEvent(document, new Event("visibilitychange"));
|
|
});
|
|
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "visible",
|
|
});
|
|
act(() => {
|
|
fireEvent(document, new Event("visibilitychange"));
|
|
});
|
|
|
|
expect(lastVisibilityTransition().hiddenAt).not.toBeNull();
|
|
expect(lastVisibilityTransition().visibleAt).not.toBeNull();
|
|
expect(result.current.wasRecentlyHidden(5000)).toBe(true);
|
|
|
|
act(() => {
|
|
vi.advanceTimersByTime(6000);
|
|
});
|
|
|
|
expect(result.current.wasRecentlyHidden(5000)).toBe(false);
|
|
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("fires onBecameVisible on hidden to visible transition", () => {
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "visible",
|
|
});
|
|
const { result } = renderHook(() => useTabVisibilitySuspension());
|
|
const callback = vi.fn();
|
|
result.current.onBecameVisible(callback);
|
|
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "hidden",
|
|
});
|
|
act(() => {
|
|
fireEvent(document, new Event("visibilitychange"));
|
|
});
|
|
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "visible",
|
|
});
|
|
act(() => {
|
|
fireEvent(document, new Event("visibilitychange"));
|
|
});
|
|
|
|
expect(callback).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("does not fire onBecameVisible when moving visible to hidden", () => {
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "visible",
|
|
});
|
|
const { result } = renderHook(() => useTabVisibilitySuspension());
|
|
const callback = vi.fn();
|
|
result.current.onBecameVisible(callback);
|
|
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "hidden",
|
|
});
|
|
act(() => {
|
|
fireEvent(document, new Event("visibilitychange"));
|
|
});
|
|
|
|
expect(callback).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("unsubscribes onBecameVisible callback", () => {
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "visible",
|
|
});
|
|
const { result } = renderHook(() => useTabVisibilitySuspension());
|
|
const callback = vi.fn();
|
|
const unsubscribe = result.current.onBecameVisible(callback);
|
|
unsubscribe();
|
|
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "hidden",
|
|
});
|
|
act(() => {
|
|
fireEvent(document, new Event("visibilitychange"));
|
|
});
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "visible",
|
|
});
|
|
act(() => {
|
|
fireEvent(document, new Event("visibilitychange"));
|
|
});
|
|
|
|
expect(callback).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("fires all onBecameVisible subscribers", () => {
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "visible",
|
|
});
|
|
const { result } = renderHook(() => useTabVisibilitySuspension());
|
|
const callbackA = vi.fn();
|
|
const callbackB = vi.fn();
|
|
result.current.onBecameVisible(callbackA);
|
|
result.current.onBecameVisible(callbackB);
|
|
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "hidden",
|
|
});
|
|
act(() => {
|
|
fireEvent(document, new Event("visibilitychange"));
|
|
});
|
|
Object.defineProperty(document, "visibilityState", {
|
|
configurable: true,
|
|
get: () => "visible",
|
|
});
|
|
act(() => {
|
|
fireEvent(document, new Event("visibilitychange"));
|
|
});
|
|
|
|
expect(callbackA).toHaveBeenCalledTimes(1);
|
|
expect(callbackB).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|