package components import ( "fmt" "regexp" "strings" "github.com/samber/lo" ) type TextMatcher struct { // If you add or change a field here, be sure to update the copy // code in checkIsSelected() *Matcher[string] } func (self *TextMatcher) Contains(target string) *TextMatcher { self.appendRule(matcherRule[string]{ name: fmt.Sprintf("contains '%s'", target), testFn: func(value string) (bool, string) { // everything contains the empty string so we unconditionally return true here if target == "" { return true, "" } return strings.Contains(value, target), fmt.Sprintf("Expected '%s' to be found in '%s'", target, value) }, }) return self } func (self *TextMatcher) DoesNotContain(target string) *TextMatcher { self.appendRule(matcherRule[string]{ name: fmt.Sprintf("does not contain '%s'", target), testFn: func(value string) (bool, string) { return !strings.Contains(value, target), fmt.Sprintf("Expected '%s' to NOT be found in '%s'", target, value) }, }) return self } func (self *TextMatcher) DoesNotContainAnyOf(targets []string) *TextMatcher { self.appendRule(matcherRule[string]{ name: fmt.Sprintf("does not contain any of '%s'", targets), testFn: func(value string) (bool, string) { return lo.NoneBy(targets, func(target string) bool { return strings.Contains(value, target) }), fmt.Sprintf("Expected none of '%s' to be found in '%s'", targets, value) }, }) return self } func (self *TextMatcher) MatchesRegexp(target string) *TextMatcher { self.appendRule(matcherRule[string]{ name: fmt.Sprintf("matches regular expression '%s'", target), testFn: func(value string) (bool, string) { matched, err := regexp.MatchString(target, value) if err != nil { return false, fmt.Sprintf("Unexpected error parsing regular expression '%s': %s", target, err.Error()) } return matched, fmt.Sprintf("Expected '%s' to match regular expression /%s/", value, target) }, }) return self } func (self *TextMatcher) Equals(target string) *TextMatcher { self.appendRule(matcherRule[string]{ name: fmt.Sprintf("equals '%s'", target), testFn: func(value string) (bool, string) { return target == value, fmt.Sprintf("Expected '%s' to equal '%s'", value, target) }, }) return self } 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 *TextMatcher) IsSelected() *TextMatcher { self.appendRule(matcherRule[string]{ 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.") }, }) return self } // if the matcher has an `IsSelected` rule, it returns true, along with the matcher after that rule has been removed func (self *TextMatcher) checkIsSelected() (bool, *TextMatcher) { // copying into a new matcher in case we want to re-use the original later newMatcher := &TextMatcher{Matcher: &Matcher[string]{}} *newMatcher.Matcher = *self.Matcher check := lo.ContainsBy(newMatcher.rules, func(rule matcherRule[string]) bool { return rule.name == IS_SELECTED_RULE_NAME }) newMatcher.rules = lo.Filter(newMatcher.rules, func(rule matcherRule[string], _ int) bool { return rule.name != IS_SELECTED_RULE_NAME }) return check, newMatcher } // 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 AnyString() *TextMatcher { return &TextMatcher{Matcher: &Matcher[string]{}} } func Contains(target string) *TextMatcher { return AnyString().Contains(target) } func DoesNotContain(target string) *TextMatcher { return AnyString().DoesNotContain(target) } func DoesNotContainAnyOf(targets ...string) *TextMatcher { return AnyString().DoesNotContainAnyOf(targets) } func MatchesRegexp(target string) *TextMatcher { return AnyString().MatchesRegexp(target) } func Equals(target string) *TextMatcher { return AnyString().Equals(target) }