From a897a3b00a2010ce291c3060d2a77e3668fc1621 Mon Sep 17 00:00:00 2001
From: Lukas Holecek <hluk@email.cz>
Date: Fri, 10 Aug 2012 12:39:29 +0200
Subject: [PATCH] fakevim: Added tests

Change-Id: I6b6e54817bf41402f5575142b1adb9362c717439
Reviewed-by: hjk <qthjk@ovi.com>
---
 src/plugins/fakevim/fakevim.pro       |   4 +
 src/plugins/fakevim/fakevim_test.cpp  | 646 ++++++++++++++++++++++++++
 src/plugins/fakevim/fakevimplugin.cpp |   7 +-
 src/plugins/fakevim/fakevimplugin.h   |  17 +
 4 files changed, 673 insertions(+), 1 deletion(-)
 create mode 100644 src/plugins/fakevim/fakevim_test.cpp

diff --git a/src/plugins/fakevim/fakevim.pro b/src/plugins/fakevim/fakevim.pro
index 442e7a7206..44e3c48c20 100644
--- a/src/plugins/fakevim/fakevim.pro
+++ b/src/plugins/fakevim/fakevim.pro
@@ -16,3 +16,7 @@ HEADERS += fakevimactions.h \
     fakevimhandler.h \
     fakevimplugin.h
 FORMS += fakevimoptions.ui
