packages feed

monomer-1.4.0.0: test/unit/Monomer/Widgets/Singles/TextFieldSpec.hs

{-|
Module      : Monomer.Widgets.Singles.TextFieldSpec
Copyright   : (c) 2018 Francisco Vallarino
License     : BSD-3-Clause (see the LICENSE file)
Maintainer  : fjvallarino@gmail.com
Stability   : experimental
Portability : non-portable

Unit tests for TextField widget.
-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}

module Monomer.Widgets.Singles.TextFieldSpec (spec) where

import Control.Lens ((&), (^.), (.~))
import Control.Lens.TH (abbreviatedFields, makeLensesWith)
import Data.Default
import Data.Text (Text)
import Test.Hspec

import qualified Data.Sequence as Seq

import Monomer.Core
import Monomer.Core.Combinators
import Monomer.Event
import Monomer.TestUtil
import Monomer.TestEventUtil
import Monomer.Widgets.Containers.Stack
import Monomer.Widgets.Singles.TextField

import qualified Monomer.Lens as L

data TestEvt
  = TextChanged Text
  | GotFocus Path
  | LostFocus Path
  deriving (Eq, Show)

newtype TestModel = TestModel {
  _tmTextValue :: Text
} deriving (Eq, Show)

makeLensesWith abbreviatedFields ''TestModel

spec :: Spec
spec = describe "TextField" $ do
  handleEvent
  handleEventValue
  handleEventMouseSelect
  handleEventHistory
  handleEventMouseDrag
  handleEventWheel
  handleEventReadOnly
  getSizeReq

handleEvent :: Spec
handleEvent = describe "handleEvent" $ do
  it "should input an 'a'" $ do
    model [evtT "a"] ^. textValue `shouldBe` "a"
    ctx [evtT "a"] ^. L.renderSchedule `shouldSatisfy` (==1) . length

  it "should input 'ababa', remove the middle 'a' and input 'c'" $ do
    let steps = [evtT "ababa", moveCharL, moveCharL, evtK keyBackspace, evtT "c"]
    model steps ^. textValue `shouldBe` "abcba"

  it "should input 'ababa', select last two and input 'c'" $ do
    let steps = [evtT "ababa", selCharL, selCharL, selCharL, evtT "c"]
    model steps ^. textValue `shouldBe` "abc"

  it "should input 'This is a dog', move to beginning, select first word and input 'that'" $ do
    let str = "This is a dog"
    let steps = [evtT str, moveWordL, moveWordL, moveWordL, moveWordL, selWordR, evtT "that"]
    model steps ^. textValue `shouldBe` "that is a dog"

  it "should input 'This is a dog', select one word, deselect and input 'big '" $ do
    let str = "This is a dog"
    let steps = [evtT str, selWordL, moveCharL, evtT "big "]
    model steps ^. textValue `shouldBe` "This is a big dog"

  it "should input 'This string very long text is', and reject ' invalid' since maxLength == 40" $ do
    let str = "This string very very long text is"
    let steps = [evtT str, evtT " invalid"]
    model steps ^. textValue `shouldBe` "This string very very long text is"

  it "should input 'This is text', select all and input 'No'" $ do
    let str = "This is text"
    let steps = [evtT str, evtKG keyA, evtT "No"]
    model steps ^. textValue `shouldBe` "No"

  it "should input 'This is text', receive focus (with select on Focus) and input 'No'" $ do
    let str = "This is text"
    let steps = [evtT str, evtFocus, evtT "No"]
    model steps ^. textValue `shouldBe` "No"

  it "should copy and paste text around" $ do
    let str = "This is some long text"
    let steps = [evtT str, selWordL, selWordL, selCharL, evtKG keyC, moveWordL, moveWordL, moveCharL, evtKG keyV]

    testInVideoSubSystem $
      model steps ^. textValue `shouldBe` "This long text is some long text"

  it "should cut and paste text around" $ do
    let str = "This is long text"
    let steps = [evtT str, selWordL, selCharL, evtKG keyX, moveWordL, moveWordL, moveCharL, evtKG keyV]

    testInVideoSubSystem $
      model steps ^. textValue `shouldBe` "This text is long"

  it "should generate an event when focus is received" $ do
    events [evtFocus] `shouldBe` Seq.singleton (GotFocus emptyPath)
    ctx [evtFocus] ^. L.renderSchedule `shouldSatisfy` (==1) . length

  it "should generate an event when focus is lost" $ do
    events [evtBlur] `shouldBe` Seq.singleton (LostFocus emptyPath)
    ctx [evtFocus, evtBlur] ^. L.renderSchedule `shouldSatisfy` null

  where
    wenv = mockWenv (TestModel "")
    txtCfg = [maxLength 40, selectOnFocus, onFocus GotFocus, onBlur LostFocus]
    txtNode = textField_ textValue txtCfg
    model es = nodeHandleEventModel wenv es txtNode
    events es = nodeHandleEventEvts wenv es txtNode
    ctx evts = nodeHandleEventCtx wenv evts txtNode

