From e65c7248e793a5594d1f772add07c1833dbe5ef4 Mon Sep 17 00:00:00 2001 From: naumov Date: Fri, 22 Sep 2023 13:56:18 +0200 Subject: [PATCH 1/9] CB-3898 add base CachedMapResource tests --- webapp/packages/core-sdk/package.json | 1 + .../src/Resource/CachedMapResource.test.ts | 128 ++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts diff --git a/webapp/packages/core-sdk/package.json b/webapp/packages/core-sdk/package.json index a9bcb942db..657f5471e2 100644 --- a/webapp/packages/core-sdk/package.json +++ b/webapp/packages/core-sdk/package.json @@ -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": { diff --git a/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts new file mode 100644 index 0000000000..16ed1bd82f --- /dev/null +++ b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts @@ -0,0 +1,128 @@ +/* + * 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 { CachedMapResource } from './CachedMapResource'; +import type { ResourceKey } from './ResourceKey'; +import { resourceKeyList } from './ResourceKeyList'; + +interface ILoaderData { + id: string; + value: number; +} + +const LOADER_DATA_MOCK: ILoaderData[] = [ + { + id: '1', + value: 1, + }, + { + id: '2', + value: 2, + }, +]; + +class TestMapResource extends CachedMapResource { + constructor() { + super(); + } + + private async fetchFromAPI(key: ResourceKey | undefined): Promise { + return new Promise(resolve => { + setTimeout(() => { + if (key) { + const data = LOADER_DATA_MOCK.find(d => d.id === key); + if (data) { + resolve([data]); + } + } else { + resolve(LOADER_DATA_MOCK); + } + }, 1); + }); + } + + protected async loader(key: ResourceKey) { + const data = await this.fetchFromAPI(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 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 initialize with an empty map', () => { + expect(mapResource.entries).toEqual([]); + }); + + 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 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.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 }); + }); +}); From ffda916565c8b3b7bbdf9755e9a217c07e853127 Mon Sep 17 00:00:00 2001 From: naumov Date: Fri, 22 Sep 2023 14:38:24 +0200 Subject: [PATCH 2/9] CB-3908 add tests for the handlers --- .../src/Resource/CachedMapResource.test.ts | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts index 16ed1bd82f..e8574f2890 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts @@ -14,6 +14,8 @@ interface ILoaderData { value: number; } +const ERROR_ITEM_ID = 'error'; + const LOADER_DATA_MOCK: ILoaderData[] = [ { id: '1', @@ -23,13 +25,27 @@ const LOADER_DATA_MOCK: ILoaderData[] = [ id: '2', value: 2, }, + { + id: ERROR_ITEM_ID, + value: 3, + }, ]; +const TEST_ERROR_MESSAGE = 'Test error'; + class TestMapResource extends CachedMapResource { constructor() { super(); } + async fetchAnError() { + return new Promise((_, reject) => { + setTimeout(() => { + reject(new Error(TEST_ERROR_MESSAGE)); + }, 1); + }); + } + private async fetchFromAPI(key: ResourceKey | undefined): Promise { return new Promise(resolve => { setTimeout(() => { @@ -46,6 +62,10 @@ class TestMapResource extends CachedMapResource { } protected async loader(key: ResourceKey) { + if (key === ERROR_ITEM_ID) { + await this.fetchAnError(); + } + const data = await this.fetchFromAPI(key); this.replace(resourceKeyList(data.map(d => d.id)), data); return this.data; @@ -97,6 +117,16 @@ describe('CachedMapResource', () => { 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 change loaded state when data is 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 }); @@ -125,4 +155,65 @@ describe('CachedMapResource', () => { expect(mapResource.get('key2')).toBeUndefined(); expect(mapResource.get('key3')).toStrictEqual({ id: 'key3', value: 33 }); }); + + 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', () => { + mapResource.set('key1', { id: 'key1', value: 1 }); + mapResource.set('key2', { id: 'key2', value: 2 }); + + mapResource.onDataOutdated.addHandler(key => { + expect(key).toBe('key1'); + }); + + mapResource.markOutdated('key1'); + }); + + test('should run onDataUpdate handlers on data update', () => { + mapResource.set('key1', { id: 'key1', value: 1 }); + mapResource.set('key2', { id: 'key2', value: 2 }); + + mapResource.onDataUpdate.addHandler(key => { + expect(key).toBe('key2'); + }); + + mapResource.dataUpdate('key2'); + }); + + test('should run onItemDelete handlers on data delete', () => { + mapResource.set('key1', { id: 'key1', value: 1 }); + mapResource.set('key2', { id: 'key2', value: 2 }); + + mapResource.onItemDelete.addHandler(key => { + expect(key).toBe('key1'); + }); + + mapResource.delete('key1'); + }); + + test('should run onItemDelete handlers on data delete', () => { + mapResource.set('key1', { id: 'key1', value: 1 }); + mapResource.set('key2', { id: 'key2', value: 2 }); + + mapResource.onItemUpdate.addHandler(key => { + expect(key).toBe('key2'); + }); + + mapResource.set('key2', { id: 'key2', value: 22 }); + }); + + test('should run onItemDelete handlers on data delete', () => { + mapResource.set('key1', { id: 'key1', value: 1 }); + mapResource.set('key2', { id: 'key2', value: 2 }); + + mapResource.set('key2', { id: 'key2', value: 22 }); + }); }); From e041754964394a081d5710e15647b963c61df4ce Mon Sep 17 00:00:00 2001 From: naumov Date: Fri, 22 Sep 2023 14:51:52 +0200 Subject: [PATCH 3/9] CB-3908 add error tests --- .../src/Resource/CachedMapResource.test.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts index e8574f2890..2e8cf1e696 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts @@ -199,7 +199,7 @@ describe('CachedMapResource', () => { mapResource.delete('key1'); }); - test('should run onItemDelete handlers on data delete', () => { + test('should run onItemUpdate handlers on data delete', () => { mapResource.set('key1', { id: 'key1', value: 1 }); mapResource.set('key2', { id: 'key2', value: 2 }); @@ -210,10 +210,20 @@ describe('CachedMapResource', () => { mapResource.set('key2', { id: 'key2', value: 22 }); }); - test('should run onItemDelete handlers on data delete', () => { + test('should run onDataError handlers on data error', async () => { mapResource.set('key1', { id: 'key1', value: 1 }); mapResource.set('key2', { id: 'key2', value: 2 }); - mapResource.set('key2', { id: 'key2', value: 22 }); + mapResource.onDataError.addHandler(data => { + 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); + }); + + 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); }); }); From 911c5b6d6918bbbb2ce4019b46e57cd3d7dc0512 Mon Sep 17 00:00:00 2001 From: naumov Date: Fri, 22 Sep 2023 15:36:43 +0200 Subject: [PATCH 4/9] CB-3908 add initial tests for the CachedDataResource --- .../src/Resource/CachedDataResource.test.ts | 92 +++++++++++++++++++ .../src/Resource/CachedMapResource.test.ts | 20 ++++ 2 files changed, 112 insertions(+) create mode 100644 webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts diff --git a/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts new file mode 100644 index 0000000000..022d8e6bdf --- /dev/null +++ b/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts @@ -0,0 +1,92 @@ +/* + * 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 ILoaderData { + id: string; + value: number; +} + +const LOADER_DATA_MOCK: ILoaderData[] = [ + { + id: '1', + value: 1, + }, + { + id: '2', + value: 2, + }, +]; + +class TestDataResource extends CachedDataResource { + constructor() { + super(() => []); + } + + private async fetchFromAPI(): Promise { + return new Promise(resolve => { + setTimeout(() => { + resolve(LOADER_DATA_MOCK); + }, 1); + }); + } + + protected async loader() { + const data = await this.fetchFromAPI(); + return data; + } +} + +describe('CachedDataResource', () => { + let dataResource: TestDataResource; + + beforeEach(() => { + dataResource = new TestDataResource(); + }); + + test('should initialize with an empty array', () => { + expect(dataResource.data).toEqual([]); + }); + + test('should load data', async () => { + await dataResource.load(); + expect(dataResource.data).toEqual(LOADER_DATA_MOCK); + }); + + 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 run onDataUpdate handlers on data update', () => { + const handler = jest.fn(); + + dataResource.onDataUpdate.addHandler(handler); + dataResource.dataUpdate(); + + expect(handler).toHaveBeenCalled(); + }); +}); diff --git a/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts index 2e8cf1e696..ac030bac00 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts @@ -167,59 +167,79 @@ describe('CachedMapResource', () => { }); 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 data delete', () => { + 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 () => { From 5e7656f607e9303af39c110f1d01a3bad5d18b86 Mon Sep 17 00:00:00 2001 From: naumov Date: Fri, 22 Sep 2023 15:37:37 +0200 Subject: [PATCH 5/9] CB-3908 remove extra test --- .../core-sdk/src/Resource/CachedDataResource.test.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts index 022d8e6bdf..41e0e7a678 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts @@ -80,13 +80,4 @@ describe('CachedDataResource', () => { expect(handler).toHaveBeenCalled(); }); - - test('should run onDataUpdate handlers on data update', () => { - const handler = jest.fn(); - - dataResource.onDataUpdate.addHandler(handler); - dataResource.dataUpdate(); - - expect(handler).toHaveBeenCalled(); - }); }); From 3449de799a69098f4f4fb55d5946337da02356b4 Mon Sep 17 00:00:00 2001 From: naumov Date: Fri, 22 Sep 2023 15:53:17 +0200 Subject: [PATCH 6/9] CB-3908 add clear data test --- .../core-sdk/src/Resource/CachedDataResource.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts index 41e0e7a678..d39497900e 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts @@ -80,4 +80,11 @@ describe('CachedDataResource', () => { expect(handler).toHaveBeenCalled(); }); + + test('should be able to clear the data', async () => { + await dataResource.load(); + dataResource.clear(); + + expect(dataResource.data).toEqual([]); + }); }); From 7850ecdeb60e4fe13e43ca0103da18d2afdd708d Mon Sep 17 00:00:00 2001 From: naumov Date: Sun, 24 Sep 2023 23:02:26 +0200 Subject: [PATCH 7/9] CB-3908 add CachedTreeResource tests --- .../src/Resource/CachedDataResource.test.ts | 4 + .../src/Resource/CachedMapResource.test.ts | 4 + .../src/Resource/CachedTreeResource.test.ts | 138 ++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 webapp/packages/core-sdk/src/Resource/CachedTreeResource.test.ts diff --git a/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts index d39497900e..790ed32b90 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts @@ -49,6 +49,10 @@ describe('CachedDataResource', () => { dataResource = new TestDataResource(); }); + test('should instantiate correctly', () => { + expect(dataResource).toBeInstanceOf(CachedDataResource); + }); + test('should initialize with an empty array', () => { expect(dataResource.data).toEqual([]); }); diff --git a/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts index ac030bac00..8b44966910 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts @@ -83,6 +83,10 @@ describe('CachedMapResource', () => { mapResource = new TestMapResource(); }); + test('should instantiate correctly', () => { + expect(mapResource).toBeInstanceOf(CachedMapResource); + }); + test('should return all entries', () => { mapResource.set('key1', { id: 'key1', value: 1 }); mapResource.set('key2', { id: 'key2', value: 2 }); diff --git a/webapp/packages/core-sdk/src/Resource/CachedTreeResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedTreeResource.test.ts new file mode 100644 index 0000000000..fa54970887 --- /dev/null +++ b/webapp/packages/core-sdk/src/Resource/CachedTreeResource.test.ts @@ -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 { + constructor() { + super(); + } + + protected async loader(key: ResourceKey) { + 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(); + }); +}); From 8f2d1c0044fec651c0fbc61da8c3dca1b008187c Mon Sep 17 00:00:00 2001 From: naumov Date: Tue, 26 Sep 2023 14:28:35 +0200 Subject: [PATCH 8/9] CB-3908 review fixes --- .../src/Resource/CachedDataResource.test.ts | 26 ++++--- .../src/Resource/CachedMapResource.test.ts | 75 +++++++++---------- 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts index 790ed32b90..7022435f9c 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts @@ -12,6 +12,8 @@ interface ILoaderData { value: number; } +const DEFAULT_STATE: () => ILoaderData[] = () => []; + const LOADER_DATA_MOCK: ILoaderData[] = [ { id: '1', @@ -23,21 +25,21 @@ const LOADER_DATA_MOCK: ILoaderData[] = [ }, ]; +async function fetchMock(): Promise { + return new Promise(resolve => { + setTimeout(() => { + resolve(LOADER_DATA_MOCK); + }, 1); + }); +} + class TestDataResource extends CachedDataResource { constructor() { - super(() => []); - } - - private async fetchFromAPI(): Promise { - return new Promise(resolve => { - setTimeout(() => { - resolve(LOADER_DATA_MOCK); - }, 1); - }); + super(DEFAULT_STATE); } protected async loader() { - const data = await this.fetchFromAPI(); + const data = await fetchMock(); return data; } } @@ -53,8 +55,8 @@ describe('CachedDataResource', () => { expect(dataResource).toBeInstanceOf(CachedDataResource); }); - test('should initialize with an empty array', () => { - expect(dataResource.data).toEqual([]); + test('should initialize with the default state', () => { + expect(dataResource.data).toEqual(DEFAULT_STATE()); }); test('should load data', async () => { diff --git a/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts index 8b44966910..abb89f7d66 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts @@ -5,18 +5,20 @@ * 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 ILoaderData { +interface IEntityData { id: string; value: number; } const ERROR_ITEM_ID = 'error'; -const LOADER_DATA_MOCK: ILoaderData[] = [ +const DATA_MOCK: IEntityData[] = [ { id: '1', value: 1, @@ -32,41 +34,34 @@ const LOADER_DATA_MOCK: ILoaderData[] = [ ]; const TEST_ERROR_MESSAGE = 'Test error'; +const DEFAULT_STATE = () => new Map(); + +async function fetchMock(key: ResourceKey | undefined): Promise { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (key) { + if (key === ERROR_ITEM_ID) { + reject(new Error(TEST_ERROR_MESSAGE)); + } -class TestMapResource extends CachedMapResource { - constructor() { - super(); - } - - async fetchAnError() { - return new Promise((_, reject) => { - setTimeout(() => { - reject(new Error(TEST_ERROR_MESSAGE)); - }, 1); - }); - } - - private async fetchFromAPI(key: ResourceKey | undefined): Promise { - return new Promise(resolve => { - setTimeout(() => { - if (key) { - const data = LOADER_DATA_MOCK.find(d => d.id === key); - if (data) { - resolve([data]); - } - } else { - resolve(LOADER_DATA_MOCK); + const data = DATA_MOCK.find(d => d.id === key); + if (data) { + resolve([data]); } - }, 1); - }); + } else { + resolve(DATA_MOCK); + } + }, 1); + }); +} + +class TestMapResource extends CachedMapResource { + constructor() { + super(DEFAULT_STATE); } protected async loader(key: ResourceKey) { - if (key === ERROR_ITEM_ID) { - await this.fetchAnError(); - } - - const data = await this.fetchFromAPI(key); + const data = await fetchMock(key); this.replace(resourceKeyList(data.map(d => d.id)), data); return this.data; } @@ -87,6 +82,10 @@ describe('CachedMapResource', () => { expect(mapResource).toBeInstanceOf(CachedMapResource); }); + test('should initialize with the initial value', () => { + expect(toJS(mapResource.data)).toEqual(DEFAULT_STATE()); + }); + test('should return all entries', () => { mapResource.set('key1', { id: 'key1', value: 1 }); mapResource.set('key2', { id: 'key2', value: 2 }); @@ -111,10 +110,6 @@ describe('CachedMapResource', () => { expect(mapResource.keys).toEqual(['key1', 'key2']); }); - test('should initialize with an empty map', () => { - expect(mapResource.entries).toEqual([]); - }); - test('should load data for a specific key', async () => { await mapResource.load('1'); expect(mapResource.get('1')).toEqual({ id: '1', value: 1 }); @@ -126,7 +121,7 @@ describe('CachedMapResource', () => { expect(mapResource.get(ERROR_ITEM_ID)).toBeUndefined(); }); - test('should change loaded state when data is loaded ', async () => { + test('should mark loaded data as loaded', async () => { await mapResource.load('1'); expect(mapResource.isLoaded('1')).toBe(true); }); @@ -151,13 +146,17 @@ describe('CachedMapResource', () => { 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', () => { @@ -215,7 +214,7 @@ describe('CachedMapResource', () => { expect(handler).toHaveBeenCalled(); }); - test('should run onItemUpdate handlers on data delete', () => { + test('should run onItemUpdate handlers on item update', () => { const handler = jest.fn(); mapResource.set('key1', { id: 'key1', value: 1 }); From b69600093aa8c453a4e1b48acb3324febfd474b0 Mon Sep 17 00:00:00 2001 From: naumov Date: Tue, 26 Sep 2023 16:26:39 +0200 Subject: [PATCH 9/9] CB-3908 transform mock data to getters --- .../src/Resource/CachedDataResource.test.ts | 19 +++++++++---------- .../src/Resource/CachedMapResource.test.ts | 18 ++++++++++-------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts index 7022435f9c..090cc5c880 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedDataResource.test.ts @@ -7,14 +7,13 @@ */ import { CachedDataResource } from './CachedDataResource'; -interface ILoaderData { +interface IEntityData { id: string; value: number; } -const DEFAULT_STATE: () => ILoaderData[] = () => []; - -const LOADER_DATA_MOCK: ILoaderData[] = [ +const DEFAULT_STATE_GETTER: () => IEntityData[] = () => []; +const DATA_MOCK_GETTER: () => IEntityData[] = () => [ { id: '1', value: 1, @@ -25,17 +24,17 @@ const LOADER_DATA_MOCK: ILoaderData[] = [ }, ]; -async function fetchMock(): Promise { +async function fetchMock(): Promise { return new Promise(resolve => { setTimeout(() => { - resolve(LOADER_DATA_MOCK); + resolve(DATA_MOCK_GETTER()); }, 1); }); } -class TestDataResource extends CachedDataResource { +class TestDataResource extends CachedDataResource { constructor() { - super(DEFAULT_STATE); + super(DEFAULT_STATE_GETTER); } protected async loader() { @@ -56,12 +55,12 @@ describe('CachedDataResource', () => { }); test('should initialize with the default state', () => { - expect(dataResource.data).toEqual(DEFAULT_STATE()); + expect(dataResource.data).toEqual(DEFAULT_STATE_GETTER()); }); test('should load data', async () => { await dataResource.load(); - expect(dataResource.data).toEqual(LOADER_DATA_MOCK); + expect(dataResource.data).toEqual(DATA_MOCK_GETTER()); }); test('should be able to outdate the data', () => { diff --git a/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts index abb89f7d66..7981a80274 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedMapResource.test.ts @@ -18,7 +18,7 @@ interface IEntityData { const ERROR_ITEM_ID = 'error'; -const DATA_MOCK: IEntityData[] = [ +const DATA_MOCK_GETTER: () => IEntityData[] = () => [ { id: '1', value: 1, @@ -34,9 +34,11 @@ const DATA_MOCK: IEntityData[] = [ ]; const TEST_ERROR_MESSAGE = 'Test error'; -const DEFAULT_STATE = () => new Map(); +const DEFAULT_STATE_GETTER = () => new Map(); async function fetchMock(key: ResourceKey | undefined): Promise { + const data = DATA_MOCK_GETTER(); + return new Promise((resolve, reject) => { setTimeout(() => { if (key) { @@ -44,12 +46,12 @@ async function fetchMock(key: ResourceKey | undefined): Promise d.id === key); - if (data) { - resolve([data]); + const item = data.find(d => d.id === key); + if (item) { + resolve([item]); } } else { - resolve(DATA_MOCK); + resolve(data); } }, 1); }); @@ -57,7 +59,7 @@ async function fetchMock(key: ResourceKey | undefined): Promise { constructor() { - super(DEFAULT_STATE); + super(DEFAULT_STATE_GETTER); } protected async loader(key: ResourceKey) { @@ -83,7 +85,7 @@ describe('CachedMapResource', () => { }); test('should initialize with the initial value', () => { - expect(toJS(mapResource.data)).toEqual(DEFAULT_STATE()); + expect(toJS(mapResource.data)).toEqual(DEFAULT_STATE_GETTER()); }); test('should return all entries', () => {