mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-3898 add base CachedMapResource tests (#2013)
* CB-3898 add base CachedMapResource tests * CB-3908 add tests for the handlers * CB-3908 add error tests * CB-3908 add initial tests for the CachedDataResource * CB-3908 remove extra test * CB-3908 add clear data test * CB-3908 add CachedTreeResource tests * CB-3908 review fixes * CB-3908 transform mock data to getters
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
"gql:gen:dev": "graphql-codegen --watch",
|
||||
"lint": "eslint ./src/ --ext .ts,.tsx",
|
||||
"lint-fix": "eslint ./src/ --ext .ts,.tsx --fix",
|
||||
"test": "core-cli-test",
|
||||
"validate-dependencies": "core-cli-validate-dependencies"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { CachedDataResource } from './CachedDataResource';
|
||||
|
||||
interface IEntityData {
|
||||
id: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
const DEFAULT_STATE_GETTER: () => IEntityData[] = () => [];
|
||||
const DATA_MOCK_GETTER: () => IEntityData[] = () => [
|
||||
{
|
||||
id: '1',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
value: 2,
|
||||
},
|
||||
];
|
||||
|
||||
async function fetchMock(): Promise<IEntityData[]> {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(DATA_MOCK_GETTER());
|
||||
}, 1);
|
||||
});
|
||||
}
|
||||
|
||||
class TestDataResource extends CachedDataResource<IEntityData[]> {
|
||||
constructor() {
|
||||
super(DEFAULT_STATE_GETTER);
|
||||
}
|
||||
|
||||
protected async loader() {
|
||||
const data = await fetchMock();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
describe('CachedDataResource', () => {
|
||||
let dataResource: TestDataResource;
|
||||
|
||||
beforeEach(() => {
|
||||
dataResource = new TestDataResource();
|
||||
});
|
||||
|
||||
test('should instantiate correctly', () => {
|
||||
expect(dataResource).toBeInstanceOf(CachedDataResource);
|
||||
});
|
||||
|
||||
test('should initialize with the default state', () => {
|
||||
expect(dataResource.data).toEqual(DEFAULT_STATE_GETTER());
|
||||
});
|
||||
|
||||
test('should load data', async () => {
|
||||
await dataResource.load();
|
||||
expect(dataResource.data).toEqual(DATA_MOCK_GETTER());
|
||||
});
|
||||
|
||||
test('should be able to outdate the data', () => {
|
||||
dataResource.markOutdated();
|
||||
expect(dataResource.isOutdated()).toBe(true);
|
||||
});
|
||||
|
||||
test('should run onDataOutdated handlers on data outdate', () => {
|
||||
const handler = jest.fn();
|
||||
|
||||
dataResource.onDataOutdated.addHandler(handler);
|
||||
dataResource.markOutdated();
|
||||
|
||||
expect(handler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should run onDataUpdate handlers on data update', () => {
|
||||
const handler = jest.fn();
|
||||
|
||||
dataResource.onDataUpdate.addHandler(handler);
|
||||
dataResource.dataUpdate();
|
||||
|
||||
expect(handler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should be able to clear the data', async () => {
|
||||
await dataResource.load();
|
||||
dataResource.clear();
|
||||
|
||||
expect(dataResource.data).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { toJS } from 'mobx';
|
||||
|
||||
import { CachedMapResource } from './CachedMapResource';
|
||||
import type { ResourceKey } from './ResourceKey';
|
||||
import { resourceKeyList } from './ResourceKeyList';
|
||||
|
||||
interface IEntityData {
|
||||
id: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
const ERROR_ITEM_ID = 'error';
|
||||
|
||||
const DATA_MOCK_GETTER: () => IEntityData[] = () => [
|
||||
{
|
||||
id: '1',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
id: ERROR_ITEM_ID,
|
||||
value: 3,
|
||||
},
|
||||
];
|
||||
|
||||
const TEST_ERROR_MESSAGE = 'Test error';
|
||||
const DEFAULT_STATE_GETTER = () => new Map();
|
||||
|
||||
async function fetchMock(key: ResourceKey<string> | undefined): Promise<IEntityData[]> {
|
||||
const data = DATA_MOCK_GETTER();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
if (key) {
|
||||
if (key === ERROR_ITEM_ID) {
|
||||
reject(new Error(TEST_ERROR_MESSAGE));
|
||||
}
|
||||
|
||||
const item = data.find(d => d.id === key);
|
||||
if (item) {
|
||||
resolve([item]);
|
||||
}
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
}, 1);
|
||||
});
|
||||
}
|
||||
|
||||
class TestMapResource extends CachedMapResource<string, IEntityData> {
|
||||
constructor() {
|
||||
super(DEFAULT_STATE_GETTER);
|
||||
}
|
||||
|
||||
protected async loader(key: ResourceKey<string>) {
|
||||
const data = await fetchMock(key);
|
||||
this.replace(resourceKeyList(data.map(d => d.id)), data);
|
||||
return this.data;
|
||||
}
|
||||
|
||||
protected validateKey(key: string): boolean {
|
||||
return typeof key === 'string';
|
||||
}
|
||||
}
|
||||
|
||||
describe('CachedMapResource', () => {
|
||||
let mapResource: TestMapResource;
|
||||
|
||||
beforeEach(() => {
|
||||
mapResource = new TestMapResource();
|
||||
});
|
||||
|
||||
test('should instantiate correctly', () => {
|
||||
expect(mapResource).toBeInstanceOf(CachedMapResource);
|
||||
});
|
||||
|
||||
test('should initialize with the initial value', () => {
|
||||
expect(toJS(mapResource.data)).toEqual(DEFAULT_STATE_GETTER());
|
||||
});
|
||||
|
||||
test('should return all entries', () => {
|
||||
mapResource.set('key1', { id: 'key1', value: 1 });
|
||||
mapResource.set('key2', { id: 'key2', value: 2 });
|
||||
expect(mapResource.entries).toEqual([
|
||||
['key1', { id: 'key1', value: 1 }],
|
||||
['key2', { id: 'key2', value: 2 }],
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return all values', () => {
|
||||
mapResource.set('key1', { id: 'key1', value: 1 });
|
||||
mapResource.set('key2', { id: 'key2', value: 2 });
|
||||
expect(mapResource.values).toEqual([
|
||||
{ id: 'key1', value: 1 },
|
||||
{ id: 'key2', value: 2 },
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return all keys', () => {
|
||||
mapResource.set('key1', { id: 'key1', value: 1 });
|
||||
mapResource.set('key2', { id: 'key2', value: 2 });
|
||||
expect(mapResource.keys).toEqual(['key1', 'key2']);
|
||||
});
|
||||
|
||||
test('should load data for a specific key', async () => {
|
||||
await mapResource.load('1');
|
||||
expect(mapResource.get('1')).toEqual({ id: '1', value: 1 });
|
||||
expect(mapResource.get('2')).toBeUndefined(); // This key was not loaded
|
||||
});
|
||||
|
||||
test('should NOT load data for a key that produces an error', async () => {
|
||||
await expect(mapResource.load(ERROR_ITEM_ID)).rejects.toThrow(TEST_ERROR_MESSAGE);
|
||||
expect(mapResource.get(ERROR_ITEM_ID)).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should mark loaded data as loaded', async () => {
|
||||
await mapResource.load('1');
|
||||
expect(mapResource.isLoaded('1')).toBe(true);
|
||||
});
|
||||
|
||||
test('should set and get a value', () => {
|
||||
mapResource.set('key1', { id: 'key1', value: 1 });
|
||||
expect(mapResource.get('key1')).toStrictEqual({ id: 'key1', value: 1 });
|
||||
});
|
||||
|
||||
test('should delete a value', () => {
|
||||
mapResource.set('key1', { id: 'key1', value: 1 });
|
||||
mapResource.delete('key1');
|
||||
expect(mapResource.get('key1')).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should check if a key exists', () => {
|
||||
mapResource.set('key1', { id: 'key1', value: 1 });
|
||||
expect(mapResource.has('key1')).toBe(true);
|
||||
expect(mapResource.has('key2')).toBe(false);
|
||||
});
|
||||
|
||||
test('should replace multiple keys', () => {
|
||||
mapResource.set('key1', { id: 'key1', value: 1 });
|
||||
mapResource.set('key2', { id: 'key2', value: 2 });
|
||||
mapResource.set('key4', { id: 'key4', value: 4 });
|
||||
|
||||
mapResource.replace(resourceKeyList(['key1', 'key3']), [
|
||||
{ id: 'key1', value: 11 },
|
||||
{ id: 'key3', value: 33 },
|
||||
]);
|
||||
|
||||
expect(mapResource.get('key1')).toStrictEqual({ id: 'key1', value: 11 });
|
||||
expect(mapResource.get('key2')).toBeUndefined();
|
||||
expect(mapResource.get('key3')).toStrictEqual({ id: 'key3', value: 33 });
|
||||
expect(mapResource.data.size).toBe(2);
|
||||
});
|
||||
|
||||
test('should outdated certain keys', () => {
|
||||
mapResource.set('key1', { id: 'key1', value: 1 });
|
||||
mapResource.set('key2', { id: 'key2', value: 2 });
|
||||
|
||||
mapResource.markOutdated('key1');
|
||||
|
||||
expect(mapResource.isOutdated('key1')).toBe(true);
|
||||
expect(mapResource.isOutdated('key2')).toBe(false);
|
||||
});
|
||||
|
||||
test('should run onDataOutdated handlers on data outdate', () => {
|
||||
const handler = jest.fn();
|
||||
|
||||
mapResource.set('key1', { id: 'key1', value: 1 });
|
||||
mapResource.set('key2', { id: 'key2', value: 2 });
|
||||
|
||||
mapResource.onDataOutdated.addHandler(key => {
|
||||
handler();
|
||||
expect(key).toBe('key1');
|
||||
});
|
||||
|
||||
mapResource.markOutdated('key1');
|
||||
expect(handler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should run onDataUpdate handlers on data update', () => {
|
||||
const handler = jest.fn();
|
||||
|
||||
mapResource.set('key1', { id: 'key1', value: 1 });
|
||||
mapResource.set('key2', { id: 'key2', value: 2 });
|
||||
|
||||
mapResource.onDataUpdate.addHandler(key => {
|
||||
handler();
|
||||
expect(key).toBe('key2');
|
||||
});
|
||||
|
||||
mapResource.dataUpdate('key2');
|
||||
expect(handler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should run onItemDelete handlers on data delete', () => {
|
||||
const handler = jest.fn();
|
||||
|
||||
mapResource.set('key1', { id: 'key1', value: 1 });
|
||||
mapResource.set('key2', { id: 'key2', value: 2 });
|
||||
|
||||
mapResource.onItemDelete.addHandler(key => {
|
||||
handler();
|
||||
expect(key).toBe('key1');
|
||||
});
|
||||
|
||||
mapResource.delete('key1');
|
||||
expect(handler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should run onItemUpdate handlers on item update', () => {
|
||||
const handler = jest.fn();
|
||||
|
||||
mapResource.set('key1', { id: 'key1', value: 1 });
|
||||
mapResource.set('key2', { id: 'key2', value: 2 });
|
||||
|
||||
mapResource.onItemUpdate.addHandler(key => {
|
||||
handler();
|
||||
expect(key).toBe('key2');
|
||||
});
|
||||
|
||||
mapResource.set('key2', { id: 'key2', value: 22 });
|
||||
expect(handler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should run onDataError handlers on data error', async () => {
|
||||
const handler = jest.fn();
|
||||
|
||||
mapResource.set('key1', { id: 'key1', value: 1 });
|
||||
mapResource.set('key2', { id: 'key2', value: 2 });
|
||||
|
||||
mapResource.onDataError.addHandler(data => {
|
||||
handler();
|
||||
expect(data.param).toBe(ERROR_ITEM_ID);
|
||||
expect(data.exception.message).toBe(TEST_ERROR_MESSAGE);
|
||||
});
|
||||
|
||||
await expect(mapResource.load(ERROR_ITEM_ID)).rejects.toThrow(TEST_ERROR_MESSAGE);
|
||||
expect(handler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should be able to get an exception if the one occurred for the key', async () => {
|
||||
await expect(mapResource.load(ERROR_ITEM_ID)).rejects.toThrow(TEST_ERROR_MESSAGE);
|
||||
expect(mapResource.getException(ERROR_ITEM_ID)?.message).toBe(TEST_ERROR_MESSAGE);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { CachedTreeChildrenKey, CachedTreeResource, CachedTreeRootChildrenKey } from './CachedTreeResource';
|
||||
import type { ResourceKey } from './ResourceKey';
|
||||
import { resourceKeyList } from './ResourceKeyList';
|
||||
|
||||
interface IMockDataEntity {
|
||||
name: string;
|
||||
}
|
||||
|
||||
class TestTreeResource extends CachedTreeResource<IMockDataEntity> {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async loader(key: ResourceKey<string>) {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
protected validateKey(key: string): boolean {
|
||||
return typeof key === 'string';
|
||||
}
|
||||
}
|
||||
|
||||
describe('CachedMapResource', () => {
|
||||
let treeResource: TestTreeResource;
|
||||
|
||||
beforeEach(() => {
|
||||
treeResource = new TestTreeResource();
|
||||
});
|
||||
|
||||
test('should instantiate correctly', () => {
|
||||
expect(treeResource).toBeInstanceOf(CachedTreeResource);
|
||||
});
|
||||
|
||||
test('should set data correctly', () => {
|
||||
treeResource.set('root', { name: 'root' });
|
||||
expect(treeResource.get('root')).toEqual({ name: 'root' });
|
||||
});
|
||||
|
||||
test('.has should check if node exists correctly', () => {
|
||||
treeResource.set('root', { name: 'root' });
|
||||
treeResource.set('root/level2', { name: 'level2' });
|
||||
|
||||
expect(treeResource.has('root/level2')).toBe(true);
|
||||
expect(treeResource.has('root/level3')).toBe(false);
|
||||
});
|
||||
|
||||
test('the node should be outdated after markOutdated is called on it', () => {
|
||||
treeResource.set('root', { name: 'root' });
|
||||
treeResource.set('root/level2', { name: 'level2' });
|
||||
treeResource.markOutdated('root');
|
||||
|
||||
expect(treeResource.isOutdated('root')).toBe(true);
|
||||
expect(treeResource.isOutdated('root/level2')).toBe(false);
|
||||
});
|
||||
|
||||
test('the nodes metadata should be outdated after markOutdated is called on it', () => {
|
||||
treeResource.set('root', { name: 'root' });
|
||||
treeResource.set('root/level2', { name: 'level2' });
|
||||
treeResource.markOutdated('root');
|
||||
|
||||
expect(treeResource.getMetadata('root').outdated).toBe(true);
|
||||
expect(treeResource.getMetadata('root/level2').outdated).toBe(false);
|
||||
});
|
||||
|
||||
test('should run onDataOutdated handlers on data outdate', () => {
|
||||
const handler = jest.fn();
|
||||
treeResource.set('root', { name: 'root' });
|
||||
treeResource.set('root/level2', { name: 'level2' });
|
||||
|
||||
treeResource.onDataOutdated.addHandler(key => {
|
||||
handler();
|
||||
expect(key).toBe('root');
|
||||
});
|
||||
|
||||
treeResource.markOutdated('root');
|
||||
|
||||
expect(treeResource.isOutdated('root')).toBe(true);
|
||||
expect(handler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('CachedTreeChildrenKey alias should return key children of the node', () => {
|
||||
const handler = jest.fn();
|
||||
|
||||
treeResource.set('root', { name: 'root' });
|
||||
treeResource.set('root/level2', { name: 'level2' });
|
||||
treeResource.set('root/level2/level3', { name: 'level3' });
|
||||
|
||||
treeResource.onDataOutdated.addHandler(key => {
|
||||
handler();
|
||||
expect(key).toEqual(resourceKeyList(['root/level2']));
|
||||
});
|
||||
|
||||
treeResource.markOutdated(CachedTreeChildrenKey('root'));
|
||||
|
||||
expect(handler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('CachedTreeRootChildrenKey alias should return children for the root node', () => {
|
||||
treeResource.set('root', { name: 'root' });
|
||||
treeResource.set('root/level2', { name: 'level2' });
|
||||
treeResource.set('root/level2/level3', { name: 'level3' });
|
||||
|
||||
expect(treeResource.get(CachedTreeRootChildrenKey)).toEqual([{ name: 'root' }]);
|
||||
});
|
||||
|
||||
test('should be able to delete the node', () => {
|
||||
treeResource.set('root', { name: 'root' });
|
||||
treeResource.set('root/level2', { name: 'level2' });
|
||||
|
||||
treeResource.delete('root/level2');
|
||||
|
||||
expect(treeResource.has('root/level2')).toBe(false);
|
||||
expect(treeResource.has('root')).toBe(true);
|
||||
});
|
||||
|
||||
test('should run onItemDelete handlers on data delete', () => {
|
||||
const handler = jest.fn();
|
||||
|
||||
treeResource.set('root', { name: 'root' });
|
||||
treeResource.set('root/level2', { name: 'level2' });
|
||||
|
||||
treeResource.onItemDelete.addHandler(key => {
|
||||
handler();
|
||||
expect(key).toBe('root/level2');
|
||||
});
|
||||
|
||||
treeResource.delete('root/level2');
|
||||
|
||||
expect(handler).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user