1- import { describe , it , expect , vi , beforeEach } from 'vitest'
1+ import { beforeEach , describe , expect , it , vi } from 'vitest'
22import { render , screen } from '@testing-library/react'
33import { StorageOverview } from '../StorageOverview'
44
5- // āā Mocks āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
5+ vi . mock ( 'react-i18next' , ( ) => ( {
6+ initReactI18next : { type : '3rdParty' , init : ( ) => { } } ,
7+ useTranslation : ( ) => ( {
8+ t : ( key : string , opts ?: Record < string , unknown > ) => {
9+ if ( opts && 'count' in opts ) return `${ opts . count } `
10+ if ( opts && 'pvcs' in opts && 'clusters' in opts ) return `${ opts . pvcs } PVCs, ${ opts . clusters } clusters`
11+ if ( opts && 'error' in opts ) return `Failed: ${ opts . error } `
12+ return key
13+ } ,
14+ } ) ,
15+ } ) )
616
17+ const mockUseClusters = vi . fn ( )
718vi . mock ( '../../../hooks/useMCP' , ( ) => ( {
8- useClusters : ( ) => ( {
9- deduplicatedClusters : [ { name : 'cluster-1' , storageGB : 100 , nodeCount : 3 , reachable : true } ] ,
10- isLoading : false ,
11- isRefreshing : false ,
12- } ) ,
19+ useClusters : ( ) => mockUseClusters ( ) ,
1320} ) )
1421
22+ const mockUseCachedPVCs = vi . fn ( )
1523vi . mock ( '../../../hooks/useCachedData' , ( ) => ( {
16- useCachedPVCs : vi . fn ( ( ) => ( {
17- pvcs : [ ] ,
18- isLoading : false ,
19- isRefreshing : false ,
20- isDemoFallback : false ,
21- isFailed : false ,
22- consecutiveFailures : 0 ,
23- } ) ) ,
24+ useCachedPVCs : ( ) => mockUseCachedPVCs ( ) ,
2425} ) )
2526
27+ const mockUseGlobalFilters = vi . fn ( )
2628vi . mock ( '../../../hooks/useGlobalFilters' , ( ) => ( {
27- useGlobalFilters : ( ) => ( { selectedClusters : [ ] , isAllClustersSelected : true } ) ,
28- } ) )
29-
30- vi . mock ( '../../../hooks/useDrillDown' , ( ) => ( {
31- useDrillDownActions : ( ) => ( { } ) ,
32- } ) )
33-
34- vi . mock ( '../CardDataContext' , ( ) => ( {
35- useCardLoadingState : vi . fn ( ( ) => ( { showSkeleton : false , showEmptyState : false } ) ) ,
29+ useGlobalFilters : ( ) => mockUseGlobalFilters ( ) ,
3630} ) )
3731
32+ const mockUseDemoMode = vi . fn ( )
3833vi . mock ( '../../../hooks/useDemoMode' , ( ) => ( {
39- useDemoMode : ( ) => ( { isDemoMode : false } ) ,
40- getDemoMode : ( ) => false , default : ( ) => false ,
41- hasRealToken : ( ) => false , isDemoModeForced : false , isNetlifyDeployment : false ,
42- canToggleDemoMode : ( ) => true , isDemoToken : ( ) => true , setDemoToken : vi . fn ( ) ,
43- setGlobalDemoMode : vi . fn ( ) ,
34+ useDemoMode : ( ) => mockUseDemoMode ( ) ,
4435} ) )
4536
46- vi . mock ( 'react-i18next' , ( ) => ( {
47- initReactI18next : { type : '3rdParty' , init : ( ) => { } } ,
48- useTranslation : ( ) => ( {
49- t : ( k : string , opts ?: Record < string , unknown > ) => {
50- if ( opts ?. count !== undefined ) return `${ k } :${ opts . count } `
51- return k
52- } ,
53- } ) ,
37+ const mockUseCardLoadingState = vi . fn ( )
38+ vi . mock ( '../CardDataContext' , ( ) => ( {
39+ useCardLoadingState : ( args : Record < string , unknown > ) => mockUseCardLoadingState ( args ) ,
5440} ) )
5541
42+ const mockUseChartFilters = vi . fn ( )
5643vi . mock ( '../../../lib/cards/cardHooks' , ( ) => ( {
57- useChartFilters : ( ) => ( {
58- localClusterFilter : [ ] ,
59- toggleClusterFilter : vi . fn ( ) ,
60- clearClusterFilter : vi . fn ( ) ,
61- availableClusters : [ { name : 'cluster-1' } ] ,
62- showClusterFilter : false ,
63- setShowClusterFilter : vi . fn ( ) ,
64- clusterFilterRef : { current : null } ,
65- } ) ,
44+ useChartFilters : ( ) => mockUseChartFilters ( ) ,
6645} ) )
6746
6847vi . mock ( '../../../lib/cards/CardComponents' , ( ) => ( {
69- CardClusterFilter : ( ) => < div data-testid = "cluster-filter" /> ,
48+ CardClusterFilter : ( { availableClusters } : { availableClusters : Array < { name : string } > } ) => (
49+ < div data-testid = "cluster-filter" data-count = { availableClusters . length } />
50+ ) ,
7051} ) )
7152
7253vi . mock ( '../../../lib/formatStats' , ( ) => ( {
73- formatStat : ( n : number ) => String ( n ) ,
74- formatStorageStat : ( n : number , real ?: boolean ) => ( real ? ` ${ n } GB` : 'N/A' ) ,
54+ formatStat : ( value : number ) => String ( value ) ,
55+ formatStorageStat : ( value : number , hasRealData ?: boolean ) => ( hasRealData === false ? 'N/A' : ` ${ value } GB` ) ,
7556} ) )
7657
77- // āā Tests āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
58+ vi . mock ( '../../ui/Skeleton' , ( ) => ( {
59+ Skeleton : ( { width, height } : { width ?: number ; height ?: number } ) => (
60+ < div data-testid = "skeleton" style = { { width, height } } />
61+ ) ,
62+ SkeletonStats : ( { className } : { className ?: string } ) => (
63+ < div data-testid = "skeleton-stats" className = { className } />
64+ ) ,
65+ SkeletonList : ( { items, className } : { items ?: number ; className ?: string } ) => (
66+ < div data-testid = "skeleton-list" data-items = { items } className = { className } />
67+ ) ,
68+ } ) )
69+
70+ type MockPVC = {
71+ status : string
72+ cluster : string
73+ namespace : string
74+ name : string
75+ storageClass : string
76+ }
77+
78+ const makePVC = (
79+ status : string ,
80+ cluster = 'cluster-1' ,
81+ storageClass = 'standard' ,
82+ name = `pvc-${ status . toLowerCase ( ) } `
83+ ) : MockPVC => ( {
84+ status,
85+ cluster,
86+ namespace : 'default' ,
87+ name,
88+ storageClass,
89+ } )
90+
91+ const defaultClustersReturn = {
92+ deduplicatedClusters : [ { name : 'cluster-1' , storageGB : 100 , nodeCount : 3 , reachable : true } ] ,
93+ isLoading : false ,
94+ isRefreshing : false ,
95+ }
96+
97+ const defaultPVCsReturn = {
98+ pvcs : [ makePVC ( 'Bound' ) , makePVC ( 'Pending' ) , makePVC ( 'Lost' ) ] ,
99+ isLoading : false ,
100+ isRefreshing : false ,
101+ isDemoFallback : false ,
102+ isFailed : false ,
103+ consecutiveFailures : 0 ,
104+ error : null ,
105+ }
106+
107+ const defaultGlobalFilters = {
108+ selectedClusters : [ ] ,
109+ isAllClustersSelected : true ,
110+ }
111+
112+ const defaultChartFilters = {
113+ localClusterFilter : [ ] ,
114+ toggleClusterFilter : vi . fn ( ) ,
115+ clearClusterFilter : vi . fn ( ) ,
116+ availableClusters : [ { name : 'cluster-1' } ] ,
117+ showClusterFilter : false ,
118+ setShowClusterFilter : vi . fn ( ) ,
119+ clusterFilterRef : { current : null } ,
120+ }
121+
122+ function setup ( ) : void {
123+ mockUseClusters . mockReturnValue ( defaultClustersReturn )
124+ mockUseCachedPVCs . mockReturnValue ( defaultPVCsReturn )
125+ mockUseGlobalFilters . mockReturnValue ( defaultGlobalFilters )
126+ mockUseDemoMode . mockReturnValue ( { isDemoMode : false } )
127+ mockUseChartFilters . mockReturnValue ( defaultChartFilters )
128+ mockUseCardLoadingState . mockReturnValue ( { showSkeleton : false , showEmptyState : false } )
129+ }
78130
79131describe ( 'StorageOverview' , ( ) => {
80- beforeEach ( async ( ) => {
132+ beforeEach ( ( ) => {
81133 vi . clearAllMocks ( )
82- const { useCardLoadingState } = await import ( '../CardDataContext' )
83- vi . mocked ( useCardLoadingState ) . mockReturnValue ( { showSkeleton : false , showEmptyState : false } as never )
134+ setup ( )
84135 } )
85136
86137 describe ( 'Skeleton / empty states' , ( ) => {
87- it ( 'renders loading spinner when showSkeleton' , async ( ) => {
88- const { useCardLoadingState } = await import ( '../CardDataContext' )
89- vi . mocked ( useCardLoadingState ) . mockReturnValue ( { showSkeleton : true , showEmptyState : false } as never )
138+ it ( 'renders loading spinner when showSkeleton' , ( ) => {
139+ mockUseCardLoadingState . mockReturnValue ( { showSkeleton : true , showEmptyState : false } )
140+
90141 render ( < StorageOverview /> )
91- expect ( screen . getByText ( 'storageOverview.loading' ) ) . toBeTruthy ( )
142+
143+ expect ( screen . getByText ( 'storageOverview.loading' ) ) . toBeInTheDocument ( )
144+ expect ( screen . getByTestId ( 'skeleton-stats' ) ) . toBeInTheDocument ( )
145+ expect ( screen . getByTestId ( 'skeleton-list' ) ) . toBeInTheDocument ( )
92146 } )
93147
94- it ( 'renders no data message when showEmptyState' , async ( ) => {
95- const { useCardLoadingState } = await import ( '../CardDataContext' )
96- vi . mocked ( useCardLoadingState ) . mockReturnValue ( { showSkeleton : false , showEmptyState : true } as never )
148+ it ( 'renders no data message when showEmptyState' , ( ) => {
149+ mockUseCardLoadingState . mockReturnValue ( { showSkeleton : false , showEmptyState : true } )
150+ mockUseCachedPVCs . mockReturnValue ( { ...defaultPVCsReturn , pvcs : [ ] } )
151+
97152 render ( < StorageOverview /> )
98- expect ( screen . getByText ( 'storageOverview.noData' ) ) . toBeTruthy ( )
153+
154+ expect ( screen . getByText ( 'storageOverview.noData' ) ) . toBeInTheDocument ( )
99155 } )
100156 } )
101157
102158 describe ( 'Main stats' , ( ) => {
103159 it ( 'renders total capacity and PVCs tiles' , ( ) => {
104160 render ( < StorageOverview /> )
105- expect ( screen . getByText ( 'storageOverview.totalCapacity' ) ) . toBeTruthy ( )
106- expect ( screen . getByText ( 'storageOverview.pvcs' ) ) . toBeTruthy ( )
161+
162+ expect ( screen . getByText ( 'storageOverview.totalCapacity' ) ) . toBeInTheDocument ( )
163+ expect ( screen . getByText ( 'storageOverview.pvcs' ) ) . toBeInTheDocument ( )
164+ expect ( screen . getByText ( '100GB' ) ) . toBeInTheDocument ( )
165+ expect ( screen . getAllByText ( '3' ) . length ) . toBeGreaterThan ( 0 )
107166 } )
108167
109168 it ( 'renders bound, pending, failed PVC breakdown' , ( ) => {
110169 render ( < StorageOverview /> )
111- expect ( screen . getByText ( 'storageOverview.bound' ) ) . toBeTruthy ( )
112- expect ( screen . getByText ( 'common:common.pending' ) ) . toBeTruthy ( )
113- expect ( screen . getByText ( 'common:common.failed' ) ) . toBeTruthy ( )
170+
171+ expect ( screen . getByText ( 'storageOverview.bound' ) ) . toBeInTheDocument ( )
172+ expect ( screen . getByText ( 'common:common.pending' ) ) . toBeInTheDocument ( )
173+ expect ( screen . getByText ( 'common:common.failed' ) ) . toBeInTheDocument ( )
114174 } )
115175 } )
116176
117177 describe ( 'PVC counts' , ( ) => {
118- it ( 'counts bound/pending/failed PVCs correctly' , async ( ) => {
119- const { useCachedPVCs } = await import ( '../../../hooks/useCachedData' )
120- vi . mocked ( useCachedPVCs ) . mockReturnValue ( {
178+ it ( 'counts bound/pending/failed PVCs correctly' , ( ) => {
179+ mockUseCachedPVCs . mockReturnValue ( {
180+ ... defaultPVCsReturn ,
121181 pvcs : [
122- { cluster : 'cluster-1' , namespace : 'default ', name : 'pvc-1' , status : 'Bound' , storageClass : 'gp2' } ,
123- { cluster : 'cluster-1' , namespace : 'default ', name : 'pvc-2' , status : 'Pending' , storageClass : 'gp2' } ,
124- { cluster : 'cluster-1' , namespace : 'default ', name : 'pvc-3' , status : 'Lost' , storageClass : 'gp2' } ,
182+ makePVC ( 'Bound' , 'cluster-1' , 'gp2 ', 'pvc-1' ) ,
183+ makePVC ( 'Pending' , 'cluster-1' , 'gp2 ', 'pvc-2' ) ,
184+ makePVC ( 'Lost' , 'cluster-1' , 'gp2 ', 'pvc-3' ) ,
125185 ] ,
126- isLoading : false ,
127- isRefreshing : false ,
128- isDemoFallback : false ,
129- isFailed : false ,
130- consecutiveFailures : 0 ,
131- } as never )
186+ } )
187+
132188 render ( < StorageOverview /> )
133- // bound=1, pending=1, failed=1 ā all rendered as "1"
189+
134190 const ones = screen . getAllByText ( '1' )
135191 expect ( ones . length ) . toBeGreaterThanOrEqual ( 3 )
136192 } )
137193 } )
138194
139- describe ( 'Storage classes' , ( ) => {
140- it ( 'renders storage class list when PVCs have classes' , async ( ) => {
141- const { useCachedPVCs } = await import ( '../../../hooks/useCachedData' )
142- vi . mocked ( useCachedPVCs ) . mockReturnValue ( {
143- pvcs : [
144- { cluster : 'cluster-1' , namespace : 'default' , name : 'p1' , status : 'Bound' , storageClass : 'gp2' } ,
145- { cluster : 'cluster-1' , namespace : 'default' , name : 'p2' , status : 'Bound' , storageClass : 'standard' } ,
146- ] ,
147- isLoading : false ,
148- isRefreshing : false ,
149- isDemoFallback : false ,
150- isFailed : false ,
151- consecutiveFailures : 0 ,
152- } as never )
195+ describe ( 'PVC tiles' , ( ) => {
196+ it ( 'PVC status tiles are not clickable (no drilldown view)' , ( ) => {
153197 render ( < StorageOverview /> )
154- expect ( screen . getByText ( 'storageOverview.storageClasses' ) ) . toBeTruthy ( )
155- expect ( screen . getByText ( 'gp2' ) ) . toBeTruthy ( )
156- expect ( screen . getByText ( 'standard' ) ) . toBeTruthy ( )
198+
199+ const boundLabel = screen . getByText ( 'storageOverview.bound' )
200+ const tile = boundLabel . closest ( '[class*="border"]' )
201+ expect ( tile ) . not . toBeNull ( )
202+ expect ( tile ?. className ) . toContain ( 'cursor-default' )
203+ expect ( tile ?. className ) . not . toContain ( 'cursor-pointer' )
157204 } )
158205 } )
159206
160- describe ( 'PVC tiles' , ( ) => {
161- it ( 'PVC status tiles are not clickable (no drilldown view)' , async ( ) => {
162- const { useCachedPVCs } = await import ( '../../../hooks/useCachedData' )
163- vi . mocked ( useCachedPVCs ) . mockReturnValue ( {
164- pvcs : [ { cluster : 'cluster-1' , namespace : 'default' , name : 'pvc-1' , status : 'Bound' , storageClass : 'gp2' } ] ,
165- isLoading : false ,
166- isRefreshing : false ,
167- isDemoFallback : false ,
168- isFailed : false ,
169- consecutiveFailures : 0 ,
170- } as never )
207+ describe ( 'Storage classes' , ( ) => {
208+ it ( 'renders storage class list when PVCs have classes' , ( ) => {
209+ mockUseCachedPVCs . mockReturnValue ( {
210+ ...defaultPVCsReturn ,
211+ pvcs : [
212+ makePVC ( 'Bound' , 'cluster-1' , 'gp2' , 'p1' ) ,
213+ makePVC ( 'Bound' , 'cluster-1' , 'standard' , 'p2' ) ,
214+ ] ,
215+ } )
216+
171217 render ( < StorageOverview /> )
172- // The text is inside a nested div; walk up to the tile container that carries the cursor class
173- const boundLabel = screen . getByText ( 'storageOverview.bound' )
174- // The tile div is the one with border/bg classes ā two levels up from the label span
175- const tileDivs = boundLabel . closest ( '[class*="border"]' ) !
176- expect ( tileDivs . className ) . toContain ( 'cursor-default' )
177- expect ( tileDivs . className ) . not . toContain ( 'cursor-pointer' )
218+
219+ expect ( screen . getByText ( 'storageOverview.storageClasses' ) ) . toBeInTheDocument ( )
220+ expect ( screen . getByText ( 'gp2' ) ) . toBeInTheDocument ( )
221+ expect ( screen . getByText ( 'standard' ) ) . toBeInTheDocument ( )
178222 } )
179223 } )
180224
181225 describe ( 'Cluster filter' , ( ) => {
182226 it ( 'renders cluster filter dropdown' , ( ) => {
183227 render ( < StorageOverview /> )
184- expect ( screen . getByTestId ( 'cluster-filter' ) ) . toBeTruthy ( )
228+
229+ expect ( screen . getByTestId ( 'cluster-filter' ) ) . toBeInTheDocument ( )
185230 } )
186231 } )
187232
188233 describe ( 'Footer' , ( ) => {
189234 it ( 'renders footer with PVC and cluster count' , ( ) => {
190235 render ( < StorageOverview /> )
191- expect ( screen . getByText ( / s t o r a g e O v e r v i e w .f o o t e r / ) ) . toBeTruthy ( )
236+
237+ expect ( screen . getByText ( / P V C s , 1 c l u s t e r s / ) ) . toBeInTheDocument ( )
192238 } )
193239 } )
194- } )
240+ } )
0 commit comments