support paragraph wrapping case

This commit is contained in:
Elm UI Automation 2020-05-13 21:13:36 -04:00
parent 10952369fb
commit fe73cbd688
6 changed files with 340 additions and 136 deletions

View File

@ -20,6 +20,7 @@ module Testable exposing
, getIds
, getSpacing
, getSpacingFromAttributes
, lessThanOrEqual
, runTests
, textHeight
, toElement
@ -192,9 +193,21 @@ type alias TextMetrics =
}
{-| The font metrics we currently have are `actual`, meaning for the text actually rendered, not the font as a whole.
We also know the font size is 20, so we're just going to return 20.
TODO: improve font metric collection using something like opentype.js
-}
textHeight metrics =
metrics.actualBoundingBoxAscent
+ metrics.actualBoundingBoxDescent
let
actualHeight =
metrics.actualBoundingBoxAscent
+ metrics.actualBoundingBoxDescent
in
-- actualHeight / 20
20
{-| -}
@ -238,6 +251,19 @@ type alias LayoutTest =
{- Expectations -}
floatToString : Float -> String
floatToString x =
String.fromFloat (toFloat (round (x * 100)) / 100)
lessThanOrEqual : String -> Float -> Float -> LayoutExpectation
lessThanOrEqual label one two =
Expect
{ description = label ++ " " ++ floatToString one ++ " <= " ++ floatToString two
, result = one <= two
}
equal : a -> a -> LayoutExpectation
equal one two =
Expect
@ -324,7 +350,12 @@ toElement el =
toHtml : Element msg -> Html msg
toHtml el =
Element.layout [ idAttr "0" ] <|
Element.layout
[ idAttr "0"
, Element.width (Element.px 1000)
, Element.height (Element.px 1000)
]
<|
renderElement [ 0, 0 ] el

View File

