text: ﻿// Copyright (c) Microsoft Corporation.
  // Licensed under the MIT license.
  
  #include "precomp.h"
  #include "WexTestClass.h"
  #include "../../inc/consoletaeftemplates.hpp"
  
  #include "CommonState.hpp"
  
  #include "globals.h"
  
  #include "selection.hpp"
  #include "cmdline.h"
  
  #include "../interactivity/inc/ServiceLocator.hpp"
  
  using namespace WEX::Common;
  using namespace WEX::Logging;
  using namespace WEX::TestExecution;
  using Microsoft::Console::Interactivity::ServiceLocator;
  
  class SelectionTests
  {
      TEST_CLASS(SelectionTests);
  
      CommonState* m_state;
      Selection* m_pSelection;
  
      TEST_CLASS_SETUP(ClassSetup)
      {
          m_state = new CommonState();
  
          m_state->PrepareGlobalFont();
          m_state->PrepareGlobalScreenBuffer();
  
          m_pSelection = &Selection::Instance();
          return true;
      }
  
      TEST_CLASS_CLEANUP(ClassCleanup)
      {
          m_pSelection = nullptr;
  
          m_state->CleanupGlobalScreenBuffer();
          m_state->CleanupGlobalFont();
  
          delete m_state;
  
          return true;
      }
  
      TEST_METHOD_SETUP(MethodSetup)
      {
          return true;
      }
  
      TEST_METHOD_CLEANUP(MethodCleanup)
      {
          return true;
      }
  
      void VerifyGetSelectionRects_BoxMode()
      {
          const auto selectionRects = m_pSelection->GetSelectionRects();
          const UINT cRectanglesExpected = m_pSelection->_srSelectionRect.Bottom - m_pSelection->_srSelectionRect.Top + 1;
  
          if (VERIFY_ARE_EQUAL(cRectanglesExpected, selectionRects.size()))
          {
              for (auto iRect = 0; iRect < gsl::narrow<int>(selectionRects.size()); iRect++)
              {
                  // ensure each rectangle is exactly the width requested (block selection)
                  const SMALL_RECT* const psrRect = &selectionRects[iRect];
  
                  const short sRectangleLineNumber = (short)iRect + m_pSelection->_srSelectionRect.Top;
  
                  VERIFY_ARE_EQUAL(psrRect->Top, sRectangleLineNumber);
                  VERIFY_ARE_EQUAL(psrRect->Bottom, sRectangleLineNumber);
  
                  VERIFY_ARE_EQUAL(psrRect->Left, m_pSelection->_srSelectionRect.Left);
                  VERIFY_ARE_EQUAL(psrRect->Right, m_pSelection->_srSelectionRect.Right);
              }
          }
      }
  
      TEST_METHOD(TestGetSelectionRects_BoxMode)
      {
          m_pSelection->_fSelectionVisible = true;
  
          // set selection region
          m_pSelection->_srSelectionRect.Top = 0;
          m_pSelection->_srSelectionRect.Bottom = 3;
          m_pSelection->_srSelectionRect.Left = 1;
          m_pSelection->_srSelectionRect.Right = 10;
  
          // #1 top-left to bottom right selection first
          m_pSelection->_coordSelectionAnchor.X = m_pSelection->_srSelectionRect.Left;
          m_pSelection->_coordSelectionAnchor.Y = m_pSelection->_srSelectionRect.Top;
  
          // A. false/false for the selection modes should mean box selection
          m_pSelection->_fLineSelection = false;
          m_pSelection->_fUseAlternateSelection = false;
  
          VerifyGetSelectionRects_BoxMode();
  
          // B. true/true for the selection modes should also mean box selection
          m_pSelection->_fLineSelection = true;
          m_pSelection->_fUseAlternateSelection = true;
  
          VerifyGetSelectionRects_BoxMode();
  
          // now try the other 3 configurations of box region.
          // #2 top-right to bottom-left selection
          m_pSelection->_coordSelectionAnchor.X = m_pSelection->_srSelectionRect.Right;
          m_pSelection->_coordSelectionAnchor.Y = m_pSelection->_srSelectionRect.Top;
  
          VerifyGetSelectionRects_BoxMode();
  
          // #3 bottom-left to top-right selection
          m_pSelection->_coordSelectionAnchor.X = m_pSelection->_srSelectionRect.Left;
          m_pSelection->_coordSelectionAnchor.Y = m_pSelection->_srSelectionRect.Bottom;
  
          VerifyGetSelectionRects_BoxMode();
  
          // #4 bottom-right to top-left selection
          m_pSelection->_coordSelectionAnchor.X = m_pSelection->_srSelectionRect.Right;
          m_pSelection->_coordSelectionAnchor.Y = m_pSelection->_srSelectionRect.Bottom;
  
          VerifyGetSelectionRects_BoxMode();
      }
  
      void VerifyGetSelectionRects_LineMode()
      {
          const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
  
          const auto selectionRects = m_pSelection->GetSelectionRects();
          const UINT cRectanglesExpected = m_pSelection->_srSelectionRect.Bottom - m_pSelection->_srSelectionRect.Top + 1;
  
          if (VERIFY_ARE_EQUAL(cRectanglesExpected, selectionRects.size()))
          {
              // RULES:
              // 1. If we're only selection one line, select the entire region between the two rectangles.
              //    Else if we're selecting multiple lines...
              // 2. Extend all lines except the last line to the right edge of the screen
              //    Extend all lines except the first line to the left edge of the screen
              // 3. If our anchor is in the top-right or bottom-left corner of the rectangle...
              //    The inside portion of our rectangle on the first and last lines is invalid.
              //    Remove from selection (but preserve the anchors themselves).
  
              // RULE #1: If 1 line, entire region selected.
              bool fHaveOneLine = selectionRects.size() == 1;
  
              if (fHaveOneLine)
              {
                  SMALL_RECT srSelectionRect = m_pSelection->_srSelectionRect;
                  VERIFY_ARE_EQUAL(srSelectionRect.Top, srSelectionRect.Bottom);
  
                  const SMALL_RECT* const psrRect = &selectionRects[0];
  
                  VERIFY_ARE_EQUAL(psrRect->Top, srSelectionRect.Top);
                  VERIFY_ARE_EQUAL(psrRect->Bottom, srSelectionRect.Bottom);
  
                  VERIFY_ARE_EQUAL(psrRect->Left, srSelectionRect.Left);
                  VERIFY_ARE_EQUAL(psrRect->Right, srSelectionRect.Right);
              }
              else
              {
                  // RULE #2 : Check extension to edges
                  for (UINT iRect = 0; iRect < selectionRects.size(); iRect++)
                  {
                      // ensure each rectangle is exactly the width requested (block selection)
                      const SMALL_RECT* const psrRect = &selectionRects[iRect];
  
                      const short sRectangleLineNumber = (short)iRect + m_pSelection->_srSelectionRect.Top;
  
                      VERIFY_ARE_EQUAL(psrRect->Top, sRectangleLineNumber);
                      VERIFY_ARE_EQUAL(psrRect->Bottom, sRectangleLineNumber);
  
                      bool fIsFirstLine = iRect == 0;
                      bool fIsLastLine = iRect == selectionRects.size() - 1;
  
                      // for all lines except the last, the line should reach the right edge of the buffer
                      if (!fIsLastLine)
                      {
                          // buffer size = 80, then selection goes 0 to 79. Thus X - 1.
                          VERIFY_ARE_EQUAL(psrRect->Right, gci.GetActiveOutputBuffer().GetTextBuffer().GetSize().RightInclusive());
                      }
  
                      // for all lines except the first, the line should reach the left edge of the buffer
                      if (!fIsFirstLine)
                      {
                          VERIFY_ARE_EQUAL(psrRect->Left, 0);
                      }
                  }
  
                  // RULE #3: Check first and last line have invalid regions removed, if applicable
                  UINT iFirst = 0;
                  UINT iLast = gsl::narrow<UINT>(selectionRects.size() - 1u);
  
                  const SMALL_RECT* const psrFirst = &selectionRects[iFirst];
                  const SMALL_RECT* const psrLast = &selectionRects[iLast];
  
                  bool fRemoveRegion = false;
  
                  SMALL_RECT srSelectionRect = m_pSelection->_srSelectionRect;
                  COORD coordAnchor = m_pSelection->_coordSelectionAnchor;
  
                  // if the anchor is in the top right or bottom left corner, we must have removed a region. otherwise, it stays as is.
                  if (coordAnchor.Y == srSelectionRect.Top && coordAnchor.X == srSelectionRect.Right)
                  {
                      fRemoveRegion = true;
                  }
                  else if (coordAnchor.Y == srSelectionRect.Bottom && coordAnchor.X == srSelectionRect.Left)
                  {
                      fRemoveRegion = true;
                  }
  
                  // now check the first row's left based on removal
                  if (!fRemoveRegion)
                  {
                      VERIFY_ARE_EQUAL(psrFirst->Left, srSelectionRect.Left);
                  }
                  else
                  {
                      VERIFY_ARE_EQUAL(psrFirst->Left, srSelectionRect.Right);
                  }
  
                  // and the last row's right based on removal
                  if (!fRemoveRegion)
                  {
                      VERIFY_ARE_EQUAL(psrLast->Right, srSelectionRect.Right);
                  }
                  else
                  {
                      VERIFY_ARE_EQUAL(psrLast->Right, srSelectionRect.Left);
                  }
              }
          }
      }
  
      TEST_METHOD(TestGetSelectionRects_LineMode)
      {
          m_pSelection->_fSelectionVisible = true;
  
          // Part I: Multiple line selection
          // set selection region
          m_pSelection->_srSelectionRect.Top = 0;
          m_pSelection->_srSelectionRect.Bottom = 3;
          m_pSelection->_srSelectionRect.Left = 1;
          m_pSelection->_srSelectionRect.Right = 10;
  
          // #1 top-left to bottom right selection first
          m_pSelection->_coordSelectionAnchor.X = m_pSelection->_srSelectionRect.Left;
          m_pSelection->_coordSelectionAnchor.Y = m_pSelection->_srSelectionRect.Top;
  
          // A. true/false for the selection modes should mean line selection
          m_pSelection->_fLineSelection = true;
          m_pSelection->_fUseAlternateSelection = false;
  
          VerifyGetSelectionRects_LineMode();
  
          // B. false/true for the selection modes should also mean line selection
          m_pSelection->_fLineSelection = false;
          m_pSelection->_fUseAlternateSelection = true;
  
          VerifyGetSelectionRects_LineMode();
  
          // now try the other 3 configurations of box region.
          // #2 top-right to bottom-left selection
          m_pSelection->_coordSelectionAnchor.X = m_pSelection->_srSelectionRect.Right;
          m_pSelection->_coordSelectionAnchor.Y = m_pSelection->_srSelectionRect.Top;
  
          VerifyGetSelectionRects_LineMode();
  
          // #3 bottom-left to top-right selection
          m_pSelection->_coordSelectionAnchor.X = m_pSelection->_srSelectionRect.Left;
          m_pSelection->_coordSelectionAnchor.Y = m_pSelection->_srSelectionRect.Bottom;
  
          VerifyGetSelectionRects_LineMode();
  
          // #4 bottom-right to top-left selection
          m_pSelection->_coordSelectionAnchor.X = m_pSelection->_srSelectionRect.Right;
          m_pSelection->_coordSelectionAnchor.Y = m_pSelection->_srSelectionRect.Bottom;
  
          VerifyGetSelectionRects_LineMode();
  
          // Part II: Single line selection
          m_pSelection->_srSelectionRect.Top = 2;
          m_pSelection->_srSelectionRect.Bottom = 2;
          m_pSelection->_srSelectionRect.Left = 1;
          m_pSelection->_srSelectionRect.Right = 10;
  
          // #1: left to right selection
          m_pSelection->_coordSelectionAnchor.X = m_pSelection->_srSelectionRect.Left;
          VERIFY_IS_TRUE(m_pSelection->_srSelectionRect.Bottom == m_pSelection->_srSelectionRect.Top);
          m_pSelection->_coordSelectionAnchor.Y = m_pSelection->_srSelectionRect.Bottom;
  
          VerifyGetSelectionRects_LineMode();
  
          // #2: right to left selection
          m_pSelection->_coordSelectionAnchor.X = m_pSelection->_srSelectionRect.Right;
          VERIFY_IS_TRUE(m_pSelection->_srSelectionRect.Bottom == m_pSelection->_srSelectionRect.Top);
          m_pSelection->_coordSelectionAnchor.Y = m_pSelection->_srSelectionRect.Top;
  
          VerifyGetSelectionRects_LineMode();
      }
  
      void TestBisectSelectionDelta(SHORT sTargetX, SHORT sTargetY, SHORT sLength, SHORT sDeltaLeft, SHORT sDeltaRight)
      {
          const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
          const SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer();
  
          short sStringLength;
          COORD coordTargetPoint;
          SMALL_RECT srSelection;
          SMALL_RECT srOriginal;
  
          sStringLength = sLength;
          coordTargetPoint.X = sTargetX;
          coordTargetPoint.Y = sTargetY;
  
          // selection area is always one row at a time so top/bottom = Y = row position
          srSelection.Top = srSelection.Bottom = coordTargetPoint.Y;
  
          // selection rectangle starts from the target and goes for the length requested
          srSelection.Left = coordTargetPoint.X;
          srSelection.Right = coordTargetPoint.X + sStringLength;
  
          // save original for comparison
          srOriginal.Top = srSelection.Top;
          srOriginal.Bottom = srSelection.Bottom;
          srOriginal.Left = srSelection.Left;
          srOriginal.Right = srSelection.Right;
  
          COORD startPos{ sTargetX, sTargetY };
          COORD endPos{ base::ClampAdd(sTargetX, sLength), sTargetY };
          const auto selectionRects = screenInfo.GetTextBuffer().GetTextRects(startPos, endPos, false, false);
  
          VERIFY_ARE_EQUAL(static_cast<size_t>(1), selectionRects.size());
          srSelection = selectionRects.at(0);
  
          VERIFY_ARE_EQUAL(srOriginal.Top, srSelection.Top);
          VERIFY_ARE_EQUAL(srOriginal.Bottom, srSelection.Bottom);
          VERIFY_ARE_EQUAL(srOriginal.Left + sDeltaLeft, srSelection.Left);
          VERIFY_ARE_EQUAL(srOriginal.Right + sDeltaRight, srSelection.Right);
      }
  
      TEST_METHOD(TestBisectSelection)
      {
          m_state->FillTextBufferBisect();
  
          // From CommonState, this is what rows look like:
          // positions of き are at 0, 27-28, 39-40, 67-68, 79
          // きABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789ききABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789き
          // きABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789ききABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789き
          // きABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789ききABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789き
          // きABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789ききABCDEFGHIJKLMNOPQRSTUVWXYZきき0123456789き
  
          // 1a. Start position is trailing half and is at beginning of row
  
          // start from position Column 0, Row 2
          // selection is 5 characters long
          // the left edge should move one to the right (+1) to not select the trailing byte
          // right edge shouldn't move
          TestBisectSelectionDelta(0, 2, 5, 1, 0);
  
          // 1b. Start position is trailing half and is elsewhere in the row
  
          // start from position Column 28, Row 2, which is the position of a trailing き in the mid row
          // selection is 5 characters long
          // the left edge should move one to the left (-1) to select the leading byte also
          // right edge shouldn't move
          TestBisectSelectionDelta(28, 2, 5, -1, 0);
  
          // 1c. Start position is trailing half and is beginning of buffer
  
          // start from position Column 0, Row 0 which is a trailing byte
          // selection is 5 characters long
          // the left edge should move one to the right (+1) to not select the trailing byte
          // right edge shouldn't move
          TestBisectSelectionDelta(0, 0, 5, 1, 0);
  
          // 2a. End position is leading half and is at end of row
  
          // start from position 10 before end of row (80 length row)
          // row is 2
          // selection is 9 characters long
          // the left edge shouldn't move
          // the right edge should move one to the left (-1) to not select the leading byte
          TestBisectSelectionDelta(70, 2, 9, 0, -1);
  
          // 2b. End position is leading half and is elsewhere in the row
  
          // start from 10 before trailing き in the mid row (pos 68 - 10 = 58)
          // row is 2
          // selection is 10 characters long
          // the left edge shouldn't move
          // the right edge should not move, because it is already on the trailing byte
          TestBisectSelectionDelta(58, 2, 10, 0, 0);
  
          // 2c. End position is leading half and is at end of buffer
          // start from position 10 before end of row (80 length row)
          // row is 300 (or 299 for the index)
          // selection is 9 characters long
          // the left edge shouldn't move
          // the right edge should move one to the left (-1) to not select the leading byte
          TestBisectSelectionDelta(70, 299, 9, 0, -1);
      }
  };
  
  class SelectionInputTests
  {
      TEST_CLASS(SelectionInputTests);
  
      CommonState* m_state;
      CommandHistory* m_pHistory;
  
      TEST_CLASS_SETUP(ClassSetup)
      {
          m_state = new CommonState();
  
          m_state->PrepareGlobalFont();
          m_state->PrepareGlobalScreenBuffer();
          m_state->PrepareGlobalInputBuffer();
          m_pHistory = CommandHistory::s_Allocate(L"cmd.exe", nullptr);
          if (!m_pHistory)
          {
              return false;
          }
          // History must be prepared before COOKED_READ (as it uses s_Find to get at it)
  
          return true;
      }
  
      TEST_CLASS_CLEANUP(ClassCleanup)
      {
          CommandHistory::s_Free(nullptr);
          m_pHistory = nullptr;
          m_state->CleanupGlobalScreenBuffer();
          m_state->CleanupGlobalFont();
          m_state->CleanupGlobalInputBuffer();
  
          delete m_state;
  
          return true;
      }
  
      TEST_METHOD(TestGetInputLineBoundaries)
      {
          CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
          // 80x80 box
          const SHORT sRowWidth = 80;
  
          SMALL_RECT srectEdges;
          srectEdges.Left = srectEdges.Top = 0;
          srectEdges.Right = srectEdges.Bottom = sRowWidth - 1;
  
          // false when no cooked read data exists
          VERIFY_IS_FALSE(gci.HasPendingCookedRead());
  
          bool fResult = Selection::s_GetInputLineBoundaries(nullptr, nullptr);
          VERIFY_IS_FALSE(fResult);
  
          // prepare some read data
          m_state->PrepareReadHandle();
          auto cleanupReadHandle = wil::scope_exit([&]() { m_state->CleanupReadHandle(); });
  
          m_state->PrepareCookedReadData();
          // set up to clean up read data later
          auto cleanupCookedRead = wil::scope_exit([&]() { m_state->CleanupCookedReadData(); });
  
          COOKED_READ_DATA& readData = gci.CookedReadData();
  
          // backup text info position over remainder of text execution duration
          TextBuffer& textBuffer = gci.GetActiveOutputBuffer().GetTextBuffer();
          COORD coordOldTextInfoPos;
          coordOldTextInfoPos.X = textBuffer.GetCursor().GetPosition().X;
          coordOldTextInfoPos.Y = textBuffer.GetCursor().GetPosition().Y;
  
          // set various cursor positions
          readData.OriginalCursorPosition().X = 15;
          readData.OriginalCursorPosition().Y = 3;
  
          readData.VisibleCharCount() = 200;
  
          textBuffer.GetCursor().SetXPosition(35);
          textBuffer.GetCursor().SetYPosition(35);
  
          // try getting boundaries with no pointers. parameters should be fully optional.
          fResult = Selection::s_GetInputLineBoundaries(nullptr, nullptr);
          VERIFY_IS_TRUE(fResult);
  
          // now let's get some actual data
          COORD coordStart;
          COORD coordEnd;
  
          fResult = Selection::s_GetInputLineBoundaries(&coordStart, &coordEnd);
          VERIFY_IS_TRUE(fResult);
  
          // starting position/boundary should always be where the input line started
          VERIFY_ARE_EQUAL(coordStart.X, readData.OriginalCursorPosition().X);
          VERIFY_ARE_EQUAL(coordStart.Y, readData.OriginalCursorPosition().Y);
  
          // ending position can vary. it's in one of two spots
          // 1. If the original cooked cursor was valid (which it was this first time), it's NumberOfVisibleChars ahead.
          COORD coordFinalPos;
  
          const short cCharsToAdjust = ((short)readData.VisibleCharCount() - 1); // then -1 to be on the last piece of text, not past it
  
          coordFinalPos.X = (readData.OriginalCursorPosition().X + cCharsToAdjust) % sRowWidth;
          coordFinalPos.Y = readData.OriginalCursorPosition().Y + ((readData.OriginalCursorPosition().X + cCharsToAdjust) / sRowWidth);
  
          VERIFY_ARE_EQUAL(coordEnd.X, coordFinalPos.X);
          VERIFY_ARE_EQUAL(coordEnd.Y, coordFinalPos.Y);
  
          // 2. if the original cooked cursor is invalid, then it's the text info cursor position
          readData.OriginalCursorPosition().X = -1;
          readData.OriginalCursorPosition().Y = -1;
  
          fResult = Selection::s_GetInputLineBoundaries(nullptr, &coordEnd);
          VERIFY_IS_TRUE(fResult);
  
          VERIFY_ARE_EQUAL(coordEnd.X, textBuffer.GetCursor().GetPosition().X - 1); // -1 to be on the last piece of text, not past it
          VERIFY_ARE_EQUAL(coordEnd.Y, textBuffer.GetCursor().GetPosition().Y);
  
          // restore text buffer info position
          textBuffer.GetCursor().SetXPosition(coordOldTextInfoPos.X);
          textBuffer.GetCursor().SetYPosition(coordOldTextInfoPos.Y);
      }
  
      TEST_METHOD(TestWordByWordPrevious)
      {
          BEGIN_TEST_METHOD_PROPERTIES()
              TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
          END_TEST_METHOD_PROPERTIES()
  
          CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
          SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer();
  
          const std::wstring text(L"this is some test text.");
          screenInfo.Write(OutputCellIterator(text));
  
          // Get the left and right side of the text we inserted (right is one past the end)
          const COORD left = { 0, 0 };
          const COORD right = { gsl::narrow<SHORT>(text.length()), 0 };
  
          // Get the selection instance and buffer size
          auto& sel = Selection::Instance();
          const auto bufferSize = screenInfo.GetBufferSize();
  
          // The anchor is where the selection started from.
          const auto anchor = right;
  
          // The point is the "other end" of the anchor forming the rectangle of what is covered.
          // It starts at the same spot as the anchor to represent the initial 1x1 selection.
          auto point = anchor;
  
          // Walk through the sequence in reverse extending the sequence by one word each time to the left.
          // The anchor is always the end of the line and the selection just gets bigger.
          do
          {
              // We expect the result to be left of where we started.
              // It will point at the character just right of the space (or the beginning of the line).
              COORD resultExpected = point;
  
              do
              {
                  resultExpected.X--;
              } while (resultExpected.X > 0 && text.at(resultExpected.X - 1) != UNICODE_SPACE);
  
              point = sel.WordByWordSelection(true, bufferSize, anchor, point);
  
              VERIFY_ARE_EQUAL(resultExpected, point);
  
          } while (point.X > left.X);
      }
  
      TEST_METHOD(TestWordByWordNext)
      {
          BEGIN_TEST_METHOD_PROPERTIES()
              TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
          END_TEST_METHOD_PROPERTIES()
  
          CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
          SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer();
  
          const std::wstring text(L"this is some test text.");
          screenInfo.Write(OutputCellIterator(text));
  
          // Get the left and right side of the text we inserted (right is one past the end)
          const COORD left = { 0, 0 };
          const COORD right = { gsl::narrow<SHORT>(text.length()), 0 };
  
          // Get the selection instance and buffer size
          auto& sel = Selection::Instance();
          const auto bufferSize = screenInfo.GetBufferSize();
  
          // The anchor is where the selection started from.
          const auto anchor = left;
  
          // The point is the "other end" of the anchor forming the rectangle of what is covered.
          // It starts at the same spot as the anchor to represent the initial 1x1 selection.
          auto point = anchor;
  
          // Walk through the sequence forward extending the sequence by one word each time to the right.
          // The anchor is always the end of the line and the selection just gets bigger.
          do
          {
              // We expect the result to be right of where we started.
  
              COORD resultExpected = point;
  
              do
              {
                  resultExpected.X++;
              } while (resultExpected.X + 1 < right.X && text.at(resultExpected.X + 1) != UNICODE_SPACE);
              resultExpected.X++;
  
              // when we reach the end, word by word selection will seek forward to the end of the buffer, so update
              // the expected to the end in that circumstance
              if (resultExpected.X >= right.X)
              {
                  resultExpected.X = bufferSize.RightInclusive();
                  resultExpected.Y = bufferSize.BottomInclusive();
              }
  
              point = sel.WordByWordSelection(false, bufferSize, anchor, point);
  
              VERIFY_ARE_EQUAL(resultExpected, point);
  
          } while (point.Y < bufferSize.BottomInclusive()); // stop once we've advanced to a point on the bottom row of the buffer.
      }
  };
  