Skip to content

Commit 7a32f67

Browse files
committed
extract logic in hook
1 parent 6c0265d commit 7a32f67

File tree

5 files changed

+190
-24
lines changed

5 files changed

+190
-24
lines changed

redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/ConfigurationCard.spec.tsx

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ import ConfigurationCard, { ConfigurationCardProps } from './ConfigurationCard'
77

88
const mockedProps = mock<ConfigurationCardProps>()
99

10-
const mockUseSelector = jest.fn()
11-
jest.mock('react-redux', () => ({
12-
...jest.requireActual('react-redux'),
13-
useSelector: () => mockUseSelector(),
10+
const mockUseConfigurationState = jest.fn()
11+
jest.mock('./hooks/useConfigurationState', () => ({
12+
useConfigurationState: () => mockUseConfigurationState(),
1413
}))
1514

1615
describe('ConfigurationCard', () => {
1716
beforeEach(() => {
18-
mockUseSelector.mockReturnValue({
19-
changes: {},
17+
mockUseConfigurationState.mockReturnValue({
18+
hasChanges: false,
19+
isValid: true,
2020
configValidationErrors: [],
2121
})
2222
})
@@ -65,8 +65,9 @@ describe('ConfigurationCard', () => {
6565

6666
describe('Changes indicator', () => {
6767
it('should not show changes indicator when no changes', () => {
68-
mockUseSelector.mockReturnValue({
69-
changes: {},
68+
mockUseConfigurationState.mockReturnValue({
69+
hasChanges: false,
70+
isValid: true,
7071
configValidationErrors: [],
7172
})
7273

@@ -78,8 +79,9 @@ describe('ConfigurationCard', () => {
7879
})
7980

8081
it('should show changes indicator when config has changes', () => {
81-
mockUseSelector.mockReturnValue({
82-
changes: { config: 'modified' },
82+
mockUseConfigurationState.mockReturnValue({
83+
hasChanges: true,
84+
isValid: true,
8385
configValidationErrors: [],
8486
})
8587

@@ -93,8 +95,9 @@ describe('ConfigurationCard', () => {
9395

9496
describe('Validation errors', () => {
9597
it('should not show error icon when config is valid', () => {
96-
mockUseSelector.mockReturnValue({
97-
changes: {},
98+
mockUseConfigurationState.mockReturnValue({
99+
hasChanges: false,
100+
isValid: true,
98101
configValidationErrors: [],
99102
})
100103

@@ -106,8 +109,9 @@ describe('ConfigurationCard', () => {
106109
})
107110

108111
it('should show error icon when config has validation errors', () => {
109-
mockUseSelector.mockReturnValue({
110-
changes: {},
112+
mockUseConfigurationState.mockReturnValue({
113+
hasChanges: false,
114+
isValid: false,
111115
configValidationErrors: [
112116
'Invalid configuration',
113117
'Missing required field',
@@ -122,8 +126,9 @@ describe('ConfigurationCard', () => {
122126
})
123127

124128
it('should handle single validation error', () => {
125-
mockUseSelector.mockReturnValue({
126-
changes: {},
129+
mockUseConfigurationState.mockReturnValue({
130+
hasChanges: false,
131+
isValid: false,
127132
configValidationErrors: ['Single error'],
128133
})
129134

@@ -136,8 +141,9 @@ describe('ConfigurationCard', () => {
136141
})
137142

138143
it('should show both changes indicator and error icon when config has changes and errors', () => {
139-
mockUseSelector.mockReturnValue({
140-
changes: { config: 'modified' },
144+
mockUseConfigurationState.mockReturnValue({
145+
hasChanges: true,
146+
isValid: false,
141147
configValidationErrors: ['Invalid configuration'],
142148
})
143149

redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/ConfigurationCard.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import React from 'react'
22
import { RdiPipelineTabs } from 'uiSrc/slices/interfaces'
3-
import { useSelector } from 'react-redux'
43

5-
import { rdiPipelineSelector } from 'uiSrc/slices/rdi/pipeline'
64
import { RiTooltip } from 'uiSrc/components'
75
import { Indicator } from 'uiSrc/components/base/text/text.styles'
86
import { Row } from 'uiSrc/components/base/layout/flex'
97
import { Text } from 'uiSrc/components/base/text'
108
import { Icon, ToastNotificationIcon } from 'uiSrc/components/base/icons'
9+
import { useConfigurationState } from './hooks'
1110

1211
import BaseCard, { BaseCardProps } from './BaseCard'
1312
import ValidationErrorsList from '../../validation-errors-list/ValidationErrorsList'
@@ -23,15 +22,13 @@ const ConfigurationCard = ({
2322
onSelect,
2423
isSelected,
2524
}: ConfigurationCardProps) => {
26-
const { changes, configValidationErrors } = useSelector(rdiPipelineSelector)
25+
const { hasChanges, isValid, configValidationErrors } =
26+
useConfigurationState()
2727

2828
const handleClick = () => {
2929
onSelect(RdiPipelineTabs.Config)
3030
}
3131

32-
const hasChanges = !!changes.config
33-
const isValid = configValidationErrors.length === 0
34-
3532
return (
3633
<BaseCard
3734
title="Configuration"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useConfigurationState'
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { renderHook } from 'uiSrc/utils/test-utils'
2+
import { rdiPipelineSelector } from 'uiSrc/slices/rdi/pipeline'
3+
import { useConfigurationState } from './useConfigurationState'
4+
5+
jest.mock('uiSrc/slices/rdi/pipeline', () => ({
6+
...jest.requireActual('uiSrc/slices/rdi/pipeline'),
7+
rdiPipelineSelector: jest.fn(),
8+
}))
9+
10+
const mockRdiPipelineSelector = rdiPipelineSelector as jest.MockedFunction<
11+
typeof rdiPipelineSelector
12+
>
13+
14+
describe('useConfigurationState', () => {
15+
beforeEach(() => {
16+
jest.clearAllMocks()
17+
})
18+
19+
it('should return correct state when no changes and no validation errors', () => {
20+
mockRdiPipelineSelector.mockReturnValue({
21+
changes: {},
22+
configValidationErrors: [],
23+
} as any)
24+
25+
const { result } = renderHook(() => useConfigurationState())
26+
27+
expect(result.current).toEqual({
28+
hasChanges: false,
29+
isValid: true,
30+
configValidationErrors: [],
31+
})
32+
})
33+
34+
it('should return hasChanges as true when config has changes', () => {
35+
mockRdiPipelineSelector.mockReturnValue({
36+
changes: { config: 'modified' },
37+
configValidationErrors: [],
38+
} as any)
39+
40+
const { result } = renderHook(() => useConfigurationState())
41+
42+
expect(result.current).toEqual({
43+
hasChanges: true,
44+
isValid: true,
45+
configValidationErrors: [],
46+
})
47+
})
48+
49+
it('should return isValid as false when config has validation errors', () => {
50+
mockRdiPipelineSelector.mockReturnValue({
51+
changes: {},
52+
configValidationErrors: [
53+
'Invalid configuration',
54+
'Missing required field',
55+
],
56+
} as any)
57+
58+
const { result } = renderHook(() => useConfigurationState())
59+
60+
expect(result.current).toEqual({
61+
hasChanges: false,
62+
isValid: false,
63+
configValidationErrors: [
64+
'Invalid configuration',
65+
'Missing required field',
66+
],
67+
})
68+
})
69+
70+
it('should handle both changes and validation errors', () => {
71+
mockRdiPipelineSelector.mockReturnValue({
72+
changes: { config: 'added' },
73+
configValidationErrors: ['Configuration error'],
74+
} as any)
75+
76+
const { result } = renderHook(() => useConfigurationState())
77+
78+
expect(result.current).toEqual({
79+
hasChanges: true,
80+
isValid: false,
81+
configValidationErrors: ['Configuration error'],
82+
})
83+
})
84+
85+
it('should handle empty validation errors array', () => {
86+
mockRdiPipelineSelector.mockReturnValue({
87+
changes: {},
88+
configValidationErrors: [],
89+
} as any)
90+
91+
const { result } = renderHook(() => useConfigurationState())
92+
93+
expect(result.current.isValid).toBe(true)
94+
})
95+
96+
it('should handle single validation error', () => {
97+
mockRdiPipelineSelector.mockReturnValue({
98+
changes: {},
99+
configValidationErrors: ['Single error'],
100+
} as any)
101+
102+
const { result } = renderHook(() => useConfigurationState())
103+
104+
expect(result.current).toEqual({
105+
hasChanges: false,
106+
isValid: false,
107+
configValidationErrors: ['Single error'],
108+
})
109+
})
110+
111+
it('should handle multiple validation errors', () => {
112+
const errors = ['Error 1', 'Error 2', 'Error 3']
113+
mockRdiPipelineSelector.mockReturnValue({
114+
changes: {},
115+
configValidationErrors: errors,
116+
} as any)
117+
118+
const { result } = renderHook(() => useConfigurationState())
119+
120+
expect(result.current).toEqual({
121+
hasChanges: false,
122+
isValid: false,
123+
configValidationErrors: errors,
124+
})
125+
})
126+
127+
it('should handle changes in other files without affecting config state', () => {
128+
mockRdiPipelineSelector.mockReturnValue({
129+
changes: {
130+
job1: 'modified',
131+
job2: 'added',
132+
// no config changes
133+
},
134+
configValidationErrors: [],
135+
} as any)
136+
137+
const { result } = renderHook(() => useConfigurationState())
138+
139+
expect(result.current.hasChanges).toBe(false)
140+
})
141+
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useSelector } from 'react-redux'
2+
import { rdiPipelineSelector } from 'uiSrc/slices/rdi/pipeline'
3+
4+
export interface ConfigurationState {
5+
hasChanges: boolean
6+
isValid: boolean
7+
configValidationErrors: string[]
8+
}
9+
10+
export const useConfigurationState = (): ConfigurationState => {
11+
const { changes, configValidationErrors } = useSelector(rdiPipelineSelector)
12+
13+
const hasChanges = !!changes.config
14+
const isValid = configValidationErrors.length === 0
15+
16+
return {
17+
hasChanges,
18+
isValid,
19+
configValidationErrors,
20+
}
21+
}

0 commit comments

Comments
 (0)