blob: c1524d870dcc584caa5c3238bcaa5845180bb482 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QUndoGroup>
#include <QUndoStack>
#include <QAction>
/******************************************************************************
** Commands
*/
class InsertCommand : public QUndoCommand
{
public:
InsertCommand(QString *str, int idx, const QString &text,
QUndoCommand *parent = 0);
virtual void undo();
virtual void redo();
private:
QString *m_str;
int m_idx;
QString m_text;
};
class RemoveCommand : public QUndoCommand
{
public:
RemoveCommand(QString *str, int idx, int len, QUndoCommand *parent = 0);
virtual void undo();
virtual void redo();
private:
QString *m_str;
int m_idx;
QString m_text;
};
class AppendCommand : public QUndoCommand
{
public:
AppendCommand(QString *str, const QString &text, QUndoCommand *parent = 0);
virtual void undo();
virtual void redo();
virtual int id() const;
virtual bool mergeWith(const QUndoCommand *other);
bool merged;
private:
QString *m_str;
QString m_text;
};
InsertCommand::InsertCommand(QString *str, int idx, const QString &text,
QUndoCommand *parent)
: QUndoCommand(parent)
{
QVERIFY(str->length() >= idx);
setText("insert");
m_str = str;
m_idx = idx;
m_text = text;
}
void InsertCommand::redo()
{
QVERIFY(m_str->length() >= m_idx);
m_str->insert(m_idx, m_text);
}
void InsertCommand::undo()
{
QCOMPARE(m_str->mid(m_idx, m_text.length()), m_text);
m_str->remove(m_idx, m_text.length());
}
RemoveCommand::RemoveCommand(QString *str, int idx, int len, QUndoCommand *parent)
: QUndoCommand(parent)
{
QVERIFY(str->length() >= 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.length()), m_text);
m_str->remove(m_idx, m_text.length());
}
void RemoveCommand::undo()
{
QVERIFY(m_str->length() >= m_idx);
m_str->insert(m_idx, m_text);
}
AppendCommand::AppendCommand(QString *str, const QString &text, QUndoCommand *parent)
: QUndoCommand(parent)
{
setText("append");
m_str = str;
m_text = text;
merged = false;
}
void AppendCommand::redo()
{
m_str->append(m_text);
}
void AppendCommand::undo()
{
QCOMPARE(m_str->mid(m_str->length() - m_text.length()), m_text);
m_str->truncate(m_str->length() - m_text.length());
}
int AppendCommand::id() const
{
return 1;
}
bool AppendCommand::mergeWith(const QUndoCommand *other)
{
if (other->id() != id())
return false;
m_text += static_cast<const AppendCommand*>(other)->m_text;
merged = true;
return true;
}
/******************************************************************************
** tst_QUndoStack
*/
class tst_QUndoGroup : public QObject
{
Q_OBJECT
public:
tst_QUndoGroup();
private slots:
void setActive();
void addRemoveStack();
void deleteStack();
void checkSignals();
void addStackAndDie();
void commandTextFormat();
};
tst_QUndoGroup::tst_QUndoGroup()
{
}
void tst_QUndoGroup::setActive()
{
QUndoGroup group;
QUndoStack stack1(&group), stack2(&group);
QCOMPARE(group.activeStack(), nullptr);
QCOMPARE(stack1.isActive(), false);
QCOMPARE(stack2.isActive(), false);
QUndoStack stack3;
QCOMPARE(stack3.isActive(), true);
group.addStack(&stack3);
QCOMPARE(stack3.isActive(), false);
stack1.setActive();
QCOMPARE(group.activeStack(), &stack1);
QCOMPARE(stack1.isActive(), true);
QCOMPARE(stack2.isActive(), false);
QCOMPARE(stack3.isActive(), false);
group.setActiveStack(&stack2);
QCOMPARE(group.activeStack(), &stack2);
QCOMPARE(stack1.isActive(), false);
QCOMPARE(stack2.isActive(), true);
QCOMPARE(stack3.isActive(), false);
group.removeStack(&stack2);
QCOMPARE(group.activeStack(), nullptr);
QCOMPARE(stack1.isActive(), false);
QCOMPARE(stack2.isActive(), true);
QCOMPARE(stack3.isActive(), false);
group.removeStack(&stack2);
QCOMPARE(group.activeStack(), nullptr);
QCOMPARE(stack1.isActive(), false);
QCOMPARE(stack2.isActive(), true);
QCOMPARE(stack3.isActive(), false);
}
void tst_QUndoGroup::addRemoveStack()
{
QUndoGroup group;
QUndoStack stack1(&group);
QCOMPARE(group.stacks(), QList<QUndoStack*>() << &stack1);
QUndoStack stack2;
group.addStack(&stack2);
QCOMPARE(group.stacks(), QList<QUndoStack*>() << &stack1 << &stack2);
group.addStack(&stack1);
QCOMPARE(group.stacks(), QList<QUndoStack*>() << &stack1 << &stack2);
group.removeStack(&stack1);
QCOMPARE(group.stacks(), QList<QUndoStack*>() << &stack2);
group.removeStack(&stack1);
QCOMPARE(group.stacks(), QList<QUndoStack*>() << &stack2);
group.removeStack(&stack2);
QCOMPARE(group.stacks(), QList<QUndoStack*>());
}
void tst_QUndoGroup::deleteStack()
{
QUndoGroup group;
QUndoStack *stack1 = new QUndoStack(&group);
QCOMPARE(group.stacks(), QList<QUndoStack*>() << stack1);
QCOMPARE(group.activeStack(), nullptr);
stack1->setActive();
QCOMPARE(group.activeStack(), stack1);
QUndoStack *stack2 = new QUndoStack(&group);
QCOMPARE(group.stacks(), QList<QUndoStack*>() << stack1 << stack2);
QCOMPARE(group.activeStack(), stack1);
QUndoStack *stack3 = new QUndoStack(&group);
QCOMPARE(group.stacks(), QList<QUndoStack*>() << stack1 << stack2 << stack3);
QCOMPARE(group.activeStack(), stack1);
delete stack2;
QCOMPARE(group.stacks(), QList<QUndoStack*>() << stack1 << stack3);
QCOMPARE(group.activeStack(), stack1);
delete stack1;
QCOMPARE(group.stacks(), QList<QUndoStack*>() << stack3);
QCOMPARE(group.activeStack(), nullptr);
stack3->setActive(false);
QCOMPARE(group.activeStack(), nullptr);
stack3->setActive(true);
QCOMPARE(group.activeStack(), stack3);
group.removeStack(stack3);
QCOMPARE(group.stacks(), QList<QUndoStack*>());
QCOMPARE(group.activeStack(), nullptr);
delete stack3;
}
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;
}
#define CHECK_STATE(_activeStack, _clean, _canUndo, _undoText, _canRedo, _redoText, \
_cleanChanged, _indexChanged, _undoChanged, _redoChanged) \
QCOMPARE(group.activeStack(), (QUndoStack*)_activeStack); \
QCOMPARE(group.isClean(), _clean); \
QCOMPARE(group.canUndo(), _canUndo); \
QCOMPARE(group.undoText(), QString(_undoText)); \
QCOMPARE(group.canRedo(), _canRedo); \
QCOMPARE(group.redoText(), QString(_redoText)); \
if (_indexChanged) { \
QCOMPARE(indexChangedSpy.count(), 1); \
indexChangedSpy.clear(); \
} else { \
QCOMPARE(indexChangedSpy.count(), 0); \
} \
if (_cleanChanged) { \
QCOMPARE(cleanChangedSpy.count(), 1); \
QCOMPARE(cleanChangedSpy.at(0).at(0).toBool(), _clean); \
cleanChangedSpy.clear(); \
} else { \
QCOMPARE(cleanChangedSpy.count(), 0); \
} \
if (_undoChanged) { \
QCOMPARE(canUndoChangedSpy.count(), 1); \
QCOMPARE(canUndoChangedSpy.at(0).at(0).toBool(), _canUndo); \
QCOMPARE(undo_action->isEnabled(), _canUndo); \
QCOMPARE(undoTextChangedSpy.count(), 1); \
QCOMPARE(undoTextChangedSpy.at(0).at(0).toString(), QString(_undoText)); \
QCOMPARE(undo_action->text(), glue("foo", _undoText)); \
canUndoChangedSpy.clear(); \
undoTextChangedSpy.clear(); \
} else { \
QCOMPARE(canUndoChangedSpy.count(), 0); \
QCOMPARE(undoTextChangedSpy.count(), 0); \
} \
if (_redoChanged) { \
QCOMPARE(canRedoChangedSpy.count(), 1); \
QCOMPARE(canRedoChangedSpy.at(0).at(0).toBool(), _canRedo); \
QCOMPARE(redo_action->isEnabled(), _canRedo); \
QCOMPARE(redoTextChangedSpy.count(), 1); \
QCOMPARE(redoTextChangedSpy.at(0).at(0).toString(), QString(_redoText)); \
QCOMPARE(redo_action->text(), glue("bar", _redoText)); \
canRedoChangedSpy.clear(); \
redoTextChangedSpy.clear(); \
} else { \
QCOMPARE(canRedoChangedSpy.count(), 0); \
QCOMPARE(redoTextChangedSpy.count(), 0); \
}
void tst_QUndoGroup::checkSignals()
{
QUndoGroup group;
const QScopedPointer<QAction> undo_action(group.createUndoAction(0, QString("foo")));
const QScopedPointer<QAction> redo_action(group.createRedoAction(0, QString("bar")));
QSignalSpy indexChangedSpy(&group, &QUndoGroup::indexChanged);
QSignalSpy cleanChangedSpy(&group, &QUndoGroup::cleanChanged);
QSignalSpy canUndoChangedSpy(&group, &QUndoGroup::canUndoChanged);
QSignalSpy undoTextChangedSpy(&group, &QUndoGroup::undoTextChanged);
QSignalSpy canRedoChangedSpy(&group, &QUndoGroup::canRedoChanged);
QSignalSpy redoTextChangedSpy(&group, &QUndoGroup::redoTextChanged);
QString str;
CHECK_STATE(0, // activeStack
true, // clean
false, // canUndo
"", // undoText
false, // canRedo
"", // redoText
false, // cleanChanged
false, // indexChanged
false, // undoChanged
false) // redoChanged
group.undo();
CHECK_STATE(0, // activeStack
true, // clean
false, // canUndo
"", // undoText
false, // canRedo
"", // redoText
false, // cleanChanged
false, // indexChanged
false, // undoChanged
false) // redoChanged
group.redo();
CHECK_STATE(0, // activeStack
true, // clean
false, // canUndo
"", // undoText
false, // canRedo
"", // redoText
false, // cleanChanged
false, // indexChanged
false, // undoChanged
false) // redoChanged
QUndoStack *stack1 = new QUndoStack(&group);
CHECK_STATE(0, // activeStack
true, // clean
false, // canUndo
"", // undoText
false, // canRedo
"", // redoText
false, // cleanChanged
false, // indexChanged
false, // undoChanged
false) // redoChanged
stack1->push(new AppendCommand(&str, "foo"));
CHECK_STATE(0, // activeStack
true, // clean
false, // canUndo
"", // undoText
false, // canRedo
"", // redoText
false, // cleanChanged
false, // indexChanged
false, // undoChanged
false) // redoChanged
stack1->setActive();
CHECK_STATE(stack1, // activeStack
false, // clean
true, // canUndo
"append", // undoText
false, // canRedo
"", // redoText
true, // cleanChanged
true, // indexChanged
true, // undoChanged
true) // redoChanged
stack1->push(new InsertCommand(&str, 0, "bar"));
CHECK_STATE(stack1, // activeStack
false, // clean
true, // canUndo
"insert", // undoText
false, // canRedo
"", // redoText
false, // cleanChanged
true, // indexChanged
true, // undoChanged
true) // redoChanged
stack1->undo();
CHECK_STATE(stack1, // activeStack
false, // clean
true, // canUndo
"append", // undoText
true, // canRedo
"insert", // redoText
false, // cleanChanged
true, // indexChanged
true, // undoChanged
true) // redoChanged
stack1->undo();
CHECK_STATE(stack1, // activeStack
true, // clean
false, // canUndo
"", // undoText
true, // canRedo
"append", // redoText
true, // cleanChanged
true, // indexChanged
true, // undoChanged
true) // redoChanged
stack1->undo();
CHECK_STATE(stack1, // activeStack
true, // clean
false, // canUndo
"", // undoText
true, // canRedo
"append", // redoText
false, // cleanChanged
false, // indexChanged
false, // undoChanged
false) // redoChanged
group.undo();
CHECK_STATE(stack1, // activeStack
true, // clean
false, // canUndo
"", // undoText
true, // canRedo
"append", // redoText
false, // cleanChanged
false, // indexChanged
false, // undoChanged
false) // redoChanged
group.redo();
CHECK_STATE(stack1, // activeStack
false, // clean
true, // canUndo
"append", // undoText
true, // canRedo
"insert", // redoText
true, // cleanChanged
true, // indexChanged
true, // undoChanged
true) // redoChanged
stack1->setActive(false);
CHECK_STATE(0, // activeStack
true, // clean
false, // canUndo
"", // undoText
false, // canRedo
"", // redoText
true, // cleanChanged
true, // indexChanged
true, // undoChanged
true) // redoChanged
QUndoStack *stack2 = new QUndoStack(&group);
CHECK_STATE(0, // activeStack
true, // clean
false, // canUndo
"", // undoText
false, // canRedo
"", // redoText
false, // cleanChanged
false, // indexChanged
false, // undoChanged
false) // redoChanged
stack2->setActive();
CHECK_STATE(stack2, // activeStack
true, // clean
false, // canUndo
"", // undoText
false, // canRedo
"", // redoText
true, // cleanChanged
true, // indexChanged
true, // undoChanged
true) // redoChanged
stack1->setActive();
CHECK_STATE(stack1, // activeStack
false, // clean
true, // canUndo
"append", // undoText
true, // canRedo
"insert", // redoText
true, // cleanChanged
true, // indexChanged
true, // undoChanged
true) // redoChanged
delete stack1;
CHECK_STATE(0, // activeStack
true, // clean
false, // canUndo
"", // undoText
false, // canRedo
"", // redoText
true, // cleanChanged
true, // indexChanged
true, // undoChanged
true) // redoChanged
}
void tst_QUndoGroup::addStackAndDie()
{
// Test that QUndoStack doesn't keep a reference to QUndoGroup after the
// group is deleted.
QUndoStack *stack = new QUndoStack;
QUndoGroup *group = new QUndoGroup;
group->addStack(stack);
delete group;
stack->setActive(true);
delete stack;
}
void tst_QUndoGroup::commandTextFormat()
{
#if !QT_CONFIG(process)
QSKIP("No QProcess available");
#else
QString binDir = QLibraryInfo::location(QLibraryInfo::BinariesPath);
if (QProcess::execute(binDir + "/lrelease -version") != 0)
QSKIP("lrelease is missing or broken");
const QString tsFile = QFINDTESTDATA("testdata/qundogroup.ts");
QVERIFY(!tsFile.isEmpty());
QFile::remove("qundogroup.qm"); // Avoid confusion by strays.
QVERIFY(!QProcess::execute(binDir + "/lrelease -silent " + tsFile + " -qm qundogroup.qm"));
QTranslator translator;
QVERIFY(translator.load("qundogroup.qm"));
QFile::remove("qundogroup.qm");
qApp->installTranslator(&translator);
QUndoGroup group;
const QScopedPointer<QAction> undo_action(group.createUndoAction(0));
const QScopedPointer<QAction> redo_action(group.createRedoAction(0));
QCOMPARE(undo_action->text(), QString("Undo-default-text"));
QCOMPARE(redo_action->text(), QString("Redo-default-text"));
QUndoStack stack(&group);
stack.setActive();
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
}
QTEST_MAIN(tst_QUndoGroup)
#include "tst_qundogroup.moc"