Add expectations for the data extract in the test module

This commit is contained in:
Jeroen Engels 2022-04-27 18:33:06 +02:00
parent 0f2d552595
commit 363b48c7e8
4 changed files with 325 additions and 8 deletions

View File

@ -1,4 +1,4 @@
module Ansi exposing (backgroundRed, bold, cyan, red, yellow)
module Ansi exposing (backgroundRed, bold, cyan, green, red, yellow)
-- FONTS
@ -22,6 +22,11 @@ red =
applyColor "31"
green : String -> String
green =
applyColor "32"
yellow : String -> String
yellow =
applyColor "33"

View File

@ -3,6 +3,7 @@ module Review.Test exposing
, ExpectedError, expectNoErrors, expectErrors, error, atExactly, whenFixed, expectErrorsForModules, expectErrorsForElmJson, expectErrorsForReadme
, expectGlobalErrors, expectGlobalAndLocalErrors, expectGlobalAndModuleErrors
, expectConfigurationError
, expectDataExtract
)
{-| Module that helps you test your rules, using [`elm-test`](https://package.elm-lang.org/packages/elm-explorations/test/latest/).
@ -103,14 +104,18 @@ for this module.
@docs ExpectedError, expectNoErrors, expectErrors, error, atExactly, whenFixed, expectErrorsForModules, expectErrorsForElmJson, expectErrorsForReadme
@docs expectGlobalErrors, expectGlobalAndLocalErrors, expectGlobalAndModuleErrors
@docs expectConfigurationError
@docs expectDataExtract
-}
import Array exposing (Array)
import Dict exposing (Dict)
import Elm.Syntax.Module as Module
import Elm.Syntax.Node as Node
import Elm.Syntax.Range exposing (Range)
import Expect exposing (Expectation)
import Json.Decode as Decode
import Json.Encode as Encode
import Review.Error as Error
import Review.FileParser as FileParser
import Review.Fix as Fix
@ -120,6 +125,7 @@ import Review.Test.Dependencies exposing (projectWithElmCore)
import Review.Test.FailureMessage as FailureMessage
import Set exposing (Set)
import Unicode
import Vendor.Diff as Diff
import Vendor.ListExtra as ListExtra
@ -132,7 +138,7 @@ import Vendor.ListExtra as ListExtra
type ReviewResult
= ConfigurationError { message : String, details : List String }
| FailedRun String
| SuccessfulRun (List GlobalError) (List SuccessfulRunResult)
| SuccessfulRun (List GlobalError) (List SuccessfulRunResult) ExtractResult
type alias GlobalError =
@ -156,6 +162,11 @@ type alias CodeInspector =
}
type ExtractResult
= RuleHasNoExtractor
| Extracted (Maybe Encode.Value)
{-| An expectation for an error. Use [`error`](#error) to create one.
-}
type ExpectedError
@ -399,11 +410,21 @@ runOnModulesWithProjectDataHelp project rule sources =
Nothing ->
let
runResult : { errors : List ReviewError, rules : List Rule, projectData : Maybe Rule.ProjectData, extracts : Dict String Encode.Value }
runResult =
Rule.reviewV3 [ rule ] Nothing projectWithModules
errors : List ReviewError
errors =
projectWithModules
|> Rule.reviewV2 [ rule ] Nothing
|> .errors
runResult.errors
extract : ExtractResult
extract =
if Rule.ruleExtractsData rule then
Extracted (Dict.get (Rule.ruleName rule) runResult.extracts)
else
RuleHasNoExtractor
in
case ListExtra.find (\err -> Rule.errorTarget err == Error.Global) errors of
Just globalError ->
@ -425,7 +446,7 @@ runOnModulesWithProjectDataHelp project rule sources =
|> List.filter (\error_ -> Rule.errorTarget error_ == Error.UserGlobal)
|> List.map (\error_ -> { message = Rule.errorMessage error_, details = Rule.errorDetails error_ })
in
SuccessfulRun globalErrors fileErrors
SuccessfulRun globalErrors fileErrors extract
hasOneElement : List a -> Bool
@ -749,7 +770,7 @@ expectGlobalAndLocalErrors { global, local } reviewResult =
FailedRun errorMessage ->
Expect.fail errorMessage
SuccessfulRun globalErrors runResults ->
SuccessfulRun globalErrors runResults extract ->
Expect.all
[ \() ->
if List.isEmpty global then
@ -768,6 +789,7 @@ expectGlobalAndLocalErrors { global, local } reviewResult =
_ ->
Expect.fail FailureMessage.needToUsedExpectErrorsForModules
, \() -> expectNoExtract extract
]
()
@ -791,7 +813,7 @@ expectGlobalAndModuleErrors { global, modules } reviewResult =
FailedRun errorMessage ->
Expect.fail errorMessage
SuccessfulRun globalErrors runResults ->
SuccessfulRun globalErrors runResults extract ->
Expect.all
[ \() ->
if List.isEmpty global then
@ -800,6 +822,7 @@ expectGlobalAndModuleErrors { global, modules } reviewResult =
else
checkAllGlobalErrorsMatch (List.length global) { expected = global, actual = globalErrors }
, \() -> expectErrorsForModulesHelp modules runResults
, \() -> expectNoExtract extract
]
()
@ -1541,3 +1564,97 @@ expectConfigurationErrorDetailsMatch expectedError configurationError =
else
Expect.pass
expectNoExtract : ExtractResult -> Expectation
expectNoExtract maybeExtract =
case maybeExtract of
Extracted (Just _) ->
Expect.fail FailureMessage.needToUsedExpectErrorsForModules
Extracted Nothing ->
Expect.pass
RuleHasNoExtractor ->
Expect.pass
{-| Expect the rule to produce a specific data extract.
Note: You do not need to match the exact formatting of the JSON object, though the order of fields does need to match.
import Review.Test
import Test exposing (Test, describe, test)
import The.Rule.You.Want.To.Test exposing (rule)
tests : Test
tests =
test "should extract the list of fields" <|
\() ->
"""module A exposing (..)
a = 1
b = 2
"""
|> Review.Test.run rule
|> Review.Test.expectDataExtract """
{
"fields": [ "a", "b" ]
}"""
-}
expectDataExtract : String -> ReviewResult -> Expectation
expectDataExtract expectedExtract reviewResult =
case reviewResult of
ConfigurationError configurationError ->
Expect.fail (FailureMessage.unexpectedConfigurationError configurationError)
FailedRun errorMessage ->
Expect.fail errorMessage
SuccessfulRun globalErrors runResults extract ->
Expect.all
[ \() -> expectNoGlobalErrors globalErrors
, \() -> expectNoModuleErrors runResults
, \() -> expectDataExtractContent expectedExtract extract
]
()
expectDataExtractContent : String -> ExtractResult -> Expectation
expectDataExtractContent rawExpected maybeActualExtract =
case maybeActualExtract of
RuleHasNoExtractor ->
Expect.fail FailureMessage.missingExtract
Extracted Nothing ->
Expect.fail FailureMessage.missingExtract
Extracted (Just actual) ->
case Decode.decodeString Decode.value rawExpected of
Err parsingError ->
Expect.fail (FailureMessage.invalidJsonForExpectedDataExtract parsingError)
Ok expected ->
let
differences : List (Diff.Change String)
differences =
Diff.diffLines (Encode.encode 2 actual) (Encode.encode 2 expected)
in
if containsDifferences differences then
Expect.fail (FailureMessage.extractMismatch actual expected differences)
else
Expect.pass
containsDifferences : List (Diff.Change a) -> Bool
containsDifferences changes =
case changes of
[] ->
False
(Diff.NoChange _) :: restOfChanges ->
containsDifferences restOfChanges
_ ->
True

View File

@ -7,6 +7,7 @@ module Review.Test.FailureMessage exposing
, didNotExpectGlobalErrors, expectedMoreGlobalErrors, fixedCodeWhitespaceMismatch, messageMismatchForConfigurationError
, messageMismatchForGlobalError, missingConfigurationError, tooManyGlobalErrors
, unexpectedConfigurationError, unexpectedConfigurationErrorDetails, unexpectedGlobalErrorDetails
, unexpectedExtract, missingExtract, invalidJsonForExpectedDataExtract, extractMismatch
)
{-| Failure messages for the `Review.Test` module.
@ -22,11 +23,14 @@ module Review.Test.FailureMessage exposing
@docs didNotExpectGlobalErrors, expectedMoreGlobalErrors, fixedCodeWhitespaceMismatch, messageMismatchForConfigurationError
@docs messageMismatchForGlobalError, missingConfigurationError, tooManyGlobalErrors
@docs unexpectedConfigurationError, unexpectedConfigurationErrorDetails, unexpectedGlobalErrorDetails
@docs unexpectedExtract, missingExtract, invalidJsonForExpectedDataExtract, extractMismatch
-}
import Ansi
import Elm.Syntax.Range exposing (Range)
import Json.Decode as Decode
import Json.Encode as Encode
import Review.Rule as Rule exposing (ReviewError)
import Vendor.Diff as Diff
import Vendor.ListExtra as ListExtra
@ -637,6 +641,65 @@ missingConfigurationError errorMessage =
but I could not find it.""")
unexpectedExtract : Encode.Value -> String
unexpectedExtract value =
failureMessage "UNEXPECTED EXTRACT"
("""This rule returned an extract which I did not expect.
You should use `REPLACEME` to assert that the extract fits what you had.
""" ++ formatJson value)
missingExtract : String
missingExtract =
failureMessage "MISSING EXTRACT"
"""I expected that the rule would extract information using
`Rule.withDataExtractor`, but it doesn't seem that that function was used."""
invalidJsonForExpectedDataExtract : Decode.Error -> String
invalidJsonForExpectedDataExtract parsingError =
failureMessage "INVALID JSON FOR EXPECTED DATA EXTRACT"
("""The string you passed to `expectDataExtract` can't be parsed as valid JSON.
""" ++ Decode.errorToString parsingError)
extractMismatch : Encode.Value -> Encode.Value -> List (Diff.Change String) -> String
extractMismatch actual expected differences =
failureMessage "DATA EXTRACT MISMATCH"
("""I found a different extract than expected. I got the following:
""" ++ formatJson actual ++ """
when I was expecting the following:
""" ++ formatJson expected ++ """
Here are the differences:
""" ++ formatJsonDiff differences)
formatJsonDiff : List (Diff.Change String) -> String
formatJsonDiff differences =
List.map
(\difference ->
case difference of
Diff.NoChange str ->
str
Diff.Added str ->
Ansi.green str
Diff.Removed str ->
Ansi.red str
)
differences
|> String.join "\n"
-- STYLIZING AND FORMATTING
@ -776,3 +839,8 @@ pluralizeErrors n =
else
"errors"
formatJson : Encode.Value -> String
formatJson value =
Encode.encode 2 value