@ -20,6 +20,7 @@ module Testable.Element exposing
, isVisible
, label
, layout
, link
, maximum
, minimum
, moveDown
@ -97,12 +98,36 @@ none =
paragraph : List (Testable.Attr msg) -> List (Testable.Element msg) -> Testable.Element msg
paragraph attrs =
Testable.Paragraph (implicitWidthHeightShrink attrs)
let
withImplicits =
implicitTest (widthHelper Nothing Nothing (Fill 1))
:: implicitTest (heightHelper Nothing Nothing Shrink)
:: attrs
in
Testable.Paragraph (skipOverridden withImplicits)
textColumn : List (Testable.Attr msg) -> List (Testable.Element msg) -> Testable.Element msg
textColumn attrs =
Testable.TextColumn (implicitWidthHeightShrink attrs)
let
withImplicits =
implicitTest (widthHelper (Just 500) (Just 750) (Fill 1))
:: implicitTest (heightHelper Nothing Nothing Shrink)
:: attrs
in
Testable.TextColumn (skipOverridden withImplicits)
link :
List (Testable.Attr msg)
->
{ url : String
, label : Testable.Element msg
}
-> Testable.Element msg
link attrs details =
-- TODO: this is not actually a link element!!
Testable.El (implicitWidthHeightShrink attrs) details.label
{-| Old labeling mechanism that i removed to hastily
@ -344,7 +369,10 @@ widthHelper maybeMin maybeMax len =
)
, test =
\found ->
[ Testable.true "exact width is exact" (floor found.self.bbox.width == val)
[ expectRoundedEquality
{ expected = toFloat val
, found = found.self.bbox.width
}
, Testable.true "min/max is upheld" (minMaxTest (floor found.self.bbox.width))
]
}
@ -400,6 +428,9 @@ widthHelper maybeMin maybeMax len =
Testable.true "element has fill width" <|
(floor spacePerPortion == floor context.self.bbox.width)
|| minMaxTest (floor context.self.bbox.width)
, Testable.lessThanOrEqual "not larger than parent"
context.self.bbox.width
context.parent.bbox.width
]
}
@ -434,6 +465,9 @@ widthHelper maybeMin maybeMax len =
{ expected = totalChildren + horizontalPadding
, found = context.self.bbox.width
}
, Testable.lessThanOrEqual "not larger than parent"
context.self.bbox.width
context.parent.bbox.width
]
Testable.Row rowAttrs _ ->
@ -462,6 +496,9 @@ widthHelper maybeMin maybeMax len =
{ expected = totalChildren + horizontalPadding + totalSpacing
, found = context.self.bbox.width
}
, Testable.lessThanOrEqual "not larger than parent"
context.self.bbox.width
context.parent.bbox.width
]
Testable.Column _ _ ->
@ -490,10 +527,41 @@ widthHelper maybeMin maybeMax len =
{ expected = Maybe.withDefault 0 (List.maximum allChildren)
, found = context.self.bbox.width
}
, Testable.lessThanOrEqual "not larger than parent"
context.self.bbox.width
context.parent.bbox.width
]
Testable.TextColumn _ _ ->
[]
-- The width of the column is the width of the widest child.
let
childWidth child =
child.bbox.width
+ context.self.bbox.padding.left
+ context.self.bbox.padding.right
textChildren =
List.map
(\txt ->
txt.width
+ context.self.bbox.padding.left
+ context.self.bbox.padding.right
)
context.self.textMetrics
allChildren =
context.children
|> List.map childWidth
|> List.append textChildren
in
[ expectRoundedEquality
{ expected = Maybe.withDefault 0 (List.maximum allChildren)
, found = context.self.bbox.width
}
, Testable.lessThanOrEqual "not larger than parent"
context.self.bbox.width
context.parent.bbox.width
]
Testable.Paragraph _ _ ->
-- This should be the size it's text,
@ -515,6 +583,9 @@ widthHelper maybeMin maybeMax len =
{ expected = totalChildren + horizontalPadding
, found = context.self.bbox.width
}
, Testable.lessThanOrEqual "not larger than parent"
context.self.bbox.width
context.parent.bbox.width
]
Testable.Text _ ->
@ -593,8 +664,10 @@ heightHelper maybeMin maybeMax len =
, id = Testable.IsHeight
, test =
\found ->
[ Testable.true ("exact height is exact: " ++ String.fromInt (floor found.self.bbox.height) ++ "," ++ String.fromInt val)
(floor found.self.bbox.height == val)
[ expectRoundedEquality
{ expected = toFloat val
, found = found.self.bbox.height
}
, Testable.true "min/max holds true"
(minMaxTest (floor found.self.bbox.height))
]
@ -665,46 +738,60 @@ heightHelper maybeMin maybeMax len =
Testable.El _ _ ->
let
childHeight child =
-- TODO: add margin values to heights
child.bbox.height
totalChildren =
context.children
|> List.map childHeight
|> List.append (List.map Testable.textHeight context.self.textMetrics)
|> List.sum
verticalPadding =
context.self.bbox.padding.top + context.self.bbox.padding.bottom
spacingValue =
toFloat context.parentSpacing * (toFloat (List.length context.children) - 1)
in
[ expectRoundedEquality
{ expected = totalChildren + verticalPadding + spacingValue
, found = context.self.bbox.height
}
[ if List.isEmpty context.children then
-- context.self.textMetrics
-- |> List.map Testable.textHeight
-- |> List.sum
-- TODO: apparently the font metrics we have are for the literal characters rendered
-- not for the font itself.
-- so for now, we are stubbing this in as 20, which is the default font size.
Testable.true "expected multiple of 20"
((round context.self.bbox.height |> modBy 20) == 0)
else
expectRoundedEquality
{ expected =
context.children
|> List.map childHeight
|> List.sum
|> (\h -> h + context.self.bbox.padding.top + context.self.bbox.padding.bottom)
, found = context.self.bbox.height
}
, Testable.lessThanOrEqual "not larger than parent"
context.self.bbox.height
context.parent.bbox.height
]
Testable.Row _ _ ->
let
childHeight child =
child.bbox.height
totalChildren =
context.children
|> List.map childHeight
|> List.append (List.map Testable.textHeight context.self.textMetrics)
|> List.maximum
|> Maybe.withDefault 0
verticalPadding =
context.self.bbox.padding.top + context.self.bbox.padding.bottom
in
[ expectRoundedEquality
{ expected = totalChildren + verticalPadding
, found = context.self.bbox.height
}
[ if List.isEmpty context.children then
-- context.self.textMetrics
-- |> List.map Testable.textHeight
-- |> List.sum
-- TODO: apparently the font metrics we have are for the literal characters rendered
-- not for the font itself.
-- so for now, we are stubbing this in as 20, which is the default font size.
Testable.true "expected multiple of 20"
((round context.self.bbox.height |> modBy 20) == 0)
else
expectRoundedEquality
{ expected =
context.children
|> List.map childHeight
|> List.maximum
|> Maybe.withDefault 0
|> (\h -> h + context.self.bbox.padding.top + context.self.bbox.padding.bottom)
, found = context.self.bbox.height
}
, Testable.lessThanOrEqual "not larger than parent"
context.self.bbox.height
context.parent.bbox.height
]
Testable.Column colAttrs _ ->
@ -731,13 +818,22 @@ heightHelper maybeMin maybeMax len =
{ expected = totalChildren + verticalPadding + totalSpacing
, found = context.self.bbox.height
}
, Testable.lessThanOrEqual "not larger than parent"
context.self.bbox.height
context.parent.bbox.height
]
Testable.TextColumn _ _ ->
[]
[ Testable.lessThanOrEqual "not larger than parent"
context.self.bbox.height
context.parent.bbox.height
]
Testable.Paragraph _ _ ->
[]
[ Testable.lessThanOrEqual "not larger than parent"
context.self.bbox.height
context.parent.bbox.height
]
Testable.Text _ ->
[]

View File

@ -1,4 +1,4 @@
module Testable.Element.Font exposing (color)
module Testable.Element.Font exposing (bold, color, size)
{-| -}
@ -8,6 +8,11 @@ import Element.Font as Font
import Testable
bold : Testable.Attr msg
bold =
Testable.Attr Font.bold
color : Color -> Testable.Attr msg
color clr =
Testable.LabeledTest

View File

@ -4,7 +4,8 @@ module Testable.Generator exposing (..)
import Testable
import Testable.Element exposing (..)
import Testable.Runner
import Testable.Element.Background as Background
import Testable.Element.Font as Font
{-| Given a list of attributes, generate every context this list of attributes could be in.
@ -17,11 +18,8 @@ So, this means,
-}
element : String -> List (Testable.Attr msg) -> List ( String, Testable.Element msg )
element label attrs =
-- List.map
-- (\c ->
-- ( label, el attrs c )
-- )
-- content
-- [ ( label, paragraph attrs [ short ] )
-- ]
mapEveryCombo
(\makeLayout child ->
( label, makeLayout attrs child )
@ -56,9 +54,33 @@ layouts =
]
nearbys =
[ inFront
, above
, onLeft
, onRight
, below
, behindContent
]
contents =
[ none
, text "short and small"
, text "There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc."
, el [ width (px 50), height (px 50) ] none
, text short
, text lorem
, el
[ width (px 50)
, height (px 50)
, Background.color (rgb (240 / 255) 0 (245 / 255))
, Font.color (rgb 1 1 1)
]
none
]
short =
"short and small"
lorem =
"There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc."

View File

@ -52,10 +52,11 @@ program tests =
( { current = current
, upcoming = upcoming
, finished = []
, selected = 0
, highlightDomId = Nothing
}
, Task.perform (always Analyze)
(Process.sleep 32
(Process.sleep 64
|> Task.andThen
(always Time.now)
)
@ -77,6 +78,7 @@ type alias Model msg =
{ current : Maybe ( String, Testable.Element msg )
, upcoming : List ( String, Testable.Element msg )
, finished : List (WithResults (Testable.Element msg))
, selected : Int
, highlightDomId : Maybe String
}
@ -131,6 +133,7 @@ encodeForReport withResults =
type Msg
= NoOp
| Analyze
| Select Int
| HighlightDomID (Maybe String)
| RefreshBoundingBox
(List
@ -166,6 +169,11 @@ update msg model =
, Cmd.none
)
Select index ->
( { model | selected = index }
, Cmd.none
)
RefreshBoundingBox boxes ->
case model.current of
Nothing ->
@ -208,7 +216,7 @@ update msg model =
, upcoming = remaining
}
, Task.perform (always Analyze)
(Process.sleep 32
(Process.sleep 64
|> Task.andThen
(always Time.now)
)
@ -232,58 +240,60 @@ view model =
case model.current of
Nothing ->
if List.isEmpty model.upcoming then
case model.finished of
[] ->
Element.layout [] <|
Element.column
[ Element.spacing 20
, Element.padding 20
, Element.width (Element.px 800)
]
[ Element.none ]
let
selected =
getByIndex model.selected model.finished
finished :: remaining ->
Element.layout
[ Font.size 16
, Element.inFront (Element.html (viewElementHighlight model))
-- |> Debug.log "selected"
in
Element.layout
[ Font.size 16
, Element.inFront (Element.html (viewElementHighlight model))
, Element.height Element.fill
]
<|
Element.row [ Element.width Element.fill, Element.height Element.fill ]
[ Element.el
[ Element.width
(Element.fill
|> Element.maximum 900
)
, Element.alignTop
, Element.height Element.fill
, Element.scrollbars
]
<|
Element.row [ Element.width Element.fill, Element.height Element.fill ]
[ Element.el
[ Element.width
(Element.fill
|> Element.maximum 900)
, Element.alignTop
, Element.height Element.fill
, Element.scrollbars
]
(Element.el
[ Element.centerX
, Element.padding 100
, Border.dashed
, Border.width 2
, Border.color palette.lightGrey
, Font.size 20
, Element.inFront
(Element.el
[ Element.centerX
, Element.padding 100
, Border.dashed
, Border.width 2
, Border.color palette.lightGrey
, Font.size 20
, Element.inFront
(Element.el
[ Font.size 14
, Font.color palette.lightGrey
]
(Element.text "test case")
)
[ Font.size 14
, Font.color palette.lightGrey
]
(Testable.toElement finished.element)
(Element.text "test case")
)
, Element.column
[ Element.spacing 20
, Element.padding 20
, Element.width Element.fill
, Element.height Element.fill
, Element.scrollbarY
]
(List.map viewResult (finished :: remaining))
]
(case selected of
Nothing ->
Element.text "nothing selected"
Just sel ->
Testable.toElement sel.element
)
)
, Element.column
[ Element.spacing 20
, Element.padding 20
, Element.width Element.fill
, Element.height Element.fill
, Element.scrollbarY
]
(List.indexedMap (viewResult model.selected) model.finished)
]
else
Html.text ""
@ -292,6 +302,20 @@ view model =
Testable.toHtml current
getByIndex i ls =
List.foldl
(\elem ( index, found ) ->
if i == index then
( index + 1, Just elem )
else
( index + 1, found )
)
( 0, Nothing )
ls
|> Tuple.second
viewElementHighlight model =
case model.highlightDomId of
Nothing ->
@ -322,35 +346,58 @@ viewElementHighlight model =
]
viewResult : WithResults (Testable.Element Msg) -> Element.Element Msg
viewResult testable =
let
isExpectationPassing result =
case result of
Testable.Todo label ->
True
viewResult : Int -> Int -> WithResults (Testable.Element Msg) -> Element.Element Msg
viewResult selectedIndex index testable =
if index == selectedIndex then
Element.column
[ Element.alignLeft
, Element.spacing 16
]
[ Element.el [ Font.size 24 ] (Element.text testable.label)
, Element.column
[ Element.alignLeft, Element.spacing 16 ]
(testable.results
|> groupBy .elementDomId
|> List.map (expandDetails >> viewLayoutTestGroup)
)
]
Testable.Expect details ->
details.result
else
let
isExpectationPassing result =
case result of
Testable.Todo label ->
True
isPassing layoutTest =
List.any isExpectationPassing layoutTest.expectations
Testable.Expect details ->
details.result
( passing, failing ) =
List.partition isPassing testable.results
in
Element.column
[ Element.alignLeft
, Element.spacing 16
]
[ Element.el [ Font.size 24 ] (Element.text testable.label)
, Element.column
[ Element.alignLeft, Element.spacing 16 ]
(testable.results
|> groupBy .elementDomId
|> List.map (expandDetails >> viewLayoutTestGroup)
)
]
isPassing layoutTest =
List.all isExpectationPassing layoutTest.expectations
( passing, failing ) =
List.partition isPassing testable.results
in
Element.column
[ Element.alignLeft
, Element.spacing 16
, Element.pointer
, Events.onClick (Select index)
]
[ Element.row [ Element.spacing 16 ]
[ Element.el [ Font.size 24 ] (Element.text testable.label)
, Element.text (String.fromInt (List.length passing) ++ " passing")
, let
failingCount =
List.length failing
in
if failingCount == 0 then
Element.none
else
Element.text (String.fromInt (List.length failing) ++ " failing")
]
]
expandDetails group =

View File

@ -2,6 +2,7 @@ module Tests.Run exposing (main)
{-| -}
import Testable.Generator
import Testable.Runner
import Tests.Basic
import Tests.ColumnAlignment
@ -10,20 +11,22 @@ import Tests.ElementAlignment
import Tests.Nearby
import Tests.RowAlignment
import Tests.RowSpacing
import Tests.TextWrapping
import Tests.Transparency
main : Testable.Runner.TestableProgram
main =
Testable.Runner.program
[ Tuple.pair "Basic Element" Tests.Basic.view
, Tuple.pair "Nearby" Tests.Nearby.view
, Tuple.pair "Element Alignment" Tests.ElementAlignment.view
, Tuple.pair "Transparency" Tests.Transparency.view
, Tuple.pair "Column Alignment" Tests.ColumnAlignment.view
-- This has 12k cases, so it runs slow and sometimes crashes IE
, Tuple.pair "Row Alignment" Tests.RowAlignment.view
, Tuple.pair "Column Spacing" Tests.ColumnSpacing.view
, Tuple.pair "Row Spacing" Tests.RowSpacing.view
(Testable.Runner.program << List.concat)
[ -- Testable.Generator.element "Basics" []
-- Tuple.pair "Basic Element" Tests.Basic.view
-- , Tuple.pair "Nearby" Tests.Nearby.view
-- , Tuple.pair "Element Alignment" Tests.ElementAlignment.view
-- , Tuple.pair "Transparency" Tests.Transparency.view
-- , Tuple.pair "Column Alignment" Tests.ColumnAlignment.view
-- -- This has 12k cases, so it runs slow and sometimes crashes IE
-- , Tuple.pair "Row Alignment" Tests.RowAlignment.view
-- , Tuple.pair "Column Spacing" Tests.ColumnSpacing.view
-- , Tuple.pair "Row Spacing" Tests.RowSpacing.view
[ Tuple.pair "Paragraph wrapping" Tests.TextWrapping.view ]
]