From a1503b2a568598b8f86e4c636338fff4b1b937c2 Mon Sep 17 00:00:00 2001 From: zalenskiSofteq Date: Thu, 16 Feb 2023 01:28:36 +0800 Subject: [PATCH 1/2] #RI-4142 - Decompressed GZIP data --- jest.config.js | 8 +-- package.json | 1 + redisinsight/ui/src/constants/browser.ts | 6 ++ redisinsight/ui/src/constants/keys.ts | 13 ++++ .../hash-details/HashDetails.spec.tsx | 24 ++++++- .../components/hash-details/HashDetails.tsx | 26 ++++--- .../key-details-header/KeyDetailsHeader.tsx | 10 +-- .../list-details/ListDetails.spec.tsx | 20 ++++++ .../components/list-details/ListDetails.tsx | 17 +++-- .../set-details/SetDetails.spec.tsx | 20 ++++++ .../components/set-details/SetDetails.tsx | 10 +-- .../StreamDetailsWrapper.spec.tsx | 31 ++++++++- .../StreamDataViewWrapper.tsx | 15 ++-- .../string-details/StringDetails.spec.tsx | 68 +++++++++++-------- .../string-details/StringDetails.tsx | 21 ++++-- .../zset-details/ZSetDetails.spec.tsx | 20 ++++++ .../components/zset-details/ZSetDetails.tsx | 8 ++- redisinsight/ui/src/slices/browser/keys.ts | 8 ++- redisinsight/ui/src/slices/interfaces/keys.ts | 3 +- .../ui/src/slices/tests/browser/keys.spec.ts | 26 ++++++- .../src/utils/decompressors/decompressors.ts | 47 +++++++++++++ .../ui/src/utils/decompressors/index.ts | 1 + .../src/utils/formatters/bufferFormatters.ts | 2 +- .../tests/decompressors/decompressors.spec.ts | 48 +++++++++++++ yarn.lock | 5 ++ 25 files changed, 381 insertions(+), 77 deletions(-) create mode 100644 redisinsight/ui/src/utils/decompressors/decompressors.ts create mode 100644 redisinsight/ui/src/utils/decompressors/index.ts create mode 100644 redisinsight/ui/src/utils/tests/decompressors/decompressors.spec.ts diff --git a/jest.config.js b/jest.config.js index d657c1e87f..01b5f117d8 100644 --- a/jest.config.js +++ b/jest.config.js @@ -54,10 +54,10 @@ module.exports = { ], coverageThreshold: { global: { - statements: 77, - branches: 55, - functions: 65, - lines: 75, + statements: 78, + branches: 61, + functions: 70, + lines: 79, }, // './redisinsight/ui/src/slices/**/*.ts': { // statements: 90, diff --git a/package.json b/package.json index f680cba32a..d643e89f17 100644 --- a/package.json +++ b/package.json @@ -228,6 +228,7 @@ "electron-log": "^4.2.4", "electron-store": "^8.0.0", "electron-updater": "^5.0.5", + "fflate": "^0.7.4", "file-saver": "^2.0.5", "formik": "^2.2.9", "html-entities": "^2.3.2", diff --git a/redisinsight/ui/src/constants/browser.ts b/redisinsight/ui/src/constants/browser.ts index e746b0797a..fed8112a87 100644 --- a/redisinsight/ui/src/constants/browser.ts +++ b/redisinsight/ui/src/constants/browser.ts @@ -1,3 +1,5 @@ +import { KeyValueFormat } from "./keys" + export const DEFAULT_DELIMITER = ':' export const TEXT_UNPRINTABLE_CHARACTERS = { @@ -10,3 +12,7 @@ export const TEXT_INVALID_VALUE = { title: 'Value will be saved as Unicode', text: 'as it is not valid in the selected format.', } + +export const TEXT_DISABLED_COMPRESSED_VALUE: string = 'Cannot edit the decompressed value' + +export const TEXT_FAILED_CONVENT_FORMATTER = (format: KeyValueFormat) => `Failed to convert to ${format}` diff --git a/redisinsight/ui/src/constants/keys.ts b/redisinsight/ui/src/constants/keys.ts index 18a8bc856f..080430ca3c 100644 --- a/redisinsight/ui/src/constants/keys.ts +++ b/redisinsight/ui/src/constants/keys.ts @@ -180,6 +180,19 @@ export enum KeyValueFormat { Pickle = 'Pickle', } +export enum KeyValueCompressor { + GZIP = 'GZIP', + LZ4 = 'LZ4', + ZSTD = 'ZSTD', + SNAPPY = 'SNAPPY', + Brotli = 'Brotli', + PHPGZCompress = 'PHPGZCompress', +} + +export const COMPRESSOR_MAGIC_SYMBOLS = Object.freeze({ + [KeyValueCompressor.GZIP]: [31, 139], // 1f 8b hex +}) + export enum SearchHistoryMode { Pattern = 'pattern', Redisearch = 'redisearch' diff --git a/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.spec.tsx b/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.spec.tsx index bca2fe9caa..bca7815113 100644 --- a/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.spec.tsx +++ b/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.spec.tsx @@ -1,8 +1,10 @@ import React from 'react' import { instance, mock } from 'ts-mockito' +import { hashDataSelector } from 'uiSrc/slices/browser/hash' import { RedisResponseBufferType } from 'uiSrc/slices/interfaces' -import { bufferToString } from 'uiSrc/utils' +import { anyToBuffer, bufferToString } from 'uiSrc/utils' import { fireEvent, render, screen } from 'uiSrc/utils/test-utils' +import { GZIP_COMPRESSED_VALUE_1, GZIP_COMPRESSED_VALUE_2, GZIP_DECOMPRESSED_VALUE_1, GZIP_DECOMPRESSED_VALUE_2 } from 'uiSrc/utils/tests/decompressors/decompressors.spec' import HashDetails, { Props } from './HashDetails' const mockedProps = mock() @@ -70,4 +72,24 @@ describe('HashDetails', () => { render() expect(screen.getByTestId('resize-trigger-field')).toBeInTheDocument() }) + + it('should render decompressed GZIP data', () => { + const defaultState = jest.requireActual('uiSrc/slices/browser/hash').initialState + const hashDataSelectorMock = jest.fn().mockReturnValue({ + ...defaultState, + total: 1, + key: '123zxczxczxc', + fields: [ + { field: anyToBuffer(GZIP_COMPRESSED_VALUE_1), value: anyToBuffer(GZIP_COMPRESSED_VALUE_2) }, + ] + }) + hashDataSelector.mockImplementation(hashDataSelectorMock) + + const { queryByTestId, queryAllByTestId } = render() + const fieldEl = queryAllByTestId(/hash-field-/)?.[0] + const valueEl = queryByTestId(/hash-field-value/) + + expect(fieldEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) + expect(valueEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_2) + }) }) diff --git a/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.tsx b/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.tsx index b50bd15008..482556f95c 100644 --- a/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.tsx +++ b/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.tsx @@ -17,7 +17,9 @@ import { KeyTypes, OVER_RENDER_BUFFER_COUNT, TableCellAlignment, + TEXT_DISABLED_COMPRESSED_VALUE, TEXT_DISABLED_FORMATTER_EDITING, + TEXT_FAILED_CONVENT_FORMATTER, TEXT_INVALID_VALUE, TEXT_UNPRINTABLE_CHARACTERS } from 'uiSrc/constants' @@ -53,6 +55,7 @@ import { stringToSerializedBufferFormat } from 'uiSrc/utils' import { stringToBuffer } from 'uiSrc/utils/formatters/bufferFormatters' +import { decompressingBuffer, getCompressor } from 'uiSrc/utils/decompressors' import { AddFieldsToHashDto, GetHashFieldsResponse, HashFieldDto, } from 'apiSrc/modules/browser/dto/hash.dto' import PopoverDelete from '../popover-delete/PopoverDelete' @@ -292,17 +295,18 @@ const HashDetails = (props: Props) => { className: 'value-table-separate-border', headerClassName: 'value-table-separate-border', render: (_name: string, { field: fieldItem }: HashFieldDto, expanded?: boolean) => { - // Better to cut the long string, because it could affect virtual scroll performance + const { value: decompressedItem } = decompressingBuffer(fieldItem) const field = bufferToString(fieldItem) || '' + // Better to cut the long string, because it could affect virtual scroll performance const tooltipContent = formatLongName(field) - const { value, isValid } = formattingBuffer(fieldItem, viewFormatProp, { expanded }) + const { value, isValid } = formattingBuffer(decompressedItem, viewFormatProp, { expanded }) return (
{!expanded && ( { expanded?: boolean, rowIndex = 0 ) { - // Better to cut the long string, because it could affect virtual scroll performance + const { value: decompressedFieldItem } = decompressingBuffer(fieldItem) + const { value: decompressedValueItem } = decompressingBuffer(valueItem) const value = bufferToString(valueItem) - const field = bufferToString(fieldItem) + const field = bufferToString(decompressedFieldItem) + // Better to cut the long string, because it could affect virtual scroll performance const tooltipContent = formatLongName(value) - const { value: formattedValue, isValid } = formattingBuffer(valueItem, viewFormatProp, { expanded }) + const { value: formattedValue, isValid } = formattingBuffer(decompressedValueItem, viewFormatProp, { expanded }) if (rowIndex === editingIndex) { const disabled = !isNonUnicodeFormatter(viewFormat, isValid) @@ -404,7 +410,7 @@ const HashDetails = (props: Props) => { > {!expanded && ( { minWidth: 95, maxWidth: 95, render: function Actions(_act: any, { field: fieldItem, value: valueItem }: HashFieldDto, _, rowIndex?: number) { + const compressor = getCompressor(valueItem) const field = bufferToString(fieldItem, viewFormat) - const isEditable = isFormatEditable(viewFormat) + const isEditable = !compressor && isFormatEditable(viewFormat) + const tooltipContent = compressor ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING return (
- + { - const isEditable = isFormatEditable(viewFormatProp) + const isEditable = !compressor && isFormatEditable(viewFormatProp) + const noEditableText = compressor ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING return ( <> {KEY_TYPES_ACTIONS[keyType] && 'addItems' in KEY_TYPES_ACTIONS[keyType] && ( @@ -401,7 +403,7 @@ const KeyDetailsHeader = ({ {KEY_TYPES_ACTIONS[keyType] && 'editItem' in KEY_TYPES_ACTIONS[keyType] && (
() @@ -68,4 +71,21 @@ describe('ListDetails', () => { render() expect(screen.getByTestId('resize-trigger-index')).toBeInTheDocument() }) + + it('should render decompressed GZIP data = "1"', () => { + const defaultState = jest.requireActual('uiSrc/slices/browser/list').initialState + const listDataSelectorMock = jest.fn().mockReturnValue({ + ...defaultState, + key: '123zxczxczxc', + elements: [ + { element: anyToBuffer(GZIP_COMPRESSED_VALUE_1), index: 0 }, + ] + }) + listDataSelector.mockImplementation(listDataSelectorMock) + + const { queryByTestId } = render() + const elementEl = queryByTestId(/list-element-value-/) + + expect(elementEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) + }) }) diff --git a/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.tsx b/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.tsx index 354b81a7d5..1dbd11ec7b 100644 --- a/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.tsx +++ b/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.tsx @@ -31,7 +31,9 @@ import { TableCellAlignment, TEXT_INVALID_VALUE, TEXT_DISABLED_FORMATTER_EDITING, - TEXT_UNPRINTABLE_CHARACTERS + TEXT_UNPRINTABLE_CHARACTERS, + TEXT_DISABLED_COMPRESSED_VALUE, + TEXT_FAILED_CONVENT_FORMATTER, } from 'uiSrc/constants' import { bufferToSerializedFormat, @@ -52,6 +54,8 @@ import VirtualTable from 'uiSrc/components/virtual-table/VirtualTable' import InlineItemEditor from 'uiSrc/components/inline-item-editor/InlineItemEditor' import { StopPropagation } from 'uiSrc/components/virtual-table' import { getColumnWidth } from 'uiSrc/components/virtual-grid' +import { decompressingBuffer, getCompressor } from 'uiSrc/utils/decompressors' + import { SetListElementDto, SetListElementResponse, @@ -276,9 +280,10 @@ const ListDetails = (props: Props) => { expanded: boolean = false, rowIndex = 0 ) { + const { value: decompressedElementItem } = decompressingBuffer(elementItem) const element = bufferToString(elementItem) const tooltipContent = formatLongName(element) - const { value, isValid } = formattingBuffer(elementItem, viewFormatProp, { expanded }) + const { value, isValid } = formattingBuffer(decompressedElementItem, viewFormatProp, { expanded }) if (index === editingIndex) { const disabled = !isNonUnicodeFormatter(viewFormat, isValid) @@ -350,7 +355,7 @@ const ListDetails = (props: Props) => { > {!expanded && ( { maxWidth: 60, absoluteWidth: 60, render: function Actions(_element: any, { index, element }: IListElement) { - const isEditable = isFormatEditable(viewFormat) + const compressor = getCompressor(element) + const isEditable = !compressor && isFormatEditable(viewFormat) + const tooltipContent = compressor ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING return (
- + { fireEvent.click(screen.getAllByTestId(/set-remove-btn/)[0]) expect(screen.getByTestId(/set-remove-btn-1-icon/)).toBeInTheDocument() }) + + it('should render decompressed GZIP data = "1"', () => { + const defaultState = jest.requireActual('uiSrc/slices/browser/set').initialState + const setDataSelectorMock = jest.fn().mockReturnValue({ + ...defaultState, + key: '123zxczxczxc', + members: [ + anyToBuffer(GZIP_COMPRESSED_VALUE_1), + ] + }) + setDataSelector.mockImplementation(setDataSelectorMock) + + const { queryByTestId } = render() + const memberEl = queryByTestId(/set-member-value-/) + + expect(memberEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) + }) }) diff --git a/redisinsight/ui/src/pages/browser/components/set-details/SetDetails.tsx b/redisinsight/ui/src/pages/browser/components/set-details/SetDetails.tsx index d734a92ab2..273e36ef2d 100644 --- a/redisinsight/ui/src/pages/browser/components/set-details/SetDetails.tsx +++ b/redisinsight/ui/src/pages/browser/components/set-details/SetDetails.tsx @@ -16,7 +16,7 @@ import { formatLongName, formattingBuffer, } from 'uiSrc/utils' -import { KeyTypes, OVER_RENDER_BUFFER_COUNT } from 'uiSrc/constants' +import { KeyTypes, OVER_RENDER_BUFFER_COUNT, TEXT_FAILED_CONVENT_FORMATTER } from 'uiSrc/constants' import { sendEventTelemetry, TelemetryEvent, getBasedOnViewTypeEvent, getMatchType } from 'uiSrc/telemetry' import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances' import { selectedKeyDataSelector, keysSelector, selectedKeySelector } from 'uiSrc/slices/browser/keys' @@ -35,6 +35,7 @@ import PopoverDelete from 'uiSrc/pages/browser/components/popover-delete/Popover import { getColumnWidth } from 'uiSrc/components/virtual-grid' import { IColumnSearchState, ITableColumn } from 'uiSrc/components/virtual-table/interfaces' import { stringToBuffer } from 'uiSrc/utils/formatters/bufferFormatters' +import { decompressingBuffer } from 'uiSrc/utils/decompressors' import { GetSetMembersResponse } from 'apiSrc/modules/browser/dto/set.dto' import styles from './styles.module.scss' @@ -190,10 +191,11 @@ const SetDetails = (props: Props) => { initialSearchValue: '', truncateText: true, render: function Name(_name: string, memberItem: RedisResponseBuffer, expanded: boolean = false) { - // Better to cut the long string, because it could affect virtual scroll performance + const { value: decompressedMemberItem } = decompressingBuffer(memberItem) const member = bufferToString(memberItem) + // Better to cut the long string, because it could affect virtual scroll performance const tooltipContent = formatLongName(member) - const { value, isValid } = formattingBuffer(memberItem, viewFormatProp, { expanded }) + const { value, isValid } = formattingBuffer(decompressedMemberItem, viewFormatProp, { expanded }) const cellContent = value?.substring?.(0, 200) ?? value return ( @@ -204,7 +206,7 @@ const SetDetails = (props: Props) => { > {!expanded && ( { expect(queryByTestId('range-bar')).not.toBeInTheDocument() }) + + it('should render decompressed GZIP data', () => { + const mockId = '1232-123123123' + const entryWithCompressedGZIPData = { + id: mockId, + fields: [{ + name: anyToBuffer(GZIP_COMPRESSED_VALUE_1), + value: anyToBuffer(GZIP_COMPRESSED_VALUE_2), + }] + } + + streamDataSelector.mockImplementation(() => ({ + ...mockedEntryData, + firstEntry: entryWithCompressedGZIPData, + lastEntry: entryWithCompressedGZIPData, + entries: [ + entryWithCompressedGZIPData + ], + })) + + const { queryAllByTestId } = render() + + const fieldNameEl = queryAllByTestId(/stream-field-name-/)?.[0] + const entryFieldEl = queryAllByTestId(/stream-entry-field-/)?.[0] + + expect(fieldNameEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) + expect(entryFieldEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_2) + }) }) diff --git a/redisinsight/ui/src/pages/browser/components/stream-details/stream-data-view/StreamDataViewWrapper.tsx b/redisinsight/ui/src/pages/browser/components/stream-details/stream-data-view/StreamDataViewWrapper.tsx index 1dce2b0638..e2dfb14c1f 100644 --- a/redisinsight/ui/src/pages/browser/components/stream-details/stream-data-view/StreamDataViewWrapper.tsx +++ b/redisinsight/ui/src/pages/browser/components/stream-details/stream-data-view/StreamDataViewWrapper.tsx @@ -16,10 +16,11 @@ import { streamDataSelector, deleteStreamEntry } from 'uiSrc/slices/browser/stre import { ITableColumn } from 'uiSrc/components/virtual-table/interfaces' import PopoverDelete from 'uiSrc/pages/browser/components/popover-delete/PopoverDelete' import { getFormatTime } from 'uiSrc/utils/streamUtils' -import { KeyTypes, TableCellTextAlignment } from 'uiSrc/constants' +import { KeyTypes, TableCellTextAlignment, TEXT_FAILED_CONVENT_FORMATTER } from 'uiSrc/constants' import { getBasedOnViewTypeEvent, sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances' import { keysSelector, selectedKeySelector, updateSelectedKeyRefreshTime } from 'uiSrc/slices/browser/keys' +import { decompressingBuffer } from 'uiSrc/utils/decompressors' import { StreamEntryDto } from 'apiSrc/modules/browser/dto/stream.dto' import StreamDataView from './StreamDataView' @@ -96,8 +97,9 @@ const StreamDataViewWrapper = (props: Props) => { id: field, label: field, render: () => { + const { value: decompressedName } = decompressingBuffer(name) const value = name ? bufferToString(name) : '' - const { value: formattedValue, isValid } = formattingBuffer(name || stringToBuffer(''), viewFormatProp) + const { value: formattedValue, isValid } = formattingBuffer(decompressedName || stringToBuffer(''), viewFormatProp) const tooltipContent = formatLongName(value) return ( <> @@ -107,7 +109,7 @@ const StreamDataViewWrapper = (props: Props) => { data-testid={`stream-field-name-${field}`} > { const values = fields.filter(({ name: fieldName }) => bufferToString(fieldName, viewFormat) === name) const value = values[index] ? bufferToString(values[index]?.value) : '' - const bufferValue = values[index]?.value || stringToBuffer('') - const { value: formattedValue, isValid } = formattingBuffer(bufferValue, viewFormatProp, { expanded }) + const { value: decompressedBufferValue } = decompressingBuffer(values[index]?.value || stringToBuffer('')) + // const bufferValue = values[index]?.value || stringToBuffer('') + const { value: formattedValue, isValid } = formattingBuffer(decompressedBufferValue, viewFormatProp, { expanded }) const cellContent = formattedValue?.substring?.(0, 650) ?? formattedValue const tooltipContent = formatLongName(value) @@ -223,7 +226,7 @@ const StreamDataViewWrapper = (props: Props) => { > {!expanded && ( ({ }), })) -jest.mock('react-redux', () => ({ - ...jest.requireActual('react-redux'), - useSelector: jest.fn() -})) - -beforeEach(() => { - const state: RootState = store.getState(); - - (useSelector as jest.Mock).mockImplementation((callback: (arg0: RootState) => RootState) => callback({ - ...state, - browser: { - ...state.browser, - string: { - ...state.browser.string, - data: { - key: 'test', - value: { - type: 'Buffer', - data: [49, 34, 43], - } - } - } - } - })) -}) - describe('StringDetails', () => { it('should render', () => { expect( @@ -127,4 +101,40 @@ describe('StringDetails', () => { fireEvent.click(btnApply) expect(textArea).toHaveValue(STRING_VALUE_SPACE) }) + + it('should render decompressed GZIP data = "1"', () => { + const stringDataSelectorMock = jest.fn().mockReturnValue({ + value: anyToBuffer(GZIP_COMPRESSED_VALUE_1) + }) + stringDataSelector.mockImplementation(stringDataSelectorMock) + + render( + + ) + const textArea = screen.getByTestId(STRING_VALUE) + + expect(textArea).toHaveValue(GZIP_DECOMPRESSED_VALUE_1) + }) + + it('should render decompressed GZIP data = "2"', () => { + const stringDataSelectorMock = jest.fn().mockReturnValue({ + value: anyToBuffer(GZIP_COMPRESSED_VALUE_2) + }) + stringDataSelector.mockImplementation(stringDataSelectorMock) + + render( + + ) + const textArea = screen.getByTestId(STRING_VALUE) + + expect(textArea).toHaveValue(GZIP_DECOMPRESSED_VALUE_2) + }) }) diff --git a/redisinsight/ui/src/pages/browser/components/string-details/StringDetails.tsx b/redisinsight/ui/src/pages/browser/components/string-details/StringDetails.tsx index 9abd861a9b..08ce1d5611 100644 --- a/redisinsight/ui/src/pages/browser/components/string-details/StringDetails.tsx +++ b/redisinsight/ui/src/pages/browser/components/string-details/StringDetails.tsx @@ -19,7 +19,7 @@ import { isEqualBuffers, isFormatEditable, stringToBuffer, - stringToSerializedBufferFormat + stringToSerializedBufferFormat, } from 'uiSrc/utils' import { resetStringValue, @@ -29,9 +29,10 @@ import { } from 'uiSrc/slices/browser/string' import InlineItemEditor from 'uiSrc/components/inline-item-editor/InlineItemEditor' import { AddStringFormConfig as config } from 'uiSrc/pages/browser/components/add-key/constants/fields-config' -import { selectedKeyDataSelector, selectedKeySelector } from 'uiSrc/slices/browser/keys' -import { TEXT_INVALID_VALUE, TEXT_UNPRINTABLE_CHARACTERS } from 'uiSrc/constants' +import { selectedKeyDataSelector, selectedKeySelector, setCompressor } from 'uiSrc/slices/browser/keys' +import { TEXT_DISABLED_COMPRESSED_VALUE, TEXT_FAILED_CONVENT_FORMATTER, TEXT_INVALID_VALUE, TEXT_UNPRINTABLE_CHARACTERS } from 'uiSrc/constants' import { calculateTextareaLines } from 'uiSrc/utils/calculateTextareaLines' +import { decompressingBuffer } from 'uiSrc/utils/decompressors' import styles from './styles.module.scss' @@ -59,6 +60,7 @@ const StringDetails = (props: Props) => { const [isValid, setIsValid] = useState(true) const [isDisabled, setIsDisabled] = useState(false) const [isEditable, setIsEditable] = useState(true) + const [noEditableText, setNoEditableText] = useState(TEXT_DISABLED_COMPRESSED_VALUE) const textAreaRef: Ref = useRef(null) const viewValueRef: Ref = useRef(null) @@ -72,8 +74,10 @@ const StringDetails = (props: Props) => { useEffect(() => { if (!initialValue) return - const initialValueString = bufferToString(initialValue, viewFormat) - const { value: formattedValue, isValid } = formattingBuffer(initialValue, viewFormatProp, { expanded: true }) + const { value: decompressedValue, compressor } = decompressingBuffer(initialValue) + + const initialValueString = bufferToString(decompressedValue, viewFormat) + const { value: formattedValue, isValid } = formattingBuffer(decompressedValue, viewFormatProp, { expanded: true }) setAreaValue(initialValueString) setValue(formattedValue) @@ -82,7 +86,10 @@ const StringDetails = (props: Props) => { !isNonUnicodeFormatter(viewFormatProp, isValid) && !isEqualBuffers(initialValue, stringToBuffer(initialValueString)) ) - setIsEditable(isFormatEditable(viewFormatProp)) + setIsEditable(!compressor && isFormatEditable(viewFormatProp)) + setNoEditableText(compressor ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_FAILED_CONVENT_FORMATTER(viewFormat)) + + dispatch(setCompressor(compressor)) if (viewFormat !== viewFormatProp) { setViewFormat(viewFormatProp) @@ -153,7 +160,7 @@ const StringDetails = (props: Props) => { ? value : ( diff --git a/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.spec.tsx b/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.spec.tsx index 31a7c32d09..a24cf24d38 100644 --- a/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.spec.tsx +++ b/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.spec.tsx @@ -1,6 +1,9 @@ import React from 'react' import { instance, mock } from 'ts-mockito' +import { zsetDataSelector } from 'uiSrc/slices/browser/zset' +import { anyToBuffer } from 'uiSrc/utils' import { render, screen, fireEvent } from 'uiSrc/utils/test-utils' +import { GZIP_COMPRESSED_VALUE_1, GZIP_DECOMPRESSED_VALUE_1 } from 'uiSrc/utils/tests/decompressors/decompressors.spec' import ZSetDetails, { Props } from './ZSetDetails' const mockedProps = mock() @@ -75,4 +78,21 @@ describe('ZSetDetails', () => { render() expect(screen.getByTestId('resize-trigger-name')).toBeInTheDocument() }) + + it('should render decompressed GZIP data = "1"', () => { + const defaultState = jest.requireActual('uiSrc/slices/browser/zset').initialState + const zsetDataSelectorMock = jest.fn().mockReturnValue({ + ...defaultState, + key: '123zxczxczxc', + members: [ + { name: anyToBuffer(GZIP_COMPRESSED_VALUE_1), score: 1 }, + ] + }) + zsetDataSelector.mockImplementation(zsetDataSelectorMock) + + const { queryByTestId } = render() + const memberEl = queryByTestId(/zset-member-value-/) + + expect(memberEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) + }) }) diff --git a/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.tsx b/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.tsx index 6090df8e29..bdbb959517 100644 --- a/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.tsx +++ b/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.tsx @@ -17,7 +17,7 @@ import { fetchSearchZSetMembers, fetchSearchMoreZSetMembers, } from 'uiSrc/slices/browser/zset' -import { KeyTypes, OVER_RENDER_BUFFER_COUNT, SortOrder, TableCellAlignment } from 'uiSrc/constants' +import { KeyTypes, OVER_RENDER_BUFFER_COUNT, SortOrder, TableCellAlignment, TEXT_FAILED_CONVENT_FORMATTER } from 'uiSrc/constants' import { SCAN_COUNT_DEFAULT } from 'uiSrc/constants/api' import HelpTexts from 'uiSrc/constants/help-texts' import { NoResultsFoundText } from 'uiSrc/constants/texts' @@ -41,6 +41,7 @@ import { IColumnSearchState, ITableColumn, RelativeWidthSizes } from 'uiSrc/comp import { StopPropagation } from 'uiSrc/components/virtual-table' import { getColumnWidth } from 'uiSrc/components/virtual-grid' import { stringToBuffer } from 'uiSrc/utils/formatters/bufferFormatters' +import { decompressingBuffer } from 'uiSrc/utils/decompressors' import { AddMembersToZSetDto, SearchZSetMembersResponse } from 'apiSrc/modules/browser/dto' import PopoverDelete from '../popover-delete/PopoverDelete' @@ -257,9 +258,10 @@ const ZSetDetails = (props: Props) => { className: 'value-table-separate-border', headerClassName: 'value-table-separate-border', render: function Name(_name: string, { name: nameItem }: IZsetMember, expanded?: boolean) { + const { value: decompressedNameItem } = decompressingBuffer(nameItem) const name = bufferToString(nameItem) const tooltipContent = formatLongName(name) - const { value, isValid } = formattingBuffer(nameItem, viewFormat, { expanded }) + const { value, isValid } = formattingBuffer(decompressedNameItem, viewFormat, { expanded }) const cellContent = value?.substring?.(0, 200) ?? value return ( @@ -270,7 +272,7 @@ const ZSetDetails = (props: Props) => { > {!expanded && ( { state.searchHistory.loading = false }, + setCompressor: (state, { payload }:PayloadAction>) => { + state.selectedKey.compressor = payload + }, }, }) @@ -433,7 +438,8 @@ export const { loadSearchHistoryFailure, deleteSearchHistory, deleteSearchHistorySuccess, - deleteSearchHistoryFailure + deleteSearchHistoryFailure, + setCompressor, } = keysSlice.actions // A selector diff --git a/redisinsight/ui/src/slices/interfaces/keys.ts b/redisinsight/ui/src/slices/interfaces/keys.ts index 5a10153d86..ce732a2229 100644 --- a/redisinsight/ui/src/slices/interfaces/keys.ts +++ b/redisinsight/ui/src/slices/interfaces/keys.ts @@ -1,4 +1,4 @@ -import { KeyTypes, KeyValueFormat } from 'uiSrc/constants' +import { KeyTypes, KeyValueCompressor, KeyValueFormat } from 'uiSrc/constants' import { IKeyPropTypes } from 'uiSrc/constants/prop-types/keys' import { Maybe, Nullable } from 'uiSrc/utils' import { GetKeyInfoResponse } from 'apiSrc/modules/browser/dto' @@ -40,6 +40,7 @@ export interface KeysStore { data: Nullable length: Maybe viewFormat: KeyValueFormat + compressor: Nullable } addKey: { loading: boolean diff --git a/redisinsight/ui/src/slices/tests/browser/keys.spec.ts b/redisinsight/ui/src/slices/tests/browser/keys.spec.ts index aa03b88809..5ec97f335a 100644 --- a/redisinsight/ui/src/slices/tests/browser/keys.spec.ts +++ b/redisinsight/ui/src/slices/tests/browser/keys.spec.ts @@ -1,6 +1,6 @@ import { cloneDeep } from 'lodash' import { AxiosError } from 'axios' -import { KeyTypes, KeyValueFormat } from 'uiSrc/constants' +import { KeyTypes, KeyValueCompressor, KeyValueFormat } from 'uiSrc/constants' import { apiService } from 'uiSrc/services' import { parseKeysListResponse, stringToBuffer, UTF8ToBuffer } from 'uiSrc/utils' import { cleanup, initialStateDefault, mockedStore } from 'uiSrc/utils/test-utils' @@ -68,6 +68,7 @@ import reducer, { resetAddKey, resetKeyInfo, resetKeys, + setCompressor, setLastBatchPatternKeys, updateSelectedKeyRefreshTime, } from '../../browser/keys' @@ -990,6 +991,29 @@ describe('keys slice', () => { }) }) + describe('setCompressor', () => { + it('should properly set state', () => { + // Arrange + const data: KeyValueCompressor = KeyValueCompressor.GZIP + const state = { + ...initialState, + selectedKey: { + ...initialState.selectedKey, + compressor: data, + } + } + + // Act + const nextState = reducer(state, setCompressor(data)) + + // Assert + const rootState = Object.assign(initialStateDefault, { + browser: { keys: nextState }, + }) + expect(keysSelector(rootState)).toEqual(state) + }) + }) + describe('thunks', () => { describe('fetchKeys', () => { it('call both loadKeys and loadKeysSuccess when fetch is successed', async () => { diff --git a/redisinsight/ui/src/utils/decompressors/decompressors.ts b/redisinsight/ui/src/utils/decompressors/decompressors.ts new file mode 100644 index 0000000000..14a76fa98d --- /dev/null +++ b/redisinsight/ui/src/utils/decompressors/decompressors.ts @@ -0,0 +1,47 @@ +import * as fflate from 'fflate' +import { COMPRESSOR_MAGIC_SYMBOLS, KeyValueCompressor } from 'uiSrc/constants' +import { RedisResponseBuffer, RedisString } from 'uiSrc/slices/interfaces' +import { anyToBuffer } from '../formatters' +import { Nullable } from '../types' + +const decompressingBuffer = ( + reply: RedisResponseBuffer, +): { value: RedisString, compressor: Nullable } => { + const compressor = getCompressor(reply) + + switch (compressor) { + case KeyValueCompressor.GZIP: { + const value = fflate.gunzipSync(Buffer.from(reply)) + + return { + value: anyToBuffer(Array.from((value))), + compressor: KeyValueCompressor.GZIP, + } + } + case KeyValueCompressor.PHPGZCompress: { + return { value: reply, compressor: KeyValueCompressor.PHPGZCompress } + } + default: { + return { value: reply, compressor: null } + } + } +} + +const getCompressor = (reply: RedisResponseBuffer): Nullable => { + const firstThree = reply.data?.slice?.(0, 3) ?? [] + + // GZIP + const gzipSymbols = COMPRESSOR_MAGIC_SYMBOLS[KeyValueCompressor.GZIP] + const isGZIP = firstThree?.[0] === gzipSymbols?.[0] && firstThree?.[1] === gzipSymbols?.[1] + + if (isGZIP) { + return KeyValueCompressor.GZIP + } + + return null +} + +export { + getCompressor, + decompressingBuffer, +} diff --git a/redisinsight/ui/src/utils/decompressors/index.ts b/redisinsight/ui/src/utils/decompressors/index.ts new file mode 100644 index 0000000000..a2a4b6423b --- /dev/null +++ b/redisinsight/ui/src/utils/decompressors/index.ts @@ -0,0 +1 @@ +export * from './decompressors' diff --git a/redisinsight/ui/src/utils/formatters/bufferFormatters.ts b/redisinsight/ui/src/utils/formatters/bufferFormatters.ts index 218f3eb8ec..1431174378 100644 --- a/redisinsight/ui/src/utils/formatters/bufferFormatters.ts +++ b/redisinsight/ui/src/utils/formatters/bufferFormatters.ts @@ -1,7 +1,7 @@ import { isString } from 'lodash' import { ObjectInputStream } from 'java-object-serialization' -import { KeyValueFormat } from 'uiSrc/constants' import { Buffer } from 'buffer' +import { KeyValueFormat } from 'uiSrc/constants' // eslint-disable-next-line import/order import { RedisResponseBuffer, diff --git a/redisinsight/ui/src/utils/tests/decompressors/decompressors.spec.ts b/redisinsight/ui/src/utils/tests/decompressors/decompressors.spec.ts new file mode 100644 index 0000000000..316256607d --- /dev/null +++ b/redisinsight/ui/src/utils/tests/decompressors/decompressors.spec.ts @@ -0,0 +1,48 @@ +import { KeyValueCompressor } from 'uiSrc/constants' +import { decompressingBuffer, getCompressor } from 'uiSrc/utils/decompressors' +import { anyToBuffer, stringToBuffer } from 'uiSrc/utils/formatters' + +export const GZIP_COMPRESSED_VALUE_1 = [ + 31, 139, 8, 0, 223, 246, 236, 99, 0, 3, 1, 1, 0, 254, 255, 49, 183, 239, 220, 131, 1, 0, 0, 0 +] +export const GZIP_COMPRESSED_VALUE_2 = [ + 31, 139, 8, 0, 180, 246, 236, 99, 0, 3, 1, 1, 0, 254, 255, 50, 13, 190, 213, 26, 1, 0, 0, 0 +] +export const GZIP_DECOMPRESSED_VALUE_1 = '1' +export const GZIP_DECOMPRESSED_VALUE_2 = '2' + +const defaultValues = [ + { input: [49], compressor: null, output: '1' }, + { input: [49, 50], compressor: null, output: '12' }, + { + input: GZIP_COMPRESSED_VALUE_1, + compressor: KeyValueCompressor.GZIP, + output: GZIP_DECOMPRESSED_VALUE_1, + }, + { + input: GZIP_COMPRESSED_VALUE_2, + compressor: KeyValueCompressor.GZIP, + output: GZIP_DECOMPRESSED_VALUE_2, + }, +].map((value) => ({ + ...value, + input: anyToBuffer(value.input) +})) + +describe('getCompressor', () => { + test.each(defaultValues)('%j', ({ input, compressor: expected }) => { + const result = getCompressor(input) + expect(result).toEqual(expected) + }) +}) + +describe('decompressingBuffer', () => { + test.each(defaultValues)('%j', ({ input, compressor, output }) => { + const result = decompressingBuffer(input) + + const expectedValue = stringToBuffer(output) + expectedValue.data = Array.from(expectedValue.data) + + expect(result).toEqual({ value: expectedValue, compressor }) + }) +}) diff --git a/yarn.lock b/yarn.lock index 0fc6846f21..2c02941af2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7566,6 +7566,11 @@ fengari@^0.1.4: sprintf-js "^1.1.1" tmp "^0.0.33" +fflate@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50" + integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw== + figures@^3.0.0, figures@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" From 30c6813621c92b42a2e1d5994795ff7b1f824329 Mon Sep 17 00:00:00 2001 From: zalenskiSofteq Date: Thu, 16 Feb 2023 13:26:09 +0800 Subject: [PATCH 2/2] #RI-4142 - added more unit tests --- .../QueryCardHeader/QueryCardHeader.spec.tsx | 2 +- .../hash-details/HashDetails.spec.tsx | 61 ++++++++++++++----- .../components/hash-details/HashDetails.tsx | 2 +- .../key-details-header/KeyDetailsHeader.tsx | 1 + .../list-details/ListDetails.spec.tsx | 55 +++++++++++++---- .../components/list-details/ListDetails.tsx | 2 +- .../set-details/SetDetails.spec.tsx | 28 +++++---- .../StreamDetailsWrapper.spec.tsx | 54 ++++++++-------- .../string-details/StringDetails.spec.tsx | 60 +++++++++--------- .../zset-details/ZSetDetails.spec.tsx | 28 +++++---- 10 files changed, 180 insertions(+), 113 deletions(-) diff --git a/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.spec.tsx b/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.spec.tsx index b480462805..7feddf9367 100644 --- a/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.spec.tsx +++ b/redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.spec.tsx @@ -2,9 +2,9 @@ import { cloneDeep } from 'lodash' import React from 'react' import { instance, mock } from 'ts-mockito' import { cleanup, mockedStore, render, fireEvent, act, screen, waitForEuiToolTipVisible } from 'uiSrc/utils/test-utils' -import QueryCardHeader, { Props } from './QueryCardHeader' import { TelemetryEvent, sendEventTelemetry } from 'uiSrc/telemetry' import { INSTANCE_ID_MOCK } from 'uiSrc/mocks/handlers/instances/instancesHandlers' +import QueryCardHeader, { Props } from './QueryCardHeader' const mockedProps = mock() diff --git a/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.spec.tsx b/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.spec.tsx index bca7815113..e53d41a454 100644 --- a/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.spec.tsx +++ b/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.spec.tsx @@ -1,9 +1,10 @@ import React from 'react' import { instance, mock } from 'ts-mockito' +import { TEXT_DISABLED_COMPRESSED_VALUE } from 'uiSrc/constants' import { hashDataSelector } from 'uiSrc/slices/browser/hash' import { RedisResponseBufferType } from 'uiSrc/slices/interfaces' import { anyToBuffer, bufferToString } from 'uiSrc/utils' -import { fireEvent, render, screen } from 'uiSrc/utils/test-utils' +import { act, fireEvent, render, screen, waitForEuiToolTipVisible } from 'uiSrc/utils/test-utils' import { GZIP_COMPRESSED_VALUE_1, GZIP_COMPRESSED_VALUE_2, GZIP_DECOMPRESSED_VALUE_1, GZIP_DECOMPRESSED_VALUE_2 } from 'uiSrc/utils/tests/decompressors/decompressors.spec' import HashDetails, { Props } from './HashDetails' @@ -73,23 +74,51 @@ describe('HashDetails', () => { expect(screen.getByTestId('resize-trigger-field')).toBeInTheDocument() }) - it('should render decompressed GZIP data', () => { - const defaultState = jest.requireActual('uiSrc/slices/browser/hash').initialState - const hashDataSelectorMock = jest.fn().mockReturnValue({ - ...defaultState, - total: 1, - key: '123zxczxczxc', - fields: [ - { field: anyToBuffer(GZIP_COMPRESSED_VALUE_1), value: anyToBuffer(GZIP_COMPRESSED_VALUE_2) }, - ] + describe('decompressed data', () => { + it('should render decompressed GZIP data', () => { + const defaultState = jest.requireActual('uiSrc/slices/browser/hash').initialState + const hashDataSelectorMock = jest.fn().mockReturnValue({ + ...defaultState, + total: 1, + key: '123zxczxczxc', + fields: [ + { field: anyToBuffer(GZIP_COMPRESSED_VALUE_1), value: anyToBuffer(GZIP_COMPRESSED_VALUE_2) }, + ] + }) + hashDataSelector.mockImplementation(hashDataSelectorMock) + + const { queryByTestId, queryAllByTestId } = render() + const fieldEl = queryAllByTestId(/hash-field-/)?.[0] + const valueEl = queryByTestId(/hash-field-value/) + + expect(fieldEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) + expect(valueEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_2) }) - hashDataSelector.mockImplementation(hashDataSelectorMock) - const { queryByTestId, queryAllByTestId } = render() - const fieldEl = queryAllByTestId(/hash-field-/)?.[0] - const valueEl = queryByTestId(/hash-field-value/) + it('edit button should be disabled if data was compressed', async () => { + const defaultState = jest.requireActual('uiSrc/slices/browser/hash').initialState + const hashDataSelectorMock = jest.fn().mockReturnValue({ + ...defaultState, + total: 1, + key: '123zxczxczxc', + fields: [ + { field: anyToBuffer(GZIP_COMPRESSED_VALUE_1), value: anyToBuffer(GZIP_COMPRESSED_VALUE_2) }, + ] + }) + hashDataSelector.mockImplementation(hashDataSelectorMock) + const { queryByTestId } = render() + const editBtn = queryByTestId(/edit-hash-button/) - expect(fieldEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) - expect(valueEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_2) + fireEvent.click(editBtn) + + await act(async () => { + fireEvent.mouseOver(editBtn) + }) + await waitForEuiToolTipVisible() + + expect(editBtn).toBeDisabled() + expect(screen.getByTestId('hash-edit-tooltip')).toHaveTextContent(TEXT_DISABLED_COMPRESSED_VALUE) + expect(queryByTestId('hash-value-editor')).not.toBeInTheDocument() + }) }) }) diff --git a/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.tsx b/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.tsx index 482556f95c..a9133761ee 100644 --- a/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.tsx +++ b/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.tsx @@ -441,7 +441,7 @@ const HashDetails = (props: Props) => { return (
- + { expect(screen.getByTestId('resize-trigger-index')).toBeInTheDocument() }) - it('should render decompressed GZIP data = "1"', () => { - const defaultState = jest.requireActual('uiSrc/slices/browser/list').initialState - const listDataSelectorMock = jest.fn().mockReturnValue({ - ...defaultState, - key: '123zxczxczxc', - elements: [ - { element: anyToBuffer(GZIP_COMPRESSED_VALUE_1), index: 0 }, - ] + describe('decompressed data', () => { + it('should render decompressed GZIP data = "1"', () => { + const defaultState = jest.requireActual('uiSrc/slices/browser/list').initialState + const listDataSelectorMock = jest.fn().mockReturnValue({ + ...defaultState, + key: '123zxczxczxc', + elements: [ + { element: anyToBuffer(GZIP_COMPRESSED_VALUE_1), index: 0 }, + ] + }) + listDataSelector.mockImplementation(listDataSelectorMock) + + const { queryByTestId } = render() + const elementEl = queryByTestId(/list-element-value-/) + + expect(elementEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) }) - listDataSelector.mockImplementation(listDataSelectorMock) - const { queryByTestId } = render() - const elementEl = queryByTestId(/list-element-value-/) + it('edit button should be disabled if data was compressed', async () => { + const defaultState = jest.requireActual('uiSrc/slices/browser/list').initialState + const listDataSelectorMock = jest.fn().mockReturnValue({ + ...defaultState, + key: '123zxczxczxc', + elements: [ + { element: anyToBuffer(GZIP_COMPRESSED_VALUE_1), index: 0 }, + ] + }) + listDataSelector.mockImplementation(listDataSelectorMock) + + const { queryByTestId } = render() + const editBtn = queryByTestId(/edit-list-button-/) - expect(elementEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) + fireEvent.click(editBtn) + + await act(async () => { + fireEvent.mouseOver(editBtn) + }) + await waitForEuiToolTipVisible() + + expect(editBtn).toBeDisabled() + expect(screen.getByTestId('list-edit-tooltip')).toHaveTextContent(TEXT_DISABLED_COMPRESSED_VALUE) + expect(queryByTestId('list-value-editor')).not.toBeInTheDocument() + }) }) }) diff --git a/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.tsx b/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.tsx index 1dbd11ec7b..a906563b89 100644 --- a/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.tsx +++ b/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.tsx @@ -385,7 +385,7 @@ const ListDetails = (props: Props) => { return (
- + { expect(screen.getByTestId(/set-remove-btn-1-icon/)).toBeInTheDocument() }) - it('should render decompressed GZIP data = "1"', () => { - const defaultState = jest.requireActual('uiSrc/slices/browser/set').initialState - const setDataSelectorMock = jest.fn().mockReturnValue({ - ...defaultState, - key: '123zxczxczxc', - members: [ - anyToBuffer(GZIP_COMPRESSED_VALUE_1), - ] - }) - setDataSelector.mockImplementation(setDataSelectorMock) + describe('decompressed data', () => { + it('should render decompressed GZIP data = "1"', () => { + const defaultState = jest.requireActual('uiSrc/slices/browser/set').initialState + const setDataSelectorMock = jest.fn().mockReturnValue({ + ...defaultState, + key: '123zxczxczxc', + members: [ + anyToBuffer(GZIP_COMPRESSED_VALUE_1), + ] + }) + setDataSelector.mockImplementation(setDataSelectorMock) - const { queryByTestId } = render() - const memberEl = queryByTestId(/set-member-value-/) + const { queryByTestId } = render() + const memberEl = queryByTestId(/set-member-value-/) - expect(memberEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) + expect(memberEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) + }) }) }) diff --git a/redisinsight/ui/src/pages/browser/components/stream-details/StreamDetailsWrapper.spec.tsx b/redisinsight/ui/src/pages/browser/components/stream-details/StreamDetailsWrapper.spec.tsx index d6902093a5..3f4414b673 100644 --- a/redisinsight/ui/src/pages/browser/components/stream-details/StreamDetailsWrapper.spec.tsx +++ b/redisinsight/ui/src/pages/browser/components/stream-details/StreamDetailsWrapper.spec.tsx @@ -110,31 +110,33 @@ describe('StreamDetailsWrapper', () => { expect(queryByTestId('range-bar')).not.toBeInTheDocument() }) - it('should render decompressed GZIP data', () => { - const mockId = '1232-123123123' - const entryWithCompressedGZIPData = { - id: mockId, - fields: [{ - name: anyToBuffer(GZIP_COMPRESSED_VALUE_1), - value: anyToBuffer(GZIP_COMPRESSED_VALUE_2), - }] - } - - streamDataSelector.mockImplementation(() => ({ - ...mockedEntryData, - firstEntry: entryWithCompressedGZIPData, - lastEntry: entryWithCompressedGZIPData, - entries: [ - entryWithCompressedGZIPData - ], - })) - - const { queryAllByTestId } = render() - - const fieldNameEl = queryAllByTestId(/stream-field-name-/)?.[0] - const entryFieldEl = queryAllByTestId(/stream-entry-field-/)?.[0] - - expect(fieldNameEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) - expect(entryFieldEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_2) + describe('decompressed data', () => { + it('should render decompressed GZIP data', () => { + const mockId = '1232-123123123' + const entryWithCompressedGZIPData = { + id: mockId, + fields: [{ + name: anyToBuffer(GZIP_COMPRESSED_VALUE_1), + value: anyToBuffer(GZIP_COMPRESSED_VALUE_2), + }] + } + + streamDataSelector.mockImplementation(() => ({ + ...mockedEntryData, + firstEntry: entryWithCompressedGZIPData, + lastEntry: entryWithCompressedGZIPData, + entries: [ + entryWithCompressedGZIPData + ], + })) + + const { queryAllByTestId } = render() + + const fieldNameEl = queryAllByTestId(/stream-field-name-/)?.[0] + const entryFieldEl = queryAllByTestId(/stream-entry-field-/)?.[0] + + expect(fieldNameEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) + expect(entryFieldEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_2) + }) }) }) diff --git a/redisinsight/ui/src/pages/browser/components/string-details/StringDetails.spec.tsx b/redisinsight/ui/src/pages/browser/components/string-details/StringDetails.spec.tsx index a2abcc1310..b7f44e3195 100644 --- a/redisinsight/ui/src/pages/browser/components/string-details/StringDetails.spec.tsx +++ b/redisinsight/ui/src/pages/browser/components/string-details/StringDetails.spec.tsx @@ -102,39 +102,41 @@ describe('StringDetails', () => { expect(textArea).toHaveValue(STRING_VALUE_SPACE) }) - it('should render decompressed GZIP data = "1"', () => { - const stringDataSelectorMock = jest.fn().mockReturnValue({ - value: anyToBuffer(GZIP_COMPRESSED_VALUE_1) - }) - stringDataSelector.mockImplementation(stringDataSelectorMock) - - render( - - ) - const textArea = screen.getByTestId(STRING_VALUE) + describe('decompressed data', () => { + it('should render decompressed GZIP data = "1"', () => { + const stringDataSelectorMock = jest.fn().mockReturnValue({ + value: anyToBuffer(GZIP_COMPRESSED_VALUE_1) + }) + stringDataSelector.mockImplementation(stringDataSelectorMock) - expect(textArea).toHaveValue(GZIP_DECOMPRESSED_VALUE_1) - }) + render( + + ) + const textArea = screen.getByTestId(STRING_VALUE) - it('should render decompressed GZIP data = "2"', () => { - const stringDataSelectorMock = jest.fn().mockReturnValue({ - value: anyToBuffer(GZIP_COMPRESSED_VALUE_2) + expect(textArea).toHaveValue(GZIP_DECOMPRESSED_VALUE_1) }) - stringDataSelector.mockImplementation(stringDataSelectorMock) - render( - - ) - const textArea = screen.getByTestId(STRING_VALUE) + it('should render decompressed GZIP data = "2"', () => { + const stringDataSelectorMock = jest.fn().mockReturnValue({ + value: anyToBuffer(GZIP_COMPRESSED_VALUE_2) + }) + stringDataSelector.mockImplementation(stringDataSelectorMock) - expect(textArea).toHaveValue(GZIP_DECOMPRESSED_VALUE_2) + render( + + ) + const textArea = screen.getByTestId(STRING_VALUE) + + expect(textArea).toHaveValue(GZIP_DECOMPRESSED_VALUE_2) + }) }) }) diff --git a/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.spec.tsx b/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.spec.tsx index a24cf24d38..3f2d7ab0c1 100644 --- a/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.spec.tsx +++ b/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.spec.tsx @@ -79,20 +79,22 @@ describe('ZSetDetails', () => { expect(screen.getByTestId('resize-trigger-name')).toBeInTheDocument() }) - it('should render decompressed GZIP data = "1"', () => { - const defaultState = jest.requireActual('uiSrc/slices/browser/zset').initialState - const zsetDataSelectorMock = jest.fn().mockReturnValue({ - ...defaultState, - key: '123zxczxczxc', - members: [ - { name: anyToBuffer(GZIP_COMPRESSED_VALUE_1), score: 1 }, - ] - }) - zsetDataSelector.mockImplementation(zsetDataSelectorMock) + describe('decompressed data', () => { + it('should render decompressed GZIP data = "1"', () => { + const defaultState = jest.requireActual('uiSrc/slices/browser/zset').initialState + const zsetDataSelectorMock = jest.fn().mockReturnValue({ + ...defaultState, + key: '123zxczxczxc', + members: [ + { name: anyToBuffer(GZIP_COMPRESSED_VALUE_1), score: 1 }, + ] + }) + zsetDataSelector.mockImplementation(zsetDataSelectorMock) - const { queryByTestId } = render() - const memberEl = queryByTestId(/zset-member-value-/) + const { queryByTestId } = render() + const memberEl = queryByTestId(/zset-member-value-/) - expect(memberEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) + expect(memberEl).toHaveTextContent(GZIP_DECOMPRESSED_VALUE_1) + }) }) })