Repository: gavv/httpexpect (go)

Issue:
Add Array.IsValueEqual and NotValueEqual
These two method should work similar to Object.IsValueEqual and NotValueEqual, but accept index instead of key.

New methods should have documentation comments with code examples, and be covered by unit tests.

Pull request description:
Add Array.IsValueEqual and Array.NotValueEqual
Closes #286 

Patch:
diff --git a/array.go b/array.go
index cb7b4bf..225916b 100644
--- a/array.go
+++ b/array.go
@@ -1452,6 +1452,114 @@ func (a *Array) NotContainsOnly(values ...interface{}) *Array {
 	return a
 }
 
+// IsValueEqual succeeds if array's value at the given index is equal to given value.
+//
+// Before comparison, both values are converted to canonical form. value should be
+// map[string]interface{} or struct.
+//
+// Example:
+//
+//	array := NewArray(t, []interface{}{"foo", "123"})
+//	array.IsValueEqual(1, 123)
+func (a *Array) IsValueEqual(index int, value interface{}) *Array {
+	opChain := a.chain.enter("IsValueEqual(%d)", index)
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return a
+	}
+
+	if index < 0 || index >= len(a.value) {
+		opChain.fail(AssertionFailure{
+			Type:   AssertInRange,
+			Actual: &AssertionValue{index},
+			Expected: &AssertionValue{AssertionRange{
+				Min: 0,
+				Max: len(a.value) - 1,
+			}},
+			Errors: []error{
+				errors.New("expected: valid element index"),
+			},
+		})
+		return a
+	}
+
+	expected, ok := canonValue(opChain, value)
+	if !ok {
+		return a
+	}
+
+	if !reflect.DeepEqual(expected, a.value[index]) {
+		opChain.fail(AssertionFailure{
+			Type:     AssertEqual,
+			Actual:   &AssertionValue{a.value[index]},
+			Expected: &AssertionValue{value},
+			Errors: []error{
+				fmt.Errorf(
+					"expected: array value at index %d is equal to given value",
+					index),
+			},
+		})
+		return a
+	}
+
+	return a
+}
+
+// NotValueEqual succeeds if array's value at the given index is not equal to given value.
+//
+// Before comparison, both values are converted to canonical form. value should be
+// map[string]interface{} or struct.
+//
+// Example:
+//
+//	array := NewArray(t, []interface{}{"foo", "123"})
+//	array.NotValueEqual(1, 234)
+func (a *Array) NotValueEqual(index int, value interface{}) *Array {
+	opChain := a.chain.enter("NotValueEqual(%d)", index)
+	defer opChain.leave()
+
+	if opChain.failed() {
+		return a
+	}
+
+	if index < 0 || index >= len(a.value) {
+		opChain.fail(AssertionFailure{
+			Type:   AssertInRange,
+			Actual: &AssertionValue{index},
+			Expected: &AssertionValue{AssertionRange{
+				Min: 0,
+				Max: len(a.value) - 1,
+			}},
+			Errors: []error{
+				errors.New("expected: valid element index"),
+			},
+		})
+		return a
+	}
+
+	expected, ok := canonValue(opChain, value)
+	if !ok {
+		return a
+	}
+
+	if reflect.DeepEqual(expected, a.value[index]) {
+		opChain.fail(AssertionFailure{
+			Type:     AssertNotEqual,
+			Actual:   &AssertionValue{a.value[index]},
+			Expected: &AssertionValue{value},
+			Errors: []error{
+				fmt.Errorf(
+					"expected: array value at index %d is not equal to given value",
+					index),
+			},
+		})
+		return a
+	}
+
+	return a
+}
+
 // IsOrdered succeeds if every element is not less than the previous element
 // as defined on the given `less` comparator function.
 // For default, it will use built-in comparator function for each data type.


Test patch:
diff --git a/array_test.go b/array_test.go
index 4ba31f9..a54f44d 100644
--- a/array_test.go
+++ b/array_test.go
@@ -40,6 +40,8 @@ func TestArray_FailedChain(t *testing.T) {
 		value.NotContainsAny("foo")
 		value.ContainsOnly("foo")
 		value.NotContainsOnly("foo")
+		value.IsValueEqual(0, nil)
+		value.NotValueEqual(0, nil)
 
 		assert.NotNil(t, value.Iter())
 		assert.Equal(t, 0, len(value.Iter()))
@@ -1817,6 +1819,147 @@ func TestArray_NotOrdered(t *testing.T) {
 	}
 }
 
