feat(test-utils): mock storage api

This commit is contained in:
Fredrik Adelöw
2020-06-26 09:41:28 +02:00
parent 22d1c40629
commit 879bc21893
6 changed files with 255 additions and 2 deletions
+2 -1
View File
@@ -41,7 +41,8 @@
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-router": "^6.0.0-alpha.5",
"react-router-dom": "^6.0.0-alpha.5"
"react-router-dom": "^6.0.0-alpha.5",
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@types/jest": "^25.2.2",
@@ -0,0 +1,142 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { MockStorageApi } from './MockStorageApi';
import { StorageApi } from '@backstage/core-api';
describe('WebStorage Storage API', () => {
const createMockStorage = (): StorageApi => {
return MockStorageApi.create();
};
it('should return undefined for values which are unset', async () => {
const storage = createMockStorage();
expect(storage.get('myfakekey')).toBeUndefined();
});
it('should allow the setting and getting of the simple data structures', async () => {
const storage = createMockStorage();
await storage.set('myfakekey', 'helloimastring');
await storage.set('mysecondfakekey', 1234);
await storage.set('mythirdfakekey', true);
expect(storage.get('myfakekey')).toBe('helloimastring');
expect(storage.get('mysecondfakekey')).toBe(1234);
expect(storage.get('mythirdfakekey')).toBe(true);
});
it('should allow setting of complex datastructures', async () => {
const storage = createMockStorage();
const mockData = {
something: 'here',
is: [{ super: { complex: [{ but: 'something', why: true }] } }],
};
await storage.set('myfakekey', mockData);
expect(storage.get('myfakekey')).toEqual(mockData);
});
it('should subscribe to key changes when setting a new value', async () => {
const storage = createMockStorage();
const wrongKeyNextHandler = jest.fn();
const selectedKeyNextHandler = jest.fn();
const mockData = { hello: 'im a great new value' };
await new Promise(resolve => {
storage.observe$<String>('correctKey').subscribe({
next: (...args) => {
selectedKeyNextHandler(...args);
resolve();
},
});
storage.observe$('wrongKey').subscribe({ next: wrongKeyNextHandler });
storage.set('correctKey', mockData);
});
expect(wrongKeyNextHandler).not.toHaveBeenCalled();
expect(selectedKeyNextHandler).toHaveBeenCalledTimes(1);
expect(selectedKeyNextHandler).toHaveBeenCalledWith({
key: 'correctKey',
newValue: mockData,
});
});
it('should subscribe to key changes when deleting a value', async () => {
const storage = createMockStorage();
const wrongKeyNextHandler = jest.fn();
const selectedKeyNextHandler = jest.fn();
const mockData = { hello: 'im a great new value' };
storage.set('correctKey', mockData);
await new Promise(resolve => {
storage.observe$('correctKey').subscribe({
next: (...args) => {
selectedKeyNextHandler(...args);
resolve();
},
});
storage.observe$('wrongKey').subscribe({ next: wrongKeyNextHandler });
storage.remove('correctKey');
});
expect(wrongKeyNextHandler).not.toHaveBeenCalled();
expect(selectedKeyNextHandler).toHaveBeenCalledTimes(1);
expect(selectedKeyNextHandler).toHaveBeenCalledWith({
key: 'correctKey',
newValue: undefined,
});
});
it('should be able to create different buckets for different uses', async () => {
const rootStorage = createMockStorage();
const firstStorage = rootStorage.forBucket('userSettings');
const secondStorage = rootStorage.forBucket('profileSettings');
const keyName = 'blobby';
await firstStorage.set(keyName, 'boop');
await secondStorage.set(keyName, 'deerp');
expect(firstStorage.get(keyName)).not.toBe(secondStorage.get(keyName));
expect(firstStorage.get(keyName)).toBe('boop');
expect(secondStorage.get(keyName)).toBe('deerp');
});
it('should not clash with other namesapces when creating buckets', async () => {
const rootStorage = createMockStorage();
// when getting key test2 it will translate to /profile/something/deep/test2
const firstStorage = rootStorage
.forBucket('profile')
.forBucket('something')
.forBucket('deep');
// when getting key deep/test2 it will translate to /profile/something/deep/test2
const secondStorage = rootStorage.forBucket('profile/something');
await firstStorage.set('test2', { error: true });
expect(secondStorage.get('deep/test2')).toBe(undefined);
});
});
@@ -0,0 +1,90 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Observable,
StorageApi,
storageApiRef,
StorageValueChange,
} from '@backstage/core-api';
import ObservableImpl from 'zen-observable';
export type MockStorageBucket = { [key: string]: any };
export class MockStorageApi implements StorageApi {
static factory = {
implements: storageApiRef,
deps: {},
factory: () => MockStorageApi.create(),
};
private readonly namespace: string;
private readonly data: MockStorageBucket;
private constructor(namespace: string, data?: MockStorageBucket) {
this.namespace = namespace;
this.data = { ...data };
}
static create(data?: MockStorageBucket) {
return new MockStorageApi('', data);
}
forBucket(name: string): StorageApi {
return new MockStorageApi(`${this.namespace}/${name}`, this.data);
}
get<T>(key: string): T | undefined {
return this.data[this.getKeyName(key)];
}
async set<T>(key: string, data: T): Promise<void> {
this.data[this.getKeyName(key)] = data;
this.notifyChanges({ key, newValue: data });
}
async remove(key: string): Promise<void> {
delete this.data[this.getKeyName(key)];
this.notifyChanges({ key, newValue: undefined });
}
observe$<T>(key: string): Observable<StorageValueChange<T>> {
return this.observable.filter(({ key: messageKey }) => messageKey === key);
}
private getKeyName(key: string) {
return `${this.namespace}/${encodeURIComponent(key)}`;
}
private notifyChanges<T>(message: StorageValueChange<T>) {
for (const subscription of this.subscribers) {
subscription.next(message);
}
}
private subscribers = new Set<
ZenObservable.SubscriptionObserver<StorageValueChange>
>();
private readonly observable = new ObservableImpl<StorageValueChange>(
subscriber => {
this.subscribers.add(subscriber);
return () => {
this.subscribers.delete(subscriber);
};
},
);
}
@@ -0,0 +1,18 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { MockStorageApi } from './MockStorageApi';
export type { MockStorageBucket } from './MockStorageApi';
@@ -15,3 +15,4 @@
*/
export * from './ErrorApi';
export * from './StorageApi';
@@ -15,12 +15,13 @@
*/
import { ApiTestRegistry } from '@backstage/core-api';
import { MockErrorApi } from './apis';
import { MockErrorApi, MockStorageApi } from './apis';
export function createMockApiRegistry(): ApiTestRegistry {
const registry = new ApiTestRegistry();
registry.register(MockErrorApi.factory);
registry.register(MockStorageApi.factory);
return registry;
}