import { ObservableStore } from '@metamask/obs-store'; import log from 'loglevel'; import { ORIGIN_METAMASK } from '../../../shared/constants/app'; import AppStateController from './app-state'; jest.mock('loglevel'); let appStateController, mockStore; describe('AppStateController', () => { mockStore = new ObservableStore(); const createAppStateController = (initState = {}) => { return new AppStateController({ addUnlockListener: jest.fn(), isUnlocked: jest.fn(() => true), initState, onInactiveTimeout: jest.fn(), showUnlockRequest: jest.fn(), preferencesStore: { subscribe: jest.fn(), getState: jest.fn(() => ({ preferences: { autoLockTimeLimit: 0, }, })), }, qrHardwareStore: { subscribe: jest.fn(), }, messenger: { call: jest.fn(() => ({ catch: jest.fn(), })), }, }); }; beforeEach(() => { appStateController = createAppStateController({ store: mockStore }); }); describe('setOutdatedBrowserWarningLastShown', () => { it('sets the last shown time', () => { appStateController = createAppStateController(); const date = new Date(); appStateController.setOutdatedBrowserWarningLastShown(date); expect( appStateController.store.getState().outdatedBrowserWarningLastShown, ).toStrictEqual(date); }); it('sets outdated browser warning last shown timestamp', () => { const lastShownTimestamp = Date.now(); appStateController = createAppStateController(); const updateStateSpy = jest.spyOn( appStateController.store, 'updateState', ); appStateController.setOutdatedBrowserWarningLastShown(lastShownTimestamp); expect(updateStateSpy).toHaveBeenCalledTimes(1); expect(updateStateSpy).toHaveBeenCalledWith({ outdatedBrowserWarningLastShown: lastShownTimestamp, }); updateStateSpy.mockRestore(); }); }); describe('getUnlockPromise', () => { it('waits for unlock if the extension is locked', async () => { appStateController = createAppStateController(); const isUnlockedMock = jest .spyOn(appStateController, 'isUnlocked') .mockReturnValue(false); const waitForUnlockSpy = jest.spyOn(appStateController, 'waitForUnlock'); appStateController.getUnlockPromise(true); expect(isUnlockedMock).toHaveBeenCalled(); expect(waitForUnlockSpy).toHaveBeenCalledWith(expect.any(Function), true); }); it('resolves immediately if the extension is already unlocked', async () => { appStateController = createAppStateController(); const isUnlockedMock = jest .spyOn(appStateController, 'isUnlocked') .mockReturnValue(true); await expect( appStateController.getUnlockPromise(false), ).resolves.toBeUndefined(); expect(isUnlockedMock).toHaveBeenCalled(); }); }); describe('waitForUnlock', () => { it('resolves immediately if already unlocked', async () => { const emitSpy = jest.spyOn(appStateController, 'emit'); const resolveFn = jest.fn(); appStateController.waitForUnlock(resolveFn, false); expect(emitSpy).toHaveBeenCalledWith('updateBadge'); expect(appStateController.messagingSystem.call).toHaveBeenCalledTimes(0); }); it('creates approval request when waitForUnlock is called with shouldShowUnlockRequest as true', async () => { jest.spyOn(appStateController, 'isUnlocked').mockReturnValue(false); const resolveFn = jest.fn(); appStateController.waitForUnlock(resolveFn, true); expect(appStateController.messagingSystem.call).toHaveBeenCalledTimes(1); expect(appStateController.messagingSystem.call).toHaveBeenCalledWith( 'ApprovalController:addRequest', expect.objectContaining({ id: expect.any(String), origin: ORIGIN_METAMASK, type: 'unlock', }), true, ); }); }); describe('handleUnlock', () => { beforeEach(() => { jest.spyOn(appStateController, 'isUnlocked').mockReturnValue(false); }); afterEach(() => { jest.clearAllMocks(); }); it('accepts approval request revolving all the related promises', async () => { const emitSpy = jest.spyOn(appStateController, 'emit'); const resolveFn = jest.fn(); appStateController.waitForUnlock(resolveFn, true); appStateController.handleUnlock(); expect(emitSpy).toHaveBeenCalled(); expect(emitSpy).toHaveBeenCalledWith('updateBadge'); expect(appStateController.messagingSystem.call).toHaveBeenCalled(); expect(appStateController.messagingSystem.call).toHaveBeenCalledWith( 'ApprovalController:acceptRequest', expect.any(String), ); }); it('logs if rejecting approval request throws', async () => { appStateController._approvalRequestId = 'mock-approval-request-id'; appStateController = new AppStateController({ addUnlockListener: jest.fn(), isUnlocked: jest.fn(() => true), onInactiveTimeout: jest.fn(), showUnlockRequest: jest.fn(), preferencesStore: { subscribe: jest.fn(), getState: jest.fn(() => ({ preferences: { autoLockTimeLimit: 0, }, })), }, qrHardwareStore: { subscribe: jest.fn(), }, messenger: { call: jest.fn(() => { throw new Error('mock error'); }), }, }); appStateController.handleUnlock(); expect(log.error).toHaveBeenCalledTimes(1); expect(log.error).toHaveBeenCalledWith( 'Attempted to accept missing unlock approval request', ); }); it('returns without call messenger if no approval request in pending', async () => { const emitSpy = jest.spyOn(appStateController, 'emit'); appStateController.handleUnlock(); expect(emitSpy).toHaveBeenCalledTimes(0); expect(appStateController.messagingSystem.call).toHaveBeenCalledTimes(0); expect(log.error).toHaveBeenCalledTimes(1); expect(log.error).toHaveBeenCalledWith( 'Attempted to accept missing unlock approval request', ); }); }); describe('setDefaultHomeActiveTabName', () => { it('sets the default home tab name', () => { appStateController.setDefaultHomeActiveTabName('testTabName'); expect(appStateController.store.getState().defaultHomeActiveTabName).toBe( 'testTabName', ); }); }); describe('setConnectedStatusPopoverHasBeenShown', () => { it('sets connected status popover as shown', () => { appStateController.setConnectedStatusPopoverHasBeenShown(); expect( appStateController.store.getState().connectedStatusPopoverHasBeenShown, ).toBe(true); }); }); describe('setRecoveryPhraseReminderHasBeenShown', () => { it('sets recovery phrase reminder as shown', () => { appStateController.setRecoveryPhraseReminderHasBeenShown(); expect( appStateController.store.getState().recoveryPhraseReminderHasBeenShown, ).toBe(true); }); }); describe('setRecoveryPhraseReminderLastShown', () => { it('sets the last shown time of recovery phrase reminder', () => { const timestamp = Date.now(); appStateController.setRecoveryPhraseReminderLastShown(timestamp); expect( appStateController.store.getState().recoveryPhraseReminderLastShown, ).toBe(timestamp); }); }); describe('setLastActiveTime', () => { it('sets the last active time to the current time', () => { const spy = jest.spyOn(appStateController, '_resetTimer'); appStateController.setLastActiveTime(); expect(spy).toHaveBeenCalled(); }); }); describe('setBrowserEnvironment', () => { it('sets the current browser and OS environment', () => { appStateController.setBrowserEnvironment('Windows', 'Chrome'); expect( appStateController.store.getState().browserEnvironment, ).toStrictEqual({ os: 'Windows', browser: 'Chrome', }); }); }); describe('addPollingToken', () => { it('adds a pollingToken for a given environmentType', () => { const pollingTokenType = 'popupGasPollTokens'; appStateController.addPollingToken('token1', pollingTokenType); expect(appStateController.store.getState()[pollingTokenType]).toContain( 'token1', ); }); }); describe('removePollingToken', () => { it('removes a pollingToken for a given environmentType', () => { const pollingTokenType = 'popupGasPollTokens'; appStateController.addPollingToken('token1', pollingTokenType); appStateController.removePollingToken('token1', pollingTokenType); expect( appStateController.store.getState()[pollingTokenType], ).not.toContain('token1'); }); }); describe('clearPollingTokens', () => { it('clears all pollingTokens', () => { appStateController.addPollingToken('token1', 'popupGasPollTokens'); appStateController.addPollingToken('token2', 'notificationGasPollTokens'); appStateController.addPollingToken('token3', 'fullScreenGasPollTokens'); appStateController.clearPollingTokens(); expect( appStateController.store.getState().popupGasPollTokens, ).toStrictEqual([]); expect( appStateController.store.getState().notificationGasPollTokens, ).toStrictEqual([]); expect( appStateController.store.getState().fullScreenGasPollTokens, ).toStrictEqual([]); }); }); describe('setShowTestnetMessageInDropdown', () => { it('sets whether the testnet dismissal link should be shown in the network dropdown', () => { appStateController.setShowTestnetMessageInDropdown(true); expect( appStateController.store.getState().showTestnetMessageInDropdown, ).toBe(true); appStateController.setShowTestnetMessageInDropdown(false); expect( appStateController.store.getState().showTestnetMessageInDropdown, ).toBe(false); }); }); describe('setShowBetaHeader', () => { it('sets whether the beta notification heading on the home page', () => { appStateController.setShowBetaHeader(true); expect(appStateController.store.getState().showBetaHeader).toBe(true); appStateController.setShowBetaHeader(false); expect(appStateController.store.getState().showBetaHeader).toBe(false); }); }); describe('setCurrentPopupId', () => { it('sets the currentPopupId in the appState', () => { const popupId = 'popup1'; appStateController.setCurrentPopupId(popupId); expect(appStateController.store.getState().currentPopupId).toBe(popupId); }); }); describe('getCurrentPopupId', () => { it('retrieves the currentPopupId saved in the appState', () => { const popupId = 'popup1'; appStateController.setCurrentPopupId(popupId); expect(appStateController.getCurrentPopupId()).toBe(popupId); }); }); describe('setFirstTimeUsedNetwork', () => { it('updates the array of the first time used networks', () => { const chainId = '0x1'; appStateController.setFirstTimeUsedNetwork(chainId); expect(appStateController.store.getState().usedNetworks[chainId]).toBe( true, ); }); }); });