Prevent more unnecessary filtering

This commit is contained in:
Tom Nunn 2023-03-11 21:24:47 +00:00
parent a75b080360
commit 559129d85e
6 changed files with 130 additions and 75 deletions

View File

@ -25,6 +25,7 @@ exampleProgramTest =
[ Test.test "Filter for United Kingdom produces one result" <|
\() ->
programTest
|> focusInput
|> ProgramTest.fillIn "" "Choose a country" "United Kingdom"
|> ProgramTest.ensureView
(Query.find [ Selector.id (Select.toMenuElementId countrySelect) ]
@ -38,12 +39,14 @@ exampleProgramTest =
, Test.test "Click United Kingdom selects it" <|
\() ->
programTest
|> focusInput
|> ProgramTest.fillIn "" "Choose a country" "United"
|> Select.Effect.simulateClickOption simulateInputConfig "country-select" "🇬🇧 United Kingdom of Great Britain and Northern Ireland"
|> ProgramTest.expectViewHas [ Selector.text "You chose United Kingdom of Great Britain and Northern Ireland" ]
, Test.test "Keyboard select United Kingdom" <|
\() ->
programTest
|> focusInput
|> ProgramTest.fillIn "" "Choose a country" "United"
|> SimulateInput.arrowDown "country-select"
|> SimulateInput.enter "country-select"
@ -51,12 +54,12 @@ exampleProgramTest =
, Test.test "Focusing on the input triggers the onFocus msg" <|
\() ->
programTest
|> ProgramTest.simulateDomEvent (Query.find [ Selector.id (Select.toInputElementId countrySelect) ]) Test.Html.Event.focus
|> focusInput
|> ProgramTest.expectModel (.inputIsFocused >> Expect.equal (Just True))
, Test.test "Input losing focus triggers the onLoseFocus msg" <|
\() ->
programTest
|> ProgramTest.simulateDomEvent (Query.find [ Selector.id (Select.toInputElementId countrySelect) ]) Test.Html.Event.focus
|> focusInput
|> ProgramTest.simulateDomEvent (Query.find [ Selector.id (Select.toInputElementId countrySelect) ]) Test.Html.Event.blur
|> ProgramTest.expectModel (.inputIsFocused >> Expect.equal (Just False))
, Test.test "Filling in the input triggers the onInput msg" <|
@ -67,26 +70,31 @@ exampleProgramTest =
, Test.test "Typing 2 chars with withMinInputLength (Just 3) does not show any items" <|
\() ->
programTestWith (Select.withMinInputLength (Just 3))
|> focusInput
|> ProgramTest.fillIn "" "Choose a country" "un"
|> ProgramTest.expectViewHasNot [ Selector.text "🇬🇧 United Kingdom of Great Britain and Northern Ireland" ]
, Test.test "Typing 3 chars with withMinInputLength (Just 3) does shows items" <|
\() ->
programTestWith (Select.withMinInputLength (Just 3))
|> focusInput
|> ProgramTest.fillIn "" "Choose a country" "uni"
|> ProgramTest.expectViewHas [ Selector.text "🇬🇧 United Kingdom of Great Britain and Northern Ireland" ]
, Test.test "Typing less than minInputLength does not show no matches even if nothing matched" <|
\() ->
programTestWith (Select.withMinInputLength (Just 5))
|> focusInput
|> ProgramTest.fillIn "" "Choose a country" "zzzz"
|> ProgramTest.expectViewHasNot [ Selector.text "No matches" ]
, Test.test "Typing up to the minInputLength shows no matches if nothing matched" <|
\() ->
programTestWith (Select.withMinInputLength (Just 3))
|> focusInput
|> ProgramTest.fillIn "" "Choose a country" "zzzz"
|> ProgramTest.expectViewHas [ Selector.text "No matches" ]
, Test.test "Choosing an option and then focusing back on the input shows all the options again" <|
\() ->
programTest
|> focusInput
|> ProgramTest.fillIn "" "Choose a country" "United"
|> Select.Effect.simulateClickOption simulateInputConfig "country-select" "🇬🇧 United Kingdom of Great Britain and Northern Ireland"
|> ProgramTest.simulateDomEvent (Query.find [ Selector.id (Select.toInputElementId countrySelect) ]) Test.Html.Event.focus
@ -155,3 +163,8 @@ simulateEffect effect =
, sleep = SimulatedProcess.sleep
}
selectEffect
focusInput : ProgramTest model msg effect -> ProgramTest model msg effect
focusInput =
ProgramTest.simulateDomEvent (Query.find [ Selector.id (Select.toInputElementId countrySelect) ]) Test.Html.Event.focus

View File

@ -1,10 +1,10 @@
module RequestTest exposing (exampleProgramTest)
import EffectRequestExample as App
import EffectRequestExample as App exposing (Cocktail)
import Http
import Json.Decode as Decode
import ProgramTest exposing (ProgramTest, SimulatedEffect)
import Select
import Select exposing (Select)
import Select.Effect
import SimulateInput
import SimulatedEffect.Cmd as SimulatedCmd
@ -12,6 +12,7 @@ import SimulatedEffect.Http as SimulateHttp
import SimulatedEffect.Process as SimulatedProcess
import SimulatedEffect.Task as SimulatedTask
import Test exposing (Test)
import Test.Html.Event
import Test.Html.Query as Query exposing (Single)
import Test.Html.Selector as Selector exposing (Selector)
@ -22,6 +23,7 @@ exampleProgramTest =
[ Test.test "Type in Chocolate, and choose second option with keyboard navigation" <|
\() ->
programTest
|> focusInput
|> ProgramTest.fillIn "" "Find a cocktail" "Chocolate"
|> ProgramTest.advanceTime 500
|> ProgramTest.simulateHttpOk "GET"
@ -33,6 +35,7 @@ exampleProgramTest =
, Test.test "Type in Chocolate, and choose \"Chocolate Drink\" with mouse click" <|
\() ->
programTest
|> focusInput
|> ProgramTest.fillIn "" "Find a cocktail" "Chocolate"
|> ProgramTest.advanceTime 300
|> ProgramTest.simulateHttpOk "GET"
@ -97,6 +100,18 @@ simulateConfig =
}
drinkSelect : Select Cocktail
drinkSelect =
App.init ()
|> Tuple.first
|> .select
focusInput : ProgramTest model msg effect -> ProgramTest model msg effect
focusInput =
ProgramTest.simulateDomEvent (Query.find [ Selector.id (Select.toInputElementId drinkSelect) ]) Test.Html.Event.focus
cocktailsResponse : String
cocktailsResponse =
"""{

View File

@ -10,6 +10,7 @@ module Internal.Model exposing
, isOpen
, isRequestFailed
, openMenu
, requiresNewFilteredOptions
, selectOption
, setElements
, setFilteredOptions
@ -128,6 +129,10 @@ wasHighlightedByMouse (Model { highlightedByMouse }) =
toFilteredOptions : Maybe Int -> (a -> String) -> Maybe (Filter a) -> Model a -> List (Option a)
toFilteredOptions minInputLength itemToString filter (Model model) =
if not model.focused then
[]
else
case minInputLength of
Just chars ->
if String.length model.inputValue >= chars then
@ -238,6 +243,11 @@ isRequestFailed (Model { requestState }) =
requestState == Just Failed
requiresNewFilteredOptions : Model a -> Bool
requiresNewFilteredOptions (Model { filteredOptions }) =
filteredOptions == Nothing
-- UPDATE

View File

@ -8,6 +8,7 @@ type Msg a
= InputChanged String (List (Option a))
| OptionClicked (Option a)
| InputFocused (Maybe Int)
| GotNewFilteredOptions (List (Option a))
| InputClicked (Maybe Int)
| InputLostFocus
{ clearInputValue : Bool

View File

@ -85,13 +85,19 @@ update_ { request, requestMinInputLength, debounceRequest, onFocus, onLoseFocus,
, Effect.emitJust onLoseFocus
)
GotNewFilteredOptions options ->
( Model.setFilteredOptions options model
, Effect.None
)
MouseEnteredOption i ->
( Model.highlightIndex (Just i) True model
, Effect.none
)
KeyDown selectOnTab filteredOptions key ->
handleKey selectOnTab tagger model key filteredOptions
Model.setFilteredOptions filteredOptions model
|> handleKey selectOnTab tagger key filteredOptions
GotContainerAndMenuElements maybeIdx result ->
( model
@ -176,8 +182,8 @@ onFocusMenu tagger maybeOptionIdx hasRequest model =
)
handleKey : Bool -> (Msg a -> msg) -> Model a -> String -> List (Option a) -> ( Model a, Effect effect msg )
handleKey selectOnTab tagger model key filteredOptions =
handleKey : Bool -> (Msg a -> msg) -> String -> List (Option a) -> Model a -> ( Model a, Effect effect msg )
handleKey selectOnTab tagger key filteredOptions model =
let
selectHighlighted =
case Model.toHighlighted model |> Maybe.andThen (\idx -> getAt idx filteredOptions) of

View File

@ -89,7 +89,8 @@ toElement model config =
toElement_ : Placement -> List (Option a) -> Model a -> ViewConfigInternal a msg -> Element msg
toElement_ placement filteredOptions model config =
Element.el
([ Element.htmlAttribute (Html.Attributes.id <| Model.toContainerElementId model)
(List.concat
[ [ Element.htmlAttribute (Html.Attributes.id <| Model.toContainerElementId model)
, Element.width Element.fill
, Element.below <|
if
@ -135,12 +136,19 @@ toElement_ placement filteredOptions model config =
, optionElement = config.optionElement
}
]
++ (if Model.isOpen model then
, if Model.isOpen model then
[ Element.htmlAttribute <| Html.Attributes.style "z-index" "21" ]
else
[]
)
, if Model.isFocused model && Model.requiresNewFilteredOptions model then
[ Element.htmlAttribute (Html.Events.on "focusin" (Decode.succeed (GotNewFilteredOptions filteredOptions |> config.onChange)))
, Element.htmlAttribute (Html.Events.on "mousemove" (Decode.succeed (GotNewFilteredOptions filteredOptions |> config.onChange)))
]
else
[]
]
)
(inputView filteredOptions model config)
@ -153,8 +161,9 @@ inputView filteredOptions model config =
|> Maybe.andThen (Option.findIndex filteredOptions)
in
Input.text
(config.inputAttribs
++ [ Events.onFocus (InputFocused selectedIdx |> config.onChange)
(List.concat
[ config.inputAttribs
, [ Events.onFocus (InputFocused selectedIdx |> config.onChange)
, Events.onClick (InputClicked selectedIdx |> config.onChange)
, Events.onLoseFocus
(config.onChange
@ -174,7 +183,8 @@ inputView filteredOptions model config =
else
Element.none
]
++ inputAccessibilityAttributes filteredOptions model
, inputAccessibilityAttributes filteredOptions model
]
)
{ onChange =
\v ->