+
+equals(TEST, 1) {
+    SOURCES += fakevim_test.cpp
+}
diff --git a/src/plugins/fakevim/fakevim_test.cpp b/src/plugins/fakevim/fakevim_test.cpp
new file mode 100644
index 0000000000..11966ebe9e
--- /dev/null
+++ b/src/plugins/fakevim/fakevim_test.cpp
@@ -0,0 +1,646 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2012 Lukas Holecek <hluk@email.cz>
+**
+** Contact: http://www.qt-project.org/
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**************************************************************************/
+
+/*!
+ * Tests for FakeVim plugin.
+ * All test are based on Vim behaviour.
+ */
+
+#include "fakevimplugin.h"
+#include "fakevimhandler.h"
+
+#include <QtTest>
+#include <QTextEdit>
+#include <QTextDocument>
+
+/*!
+ * Tests after this macro will be skipped and warning printed.
+ * Uncomment it to test a feature -- if tests succeeds it should be removed from the test.
+ */
+#define NOT_IMPLEMENTED QSKIP("Not fully implemented!", SkipSingle);
+
+// text cursor representation in comparisons (set empty to disable cursor position checking)
+#define X "|"
+static const QString cursorString(X);
+
+// a more distinct line separator in code
+#define N "\n"
+
+// document line start and end string in error text
+#define LINE_START "\t\t<"
+#define LINE_END ">\n"
+
+// Compare document contents with a expectedText.
+// Also check cursor position if the expectedText contains cursorString.
+#define COMPARE(beforeText, beforePosition, afterText, afterPosition, expectedText, cmd) \
+    do { \
+        QString before(beforeText); \
+        QString actual(afterText); \
+        QString expected(expectedText); \
+        if (!cursorString.isEmpty() && expected.contains(cursorString)) {\
+            before.insert(beforePosition, cursorString); \
+            actual.insert(afterPosition, cursorString); \
+        } \
+        QString help = "\n\tBefore command [" + QString(cmd) + "]:\n" LINE_START \
+            + (before.replace('\n', LINE_END LINE_START)) \
+            + LINE_END "\n\tAfter the command:\n" LINE_START \
+            + actual.replace('\n', LINE_END LINE_START) \
+            + LINE_END "\n\tShould be:\n" LINE_START \
+            + expected.replace('\n', LINE_END LINE_START) + LINE_END; \
+        QVERIFY2(actual == expected, help.toLatin1().constData()); \
+    } while (false)
+
+// Send keys and check if the expected result is same as document contents.
+// Escape is always prepended to keys so that previous command is cancled.
+#define KEYS(keys, expected) \
+    do { \
+        QString beforeText(data.text()); \
+        int beforePosition = data.position(); \
+        data.doKeys("<ESC>" keys); \
+        COMPARE(beforeText, beforePosition, data.text(), data.position(), (expected), (keys)); \
+    } while (false);
+
+// Run Ex command and check if the expected result is same as document contents.
+#define COMMAND(cmd, expected) \
+    do { \
+        QString beforeText(data.text()); \
+        int beforePosition = data.position(); \
+        data.doCommand(cmd); \
+        COMPARE(beforeText, beforePosition, data.text(), data.position(), (expected), (":" cmd)); \
+    } while (false);
+
+using namespace FakeVim::Internal;
+
+namespace {
+
+struct TestData
+{
+    FakeVimHandler *handler;
+    QTextEdit *edit;
+    QWidget parent;
+
+    QTextCursor cursor() const { return edit->textCursor(); }
+
+    int position() const
+    {
+        int pos = cursor().position();
+        // text cursor position is never behind last character in document
+        return qMax(0, qMin(pos, edit->document()->characterCount() - 2));
+    }
+
+    QString text() const { return edit->toPlainText(); }
+
+    void setText(const QString &text)
+    {
+        QTextCursor tc = cursor();
+        QString str = text;
+        int i = str.indexOf(cursorString);
+        if (!cursorString.isEmpty() && i != -1)
+            str.remove(i, 1);
+        edit->document()->setPlainText(str);
+        tc.setPosition(qMax(0, i));
+        edit->setTextCursor(tc);
+    }
+
+    void doCommand(const QString &cmd) { handler->handleCommand(cmd); }
+    void doKeys(const QString &keys) { handler->handleInput(keys); }
+};
+
+static void setup(TestData *data)
+{
+    data->edit = new QTextEdit(&data->parent);
+    data->handler = new FakeVimHandler(data->edit, &data->parent);
+    data->handler->handleCommand("set startofline");
+}
+
+} // namespace
+
+void FakeVimPlugin::test_vim_movement()
+{
+    TestData data;
+    setup(&data);
+
+    // vertical movement
+    data.setText("123" N   "456" N   "789" N   "abc");
+    KEYS("",   X "123" N   "456" N   "789" N   "abc");
+    KEYS("j",    "123" N X "456" N   "789" N   "abc");
+    KEYS("G",    "123" N   "456" N   "789" N X "abc");
+    KEYS("k",    "123" N   "456" N X "789" N   "abc");
+    KEYS("2k", X "123" N   "456" N   "789" N   "abc");
+    KEYS("k",  X "123" N   "456" N   "789" N   "abc");
+    KEYS("jj",   "123" N   "456" N X "789" N   "abc");
+    KEYS("gg", X "123" N   "456" N   "789" N   "abc");
+
+    // horizontal movement
+    data.setText(" " X "x"   "x"   "x"   "x");
+    KEYS("",     " " X "x"   "x"   "x"   "x");
+    KEYS("h",  X " "   "x"   "x"   "x"   "x");
+    KEYS("l",    " " X "x"   "x"   "x"   "x");
+    KEYS("3l",   " "   "x"   "x"   "x" X "x");
+    KEYS("2h",   " "   "x" X "x"   "x"   "x");
+    KEYS("$",    " "   "x"   "x"   "x" X "x");
+    KEYS("^",    " " X "x"   "x"   "x"   "x");
+    KEYS("0",  X " "   "x"   "x"   "x"   "x");
+
+    // skip words
+    data.setText("123 "   "456"   "."   "789 "   "abc");
+    KEYS("b",  X "123 "   "456"   "."   "789 "   "abc");
+    KEYS("w",    "123 " X "456"   "."   "789 "   "abc");
+    KEYS("2w",   "123 "   "456"   "." X "789 "   "abc");
+    KEYS("3w",   "123 "   "456"   "."   "789 "   "ab" X "c");
+    KEYS("3b",   "123 "   "456" X "."   "789 "   "abc");
+
+    data.setText("123 "   "456.789 "   "abc "   "def");
+    KEYS("B",  X "123 "   "456.789 "   "abc "   "def");
+    KEYS("W",    "123 " X "456.789 "   "abc "   "def");
+    KEYS("2W",   "123 "   "456.789 "   "abc " X "def");
+    KEYS("B",    "123 "   "456.789 " X "abc "   "def");
+    KEYS("2B", X "123 "   "456.789 "   "abc "   "def");
+    KEYS("4W",   "123 "   "456.789 "   "abc "   "de" X "f");
+
+    data.setText("123" N   "45."   "6" N   "" N " " N   "789");
+    KEYS("3w",   "123" N   "45." X "6" N   "" N " " N   "789");
+    // From Vim help (motion.txt): An empty line is also considered to be a word.
+    KEYS("w",    "123" N   "45."   "6" N X "" N " " N   "789");
+    KEYS("w",    "123" N   "45."   "6" N   "" N " " N X "789");
+
+    KEYS("b",    "123" N   "45."   "6" N X "" N " " N   "789");
+    KEYS("4b", X "123" N   "45."   "6" N   "" N " " N   "789");
+
+    KEYS("3e",    "123" N "45" X "."   "6" N "" N " " N "789");
+    KEYS("e",     "123" N "45"   "." X "6" N "" N " " N "789");
+    // Command "e" does not stop on empty lines ("ge" does).
+    KEYS("e",     "123" N "45"   "."   "6" N "" N " " N "78" X "9");
+    KEYS("ge",    "123" N "45"   "."   "6" N X "" N " " N "789");
+    KEYS("2ge",   "123" N "45" X "."   "6" N   "" N " " N "789");
+}
+
+void FakeVimPlugin::test_vim_fFtT()
+{
+    TestData data;
+    setup(&data);
+
+    data.setText("123()456" N "a(b(c)d)e");
+    KEYS("t(", "12" X "3()456" N "a(b(c)d)e");
+    KEYS("lt(", "123" X "()456" N "a(b(c)d)e");
+    KEYS("0j2t(", "123()456" N "a(" X "b(c)d)e");
+    KEYS("l2T(", "123()456" N "a(b" X "(c)d)e");
+    KEYS("l2T(", "123()456" N "a(" X "b(c)d)e");
+    KEYS("T(", "123()456" N "a(" X "b(c)d)e");
+
+    KEYS("ggf(", "123" X "()456" N "a(b(c)d)e");
+    KEYS("lf(", "123(" X ")456" N "a(b(c)d)e");
+    KEYS("0j2f(", "123()456" N "a(b" X "(c)d)e");
+    KEYS("2F(", "123()456" N "a(b" X "(c)d)e");
+    KEYS("l2F(", "123()456" N "a" X "(b(c)d)e");
+    KEYS("F(", "123()456" N "a" X "(b(c)d)e");
+}
+
+void FakeVimPlugin::test_vim_delete()
+{
+    TestData data;
+    setup(&data);
+
+    data.setText("123" N "456");
+    KEYS("x",  "23" N "456");
+    KEYS("dd", "456");
+    KEYS("2x", "6");
+    KEYS("dd", "");
+
+    data.setText("void main()");
+    KEYS("dt(", "()");
+
+    data.setText("void main()");
+    KEYS("df(", ")");
+
+    data.setText("void main()");
+    KEYS("wD", "void ");
+    KEYS("ggd$", "");
+
+    data.setText("abc def ghi");
+    KEYS("2dw", X "ghi");
+    data.setText("abc def ghi");
+    KEYS("d2w", X "ghi");
+
+    data.setText("abc  " N "  def" N "  ghi" N "jkl");
+    KEYS("3dw", X "jkl");
+    data.setText("abc  " N "  def" N "  ghi" N "jkl");
+    KEYS("d3w", X "jkl");
+}
+
+void FakeVimPlugin::test_vim_delete_inner_word()
+{
+    TestData data;
+    setup(&data);
+
+    data.setText("abc def ghi");
+    KEYS("wlldiw", "abc " X " ghi");
+
+    data.setText("abc def ghi jkl");
+    KEYS("3diw", X  " ghi jkl");
+
+    data.setText("abc " X "  def");
+    KEYS("diw", "abc" X "def");
+    KEYS("diw", "");
+
+    data.setText("abc  " N "  def");
+    KEYS("3diw", X "def");
+
+    data.setText("abc  " N "  def" N "  ghi");
+    KEYS("4diw", "  " X "ghi");
+    data.setText("ab" X "c  " N "  def" N "  ghi");
+    KEYS("4diw", "  " X "ghi");
+    data.setText("a b" X "c  " N "  def" N "  ghi");
+    KEYS("4diw", "a" X " " N "  ghi");
+
+    data.setText("abc def" N "ghi");
+    KEYS("2diw", X "def" N "ghi");
+    data.setText("abc def" N "ghi");
+    KEYS("3diw", X "" N "ghi");
+
+    data.setText("x" N X "" N "" N "  ");
+    KEYS("diw", "x" N X "" N "" N "  ");
+    data.setText("x" N X "" N "" N "  ");
+    KEYS("2diw", "x" N " " X " ");
+    data.setText("x" N X "" N "" N "" N "" N "  ");
+    KEYS("3diw", "x" N " " X " ");
+    data.setText("x" N X "" N "" N "" N "" N "" N "  ");
+    KEYS("3diw", "x" N X "" N "  ");
+    data.setText("x" N X "" N "" N "" N "" N "" N "" N "  ");
+    KEYS("4diw", "x" N X "" N "  ");
+}
+
+void FakeVimPlugin::test_vim_delete_a_word()
+{
+    TestData data;
+    setup(&data);
+
+    data.setText("abc def ghi");
+    KEYS("wlldaw", "abc " X "ghi");
+
+    data.setText("abc def ghi jkl");
+    KEYS("wll2daw", "abc " X "jkl");
+
+    data.setText("abc" X " def ghi");
+    KEYS("daw", "abc" X " ghi");
+    KEYS("daw", "ab" X "c");
+    KEYS("daw", "");
+
+    data.setText(X " ghi jkl");
+    KEYS("daw", X " jkl");
+    KEYS("ldaw", X " ");
+
+    data.setText("abc def ghi jkl");
+    KEYS("3daw", X "jkl");
+
+    // remove trailing spaces
+    data.setText("abc  " N "  def" N "  ghi" N "jkl");
+    KEYS("3daw", X "jkl");
+
+    data.setText("abc  " N "  def" N "  ghi" N "jkl");
+    KEYS("3daw", X "jkl");
+
+    data.setText("abc def" N "ghi");
+    KEYS("2daw", X "" N "ghi");
+
+    data.setText("x" N X "" N "" N "  ");
+    KEYS("daw", "x" N " " X " ");
+    data.setText("x" N X "" N "" N "" N "" N "  ");
+    KEYS("2daw", "x" N " " X " ");
+    data.setText("x" N X "" N "" N "" N "" N "" N "  ");
+    KEYS("2daw", "x" N X "" N "  ");
+    data.setText("x" N X "" N "" N "" N "" N "" N "" N "  ");
+    KEYS("3daw", "x" N " " X " ");
+}
+
+void FakeVimPlugin::test_vim_change_a_word()
+{
+    TestData data;
+    setup(&data);
+
+    data.setText("abc " X "def ghi");
+    KEYS("caw#", "abc #" X "ghi");
+    data.setText("abc d" X "ef ghi");
+    KEYS("caw#", "abc #" X "ghi");
+    data.setText("abc de" X "f ghi");
+    KEYS("caw#", "abc #" X "ghi");
+
+    data.setText("abc de" X "f ghi jkl");
+    KEYS("2caw#", "abc #" X "jkl");
+
+    data.setText("abc" X " def ghi jkl");
+    KEYS("2caw#", "abc#" X " jkl");
+
+    data.setText("abc " X "  def ghi jkl");
+    KEYS("2caw#", "abc#" X " jkl");
+
+    data.setText(" abc  " N "  def" N "  ghi" N " jkl");
+    KEYS("3caw#", "#" X N " jkl");
+}
+
+void FakeVimPlugin::test_vim_block_selection()
+{
+    TestData data;
+    setup(&data);
+
+    data.setText("int main(int /* (unused) */, char *argv[]);");
+    KEYS("f(", "int main" X "(int /* (unused) */, char *argv[]);");
+    KEYS("da(", "int main" X ";")
+
+    data.setText("int main(int /* (unused) */, char *argv[]);");
+    KEYS("f(", "int main" X "(int /* (unused) */, char *argv[]);");
+    KEYS("di(", "int main(" X ");")
+
+    data.setText("int main(int /* (unused) */, char *argv[]);");
+    KEYS("2f)", "int main(int /* (unused) */, char *argv[]" X ");");
+    KEYS("da(", "int main" X ";")
+
+    data.setText("int main(int /* (unused) */, char *argv[]);");
+    KEYS("2f)", "int main(int /* (unused) */, char *argv[]" X ");");
+    KEYS("di(", "int main(" X ");")
+
+    data.setText("{ { { } } }");
+    KEYS("2f{l", "{ { {" X " } } }")
+    KEYS("da{", "{ { " X " } }")
+    KEYS("da{", "{ " X " }")
+
+    data.setText("{ { { } } }");
+    KEYS("2f{l", "{ { {" X " } } }")
+    KEYS("2da{", "{ " X " }")
+
+    data.setText("{" N " { " N " } " N "}");
+    KEYS("di{", "{" N "}")
+}
+
+void FakeVimPlugin::test_vim_repeat()
+{
+    NOT_IMPLEMENTED
+
+    TestData data;
+    setup(&data);
+
+    data.setText("test text");
+    KEYS("ciwWORD", "WOR" X "D text");
+    KEYS("w.", "WORD WOR" X "D");
+
+    /* QTCREATORBUG-7248 */
+    data.setText("test tex" X "t");
+    KEYS("vbcWORD", "test " "WOR" X "D");
+    KEYS("bb.", X "WORD WORD");
+}
+
+void FakeVimPlugin::test_vim_search()
+{
+    TestData data;
+    setup(&data);
+
+    data.setText("abc" N "def" N "ghi");
+    KEYS("/ghi<CR>", "abc" N "def" N X "ghi");
+    KEYS("gg/\\w\\{3}<CR>", X "abc" N "def" N "ghi");
+    KEYS("n", "abc" N X "def" N "ghi");
+    KEYS("N", X "abc" N "def" N "ghi");
+
+    NOT_IMPLEMENTED
+    KEYS("2n", "abc" N "def" N X "ghi");
+    KEYS("2N", X "abc" N "def" N "ghi");
+
+    /* QTCREATORBUG-7251 */
+    data.setText("abc abc abc abc");
+    KEYS("$?abc<CR>", "abc abc abc " X "abc");
+}
+
+void FakeVimPlugin::test_vim_indent()
+{
+    TestData data;
+    setup(&data);
+
+    data.doCommand("set expandtab");
+    data.doCommand("set shiftwidth=4");
+
+    data.setText(
+        "abc" N
+        "def" N
+        "ghi" N
+        "jkl" N
+        "mno");
+    KEYS("j3>>",
+        "abc" N
+        "    " X "def" N
+        "    ghi" N
+        "    jkl" N
+        "mno");
+    KEYS("j2>>",
+        "abc" N
+        "    def" N
+        "        " X "ghi" N
+        "        jkl" N
+        "mno");
+
+    NOT_IMPLEMENTED
+    KEYS("2<<",
+        "abc" N
+        "    " X "def" N
+        "    ghi" N
+        "    jkl" N
+        "mno");
+    KEYS("k3<<",
+        "abc" N
+        X "def" N
+        "ghi" N
+        "jkl" N
+        "mno");
+
+    data.setText(
+        "abc" N
+        "def" N
+        "ghi" N
+        "jkl" N
+        "mno");
+    KEYS("jj>j",
+        "abc" N
+        "def" N
+        "    " X "ghi" N
+        "    jkl" N
+        "mno");
+
+    data.setText("abc");
+    KEYS(">>", "    " X "abc");
+
+    data.setText("abc");
+    data.doCommand("set shiftwidth=2");
+    KEYS(">>", "  " X "abc");
+
+    /* possibly QTCREATORBUG-7376 */
+    data.setText("abc");
+    data.doCommand("set noexpandtab");
+    data.doCommand("set tabstop=2");
+    data.doCommand("set shiftwidth=5");
+    // shiftwidth = TABS * tabstop + SPACES
+    //          7 = 3    * 2       + 1
+    KEYS(">>", "\t\t\t abc");
+}
+
+void FakeVimPlugin::test_vim_marks()
+{
+    TestData data;
+    setup(&data);
+
+    data.setText("  abc" N "  def" N "  ghi");
+    data.doKeys("ma");
+    data.doKeys("ma");
+    data.doKeys("jmb");
+    data.doKeys("j^mc");
+    KEYS("'a",   "  " X "abc" N   "  "   "def" N   "  "   "ghi");
+    KEYS("`a", X "  "   "abc" N   "  "   "def" N   "  "   "ghi");
+    KEYS("`b",   "  "   "abc" N X "  "   "def" N   "  "   "ghi");
+    KEYS("'b",   "  "   "abc" N   "  " X "def" N   "  "   "ghi");
+    KEYS("`c",   "  "   "abc" N   "  "   "def" N   "  " X "ghi");
+    KEYS("'c",   "  "   "abc" N   "  "   "def" N   "  " X "ghi");
+
+    KEYS("`b",   "  "   "abc" N X "  "   "def" N   "  "   "ghi");
+    KEYS("'c",   "  "   "abc" N   "  "   "def" N   "  " X "ghi");
+
+    KEYS("`'",   "  "   "abc" N X "  "   "def" N   "  "   "ghi");
+    KEYS("`a", X "  "   "abc" N   "  "   "def" N   "  "   "ghi");
+    KEYS("''",   "  "   "abc" N   "  " X "def" N   "  "   "ghi");
+    KEYS("`'", X "  "   "abc" N   "  "   "def" N   "  "   "ghi");
+    KEYS("`'",   "  "   "abc" N   "  " X "def" N   "  "   "ghi");
+}
+
+void FakeVimPlugin::test_vim_copy_paste()
+{
+    TestData data;
+    setup(&data);
+
+    data.setText("123" N "456");
+    KEYS("llyy2P", X "123" N "123" N "123" N "456");
+
+    data.setText("123" N "456");
+    KEYS("yyp", "123" N X "123" N "456");
+    KEYS("2p", "123" N "123" N X "123" N "123" N "456");
+
+    data.setText("123 456");
+    KEYS("yw2P", "123 123" X " 123 456");
+    KEYS("2p", "123 123 123 123" X " 123 456");
+
+    data.setText("123" N "456");
+    KEYS("2yyp", "123" N X "123" N "456" N "456");
+
+    data.setText("123" N "456");
+    KEYS("2yyP", X "123" N "456" N "123" N "456");
+
+    data.setText("123" N "456" N "789");
+    KEYS("ddp", "456" N X "123" N "789");
+
+    // block-select middle column, copy and paste twice
+    data.setText("123" N "456");
+    KEYS("l<C-v>j\"xy2\"xp", "12" X "223" N "45556");
+}
+
+void FakeVimPlugin::test_vim_undo_redo()
+{
+    TestData data;
+    setup(&data);
+
+    data.setText("abc def" N "xyz" N "123");
+    KEYS("ddu", X "abc def" N "xyz" N "123");
+    COMMAND("redo", X "xyz" N "123");
+    COMMAND("undo", X "abc def" N "xyz" N "123");
+    COMMAND("redo", X "xyz" N "123");
+    KEYS("dd", X "123");
+    KEYS("3x", X "");
+    KEYS("uuu", X "abc def" N "xyz" N "123");
+    KEYS("<C-r>", X "xyz" N "123");
+    KEYS("2<C-r>", X "");
+    KEYS("3u", X "abc def" N "xyz" N "123");
+
+    KEYS("wved", "abc" X " " N "xyz" N "123");
+    KEYS("2w", "abc " N "xyz" N X "123");
+    KEYS("u", "abc " X "def" N "xyz" N "123");
+    KEYS("<C-r>", "abc" X " " N "xyz" N "123");
+    KEYS("10ugg", X "abc def" N "xyz" N "123");
+
+    KEYS("A xxx<ESC>", "abc def xx" X "x" N "xyz" N "123");
+    KEYS("A yyy<ESC>", "abc def xxx yy" X "y" N "xyz" N "123");
+    KEYS("u", "abc def xx" X "x" N "xyz" N "123");
+    KEYS("u", "abc de" X "f" N "xyz" N "123");
+    KEYS("<C-r>", "abc def" X " xxx" N "xyz" N "123");
+    KEYS("<C-r>", "abc def xxx" X " yyy" N "xyz" N "123");
+
+    KEYS("izzz<ESC>", "abc def xxxzz" X "z yyy" N "xyz" N "123");
+    KEYS("<C-r>", "abc def xxxzz" X "z yyy" N "xyz" N "123");
+    KEYS("u", "abc def xxx" X " yyy" N "xyz" N "123");
+
+    data.setText("abc" N X "def");
+    KEYS("oxyz<ESC>", "abc" N "def" N "xy" X "z");
+    KEYS("u", "abc" N X "def");
+
+    // undo paste lines
+    data.setText("abc" N);
+    KEYS("yy2p", "abc" N X "abc" N "abc" N);
+    KEYS("yy3p", "abc" N "abc" N X "abc" N "abc" N "abc" N "abc" N);
+    KEYS("u", "abc" N X "abc" N "abc" N);
+    KEYS("u", X "abc" N);
+    KEYS("<C-r>", X "abc" N "abc" N "abc" N);
+    KEYS("<C-r>", "abc" N X "abc" N "abc" N "abc" N "abc" N "abc" N);
+    KEYS("u", "abc" N X "abc" N "abc" N);
+    KEYS("u", X "abc" N);
+
+    // undo paste block
+    data.setText("abc" N "def" N "ghi");
+    KEYS("<C-v>jyp", "a" X "abc" N "ddef" N "ghi");
+    KEYS("2p", "aa" X "aabc" N "ddddef" N "ghi");
+    KEYS("3p", "aaa" X "aaaabc" N "dddddddef" N "ghi");
+    KEYS("u", "aa" X "aabc" N "ddddef" N "ghi");
+    KEYS("u", "a" X "abc" N "ddef" N "ghi");
+
+    // undo indent
+    data.doCommand("set expandtab");
+    data.doCommand("set shiftwidth=4");
+    data.setText("abc" N "def");
+    KEYS(">>", "    " X "abc" N "def");
+    KEYS(">>", "        " X "abc" N "def");
+    KEYS("<<", "    " X "abc" N "def");
+    KEYS("<<", X "abc" N "def");
+    KEYS("u", "    " X "abc" N "def");
+    KEYS("u", "        " X "abc" N "def");
+    KEYS("u", "    " X "abc" N "def");
+    KEYS("u", X "abc" N "def");
+    KEYS("<C-r>", X "    abc" N "def");
+    KEYS("<C-r>", "    " X "    abc" N "def");
+    KEYS("<C-r>", "    ab" X "c" N "def");
+    KEYS("<C-r>", "ab" X "c" N "def");
+    KEYS("<C-r>", "ab" X "c" N "def");
+
+    // undo replace line
+    data.setText("abc" N "  def" N "ghi");
+    KEYS("jlllSxyz<ESC>", "abc" N "xyz" N "ghi");
+    KEYS("u", "abc" N "  " X "def" N "ghi");
+}
diff --git a/src/plugins/fakevim/fakevimplugin.cpp b/src/plugins/fakevim/fakevimplugin.cpp
index aef12604e1..fe018422fc 100644
--- a/src/plugins/fakevim/fakevimplugin.cpp
+++ b/src/plugins/fakevim/fakevimplugin.cpp
@@ -78,6 +78,8 @@
 
 #include <cpptools/cpptoolsconstants.h>
 
