#include "config.h"
#include "core/html/HTMLTextFormControlElement.h"
+#include "core/dom/Position.h"
+#include "core/dom/Text.h"
+#include "core/editing/FrameSelection.h"
+#include "core/editing/SpellChecker.h"
+#include "core/editing/VisibleSelection.h"
+#include "core/editing/VisibleUnits.h"
#include "core/frame/FrameView.h"
+#include "core/html/HTMLBRElement.h"
#include "core/html/HTMLDocument.h"
+#include "core/html/HTMLInputElement.h"
+#include "core/html/HTMLTextAreaElement.h"
+#include "core/loader/EmptyClients.h"
+#include "core/page/SpellCheckerClient.h"
+#include "core/rendering/RenderTreeAsText.h"
#include "core/testing/DummyPageHolder.h"
+#include "core/testing/UnitTestHelpers.h"
#include "wtf/OwnPtr.h"
#include <gtest/gtest.h>
-using namespace WebCore;
+using namespace blink;
namespace {
protected:
virtual void SetUp() OVERRIDE;
+ DummyPageHolder& page() const { return *m_dummyPageHolder; }
+ HTMLDocument& document() const { return *m_document; }
HTMLTextFormControlElement& textControl() const { return *m_textControl; }
+ HTMLInputElement& input() const { return *m_input; }
+
+ int layoutCount() const { return page().frameView().layoutCount(); }
+ void forceLayoutFlag();
private:
+ OwnPtr<SpellCheckerClient> m_spellCheckerClient;
OwnPtr<DummyPageHolder> m_dummyPageHolder;
+ RefPtrWillBePersistent<HTMLDocument> m_document;
RefPtrWillBePersistent<HTMLTextFormControlElement> m_textControl;
+ RefPtrWillBePersistent<HTMLInputElement> m_input;
+};
+
+class DummyTextCheckerClient : public EmptyTextCheckerClient {
+public:
+ ~DummyTextCheckerClient() { }
+
+ virtual bool shouldEraseMarkersAfterChangeSelection(TextCheckingType) const OVERRIDE { return false; }
+};
+
+class DummySpellCheckerClient : public EmptySpellCheckerClient {
+public:
+ virtual ~DummySpellCheckerClient() { }
+
+ virtual bool isContinuousSpellCheckingEnabled() OVERRIDE { return true; }
+ virtual bool isGrammarCheckingEnabled() OVERRIDE { return true; }
+
+ virtual TextCheckerClient& textChecker() OVERRIDE { return m_dummyTextCheckerClient; }
+
+private:
+ DummyTextCheckerClient m_dummyTextCheckerClient;
};
void HTMLTextFormControlElementTest::SetUp()
{
- m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600));
- HTMLDocument& document = toHTMLDocument(m_dummyPageHolder->document());
- document.documentElement()->setInnerHTML("<body><textarea id=textarea></textarea></body>", ASSERT_NO_EXCEPTION);
- document.view()->updateLayoutAndStyleIfNeededRecursive();
- m_textControl = toHTMLTextFormControlElement(document.getElementById("textarea"));
+ Page::PageClients pageClients;
+ fillWithEmptyClients(pageClients);
+ m_spellCheckerClient = adoptPtr(new DummySpellCheckerClient);
+ pageClients.spellCheckerClient = m_spellCheckerClient.get();
+ m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600), &pageClients);
+
+ m_document = toHTMLDocument(&m_dummyPageHolder->document());
+ m_document->documentElement()->setInnerHTML("<body><textarea id=textarea></textarea><input id=input /></body>", ASSERT_NO_EXCEPTION);
+ m_document->view()->updateLayoutAndStyleIfNeededRecursive();
+ m_textControl = toHTMLTextFormControlElement(m_document->getElementById("textarea"));
m_textControl->focus();
+ m_input = toHTMLInputElement(m_document->getElementById("input"));
+}
+
+void HTMLTextFormControlElementTest::forceLayoutFlag()
+{
+ FrameView& frameView = page().frameView();
+ IntRect frameRect = frameView.frameRect();
+ frameRect.setWidth(frameRect.width() + 1);
+ frameRect.setHeight(frameRect.height() + 1);
+ page().frameView().setFrameRect(frameRect);
}
TEST_F(HTMLTextFormControlElementTest, SetSelectionRange)
EXPECT_EQ(3, textControl().selectionEnd());
}
+TEST_F(HTMLTextFormControlElementTest, SetSelectionRangeDoesNotCauseLayout)
+{
+ input().focus();
+ input().setValue("Hello, input form.");
+ input().setSelectionRange(1, 1);
+ FrameSelection& frameSelection = document().frame()->selection();
+ forceLayoutFlag();
+ LayoutRect oldCaretRect = frameSelection.absoluteCaretBounds();
+ EXPECT_FALSE(oldCaretRect.isEmpty());
+ int startLayoutCount = layoutCount();
+ input().setSelectionRange(1, 1);
+ EXPECT_EQ(startLayoutCount, layoutCount());
+ LayoutRect newCaretRect = frameSelection.absoluteCaretBounds();
+ EXPECT_EQ(oldCaretRect, newCaretRect);
+
+ forceLayoutFlag();
+ oldCaretRect = frameSelection.absoluteCaretBounds();
+ EXPECT_FALSE(oldCaretRect.isEmpty());
+ startLayoutCount = layoutCount();
+ input().setSelectionRange(2, 2);
+ EXPECT_EQ(startLayoutCount, layoutCount());
+ newCaretRect = frameSelection.absoluteCaretBounds();
+ EXPECT_NE(oldCaretRect, newCaretRect);
+}
+
+typedef Position (*PositionFunction)(const Position&);
+typedef VisiblePosition(*VisblePositionFunction)(const VisiblePosition&);
+
+void testFunctionEquivalence(const Position& position, PositionFunction positionFunction, VisblePositionFunction visibleFunction)
+{
+ VisiblePosition visiblePosition(position);
+ VisiblePosition expected = visibleFunction(visiblePosition);
+ VisiblePosition actual = VisiblePosition(positionFunction(position));
+ EXPECT_EQ(expected, actual);
+}
+
+static VisiblePosition startOfWord(const VisiblePosition& position)
+{
+ return startOfWord(position, LeftWordIfOnBoundary);
+}
+
+static VisiblePosition endOfWord(const VisiblePosition& position)
+{
+ return endOfWord(position, RightWordIfOnBoundary);
+}
+
+void testBoundary(HTMLDocument& document, HTMLTextFormControlElement& textControl)
+{
+ for (unsigned i = 0; i < textControl.innerEditorValue().length(); i++) {
+ textControl.setSelectionRange(i, i);
+ Position position = document.frame()->selection().start();
+ SCOPED_TRACE(::testing::Message() << "offset " << position.deprecatedEditingOffset() << " of " << nodePositionAsStringForTesting(position.deprecatedNode()).ascii().data());
+ {
+ SCOPED_TRACE("HTMLTextFormControlElement::startOfWord");
+ testFunctionEquivalence(position, HTMLTextFormControlElement::startOfWord, startOfWord);
+ }
+ {
+ SCOPED_TRACE("HTMLTextFormControlElement::endOfWord");
+ testFunctionEquivalence(position, HTMLTextFormControlElement::endOfWord, endOfWord);
+ }
+ {
+ SCOPED_TRACE("HTMLTextFormControlElement::startOfSentence");
+ testFunctionEquivalence(position, HTMLTextFormControlElement::startOfSentence, startOfSentence);
+ }
+ {
+ SCOPED_TRACE("HTMLTextFormControlElement::endOfSentence");
+ testFunctionEquivalence(position, HTMLTextFormControlElement::endOfSentence, endOfSentence);
+ }
+ }
+}
+
+TEST_F(HTMLTextFormControlElementTest, WordAndSentenceBoundary)
+{
+ HTMLElement* innerText = textControl().innerEditorElement();
+ {
+ SCOPED_TRACE("String is value.");
+ innerText->removeChildren();
+ innerText->setNodeValue("Hel\nlo, text form.\n");
+ testBoundary(document(), textControl());
+ }
+ {
+ SCOPED_TRACE("A Text node and a BR element");
+ innerText->removeChildren();
+ innerText->setNodeValue("");
+ innerText->appendChild(Text::create(document(), "Hello, text form."));
+ innerText->appendChild(HTMLBRElement::create(document()));
+ testBoundary(document(), textControl());
+ }
+ {
+ SCOPED_TRACE("Text nodes.");
+ innerText->removeChildren();
+ innerText->setNodeValue("");
+ innerText->appendChild(Text::create(document(), "Hel\nlo, te"));
+ innerText->appendChild(Text::create(document(), "xt form."));
+ testBoundary(document(), textControl());
+ }
+}
+
+TEST_F(HTMLTextFormControlElementTest, SpellCheckDoesNotCauseUpdateLayout)
+{
+ HTMLInputElement* input = toHTMLInputElement(document().getElementById("input"));
+ input->focus();
+ input->setValue("Hello, input field");
+ VisibleSelection oldSelection = document().frame()->selection().selection();
+
+ Position newPosition(input->innerEditorElement()->firstChild(), 3, Position::PositionIsOffsetInAnchor);
+ VisibleSelection newSelection(newPosition, DOWNSTREAM);
+ document().frame()->selection().setSelection(newSelection, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle | FrameSelection::DoNotUpdateAppearance);
+ ASSERT_EQ(3, input->selectionStart());
+
+ OwnPtr<SpellChecker> spellChecker(SpellChecker::create(page().frame()));
+ forceLayoutFlag();
+ int startCount = layoutCount();
+ spellChecker->respondToChangedSelection(oldSelection, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle);
+ EXPECT_EQ(startCount, layoutCount());
+}
+
}