Skip to content

Commit ca8d15e

Browse files
committed
fix: improve test coverage for nodejs and browser adapters
1 parent 6d69f40 commit ca8d15e

1 file changed

Lines changed: 176 additions & 156 deletions

File tree

Lines changed: 176 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as t from 'vitest'
22
import fs from 'fs'
3+
import path from 'path';
34
import AsyncStorage from '@react-native-async-storage/async-storage';
45

56

@@ -34,198 +35,217 @@ function createStoreTests(
3435
setupMocks?: () => void,
3536
cleanupMocks?: () => void
3637
) {
37-
t.describe(`Store with ${adapterName}`, () => {
38-
let store: Store;
39-
let adapter: StoreAdapter;
38+
let store: Store;
39+
let adapter: StoreAdapter;
4040

41-
t.beforeEach(async () => {
42-
setupMocks?.();
43-
adapter = createAdapter();
44-
store = new Store(adapter);
45-
});
41+
t.beforeEach(async () => {
42+
setupMocks?.();
43+
adapter = createAdapter();
44+
store = new Store(adapter);
45+
});
4646

47-
t.afterEach(async () => {
48-
nativeStorageStub.data.clear()
49-
cleanupMocks?.();
50-
});
47+
t.afterEach(async () => {
48+
nativeStorageStub.data.clear()
49+
cleanupMocks?.();
50+
});
5151

52-
t.describe(`${adapterName} constructor`, () => {
53-
t.it('should create a Store instance with the provided adapter', () => {
54-
t.expect(store).toBeInstanceOf(Store);
55-
});
52+
t.describe(`${adapterName} constructor`, () => {
53+
t.it('should create a Store instance with the provided adapter', () => {
54+
t.expect(store).toBeInstanceOf(Store);
5655
});
56+
});
5757

58-
t.describe(`${adapterName} getAnonId`, () => {
59-
t.it('should return the anonymous ID when it exists', async () => {
60-
await adapter.setItem('anonId', 'test-anon-id');
61-
const result = await store.getAnonId();
62-
t.expect(result).toBe('test-anon-id');
63-
});
64-
65-
t.it('should return null when anonymous ID does not exist', async () => {
66-
const result = await store.getAnonId();
67-
t.expect(result).toBeNull();
68-
});
58+
t.describe(`${adapterName} getAnonId`, () => {
59+
t.it('should return the anonymous ID when it exists', async () => {
60+
await adapter.setItem('anonId', 'test-anon-id');
61+
const result = await store.getAnonId();
62+
t.expect(result).toBe('test-anon-id');
6963
});
7064

71-
t.describe(`${adapterName} setAnonId`, () => {
72-
t.it('should set the anonymous ID successfully', async () => {
73-
await store.setAnonId('new-anon-id');
74-
const result = await adapter.getItem('anonId');
75-
t.expect(result).toBe('new-anon-id');
76-
});
65+
t.it('should return null when anonymous ID does not exist', async () => {
66+
const result = await store.getAnonId();
67+
t.expect(result).toBeNull();
7768
});
69+
});
7870

79-
t.describe(`${adapterName} removeAnonId`, () => {
80-
t.it('should remove the anonymous ID successfully', async () => {
81-
await adapter.setItem('anonId', 'test-anon-id');
82-
const beforeRemove = await adapter.getItem('anonId');
83-
t.expect(beforeRemove).toBe('test-anon-id');
84-
85-
await store.removeAnonId();
86-
const afterRemove = await adapter.getItem('anonId');
87-
t.expect(afterRemove).toBeNull();
88-
});
71+
t.describe(`${adapterName} setAnonId`, () => {
72+
t.it('should set the anonymous ID successfully', async () => {
73+
await store.setAnonId('new-anon-id');
74+
const result = await adapter.getItem('anonId');
75+
t.expect(result).toBe('new-anon-id');
8976
});
77+
});
9078

91-
t.describe(`${adapterName} getExtensionId`, () => {
92-
t.it('should return the extension ID when it exists', async () => {
93-
await adapter.setItem('extensionId', 'test-extension-id');
94-
const result = await store.getExtensionId();
95-
t.expect(result).toBe('test-extension-id');
96-
});
79+
t.describe(`${adapterName} removeAnonId`, () => {
80+
t.it('should remove the anonymous ID successfully', async () => {
81+
await adapter.setItem('anonId', 'test-anon-id');
82+
const beforeRemove = await adapter.getItem('anonId');
83+
t.expect(beforeRemove).toBe('test-anon-id');
9784

98-
t.it('should return null when extension ID does not exist', async () => {
99-
const result = await store.getExtensionId();
100-
t.expect(result).toBeNull();
101-
});
85+
await store.removeAnonId();
86+
const afterRemove = await adapter.getItem('anonId');
87+
t.expect(afterRemove).toBeNull();
10288
});
89+
});
10390

104-
t.describe(`${adapterName} setExtensionId`, () => {
105-
t.it('should set the extension ID successfully', async () => {
106-
await store.setExtensionId('new-extension-id');
107-
const result = await adapter.getItem('extensionId');
108-
t.expect(result).toBe('new-extension-id');
109-
});
91+
t.describe(`${adapterName} getExtensionId`, () => {
92+
t.it('should return the extension ID when it exists', async () => {
93+
await adapter.setItem('extensionId', 'test-extension-id');
94+
const result = await store.getExtensionId();
95+
t.expect(result).toBe('test-extension-id');
11096
});
11197

112-
t.describe(`${adapterName} removeExtensionId`, () => {
113-
t.it('should remove the extension ID successfully', async () => {
114-
await adapter.setItem('extensionId', 'test-extension-id');
115-
const beforeRemove = await adapter.getItem('extensionId');
116-
t.expect(beforeRemove).toBe('test-extension-id');
98+
t.it('should return null when extension ID does not exist', async () => {
99+
const result = await store.getExtensionId();
100+
t.expect(result).toBeNull();
101+
});
102+
});
117103

118-
await store.removeExtensionId();
119-
const afterRemove = await adapter.getItem('extensionId');
120-
t.expect(afterRemove).toBeNull();
121-
});
104+
t.describe(`${adapterName} setExtensionId`, () => {
105+
t.it('should set the extension ID successfully', async () => {
106+
await store.setExtensionId('new-extension-id');
107+
const result = await adapter.getItem('extensionId');
108+
t.expect(result).toBe('new-extension-id');
122109
});
110+
});
123111

124-
t.describe(`${adapterName} getChannelConfig`, () => {
125-
t.it('should return the channel config when it exists and is valid JSON', async () => {
126-
const channelConfig: ChannelConfig = {
127-
channelId: 'test-channel',
128-
validUntil: Date.now() + 3600000,
129-
otherKey: 'other-key',
130-
localKey: 'local-key',
131-
walletVersion: '1.0.0',
132-
deeplinkProtocolAvailable: true,
133-
relayPersistence: false,
134-
lastActive: Date.now()
135-
};
112+
t.describe(`${adapterName} removeExtensionId`, () => {
113+
t.it('should remove the extension ID successfully', async () => {
114+
await adapter.setItem('extensionId', 'test-extension-id');
115+
const beforeRemove = await adapter.getItem('extensionId');
116+
t.expect(beforeRemove).toBe('test-extension-id');
136117

137-
await adapter.setItem('channelConfig', JSON.stringify(channelConfig));
138-
const result = await store.getChannelConfig();
118+
await store.removeExtensionId();
119+
const afterRemove = await adapter.getItem('extensionId');
120+
t.expect(afterRemove).toBeNull();
121+
});
122+
});
139123

140-
t.expect(result).toEqual(channelConfig);
141-
});
124+
t.describe(`${adapterName} getChannelConfig`, () => {
125+
t.it('should return the channel config when it exists and is valid JSON', async () => {
126+
const channelConfig: ChannelConfig = {
127+
channelId: 'test-channel',
128+
validUntil: Date.now() + 3600000,
129+
otherKey: 'other-key',
130+
localKey: 'local-key',
131+
walletVersion: '1.0.0',
132+
deeplinkProtocolAvailable: true,
133+
relayPersistence: false,
134+
lastActive: Date.now()
135+
};
136+
137+
await adapter.setItem('channelConfig', JSON.stringify(channelConfig));
138+
const result = await store.getChannelConfig();
139+
140+
t.expect(result).toEqual(channelConfig);
141+
});
142142

143-
t.it('should return null when channel config does not exist', async () => {
144-
const result = await store.getChannelConfig();
145-
t.expect(result).toBeNull();
146-
});
143+
t.it('should return null when channel config does not exist', async () => {
144+
const result = await store.getChannelConfig();
145+
t.expect(result).toBeNull();
146+
});
147147

148-
t.it('should throw an error when stored JSON is invalid', async () => {
149-
await adapter.setItem('channelConfig', 'invalid-json');
150-
await t.expect(store.getChannelConfig()).rejects.toThrow();
151-
});
148+
t.it('should throw an error when stored JSON is invalid', async () => {
149+
await adapter.setItem('channelConfig', 'invalid-json');
150+
await t.expect(store.getChannelConfig()).rejects.toThrow();
152151
});
152+
});
153153

154-
t.describe(`${adapterName} setChannelConfig`, () => {
155-
t.it('should set the channel config successfully', async () => {
156-
const channelConfig: ChannelConfig = {
157-
channelId: 'test-channel',
158-
validUntil: Date.now() + 3600000,
159-
otherKey: 'other-key',
160-
localKey: 'local-key'
161-
};
154+
t.describe(`${adapterName} setChannelConfig`, () => {
155+
t.it('should set the channel config successfully', async () => {
156+
const channelConfig: ChannelConfig = {
157+
channelId: 'test-channel',
158+
validUntil: Date.now() + 3600000,
159+
otherKey: 'other-key',
160+
localKey: 'local-key'
161+
};
162162

163-
await store.setChannelConfig(channelConfig);
163+
await store.setChannelConfig(channelConfig);
164164

165-
const storedValue = await adapter.getItem('channelConfig');
166-
t.expect(JSON.parse(storedValue!)).toEqual(channelConfig);
167-
});
165+
const storedValue = await adapter.getItem('channelConfig');
166+
t.expect(JSON.parse(storedValue!)).toEqual(channelConfig);
167+
});
168168

169-
t.it('should handle minimal channel config', async () => {
170-
const channelConfig: ChannelConfig = {
171-
channelId: 'minimal-channel',
172-
validUntil: Date.now() + 3600000
173-
};
169+
t.it('should handle minimal channel config', async () => {
170+
const channelConfig: ChannelConfig = {
171+
channelId: 'minimal-channel',
172+
validUntil: Date.now() + 3600000
173+
};
174174

175-
await store.setChannelConfig(channelConfig);
175+
await store.setChannelConfig(channelConfig);
176176

177-
const storedValue = await adapter.getItem('channelConfig');
178-
t.expect(JSON.parse(storedValue!)).toEqual(channelConfig);
179-
});
177+
const storedValue = await adapter.getItem('channelConfig');
178+
t.expect(JSON.parse(storedValue!)).toEqual(channelConfig);
180179
});
180+
});
181181

182-
t.describe(`${adapterName} getDebug`, () => {
183-
t.it('should return the debug value when it exists', async () => {
184-
await adapter.setItem('DEBUG', 'metamask-sdk:*');
185-
const result = await store.getDebug();
186-
t.expect(result).toBe('metamask-sdk:*');
187-
});
182+
t.describe(`${adapterName} getDebug`, () => {
183+
t.it('should return the debug value when it exists', async () => {
184+
await adapter.setItem('DEBUG', 'metamask-sdk:*');
185+
const result = await store.getDebug();
186+
t.expect(result).toBe('metamask-sdk:*');
187+
});
188188

189-
t.it('should return null when debug value does not exist', async () => {
190-
const result = await store.getDebug();
191-
t.expect(result).toBeNull();
192-
});
189+
t.it('should return null when debug value does not exist', async () => {
190+
const result = await store.getDebug();
191+
t.expect(result).toBeNull();
193192
});
194193
});
195194
}
196195

197196

198-
199-
// Test with Node Adapter and mocked file system
200-
createStoreTests(
201-
'NodeAdapter',
202-
() => new StoreAdapterNode(),
203-
async () => {
204-
const memfs = new Map<string, any>()
205-
t.vi.spyOn(fs, 'existsSync').mockImplementation((path) => memfs.has(path.toString()))
206-
t.vi.spyOn(fs, 'writeFileSync').mockImplementation((path, data) => memfs.set(path.toString(), data))
207-
t.vi.spyOn(fs, 'readFileSync').mockImplementation((path) => memfs.get(path.toString()))
208-
}
209-
);
210-
211-
//Test browser storage with mocked local storage
212-
createStoreTests(
213-
'WebAdapter',
214-
() => new StoreAdapterWeb(),
215-
() => {
197+
t.describe(`Store with NodeAdapter`, () => {
198+
// Test with Node Adapter and mocked file system
199+
createStoreTests(
200+
'NodeAdapter',
201+
() => new StoreAdapterNode(),
202+
async () => {
203+
const memfs = new Map<string, any>()
204+
t.vi.spyOn(fs, 'existsSync').mockImplementation((path) => memfs.has(path.toString()))
205+
t.vi.spyOn(fs, 'writeFileSync').mockImplementation((path, data) => memfs.set(path.toString(), data))
206+
t.vi.spyOn(fs, 'readFileSync').mockImplementation((path) => memfs.get(path.toString()))
207+
}
208+
);
209+
210+
t.it("Should gracefully manage deleteItem even if the config file does not exist", async () => {
211+
const CONFIG_FILE = path.resolve(process.cwd(), '.metamask.json');
212+
t.vi.spyOn(fs, 'existsSync').mockImplementation(() => false)
213+
const store = new Store(new StoreAdapterNode())
214+
await store.removeExtensionId()
215+
t.expect(fs.existsSync).toHaveBeenCalledWith(CONFIG_FILE)
216+
})
217+
});
218+
219+
t.describe(`Store with WebAdapter`, () => {
220+
//Test browser storage with mocked local storage
221+
createStoreTests(
222+
'WebAdapter',
223+
() => new StoreAdapterWeb(),
224+
() => {
225+
t.vi.stubGlobal('window', {
226+
localStorage: nativeStorageStub,
227+
});
228+
}
229+
);
230+
231+
t.it("Should throw an exception if we try using the store with a browser that doesn't support localStorage", async () => {
216232
t.vi.stubGlobal('window', {
217-
localStorage: nativeStorageStub,
218-
});
219-
}
220-
);
221-
222-
// Test RN storage with mocked AsyncStorage
223-
createStoreTests(
224-
'RNAdapter',
225-
() => new StoreAdapterRN(),
226-
() => {
227-
t.vi.spyOn(AsyncStorage, 'getItem').mockImplementation(async (key) => nativeStorageStub.getItem(key))
228-
t.vi.spyOn(AsyncStorage, 'setItem').mockImplementation(async (key, value) => nativeStorageStub.setItem(key, value))
229-
t.vi.spyOn(AsyncStorage, 'removeItem').mockImplementation(async (key) => nativeStorageStub.removeItem(key))
230-
}
231-
);
233+
localStorage: null,
234+
});
235+
const store = new Store(new StoreAdapterWeb());
236+
await t.expect(() => store.getAnonId()).rejects.toThrow();
237+
});
238+
});
239+
240+
t.describe(`Store with RNAdapter`, () => {
241+
// Test RN storage with mocked AsyncStorage
242+
createStoreTests(
243+
'RNAdapter',
244+
() => new StoreAdapterRN(),
245+
() => {
246+
t.vi.spyOn(AsyncStorage, 'getItem').mockImplementation(async (key) => nativeStorageStub.getItem(key))
247+
t.vi.spyOn(AsyncStorage, 'setItem').mockImplementation(async (key, value) => nativeStorageStub.setItem(key, value))
248+
t.vi.spyOn(AsyncStorage, 'removeItem').mockImplementation(async (key) => nativeStorageStub.removeItem(key))
249+
}
250+
);
251+
});

0 commit comments

Comments
 (0)