Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
matrix:
# Run on Mac and Linux, for multiple Node versions
os: [macos-latest, ubuntu-latest]
node: ['8', '10', '12', '14']
node: ['16', '18', '20', '22', '24']

env:
ELM_HOME: '${{ github.workspace }}/elm-home'
Expand All @@ -32,7 +32,7 @@ jobs:

# Install elm and cache ELM_HOME
- name: Install elm
uses: mpizenberg/elm-tooling-action@v1.2
uses: mpizenberg/elm-tooling-action@v1.7
with:
cache-key: tests-${{ matrix.os }}-node${{ matrix.node }}-0
cache-restore-key: tests-${{ matrix.os }}-node${{ matrix.node }}
Expand All @@ -50,7 +50,7 @@ jobs:

# Install elm-format
- name: Install elm-format
uses: mpizenberg/elm-tooling-action@v1.2
uses: mpizenberg/elm-tooling-action@v1.7
with:
cache-key: format-${{ matrix.os }}-node${{ matrix.node }}-0
cache-restore-key: format-${{ matrix.os }}-node${{ matrix.node }}
Expand Down
37 changes: 19 additions & 18 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
## Releases

| Version | Notes |
| ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| [**2.2.0**](https://github.com/elm-explorations/test/tree/2.2.0) | `Fuzz`: Add `Fuzz.filterMap`
| [**2.1.2**](https://github.com/elm-explorations/test/tree/2.1.2) | `Fuzz`: Remove arbitrary limit on amount of randomness drawn
| [**2.1.1**](https://github.com/elm-explorations/test/tree/2.1.1) | `Test.Html.Query`: Change how boolean attributes are rendered
| [**2.1.0**](https://github.com/elm-explorations/test/tree/2.1.0) | Add `Test.Html.Selector.exactText` |
| [**2.0.1**](https://github.com/elm-explorations/test/tree/2.0.1) | Documentation fixes |
| [**2.0.0**](https://github.com/elm-explorations/test/tree/2.0.0) | Reimplements fuzzing+shrinking, adds fuzzer distribution reporting. Most notably readds `Fuzz.andThen`. See ["Changes in 2.0.0"](#changes-in-200) |
| [**1.2.2**](https://github.com/elm-explorations/test/tree/1.2.2) | Fixes a crash in `Test.Html` when the HTML contains nested `Html.Lazy` nodes. [#78](https://github.com/elm-explorations/test/issues/78) |
| [**1.2.1**](https://github.com/elm-explorations/test/tree/1.2.1) | Many small documentation fixes. Improve error messages when failing to simulate an event. |
| [**1.2.0**](https://github.com/elm-explorations/test/tree/1.2.0) | Add HTML tests. [#41](https://github.com/elm-explorations/test/pull/41) |
| [**1.0.0**](https://github.com/elm-explorations/test/tree/1.0.0) | Update for Elm 0.19. Remove `Fuzz.andThen`, `Fuzz.conditional`, and `Test.Runner.getFailure`. Fail on equating floats to encourage checks with tolerance. `Test.Runner.fuzz` now returns a `Result`. |
| Version | Notes |
| ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| [**2.2.1**](https://github.com/elm-explorations/test/tree/2.2.1) | `Test.Html.Query`: Support classes set using the `class` attribute |
| [**2.2.0**](https://github.com/elm-explorations/test/tree/2.2.0) | `Fuzz`: Add `Fuzz.filterMap` |
| [**2.1.2**](https://github.com/elm-explorations/test/tree/2.1.2) | `Fuzz`: Remove arbitrary limit on amount of randomness drawn |
| [**2.1.1**](https://github.com/elm-explorations/test/tree/2.1.1) | `Test.Html.Query`: Change how boolean attributes are rendered |
| [**2.1.0**](https://github.com/elm-explorations/test/tree/2.1.0) | Add `Test.Html.Selector.exactText` |
| [**2.0.1**](https://github.com/elm-explorations/test/tree/2.0.1) | Documentation fixes |
| [**2.0.0**](https://github.com/elm-explorations/test/tree/2.0.0) | Reimplements fuzzing+shrinking, adds fuzzer distribution reporting. Most notably readds `Fuzz.andThen`. See ["Changes in 2.0.0"](#changes-in-200) |
| [**1.2.2**](https://github.com/elm-explorations/test/tree/1.2.2) | Fixes a crash in `Test.Html` when the HTML contains nested `Html.Lazy` nodes. [#78](https://github.com/elm-explorations/test/issues/78) |
| [**1.2.1**](https://github.com/elm-explorations/test/tree/1.2.1) | Many small documentation fixes. Improve error messages when failing to simulate an event. |
| [**1.2.0**](https://github.com/elm-explorations/test/tree/1.2.0) | Add HTML tests. [#41](https://github.com/elm-explorations/test/pull/41) |
| [**1.0.0**](https://github.com/elm-explorations/test/tree/1.0.0) | Update for Elm 0.19. Remove `Fuzz.andThen`, `Fuzz.conditional`, and `Test.Runner.getFailure`. Fail on equating floats to encourage checks with tolerance. `Test.Runner.fuzz` now returns a `Result`. |
| renamed from **elm-community/elm-test** (below) to **elm-explorations/test** (above) | |
| [**4.0.0**](https://github.com/elm-community/elm-test/tree/4.0.0) | Add `only`, `skip`, `todo`; change `Fuzz.frequency` to fail rather than crash on bad input, disallow tests with blank or duplicate descriptions. |
| [**3.1.0**](https://github.com/elm-community/elm-test/tree/3.1.0) | Add `Expect.all` |
| [**3.0.0**](https://github.com/elm-community/elm-test/tree/3.0.0) | Update for Elm 0.18; switch the argument order of `Fuzz.andMap`. |
| [**2.1.0**](https://github.com/elm-community/elm-test/tree/2.1.0) | Switch to rose trees for `Fuzz.andThen`, other API additions. |
| [**2.0.0**](https://github.com/elm-community/elm-test/tree/2.0.0) | Scratch-rewrite to project-fuzzball |
| [**1.0.0**](https://github.com/elm-community/elm-test/tree/1.0.0) | ElmTest initial release |
| [**4.0.0**](https://github.com/elm-community/elm-test/tree/4.0.0) | Add `only`, `skip`, `todo`; change `Fuzz.frequency` to fail rather than crash on bad input, disallow tests with blank or duplicate descriptions. |
| [**3.1.0**](https://github.com/elm-community/elm-test/tree/3.1.0) | Add `Expect.all` |
| [**3.0.0**](https://github.com/elm-community/elm-test/tree/3.0.0) | Update for Elm 0.18; switch the argument order of `Fuzz.andMap`. |
| [**2.1.0**](https://github.com/elm-community/elm-test/tree/2.1.0) | Switch to rose trees for `Fuzz.andThen`, other API additions. |
| [**2.0.0**](https://github.com/elm-community/elm-test/tree/2.0.0) | Scratch-rewrite to project-fuzzball |
| [**1.0.0**](https://github.com/elm-community/elm-test/tree/1.0.0) | ElmTest initial release |

## Changes in 2.0.0

Expand Down
2 changes: 1 addition & 1 deletion elm.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "elm-explorations/test",
"summary": "Write unit and fuzz tests for Elm code.",
"license": "BSD-3-Clause",
"version": "2.2.0",
"version": "2.2.1",
"exposed-modules": [
"Test",
"Test.Runner",
Expand Down
24 changes: 24 additions & 0 deletions src/MicroListExtra.elm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module MicroListExtra exposing
, setAt
, splitWhen
, transpose
, unique
)


Expand Down Expand Up @@ -98,3 +99,26 @@ rowsLength listOfLists =

x :: _ ->
List.length x


unique : List a -> List a
unique list =
uniqueHelp identity [] list []


uniqueHelp : (a -> b) -> List b -> List a -> List a -> List a
uniqueHelp f existing remaining accumulator =
case remaining of
[] ->
List.reverse accumulator

first :: rest ->
let
computedFirst =
f first
in
if List.member computedFirst existing then
uniqueHelp f existing rest accumulator

else
uniqueHelp f (computedFirst :: existing) rest (first :: accumulator)
Comment on lines +106 to +124
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uniqueHelp function accepts a transformation function parameter f that's always called with identity. This suggests either the function was designed for future extensibility or was copied from a more generic implementation. Consider simplifying by removing the f parameter and directly using the list elements in the comparison, or if the flexibility is intentional for future use, add a comment explaining this design choice.

Suggested change
uniqueHelp identity [] list []
uniqueHelp : (a -> b) -> List b -> List a -> List a -> List a
uniqueHelp f existing remaining accumulator =
case remaining of
[] ->
List.reverse accumulator
first :: rest ->
let
computedFirst =
f first
in
if List.member computedFirst existing then
uniqueHelp f existing rest accumulator
else
uniqueHelp f (computedFirst :: existing) rest (first :: accumulator)
uniqueHelp [] list []
uniqueHelp : List a -> List a -> List a -> List a
uniqueHelp existing remaining accumulator =
case remaining of
[] ->
List.reverse accumulator
first :: rest ->
if List.member first existing then
uniqueHelp existing rest accumulator
else
uniqueHelp (first :: existing) rest (first :: accumulator)

Copilot uses AI. Check for mistakes.
51 changes: 47 additions & 4 deletions src/Test/Html/Internal/ElmHtml/InternalTypes.elm
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Test.Html.Internal.ElmHtml.InternalTypes exposing
( ElmHtml(..), TextTagRecord, NodeRecord, CustomNodeRecord, MarkdownNodeRecord
, Facts, Tagger, EventHandler, ElementKind(..)
, Attribute(..), AttributeRecord, NamespacedAttributeRecord, PropertyRecord, EventRecord
, Validation(..), validationMessage, validationFromMessage
, decodeElmHtml, emptyFacts, toElementKind, decodeAttribute
)

Expand All @@ -13,6 +14,8 @@ module Test.Html.Internal.ElmHtml.InternalTypes exposing

@docs Attribute, AttributeRecord, NamespacedAttributeRecord, PropertyRecord, EventRecord

@docs Validation, validationMessage, validationFromMessage

@docs decodeElmHtml, emptyFacts, toElementKind, decodeAttribute

-}
Expand Down Expand Up @@ -317,16 +320,56 @@ decodeStyles =
]


type Validation
= ClassVsClassNameValidation


classVsClassNameValidationMessage : String
classVsClassNameValidationMessage =
"Found the `class` attribute and the `className` property used in the same HTML node. This would result in unspecified behaviour, and elm-test wouldn't be able to reliably query for classnames. Please only use one of the two."


validationMessage : Validation -> String
validationMessage validation =
case validation of
ClassVsClassNameValidation ->
classVsClassNameValidationMessage


validationFromMessage : String -> Maybe Validation
validationFromMessage message =
if message == classVsClassNameValidationMessage then
Just ClassVsClassNameValidation

else
Nothing


{-| grab things from attributes via a decoder, then anything that isn't filtered on
the object
-}
decodeOthers : Json.Decode.Decoder a -> Json.Decode.Decoder (Dict String a)
decodeOthers otherDecoder =
decodeOthers : Json.Decode.Decoder a -> Maybe Validation -> Json.Decode.Decoder (Dict String a)
decodeOthers otherDecoder validation =
decodeAttributes otherDecoder
|> Json.Decode.andThen
(\attributes ->
decodeDictFilterMap otherDecoder
|> Json.Decode.map (filterKnownKeys >> Dict.union attributes)
|> (case validation of
Nothing ->
identity

Just ClassVsClassNameValidation ->
Json.Decode.andThen
(\dict ->
if Dict.member "class" dict && Dict.member "className" dict then
-- Due to Json.Decode.Error API we need to drop down to strings.
Json.Decode.fail classVsClassNameValidationMessage

else
Json.Decode.succeed dict
)
)
)


Expand Down Expand Up @@ -374,8 +417,8 @@ decodeFacts (HtmlContext taggers eventDecoder) =
decodeStyles
(decodeEvents (eventDecoder taggers))
(Json.Decode.maybe (Json.Decode.field attributeNamespaceKey Json.Decode.value))
(decodeOthers Json.Decode.string)
(decodeOthers Json.Decode.bool)
(decodeOthers Json.Decode.string (Just ClassVsClassNameValidation))
(decodeOthers Json.Decode.bool Nothing)


{-| Just empty facts
Expand Down
29 changes: 27 additions & 2 deletions src/Test/Html/Internal/ElmHtml/Query.elm
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,33 @@ hasStyle style facts =

classnames : Facts msg -> List String
classnames facts =
Dict.get "className" facts.stringAttributes
|> Maybe.withDefault ""
(case
( Dict.get "class" facts.stringAttributes
, Dict.get "className" facts.stringAttributes
)
of
( Just _, Just _ ) ->
-- If you use both the `class` attribute and the `className` property at the same time,
-- it’s undefined which classes you end up with. It depends on which order they are specified,
-- which order elm/virtual-dom happens to apply them, and which of them changed most recently.
-- Mixing both is not a good idea.
--
-- This code should be impossible to reach because of the validation in
-- Test.Html.Internal.ElmHtml.InternalTypes.decodeOthers.
--
-- If we ever reach this code, silently claim that there are no classes (that no classes match
-- the node).
""

( Just class, Nothing ) ->
class

( Nothing, Just className ) ->
className

( Nothing, Nothing ) ->
""
)
|> String.split " "


Expand Down
40 changes: 36 additions & 4 deletions src/Test/Html/Internal/Inert.elm
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
module Test.Html.Internal.Inert exposing (Node, fromElmHtml, fromHtml, parseAttribute, toElmHtml)
module Test.Html.Internal.Inert exposing (Node, Error(..), fromElmHtml, fromHtml, parseAttribute, toElmHtml)

{-| Inert Html - that is, can't do anything with events.

@docs Node, fromElmHtml, fromHtml, parseAttribute, toElmHtml
@docs Node, Error, fromElmHtml, fromHtml, parseAttribute, toElmHtml

-}

import Elm.Kernel.HtmlAsJson
import Html exposing (Html)
import Json.Decode
import MicroListExtra as List
import Test.Html.Internal.ElmHtml.InternalTypes as InternalTypes exposing (ElmHtml, EventHandler, Tagger, decodeAttribute, decodeElmHtml)
import VirtualDom

Expand All @@ -17,14 +18,45 @@ type Node msg
= Node (ElmHtml msg)


fromHtml : Html msg -> Result String (Node msg)
type Error
= DecodeError Json.Decode.Error
| ValidationErrors { deduped : List InternalTypes.Validation }


fromHtml : Html msg -> Result Error (Node msg)
fromHtml html =
case Json.Decode.decodeValue (decodeElmHtml taggedEventDecoder) (toJson html) of
Ok elmHtml ->
Ok (Node elmHtml)

Err jsonError ->
Err (Json.Decode.errorToString jsonError)
case findValidationErrors jsonError of
[] ->
Err (DecodeError jsonError)

failedValidations ->
Err (ValidationErrors { deduped = List.unique failedValidations })


findValidationErrors : Json.Decode.Error -> List InternalTypes.Validation
findValidationErrors error =
case error of
Json.Decode.Field _ e ->
findValidationErrors e

Json.Decode.Index _ e ->
findValidationErrors e

Json.Decode.OneOf es ->
List.concatMap findValidationErrors es

Json.Decode.Failure stringError _ ->
case InternalTypes.validationFromMessage stringError of
Nothing ->
[]

Just validation ->
[ validation ]


fromElmHtml : ElmHtml msg -> Node msg
Expand Down
32 changes: 24 additions & 8 deletions src/Test/Html/Query.elm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ module Test.Html.Query exposing

import Expect exposing (Expectation)
import Html exposing (Html)
import Json.Decode
import Test.Html.Internal.ElmHtml.InternalTypes as InternalTypes
import Test.Html.Internal.Inert as Inert
import Test.Html.Query.Internal as Internal exposing (failWithQuery)
import Test.Html.Selector exposing (Selector)
Expand Down Expand Up @@ -95,8 +97,11 @@ fromHtml html =
Ok node ->
Internal.Query node []

Err message ->
Internal.InternalError message
Err (Inert.DecodeError decodeError) ->
Internal.InternalError (Json.Decode.errorToString decodeError)

Err (Inert.ValidationErrors validations) ->
Internal.ValidationErrors validations



Expand Down Expand Up @@ -377,12 +382,23 @@ contains expectedHtml (Internal.Single showTrace query) =
|> failWithQuery showTrace "Query.contains" query

Err errors ->
Expect.fail <|
String.join "\n" <|
List.concat
[ [ "Internal Error: failed to decode the virtual dom. Please report this at <https://github.com/elm-explorations/test/issues>." ]
, errors
]
errors
|> List.map
(\error ->
(case error of
Inert.DecodeError decodeError ->
[ "Internal Error: failed to decode the virtual dom. Please report this at <https://github.com/elm-explorations/test/issues>."
, Json.Decode.errorToString decodeError
]

Inert.ValidationErrors { deduped } ->
deduped
|> List.map InternalTypes.validationMessage
)
|> String.join "\n"
)
|> String.join "\n\n"
|> Expect.fail


collectResults : List (Result x a) -> Result (List x) (List a)
Expand Down
Loading
Loading