// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0


#include <QTest>
#include <QAction>
#include <QUndoStack>
#include <QSignalSpy>
#if QT_CONFIG(process)
#include <QProcess>
#endif
#include <QTranslator>
#include <QLibraryInfo>

/******************************************************************************
** Commands
*/

class InsertCommand : public QUndoCommand
{
public:
    InsertCommand(QString *str, int idx, const QString &text,
                    QUndoCommand *parent = nullptr);

    virtual void undo() override;
    virtual void redo() override;

private:
    QString *m_str;
    int m_idx;
    QString m_text;
};

class RemoveCommand : public QUndoCommand
{
public:
    RemoveCommand(QString *str, int idx, int len, QUndoCommand *parent = nullptr);

    virtual void undo() override;
    virtual void redo() override;

private:
    QString *m_str;
    int m_idx;
    QString m_text;
};

class AppendCommand : public QUndoCommand
{
public:
    AppendCommand(QString *str, const QString &text, bool _fail_merge = false,
                    QUndoCommand *parent = nullptr);
    ~AppendCommand();

    virtual void undo() override;
    virtual void redo() override;
    virtual int id() const override;
    virtual bool mergeWith(const QUndoCommand *other) override;

    bool merged;
    bool fail_merge;
    static int delete_cnt;

private:
    QString *m_str;
    QString m_text;
};

class IdleCommand : public QUndoCommand
{
public:
    IdleCommand(QUndoCommand *parent = nullptr);
    ~IdleCommand();

    virtual void undo() override;
    virtual void redo() override;
};

class MoveMouseCommand : public QUndoCommand
{
public:
    MoveMouseCommand(QPoint *mouse, QPoint oldPoint, QPoint newPoint, QUndoCommand *parent = nullptr);
    ~MoveMouseCommand();

    virtual void undo() override;
    virtual void redo() override;
    virtual int id() const override;
    virtual bool mergeWith(const QUndoCommand *other) override;

private:
    QPoint *m_mouse;
    QPoint m_oldPoint;
    QPoint m_newPoint;
};

InsertCommand::InsertCommand(QString *str, int idx, const QString &text,
                            QUndoCommand *parent)
    : QUndoCommand(parent)
{
    QVERIFY(str->size() >= idx);

    setText("insert");

    m_str = str;
    m_idx = idx;
    m_text = text;
}

void InsertCommand::redo()
{
    QVERIFY(m_str->size() >= m_idx);

    m_str->insert(m_idx, m_text);
}

void InsertCommand::undo()
{
    QCOMPARE(m_str->mid(m_idx, m_text.size()), m_text);

    m_str->remove(m_idx, m_text.size());
}

RemoveCommand::RemoveCommand(QString *str, int idx, int len, QUndoCommand *parent)
    : QUndoCommand(parent)
{
    QVERIFY(str->size() >= idx + len);

    setText("remove");

    m_str = str;
    m_idx = idx;
    m_text = m_str->mid(m_idx, len);
}

void RemoveCommand::redo()
{
    QCOMPARE(m_str->mid(m_idx, m_text.size()), m_text);

    m_str->remove(m_idx, m_text.size());
}

void RemoveCommand::undo()
{
    QVERIFY(m_str->size() >= m_idx);

    m_str->insert(m_idx, m_text);
}

int AppendCommand::delete_cnt = 0;

AppendCommand::AppendCommand(QString *str, const QString &text, bool _fail_merge,
                                QUndoCommand *parent)
    : QUndoCommand(parent)
{
    setText("append");

    m_str = str;
    m_text = text;
    merged = false;
    fail_merge = _fail_merge;
}

AppendCommand::~AppendCommand()
{
    ++delete_cnt;
}

void AppendCommand::redo()
{
    m_str->append(m_text);
}

void AppendCommand::undo()
{
    QCOMPARE(m_str->mid(m_str->size() - m_text.size()), m_text);

    m_str->truncate(m_str->size() - m_text.size());
}

int AppendCommand::id() const
{
    return 1;
}

bool AppendCommand::mergeWith(const QUndoCommand *other)
{
    if (other->id() != id())
        return false;
    if (fail_merge)
        return false;
    m_text += static_cast<const AppendCommand*>(other)->m_text;
    merged = true;
    return true;
}

IdleCommand::IdleCommand(QUndoCommand *parent)
    : QUndoCommand(parent)
{
    // "idle-item" goes to QUndoStack::{redo,undo}Text
    // "idle-action" goes to all other places (e.g. QUndoView)
    setText("idle-item\nidle-action");
}

IdleCommand::~IdleCommand()
{
}

void IdleCommand::redo()
{
}

void IdleCommand::undo()
{
}

MoveMouseCommand::MoveMouseCommand(QPoint *mouse, QPoint oldPoint, QPoint newPoint, QUndoCommand *parent)
    : QUndoCommand(parent)
{
    setText("move mouse");

    m_mouse = mouse;
    m_oldPoint = oldPoint;
    m_newPoint = newPoint;

    if (m_oldPoint == m_newPoint)
        setObsolete(true);
}

MoveMouseCommand::~MoveMouseCommand()
{
}

void MoveMouseCommand::redo()
{
    *m_mouse = m_newPoint;
}

void MoveMouseCommand::undo()
{
    *m_mouse = m_oldPoint;
}

int MoveMouseCommand::id() const
{
    return 2;
}

