Skip to content

Commit

Permalink
feat(25903): Move state management from storage.local to IndexedDB
Browse files Browse the repository at this point in the history
  • Loading branch information
DDDDDanica committed Sep 24, 2024
1 parent d3d9fa8 commit a5f5f56
Show file tree
Hide file tree
Showing 7 changed files with 442 additions and 228 deletions.
4 changes: 2 additions & 2 deletions app/scripts/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ import { getCurrentChainId } from '../../ui/selectors';
import migrations from './migrations';
import Migrator from './lib/migrator';
import ExtensionPlatform from './platforms/extension';
import LocalStore from './lib/local-store';
import ReadOnlyNetworkStore from './lib/network-store';
import LocalStore from './lib/state-management/local-store';
import ReadOnlyNetworkStore from './lib/state-management/network-store';
import { SENTRY_BACKGROUND_STATE } from './constants/sentry-state';

import createStreamSink from './lib/createStreamSink';
Expand Down
139 changes: 0 additions & 139 deletions app/scripts/lib/local-store.js

This file was deleted.

4 changes: 2 additions & 2 deletions app/scripts/lib/setup-initial-state-hooks.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { maskObject } from '../../../shared/modules/object.utils';
import ExtensionPlatform from '../platforms/extension';
import { SENTRY_BACKGROUND_STATE } from '../constants/sentry-state';
import LocalStore from './local-store';
import ReadOnlyNetworkStore from './network-store';
import LocalStore from './state-management/local-store';
import ReadOnlyNetworkStore from './state-management/network-store';

const platform = new ExtensionPlatform();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,76 @@
import browser from 'webextension-polyfill';
import LocalStore from './local-store';

jest.mock('webextension-polyfill', () => ({
runtime: { lastError: null },
storage: { local: true },
}));

const setup = ({ localMock = jest.fn() } = {}) => {
browser.storage.local = localMock;
return new LocalStore();
const mockIDBRequest = (result, isError) => {
const request = {
onsuccess: null,
onerror: null,
result,
};

// Delay execution to simulate async behavior of IDBRequest
setTimeout(() => {
if (isError) {
if (typeof request.onerror === 'function') {
request.onerror({ target: { error: 'Mock error' } });
}
} else if (typeof request.onsuccess === 'function') {
request.onsuccess({ target: request });
}
}, 0);

return request;
};

const createEmptySetup = () =>
(global.indexedDB = {
open: jest.fn(() =>
mockIDBRequest({
transaction: jest.fn(() => ({
objectStore: jest.fn(() => ({
get: jest.fn(() => mockIDBRequest({})),
put: jest.fn(() => mockIDBRequest({})),
})),
})),
}),
),
});

describe('LocalStore', () => {
let setup;
beforeEach(() => {
setup = () => {
// Mock the indexedDB open function
global.indexedDB = {
open: jest.fn(() =>
mockIDBRequest({
transaction: jest.fn(() => ({
objectStore: jest.fn(() => ({
get: jest.fn(() =>
mockIDBRequest({ appState: { test: true } }),
),
put: jest.fn(() => mockIDBRequest({})),
})),
})),
}),
),
};
return new LocalStore();
};
});

afterEach(() => {
jest.resetModules();
jest.clearAllMocks();
});
describe('contructor', () => {
it('should set isSupported property to false when browser does not support local storage', () => {
const localStore = setup({ localMock: false });

expect(localStore.isSupported).toBe(false);
});

it('should set isSupported property to true when browser supports local storage', () => {
const localStore = setup();
expect(localStore.isSupported).toBe(true);
});

describe('constructor', () => {
it('should initialize mostRecentRetrievedState to null', () => {
const localStore = setup({ localMock: false });
const localStore = setup();
expect(localStore.mostRecentRetrievedState).toBeNull();
});

it('should initialize isExtensionInitialized to false', () => {
const localStore = setup({ localMock: false });
const localStore = setup();
expect(localStore.isExtensionInitialized).toBeFalsy();
});
});
Expand All @@ -48,13 +86,6 @@ describe('LocalStore', () => {
});

describe('set', () => {
it('should throw an error if called in a browser that does not support local storage', async () => {
const localStore = setup({ localMock: false });
await expect(() => localStore.set()).rejects.toThrow(
'Metamask- cannot persist state to local store as this browser does not support this action',
);
});

it('should throw an error if not passed a truthy value as an argument', async () => {
const localStore = setup();
await expect(() => localStore.set()).rejects.toThrow(
Expand All @@ -74,8 +105,8 @@ describe('LocalStore', () => {
it('should not throw if passed a valid argument and metadata has been set', async () => {
const localStore = setup();
localStore.setMetadata({ version: 74 });
await expect(async function () {
localStore.set({ appState: { test: true } });
await expect(async () => {
await localStore.set({ appState: { test: true } });
}).not.toThrow();
});

Expand All @@ -88,22 +119,19 @@ describe('LocalStore', () => {
});

describe('get', () => {
it('should return undefined if called in a browser that does not support local storage', async () => {
const localStore = setup({ localMock: false });
it('should return undefined if no state is stored', async () => {
setup = () => {
createEmptySetup();
return new LocalStore();
};

const localStore = setup();
const result = await localStore.get();
expect(result).toStrictEqual(undefined);
});

it('should update mostRecentRetrievedState', async () => {
const localStore = setup({
localMock: {
get: jest
.fn()
.mockImplementation(() =>
Promise.resolve({ appState: { test: true } }),
),
},
});
const localStore = setup();

await localStore.get();

Expand All @@ -112,24 +140,19 @@ describe('LocalStore', () => {
});
});

it('should reset mostRecentRetrievedState to null if storage.local is empty', async () => {
const localStore = setup({
localMock: {
get: jest.fn().mockImplementation(() => Promise.resolve({})),
},
});
it('should reset mostRecentRetrievedState to null if storage is empty', async () => {
setup = () => {
createEmptySetup();
return new LocalStore();
};

const localStore = setup();
await localStore.get();

expect(localStore.mostRecentRetrievedState).toStrictEqual(null);
});

it('should set mostRecentRetrievedState to current state if isExtensionInitialized is true', async () => {
const localStore = setup({
localMock: {
get: jest.fn().mockImplementation(() => Promise.resolve({})),
},
});
const localStore = setup();
localStore.setMetadata({ version: 74 });
await localStore.set({ appState: { test: true } });
await localStore.get();
Expand All @@ -139,25 +162,14 @@ describe('LocalStore', () => {

describe('cleanUpMostRecentRetrievedState', () => {
it('should set mostRecentRetrievedState to null if it is defined', async () => {
const localStore = setup({
localMock: {
get: jest
.fn()
.mockImplementation(() =>
Promise.resolve({ appState: { test: true } }),
),
},
});
const localStore = setup();
await localStore.get();

// mostRecentRetrievedState should be { appState: { test: true } } at this stage
await localStore.cleanUpMostRecentRetrievedState();
expect(localStore.mostRecentRetrievedState).toStrictEqual(null);
});

it('should not set mostRecentRetrievedState if it is null', async () => {
const localStore = setup();

expect(localStore.mostRecentRetrievedState).toStrictEqual(null);
await localStore.cleanUpMostRecentRetrievedState();
expect(localStore.mostRecentRetrievedState).toStrictEqual(null);
Expand Down
Loading

0 comments on commit a5f5f56

Please sign in to comment.