View File

@ -2,10 +2,13 @@ module Review.Test.FailureMessageTest exposing (all)
import Elm.Syntax.Range exposing (Range)
import Expect exposing (Expectation)
import Json.Decode as Decode
import Json.Encode as Encode
import Review.Error exposing (ReviewError)
import Review.Fix as Fix
import Review.Test.FailureMessage as FailureMessage exposing (ExpectedErrorData)
import Test exposing (Test, describe, test)
import Vendor.Diff as Diff
all : Test
@ -39,6 +42,9 @@ all =
, unchangedSourceAfterFixTest
, invalidSourceAfterFixTest
, hasCollisionsInFixRangesTest
, unexpectedExtractTest
, invalidJsonForExpectedDataExtractTest
, extractMismatchTest
]
@ -1157,6 +1163,127 @@ Hint: Maybe you duplicated a fix, or you targeted the wrong node for one
of your fixes."""
unexpectedExtractTest : Test
unexpectedExtractTest =
test "unexpectedExtract" <|
\() ->
let
extract : Encode.Value
extract =
Encode.object
[ ( "foo", Encode.string "bar" )
, ( "other", Encode.list Encode.int [ 1, 2, 3 ] )
, ( "null", Encode.null )
]
in
FailureMessage.unexpectedExtract extract
|> expectMessageEqual """
\u{001B}[31m\u{001B}[1mUNEXPECTED EXTRACT\u{001B}[22m\u{001B}[39m
This rule returned an extract which I did not expect.
You should use `REPLACEME` to assert that the extract fits what you had.
{
"foo": "bar",
"other": [
1,
2,
3
],
"null": null
}"""
invalidJsonForExpectedDataExtractTest : Test
invalidJsonForExpectedDataExtractTest =
test "invalidJsonForExpectedDataExtract" <|
\() ->
case Decode.decodeString Decode.value "not JSON" of
Ok _ ->
Expect.fail "Incorrect test setup, should have been incorrect JSON"
Err parsingError ->
FailureMessage.invalidJsonForExpectedDataExtract parsingError
|> expectMessageEqual """
\u{001B}[31m\u{001B}[1mINVALID JSON FOR EXPECTED DATA EXTRACT\u{001B}[22m\u{001B}[39m
The string you passed to `expectDataExtract` can't be parsed as valid JSON.
Problem with the given value:
"not JSON"
This is not valid JSON! Unexpected token o in JSON at position 1"""
extractMismatchTest : Test
extractMismatchTest =
test "extractMismatch" <|
\() ->
let
extractActual : Encode.Value
extractActual =
Encode.object
[ ( "foo", Encode.string "bar" )
, ( "other", Encode.list Encode.int [ 1, 2, 3 ] )
, ( "actual", Encode.null )
]
extractExpected : Encode.Value
extractExpected =
Encode.object
[ ( "foo", Encode.string "bar" )
, ( "other", Encode.list Encode.int [ 1, 20, 3 ] )
, ( "expected", Encode.object [] )
]
in
FailureMessage.extractMismatch
extractActual
extractExpected
(Diff.diffLines (Encode.encode 2 extractActual) (Encode.encode 2 extractExpected))
|> expectMessageEqual """
\u{001B}[31m\u{001B}[1mDATA EXTRACT MISMATCH\u{001B}[22m\u{001B}[39m
I found a different extract than expected. I got the following:
{
"foo": "bar",
"other": [
1,
2,
3
],
"actual": null
}
when I was expecting the following:
{
"foo": "bar",
"other": [
1,
20,
3
],
"expected": {}
}
Here are the differences:
{
"foo": "bar",
"other": [
1,
\u{001B}[31m 2,\u{001B}[39m
\u{001B}[32m 20,\u{001B}[39m
3
],
\u{001B}[31m "actual": null\u{001B}[39m
\u{001B}[32m "expected": {}\u{001B}[39m
}"""
dummyRange : Range
dummyRange =
{ start = { row = 2, column = 1 }, end = { row = 2, column = 5 } }