handleEventValue :: Spec
handleEventValue = describe "handleEventValue" $ do
  it "should input an 'ab'" $
    evts [evtT "a", evtT "b"] `shouldBe` Seq.fromList [TextChanged "a", TextChanged "ab"]

  it "should input 'this is a dog', input '?', move to beginning and input 'Is '" $ do
    let str = "this is a dog"
    let steps = [evtT str, evtT "?", moveLineL, evtT "Is "]
    lastEvt steps `shouldBe` TextChanged "Is this is a dog?"

  it "should input 'This is a dog', move before 'is', select 'is', deselect it and input 'nt'" $ do
    let str = "This is a dog"
    let steps = [evtT str, moveWordL, moveWordL, moveWordL, selWordR, moveCharR, evtT "n't"]
    lastEvt steps `shouldBe` TextChanged "This isn't a dog"

  it "should input 'This is a dog', remove one word and input 'bird'" $ do
    let str = "This is a dog"
    let steps = [evtT str, delWordL, evtT "cat"]
    lastEvt steps `shouldBe` TextChanged "This is a cat"

  it "should input 'This is a dog', select to beginning and input 'No'" $ do
    let str = "This is a dog"
    let steps = [evtT str, selLineL, evtT "No"]
    lastEvt steps `shouldBe` TextChanged "No"

  it "should input 'This is a dog', move to beginning, select until end and input 'No'" $ do
    let str = "This is a dog"
    let steps = [evtT str, moveLineL, selLineR, evtT "No"]
    lastEvt steps `shouldBe` TextChanged "No"

  it "should input 'a', move to beginning, input 'H', move to end and input 't'" $ do
    let steps = [evtT "a", evtK keyHome, evtT "H", evtK keyEnd, evtT "t"]
    lastEvt steps `shouldBe` TextChanged "Hat"

  it "should input 'abc', select to beginning, input 'def', move back twice, select to end and input 'dd'" $ do
    let steps = [evtT "abc", evtKCS keyHome, evtT "def", moveCharL, moveCharL, evtKCS keyEnd, evtT "dd"]
    lastEvt steps `shouldBe` TextChanged "ddd"

  it "should input 'abc123', move to beginning, select three letters, copy, move to end, paste" $ do
    let steps = [evtT "abc123", evtKC keyHome, selCharR, selCharR, selCharR, evtKG keyC, evtK keyEnd, evtKG keyV]

    testInVideoSubSystem $
      lastEvt steps `shouldBe` TextChanged "abc123abc"

  where
    wenv = mockWenv (TestModel "")
    txtNode = textFieldV "" TextChanged
    evts es = nodeHandleEventEvts wenv es txtNode
    lastIdx es = Seq.index es (Seq.length es - 1)
    lastEvt es = lastIdx (evts es)

handleEventMouseSelect :: Spec
handleEventMouseSelect = describe "handleEventMouseSelect" $ do
  it "should add text at the end, since click + drag started outside of viewport" $ do
    let str = "This is text"
    let selStart = Point 50 100
    let selEnd = Point 120 10
    let steps = [evtT str, evtPress selStart, evtMove selEnd, evtRelease selEnd, evtT "!"]
    model steps ^. textValue `shouldBe` "This is text!"

  it "should drag around and input 'Text'" $ do
    let str = ""
    let selStart = Point 50 10
    let selMid1 = Point 0 10
    let selMid2 = Point 200 10
    let selMid3 = Point (-200) 10
    let selEnd = Point 120 10
    let moves = [evtMove selMid1, evtMove selMid2, evtMove selMid3, evtMove selEnd]
    let steps = [evtT str, evtPress selStart] ++ moves ++ [evtRelease selEnd, evtT "Text"]
    model steps ^. textValue `shouldBe` "Text"

  it "should input 'This is text', select 'is text' and input 'test'" $ do
    let str = "This is text"
    let selStart = Point 40 10
    let selEnd = Point 120 10
    let steps = [evtT str, evtPress selStart, evtMove selEnd, evtRelease selEnd, evtT "test"]
    model steps ^. textValue `shouldBe` "This test"

  it "should input 'This is text', select all from beginning and input 'New'" $ do
    let str = "This is new"
    let selStart = Point 0 10
    let selEnd = Point 200 10
    let steps = [evtT str, evtPress selStart, evtMove selEnd, evtRelease selEnd, evtT "New"]
    model steps ^. textValue `shouldBe` "New"

  it "should input 'This is text', select all from the end and input 'New'" $ do
    let str = "This is"
    let selStart = Point 70 10
    let selEnd = Point 0 10
    let steps = [evtT str, evtPress selStart, evtMove selEnd, evtRelease selEnd, evtT "New"]
    model steps ^. textValue `shouldBe` "New"

  where
    wenv = mockWenvEvtUnit (TestModel "")
    txtNode = vstack [
        hstack [
          textField textValue `styleBasic` [width 105],
          hstack []
        ]
      ]
    model es = nodeHandleEventModel wenv es txtNode
    events es = nodeHandleEventEvts wenv es txtNode