bool MoveMouseCommand::mergeWith(const QUndoCommand *other)
{
    m_newPoint = static_cast<const MoveMouseCommand*>(other)->m_newPoint;

    if (m_newPoint == m_oldPoint)
        setObsolete(true);

    return true;
}

/******************************************************************************
** tst_QUndoStack
*/

class tst_QUndoStack : public QObject
{
    Q_OBJECT
public:
    tst_QUndoStack();

private slots:
    void undoRedo();
    void setIndex();
    void setClean();
    void clear();
    void childCommand();
    void macroBeginEnd();
    void compression();
    void obsolete();
    void undoLimit();
    void commandTextFormat();
    void separateUndoText();
};

tst_QUndoStack::tst_QUndoStack()
{
}

static QString glue(const QString &s1, const QString &s2)
{
    QString result;

    result.append(s1);
    if (!s1.isEmpty() && !s2.isEmpty())
        result.append(' ');
    result.append(s2);

    return result;
}

static void checkState(QSignalSpy &redoTextChangedSpy,
                       QSignalSpy &canRedoChangedSpy,
                       QSignalSpy &undoTextChangedSpy,
                       const QScopedPointer<QAction> &redoAction,
                       const QScopedPointer<QAction> &undoAction,
                       QSignalSpy &canUndoChangedSpy,
                       QSignalSpy &cleanChangedSpy,
                       QSignalSpy &indexChangedSpy,
                       QUndoStack &stack,
                       const bool _clean,
                       const int _count,
                       const int _index,
                       const bool _canUndo,
                       const QString &_undoText,
                       const bool _canRedo,
                       const QString &_redoText,
                       const bool _cleanChanged,
                       const bool _indexChanged,
                       const bool _undoChanged,
                       const bool _redoChanged)
{
    QCOMPARE(stack.count(), _count);
    QCOMPARE(stack.isClean(), _clean);
    QCOMPARE(stack.index(), _index);
    QCOMPARE(stack.canUndo(), _canUndo);
    QCOMPARE(stack.undoText(), QString(_undoText));
    QCOMPARE(stack.canRedo(), _canRedo);
    QCOMPARE(stack.redoText(), QString(_redoText));
    if (_indexChanged) {
        QCOMPARE(indexChangedSpy.size(), 1);
        QCOMPARE(indexChangedSpy.at(0).at(0).toInt(), _index);
        indexChangedSpy.clear();
    } else {
        QCOMPARE(indexChangedSpy.size(), 0);
    }
    if (_cleanChanged) {
        QCOMPARE(cleanChangedSpy.size(), 1);
        QCOMPARE(cleanChangedSpy.at(0).at(0).toBool(), _clean);
        cleanChangedSpy.clear();
    } else {
        QCOMPARE(cleanChangedSpy.size(), 0);
    }
    if (_undoChanged) {
        QCOMPARE(canUndoChangedSpy.size(), 1);
        QCOMPARE(canUndoChangedSpy.at(0).at(0).toBool(), _canUndo);
        QCOMPARE(undoAction->isEnabled(), _canUndo);
        QCOMPARE(undoTextChangedSpy.size(), 1);
        QCOMPARE(undoTextChangedSpy.at(0).at(0).toString(), QString(_undoText));
        QCOMPARE(undoAction->text(), glue("foo", _undoText));
        canUndoChangedSpy.clear();
        undoTextChangedSpy.clear();
    } else {
        QCOMPARE(canUndoChangedSpy.size(), 0);
        QCOMPARE(undoTextChangedSpy.size(), 0);
    }
    if (_redoChanged) {
        QCOMPARE(canRedoChangedSpy.size(), 1);
        QCOMPARE(canRedoChangedSpy.at(0).at(0).toBool(), _canRedo);
        QCOMPARE(redoAction->isEnabled(), _canRedo);
        QCOMPARE(redoTextChangedSpy.size(), 1);
        QCOMPARE(redoTextChangedSpy.at(0).at(0).toString(), QString(_redoText));
        QCOMPARE(redoAction->text(), glue("bar", _redoText));
        canRedoChangedSpy.clear();
        redoTextChangedSpy.clear();
    } else {
        QCOMPARE(canRedoChangedSpy.size(), 0);
        QCOMPARE(redoTextChangedSpy.size(), 0);
    }
}