+func TestArray_ValueEqual(t *testing.T) {
+	reporter := newMockReporter(t)
+
+	array := NewArray(reporter, []interface{}{
+		123,
+		[]interface{}{"456", 789},
+		map[string]interface{}{
+			"a": "b",
+		},
+	})
+
+	array.IsValueEqual(0, 123)
+	array.chain.assertNotFailed(t)
+	array.chain.clearFailed()
+
+	array.NotValueEqual(0, 123)
+	array.chain.assertFailed(t)
+	array.chain.clearFailed()
+
+	array.IsValueEqual(1, []interface{}{"456", 789})
+	array.chain.assertNotFailed(t)
+	array.chain.clearFailed()
+
+	array.NotValueEqual(1, []interface{}{"456", 789})
+	array.chain.assertFailed(t)
+	array.chain.clearFailed()
+
+	array.IsValueEqual(2, map[string]interface{}{"a": "b"})
+	array.chain.assertNotFailed(t)
+	array.chain.clearFailed()
+
+	array.NotValueEqual(2, map[string]interface{}{"a": "b"})
+	array.chain.assertFailed(t)
+	array.chain.clearFailed()
+
+	array.IsValueEqual(2, func() {})
+	array.chain.assertFailed(t)
+	array.chain.clearFailed()
+
+	array.NotValueEqual(2, func() {})
+	array.chain.assertFailed(t)
+	array.chain.clearFailed()
+
+	array.IsValueEqual(3, 777)
+	array.chain.assertFailed(t)
+	array.chain.clearFailed()
+
+	array.NotValueEqual(3, 777)
+	array.chain.assertFailed(t)
+	array.chain.clearFailed()
+
+	array.IsValueEqual(-1, 999)
+	array.chain.assertFailed(t)
+	array.chain.clearFailed()
+
+	array.NotValueEqual(-1, 999)
+	array.chain.assertFailed(t)
+	array.chain.clearFailed()
+}
+
+func TestArray_ValueEqualStruct(t *testing.T) {
+	reporter := newMockReporter(t)
+
+	array := NewArray(reporter, []interface{}{
+		map[string]interface{}{
+			"a": map[string]interface{}{
+				"b": 333,
+				"c": 444,
+			},
+		},
+	})
+
+	type (
+		A struct {
+			B int `json:"b"`
+			C int `json:"c"`
+		}
+
+		Baz struct {
+			A A `json:"a"`
+		}
+	)
+
+	baz := Baz{
+		A: A{
+			B: 333,
+			C: 444,
+		},
+	}
+
+	array.IsValueEqual(0, baz)
+	array.chain.assertNotFailed(t)
+	array.chain.clearFailed()
+
+	array.NotValueEqual(0, baz)
+	array.chain.assertFailed(t)
+	array.chain.clearFailed()
+
+	array.IsValueEqual(0, Baz{})
+	array.chain.assertFailed(t)
+	array.chain.clearFailed()
+
+	array.NotValueEqual(0, Baz{})
+	array.chain.assertNotFailed(t)
+	array.chain.clearFailed()
+}
+
+func TestArray_ConvertValueEqual(t *testing.T) {
+	type (
+		myArray []interface{}
+		myMap   map[string]interface{}
+		myInt   int
+	)
+
+	reporter := newMockReporter(t)
+
+	array := NewArray(reporter, []interface{}{
+		123,
+		[]interface{}{"456", 789},
+		map[string]interface{}{
+			"a": "b",
+		},
+	})
+
+	array.IsValueEqual(1, myArray{"456", myInt(789)})
+	array.chain.assertNotFailed(t)
+	array.chain.clearFailed()
+
+	array.NotValueEqual(1, myArray{"456", myInt(789)})
+	array.chain.assertFailed(t)
+	array.chain.clearFailed()
+
+	array.IsValueEqual(2, myMap{"a": "b"})
+	array.chain.assertNotFailed(t)
+	array.chain.clearFailed()
+
+	array.NotValueEqual(2, myMap{"a": "b"})
+	array.chain.assertFailed(t)
+	array.chain.clearFailed()
+}
+
 func TestArray_ComparatorErrors(t *testing.T) {
 	t.Run("nil_slice", func(t *testing.T) {
 		chain := newMockChain(t).enter("test")