@@ -232,6 +232,90 @@ const stringInvalidOperatorColumn: Column<TestRow> = {
232232 } ,
233233} ;
234234
235+ describe . each ( [
236+ { isComposing : true , keyCode : 13 } ,
237+ { isComposing : false , keyCode : 229 } ,
238+ ] ) ( "filter IME handling ($isComposing, $keyCode)" , ( compositionEvent ) => {
239+ describe . each ( [ "panel" , "chip" ] as const ) ( "%s" , ( surface ) => {
240+ it . each ( [
241+ { column : stringColumn , initial : "Alice" , draft : "株式会社" , final : "株式会社東京" } ,
242+ { column : uuidColumn , initial : "uuid-1" , draft : "uuid-2" , final : "uuid-3" } ,
243+ { column : numberColumn , initial : 1 , draft : "2" , final : 3 } ,
244+ { column : timeColumn , initial : "09:30" , draft : "10:30" , final : "11:30" } ,
245+ ] ) (
246+ "does not commit $column.id until Enter outside composition" ,
247+ async ( { column, initial, draft, final } ) => {
248+ const user = userEvent . setup ( ) ;
249+ const field = column . id ! ;
250+ const operator = column === stringColumn ? "contains" : "eq" ;
251+ const control = makeControl ( { filters : [ { field, operator, value : initial } ] } ) ;
252+ render ( < TestFilters control = { control } columns = { [ column ] } /> , { wrapper } ) ;
253+
254+ if ( surface === "panel" ) {
255+ await user . click ( screen . getByRole ( "button" , { name : "Add filter" } ) ) ;
256+ } else {
257+ await openValueEditor ( user ) ;
258+ }
259+
260+ const input = await screen . findByDisplayValue ( String ( initial ) ) ;
261+ await user . click ( input ) ;
262+ fireEvent . change ( input , { target : { value : draft } } ) ;
263+ fireEvent . keyDown ( input , { key : "Enter" , ...compositionEvent } ) ;
264+
265+ expect ( control . addFilter ) . not . toHaveBeenCalled ( ) ;
266+ expect ( control . removeFilter ) . not . toHaveBeenCalled ( ) ;
267+ expect ( document . activeElement ) . toBe ( input ) ;
268+ expect ( screen . getByDisplayValue ( draft ) ) . toBe ( input ) ;
269+
270+ fireEvent . change ( input , { target : { value : String ( final ) } } ) ;
271+ fireEvent . keyDown ( input , { key : "Enter" , ...compositionEvent } ) ;
272+ expect ( control . addFilter ) . not . toHaveBeenCalled ( ) ;
273+ expect ( control . removeFilter ) . not . toHaveBeenCalled ( ) ;
274+
275+ fireEvent . keyDown ( input , { key : "Enter" , isComposing : false , keyCode : 13 } ) ;
276+ expect ( control . addFilter ) . toHaveBeenCalledTimes ( 1 ) ;
277+ expect ( vi . mocked ( control . addFilter ) . mock . calls [ 0 ] . slice ( 0 , 3 ) ) . toEqual ( [
278+ field ,
279+ operator ,
280+ final ,
281+ ] ) ;
282+ if ( surface === "chip" ) {
283+ await vi . waitFor ( ( ) => expect ( screen . queryByDisplayValue ( String ( final ) ) ) . toBeNull ( ) ) ;
284+ } else {
285+ expect ( screen . getByDisplayValue ( String ( final ) ) ) . toBe ( input ) ;
286+ }
287+ } ,
288+ ) ;
289+
290+ it . each ( [ "Min" , "Max" ] ) ( "does not commit a range from %s during composition" , async ( bound ) => {
291+ const user = userEvent . setup ( ) ;
292+ const control = makeControl ( {
293+ filters : [ { field : "count" , operator : "between" , value : { min : 1 , max : 10 } } ] ,
294+ } ) ;
295+ render ( < TestFilters control = { control } columns = { [ numberColumn ] } /> , { wrapper } ) ;
296+ if ( surface === "panel" ) {
297+ await user . click ( screen . getByRole ( "button" , { name : "Add filter" } ) ) ;
298+ } else {
299+ await openValueEditor ( user ) ;
300+ }
301+
302+ const input = await screen . findByRole ( "spinbutton" , { name : bound } ) ;
303+ fireEvent . change ( input , { target : { value : bound === "Min" ? "2" : "20" } } ) ;
304+ fireEvent . keyDown ( input , { key : "Enter" , ...compositionEvent } ) ;
305+ expect ( control . addFilter ) . not . toHaveBeenCalled ( ) ;
306+ expect ( control . removeFilter ) . not . toHaveBeenCalled ( ) ;
307+ expect ( screen . getByRole ( "spinbutton" , { name : bound } ) ) . toBe ( input ) ;
308+
309+ fireEvent . keyDown ( input , { key : "Enter" , isComposing : false , keyCode : 13 } ) ;
310+ expect ( control . addFilter ) . toHaveBeenCalledTimes ( 1 ) ;
311+ expect ( control . addFilter ) . toHaveBeenCalledWith ( "count" , "between" , {
312+ min : bound === "Min" ? 2 : 1 ,
313+ max : bound === "Max" ? 20 : 10 ,
314+ } ) ;
315+ } ) ;
316+ } ) ;
317+ } ) ;
318+
235319// ---------------------------------------------------------------------------
236320// DataTable.Filters — rendering
237321// ---------------------------------------------------------------------------
0 commit comments