2022-12-24 17:48:57 +11:00
package components
2022-12-26 11:12:56 +11:00
import (
"fmt"
"regexp"
"strings"
2022-12-26 17:15:33 +11:00
"github.com/samber/lo"
2022-12-26 11:12:56 +11:00
)
2022-12-24 17:48:57 +11:00
// for making assertions on string values
type matcher struct {
2022-12-26 17:15:33 +11:00
rules [ ] matcherRule
// this is printed when there's an error so that it's clear what the context of the assertion is
prefix string
}
type matcherRule struct {
2022-12-24 17:48:57 +11:00
// e.g. "contains 'foo'"
name string
// returns a bool that says whether the test passed and if it returns false, it
// also returns a string of the error message
testFn func ( string ) ( bool , string )
}
func NewMatcher ( name string , testFn func ( string ) ( bool , string ) ) * matcher {
2022-12-26 17:15:33 +11:00
rules := [ ] matcherRule { { name : name , testFn : testFn } }
return & matcher { rules : rules }
}
func ( self * matcher ) name ( ) string {
if len ( self . rules ) == 0 {
return "anything"
}
return strings . Join (
lo . Map ( self . rules , func ( rule matcherRule , _ int ) string { return rule . name } ) ,
", " ,
)
2022-12-24 17:48:57 +11:00
}
func ( self * matcher ) test ( value string ) ( bool , string ) {
2022-12-26 17:15:33 +11:00
for _ , rule := range self . rules {
ok , message := rule . testFn ( value )
if ok {
continue
}
2022-12-24 17:48:57 +11:00
2022-12-26 17:15:33 +11:00
if self . prefix != "" {
return false , self . prefix + " " + message
}
2022-12-24 17:48:57 +11:00
2022-12-26 17:15:33 +11:00
return false , message
}
2022-12-24 17:48:57 +11:00
2022-12-26 17:15:33 +11:00
return true , ""
2022-12-24 17:48:57 +11:00
}
2022-12-26 11:12:56 +11:00
2022-12-26 17:15:33 +11:00
func ( self * matcher ) Contains ( target string ) * matcher {
2022-12-26 17:37:41 +11:00
return self . appendRule ( matcherRule {
2022-12-26 17:15:33 +11:00
name : fmt . Sprintf ( "contains '%s'" , target ) ,
testFn : func ( value string ) ( bool , string ) {
2022-12-26 11:12:56 +11:00
return strings . Contains ( value , target ) , fmt . Sprintf ( "Expected '%s' to be found in '%s'" , target , value )
} ,
2022-12-26 17:37:41 +11:00
} )
2022-12-26 11:12:56 +11:00
}
2022-12-26 17:15:33 +11:00
func ( self * matcher ) DoesNotContain ( target string ) * matcher {
2022-12-26 17:37:41 +11:00
return self . appendRule ( matcherRule {
2022-12-26 17:15:33 +11:00
name : fmt . Sprintf ( "does not contain '%s'" , target ) ,
testFn : func ( value string ) ( bool , string ) {
2022-12-26 11:12:56 +11:00
return ! strings . Contains ( value , target ) , fmt . Sprintf ( "Expected '%s' to NOT be found in '%s'" , target , value )
} ,
2022-12-26 17:37:41 +11:00
} )
2022-12-26 11:12:56 +11:00
}
2022-12-26 17:15:33 +11:00
func ( self * matcher ) MatchesRegexp ( target string ) * matcher {
2022-12-26 17:37:41 +11:00
return self . appendRule ( matcherRule {
2022-12-26 17:15:33 +11:00
name : fmt . Sprintf ( "matches regular expression '%s'" , target ) ,
testFn : func ( value string ) ( bool , string ) {
2022-12-26 11:12:56 +11:00
matched , err := regexp . MatchString ( target , value )
if err != nil {
return false , fmt . Sprintf ( "Unexpected error parsing regular expression '%s': %s" , target , err . Error ( ) )
}
2023-02-12 17:40:53 +11:00
return matched , fmt . Sprintf ( "Expected '%s' to match regular expression /%s/" , value , target )
2022-12-26 11:12:56 +11:00
} ,
2022-12-26 17:37:41 +11:00
} )
2022-12-26 11:12:56 +11:00
}
2022-12-26 17:15:33 +11:00
func ( self * matcher ) Equals ( target string ) * matcher {
2022-12-26 17:37:41 +11:00
return self . appendRule ( matcherRule {
2022-12-26 17:15:33 +11:00
name : fmt . Sprintf ( "equals '%s'" , target ) ,
testFn : func ( value string ) ( bool , string ) {
2022-12-26 11:12:56 +11:00
return target == value , fmt . Sprintf ( "Expected '%s' to equal '%s'" , value , target )
} ,
2022-12-26 17:37:41 +11:00
} )
}
const IS_SELECTED_RULE_NAME = "is selected"
// special rule that is only to be used in the TopLines and Lines methods, as a way of
// asserting that a given line is selected.
func ( self * matcher ) IsSelected ( ) * matcher {
return self . appendRule ( matcherRule {
name : IS_SELECTED_RULE_NAME ,
testFn : func ( value string ) ( bool , string ) {
panic ( "Special IsSelected matcher is not supposed to have its testFn method called. This rule should only be used within the .Lines() and .TopLines() method on a ViewAsserter." )
} ,
} )
}
2022-12-26 17:15:33 +11:00
2022-12-26 17:37:41 +11:00
func ( self * matcher ) appendRule ( rule matcherRule ) * matcher {
2022-12-26 17:15:33 +11:00
self . rules = append ( self . rules , rule )
return self
}
2022-12-26 17:37:41 +11:00
// adds context so that if the matcher test(s) fails, we understand what we were trying to test.
// E.g. prefix: "Unexpected content in view 'files'."
2022-12-26 17:15:33 +11:00
func ( self * matcher ) context ( prefix string ) * matcher {
self . prefix = prefix
return self
}
2022-12-26 17:37:41 +11:00
// if the matcher has an `IsSelected` rule, it returns true, along with the matcher after that rule has been removed
func ( self * matcher ) checkIsSelected ( ) ( bool , * matcher ) {
check := lo . ContainsBy ( self . rules , func ( rule matcherRule ) bool { return rule . name == IS_SELECTED_RULE_NAME } )
self . rules = lo . Filter ( self . rules , func ( rule matcherRule , _ int ) bool { return rule . name != IS_SELECTED_RULE_NAME } )
return check , self
}
2022-12-26 17:15:33 +11:00
// this matcher has no rules meaning it always passes the test. Use this
// when you don't care what value you're dealing with.
func Anything ( ) * matcher {
return & matcher { }
}
func Contains ( target string ) * matcher {
return Anything ( ) . Contains ( target )
}
func DoesNotContain ( target string ) * matcher {
return Anything ( ) . DoesNotContain ( target )
}
func MatchesRegexp ( target string ) * matcher {
return Anything ( ) . MatchesRegexp ( target )
}
func Equals ( target string ) * matcher {
return Anything ( ) . Equals ( target )
2022-12-26 11:12:56 +11:00
}