+#include <extensionsystem/pluginmanager.h>
+
 #include <QAbstractTableModel>
 #include <QDebug>
 #include <QFile>
@@ -991,7 +993,10 @@ bool FakeVimPluginPrivate::initialize()
         this, SLOT(handleDelayedQuit(bool,Core::IEditor*)), Qt::QueuedConnection);
     connect(this, SIGNAL(delayedQuitAllRequested(bool)),
         this, SLOT(handleDelayedQuitAll(bool)), Qt::QueuedConnection);
-    maybeReadVimRc();
+
+    // Vimrc can break test so don't source it if running tests.
+    if (!ExtensionSystem::PluginManager::runningTests())
+        maybeReadVimRc();
     //    << "MODE: " << theFakeVimSetting(ConfigUseFakeVim)->value();
 
     return true;
diff --git a/src/plugins/fakevim/fakevimplugin.h b/src/plugins/fakevim/fakevimplugin.h
index 585fb06b00..5e5d59209f 100644
--- a/src/plugins/fakevim/fakevimplugin.h
+++ b/src/plugins/fakevim/fakevimplugin.h
@@ -57,6 +57,23 @@ private:
 private:
     friend class FakeVimPluginPrivate;
     FakeVimPluginPrivate *d;
+
+#ifdef WITH_TESTS
+private slots:
+    void test_vim_movement();
+    void test_vim_fFtT();
+    void test_vim_delete();
+    void test_vim_delete_inner_word();
+    void test_vim_delete_a_word();
+    void test_vim_change_a_word();
+    void test_vim_block_selection();
+    void test_vim_repeat();
+    void test_vim_search();
+    void test_vim_indent();
+    void test_vim_marks();
+    void test_vim_copy_paste();
+    void test_vim_undo_redo();
+#endif
 };
 
 } // namespace Internal
-- 
GitLab