mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-13 07:40:59 +00:00
017f229cfc
A switch in the account settings, inside the app on a device that can unlock that way (iOS LocalAuthentication, Android BiometricPrompt with the screen lock as fallback). Turning it on asks once, so nobody locks themselves out. With it on, TREK asks at launch and after half a minute in the background before showing anything, and the app switcher shows a blurred cover (iOS) or no snapshot (Android) instead of the screen. The setting stays on the device, not the account.
64 lines
2.5 KiB
TypeScript
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 === '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();
|
|
});
|
|
});
|