Files
TREK/client/src/components/Layout/AppLockGate.test.tsx
T
Maurice b7951baf23 fix(ci): green the dev gates after the native and popup rounds
- RTL: the measured pills and centred controls use logical forms or a marked line
- i18n: the two native widget numbers are positions, not counts (NOT_PLURAL)
- Types: styleOverride on the lazy GL map, the location and shared-file payloads,
  the capabilities fallback, the transport sheet's stay metadata
- ESLint: latest-value refs move to layout effects, usePresence keeps the last
  open state in state, the booking import opens clean with a shared document
  kept in the list (the reset effect used to clear it again)
- Layers: the phone import sheet lives under mobile/ and reads through tripRepo;
  the offline host check moves to utils/tileUrl
- Sizes: plugin layer geometry, hardNavigate and the exit animations get
  modules of their own
- Tests follow the status radios, the stay chip overview, the dock + button
  for the first list and the field token; store registry lists the new stores
2026-11-06 18:24:25 +01:00

64 lines
2.5 KiB
TypeScript

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 as string) === '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(<AppLockGate />);
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(<AppLockGate />);
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(<AppLockGate />);
expect(container).toBeEmptyDOMElement();
expect(unlockApp).not.toHaveBeenCalled();
});
});