From 115cfb3bdec5bb8f15ccadde08155e4034b3d69d Mon Sep 17 00:00:00 2001 From: alex <48489896+devnaumov@users.noreply.github.com> Date: Fri, 18 Oct 2024 07:33:39 +0200 Subject: [PATCH] Cb 5753 u tests (#2964) * CB-5753 add localization service test, fix grammar * CB-5753 add test for the getDomainFromUrl * CB-5753 add test for the client activity service * CB-5753 add NodeManagerUtils tests * CB-5753 add more cases * CB-5753 pass args correctly --- .../core-client-activity/package.json | 3 +- .../src/ClientActivityService.test.ts | 78 ++++++++++++ .../src/ClientActivityService.ts | 2 +- .../src/LocalizationService.test.ts | 118 ++++++++++++++++++ .../src/LocalizationService.ts | 3 +- .../src/NodesManager/NodeManagerUtils.test.ts | 63 ++++++++++ .../core-utils/src/getDomainFromUrl.test.ts | 66 ++++++++++ .../core-utils/src/getDomainFromUrl.ts | 1 - 8 files changed, 330 insertions(+), 4 deletions(-) create mode 100644 webapp/packages/core-client-activity/src/ClientActivityService.test.ts create mode 100644 webapp/packages/core-localization/src/LocalizationService.test.ts create mode 100644 webapp/packages/core-navigation-tree/src/NodesManager/NodeManagerUtils.test.ts create mode 100644 webapp/packages/core-utils/src/getDomainFromUrl.test.ts diff --git a/webapp/packages/core-client-activity/package.json b/webapp/packages/core-client-activity/package.json index 53f143f78e..dc866fd87f 100644 --- a/webapp/packages/core-client-activity/package.json +++ b/webapp/packages/core-client-activity/package.json @@ -15,7 +15,8 @@ "clean": "rimraf --glob dist", "lint": "eslint ./src/ --ext .ts,.tsx", "validate-dependencies": "core-cli-validate-dependencies", - "update-ts-references": "yarn run clean && typescript-resolve-references" + "update-ts-references": "yarn run clean && typescript-resolve-references", + "test": "core-cli-test" }, "dependencies": { "@cloudbeaver/core-di": "^0", diff --git a/webapp/packages/core-client-activity/src/ClientActivityService.test.ts b/webapp/packages/core-client-activity/src/ClientActivityService.test.ts new file mode 100644 index 0000000000..6860fafd66 --- /dev/null +++ b/webapp/packages/core-client-activity/src/ClientActivityService.test.ts @@ -0,0 +1,78 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 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 { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals'; + +import { ClientActivityService, INACTIVE_PERIOD_TIME } from './ClientActivityService.js'; + +jest.useFakeTimers(); + +describe('ClientActivityService', () => { + let clientActivityService: ClientActivityService; + + beforeEach(() => { + clientActivityService = new ClientActivityService(); + + jest.spyOn(global, 'setTimeout'); + jest.spyOn(global, 'clearTimeout'); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.restoreAllMocks(); + }); + + it('should initialize with isActive set to false', () => { + expect(clientActivityService.isActive).toBe(false); + }); + + it('should set isActive to true when updateActivity is called', () => { + clientActivityService.updateActivity(); + expect(clientActivityService.isActive).toBe(true); + }); + + it('should reset activity after the timeout period', () => { + clientActivityService.updateActivity(); + expect(clientActivityService.isActive).toBe(true); + + jest.advanceTimersByTime(INACTIVE_PERIOD_TIME); + + expect(clientActivityService.isActive).toBe(false); + }); + + it('should clear previous timer if updateActivity is called multiple times', () => { + clientActivityService.updateActivity(); + expect(setTimeout).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(Math.random() * INACTIVE_PERIOD_TIME - 1); + + clientActivityService.updateActivity(); + expect(clearTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenCalledTimes(2); + }); + + it('should clear timer and reset activity when resetActivity is called', () => { + clientActivityService.updateActivity(); + + jest.advanceTimersByTime(Math.random() * INACTIVE_PERIOD_TIME - 1); + + clientActivityService.resetActivity(); + + expect(clientActivityService.isActive).toBe(false); + expect(clearTimeout).toHaveBeenCalled(); + }); + + it('should call onActiveStateChange executor with correct value', () => { + const onActiveStateChangeSpy = jest.spyOn(clientActivityService.onActiveStateChange, 'execute'); + + clientActivityService.updateActivity(); + expect(onActiveStateChangeSpy).toHaveBeenCalledWith(true); + + jest.advanceTimersByTime(INACTIVE_PERIOD_TIME); + expect(onActiveStateChangeSpy).toHaveBeenCalledWith(false); + }); +}); diff --git a/webapp/packages/core-client-activity/src/ClientActivityService.ts b/webapp/packages/core-client-activity/src/ClientActivityService.ts index 01e01aec03..03dda325d5 100644 --- a/webapp/packages/core-client-activity/src/ClientActivityService.ts +++ b/webapp/packages/core-client-activity/src/ClientActivityService.ts @@ -10,7 +10,7 @@ import { makeObservable, observable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; import { Executor, type IExecutor } from '@cloudbeaver/core-executor'; -const INACTIVE_PERIOD_TIME = 1000 * 60; +export const INACTIVE_PERIOD_TIME = 1000 * 60; @injectable() export class ClientActivityService { diff --git a/webapp/packages/core-localization/src/LocalizationService.test.ts b/webapp/packages/core-localization/src/LocalizationService.test.ts new file mode 100644 index 0000000000..11df333449 --- /dev/null +++ b/webapp/packages/core-localization/src/LocalizationService.test.ts @@ -0,0 +1,118 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 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 { beforeEach, describe, expect, it } from '@jest/globals'; + +import { DEFAULT_LOCALE } from './DEFAULT_LOCALE.js'; +import { type ILocale } from './ILocale.js'; +import { LocalizationService } from './LocalizationService.js'; + +describe('LocalizationService', () => { + let service: LocalizationService; + + beforeEach(() => { + service = new LocalizationService(); + }); + + it('should initialize with default locale', () => { + service.register(); + expect(service.currentLanguage).toBe(DEFAULT_LOCALE.isoCode); + expect(service.supportedLanguages.length).toBeGreaterThan(0); + }); + + it('should set supported languages', () => { + const locales: ILocale[] = [ + { isoCode: 'de', name: 'German', nativeName: 'Deutsch' }, + { isoCode: 'es', name: 'Spanish', nativeName: 'Español' }, + ]; + + service.setSupportedLanguages(locales); + expect(service.supportedLanguages.length).toBe(2); + }); + + it('should set default to first supported language', () => { + const locales: ILocale[] = [ + { isoCode: 'de', name: 'German', nativeName: 'Deutsch' }, + { isoCode: 'es', name: 'Spanish', nativeName: 'Español' }, + ]; + + service.setSupportedLanguages(locales); + expect(service.currentLanguage).toBe('de'); + }); + + it('should return default locale if the current language is not supported', () => { + service.setLanguage('es'); + expect(service.currentLanguage).toBe(DEFAULT_LOCALE.isoCode); + }); + + it('should throw error if there are no supported languages', () => { + service.supportedLanguages = []; + expect(() => service.currentLanguage).toThrowError('No language is available'); + }); + + it('should change the current language', async () => { + service.register(); + await service.changeLocale('ru'); + expect(service.currentLanguage).toBe('ru'); + }); + + it('should throw an error when changing to an unsupported language', async () => { + await expect(service.changeLocale('jp')).rejects.toThrowError("Language 'jp' is not supported"); + }); + + it('should translate a token with a fallback', () => { + const token = 'greeting'; + const fallback = 'Hello, World!'; + + service.changeLocale('en'); + service['localeMap'].set('en', new Map([[token, 'Hello']])); + + const translation = service.translate(token, fallback); + expect(translation).toBe('Hello'); + }); + + it('should return fallback when translation is missing', () => { + const token = 'nonexistent_token'; + const fallback = 'Fallback'; + + const translation = service.translate(token, fallback); + expect(translation).toBe(fallback); + }); + + it('should replace args in the translation string', () => { + const token = 'welcome_message'; + const translationString = 'Welcome, {arg:name}!'; + service['localeMap'].set('en', new Map([[token, translationString]])); + service.setLanguage('en'); + + const translation = service.translate(token, undefined, { name: 'John' }); + expect(translation).toBe('Welcome, John!'); + }); + + it('should replace multiple args in the translation string', () => { + const token = 'greeting_message'; + const translationString = 'Hello, {arg:firstName} {arg:lastName}!'; + service['localeMap'].set('en', new Map([[token, translationString]])); + service.setLanguage('en'); + + const translation = service.translate(token, undefined, { firstName: 'John', lastName: 'Doe' }); + expect(translation).toBe('Hello, John Doe!'); + }); + + it('should return true for supported language', () => { + service.register(); + + expect(service.isLanguageSupported('en')).toBe(true); + expect(service.isLanguageSupported('ru')).toBe(true); + }); + + it('should return false for unsupported language', () => { + service.register(); + + expect(service.isLanguageSupported('n/a')).toBe(false); + }); +}); diff --git a/webapp/packages/core-localization/src/LocalizationService.ts b/webapp/packages/core-localization/src/LocalizationService.ts index 196d27d3f9..d35ede094e 100644 --- a/webapp/packages/core-localization/src/LocalizationService.ts +++ b/webapp/packages/core-localization/src/LocalizationService.ts @@ -31,7 +31,8 @@ export class LocalizationService extends Bootstrap { const firstLanguage = this.supportedLanguages[0]; if (!firstLanguage) { - throw new Error('No language is awailable'); + //@TODO do not throw error in getter + throw new Error('No language is available'); } return firstLanguage.isoCode; diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/NodeManagerUtils.test.ts b/webapp/packages/core-navigation-tree/src/NodesManager/NodeManagerUtils.test.ts new file mode 100644 index 0000000000..78b48614d2 --- /dev/null +++ b/webapp/packages/core-navigation-tree/src/NodesManager/NodeManagerUtils.test.ts @@ -0,0 +1,63 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 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 { describe, expect, it } from '@jest/globals'; + +import { NodeManagerUtils } from './NodeManagerUtils.js'; + +describe('NodeManagerUtils', () => { + describe('connectionIdToConnectionNodeId', () => { + it('should prepend "database://" to the connectionId', () => { + const connectionId = '12345'; + const result = NodeManagerUtils.connectionIdToConnectionNodeId(connectionId); + expect(result).toBe('database://12345'); + }); + + it('should work with different connectionId values', () => { + expect(NodeManagerUtils.connectionIdToConnectionNodeId('abc')).toBe('database://abc'); + expect(NodeManagerUtils.connectionIdToConnectionNodeId('')).toBe('database://'); + }); + }); + + describe('isDatabaseObject', () => { + it('should return true for objectIds starting with "database://"', () => { + expect(NodeManagerUtils.isDatabaseObject('database://123')).toBe(true); + expect(NodeManagerUtils.isDatabaseObject('database://abc')).toBe(true); + }); + + it('should return false for objectIds not starting with "database://"', () => { + expect(NodeManagerUtils.isDatabaseObject('http://example.com')).toBe(false); + expect(NodeManagerUtils.isDatabaseObject('12345')).toBe(false); + expect(NodeManagerUtils.isDatabaseObject('')).toBe(false); + }); + }); + + describe('concatSchemaAndCatalog', () => { + it('should concatenate schemaId and catalogId with "@" when both are provided', () => { + const result = NodeManagerUtils.concatSchemaAndCatalog('catalog1', 'schema1'); + expect(result).toBe('schema1@catalog1'); + }); + + it('should return just schemaId if catalogId is undefined', () => { + expect(NodeManagerUtils.concatSchemaAndCatalog(undefined, 'schema1')).toBe('schema1'); + }); + + it('should return just catalogId if schemaId is undefined', () => { + expect(NodeManagerUtils.concatSchemaAndCatalog('catalog1', undefined)).toBe('catalog1'); + }); + + it('should return an empty string if both are undefined', () => { + expect(NodeManagerUtils.concatSchemaAndCatalog(undefined, undefined)).toBe(''); + }); + + it('should handle cases with empty strings', () => { + expect(NodeManagerUtils.concatSchemaAndCatalog('', 'catalog1')).toBe('catalog1'); + expect(NodeManagerUtils.concatSchemaAndCatalog('schema1', '')).toBe('schema1'); + expect(NodeManagerUtils.concatSchemaAndCatalog('', '')).toBe(''); + }); + }); +}); diff --git a/webapp/packages/core-utils/src/getDomainFromUrl.test.ts b/webapp/packages/core-utils/src/getDomainFromUrl.test.ts new file mode 100644 index 0000000000..6309d8cd1c --- /dev/null +++ b/webapp/packages/core-utils/src/getDomainFromUrl.test.ts @@ -0,0 +1,66 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 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 { describe, expect, it } from '@jest/globals'; + +import { getDomainFromUrl } from './getDomainFromUrl.js'; + +describe('getDomainFromUrl', () => { + it('should return domain for a valid URL with http protocol', () => { + const url = 'http://example.com/path'; + const domain = getDomainFromUrl(url); + expect(domain).toBe('example.com'); + }); + + it('should return domain for a valid URL with https protocol', () => { + const url = 'https://example.com/path'; + const domain = getDomainFromUrl(url); + expect(domain).toBe('example.com'); + }); + + it('should return domain for a valid URL with www', () => { + const url = 'https://www.example.com/path'; + const domain = getDomainFromUrl(url); + expect(domain).toBe('www.example.com'); + }); + + it('should return domain for a URL with a subdomain', () => { + const url = 'https://blog.example.com'; + const domain = getDomainFromUrl(url); + expect(domain).toBe('blog.example.com'); + }); + + it('should return empty string for an invalid URL', () => { + const url = 'not-a-valid-url'; + const domain = getDomainFromUrl(url); + expect(domain).toBe(''); + }); + + it('should return empty string for an empty string input', () => { + const url = ''; + const domain = getDomainFromUrl(url); + expect(domain).toBe(''); + }); + + it('should return domain for a URL with query parameters', () => { + const url = 'https://example.com/search?q=test'; + const domain = getDomainFromUrl(url); + expect(domain).toBe('example.com'); + }); + + it('should return domain for a URL with port number', () => { + const url = 'https://example.com:8080/path'; + const domain = getDomainFromUrl(url); + expect(domain).toBe('example.com'); + }); + + it('should return domain for an IP address URL', () => { + const url = 'http://127.0.0.1:3000'; + const domain = getDomainFromUrl(url); + expect(domain).toBe('127.0.0.1'); + }); +}); diff --git a/webapp/packages/core-utils/src/getDomainFromUrl.ts b/webapp/packages/core-utils/src/getDomainFromUrl.ts index dc0e6350d8..b494cd8f31 100644 --- a/webapp/packages/core-utils/src/getDomainFromUrl.ts +++ b/webapp/packages/core-utils/src/getDomainFromUrl.ts @@ -11,7 +11,6 @@ export function getDomainFromUrl(url: string): string { const urlObject = new URL(url); return urlObject.hostname; } catch (e) { - console.error('Invalid URL:', e); return ''; } }