void tst_QUndoStack::undoRedo()
{
    QUndoStack stack;
    QScopedPointer<QAction> undoAction(stack.createUndoAction(nullptr, QString("foo")));
    QScopedPointer<QAction> redoAction(stack.createRedoAction(nullptr, QString("bar")));
    QSignalSpy indexChangedSpy(&stack, &QUndoStack::indexChanged);
    QSignalSpy cleanChangedSpy(&stack, &QUndoStack::cleanChanged);
    QSignalSpy canUndoChangedSpy(&stack, &QUndoStack::canUndoChanged);
    QSignalSpy undoTextChangedSpy(&stack, &QUndoStack::undoTextChanged);
    QSignalSpy canRedoChangedSpy(&stack, &QUndoStack::canRedoChanged);
    QSignalSpy redoTextChangedSpy(&stack, &QUndoStack::redoTextChanged);
    QString str;

    // push, undo, redo

    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                0,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.undo(); // nothing to undo
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                0,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.push(new InsertCommand(&str, 0, "hello"));
    QCOMPARE(str, QString("hello"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new InsertCommand(&str, 2, "123"));
    QCOMPARE(str, QString("he123llo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged


    stack.undo();
    QCOMPARE(str, QString("hello"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                true,       // canRedo
                "insert",   // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.redo();
    QCOMPARE(str, QString("he123llo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.redo(); // nothing to redo
    QCOMPARE(str, QString("he123llo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.undo();
    QCOMPARE(str, QString("hello"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                true,       // canRedo
                "insert",   // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                2,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "insert",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo(); // nothing to undo
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                2,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "insert",   // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    // push after undo - check that undone commands get deleted

    stack.redo();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                true,       // canRedo
                "insert",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new RemoveCommand(&str, 2, 2));
    QCOMPARE(str, QString("heo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count - still 2, last command got deleted
                2,          // index
                true,       // canUndo
                "remove",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString("hello"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                true,       // canRedo
                "remove",   // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                2,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "insert",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new InsertCommand(&str, 0, "goodbye"));
    QCOMPARE(str, QString("goodbye"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count - two commands got deleted
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
}

void tst_QUndoStack::setIndex()
{
    QUndoStack stack;
    QScopedPointer<QAction> undoAction(stack.createUndoAction(nullptr, QString("foo")));
    QScopedPointer<QAction> redoAction(stack.createRedoAction(nullptr, QString("bar")));
    QSignalSpy indexChangedSpy(&stack, &QUndoStack::indexChanged);
    QSignalSpy cleanChangedSpy(&stack, &QUndoStack::cleanChanged);
    QSignalSpy canUndoChangedSpy(&stack, &QUndoStack::canUndoChanged);
    QSignalSpy undoTextChangedSpy(&stack, &QUndoStack::undoTextChanged);
    QSignalSpy canRedoChangedSpy(&stack, &QUndoStack::canRedoChanged);
    QSignalSpy redoTextChangedSpy(&stack, &QUndoStack::redoTextChanged);
    QString str;

    stack.setIndex(10); // should do nothing
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                0,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.setIndex(0); // should do nothing
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                0,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.setIndex(-10); // should do nothing
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                0,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.push(new InsertCommand(&str, 0, "hello"));
    QCOMPARE(str, QString("hello"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new InsertCommand(&str, 2, "123"));
    QCOMPARE(str, QString("he123llo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.setIndex(2);
    QCOMPARE(str, QString("he123llo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.setIndex(0);
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                2,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "insert",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.setIndex(10); // should set index to 2
    QCOMPARE(str, QString("he123llo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.setIndex(-10); // should set index to 0
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                2,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "insert",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.setIndex(1);
    QCOMPARE(str, QString("hello"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                true,       // canRedo
                "insert",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.setIndex(2);
    QCOMPARE(str, QString("he123llo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
}

void tst_QUndoStack::setClean()
{
    QUndoStack stack;
    QScopedPointer<QAction> undoAction(stack.createUndoAction(nullptr, QString("foo")));
    QScopedPointer<QAction> redoAction(stack.createRedoAction(nullptr, QString("bar")));
    QSignalSpy indexChangedSpy(&stack, &QUndoStack::indexChanged);
    QSignalSpy cleanChangedSpy(&stack, &QUndoStack::cleanChanged);
    QSignalSpy canUndoChangedSpy(&stack, &QUndoStack::canUndoChanged);
    QSignalSpy undoTextChangedSpy(&stack, &QUndoStack::undoTextChanged);
    QSignalSpy canRedoChangedSpy(&stack, &QUndoStack::canRedoChanged);
    QSignalSpy redoTextChangedSpy(&stack, &QUndoStack::redoTextChanged);
    QString str;

    QCOMPARE(stack.cleanIndex(), 0);
    stack.setClean();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                0,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged
    QCOMPARE(stack.cleanIndex(), 0);

    stack.push(new InsertCommand(&str, 0, "goodbye"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
    QCOMPARE(stack.cleanIndex(), 0);

    stack.setClean();
    QCOMPARE(str, QString("goodbye"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged
    QCOMPARE(stack.cleanIndex(), 1);

    stack.push(new AppendCommand(&str, " cowboy"));
    QCOMPARE(str, QString("goodbye cowboy"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
    QCOMPARE(stack.cleanIndex(), 1);

    stack.undo(); // reaching clean state from above
    QCOMPARE(str, QString("goodbye"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,      // clean
                2,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                true,       // canRedo
                "append",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
    QCOMPARE(stack.cleanIndex(), 1);

    stack.undo();
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "insert",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
    QCOMPARE(stack.cleanIndex(), 1);

    stack.redo(); // reaching clean state from below
    QCOMPARE(str, QString("goodbye"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                2,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                true,       // canRedo
                "append",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
    QCOMPARE(stack.cleanIndex(), 1);

    stack.undo();
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "insert",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
    QCOMPARE(stack.cleanIndex(), 1);

    stack.push(new InsertCommand(&str, 0, "foo")); // the clean state gets deleted!
    QCOMPARE(str, QString("foo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
    QCOMPARE(stack.cleanIndex(), -1);

    stack.undo();
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "insert",   // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
    QCOMPARE(stack.cleanIndex(), -1);

    stack.setClean();
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                1,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "insert",   // redoText
                true,       // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged
    QCOMPARE(stack.cleanIndex(), 0);

    stack.resetClean();
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "insert",   // redoText
                true,       // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged
    QCOMPARE(stack.cleanIndex(), -1);

    stack.redo();
    QCOMPARE(str, QString("foo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
    QCOMPARE(stack.cleanIndex(), -1);

    stack.setClean();
    QCOMPARE(str, QString("foo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged
    QCOMPARE(stack.cleanIndex(), 1);

    stack.undo();
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "insert",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
    QCOMPARE(stack.cleanIndex(), 1);

    stack.resetClean();
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "insert",   // redoText
                false,      // cleanChanged
                false,       // indexChanged
                false,       // undoChanged
                false);      // redoChanged
    QCOMPARE(stack.cleanIndex(), -1);
}

void tst_QUndoStack::clear()
{
    QUndoStack stack;
    QScopedPointer<QAction> undoAction(stack.createUndoAction(nullptr, QString("foo")));
    QScopedPointer<QAction> redoAction(stack.createRedoAction(nullptr, QString("bar")));
    QSignalSpy indexChangedSpy(&stack, &QUndoStack::indexChanged);
    QSignalSpy cleanChangedSpy(&stack, &QUndoStack::cleanChanged);
    QSignalSpy canUndoChangedSpy(&stack, &QUndoStack::canUndoChanged);
    QSignalSpy undoTextChangedSpy(&stack, &QUndoStack::undoTextChanged);
    QSignalSpy canRedoChangedSpy(&stack, &QUndoStack::canRedoChanged);
    QSignalSpy redoTextChangedSpy(&stack, &QUndoStack::redoTextChanged);
    QString str;

    stack.clear();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                0,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.push(new InsertCommand(&str, 0, "hello"));
    QCOMPARE(str, QString("hello"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new InsertCommand(&str, 2, "123"));
    QCOMPARE(str, QString("he123llo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.clear();
    QCOMPARE(str, QString("he123llo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                0,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    str.clear();
    stack.push(new InsertCommand(&str, 0, "hello"));
    QCOMPARE(str, QString("hello"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new InsertCommand(&str, 2, "123"));
    QCOMPARE(str, QString("he123llo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.setIndex(0);
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,      // clean
                2,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "insert",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.clear();
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                0,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
}

void tst_QUndoStack::childCommand()
{
    QUndoStack stack;
    QScopedPointer<QAction> undoAction(stack.createUndoAction(nullptr, QString("foo")));
    QScopedPointer<QAction> redoAction(stack.createRedoAction(nullptr, QString("bar")));
    QSignalSpy indexChangedSpy(&stack, &QUndoStack::indexChanged);
    QSignalSpy cleanChangedSpy(&stack, &QUndoStack::cleanChanged);
    QSignalSpy canUndoChangedSpy(&stack, &QUndoStack::canUndoChanged);
    QSignalSpy undoTextChangedSpy(&stack, &QUndoStack::undoTextChanged);
    QSignalSpy canRedoChangedSpy(&stack, &QUndoStack::canRedoChanged);
    QSignalSpy redoTextChangedSpy(&stack, &QUndoStack::redoTextChanged);
    QString str;

    stack.push(new InsertCommand(&str, 0, "hello"));
    QCOMPARE(str, QString("hello"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    QUndoCommand *cmd = new QUndoCommand();
    cmd->setText("ding");
    new InsertCommand(&str, 5, "world", cmd);
    new RemoveCommand(&str, 4, 1, cmd);
    stack.push(cmd);
    QCOMPARE(str, QString("hellworld"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "ding",     // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString("hello"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                true,       // canRedo
                "ding",     // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.redo();
    QCOMPARE(str, QString("hellworld"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "ding",     // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
}

void tst_QUndoStack::macroBeginEnd()
{
    QUndoStack stack;
    QScopedPointer<QAction> undoAction(stack.createUndoAction(nullptr, QString("foo")));
    QScopedPointer<QAction> redoAction(stack.createRedoAction(nullptr, QString("bar")));
    QSignalSpy indexChangedSpy(&stack, &QUndoStack::indexChanged);
    QSignalSpy cleanChangedSpy(&stack, &QUndoStack::cleanChanged);
    QSignalSpy canUndoChangedSpy(&stack, &QUndoStack::canUndoChanged);
    QSignalSpy undoTextChangedSpy(&stack, &QUndoStack::undoTextChanged);
    QSignalSpy canRedoChangedSpy(&stack, &QUndoStack::canRedoChanged);
    QSignalSpy redoTextChangedSpy(&stack, &QUndoStack::redoTextChanged);
    QString str;

    stack.beginMacro("ding");
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.setClean(); // should do nothing
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.undo(); // should do nothing
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.redo(); // should do nothing
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.setIndex(0); // should do nothing
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.endMacro();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index - endMacro() increments index
                true,       // canUndo
                "ding",     // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new InsertCommand(&str, 0, "h"));
    QCOMPARE(str, QString("h"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new InsertCommand(&str, 1, "owdy"));
    QCOMPARE(str, QString("howdy"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                3,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.setIndex(2);
    QCOMPARE(str, QString("h"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                true,       // canUndo
                "insert",   // undoText
                true,       // canRedo
                "insert",   // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.beginMacro("dong"); // the "owdy" command gets deleted
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new InsertCommand(&str, 1, "ello"));
    QCOMPARE(str, QString("hello"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.push(new RemoveCommand(&str, 1, 2));
    QCOMPARE(str, QString("hlo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.beginMacro("dong2");
    QCOMPARE(str, QString("hlo"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.push(new RemoveCommand(&str, 1, 1));
    QCOMPARE(str, QString("ho"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.endMacro();
    QCOMPARE(str, QString("ho"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.endMacro();
    QCOMPARE(str, QString("ho"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                3,          // index
                true,       // canUndo
                "dong",     // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString("h"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                true,       // canUndo
                "insert",     // undoText
                true,       // canRedo
                "dong",     // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString(""));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                1,          // index
                true,       // canUndo
                "ding",     // undoText
                true,       // canRedo
                "insert",   // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.setIndex(3);
    QCOMPARE(str, QString("ho"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                3,          // index
                true,       // canUndo
                "dong",     // undoText
                false,      // canRedo
                "",         // redoText
                false,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.setIndex(1);
    QCOMPARE(str, QString());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                1,          // index
                true,       // canUndo
                "ding",     // undoText
                true,       // canRedo
                "insert",   // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
}

void tst_QUndoStack::compression()
{
    QUndoStack stack;
    QScopedPointer<QAction> undoAction(stack.createUndoAction(nullptr, QString("foo")));
    QScopedPointer<QAction> redoAction(stack.createRedoAction(nullptr, QString("bar")));
    QSignalSpy indexChangedSpy(&stack, &QUndoStack::indexChanged);
    QSignalSpy cleanChangedSpy(&stack, &QUndoStack::cleanChanged);
    QSignalSpy canUndoChangedSpy(&stack, &QUndoStack::canUndoChanged);
    QSignalSpy undoTextChangedSpy(&stack, &QUndoStack::undoTextChanged);
    QSignalSpy canRedoChangedSpy(&stack, &QUndoStack::canRedoChanged);
    QSignalSpy redoTextChangedSpy(&stack, &QUndoStack::redoTextChanged);
    QString str;

    AppendCommand::delete_cnt = 0;

    stack.push(new InsertCommand(&str, 0, "ene"));
    QCOMPARE(str, QString("ene"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new AppendCommand(&str, " due")); // #1
    QCOMPARE(str, QString("ene due"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new AppendCommand(&str, " rike")); // #2 should merge
    QCOMPARE(str, QString("ene due rike"));
    QCOMPARE(AppendCommand::delete_cnt, 1); // #2 should be deleted
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.setClean();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.push(new AppendCommand(&str, " fake")); // #3 should NOT merge, since the stack was clean
    QCOMPARE(str, QString("ene due rike fake"));  // and we want to be able to return to this state
    QCOMPARE(AppendCommand::delete_cnt, 1); // #3 should not be deleted
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                3,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString("ene due rike"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                3,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                true,       // canRedo
                "append",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString("ene"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                true,       // canRedo
                "append",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new AppendCommand(&str, "ma", true)); // #4 clean state gets deleted!
    QCOMPARE(str, QString("enema"));
    QCOMPARE(AppendCommand::delete_cnt, 3); // #1 got deleted
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new AppendCommand(&str, "trix")); // #5 should NOT merge
    QCOMPARE(str, QString("enematrix"));
    QCOMPARE(AppendCommand::delete_cnt, 3);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                3,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString("enema"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                true,       // canRedo
                "append",   // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    // and now for command compression inside macros

    stack.setClean();
    QCOMPARE(str, QString("enema"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                3,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                true,       // canRedo
                "append",   // redoText
                true,       // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.beginMacro("ding");
    QCOMPARE(str, QString("enema"));
    QCOMPARE(AppendCommand::delete_cnt, 4); // #5 gets deleted
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    AppendCommand *merge_cmd = new AppendCommand(&str, "top");
    stack.push(merge_cmd); // #6
    QCOMPARE(merge_cmd->merged, false);
    QCOMPARE(str, QString("enematop"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.push(new AppendCommand(&str, "eja")); // #7 should merge
    QCOMPARE(str, QString("enematopeja"));
    QCOMPARE(merge_cmd->merged, true);
    QCOMPARE(AppendCommand::delete_cnt, 5); // #7 gets deleted
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged
    merge_cmd->merged = false;

    stack.push(new InsertCommand(&str, 2, "123")); // should not merge
    QCOMPARE(str, QString("en123ematopeja"));
    QCOMPARE(merge_cmd->merged, false);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.endMacro();
    QCOMPARE(str, QString("en123ematopeja"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                3,          // index
                true,       // canUndo
                "ding",     // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString("enema"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,      // clean
                3,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                true,       // canRedo
                "ding",     // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.redo();
    QCOMPARE(str, QString("en123ematopeja"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                3,          // index
                true,       // canUndo
                "ding",     // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
}

void tst_QUndoStack::obsolete()
{
    QUndoStack stack;
    QScopedPointer<QAction> undoAction(stack.createUndoAction(nullptr, QString("foo")));
    QScopedPointer<QAction> redoAction(stack.createRedoAction(nullptr, QString("bar")));
    QSignalSpy indexChangedSpy(&stack, &QUndoStack::indexChanged);
    QSignalSpy cleanChangedSpy(&stack, &QUndoStack::cleanChanged);
    QSignalSpy canUndoChangedSpy(&stack, &QUndoStack::canUndoChanged);
    QSignalSpy undoTextChangedSpy(&stack, &QUndoStack::undoTextChanged);
    QSignalSpy canRedoChangedSpy(&stack, &QUndoStack::canRedoChanged);
    QSignalSpy redoTextChangedSpy(&stack, &QUndoStack::redoTextChanged);
    QPoint mouse(0, 0);
    QString str;
    MoveMouseCommand *cmd1 = nullptr;
    MoveMouseCommand *cmd2 = nullptr;

    stack.resetClean();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                0,            // count
                0,            // index
                false,        // canUndo
                "",           // undoText
                false,        // canRedo
                "",           // redoText
                true,        // cleanChanged
                false,        // indexChanged
                false,        // undoChanged
                false);       // redoChanged

    stack.push(new MoveMouseCommand(&mouse, mouse, QPoint(0, 0))); // #1 should not merge but will be deleted (b/c oldPoint == newPoint)
    QCOMPARE(mouse, QPoint(0, 0));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                0,            // count
                0,            // index
                false,        // canUndo
                "",           // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                false,        // indexChanged
                false,        // undoChanged
                false);       // redoChanged

    stack.push(new MoveMouseCommand(&mouse, mouse, QPoint(12, 0))); // #2 should not merge or be deleted (b/c oldPoint != newPoint)
    QCOMPARE(mouse, QPoint(12, 0));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                1,            // count
                1,            // index
                true,         // canUndo
                "move mouse", // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged

    stack.push(new MoveMouseCommand(&mouse, mouse, QPoint(8, 2))); // #3 should merge and not be deleted (b/c oldPoint != newPoint)
    QCOMPARE(mouse, QPoint(8, 2));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                1,            // count
                1,            // index
                true,         // canUndo
                "move mouse", // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged

    stack.push(new MoveMouseCommand(&mouse, mouse, QPoint(0, 0))); // #4 should merge and be deleted (b/c oldPoint == newPoint)
    QCOMPARE(mouse, QPoint(0, 0));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                0,            // count
                0,            // index
                false,        // canUndo
                "",           // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged




    stack.push(new InsertCommand(&str, 0, "ene")); // #5 should not merge or be deleted
    QCOMPARE(str, QString("ene"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    cmd1 = new MoveMouseCommand(&mouse, mouse, QPoint(6, 5));
    stack.push(cmd1); // #6 should not merge or be deleted (b/c oldPoint != newPoint)
    QCOMPARE(mouse, QPoint(6, 5));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                2,            // count
                2,            // index
                true,         // canUndo
                "move mouse", // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged

    stack.push(new InsertCommand(&str, 3, "ma")); // #7 should not merge or be deleted
    QCOMPARE(str, QString("enema"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                3,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    cmd2 = new MoveMouseCommand(&mouse, mouse, QPoint(12, 4));
    stack.push(cmd2); // #8 should not merge or be deleted (b/c oldPoint != newPoint)
    QCOMPARE(mouse, QPoint(12, 4));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                4,            // count
                4,            // index
                true,         // canUndo
                "move mouse", // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged

    stack.setClean();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,         // clean
                4,            // count
                4,            // index
                true,         // canUndo
                "move mouse", // undoText
                false,        // canRedo
                "",           // redoText
                true,         // cleanChanged
                false,        // indexChanged
                false,        // undoChanged
                false);       // redoChanged
    QCOMPARE(stack.cleanIndex(), 4);

    cmd2->setObsolete(true);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,         // clean
                4,            // count
                4,            // index
                true,         // canUndo
                "move mouse", // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                false,        // indexChanged
                false,        // undoChanged
                false);       // redoChanged

    stack.undo();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                3,            // count
                3,            // index
                true,         // canUndo
                "insert",     // undoText
                false,        // canRedo
                "",           // redoText
                true,         // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged
    QCOMPARE(stack.cleanIndex(), -1);

    stack.undo();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                3,            // count
                2,            // index
                true,         // canUndo
                "move mouse", // undoText
                true,         // canRedo
                "insert",     // redoText
                false,        // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged

    stack.setClean();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,         // clean
                3,            // count
                2,            // index
                true,         // canUndo
                "move mouse", // undoText
                true,         // canRedo
                "insert",     // redoText
                true,         // cleanChanged
                false,        // indexChanged
                false,        // undoChanged
                false);       // redoChanged
    QCOMPARE(stack.cleanIndex(), 2);

    stack.undo();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                3,            // count
                1,            // index
                true,         // canUndo
                "insert",     // undoText
                true,         // canRedo
                "move mouse", // redoText
                true,         // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged

    cmd1->setObsolete(true);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                3,            // count
                1,            // index
                true,         // canUndo
                "insert",     // undoText
                true,         // canRedo
                "move mouse", // redoText
                false,        // cleanChanged
                false,        // indexChanged
                false,        // undoChanged
                false);       // redoChanged

    stack.redo();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                2,            // count
                1,            // index
                true,         // canUndo
                "insert",     // undoText
                true,         // canRedo
                "insert",     // redoText
                false,        // cleanChanged
                false,        // indexChanged
                false,        // undoChanged
                false);       // redoChanged
    QCOMPARE(stack.cleanIndex(), -1);

    stack.redo();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                2,            // count
                2,            // index
                true,         // canUndo
                "insert",     // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged
    QCOMPARE(stack.cleanIndex(), -1);

    cmd1 = new MoveMouseCommand(&mouse, mouse, QPoint(13, 2));
    stack.push(cmd1); // #9 should not merge or be deleted (b/c oldPoint != newPoint)
    QCOMPARE(mouse, QPoint(13, 2));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                3,            // count
                3,            // index
                true,         // canUndo
                "move mouse", // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged

    stack.push(new InsertCommand(&str, 3, "ma")); // #10 should not merge or be deleted
    QCOMPARE(str, QString("enemama"));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                4,          // count
                4,          // index
                true,       // canUndo
                "insert",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    cmd2 = new MoveMouseCommand(&mouse, mouse, QPoint(6, 20));
    stack.push(cmd2); // #11 should not merge or be deleted (b/c oldPoint != newPoint)
    QCOMPARE(mouse, QPoint(6, 20));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                5,            // count
                5,            // index
                true,         // canUndo
                "move mouse", // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged

    cmd1->setObsolete(true);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                5,            // count
                5,            // index
                true,         // canUndo
                "move mouse", // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                false,        // indexChanged
                false,        // undoChanged
                false);       // redoChanged

    stack.setClean();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,         // clean
                5,            // count
                5,            // index
                true,         // canUndo
                "move mouse", // undoText
                false,        // canRedo
                "",           // redoText
                true,         // cleanChanged
                false,        // indexChanged
                false,        // undoChanged
                false);       // redoChanged
    QCOMPARE(stack.cleanIndex(), 5);

    stack.setIndex(0);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                4,            // count
                0,            // index
                false,        // canUndo
                "",           // undoText
                true,         // canRedo
                "insert",     // redoText
                true,         // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged
    QCOMPARE(stack.cleanIndex(), -1);

    cmd2->setObsolete(true);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                4,            // count
                0,            // index
                false,        // canUndo
                "",           // undoText
                true,         // canRedo
                "insert",     // redoText
                false,        // cleanChanged
                false,        // indexChanged
                false,        // undoChanged
                false);       // redoChanged

    stack.setIndex(stack.count());
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                3,            // count
                3,            // index
                true,         // canUndo
                "insert",     // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged

    mouse = QPoint(0, 0); // Reset mouse position
    stack.beginMacro("ding");
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                4,            // count
                3,            // index
                false,        // canUndo
                "",           // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                false,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged

    stack.push(new MoveMouseCommand(&mouse, mouse, QPoint(7, 7))); // #12 should not merge or be deleted (b/c oldPoint != newPoint & in macro)
    QCOMPARE(mouse, QPoint(7, 7));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                4,            // count
                3,            // index
                false,        // canUndo
                "",           // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                false,        // indexChanged
                false,        // undoChanged
                false);       // redoChanged

    stack.push(new MoveMouseCommand(&mouse, mouse, QPoint(0, 0))); // #13 should merge and be deleted (b/c oldPoint = newPoint)
    QCOMPARE(mouse, QPoint(0, 0));
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                4,            // count
                3,            // index
                false,        // canUndo
                "",           // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                false,        // indexChanged
                false,        // undoChanged
                false);       // redoChanged

    stack.endMacro();
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,        // clean
                4,            // count
                4,            // index
                true,         // canUndo
                "ding",       // undoText
                false,        // canRedo
                "",           // redoText
                false,        // cleanChanged
                true,         // indexChanged
                true,         // undoChanged
                true);        // redoChanged
}

void tst_QUndoStack::undoLimit()
{
    QUndoStack stack;
    QScopedPointer<QAction> undoAction(stack.createUndoAction(nullptr, QString("foo")));
    QScopedPointer<QAction> redoAction(stack.createRedoAction(nullptr, QString("bar")));
    QSignalSpy indexChangedSpy(&stack, &QUndoStack::indexChanged);
    QSignalSpy cleanChangedSpy(&stack, &QUndoStack::cleanChanged);
    QSignalSpy canUndoChangedSpy(&stack, &QUndoStack::canUndoChanged);
    QSignalSpy undoTextChangedSpy(&stack, &QUndoStack::undoTextChanged);
    QSignalSpy canRedoChangedSpy(&stack, &QUndoStack::canRedoChanged);
    QSignalSpy redoTextChangedSpy(&stack, &QUndoStack::redoTextChanged);
    AppendCommand::delete_cnt = 0;
    QString str;

    QCOMPARE(stack.undoLimit(), 0);
    stack.setUndoLimit(2);
    QCOMPARE(stack.undoLimit(), 2);

    stack.push(new AppendCommand(&str, "1", true));
    QCOMPARE(str, QString("1"));
    QCOMPARE(AppendCommand::delete_cnt, 0);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new AppendCommand(&str, "2", true));
    QCOMPARE(str, QString("12"));
    QCOMPARE(AppendCommand::delete_cnt, 0);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.setClean();
    QCOMPARE(str, QString("12"));
    QCOMPARE(AppendCommand::delete_cnt, 0);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.push(new AppendCommand(&str, "3", true));
    QCOMPARE(str, QString("123"));
    QCOMPARE(AppendCommand::delete_cnt, 1);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new AppendCommand(&str, "4", true));
    QCOMPARE(str, QString("1234"));
    QCOMPARE(AppendCommand::delete_cnt, 2);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString("123"));
    QCOMPARE(AppendCommand::delete_cnt, 2);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                1,          // index
                true,       // canUndo
                "append",   // undoText
                true,       // canRedo
                "append",   // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString("12"));
    QCOMPARE(AppendCommand::delete_cnt, 2);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                true,       // clean
                2,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "append",   // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new AppendCommand(&str, "3", true));
    QCOMPARE(str, QString("123"));
    QCOMPARE(AppendCommand::delete_cnt, 4);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                true,       // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new AppendCommand(&str, "4", true));
    QCOMPARE(str, QString("1234"));
    QCOMPARE(AppendCommand::delete_cnt, 4);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new AppendCommand(&str, "5", true));
    QCOMPARE(str, QString("12345"));
    QCOMPARE(AppendCommand::delete_cnt, 5);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString("1234"));
    QCOMPARE(AppendCommand::delete_cnt, 5);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                1,          // index
                true,       // canUndo
                "append",   // undoText
                true,       // canRedo
                "append",   // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString("123"));
    QCOMPARE(AppendCommand::delete_cnt, 5);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "append",   // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new AppendCommand(&str, "4", true));
    QCOMPARE(str, QString("1234"));
    QCOMPARE(AppendCommand::delete_cnt, 7);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                1,          // count
                1,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new AppendCommand(&str, "5"));
    QCOMPARE(str, QString("12345"));
    QCOMPARE(AppendCommand::delete_cnt, 7);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new AppendCommand(&str, "6", true)); // should be merged
    QCOMPARE(str, QString("123456"));
    QCOMPARE(AppendCommand::delete_cnt, 8);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "append",   // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.beginMacro("foo");
    QCOMPARE(str, QString("123456"));
    QCOMPARE(AppendCommand::delete_cnt, 8);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.push(new AppendCommand(&str, "7", true));
    QCOMPARE(str, QString("1234567"));
    QCOMPARE(AppendCommand::delete_cnt, 8);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.push(new AppendCommand(&str, "8"));
    QCOMPARE(str, QString("12345678"));
    QCOMPARE(AppendCommand::delete_cnt, 8);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                3,          // count
                2,          // index
                false,      // canUndo
                "",         // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                false,      // indexChanged
                false,      // undoChanged
                false);     // redoChanged

    stack.endMacro();
    QCOMPARE(str, QString("12345678"));
    QCOMPARE(AppendCommand::delete_cnt, 9);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                2,          // index
                true,       // canUndo
                "foo",      // undoText
                false,      // canRedo
                "",         // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString("123456"));
    QCOMPARE(AppendCommand::delete_cnt, 9);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                1,          // index
                true,       // canUndo
                "append",   // undoText
                true,       // canRedo
                "foo",      // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged

    stack.undo();
    QCOMPARE(str, QString("1234"));
    QCOMPARE(AppendCommand::delete_cnt, 9);
    checkState(redoTextChangedSpy,
                canRedoChangedSpy,
                undoTextChangedSpy,
                redoAction,
                undoAction,
                canUndoChangedSpy,
                cleanChangedSpy,
                indexChangedSpy,
                stack,
                false,      // clean
                2,          // count
                0,          // index
                false,      // canUndo
                "",         // undoText
                true,       // canRedo
                "append",   // redoText
                false,      // cleanChanged
                true,       // indexChanged
                true,       // undoChanged
                true);      // redoChanged
}

void tst_QUndoStack::commandTextFormat()
{
#if !QT_CONFIG(process)
    QSKIP("No QProcess available");
#else
    QString binDir = QLibraryInfo::path(QLibraryInfo::BinariesPath);

    if (QProcess::execute(binDir + "/lrelease -version") != 0)
        QSKIP("lrelease is missing or broken");

    const QString tsFile = QFINDTESTDATA("testdata/qundostack.ts");
    QVERIFY(!tsFile.isEmpty());
    QFile::remove("qundostack.qm"); // Avoid confusion by strays.
    QVERIFY(!QProcess::execute(binDir + "/lrelease -silent " + tsFile + " -qm qundostack.qm"));

    QTranslator translator;
    QVERIFY(translator.load("qundostack.qm"));
    QFile::remove("qundostack.qm");
    qApp->installTranslator(&translator);

    QUndoStack stack;
    QScopedPointer<QAction> undo_action(stack.createUndoAction(nullptr));
    QScopedPointer<QAction> redo_action(stack.createRedoAction(nullptr));

    QCOMPARE(undo_action->text(), QString("Undo-default-text"));
    QCOMPARE(redo_action->text(), QString("Redo-default-text"));

    QString str;

    stack.push(new AppendCommand(&str, "foo"));
    QCOMPARE(undo_action->text(), QString("undo-prefix append undo-suffix"));
    QCOMPARE(redo_action->text(), QString("Redo-default-text"));

    stack.push(new InsertCommand(&str, 0, "bar"));
    stack.undo();
    QCOMPARE(undo_action->text(), QString("undo-prefix append undo-suffix"));
    QCOMPARE(redo_action->text(), QString("redo-prefix insert redo-suffix"));

    stack.undo();
    QCOMPARE(undo_action->text(), QString("Undo-default-text"));
    QCOMPARE(redo_action->text(), QString("redo-prefix append redo-suffix"));

    qApp->removeTranslator(&translator);
#endif
}

void tst_QUndoStack::separateUndoText()
{
    QUndoStack stack;
    QScopedPointer<QAction> undo_action(stack.createUndoAction(nullptr));
    QScopedPointer<QAction> redo_action(stack.createRedoAction(nullptr));

    QUndoCommand *command1 = new IdleCommand();
    QUndoCommand *command2 = new IdleCommand();
    stack.push(command1);
    stack.push(command2);
    stack.undo();

    QCOMPARE(undo_action->text(), QString("Undo idle-action"));
    QCOMPARE(redo_action->text(), QString("Redo idle-action"));
    QCOMPARE(command1->actionText(), QString("idle-action"));

    QCOMPARE(command1->text(), QString("idle-item"));
    QCOMPARE(stack.text(0), QString("idle-item"));

    command1->setText("idle");
    QCOMPARE(command1->actionText(), QString("idle"));
    QCOMPARE(command1->text(), QString("idle"));

    command1->setText("idle-item\nidle-action");
    QCOMPARE(command1->actionText(), QString("idle-action"));
    QCOMPARE(command1->text(), QString("idle-item"));
}

QTEST_MAIN(tst_QUndoStack)

#include "tst_qundostack.moc"
