@@ -14,11 +14,22 @@ type MockContainerProps = HTMLAttributes<HTMLElement> & {
1414
1515type MockControlsPanelProps = {
1616 username : string ;
17- timezone : string ;
17+ radius : number ;
1818 onUsernameChange : ( value : string ) => void ;
19+ } ;
20+
21+ type MockAdvancedSettingsPanelProps = {
22+ timezone : string ;
23+ badgeWidth : number | '' ;
24+ badgeHeight : number | '' ;
25+ grace : number ;
1926 onTimezoneChange : ( value : string ) => void ;
2027} ;
2128
29+ const mockSearchParams = vi . hoisted ( ( ) => ( {
30+ values : new Map < string , string > ( ) ,
31+ } ) ) ;
32+
2233vi . mock ( 'next/link' , ( ) => ( {
2334 default : ( { href, children, ...props } : MockLinkProps ) => (
2435 < a href = { href } { ...props } >
@@ -39,7 +50,7 @@ vi.mock('next/navigation', () => ({
3950 replace : vi . fn ( ) ,
4051 } ) ,
4152 useSearchParams : ( ) => ( {
42- get : ( ) => null ,
53+ get : ( key : string ) => mockSearchParams . values . get ( key ) ?? null ,
4354 } ) ,
4455} ) ) ;
4556
@@ -48,33 +59,39 @@ vi.mock('@/components/InteractiveViewer', () => ({
4859} ) ) ;
4960
5061vi . mock ( './components/ControlsPanel' , ( ) => ( {
51- ControlsPanel : ( { username, onUsernameChange } : MockControlsPanelProps ) => (
62+ ControlsPanel : ( { username, radius , onUsernameChange } : MockControlsPanelProps ) => (
5263 < div >
5364 < input
5465 aria-label = "Mock username"
5566 value = { username }
5667 onChange = { ( event ) => onUsernameChange ( event . currentTarget . value ) }
5768 />
69+ < output aria-label = "Mock radius" > { String ( radius ) } </ output >
5870 </ div >
5971 ) ,
6072} ) ) ;
6173
6274vi . mock ( './components/AdvancedSettingsPanel' , ( ) => ( {
6375 AdvancedSettingsPanel : ( {
6476 timezone,
77+ badgeWidth,
78+ badgeHeight,
79+ grace,
6580 onTimezoneChange,
66- } : {
67- timezone : string ;
68- onTimezoneChange : ( value : string ) => void ;
69- } ) => (
70- < select
71- aria-label = "Mock timezone"
72- value = { timezone }
73- onChange = { ( event ) => onTimezoneChange ( event . currentTarget . value ) }
74- >
75- < option value = "UTC" > UTC</ option >
76- < option value = "Asia/Kolkata" > Asia/Kolkata</ option >
77- </ select >
81+ } : MockAdvancedSettingsPanelProps ) => (
82+ < div >
83+ < select
84+ aria-label = "Mock timezone"
85+ value = { timezone }
86+ onChange = { ( event ) => onTimezoneChange ( event . currentTarget . value ) }
87+ >
88+ < option value = "UTC" > UTC</ option >
89+ < option value = "Asia/Kolkata" > Asia/Kolkata</ option >
90+ </ select >
91+ < output aria-label = "Mock badge width" > { String ( badgeWidth ) } </ output >
92+ < output aria-label = "Mock badge height" > { String ( badgeHeight ) } </ output >
93+ < output aria-label = "Mock grace" > { String ( grace ) } </ output >
94+ </ div >
7895 ) ,
7996} ) ) ;
8097
@@ -86,6 +103,7 @@ vi.mock('./components/ExportPanel', () => ({
86103
87104describe ( 'CustomizePage timezone query params' , ( ) => {
88105 beforeEach ( ( ) => {
106+ mockSearchParams . values . clear ( ) ;
89107 global . fetch = vi . fn ( ) . mockResolvedValue ( {
90108 ok : true ,
91109 text : async ( ) => '<svg></svg>' ,
@@ -126,4 +144,26 @@ describe('CustomizePage timezone query params', () => {
126144 expect ( snippet ) . toContain ( 'user=octocat' ) ;
127145 expect ( snippet ) . toContain ( 'tz=Asia%2FKolkata' ) ;
128146 } ) ;
147+
148+ it ( 'falls back when numeric URL params are malformed' , ( ) => {
149+ mockSearchParams . values . set ( 'user' , 'octocat' ) ;
150+ mockSearchParams . values . set ( 'radius' , 'abc' ) ;
151+ mockSearchParams . values . set ( 'width' , 'NaN' ) ;
152+ mockSearchParams . values . set ( 'height' , 'nope' ) ;
153+ mockSearchParams . values . set ( 'grace' , 'bad' ) ;
154+
155+ render ( < CustomizePage /> ) ;
156+
157+ expect ( screen . getByLabelText ( 'Mock radius' ) . textContent ) . toBe ( '8' ) ;
158+ expect ( screen . getByLabelText ( 'Mock badge width' ) . textContent ) . toBe ( '' ) ;
159+ expect ( screen . getByLabelText ( 'Mock badge height' ) . textContent ) . toBe ( '' ) ;
160+ expect ( screen . getByLabelText ( 'Mock grace' ) . textContent ) . toBe ( '1' ) ;
161+
162+ const snippet = screen . getByLabelText ( 'Mock export snippet' ) . textContent ;
163+ expect ( snippet ) . toContain ( 'user=octocat' ) ;
164+ expect ( snippet ) . not . toContain ( 'radius=NaN' ) ;
165+ expect ( snippet ) . not . toContain ( 'width=NaN' ) ;
166+ expect ( snippet ) . not . toContain ( 'height=NaN' ) ;
167+ expect ( snippet ) . not . toContain ( 'grace=NaN' ) ;
168+ } ) ;
129169} ) ;
0 commit comments