Merge pull request #187 from jraqula/master
allow specifying that a matcher can apply to multiple members of a slice
diff --git a/gstruct/elements.go b/gstruct/elements.go
index 9b6b134..a315fa1 100644
--- a/gstruct/elements.go
+++ b/gstruct/elements.go
@@ -32,10 +32,11 @@
// })
func MatchElements(identifier Identifier, options Options, elements Elements) types.GomegaMatcher {
return &ElementsMatcher{
- Identifier: identifier,
- Elements: elements,
- IgnoreExtras: options&IgnoreExtras != 0,
- IgnoreMissing: options&IgnoreMissing != 0,
+ Identifier: identifier,
+ Elements: elements,
+ IgnoreExtras: options&IgnoreExtras != 0,
+ IgnoreMissing: options&IgnoreMissing != 0,
+ AllowDuplicates: options&AllowDuplicates != 0,
}
}
@@ -52,6 +53,8 @@
IgnoreExtras bool
// Whether to ignore missing elements or consider it an error.
IgnoreMissing bool
+ // Whether to key duplicates when matching IDs.
+ AllowDuplicates bool
// State.
failures []error
@@ -88,10 +91,11 @@
for i := 0; i < val.Len(); i++ {
element := val.Index(i).Interface()
id := m.Identifier(element)
- // TODO: Add options to ignore & match duplicates.
if elements[id] {
- errs = append(errs, fmt.Errorf("found duplicate element ID %s", id))
- continue
+ if !m.AllowDuplicates {
+ errs = append(errs, fmt.Errorf("found duplicate element ID %s", id))
+ continue
+ }
}
elements[id] = true
diff --git a/gstruct/elements_test.go b/gstruct/elements_test.go
index 8a63920..8ba78cb 100644
--- a/gstruct/elements_test.go
+++ b/gstruct/elements_test.go
@@ -78,6 +78,65 @@
})
Ω(allElements).ShouldNot(m, "should run nested matchers")
})
+
+ Context("with elements that share a key", func() {
+ nonUniqueID := func(element interface{}) string {
+ return element.(string)[0:1]
+ }
+
+ allElements := []string{"a123", "a213", "b321"}
+ includingBadElements := []string{"a123", "b123", "b5555"}
+ extraElements := []string{"a123", "b1234", "c345"}
+ missingElements := []string{"b123", "b1234", "b1345"}
+
+ It("should strictly allow multiple matches", func() {
+ m := MatchElements(nonUniqueID, AllowDuplicates, Elements{
+ "a": ContainSubstring("1"),
+ "b": ContainSubstring("1"),
+ })
+ Ω(allElements).Should(m, "should match all elements")
+ Ω(includingBadElements).ShouldNot(m, "should reject if a member fails the matcher")
+ Ω(extraElements).ShouldNot(m, "should reject with extra keys")
+ Ω(missingElements).ShouldNot(m, "should reject with missing keys")
+ Ω(nils).ShouldNot(m, "should fail with an uninitialized slice")
+ })
+
+ It("should ignore missing", func() {
+ m := MatchElements(nonUniqueID, AllowDuplicates|IgnoreMissing, Elements{
+ "a": ContainSubstring("1"),
+ "b": ContainSubstring("1"),
+ })
+ Ω(allElements).Should(m, "should match all elements")
+ Ω(includingBadElements).ShouldNot(m, "should reject if a member fails the matcher")
+ Ω(extraElements).ShouldNot(m, "should reject with extra keys")
+ Ω(missingElements).Should(m, "should allow missing keys")
+ Ω(nils).Should(m, "should allow an uninitialized slice")
+ })
+
+ It("should ignore extras", func() {
+ m := MatchElements(nonUniqueID, AllowDuplicates|IgnoreExtras, Elements{
+ "a": ContainSubstring("1"),
+ "b": ContainSubstring("1"),
+ })
+ Ω(allElements).Should(m, "should match all elements")
+ Ω(includingBadElements).ShouldNot(m, "should reject if a member fails the matcher")
+ Ω(extraElements).Should(m, "should allow extra keys")
+ Ω(missingElements).ShouldNot(m, "should reject missing keys")
+ Ω(nils).ShouldNot(m, "should reject an uninitialized slice")
+ })
+
+ It("should ignore missing and extras", func() {
+ m := MatchElements(nonUniqueID, AllowDuplicates|IgnoreExtras|IgnoreMissing, Elements{
+ "a": ContainSubstring("1"),
+ "b": ContainSubstring("1"),
+ })
+ Ω(allElements).Should(m, "should match all elements")
+ Ω(includingBadElements).ShouldNot(m, "should reject if a member fails the matcher")
+ Ω(extraElements).Should(m, "should allow extra keys")
+ Ω(missingElements).Should(m, "should allow missing keys")
+ Ω(nils).Should(m, "should allow an uninitialized slice")
+ })
+ })
})
func id(element interface{}) string {
diff --git a/gstruct/types.go b/gstruct/types.go
index 0b7a124..48cbbe8 100644
--- a/gstruct/types.go
+++ b/gstruct/types.go
@@ -8,4 +8,8 @@
IgnoreExtras Options = 1 << iota
//IgnoreMissing tells the matcher to ignore missing elements or fields, rather than triggering a failure.
IgnoreMissing
+ //AllowDuplicates tells the matcher to permit multiple members of the slice to produce the same ID when
+ //considered by the indentifier function. All members that map to a given key must still match successfully
+ //with the matcher that is provided for that key.
+ AllowDuplicates
)