mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-09-11 16:16:09 +03:00
Add expectations for the data extract in the test module
This commit is contained in:
parent
0f2d552595
commit
363b48c7e8
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 } }
|
||||
|
Loading…
Reference in New Issue
Block a user