@@ -2,160 +2,183 @@ import { render, screen } from "@testing-library/react";
22import userEvent from "@testing-library/user-event" ;
33import { describe , expect , it , vi } from "vitest" ;
44
5- import { DEFAULT_CREATED_BY_ME_FILTER_VALUE } from "@/utils/constants" ;
6-
75import { CreatedByFilter } from "./CreatedByFilter" ;
86
97describe ( "CreatedByFilter" , ( ) => {
108 describe ( "rendering" , ( ) => {
11- it ( "should render toggle and search input" , ( ) => {
12- render ( < CreatedByFilter value = { undefined } onChange = { vi . fn ( ) } /> ) ;
9+ it ( "should render search input with placeholder" , ( ) => {
10+ render (
11+ < CreatedByFilter
12+ value = { undefined }
13+ onChange = { vi . fn ( ) }
14+ onClear = { vi . fn ( ) }
15+ /> ,
16+ ) ;
1317
14- expect ( screen . getByRole ( "switch" ) ) . toBeInTheDocument ( ) ;
15- expect ( screen . getByLabelText ( "Created by me" ) ) . toBeInTheDocument ( ) ;
16- expect ( screen . getByPlaceholderText ( "Search by user" ) ) . toBeInTheDocument ( ) ;
1718 expect (
18- screen . getByRole ( "button" , { name : "Search" } ) ,
19+ screen . getByPlaceholderText ( "Search by user..." ) ,
1920 ) . toBeInTheDocument ( ) ;
2021 } ) ;
2122
22- it ( "should show 'Created by me' when no value " , ( ) => {
23- render ( < CreatedByFilter value = { undefined } onChange = { vi . fn ( ) } /> ) ;
24-
25- expect ( screen . getByLabelText ( "Created by me" ) ) . toBeInTheDocument ( ) ;
26- } ) ;
27-
28- it ( "should show 'Created by {user}' when value is set" , ( ) => {
29- render ( < CreatedByFilter value = "john.doe" onChange = { vi . fn ( ) } /> ) ;
23+ it ( "should not show clear button when empty " , ( ) => {
24+ render (
25+ < CreatedByFilter
26+ value = { undefined }
27+ onChange = { vi . fn ( ) }
28+ onClear = { vi . fn ( ) }
29+ /> ,
30+ ) ;
3031
31- expect ( screen . getByLabelText ( "Created by john.doe" ) ) . toBeInTheDocument ( ) ;
32+ expect (
33+ screen . queryByRole ( "button" , { name : "Clear user filter" } ) ,
34+ ) . not . toBeInTheDocument ( ) ;
3235 } ) ;
3336
34- it ( "should have switch unchecked when no value" , ( ) => {
35- render ( < CreatedByFilter value = { undefined } onChange = { vi . fn ( ) } /> ) ;
37+ it ( "should show clear button when value is set" , ( ) => {
38+ render (
39+ < CreatedByFilter
40+ value = "john.doe"
41+ onChange = { vi . fn ( ) }
42+ onClear = { vi . fn ( ) }
43+ /> ,
44+ ) ;
3645
37- expect ( screen . getByRole ( "switch" ) ) . not . toBeChecked ( ) ;
46+ expect (
47+ screen . getByRole ( "button" , { name : "Clear user filter" } ) ,
48+ ) . toBeInTheDocument ( ) ;
3849 } ) ;
3950
40- it ( "should show 'Created by {user}' and checked switch when value is set" , ( ) => {
41- render ( < CreatedByFilter value = "john.doe" onChange = { vi . fn ( ) } /> ) ;
51+ it ( "should populate input with current value" , ( ) => {
52+ render (
53+ < CreatedByFilter
54+ value = "existing-user"
55+ onChange = { vi . fn ( ) }
56+ onClear = { vi . fn ( ) }
57+ /> ,
58+ ) ;
4259
43- expect ( screen . getByRole ( "switch" ) ) . toBeChecked ( ) ;
44- expect ( screen . getByLabelText ( "Created by john.doe" ) ) . toBeInTheDocument ( ) ;
60+ expect ( screen . getByPlaceholderText ( "Search by user..." ) ) . toHaveValue (
61+ "existing-user" ,
62+ ) ;
4563 } ) ;
4664 } ) ;
4765
48- describe ( "toggle behavior" , ( ) => {
49- it ( "should call onChange with 'me' when toggle is turned on" , async ( ) => {
50- const user = userEvent . setup ( ) ;
51- const onChange = vi . fn ( ) ;
52- render ( < CreatedByFilter value = { undefined } onChange = { onChange } /> ) ;
53-
54- await user . click ( screen . getByRole ( "switch" ) ) ;
55-
56- expect ( onChange ) . toHaveBeenCalledWith ( DEFAULT_CREATED_BY_ME_FILTER_VALUE ) ;
57- } ) ;
58-
59- it ( "should call onChange with undefined when toggle is turned off" , async ( ) => {
66+ describe ( "typing behavior" , ( ) => {
67+ it ( "should call onChange when typing" , async ( ) => {
6068 const user = userEvent . setup ( ) ;
6169 const onChange = vi . fn ( ) ;
6270 render (
6371 < CreatedByFilter
64- value = { DEFAULT_CREATED_BY_ME_FILTER_VALUE }
72+ value = { undefined }
6573 onChange = { onChange }
74+ onClear = { vi . fn ( ) }
6675 /> ,
6776 ) ;
6877
69- await user . click ( screen . getByRole ( "switch" ) ) ;
78+ await user . type ( screen . getByPlaceholderText ( "Search by user..." ) , "jane" ) ;
7079
71- expect ( onChange ) . toHaveBeenCalledWith ( undefined ) ;
80+ // Called for each character typed
81+ expect ( onChange ) . toHaveBeenCalledTimes ( 4 ) ;
82+ expect ( onChange ) . toHaveBeenLastCalledWith ( "jane" ) ;
7283 } ) ;
7384
74- it ( "should clear filter when toggling off with existing value " , async ( ) => {
85+ it ( "should call onChange with undefined when input is cleared by typing " , async ( ) => {
7586 const user = userEvent . setup ( ) ;
7687 const onChange = vi . fn ( ) ;
77- render ( < CreatedByFilter value = "john.doe" onChange = { onChange } /> ) ;
88+ render (
89+ < CreatedByFilter value = "j" onChange = { onChange } onClear = { vi . fn ( ) } /> ,
90+ ) ;
7891
79- await user . click ( screen . getByRole ( "switch ") ) ;
92+ await user . clear ( screen . getByPlaceholderText ( "Search by user... ") ) ;
8093
81- expect ( onChange ) . toHaveBeenCalledWith ( undefined ) ;
94+ expect ( onChange ) . toHaveBeenLastCalledWith ( undefined ) ;
8295 } ) ;
8396 } ) ;
8497
85- describe ( "search behavior" , ( ) => {
86- it ( "should have search button disabled when input is empty" , ( ) => {
87- render ( < CreatedByFilter value = { undefined } onChange = { vi . fn ( ) } /> ) ;
88-
89- expect ( screen . getByRole ( "button" , { name : "Search" } ) ) . toBeDisabled ( ) ;
90- } ) ;
91-
92- it ( "should enable search button when input has text" , async ( ) => {
93- const user = userEvent . setup ( ) ;
94- render ( < CreatedByFilter value = { undefined } onChange = { vi . fn ( ) } /> ) ;
95-
96- await user . type ( screen . getByPlaceholderText ( "Search by user" ) , "jane" ) ;
97-
98- expect ( screen . getByRole ( "button" , { name : "Search" } ) ) . toBeEnabled ( ) ;
99- } ) ;
100-
101- it ( "should call onChange with typed user when search clicked" , async ( ) => {
98+ describe ( "clear button behavior" , ( ) => {
99+ it ( "should call onClear and clear input when clear button is clicked" , async ( ) => {
102100 const user = userEvent . setup ( ) ;
103- const onChange = vi . fn ( ) ;
104- render ( < CreatedByFilter value = { undefined } onChange = { onChange } /> ) ;
101+ const onClear = vi . fn ( ) ;
102+ render (
103+ < CreatedByFilter
104+ value = "john.doe"
105+ onChange = { vi . fn ( ) }
106+ onClear = { onClear }
107+ /> ,
108+ ) ;
105109
106- await user . type (
107- screen . getByPlaceholderText ( "Search by user" ) ,
108- "jane.doe" ,
110+ await user . click (
111+ screen . getByRole ( "button" , { name : "Clear user filter" } ) ,
109112 ) ;
110- await user . click ( screen . getByRole ( "button" , { name : "Search" } ) ) ;
111113
112- expect ( onChange ) . toHaveBeenCalledWith ( "jane.doe" ) ;
114+ expect ( onClear ) . toHaveBeenCalled ( ) ;
113115 } ) ;
114116
115- it ( "should call onChange when Enter is pressed in input " , async ( ) => {
117+ it ( "should hide clear button after clearing " , async ( ) => {
116118 const user = userEvent . setup ( ) ;
117- const onChange = vi . fn ( ) ;
118- render ( < CreatedByFilter value = { undefined } onChange = { onChange } /> ) ;
119+ render (
120+ < CreatedByFilter
121+ value = "john.doe"
122+ onChange = { vi . fn ( ) }
123+ onClear = { vi . fn ( ) }
124+ /> ,
125+ ) ;
119126
120- const input = screen . getByPlaceholderText ( "Search by user" ) ;
121- await user . type ( input , "jane.doe{Enter}" ) ;
127+ await user . click (
128+ screen . getByRole ( "button" , { name : "Clear user filter" } ) ,
129+ ) ;
122130
123- expect ( onChange ) . toHaveBeenCalledWith ( "jane.doe" ) ;
131+ expect (
132+ screen . queryByRole ( "button" , { name : "Clear user filter" } ) ,
133+ ) . not . toBeInTheDocument ( ) ;
124134 } ) ;
135+ } ) ;
125136
126- it ( "should trim whitespace from search input" , async ( ) => {
127- const user = userEvent . setup ( ) ;
128- const onChange = vi . fn ( ) ;
129- render ( < CreatedByFilter value = { undefined } onChange = { onChange } /> ) ;
130-
131- await user . type (
132- screen . getByPlaceholderText ( "Search by user" ) ,
133- " jane " ,
137+ describe ( "external value sync" , ( ) => {
138+ it ( "should sync input when value prop changes" , ( ) => {
139+ const { rerender } = render (
140+ < CreatedByFilter
141+ value = "initial"
142+ onChange = { vi . fn ( ) }
143+ onClear = { vi . fn ( ) }
144+ /> ,
134145 ) ;
135- await user . click ( screen . getByRole ( "button" , { name : "Search" } ) ) ;
136146
137- expect ( onChange ) . toHaveBeenCalledWith ( "jane" ) ;
138- } ) ;
139-
140- it ( "should not call onChange when searching with only whitespace" , async ( ) => {
141- const user = userEvent . setup ( ) ;
142- const onChange = vi . fn ( ) ;
143- render ( < CreatedByFilter value = { undefined } onChange = { onChange } /> ) ;
147+ expect ( screen . getByPlaceholderText ( "Search by user..." ) ) . toHaveValue (
148+ "initial" ,
149+ ) ;
144150
145- await user . type ( screen . getByPlaceholderText ( "Search by user" ) , " " ) ;
151+ rerender (
152+ < CreatedByFilter
153+ value = "updated"
154+ onChange = { vi . fn ( ) }
155+ onClear = { vi . fn ( ) }
156+ /> ,
157+ ) ;
146158
147- // Button should still be disabled
148- expect ( screen . getByRole ( "button" , { name : "Search" } ) ) . toBeDisabled ( ) ;
159+ expect ( screen . getByPlaceholderText ( "Search by user..." ) ) . toHaveValue (
160+ "updated" ,
161+ ) ;
149162 } ) ;
150- } ) ;
151163
152- describe ( "initial state" , ( ) => {
153- it ( "should populate search input with current value" , ( ) => {
154- render ( < CreatedByFilter value = "existing-user" onChange = { vi . fn ( ) } /> ) ;
164+ it ( "should clear input when value prop becomes undefined" , ( ) => {
165+ const { rerender } = render (
166+ < CreatedByFilter
167+ value = "some-user"
168+ onChange = { vi . fn ( ) }
169+ onClear = { vi . fn ( ) }
170+ /> ,
171+ ) ;
155172
156- expect ( screen . getByPlaceholderText ( "Search by user" ) ) . toHaveValue (
157- "existing-user" ,
173+ rerender (
174+ < CreatedByFilter
175+ value = { undefined }
176+ onChange = { vi . fn ( ) }
177+ onClear = { vi . fn ( ) }
178+ /> ,
158179 ) ;
180+
181+ expect ( screen . getByPlaceholderText ( "Search by user..." ) ) . toHaveValue ( "" ) ;
159182 } ) ;
160183 } ) ;
161184} ) ;
0 commit comments