mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Cb 5293 unit tests for hooks in core blocks (#2724)
* CB-5293 adds unit tests for useState delay hook * CB-5293 removes unused helper hook * СB-5277 adds tests for useClipboard hook --------- Co-authored-by: Alexey <wrouds@gmail.com>
This commit is contained in:
@@ -198,7 +198,6 @@ export * from './TimerIcon';
|
||||
export * from './InfoItem';
|
||||
export * from './Iframe';
|
||||
export * from './Code';
|
||||
export * from './useClickEvents';
|
||||
export * from './useControlledScroll';
|
||||
export * from './useClipboard';
|
||||
export * from './useCombinedHandler';
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* 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 { useObjectRef } from './useObjectRef';
|
||||
|
||||
interface IOptions<T> {
|
||||
onClick?: () => void;
|
||||
onDoubleClick?: (event: React.MouseEvent<T>) => void;
|
||||
}
|
||||
|
||||
interface IHandlers<T> {
|
||||
onClick: (event: React.MouseEvent<T>) => void;
|
||||
onDoubleClick: (event: React.MouseEvent<T>) => void;
|
||||
}
|
||||
|
||||
export function useClickEvents<T>(options: IOptions<T>): IHandlers<T> {
|
||||
return useObjectRef(
|
||||
() => ({
|
||||
delayed: false,
|
||||
onClick(event: React.MouseEvent<T>) {
|
||||
if (this.delayed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.delayed = true;
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.delayed) {
|
||||
this.options.onClick?.();
|
||||
this.delayed = false;
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
onDoubleClick(event: React.MouseEvent<T>) {
|
||||
this.delayed = false;
|
||||
options.onDoubleClick?.(event);
|
||||
},
|
||||
}),
|
||||
{ options },
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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 { renderHook } from '@testing-library/react';
|
||||
|
||||
import * as coreDi from '@cloudbeaver/core-di';
|
||||
import * as coreUtils from '@cloudbeaver/core-utils';
|
||||
|
||||
import { useClipboard } from './useClipboard';
|
||||
|
||||
jest.mock('@cloudbeaver/core-utils', () => ({
|
||||
copyToClipboard: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@cloudbeaver/core-di', () => ({
|
||||
useService: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@cloudbeaver/core-events', () => ({
|
||||
NotificationService: {},
|
||||
}));
|
||||
|
||||
class NotificationService {
|
||||
logSuccess = jest.fn();
|
||||
logException = jest.fn();
|
||||
}
|
||||
|
||||
const getMocks = () => {
|
||||
const copyToClipboardMock = jest.fn();
|
||||
const notificationServiceMock = new NotificationService();
|
||||
|
||||
jest.spyOn(coreUtils, 'copyToClipboard').mockImplementation(copyToClipboardMock);
|
||||
jest.spyOn(coreDi, 'useService').mockImplementation(() => notificationServiceMock);
|
||||
|
||||
return {
|
||||
copyToClipboardMock,
|
||||
notificationServiceMock,
|
||||
};
|
||||
};
|
||||
|
||||
describe('useClipboard', () => {
|
||||
const VALUE_TO_COPY = 'test';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should copy without notification', async () => {
|
||||
const { notificationServiceMock, copyToClipboardMock } = getMocks();
|
||||
|
||||
const { result } = renderHook(() => useClipboard());
|
||||
|
||||
result.current(VALUE_TO_COPY);
|
||||
expect(copyToClipboardMock).toHaveBeenCalledWith(VALUE_TO_COPY);
|
||||
|
||||
result.current(VALUE_TO_COPY, false);
|
||||
|
||||
expect(copyToClipboardMock).toHaveBeenCalledWith(VALUE_TO_COPY);
|
||||
expect(copyToClipboardMock).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(notificationServiceMock.logSuccess).not.toHaveBeenCalled();
|
||||
expect(notificationServiceMock.logException).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should copy with notification', async () => {
|
||||
const { notificationServiceMock, copyToClipboardMock } = getMocks();
|
||||
|
||||
const { result } = renderHook(() => useClipboard());
|
||||
|
||||
result.current(VALUE_TO_COPY, true);
|
||||
|
||||
expect(copyToClipboardMock).toHaveBeenCalledWith(VALUE_TO_COPY);
|
||||
expect(notificationServiceMock.logSuccess).toHaveBeenCalledWith({ title: 'ui_copy_to_clipboard_copied' });
|
||||
expect(notificationServiceMock.logException).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle exception while trying to copy', async () => {
|
||||
const { notificationServiceMock, copyToClipboardMock } = getMocks();
|
||||
|
||||
const { result } = renderHook(() => useClipboard());
|
||||
const exception = new Error('test');
|
||||
|
||||
copyToClipboardMock.mockImplementation(() => {
|
||||
throw exception;
|
||||
});
|
||||
|
||||
result.current(VALUE_TO_COPY, true);
|
||||
|
||||
expect(copyToClipboardMock).toHaveBeenCalledWith(VALUE_TO_COPY);
|
||||
expect(notificationServiceMock.logSuccess).not.toHaveBeenCalled();
|
||||
expect(notificationServiceMock.logException).toHaveBeenCalledWith(exception, 'ui_copy_to_clipboard_failed_to_copy');
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { ENotificationType, NotificationService } from '@cloudbeaver/core-events';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { copyToClipboard } from '@cloudbeaver/core-utils';
|
||||
|
||||
export function useClipboard() {
|
||||
@@ -19,7 +19,7 @@ export function useClipboard() {
|
||||
try {
|
||||
copyToClipboard(value);
|
||||
if (notify) {
|
||||
notificationService.notify({ title: 'ui_copy_to_clipboard_copied' }, ENotificationType.Success);
|
||||
notificationService.logSuccess({ title: 'ui_copy_to_clipboard_copied' });
|
||||
}
|
||||
} catch (exception: any) {
|
||||
notificationService.logException(exception, 'ui_copy_to_clipboard_failed_to_copy');
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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 { renderHook, waitFor } from '@testing-library/react';
|
||||
|
||||
import { useStateDelay } from './useStateDelay';
|
||||
|
||||
interface IHookProps {
|
||||
value: boolean;
|
||||
delay: number;
|
||||
callback?: VoidFunction;
|
||||
}
|
||||
|
||||
const useStateDelayWrapper = ({ value, delay, callback }: IHookProps) => useStateDelay(value, delay, callback);
|
||||
|
||||
describe('useStateDelay', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
test("should return initial state during whole hook's lifecycle", async () => {
|
||||
const { result } = renderHook(() => useStateDelay(true, 100));
|
||||
expect(result.current).toBe(true);
|
||||
await waitFor(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
});
|
||||
expect(result.current).toBe(true);
|
||||
await waitFor(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 60));
|
||||
});
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
|
||||
test('should return updated state after delay if it was updated', async () => {
|
||||
const { result, rerender } = renderHook(({ value, delay }: IHookProps) => useStateDelayWrapper({ value, delay }), {
|
||||
initialProps: {
|
||||
value: false,
|
||||
delay: 100,
|
||||
},
|
||||
});
|
||||
expect(result.current).toBe(false);
|
||||
rerender({
|
||||
value: true,
|
||||
delay: 100,
|
||||
});
|
||||
await waitFor(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
});
|
||||
expect(result.current).toBe(false);
|
||||
await waitFor(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 60));
|
||||
});
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
|
||||
test('should execute callback on state change', async () => {
|
||||
const callback = jest.fn();
|
||||
const { rerender } = renderHook(({ value, delay }: IHookProps) => useStateDelayWrapper({ value, delay, callback }), {
|
||||
initialProps: {
|
||||
value: false,
|
||||
delay: 100,
|
||||
callback,
|
||||
},
|
||||
});
|
||||
expect(callback).toHaveBeenCalledTimes(0);
|
||||
await waitFor(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
});
|
||||
expect(callback).toHaveBeenCalledTimes(0);
|
||||
rerender({
|
||||
value: true,
|
||||
delay: 100,
|
||||
callback,
|
||||
});
|
||||
await waitFor(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 60));
|
||||
});
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should not call callback', async () => {
|
||||
const callback = jest.fn();
|
||||
const { result, rerender } = renderHook(({ value, delay }: IHookProps) => useStateDelayWrapper({ value, delay, callback }), {
|
||||
initialProps: {
|
||||
value: false,
|
||||
delay: 100,
|
||||
callback,
|
||||
},
|
||||
});
|
||||
expect(result.current).toBe(false);
|
||||
expect(callback).toHaveBeenCalledTimes(0);
|
||||
await waitFor(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
});
|
||||
expect(callback).toHaveBeenCalledTimes(0);
|
||||
rerender({
|
||||
value: false,
|
||||
delay: 100,
|
||||
callback,
|
||||
});
|
||||
await waitFor(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 60));
|
||||
});
|
||||
expect(callback).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("should prolong delay if was updated as hook's argument", async () => {
|
||||
const { result, rerender } = renderHook(({ value, delay }: IHookProps) => useStateDelayWrapper({ value, delay }), {
|
||||
initialProps: {
|
||||
value: false,
|
||||
delay: 100,
|
||||
},
|
||||
});
|
||||
expect(result.current).toBe(false);
|
||||
await waitFor(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
});
|
||||
rerender({
|
||||
value: true,
|
||||
delay: 200,
|
||||
});
|
||||
await waitFor(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 60));
|
||||
});
|
||||
expect(result.current).toBe(false);
|
||||
await waitFor(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
});
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -13,15 +13,12 @@ import { AppContext, IServiceInjector } from '@cloudbeaver/core-di';
|
||||
import type { IApplication } from './createApp';
|
||||
|
||||
function ApplicationWrapper(serviceInjector: IServiceInjector): React.FC<React.PropsWithChildren> {
|
||||
return function render({ children }) {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<AppContext app={serviceInjector}>{children}</AppContext>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
return ({ children }) => (
|
||||
<Suspense fallback={null}>
|
||||
<AppContext app={serviceInjector}>{children}</AppContext>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderInApp<
|
||||
Q extends Queries = typeof queries,
|
||||
Container extends Element | DocumentFragment = HTMLElement,
|
||||
|
||||
Reference in New Issue
Block a user