Inheritance: EffectTerminable
        /// <summary> Transforms a single range using the given modification. </summary>
        private Range Transform(Range range, RangeModification modification)
        {
            if (modification.WasAdded)
            {
                if (range.ContainsExclusive(modification.Index))
                {
                    return(new Range(range.StartIndex, range.EndIndex + modification.NumberOfItems));
                }
                else if (range.StartIndex < modification.Index)
                {
                    return(range);
                }
                else
                {
                    return(new Range(range.StartIndex + modification.NumberOfItems,
                                     range.EndIndex + modification.NumberOfItems));
                }
            }
            else
            {
                var deletionRange = new Range(modification.Index, modification.Index + modification.NumberOfItems);

                if (deletionRange.ContainsInclusive(range.StartIndex) && deletionRange.ContainsInclusive(range.EndIndex))
                {
                    // if the deletion range took out the range, remove it altogether
                    return(new Range(deletionRange.StartIndex, deletionRange.StartIndex));
                }
                else if (deletionRange.OverlapsInclusive(range))
                {
                    if (deletionRange.StartIndex <= range.StartIndex)
                    {
                        int overlappingCharCount     = deletionRange.EndIndex - range.StartIndex;
                        int numberOfCharactersBefore = range.StartIndex - deletionRange.StartIndex;

                        int numberOfAvailableCharsToDelete = Math.Min(overlappingCharCount,
                                                                      range.Length);

                        return(new Range(range.StartIndex - numberOfCharactersBefore,
                                         range.EndIndex - numberOfCharactersBefore - numberOfAvailableCharsToDelete));
                    }
                    else /* range.StartIndex <= deletionRange.EndIndex */
                    {
                        var numberOfCharsToDelete = Math.Min(range.EndIndex - deletionRange.StartIndex, deletionRange.Length);
                        return(new Range(range.StartIndex, range.EndIndex - numberOfCharsToDelete));
                    }
                }
                else if (deletionRange.StartIndex < range.StartIndex)
                {
                    return(new Range(range.StartIndex - modification.NumberOfItems,
                                     range.EndIndex - modification.NumberOfItems));
                }
                else
                {
                    return(range);
                }
            }
        }
        /// <summary>
        ///  Verifies that when the given modification is operated on the given range, that the expect
        ///  range is output.
        /// </summary>
        private void VerifyModification(Range original, RangeModification modification, Range expected)
        {
            // note, technically the single range is all that SHOULD be tested, but since everything
            // funnels through here, we might as well test that when the ranges are duplicated, that all of
            // the ranges end up the same (this wasn't always the case :: )

            {
                // single range, easy
                Reset();
                _collection.MarkRange(original, _markupDescriptor, null);

                /* Extra Check */
                var originalMarkup = _collection.First();
                originalMarkup.GetRange().Should().BeEquivalentTo(original);
                /* /Extra Check */

                _collection.UpdateFromEvent(modification);
                Ranges.Should().HaveElementAt(0, expected);

                /* Extra Check */
                var latestMarkup = _collection.First();
                latestMarkup.Should().BeSameAs(originalMarkup);
                /* /Extra Check */
            }

            {
                // duplicated range, let's make sure that works right
                Reset();
                _collection.MarkRange(original, _markupDescriptor, null);
                _collection.MarkRange(original, _markupDescriptor, null);
                _collection.UpdateFromEvent(modification);
                Ranges.Should().HaveElementAt(0, expected, "'duplicated twice'");
                Ranges.Should().HaveElementAt(1, expected, "'duplicated twice'");
            }

            {
                // duplicated duplicated range, just for good measure
                Reset();
                _collection.MarkRange(original, _markupDescriptor, null);
                _collection.MarkRange(original, _markupDescriptor, null);
                _collection.MarkRange(original, _markupDescriptor, null);
                _collection.UpdateFromEvent(modification);

                Ranges.Should().HaveElementAt(0, expected, "'duplicated thrice'");
                Ranges.Should().HaveElementAt(1, expected, "'duplicated thrice'");
                Ranges.Should().HaveElementAt(2, expected, "'duplicated thrice'");
            }

            // this is really to make sure that our Transform method is working correctly
            var transformed = Transform(original, modification);

            transformed.Should().Be(expected, "the Transform is supposed to be correct");
        }
        public void InsertText_Iterations(int insertionPoint, int length)
        {
            // it's actually very easy to take a given TextRange and TextModification and figure out what
            // the output region does (SubFragmentMarkupCollection is so complicated because it manages the
            // text-ranges in an efficient, easy to update manner).  Given that the algorithm is so easy
            // that it can be implemented in the function Transform, we can easily loop through every
            // combination of range and modification and verify that we get what we expect.  That's what
            // this function does.
            var actualModification = new RangeModification(insertionPoint, length, wasAdded: true);

            VerifyCorrectness(actualModification);
        }
        private void VerifyCorrectness(RangeModification modification)
        {
            AddAllRangesToCollection(_allValidTextRanges.Value);
            _collection.UpdateFromEvent(modification);

            var rangesOriginal = _allValidTextRanges.Value;
            var actualRanges   = Ranges;

            for (var i = 0; i < Ranges.Length; i++)
            {
                var type = modification.WasAdded ? "insert" : "deletion";
                var msg  = $"'operation {type} @{modification.Index} of @{modification.NumberOfItems} characters'.  Original was {rangesOriginal[i]}";

                var expected = Transform(rangesOriginal[i], modification);
                DidYouKnow.That(Ranges).Should().HaveElementAt(i, expected, because: msg); // msg
            }
        }
 public TestRangeModification(RangeModification modification)
 {
     Modification = modification;
 }