diff --git a/3rd-Party-Notice.md b/3rd-Party-Notice.md
new file mode 100644
index 0000000..3f638c0
--- /dev/null
+++ b/3rd-Party-Notice.md
@@ -0,0 +1,32 @@
+# 3rd party assets used in this project
+
+## NaughtyAttributes
+
+NaughtyAttributes is an extension for the Unity Inspector.
+
+It expands the range of attributes that Unity provides so that you can
+create powerful inspectors without the need of custom editors or property
+drawers. It also provides attributes that can be applied to non-serialized
+fields or functions.
+
+It is implemented by replacing the default Unity Inspector. This means
+that if you have any custom editors, NaughtyAttributes will not work with
+them. All of your custom editors and property drawers are not affected
+in any way.
+
+* License: MIT
+* Source: https://github.com/dbrizov/NaughtyAttributes
+
+## FluentAssertions
+
+Fluent API for asserting the results of unit tests that targets .NET
+Framework 4.5, 4.7, .NET Standard 1.3, 1.6 and 2.0. Supports the unit
+test frameworks MSTest, MSTest2, Gallio, NUnit, XUnit, MBunit, MSpec,
+and NSpec.
+
+The version contained here is stripped down to remove all parts of the
+library that are inherently incompatible with Unity's interpretation
+of C# and their runtime libraries.
+
+* License: Apache License 2.0
+* Source: https://github.com/fluentassertions/fluentassertions/
diff --git a/Assembly-CSharp-firstpass.csproj.DotSettings b/Assembly-CSharp-firstpass.csproj.DotSettings
new file mode 100644
index 0000000..f3a4b90
--- /dev/null
+++ b/Assembly-CSharp-firstpass.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ True
\ No newline at end of file
diff --git a/Assets/Plugins.meta b/Assets/Plugins.meta
new file mode 100644
index 0000000..441f095
--- /dev/null
+++ b/Assets/Plugins.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1143ea67f80f30d4eb91287bd083a8a4
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/FluentAssertions.meta b/Assets/Plugins/FluentAssertions.meta
new file mode 100644
index 0000000..f323f7c
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ca263302667a4bf4cb87fe0aa1a77aec
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/FluentAssertions/Core.meta b/Assets/Plugins/FluentAssertions/Core.meta
new file mode 100644
index 0000000..53d4248
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions/Core.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b221e90a28450b04f8aec26231827895
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/FluentAssertions/Core/AndConstraint.cs b/Assets/Plugins/FluentAssertions/Core/AndConstraint.cs
new file mode 100644
index 0000000..06b21cc
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions/Core/AndConstraint.cs
@@ -0,0 +1,23 @@
+using System.Diagnostics;
+
+namespace FluentAssertions
+{
+ [DebuggerNonUserCode]
+ public class AndConstraint
+ {
+ private readonly T parentConstraint;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AndConstraint(T parentConstraint)
+ {
+ this.parentConstraint = parentConstraint;
+ }
+
+ public T And
+ {
+ get { return parentConstraint; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/FluentAssertions/Core/AndConstraint.cs.meta b/Assets/Plugins/FluentAssertions/Core/AndConstraint.cs.meta
new file mode 100644
index 0000000..aa99ceb
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions/Core/AndConstraint.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8e6a47299cda6a04f886f40d493bb813
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/FluentAssertions/Core/AndWhichConstraint.cs b/Assets/Plugins/FluentAssertions/Core/AndWhichConstraint.cs
new file mode 100644
index 0000000..1fb2093
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions/Core/AndWhichConstraint.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using FluentAssertions.Common;
+using FluentAssertions.Formatting;
+
+namespace FluentAssertions
+{
+ ///
+ /// Constraint which can be returned from an assertion which matches a condition and which will allow
+ /// further matches to be performed on the matched condition as well as the parent constraint.
+ ///
+ /// The type of the original constraint that was matched
+ /// The type of the matched object which the parent constraint matched
+ public class AndWhichConstraint : AndConstraint
+ {
+ private readonly Lazy matchedConstraint;
+
+ public AndWhichConstraint(TParentConstraint parentConstraint, TMatchedElement matchedConstraint)
+ : base(parentConstraint)
+ {
+ this.matchedConstraint =
+ new Lazy(() => matchedConstraint);
+ }
+
+ public AndWhichConstraint(TParentConstraint parentConstraint, IEnumerable matchedConstraint)
+ : base(parentConstraint)
+ {
+ this.matchedConstraint =
+ new Lazy(
+ () => SingleOrDefault(matchedConstraint));
+ }
+
+ private static TMatchedElement SingleOrDefault(
+ IEnumerable matchedConstraint)
+ {
+ TMatchedElement[] matchedElements = matchedConstraint.ToArray();
+
+ if (matchedElements.Count() > 1)
+ {
+ string foundObjects = string.Join(Environment.NewLine,
+ matchedElements.Select(
+ ele => "\t" + Formatter.ToString(ele)));
+
+ string message = string.Format(
+ "More than one object found. FluentAssertions cannot determine which object is meant. Found objects:{0}{1}",
+ Environment.NewLine,
+ foundObjects);
+
+ Services.ThrowException(message);
+ }
+
+ return matchedElements.Single();
+ }
+
+ ///
+ /// Returns the single result of a prior assertion that is used to select a nested or collection item.
+ ///
+ public TMatchedElement Which
+ {
+ get { return matchedConstraint.Value; }
+ }
+
+ ///
+ /// Returns the single result of a prior assertion that is used to select a nested or collection item.
+ ///
+ ///
+ /// Just a convenience property that returns the same value as .
+ ///
+ public TMatchedElement Subject
+ {
+ get { return Which; }
+ }
+ }
+}
+
diff --git a/Assets/Plugins/FluentAssertions/Core/AndWhichConstraint.cs.meta b/Assets/Plugins/FluentAssertions/Core/AndWhichConstraint.cs.meta
new file mode 100644
index 0000000..98a177f
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions/Core/AndWhichConstraint.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c8307fb9bef0c0a4d865164b701628c3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/FluentAssertions/Core/AssertionOptions.cs b/Assets/Plugins/FluentAssertions/Core/AssertionOptions.cs
new file mode 100644
index 0000000..481e32a
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions/Core/AssertionOptions.cs
@@ -0,0 +1,58 @@
+#region
+
+using System;
+using FluentAssertions.Common;
+using FluentAssertions.Equivalency;
+
+#endregion
+
+namespace FluentAssertions
+{
+ ///
+ /// Holds any global options that control the behavior of FluentAssertions.
+ ///
+ public static class AssertionOptions
+ {
+ private static EquivalencyAssertionOptions defaults = new EquivalencyAssertionOptions();
+
+ static AssertionOptions()
+ {
+ EquivalencySteps = new EquivalencyStepCollection();
+ }
+
+ public static EquivalencyAssertionOptions CloneDefaults()
+ {
+ return new EquivalencyAssertionOptions(defaults);
+ }
+
+ ///
+ /// Defines a predicate with which the determines if it should process
+ /// an object's properties or not.
+ ///
+ ///
+ /// Returns true if the object should be treated as a value type and its
+ /// must be used during a structural equivalency check.
+ ///
+ public static Func IsValueType = type =>
+ (type.Namespace == typeof (int).Namespace) &&
+ !type.IsSameOrInherits(typeof(Exception));
+
+ ///
+ /// Allows configuring the defaults used during a structural equivalency assertion.
+ ///
+ ///
+ /// An action that is used to configure the defaults.
+ ///
+ public static void AssertEquivalencyUsing(
+ Func defaultsConfigurer)
+ {
+ defaults = defaultsConfigurer(defaults);
+ }
+
+ ///
+ /// Represents a mutable collection of steps that are executed while asserting a (collection of) object(s)
+ /// is structurally equivalent to another (collection of) object(s).
+ ///
+ public static EquivalencyStepCollection EquivalencySteps { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/FluentAssertions/Core/AssertionOptions.cs.meta b/Assets/Plugins/FluentAssertions/Core/AssertionOptions.cs.meta
new file mode 100644
index 0000000..c442954
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions/Core/AssertionOptions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 673bfff1b81f6c6468c0957ecf933787
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/FluentAssertions/Core/Collections.meta b/Assets/Plugins/FluentAssertions/Core/Collections.meta
new file mode 100644
index 0000000..cb7d8f6
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions/Core/Collections.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c5b22091f289dd74bbe255baa5f43351
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/FluentAssertions/Core/Collections/CollectionAssertions.cs b/Assets/Plugins/FluentAssertions/Core/Collections/CollectionAssertions.cs
new file mode 100644
index 0000000..c8e4165
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions/Core/Collections/CollectionAssertions.cs
@@ -0,0 +1,1373 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using FluentAssertions.Common;
+using FluentAssertions.Execution;
+using FluentAssertions.Primitives;
+
+namespace FluentAssertions.Collections
+{
+ ///
+ /// Contains a number of methods to assert that an is in the expected state.
+ ///
+ public abstract class CollectionAssertions : ReferenceTypeAssertions
+ where TAssertions : CollectionAssertions
+ where TSubject : IEnumerable
+ {
+ ///
+ /// Asserts that the collection does not contain any items.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint BeEmpty(string because = "", params object[] becauseArgs)
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .WithExpectation("Expected {context:collection} to be empty{reason}, ")
+ .ForCondition(!ReferenceEquals(Subject, null))
+ .FailWith("but found {0}.", Subject)
+ .Then
+ .Given(() => Subject.Cast())
+ .ForCondition(collection => !collection.Any())
+ .FailWith("but found {0}.", collection => collection);
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Asserts that the collection contains at least 1 item.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs)
+ {
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} not to be empty{reason}, but found {0}.", Subject);
+ }
+
+ IEnumerable enumerable = Subject.Cast();
+
+ Execute.Assertion
+ .ForCondition(enumerable.Any())
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} not to be empty{reason}.");
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Asserts that the collection is null or does not contain any items.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint BeNullOrEmpty(string because = "", params object[] becauseArgs)
+ {
+ var nullOrEmpty = ReferenceEquals(Subject, null) || !Subject.Cast().Any();
+
+ Execute.Assertion.ForCondition(nullOrEmpty)
+ .BecauseOf(because, becauseArgs)
+ .FailWith(
+ "Expected {context:collection} to be null or empty{reason}, but found {0}.",
+ Subject);
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Asserts that the collection is not null and contains at least 1 item.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint NotBeNullOrEmpty(string because = "", params object[] becauseArgs)
+ {
+ return NotBeNull(because, becauseArgs)
+ .And.NotBeEmpty(because, becauseArgs);
+ }
+
+ ///
+ /// Asserts that the collection does not contain any duplicate items.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint OnlyHaveUniqueItems(string because = "", params object[] becauseArgs)
+ {
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to only have unique items{reason}, but found {0}.", Subject);
+ }
+
+ IGrouping groupWithMultipleItems = Subject.Cast()
+ .GroupBy(o => o)
+ .FirstOrDefault(g => g.Count() > 1);
+
+ if (groupWithMultipleItems != null)
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to only have unique items{reason}, but item {0} is not unique.",
+ groupWithMultipleItems.Key);
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Asserts that the collection does not contain any null items.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint NotContainNulls(string because = "", params object[] becauseArgs)
+ {
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} not to contain nulls{reason}, but collection is .");
+ }
+
+ object[] values = Subject.Cast().ToArray();
+ for (int index = 0; index < values.Length; index++)
+ {
+ if (ReferenceEquals(values[index], null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} not to contain nulls{reason}, but found one at index {0}.", index);
+ }
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Expects the current collection to contain all the same elements in the same order as the collection identified by
+ /// . Elements are compared using their .
+ ///
+ /// A params array with the expected elements.
+ public AndConstraint Equal(params object[] elements)
+ {
+ return Equal(elements, String.Empty);
+ }
+
+ ///
+ /// Expects the current collection to contain all the same elements in the same order as the collection identified by
+ /// . Elements are compared using their .
+ ///
+ /// An with the expected elements.
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint Equal(IEnumerable expected, string because = "", params object[] becauseArgs)
+ {
+ AssertSubjectEquality(expected, (s, e) => s.IsSameOrEqualTo(e), because, becauseArgs);
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ protected void AssertSubjectEquality(IEnumerable expectation, Func equalityComparison,
+ string because = "", params object[] becauseArgs)
+ {
+
+ bool subjectIsNull = ReferenceEquals(Subject, null);
+ bool expectationIsNull = ReferenceEquals(expectation, null);
+ if (subjectIsNull && expectationIsNull)
+ {
+ return;
+ }
+
+ if (expectation == null)
+ {
+ throw new ArgumentNullException("expectation", "Cannot compare collection with .");
+ }
+
+ TExpected[] expectedItems = expectation.Cast().ToArray();
+
+ AssertionScope assertion = Execute.Assertion.BecauseOf(because, becauseArgs);
+ if (subjectIsNull)
+ {
+ assertion.FailWith("Expected {context:collection} to be equal to {0}{reason}, but found .", expectedItems);
+ }
+
+ assertion
+ .WithExpectation("Expected {context:collection} to be equal to {0}{reason}, ", expectedItems)
+ .Given(() => Subject.Cast().ToList().AsEnumerable())
+ .AssertCollectionsHaveSameCount(expectedItems.Length)
+ .Then
+ .AssertCollectionsHaveSameItems(expectedItems, (a, e) => a.IndexOfFirstDifferenceWith(e, equalityComparison));
+ }
+
+ ///
+ /// Expects the current collection not to contain all the same elements in the same order as the collection identified by
+ /// . Elements are compared using their .
+ ///
+ /// An with the elements that are not expected.
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint NotEqual(IEnumerable unexpected, string because = "", params object[] becauseArgs)
+ {
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected collections not to be equal{reason}, but found .");
+ }
+
+ if (unexpected == null)
+ {
+ throw new ArgumentNullException("unexpected", "Cannot compare collection with .");
+ }
+
+ List actualitems = Subject.Cast().ToList();
+
+ if (actualitems.SequenceEqual(unexpected.Cast()))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Did not expect collections {0} and {1} to be equal{reason}.", unexpected, actualitems);
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Expects the current collection to contain all elements of the collection identified by ,
+ /// regardless of the order. Elements are compared using their .
+ ///
+ /// A params array with the expected elements.
+ public AndConstraint BeEquivalentTo(params object[] elements)
+ {
+ return BeEquivalentTo(elements, String.Empty);
+ }
+
+ ///
+ /// Expects the current collection to contain all elements of the collection identified by ,
+ /// regardless of the order. Elements are compared using their .
+ ///
+ /// An with the expected elements.
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint BeEquivalentTo(IEnumerable expected, string because = "", params object[] becauseArgs)
+ {
+ if (expected == null)
+ {
+ throw new NullReferenceException("Cannot verify equivalence against a collection.");
+ }
+
+ Execute.Assertion
+ .ForCondition(!ReferenceEquals(Subject, null))
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to be equivalent to {0}{reason}, but found .", expected);
+
+ List expectedItems = expected.Cast().ToList();
+ List actualItems = Subject.Cast().ToList();
+
+ bool haveSameLength = Execute.Assertion
+ .ForCondition(actualItems.Count <= expectedItems.Count)
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} {0} to be equivalent to {1}{reason}, but it contains too many items.",
+ actualItems, expectedItems);
+
+ if (haveSameLength)
+ {
+ object[] missingItems = GetMissingItems(expectedItems, actualItems);
+
+ Execute.Assertion
+ .ForCondition(missingItems.Length == 0)
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} {0} to be equivalent to {1}{reason}, but it misses {2}.",
+ actualItems, expectedItems, missingItems);
+ }
+ return new AndConstraint((TAssertions)this);
+ }
+
+ public AndConstraint BeEquivalentTo(IEnumerable expected, string because = "", params object[] becauseArgs)
+ {
+ if (expected == null)
+ {
+ throw new NullReferenceException("Cannot verify equivalence against a collection.");
+ }
+
+ Execute.Assertion
+ .ForCondition(!ReferenceEquals(Subject, null))
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to be equivalent to {0}{reason}, but found .", expected);
+
+ List expectedItems = expected.ToList();
+ List actualItems = Subject.Cast().ToList();
+
+ bool haveSameLength = Execute.Assertion
+ .ForCondition(actualItems.Count <= expectedItems.Count)
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} {0} to be equivalent to {1}{reason}, but it contains too many items.",
+ actualItems, expectedItems);
+
+ if (haveSameLength)
+ {
+ T[] missingItems = GetMissingItems(expectedItems, actualItems);
+
+ Execute.Assertion
+ .ForCondition(missingItems.Length == 0)
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} {0} to be equivalent to {1}{reason}, but it misses {2}.",
+ actualItems, expectedItems, missingItems);
+ }
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Expects the current collection not to contain all elements of the collection identified by ,
+ /// regardless of the order. Elements are compared using their .
+ ///
+ /// An with the unexpected elements.
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint NotBeEquivalentTo(IEnumerable unexpected, string because = "",
+ params object[] becauseArgs)
+ {
+ if (unexpected == null)
+ {
+ throw new NullReferenceException("Cannot verify inequivalence against a collection.");
+ }
+
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} not to be equivalent{reason}, but found .");
+ }
+
+ IEnumerable actualItems = Subject.Cast();
+ IEnumerable unexpectedItems = unexpected.Cast();
+
+ if (actualItems.Count() == unexpectedItems.Count())
+ {
+ object[] missingItems = GetMissingItems(unexpectedItems, actualItems);
+
+ Execute.Assertion
+ .ForCondition(missingItems.Length > 0)
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} {0} not be equivalent with collection {1}{reason}.", Subject,
+ unexpected);
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Asserts that the current collection only contains items that are assignable to the type .
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint ContainItemsAssignableTo(string because = "", params object[] becauseArgs)
+ {
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to contain element assignable to type {0}{reason}, but found .",
+ typeof(T));
+ }
+
+ int index = 0;
+ foreach (object item in Subject)
+ {
+ if (!(item is T))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith(
+ "Expected {context:collection} to contain only items of type {0}{reason}, but item {1} at index {2} is of type {3}.",
+ typeof(T), item, index, item.GetType());
+ }
+
+ ++index;
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ private static T[] GetMissingItems(IEnumerable expectedItems, IEnumerable actualItems)
+ {
+ List missingItems = new List();
+ List subject = actualItems.ToList();
+
+ while (expectedItems.Any())
+ {
+ T expectation = expectedItems.First();
+ if (subject.Contains(expectation))
+ {
+ subject.Remove(expectation);
+ }
+ else
+ {
+ missingItems.Add(expectation);
+ }
+
+ expectedItems = expectedItems.Skip(1).ToArray();
+ }
+
+ return missingItems.ToArray();
+ }
+
+ ///
+ /// Expects the current collection to contain the specified elements in any order. Elements are compared
+ /// using their implementation.
+ ///
+ /// An with the expected elements.
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint Contain(IEnumerable expected, string because = "", params object[] becauseArgs)
+ {
+ if (expected == null)
+ {
+ throw new NullReferenceException("Cannot verify containment against a collection");
+ }
+
+ IEnumerable expectedObjects = expected.Cast().ToArray();
+ if (!expectedObjects.Any())
+ {
+ throw new ArgumentException("Cannot verify containment against an empty collection");
+ }
+
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to contain {0}{reason}, but found .", expected);
+ }
+
+ if (expected is string)
+ {
+ if (!Subject.Cast().Contains(expected))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} {0} to contain {1}{reason}.", Subject, expected);
+ }
+ }
+ else
+ {
+ IEnumerable missingItems = expectedObjects.Except(Subject.Cast());
+ if (missingItems.Any())
+ {
+ if (expectedObjects.Count() > 1)
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} {0} to contain {1}{reason}, but could not find {2}.", Subject,
+ expected, missingItems);
+ }
+ else
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} {0} to contain {1}{reason}.", Subject,
+ expected.Cast().First());
+ }
+ }
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Expects the current collection to contain the specified elements in the exact same order, not necessarily consecutive.
+ /// using their implementation.
+ ///
+ /// An with the expected elements.
+ public AndConstraint ContainInOrder(params object[] expected)
+ {
+ return ContainInOrder(expected, "");
+ }
+
+ ///
+ /// Expects the current collection to contain the specified elements in the exact same order, not necessarily consecutive.
+ ///
+ ///
+ /// Elements are compared using their implementation.
+ ///
+ /// An with the expected elements.
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint ContainInOrder(IEnumerable expected, string because = "",
+ params object[] becauseArgs)
+ {
+ if (expected == null)
+ {
+ throw new NullReferenceException("Cannot verify ordered containment against a collection.");
+ }
+
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to contain {0} in order{reason}, but found .", expected);
+ }
+
+ object[] expectedItems = expected.Cast().ToArray();
+ object[] actualItems = Subject.Cast().ToArray();
+
+ for (int index = 0; index < expectedItems.Length; index++)
+ {
+ object expectedItem = expectedItems[index];
+ actualItems = actualItems.SkipWhile(actualItem => !actualItem.IsSameOrEqualTo(expectedItem)).ToArray();
+ if (actualItems.Any())
+ {
+ actualItems = actualItems.Skip(1).ToArray();
+ }
+ else
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith(
+ "Expected {context:collection} {0} to contain items {1} in order{reason}, but {2} (index {3}) did not appear (in the right order).",
+ Subject, expected, expectedItem, index);
+ }
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Expects the current collection to have all elements in ascending order. Elements are compared
+ /// using their implementation.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint BeInAscendingOrder(string because = "", params object[] becauseArgs)
+ {
+ return BeInAscendingOrder(Comparer.Default, because, becauseArgs);
+ }
+
+ ///
+ /// Expects the current collection to have all elements in ascending order. Elements are compared
+ /// using the given implementation.
+ ///
+ ///
+ /// The object that should be used to determine the expected ordering.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint BeInAscendingOrder(IComparer comparer, string because = "", params object[] becauseArgs)
+ {
+ return BeInOrder(comparer, SortOrder.Ascending, because, becauseArgs);
+ }
+
+ ///
+ /// Expects the current collection to have all elements in descending order. Elements are compared
+ /// using their implementation.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint BeInDescendingOrder(string because = "", params object[] becauseArgs)
+ {
+ return BeInDescendingOrder(Comparer.Default, because, becauseArgs);
+ }
+
+ ///
+ /// Expects the current collection to have all elements in descending order. Elements are compared
+ /// using the given implementation.
+ ///
+ ///
+ /// The object that should be used to determine the expected ordering.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint BeInDescendingOrder(IComparer comparer, string because = "", params object[] becauseArgs)
+ {
+ return BeInOrder(comparer, SortOrder.Descending, because, becauseArgs);
+ }
+
+ ///
+ /// Expects the current collection to have all elements in the specified .
+ /// Elements are compared using their implementation.
+ ///
+ private AndConstraint BeInOrder(
+ IComparer comparer, SortOrder expectedOrder, string because = "", params object[] becauseArgs)
+ {
+ string sortOrder = (expectedOrder == SortOrder.Ascending) ? "ascending" : "descending";
+
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to contain items in " + sortOrder + " order{reason}, but found {1}.",
+ Subject);
+ }
+
+ object[] actualItems = Subject.Cast().ToArray();
+
+ object[] orderedItems = (expectedOrder == SortOrder.Ascending)
+ ? actualItems.OrderBy(item => item, comparer).ToArray()
+ : actualItems.OrderByDescending(item => item, comparer).ToArray();
+
+ for (int index = 0; index < orderedItems.Length; index++)
+ {
+ Execute.Assertion
+ .ForCondition(actualItems[index].IsSameOrEqualTo(orderedItems[index]))
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to contain items in " + sortOrder +
+ " order{reason}, but found {0} where item at index {1} is in wrong order.",
+ Subject, index);
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Asserts the current collection does not have all elements in ascending order. Elements are compared
+ /// using their implementation.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint NotBeAscendingInOrder(string because = "", params object[] becauseArgs)
+ {
+ return NotBeAscendingInOrder(Comparer.Default, because, becauseArgs);
+ }
+
+ ///
+ /// Asserts the current collection does not have all elements in ascending order. Elements are compared
+ /// using their implementation.
+ ///
+ ///
+ /// The object that should be used to determine the expected ordering.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint NotBeAscendingInOrder(IComparer comparer, string because = "", params object[] becauseArgs)
+ {
+ return NotBeInOrder(comparer, SortOrder.Ascending, because, becauseArgs);
+ }
+
+ ///
+ /// Asserts the current collection does not have all elements in descending order. Elements are compared
+ /// using their implementation.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint NotBeDescendingInOrder(string because = "", params object[] becauseArgs)
+ {
+ return NotBeDescendingInOrder(Comparer.Default, because, becauseArgs);
+ }
+
+ ///
+ /// Asserts the current collection does not have all elements in descending order. Elements are compared
+ /// using their implementation.
+ ///
+ ///
+ /// The object that should be used to determine the expected ordering.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint NotBeDescendingInOrder(IComparer comparer, string because = "", params object[] becauseArgs)
+ {
+ return NotBeInOrder(comparer, SortOrder.Descending, because, becauseArgs);
+ }
+
+ ///
+ /// Asserts the current collection does not have all elements in ascending order. Elements are compared
+ /// using their implementation.
+ ///
+ private AndConstraint NotBeInOrder(IComparer comparer, SortOrder order, string because = "", params object[] becauseArgs)
+ {
+ string sortOrder = (order == SortOrder.Ascending) ? "ascending" : "descending";
+
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith(
+ "Did not expect {context:collection} to contain items in " + sortOrder + " order{reason}, but found {1}.",
+ Subject);
+ }
+
+ object[] orderedItems = (order == SortOrder.Ascending)
+ ? Subject.Cast().OrderBy(item => item, comparer).ToArray()
+ : Subject.Cast().OrderByDescending(item => item, comparer).ToArray();
+
+ object[] actualItems = Subject.Cast().ToArray();
+
+ bool itemsAreUnordered = actualItems
+ .Where((actualItem, index) => !actualItem.IsSameOrEqualTo(orderedItems[index]))
+ .Any();
+
+ if (!itemsAreUnordered)
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith(
+ "Did not expect {context:collection} to contain items in " + sortOrder + " order{reason}, but found {0}.",
+ Subject);
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Asserts that the collection is a subset of the .
+ ///
+ /// An with the expected superset.
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint BeSubsetOf(IEnumerable expectedSuperset, string because = "",
+ params object[] becauseArgs)
+ {
+ if (expectedSuperset == null)
+ {
+ throw new NullReferenceException("Cannot verify a subset against a collection.");
+ }
+
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to be a subset of {0}{reason}, but found {1}.", expectedSuperset,
+ Subject);
+ }
+
+ IEnumerable expectedItems = expectedSuperset.Cast();
+ IEnumerable actualItems = Subject.Cast();
+
+ IEnumerable excessItems = actualItems.Except(expectedItems);
+
+ if (excessItems.Any())
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith(
+ "Expected {context:collection} to be a subset of {0}{reason}, but items {1} are not part of the superset.",
+ expectedSuperset, excessItems);
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Asserts that the collection is not a subset of the .
+ ///
+ /// An with the unexpected superset.
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint NotBeSubsetOf(IEnumerable unexpectedSuperset, string because = "",
+ params object[] becauseArgs)
+ {
+ Execute.Assertion
+ .ForCondition(!ReferenceEquals(Subject, null))
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Cannot assert a collection against a subset.");
+
+ IEnumerable expectedItems = unexpectedSuperset.Cast();
+ object[] actualItems = Subject.Cast().ToArray();
+
+ if (actualItems.Intersect(expectedItems).Count() == actualItems.Count())
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Did not expect {context:collection} {0} to be a subset of {1}{reason}.", actualItems, expectedItems);
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Assert that the current collection has the same number of elements as .
+ ///
+ /// The other collection with the same expected number of elements
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint HaveSameCount(IEnumerable otherCollection, string because = "",
+ params object[] becauseArgs)
+ {
+ if (otherCollection == null)
+ {
+ throw new NullReferenceException("Cannot verify count against a collection.");
+ }
+
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to have the same count as {0}{reason}, but found {1}.",
+ otherCollection,
+ Subject);
+ }
+
+ IEnumerable enumerable = Subject.Cast();
+
+ int actualCount = enumerable.Count();
+ int expectedCount = otherCollection.Cast().Count();
+
+ Execute.Assertion
+ .ForCondition(actualCount == expectedCount)
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to have {0} item(s){reason}, but found {1}.", expectedCount, actualCount);
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Asserts that the current collection has the supplied at the
+ /// supplied .
+ ///
+ /// The index where the element is expected
+ /// The expected element
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndWhichConstraint HaveElementAt(int index, object element, string because = "",
+ params object[] becauseArgs)
+ {
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to have element at index {0}{reason}, but found {1}.", index, Subject);
+ }
+
+ IEnumerable enumerable = Subject.Cast();
+
+ object actual = null;
+ if (index < enumerable.Count())
+ {
+ actual = Subject.Cast().ElementAt(index);
+
+ Execute.Assertion
+ .ForCondition(actual.IsSameOrEqualTo(element))
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {0} at index {1}{reason}, but found {2}.", element, index, actual);
+ }
+ else
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {0} at index {1}{reason}, but found no element.", element, index);
+ }
+
+ return new AndWhichConstraint((TAssertions)this, actual);
+ }
+
+ ///
+ /// Asserts that the current collection does not contain the supplied items. Elements are compared
+ /// using their implementation.
+ ///
+ /// An with the unexpected elements.
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint NotContain(IEnumerable unexpected, string because = "", params object[] becauseArgs)
+ {
+ if (unexpected == null)
+ {
+ throw new NullReferenceException("Cannot verify non-containment against a collection");
+ }
+
+ IEnumerable unexpectedObjects = unexpected.Cast().ToArray();
+ if (!unexpectedObjects.Any())
+ {
+ throw new ArgumentException("Cannot verify non-containment against an empty collection");
+ }
+
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to not contain {0}{reason}, but found .", unexpected);
+ }
+
+ if (unexpected is string)
+ {
+ if (Subject.Cast().Contains(unexpected))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} {0} to not contain {1}{reason}.", Subject, unexpected);
+ }
+ }
+ else
+ {
+ IEnumerable foundItems = unexpectedObjects.Intersect(Subject.Cast());
+ if (foundItems.Any())
+ {
+ if (unexpectedObjects.Count() > 1)
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} {0} to not contain {1}{reason}, but found {2}.", Subject,
+ unexpected, foundItems);
+ }
+ else
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} {0} to not contain element {1}{reason}.", Subject,
+ unexpectedObjects.First());
+ }
+ }
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Asserts that the collection shares one or more items with the specified .
+ ///
+ /// The with the expected shared items.
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint IntersectWith(IEnumerable otherCollection, string because = "",
+ params object[] becauseArgs)
+ {
+ if (otherCollection == null)
+ {
+ throw new NullReferenceException("Cannot verify intersection against a collection.");
+ }
+
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:collection} to intersect with {0}{reason}, but found {1}.", otherCollection,
+ Subject);
+ }
+
+ IEnumerable otherItems = otherCollection.Cast();
+ IEnumerable sharedItems = Subject.Cast().Intersect(otherItems);
+
+ if (!sharedItems.Any())
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith(
+ "Expected {context:collection} to intersect with {0}{reason}, but {1} does not contain any shared items.",
+ otherCollection, Subject);
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Asserts that the collection does not share any items with the specified .
+ ///
+ /// The to compare to.
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint NotIntersectWith(IEnumerable otherCollection, string because = "",
+ params object[] becauseArgs)
+ {
+ if (otherCollection == null)
+ {
+ throw new NullReferenceException("Cannot verify intersection against a collection.");
+ }
+
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Did not expect {context:collection} to intersect with {0}{reason}, but found {1}.", otherCollection,
+ Subject);
+ }
+
+ IEnumerable otherItems = otherCollection.Cast();
+ IEnumerable sharedItems = Subject.Cast().Intersect(otherItems);
+
+ if (sharedItems.Any())
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith(
+ "Did not expect {context:collection} to intersect with {0}{reason}, but found the following shared items {1}.",
+ otherCollection, sharedItems);
+ }
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Asserts that the collection starts with the specified .
+ ///
+ ///
+ /// The element that is expected to appear at the start of the collection. The object's
+ /// is used to compare the element.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint StartWith(object element, string because = "", params object[] becauseArgs)
+ {
+ AssertCollectionStartsWith(Subject?.Cast(), new[] { element }, ObjectExtensions.IsSameOrEqualTo, because, becauseArgs);
+ return new AndConstraint((TAssertions) this);
+ }
+
+ protected void AssertCollectionStartsWith(IEnumerable actualItems, TExpected[] expected, Func equalityComparison, string because = "", params object[] becauseArgs)
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .WithExpectation("Expected {context:collection} to start with {0}{reason}, ", expected)
+ .Given(() => actualItems)
+ .AssertCollectionIsNotNullOrEmpty(expected.Length)
+ .Then
+ .AssertCollectionHasEnoughItems(expected.Length)
+ .Then
+ .AssertCollectionsHaveSameItems(expected, (a, e) => a.Take(e.Length).IndexOfFirstDifferenceWith(e, equalityComparison));
+ }
+
+ ///
+ /// Asserts that the collection ends with the specified .
+ ///
+ ///
+ /// The element that is expected to appear at the end of the collection. The object's
+ /// is used to compare the element.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint EndWith(object element, string because = "", params object[] becauseArgs)
+ {
+ AssertCollectionEndsWith(Subject?.Cast(), new[] { element }, ObjectExtensions.IsSameOrEqualTo, because, becauseArgs);
+ return new AndConstraint((TAssertions) this);
+ }
+
+ protected void AssertCollectionEndsWith(IEnumerable actual, TExpected[] expected, Func equalityComparison, string because = "", params object[] becauseArgs)
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .WithExpectation("Expected {context:collection} to end with {0}{reason}, ", expected)
+ .Given(() => actual)
+ .AssertCollectionIsNotNullOrEmpty(expected.Length)
+ .Then
+ .AssertCollectionHasEnoughItems(expected.Length)
+ .Then
+ .AssertCollectionsHaveSameItems(expected, (a, e) =>
+ {
+ int firstIndexToCompare = a.Length - e.Length;
+ int index = a.Skip(firstIndexToCompare).IndexOfFirstDifferenceWith(e, equalityComparison);
+ return index >= 0 ? index + firstIndexToCompare : index;
+ });
+ }
+
+ ///
+ /// Asserts that the element directly precedes the .
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint HaveElementPreceding(object successor, object expectation, string because = "", params object[] becauseArgs)
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .WithExpectation("Expected {context:collection} to have {0} precede {1}{reason}, ", expectation, successor)
+ .Given(() => Subject.Cast())
+ .ForCondition(subject => subject.Any())
+ .FailWith("but the collection is empty.")
+ .Then
+ .ForCondition(subject => HasPredecessor(successor, subject))
+ .FailWith("but found nothing.")
+ .Then
+ .Given(subject => PredecessorOf(successor, subject))
+ .ForCondition(predecessor => predecessor.IsSameOrEqualTo(expectation))
+ .FailWith("but found {0}.", predecessor => predecessor);
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ private bool HasPredecessor(object successor, IEnumerable subject)
+ {
+ return !ReferenceEquals(subject.First(), successor);
+ }
+
+ private object PredecessorOf(object succesor, IEnumerable subject)
+ {
+ object[] collection = subject.ToArray();
+ int index = Array.IndexOf(collection, succesor);
+ return (index > 0) ? collection[index - 1] : null;
+ }
+
+ ///
+ /// Asserts that the element directly succeeds the .
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint HaveElementSucceeding(object predecessor, object expectation, string because = "", params object[] becauseArgs)
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .WithExpectation("Expected {context:collection} to have {0} succeed {1}{reason}, ", expectation, predecessor)
+ .Given(() => Subject.Cast())
+ .ForCondition(subject => subject.Any())
+ .FailWith("but the collection is empty.")
+ .Then
+ .ForCondition(subject => HasSuccessor(predecessor, subject))
+ .FailWith("but found nothing.")
+ .Then
+ .Given(subject => SuccessorOf(predecessor, subject))
+ .ForCondition(successor => successor.IsSameOrEqualTo(expectation))
+ .FailWith("but found {0}.", successor => successor);
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ private bool HasSuccessor(object predecessor, IEnumerable subject)
+ {
+ return !ReferenceEquals(subject.Last(), predecessor);
+ }
+
+ private object SuccessorOf(object predecessor, IEnumerable subject)
+ {
+ object[] collection = subject.ToArray();
+ int index = Array.IndexOf(collection, predecessor);
+ return (index < (collection.Length - 1)) ? collection[index + 1] : null;
+ }
+
+ ///
+ /// Asserts that all items in the collection are of the specified type
+ ///
+ /// The expected type of the objects
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint AllBeAssignableTo(string because = "", params object[] becauseArgs)
+ {
+ return AllBeAssignableTo(typeof(T), because, becauseArgs);
+ }
+
+ ///
+ /// Asserts that all items in the collection are of the specified type
+ ///
+ /// The expected type of the objects
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint AllBeAssignableTo(Type expectedType, string because = "", params object[] becauseArgs)
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .WithExpectation("Expected type to be {0}{reason}, ", expectedType.FullName)
+ .Given(() => Subject.Cast())
+ .ForCondition(subject => subject.All(x => x != null))
+ .FailWith("but found a null element.")
+ .Then
+ .ForCondition(subject => subject.All(x => expectedType.IsAssignableFrom(x.GetType())))
+ .FailWith("but found {0}.", subject => $"[{string.Join(", ", subject.Select(x => x.GetType().FullName))}]");
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Asserts that all items in the collection are of the exact specified type
+ ///
+ /// The expected type of the objects
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint AllBeOfType(string because = "", params object[] becauseArgs)
+ {
+ return AllBeOfType(typeof(T), because, becauseArgs);
+ }
+
+ ///
+ /// Asserts that all items in the collection are of the exact specified type
+ ///
+ /// The expected type of the objects
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint AllBeOfType(Type expectedType, string because = "", params object[] becauseArgs)
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .WithExpectation("Expected type to be {0}{reason}, ", expectedType.FullName)
+ .Given(() => Subject.Cast())
+ .ForCondition(subject => subject.All(x => x != null))
+ .FailWith("but found a null element.")
+ .Then
+ .ForCondition(subject => subject.All(x => expectedType == x.GetType()))
+ .FailWith("but found {0}.", subject => $"[{string.Join(", ", subject.Select(x => x.GetType().FullName))}]");
+
+ return new AndConstraint((TAssertions)this);
+ }
+
+ ///
+ /// Returns the type of the subject the assertion applies on.
+ ///
+ protected override string Context
+ {
+ get { return "collection"; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/FluentAssertions/Core/Collections/CollectionAssertions.cs.meta b/Assets/Plugins/FluentAssertions/Core/Collections/CollectionAssertions.cs.meta
new file mode 100644
index 0000000..32d4dfc
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions/Core/Collections/CollectionAssertions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7d72028d05593d846a250498229e282d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/FluentAssertions/Core/Collections/GenericCollectionAssertions.cs b/Assets/Plugins/FluentAssertions/Core/Collections/GenericCollectionAssertions.cs
new file mode 100644
index 0000000..6fb9ae9
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions/Core/Collections/GenericCollectionAssertions.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Linq.Expressions;
+
+using FluentAssertions.Common;
+using FluentAssertions.Execution;
+
+namespace FluentAssertions.Collections
+{
+ [DebuggerNonUserCode]
+ public class GenericCollectionAssertions :
+ SelfReferencingCollectionAssertions>
+ {
+ public GenericCollectionAssertions(IEnumerable actualValue) : base(actualValue)
+ {
+ }
+
+ ///
+ /// Asserts that a collection is ordered in ascending order according to the value of the specified
+ /// .
+ ///
+ ///
+ /// A lambda expression that references the property that should be used to determine the expected ordering.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint> BeInAscendingOrder(
+ Expression> propertyExpression, string because = "", params object[] args)
+ {
+ return BeInAscendingOrder(propertyExpression, Comparer.Default, because, args);
+ }
+
+ ///
+ /// Asserts that a collection is ordered in ascending order according to the value of the specified
+ /// implementation.
+ ///
+ ///
+ /// The object that should be used to determine the expected ordering.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint> BeInAscendingOrder(
+ IComparer comparer, string because = "", params object[] args)
+ {
+ return BeInAscendingOrder(item => item, comparer, because, args);
+ }
+
+ ///
+ /// Asserts that a collection is ordered in ascending order according to the value of the specified
+ /// and implementation.
+ ///
+ ///
+ /// A lambda expression that references the property that should be used to determine the expected ordering.
+ ///
+ ///
+ /// The object that should be used to determine the expected ordering.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint> BeInAscendingOrder(
+ Expression> propertyExpression, IComparer comparer, string because = "", params object[] args)
+ {
+ return BeOrderedBy(propertyExpression, comparer, SortOrder.Ascending, because, args);
+ }
+
+ ///
+ /// Asserts that a collection is ordered in descending order according to the value of the specified
+ /// .
+ ///
+ ///
+ /// A lambda expression that references the property that should be used to determine the expected ordering.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint> BeInDescendingOrder(
+ Expression> propertyExpression, string because = "", params object[] args)
+ {
+ return BeInDescendingOrder(propertyExpression, Comparer.Default, because, args);
+ }
+
+ ///
+ /// Asserts that a collection is ordered in descending order according to the value of the specified
+ /// implementation.
+ ///
+ ///
+ /// The object that should be used to determine the expected ordering.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint> BeInDescendingOrder(
+ IComparer comparer, string because = "", params object[] args)
+ {
+ return BeInDescendingOrder(item => item, comparer, because, args);
+ }
+
+ ///
+ /// Asserts that a collection is ordered in descending order according to the value of the specified
+ /// and implementation.
+ ///
+ ///
+ /// A lambda expression that references the property that should be used to determine the expected ordering.
+ ///
+ ///
+ /// The object that should be used to determine the expected ordering.
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint> BeInDescendingOrder(
+ Expression> propertyExpression, IComparer comparer, string because = "", params object[] args)
+ {
+ return BeOrderedBy(propertyExpression, comparer, SortOrder.Descending, because, args);
+ }
+
+ private AndConstraint> BeOrderedBy(
+ Expression> propertyExpression, IComparer comparer, SortOrder direction, string because, object[] args)
+ {
+ if (comparer == null)
+ {
+ throw new ArgumentNullException("comparer",
+ "Cannot assert collection ordering without specifying a comparer.");
+ }
+
+ if (IsValidProperty(propertyExpression, because, args))
+ {
+ IList unordered = (Subject as IList) ?? Subject.ToList();
+
+ Func keySelector = propertyExpression.Compile();
+
+ IOrderedEnumerable expectation = (direction == SortOrder.Ascending)
+ ? unordered.OrderBy(keySelector, comparer)
+ : unordered.OrderByDescending(keySelector, comparer);
+
+ var orderString = propertyExpression.GetMemberPath();
+ orderString = orderString == "\"\"" ? string.Empty : " by " + orderString;
+
+ Execute.Assertion
+ .ForCondition(unordered.SequenceEqual(expectation))
+ .BecauseOf(because, args)
+ .FailWith("Expected collection {0} to be ordered{1}{reason} and result in {2}.",
+ Subject, orderString, expectation);
+ }
+
+ return new AndConstraint>(this);
+ }
+
+ private bool IsValidProperty(Expression> propertyExpression, string because, object[] args)
+ {
+ if (propertyExpression == null)
+ {
+ throw new ArgumentNullException("propertyExpression",
+ "Cannot assert collection ordering without specifying a property.");
+ }
+
+ return Execute.Assertion
+ .ForCondition(!ReferenceEquals(Subject, null))
+ .BecauseOf(because, args)
+ .FailWith("Expected collection to be ordered by {0}{reason} but found .",
+ propertyExpression.GetMemberPath());
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/FluentAssertions/Core/Collections/GenericCollectionAssertions.cs.meta b/Assets/Plugins/FluentAssertions/Core/Collections/GenericCollectionAssertions.cs.meta
new file mode 100644
index 0000000..1f9a4d9
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions/Core/Collections/GenericCollectionAssertions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6c74836f9ce5f05409b98f2fe139288e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/FluentAssertions/Core/Collections/GenericDictionaryAssertions.cs b/Assets/Plugins/FluentAssertions/Core/Collections/GenericDictionaryAssertions.cs
new file mode 100644
index 0000000..94d2654
--- /dev/null
+++ b/Assets/Plugins/FluentAssertions/Core/Collections/GenericDictionaryAssertions.cs
@@ -0,0 +1,956 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Linq.Expressions;
+using FluentAssertions.Common;
+using FluentAssertions.Execution;
+using FluentAssertions.Primitives;
+
+namespace FluentAssertions.Collections
+{
+ ///
+ /// Contains a number of methods to assert that an is in the expected state.
+ ///
+ [DebuggerNonUserCode]
+ public class GenericDictionaryAssertions :
+ ReferenceTypeAssertions, GenericDictionaryAssertions>
+ {
+ public GenericDictionaryAssertions(IDictionary dictionary)
+ {
+ if (dictionary != null)
+ {
+ Subject = dictionary;
+ }
+ }
+
+ #region HaveCount
+
+ ///
+ /// Asserts that the number of items in the dictionary matches the supplied amount.
+ ///
+ /// The expected number of items.
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint> HaveCount(int expected,
+ string because = "", params object[] becauseArgs)
+ {
+ if (ReferenceEquals(Subject, null))
+ {
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {0} item(s){reason}, but found {1}.", expected, Subject);
+ }
+
+ int actualCount = Subject.Count;
+
+ Execute.Assertion
+ .ForCondition((actualCount == expected))
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Expected {context:dictionary} {0} to have {1} item(s){reason}, but found {2}.", Subject, expected, actualCount);
+
+ return new AndConstraint>(this);
+ }
+
+ ///
+ /// Asserts that the number of items in the dictionary matches a condition stated by a predicate.
+ ///
+ /// The predicate which must be satisfied by the amount of items.
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because , it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ public AndConstraint> HaveCount(Expression> countPredicate,
+ string because = "", params object[] becauseArgs)
+ {
+ if (countPredicate == null)
+ {
+ throw new NullReferenceException("Cannot compare dictionary count against a