handleEventHistory :: Spec
handleEventHistory = describe "handleEventHistory" $ do
  it "should input 'This is text', have the last word removed and then undo" $ do
    let str = "This is text"
    let steps1 = [evtT str, evtKA keyBackspace]
    let steps2 = steps1 ++ [evtKG keyZ]
    model steps1 ^. textValue `shouldBe` "This is "
    model steps2 ^. textValue `shouldBe` "This is text"
    lastEvt steps2 `shouldBe` TextChanged "This is text"

  it "should input 'This is text', have the last two words removed, undo and redo" $ do
    let str = "This is text"
    let steps1 = [evtT str, evtKA keyBackspace, evtKA keyBackspace]
    let steps2 = steps1 ++ [evtKG keyZ, evtKG keyZ, evtKGS keyZ]
    model steps1 ^. textValue `shouldBe` "This "
    model steps2 ^. textValue `shouldBe` "This is "
    lastEvt steps2 `shouldBe` TextChanged "This is "

  it "should input 'This is just a string', play around with history and come end up with 'This is just text" $ do
    let str = "This is just a string"
    let steps1 = [evtT str, evtKA keyBackspace, evtKA keyBackspace, evtKA keyBackspace, evtKA keyBackspace, evtKA keyBackspace]
    let steps2 = steps1 ++ [evtKG keyZ, evtKG keyZ, evtKG keyZ, evtKG keyZ, evtKG keyZ, evtKGS keyZ, evtKGS keyZ, evtT "text"]
    model steps1 ^. textValue `shouldBe` ""
    model steps2 ^. textValue `shouldBe` "This is just text"
    lastEvt steps2 `shouldBe` TextChanged "This is just text"

  it "should input 'This is text', remove two words, undo, input 'not' and fail to redo" $ do
    let str = "This is text"
    let steps1 = [evtT str, evtKA keyBackspace, evtKA keyBackspace]
    let steps2 = steps1 ++ [evtKG keyZ, evtT "not", evtKGS keyZ]
    model steps1 ^. textValue `shouldBe` "This "
    model steps2 ^. textValue `shouldBe` "This is not"
    lastEvt steps2 `shouldBe` TextChanged "This is not"

  where
    wenv = mockWenv (TestModel "")
    txtCfg = [onChange TextChanged, selectOnFocus, onFocus GotFocus, onBlur LostFocus]
    txtNode = textField_ textValue txtCfg
    model es = nodeHandleEventModel wenv es txtNode
    evts es = nodeHandleEventEvts wenv es txtNode
    lastIdx es = Seq.index es (Seq.length es - 1)
    lastEvt es = lastIdx (evts es)

handleEventMouseDrag :: Spec
handleEventMouseDrag = describe "handleEventMouseDrag" $ do
  it "should ignore shift+drag events" $ do
    let selStart = Point 50 10
    let selEnd = Point 50 (-70)
    let steps = [evtPress selStart, evtMove selEnd, evtRelease selEnd]
    model steps ^. textValue `shouldBe` ""

  where
    wenv = mockWenvEvtUnit (TestModel "")
      & L.inputStatus . L.keyMod . L.leftShift .~ True
    model es = nodeHandleEventModel wenv es (textField textValue)

handleEventWheel :: Spec
handleEventWheel = describe "handleEventWheel" $ do
  it "should ignore wheel events" $ do
    let p = Point 50 10
    let steps1 = [WheelScroll p (Point 0 (-8000)) WheelNormal]
    model steps1 ^. textValue `shouldBe` ""

  where
    wenv = mockWenvEvtUnit (TestModel "")
    model es = nodeHandleEventModel wenv es (textField textValue)

handleEventReadOnly :: Spec
handleEventReadOnly = describe "handleEventReadOnly" $ do
  it "should ignore text input" $ do
    model [evtT "a"] ^. textValue `shouldBe` initText
  
  it "should ignore cut" $ do
    model [selWordR, evtKG keyX] ^. textValue `shouldBe` initText
  
  it "should ignore paste" $ do
    model [selWordR, evtKG keyV] ^. textValue `shouldBe` initText
  
  where
    initText = "hello"
    wenv = mockWenv (TestModel initText)
    txtCfg = [readOnly :: TextFieldCfg TestModel TestEvt]
    txtNode = textField_ textValue txtCfg
    model es = nodeHandleEventModel wenv es txtNode

getSizeReq :: Spec
getSizeReq = describe "getSizeReq" $ do
  it "should return width = Flex 100 1" $
    sizeReqW `shouldBe` expandSize 100 1

  it "should return height = Fixed 20" $
    sizeReqH `shouldBe` fixedSize 20

  where
    wenv = mockWenvEvtUnit (TestModel "Test value")
    (sizeReqW, sizeReqH) = nodeGetSizeReq wenv (textField textValue)