diff --git a/src/Nri/Ui/Highlighter/V1.elm b/src/Nri/Ui/Highlighter/V1.elm index 5f33187c..11b1f4f6 100644 --- a/src/Nri/Ui/Highlighter/V1.elm +++ b/src/Nri/Ui/Highlighter/V1.elm @@ -709,14 +709,18 @@ viewHighlightableSegment isInteractive focusIndex highlighterId eventListeners m AttributesExtra.none , css (highlightableStyle maybeTool highlightable isInteractive) , class "highlighter-highlightable" - , Key.tabbable - (case focusIndex of - Nothing -> - False + , if isInteractive then + Key.tabbable + (case focusIndex of + Nothing -> + False - Just i -> - highlightable.groupIndex == i - ) + Just i -> + highlightable.groupIndex == i + ) + + else + AttributesExtra.none ] ) [ Html.text highlightable.text ] diff --git a/styleguide-app/Examples/Highlighter.elm b/styleguide-app/Examples/Highlighter.elm index 3c8eaa36..6d655020 100644 --- a/styleguide-app/Examples/Highlighter.elm +++ b/styleguide-app/Examples/Highlighter.elm @@ -228,9 +228,6 @@ initHighlighter settings previousHighlightables = let segments = List.filter (\x -> x /= "") (String.split "." (String.trim CommonControls.romeoAndJulietQuotation)) - - segmentCount = - List.length segments in List.indexedMap (\index sentence -> diff --git a/tests/Spec/KeyboardHelpers.elm b/tests/Spec/KeyboardHelpers.elm index 34be862e..84a4fed9 100644 --- a/tests/Spec/KeyboardHelpers.elm +++ b/tests/Spec/KeyboardHelpers.elm @@ -31,6 +31,30 @@ pressKey { targetDetails, keyCode, shiftKey } selectors = ) +releaseKey : + { targetDetails : List ( String, Encode.Value ) + , keyCode : Int + , shiftKey : Bool + } + -> List Selector + -> ProgramTest model msg effect + -> ProgramTest model msg effect +releaseKey { targetDetails, keyCode, shiftKey } selectors = + ProgramTest.simulateDomEvent + (Query.find selectors) + (Event.custom + "keyup" + (Encode.object + [ ( "keyCode", Encode.int keyCode ) + , ( "shiftKey", Encode.bool shiftKey ) + , ( "target" + , Encode.object targetDetails + ) + ] + ) + ) + + pressTabKey : { targetDetails : List ( String, Encode.Value ) } -> List Selector @@ -56,3 +80,48 @@ pressRightArrow : -> ProgramTest model msg effect pressRightArrow { targetDetails } = pressKey { targetDetails = targetDetails, keyCode = 39, shiftKey = False } + + +pressLeftArrow : + { targetDetails : List ( String, Encode.Value ) } + -> List Selector + -> ProgramTest model msg effect + -> ProgramTest model msg effect +pressLeftArrow { targetDetails } = + pressKey { targetDetails = targetDetails, keyCode = 37, shiftKey = False } + + +pressShiftRight : + { targetDetails : List ( String, Encode.Value ) } + -> List Selector + -> ProgramTest model msg effect + -> ProgramTest model msg effect +pressShiftRight { targetDetails } = + pressKey { targetDetails = targetDetails, keyCode = 39, shiftKey = True } + + +pressShiftLeft : + { targetDetails : List ( String, Encode.Value ) } + -> List Selector + -> ProgramTest model msg effect + -> ProgramTest model msg effect +pressShiftLeft { targetDetails } = + pressKey { targetDetails = targetDetails, keyCode = 37, shiftKey = True } + + +releaseShiftRight : + { targetDetails : List ( String, Encode.Value ) } + -> List Selector + -> ProgramTest model msg effect + -> ProgramTest model msg effect +releaseShiftRight { targetDetails } = + releaseKey { targetDetails = targetDetails, keyCode = 39, shiftKey = True } + + +releaseShiftLeft : + { targetDetails : List ( String, Encode.Value ) } + -> List Selector + -> ProgramTest model msg effect + -> ProgramTest model msg effect +releaseShiftLeft { targetDetails } = + releaseKey { targetDetails = targetDetails, keyCode = 37, shiftKey = True } diff --git a/tests/Spec/MouseHelpers.elm b/tests/Spec/MouseHelpers.elm new file mode 100644 index 00000000..424f9467 --- /dev/null +++ b/tests/Spec/MouseHelpers.elm @@ -0,0 +1,65 @@ +module Spec.MouseHelpers exposing (..) + +import Json.Encode as Encode +import ProgramTest exposing (ProgramTest) +import Test.Html.Event as Event +import Test.Html.Query as Query +import Test.Html.Selector exposing (Selector) + + +click : List Selector -> ProgramTest model msg effect -> ProgramTest model msg effect +click selectors = + ProgramTest.simulateDomEvent + (Query.find selectors) + Event.click + + +mouseDown : List Selector -> ProgramTest model msg effect -> ProgramTest model msg effect +mouseDown selectors = + ProgramTest.simulateDomEvent + (Query.find selectors) + Event.mouseDown + + +mouseOver : List Selector -> ProgramTest model msg effect -> ProgramTest model msg effect +mouseOver selectors = + ProgramTest.simulateDomEvent + (Query.find selectors) + Event.mouseOver + + +mouseUp : List Selector -> ProgramTest model msg effect -> ProgramTest model msg effect +mouseUp selectors = + ProgramTest.simulateDomEvent + (Query.find selectors) + Event.mouseUp + + +cancelableMouseDown : List Selector -> ProgramTest model msg effect -> ProgramTest model msg effect +cancelableMouseDown selectors = + ProgramTest.simulateDomEvent + (Query.find selectors) + (Event.custom + "mousedown" + (Encode.object [ ( "cancelable", Encode.bool True ) ]) + ) + + +cancelableMouseOver : List Selector -> ProgramTest model msg effect -> ProgramTest model msg effect +cancelableMouseOver selectors = + ProgramTest.simulateDomEvent + (Query.find selectors) + (Event.custom + "mouseover" + (Encode.object [ ( "cancelable", Encode.bool True ) ]) + ) + + +cancelableMouseUp : List Selector -> ProgramTest model msg effect -> ProgramTest model msg effect +cancelableMouseUp selectors = + ProgramTest.simulateDomEvent + (Query.find selectors) + (Event.custom + "mouseup" + (Encode.object [ ( "cancelable", Encode.bool True ) ]) + ) diff --git a/tests/Spec/Nri/Ui/Highlighter.elm b/tests/Spec/Nri/Ui/Highlighter.elm index 8b4bd85c..bc367048 100644 --- a/tests/Spec/Nri/Ui/Highlighter.elm +++ b/tests/Spec/Nri/Ui/Highlighter.elm @@ -9,9 +9,10 @@ import Nri.Ui.Highlighter.V1 as Highlighter import Nri.Ui.HighlighterTool.V1 as Tool exposing (Tool) import ProgramTest exposing (..) import Spec.KeyboardHelpers as KeyboardHelpers +import Spec.MouseHelpers as MouseHelpers import Test exposing (..) -import Test.Html.Query exposing (..) -import Test.Html.Selector exposing (..) +import Test.Html.Query as Query +import Test.Html.Selector as Selector spec : Test @@ -43,23 +44,190 @@ keyboardTests = |> rightArrow |> ensureFocusOn "light" |> done + , test "moves focus left on left arrow key" <| + \() -> + Highlightable.initFragments Nothing "Pothos indirect light" + |> program marker + |> ensureFocusOn "Pothos" + |> rightArrow + |> ensureFocusOn "indirect" + |> leftArrow + |> ensureFocusOn "Pothos" + -- once we're on the first element, pressing left arrow again should + -- _not_ wrap the focus. We should stay right where we are! + |> leftArrow + |> ensureFocusOn "Pothos" + |> done + , test "moves focus right on shift + right arrow" <| + \() -> + Highlightable.initFragments Nothing "Pothos indirect light" + |> program marker + |> ensureFocusOn "Pothos" + |> shiftRight + |> ensureFocusOn "indirect" + |> shiftRight + |> ensureFocusOn "light" + |> shiftRight + |> ensureFocusOn "light" + |> done + , test "moves focus left on shift + left arrow" <| + \() -> + Highlightable.initFragments Nothing "Pothos indirect light" + |> program marker + |> ensureFocusOn "Pothos" + |> rightArrow + |> ensureFocusOn "indirect" + |> shiftLeft + |> ensureFocusOn "Pothos" + |> shiftLeft + |> ensureFocusOn "Pothos" + |> done + , test "expands selection one element to the right on shift + right arrow and highlight selected elements" <| + \() -> + Highlightable.initFragments Nothing "Pothos indirect light" + |> program marker + |> shiftRight + |> releaseShiftRight + |> ensureMarked [ "Pothos", "", "indirect" ] + |> shiftRight + |> releaseShiftRight + |> ensureMarked [ "Pothos", "", "indirect", "", "light" ] + |> shiftRight + |> releaseShiftRight + |> ensureMarked [ "Pothos", "", "indirect", "", "light" ] + |> done + , test "expands selection one element to the left on shift + left arrow and highlight selected elements" <| + \() -> + Highlightable.initFragments Nothing "Pothos indirect light" + |> program marker + |> rightArrow + |> rightArrow + |> shiftLeft + |> releaseShiftLeft + |> ensureMarked [ "indirect", "", "light" ] + |> shiftLeft + |> releaseShiftLeft + |> ensureMarked [ "Pothos", "", "indirect", "", "light" ] + |> shiftLeft + |> releaseShiftLeft + |> ensureMarked [ "Pothos", "", "indirect", "", "light" ] + |> done + , test "merges highlights" <| + \() -> + Highlightable.initFragments Nothing "Pothos indirect light" + |> program marker + |> ensureFocusOn "Pothos" + |> shiftRight + |> releaseShiftRight + |> ensureMarked [ "Pothos", "", "indirect" ] + |> ensureFocusOn "indirect" + |> rightArrow + |> ensureFocusOn "light" + |> shiftLeft + |> releaseShiftLeft + |> ensureMarked [ "Pothos", "", "indirect", "", "light" ] + |> done + , test "selects element on MouseDown and highlights selected element on MouseUp" <| + \() -> + Highlightable.initFragments Nothing "Pothos indirect light" + |> program marker + |> ensureFocusOn "Pothos" + |> mouseDown "Pothos" + |> mouseUp "Pothos" + |> ensureMarked [ "Pothos" ] + |> done + , test "selects element on MouseDown, expands selection on MouseOver, and highlights selected elements on MouseUp" <| + \() -> + Highlightable.initFragments Nothing "Pothos indirect light" + |> program marker + |> ensureFocusOn "Pothos" + |> mouseDown "Pothos" + |> mouseOver "indirect" + |> mouseUp "Pothos" + |> ensureMarked [ "Pothos", "", "indirect" ] + |> done ] ensureFocusOn : String -> TestContext marker -> TestContext marker ensureFocusOn word textContext = textContext - |> ensureViewHas [ text word, attribute (Key.tabbable True) ] |> ensureView - (findAll [ attribute (Key.tabbable True) ] - >> count (Expect.equal 1) + (Query.find [ Selector.attribute (Key.tabbable True) ] + >> Query.has [ Selector.text word ] + ) + |> ensureView + (Query.findAll [ Selector.attribute (Key.tabbable True) ] + >> Query.count (Expect.equal 1) + ) + -- We only manage focus for interactive elements. + -- Static elements should not have explicit tabindex -1 as it is redundant, + -- they are never included in the tab sequence + |> ensureView + (Query.findAll [ Selector.attribute (Key.tabbable False) ] + >> Query.count (Expect.equal 2) + ) + + +ensureMarked : List String -> TestContext marker -> TestContext marker +ensureMarked words textContext = + textContext + |> ensureView + (Query.find [ Selector.tag "mark" ] + >> Query.children [ Selector.tag "span" ] + >> Expect.all (List.indexedMap (\i w -> Query.index i >> Query.has [ Selector.text w ]) words) ) rightArrow : TestContext marker -> TestContext marker rightArrow = KeyboardHelpers.pressRightArrow { targetDetails = [] } - [ attribute (Key.tabbable True) ] + [ Selector.attribute (Key.tabbable True) ] + + +leftArrow : TestContext marker -> TestContext marker +leftArrow = + KeyboardHelpers.pressLeftArrow { targetDetails = [] } + [ Selector.attribute (Key.tabbable True) ] + + +shiftRight : TestContext marker -> TestContext marker +shiftRight = + KeyboardHelpers.pressShiftRight { targetDetails = [] } + [ Selector.attribute (Key.tabbable True) ] + + +shiftLeft : TestContext marker -> TestContext marker +shiftLeft = + KeyboardHelpers.pressShiftLeft { targetDetails = [] } + [ Selector.attribute (Key.tabbable True) ] + + +releaseShiftRight : TestContext marker -> TestContext marker +releaseShiftRight = + KeyboardHelpers.releaseShiftRight { targetDetails = [] } + [ Selector.attribute (Key.tabbable True) ] + + +releaseShiftLeft : TestContext marker -> TestContext marker +releaseShiftLeft = + KeyboardHelpers.releaseShiftLeft { targetDetails = [] } + [ Selector.attribute (Key.tabbable True) ] + + +mouseDown : String -> TestContext marker -> TestContext marker +mouseDown word = + MouseHelpers.cancelableMouseDown [ Selector.containing [ Selector.text word ] ] + + +mouseUp : String -> TestContext marker -> TestContext marker +mouseUp word = + MouseHelpers.cancelableMouseUp [ Selector.containing [ Selector.text word ] ] + + +mouseOver : String -> TestContext marker -> TestContext marker +mouseOver word = + MouseHelpers.cancelableMouseOver [ Selector.containing [ Selector.text word ] ] marker : Tool ()