import { App } from '@capacitor/app';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { act, fireEvent, render, screen, waitFor } from '../../../tests/helpers/render';
import { isAppLockEnabled, unlockApp } from '../../native/appLock';
import AppLockGate from './AppLockGate';
vi.mock('@capacitor/app', () => ({ App: { addListener: vi.fn(() => Promise.resolve({ remove: vi.fn() })) } }));
vi.mock('../../native/appLock', () => ({
RELOCK_AFTER_MS: 30_000,
isAppLockEnabled: vi.fn(),
unlockApp: vi.fn(),
}));
function stateListener(): (state: { isActive: boolean }) => void {
const call = vi.mocked(App.addListener).mock.calls.find(([name]) => name === 'appStateChange');
if (!call) throw new Error('no appStateChange listener');
return call[1] as unknown as (state: { isActive: boolean }) => void;
}
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(isAppLockEnabled).mockReturnValue(true);
vi.mocked(unlockApp).mockResolvedValue(false);
});
describe('AppLockGate', () => {
it('covers the app at launch and asks at once; a successful unlock lifts it', async () => {
render();
expect(screen.getByRole('dialog')).toBeInTheDocument();
await waitFor(() => expect(unlockApp).toHaveBeenCalledTimes(1));
vi.mocked(unlockApp).mockResolvedValue(true);
fireEvent.click(screen.getByRole('button'));
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
});
it('locks again after half a minute in the background, not after a glance away', async () => {
vi.mocked(unlockApp).mockResolvedValue(true);
const now = vi.spyOn(Date, 'now');
render();
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
vi.mocked(unlockApp).mockResolvedValue(false);
now.mockReturnValue(1_000);
act(() => stateListener()({ isActive: false }));
now.mockReturnValue(10_000);
act(() => stateListener()({ isActive: true }));
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
act(() => stateListener()({ isActive: false }));
now.mockReturnValue(60_000);
act(() => stateListener()({ isActive: true }));
expect(screen.getByRole('dialog')).toBeInTheDocument();
now.mockRestore();
});
it('is nothing while the lock is off', () => {
vi.mocked(isAppLockEnabled).mockReturnValue(false);
const { container } = render();
expect(container).toBeEmptyDOMElement();
expect(unlockApp).not.toHaveBeenCalled();
});
});