Commit 9a644d12 authored by Christian Stenger's avatar Christian Stenger Committed by Christian Stenger
Browse files

Support sorting of test tree

Additionally preparation of filtering has been done.
parent 0357b0e9
......@@ -19,6 +19,8 @@
#ifndef AUTOTESTCONSTANTS_H
#define AUTOTESTCONSTANTS_H
#include <QtGlobal>
namespace Autotest {
namespace Constants {
......@@ -27,6 +29,7 @@ const char MENU_ID[] = "AutoTest.Menu";
const char AUTOTEST_ID[] = "AutoTest.ATP";
const char AUTOTEST_CONTEXT[] = "Auto Tests";
const char TASK_INDEX[] = "AutoTest.Task.Index";
const char UNNAMED_QUICKTESTS[] = QT_TR_NOOP("<unnamed>");
} // namespace Autotest
} // namespace Constants
......
......@@ -16,6 +16,7 @@
**
****************************************************************************/
#include "autotestconstants.h"
#include "testcodeparser.h"
#include "testinfo.h"
#include "testtreeitem.h"
......@@ -385,51 +386,60 @@ void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr doc)
m_model->removeQuickTestSubtreeByFilePath(d->fileName());
m_quickDocMap.remove(d->fileName());
}
bool hadUnnamedTestsBefore;
TestTreeItem *ttItem = m_model->unnamedQuickTests();
if (!ttItem) {
hadUnnamedTestsBefore = false;
ttItem = new TestTreeItem(QString(), QString(), TestTreeItem::TEST_CLASS,
quickTestRootItem);
foreach (const QString &func, testFunctions.keys()) {
const TestCodeLocation location = testFunctions.value(func);
TestTreeItem *ttSub = new TestTreeItem(func, location.m_fileName,
TestTreeItem::TEST_FUNCTION, ttItem);
ttSub->setLine(location.m_line);
ttSub->setColumn(location.m_column);
ttSub->setMainFile(doc->fileName());
ttItem->appendChild(ttSub);
}
} else {
hadUnnamedTestsBefore = true;
TestTreeItem *unnamedQTItem = m_model->unnamedQuickTests();
if (unnamedQTItem) {
// remove unnamed quick tests that are already found for this qml file
m_model->removeUnnamedQuickTest(d->fileName());
foreach (const QString &func, testFunctions.keys()) {
const TestCodeLocation location = testFunctions.value(func);
TestTreeItem *ttSub = new TestTreeItem(func, location.m_fileName,
TestTreeItem::TEST_FUNCTION, ttItem);
ttSub->setLine(location.m_line);
ttSub->setColumn(location.m_column);
ttSub->setMainFile(doc->fileName());
ttItem->appendChild(ttSub);
if (m_model->removeUnnamedQuickTests(d->fileName())) {
// make sure m_quickDocMap does not have a inconsistent state now
TestInfo ti = m_quickDocMap[tr(Constants::UNNAMED_QUICKTESTS)];
QStringList tiFunctions = ti.testFunctions();
foreach (const QString &func, testFunctions.keys())
tiFunctions.removeOne(func);
ti.setTestFunctions(tiFunctions);
if (tiFunctions.size() == 0)
m_quickDocMap.remove(tr(Constants::UNNAMED_QUICKTESTS));
else
m_quickDocMap.insert(tr(Constants::UNNAMED_QUICKTESTS), ti);
}
// as removeUnnamedQuickTests() could delete this item itself try to get it again
unnamedQTItem = m_model->unnamedQuickTests();
}
TestInfo info = m_quickDocMap.contains(QLatin1String("<unnamed>"))
? m_quickDocMap[QLatin1String("<unnamed>")]
: TestInfo(QString(), QStringList(), 666);
QStringList originalFunctions(info.testFunctions());
// construct new/modified TestTreeItem
TestTreeItem *ttItem = new TestTreeItem(QString(), QString(),
TestTreeItem::TEST_CLASS,
quickTestRootItem);
if (unnamedQTItem) {
for (int i = 0, count = unnamedQTItem->childCount(); i < count; ++i) {
TestTreeItem *child = new TestTreeItem(*unnamedQTItem->child(i));
child->setParent(ttItem);
ttItem->appendChild(child);
}
}
foreach (const QString &func, testFunctions.keys()) {
if (!originalFunctions.contains(func))
originalFunctions.append(func);
const TestCodeLocation location = testFunctions.value(func);
TestTreeItem *ttSub = new TestTreeItem(func, location.m_fileName,
TestTreeItem::TEST_FUNCTION, ttItem);
ttSub->setLine(location.m_line);
ttSub->setColumn(location.m_column);
ttSub->setMainFile(doc->fileName());
ttItem->appendChild(ttSub);
}
TestInfo info = m_quickDocMap.contains(tr(Constants::UNNAMED_QUICKTESTS))
? m_quickDocMap[tr(Constants::UNNAMED_QUICKTESTS)]
: TestInfo(QString(), QStringList(), 666);
QStringList originalFunctions(info.testFunctions());
foreach (const QString &func, testFunctions.keys())
originalFunctions.append(func);
info.setTestFunctions(originalFunctions);
if (hadUnnamedTestsBefore)
m_model->modifyQuickTestSubtree(ttItem->row(), ttItem);
else
if (unnamedQTItem) {
m_model->modifyQuickTestSubtree(unnamedQTItem->row(), ttItem);
delete ttItem;
} else {
m_model->addQuickTest(ttItem);
m_quickDocMap.insert(QLatin1String("<unnamed>"), info);
}
m_quickDocMap.insert(tr(Constants::UNNAMED_QUICKTESTS), info);
continue;
} // end of handling test cases without name property
......@@ -468,15 +478,20 @@ void TestCodeParser::handleQtQuickTest(CPlusPlus::Document::Ptr doc)
delete ttItem;
} else {
// if it was formerly unnamed remove the respective items
if (m_quickDocMap.contains(QLatin1String("<unnamed>"))) {
m_model->removeUnnamedQuickTest(d->fileName());
TestInfo unnamedInfo = m_quickDocMap[QLatin1String("<unnamed>")];
QStringList functions = unnamedInfo.testFunctions();
foreach (const QString &func, testFunctions.keys())
if (functions.contains(func))
functions.removeOne(func);
unnamedInfo.setTestFunctions(functions);
m_quickDocMap.insert(QLatin1String("<unnamed>"), unnamedInfo);
if (m_quickDocMap.contains(tr(Constants::UNNAMED_QUICKTESTS))) {
if (m_model->removeUnnamedQuickTests(d->fileName())) {
// make sure m_quickDocMap does not have a inconsistent state now
TestInfo unnamedInfo = m_quickDocMap[tr(Constants::UNNAMED_QUICKTESTS)];
QStringList functions = unnamedInfo.testFunctions();
foreach (const QString &func, testFunctions.keys())
if (functions.contains(func))
functions.removeOne(func);
unnamedInfo.setTestFunctions(functions);
if (functions.size() == 0)
m_quickDocMap.remove(tr(Constants::UNNAMED_QUICKTESTS));
else
m_quickDocMap.insert(tr(Constants::UNNAMED_QUICKTESTS), unnamedInfo);
}
}
m_model->addQuickTest(ttItem);
......@@ -523,7 +538,7 @@ void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &doc)
&& snapshot.contains(m_quickDocMap[fileName].referencingFile())) {
checkDocumentForTestCode(snapshot.document(m_quickDocMap[fileName].referencingFile()));
}
if (!m_quickDocMap.contains(QLatin1String("<unnamed>")))
if (!m_quickDocMap.contains(tr(Constants::UNNAMED_QUICKTESTS)))
return;
// special case of having unnamed TestCases
......
......@@ -55,18 +55,53 @@ ResultType TestResult::resultFromString(const QString &resultString)
return UNKNOWN;
}
ResultType TestResult::toResultType(int rt)
{
switch(rt) {
case PASS:
return PASS;
case FAIL:
return FAIL;
case EXPECTED_FAIL:
return EXPECTED_FAIL;
case UNEXPECTED_PASS:
return UNEXPECTED_PASS;
case SKIP:
return SKIP;
case MESSAGE_DEBUG:
return MESSAGE_DEBUG;
case MESSAGE_WARN:
return MESSAGE_WARN;
case MESSAGE_FATAL:
return MESSAGE_FATAL;
case MESSAGE_INTERNAL:
return MESSAGE_INTERNAL;
default:
return UNKNOWN;
}
}
QString TestResult::resultToString(const ResultType type)
{
switch(type) {
case PASS: return QLatin1String("PASS");
case FAIL: return QLatin1String("FAIL");
case EXPECTED_FAIL: return QLatin1String("XFAIL");
case UNEXPECTED_PASS: return QLatin1String("XPASS");
case SKIP: return QLatin1String("SKIP");
case MESSAGE_DEBUG: return QLatin1String("DEBUG");
case MESSAGE_WARN: return QLatin1String("WARN");
case MESSAGE_FATAL: return QLatin1String("FATAL");
case MESSAGE_INTERNAL: return QString();
case PASS:
return QLatin1String("PASS");
case FAIL:
return QLatin1String("FAIL");
case EXPECTED_FAIL:
return QLatin1String("XFAIL");
case UNEXPECTED_PASS:
return QLatin1String("XPASS");
case SKIP:
return QLatin1String("SKIP");
case MESSAGE_DEBUG:
return QLatin1String("DEBUG");
case MESSAGE_WARN:
return QLatin1String("WARN");
case MESSAGE_FATAL:
return QLatin1String("FATAL");
case MESSAGE_INTERNAL:
return QString();
default:
return QLatin1String("UNKNOWN");
}
......@@ -75,15 +110,24 @@ QString TestResult::resultToString(const ResultType type)
QColor TestResult::colorForType(const ResultType type)
{
switch(type) {
case PASS: return QColor("#009900");
case FAIL: return QColor("#a00000");
case EXPECTED_FAIL: return QColor("#00ff00");
case UNEXPECTED_PASS: return QColor("#ff0000");
case SKIP: return QColor("#787878");
case MESSAGE_DEBUG: return QColor("#329696");
case MESSAGE_WARN: return QColor("#d0bb00");
case MESSAGE_FATAL: return QColor("#640000");
case MESSAGE_INTERNAL: return QColor("transparent");
case PASS:
return QColor("#009900");
case FAIL:
return QColor("#a00000");
case EXPECTED_FAIL:
return QColor("#00ff00");
case UNEXPECTED_PASS:
return QColor("#ff0000");
case SKIP:
return QColor("#787878");
case MESSAGE_DEBUG:
return QColor("#329696");
case MESSAGE_WARN:
return QColor("#d0bb00");
case MESSAGE_FATAL:
return QColor("#640000");
case MESSAGE_INTERNAL:
return QColor("transparent");
default:
return QColor("#000000");
}
......
......@@ -57,6 +57,7 @@ public:
void setLine(int line) { m_line = line; }
static ResultType resultFromString(const QString &resultString);
static ResultType toResultType(int rt);
static QString resultToString(const ResultType type);
static QColor colorForType(const ResultType type);
......
......@@ -322,7 +322,7 @@ void TestResultsPane::updateFilterMenu()
foreach (QAction *action, m_filterMenu->actions()) {
if (action->isCheckable())
action->setEnabled(m_model->hasResultType(
static_cast<ResultType>(action->data().value<int>())));
TestResult::toResultType(action->data().value<int>())));
}
}
......@@ -337,7 +337,7 @@ void TestResultsPane::enableAllFilter()
void TestResultsPane::filterMenuTriggered(QAction *action)
{
m_filterModel->toggleTestResultType(static_cast<ResultType>(action->data().value<int>()));
m_filterModel->toggleTestResultType(TestResult::toResultType(action->data().value<int>()));
navigateStateChanged();
}
......
......@@ -36,6 +36,23 @@ TestTreeItem::~TestTreeItem()
removeChildren();
}
TestTreeItem::TestTreeItem(const TestTreeItem &other)
: m_name(other.m_name),
m_filePath(other.m_filePath),
m_checked(other.m_checked),
m_type(other.m_type),
m_line(other.m_line),
m_column(other.m_column),
m_mainFile(other.m_mainFile),
m_parent(other.m_parent)
{
foreach (const TestTreeItem *child, other.m_children) {
TestTreeItem *reparentedChild = new TestTreeItem(*child);
reparentedChild->m_parent = this;
m_children.append(reparentedChild);
}
}
TestTreeItem *TestTreeItem::child(int row)
{
return m_children.at(row);
......
......@@ -35,6 +35,7 @@ public:
TestTreeItem(const QString &name, const QString &filePath, Type type, TestTreeItem *parent = 0);
virtual ~TestTreeItem();
TestTreeItem(const TestTreeItem& other);
TestTreeItem *child(int row);
TestTreeItem *parent() const;
......@@ -58,6 +59,7 @@ public:
void setChecked(const Qt::CheckState checked);
Qt::CheckState checked() const { return m_checked; }
Type type() const { return m_type; }
void setParent(TestTreeItem *parent) { m_parent = parent; }
private:
void revalidateCheckState();
......
......@@ -16,6 +16,7 @@
**
****************************************************************************/
#include "autotestconstants.h"
#include "testcodeparser.h"
#include "testtreeitem.h"
#include "testtreemodel.h"
......@@ -152,7 +153,7 @@ QVariant TestTreeModel::data(const QModelIndex &index, int role) const
return QString(item->name() + tr(" (none)"));
} else {
if (item->name().isEmpty())
return tr("<unnamed>");
return tr(Constants::UNNAMED_QUICKTESTS);
return item->name();
}
......@@ -535,19 +536,24 @@ void TestTreeModel::removeAllQuickTests()
emit testTreeModelChanged();
}
void TestTreeModel::removeUnnamedQuickTest(const QString &filePath)
bool TestTreeModel::removeUnnamedQuickTests(const QString &filePath)
{
TestTreeItem *unnamedQT = unnamedQuickTests();
if (!unnamedQT)
return;
return false;
bool removed = false;
const QModelIndex unnamedQTIndex = index(1, 0).child(unnamedQT->row(), 0);
for (int childRow = unnamedQT->childCount() - 1; childRow >= 0; --childRow) {
const TestTreeItem *child = unnamedQT->child(childRow);
if (filePath == child->filePath())
removeRow(childRow, unnamedQTIndex);
removed |= removeRow(childRow, unnamedQTIndex);
}
if (unnamedQT->childCount() == 0)
removeRow(unnamedQT->row(), unnamedQTIndex.parent());
emit testTreeModelChanged();
return removed;
}
void TestTreeModel::modifyTestSubtree(QModelIndex &toBeModifiedIndex, TestTreeItem *newItem)
......@@ -566,6 +572,7 @@ void TestTreeModel::modifyTestSubtree(QModelIndex &toBeModifiedIndex, TestTreeIt
const int newChildCount = newItem->childCount();
// for keeping the CheckState on modifications
// TODO might still fail for duplicate entries (e.g. unnamed Quick Tests)
QHash<QString, Qt::CheckState> originalItems;
for (int row = 0; row < childCount; ++row) {
const TestTreeItem *child = toBeModifiedItem->child(row);
......@@ -594,9 +601,8 @@ void TestTreeModel::modifyTestSubtree(QModelIndex &toBeModifiedIndex, TestTreeIt
if (childCount < newChildCount) { // add aditional items
for (int row = childCount; row < newChildCount; ++row) {
TestTreeItem *newChild = newItem->child(row);
TestTreeItem *toBeAdded = new TestTreeItem(newChild->name(), newChild->filePath(),
newChild->type(), toBeModifiedItem);
toBeAdded->setLine(newChild->line());
TestTreeItem *toBeAdded = new TestTreeItem(*newChild);
toBeAdded->setParent(toBeModifiedItem);
beginInsertRows(toBeModifiedIndex, row, row);
toBeModifiedItem->appendChild(toBeAdded);
endInsertRows();
......@@ -628,5 +634,97 @@ void TestTreeModel::modifyTestSubtree(QModelIndex &toBeModifiedIndex, TestTreeIt
emit testTreeModelChanged();
}
/***************************** Sort/Filter Model **********************************/
TestTreeSortFilterModel::TestTreeSortFilterModel(TestTreeModel *sourceModel, QObject *parent)
: QSortFilterProxyModel(parent),
m_sourceModel(sourceModel),
m_sortMode(Alphabetically),
m_filterMode(Basic)
{
setSourceModel(sourceModel);
}
void TestTreeSortFilterModel::setSortMode(SortMode sortMode)
{
m_sortMode = sortMode;
invalidate();
}
void TestTreeSortFilterModel::setFilterMode(FilterMode filterMode)
{
m_filterMode = filterMode;
invalidateFilter();
}
void TestTreeSortFilterModel::toggleFilter(FilterMode filterMode)
{
m_filterMode = toFilterMode(m_filterMode ^ filterMode);
invalidateFilter();
}
TestTreeSortFilterModel::FilterMode TestTreeSortFilterModel::toFilterMode(int f)
{
switch (f) {
case TestTreeSortFilterModel::ShowInitAndCleanup:
return TestTreeSortFilterModel::ShowInitAndCleanup;
case TestTreeSortFilterModel::ShowTestData:
return TestTreeSortFilterModel::ShowTestData;
case TestTreeSortFilterModel::ShowAll:
return TestTreeSortFilterModel::ShowAll;
default:
return TestTreeSortFilterModel::Basic;
}
}
bool TestTreeSortFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
// root items keep the intended order: 1st Auto Tests, 2nd Quick Tests
const TestTreeItem *leftItem = static_cast<TestTreeItem *>(left.internalPointer());
if (leftItem->type() == TestTreeItem::ROOT)
return left.row() > right.row();
const QString leftVal = m_sourceModel->data(left).toString();
const QString rightVal = m_sourceModel->data(right).toString();
// unnamed Quick Tests will always be listed first
if (leftVal == tr(Constants::UNNAMED_QUICKTESTS))
return false;
if (rightVal == tr(Constants::UNNAMED_QUICKTESTS))
return true;
switch (m_sortMode) {
case Alphabetically:
return leftVal > rightVal;
case Naturally: {
const TextEditor::TextEditorWidget::Link leftLink =
m_sourceModel->data(left, LinkRole).value<TextEditor::TextEditorWidget::Link>();
const TextEditor::TextEditorWidget::Link rightLink =
m_sourceModel->data(right, LinkRole).value<TextEditor::TextEditorWidget::Link>();
if (leftLink.targetFileName == rightLink.targetFileName) {
return leftLink.targetLine == rightLink.targetLine
? leftLink.targetColumn > rightLink.targetColumn
: leftLink.targetLine > rightLink.targetLine;
} else {
return leftLink.targetFileName > rightLink.targetFileName;
}
}
default:
return true;
}
}
bool TestTreeSortFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
// TODO add filtering capabilities
QModelIndex index = m_sourceModel->index(sourceRow, 0,sourceParent);
if (!index.isValid())
return false;
return true;
}
} // namespace Internal
} // namespace Autotest
......@@ -24,6 +24,7 @@
#include <cplusplus/CppDocument.h>
#include <QAbstractItemModel>
#include <QSortFilterProxyModel>
namespace {
enum ItemRole {
......@@ -71,7 +72,7 @@ public:
void removeQuickTestSubtreeByFilePath(const QString &file);
void addQuickTest(TestTreeItem *newItem);
void removeAllQuickTests();
void removeUnnamedQuickTest(const QString &filePath);
bool removeUnnamedQuickTests(const QString &filePath);
signals:
void testTreeModelChanged();
......@@ -89,6 +90,39 @@ private:
};
class TestTreeSortFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
enum SortMode {
Alphabetically,
Naturally
};
enum FilterMode {
Basic,
ShowInitAndCleanup = 0x01,
ShowTestData = 0x02,
ShowAll = ShowInitAndCleanup | ShowTestData
};
TestTreeSortFilterModel(TestTreeModel *sourceModel, QObject *parent = 0);
void setSortMode(SortMode sortMode);
void setFilterMode(FilterMode filterMode);
void toggleFilter(FilterMode filterMode);
static FilterMode toFilterMode(int f);
protected:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
private:
TestTreeModel *m_sourceModel;
SortMode m_sortMode;
FilterMode m_filterMode;
};
} // namespace Internal
} // namespace Autotest
......
......@@ -23,6 +23,7 @@
#include "testtreemodel.h"
#include "testtreeview.h"
#include <coreplugin/coreconstants.h>
#include <coreplugin/icore.h>
#include <coreplugin/find/itemviewfind.h>
......@@ -47,8 +48,11 @@ TestTreeViewWidget::TestTreeViewWidget(QWidget *parent) :
{
setWindowTitle(tr("Tests"));
m_model = TestTreeModel::instance();
m_sortFilterModel = new TestTreeSortFilterModel(m_model, m_model);
m_sortFilterModel->setDynamicSortFilter(true);
m_view = new TestTreeView(this);
m_view->setModel(m_model);
m_view->setModel(m_sortFilterModel);
m_view->setSortingEnabled(true);
QVBoxLayout *layout = new QVBoxLayout;
layout->setMargin(0);
......@@ -116,10 +120,21 @@ QList<QToolButton *> TestTreeViewWidget::createToolButtons()
{
QList<QToolButton *> list;
m_filterButton = new QToolButton(m_view);
m_filterButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_FILTER)));
m_filterButton->setToolTip(tr("Filter Test Tree"));
m_filterButton->setProperty("noArrow", true);
m_filterButton->setAutoRaise(true);
m_filterButton->setPopupMode(QToolButton::InstantPopup);
m_filterMenu = new QMenu(m_filterButton);
initializeFilterMenu();
connect(m_filterMenu, &QMenu::triggered, this, &TestTreeViewWidget::onFilterMenuTriggered);
m_filterButton->setMenu(m_filterMenu);
m_sortAlphabetically = true;
m_sort = new QToolButton(this);
m_sort->setIcon((QIcon(QLatin1String(":/images/leafsort.png"))));
m_sort->setToolTip(tr("Sort Naturally (not implemented yet)"));
m_sort->setToolTip(tr("Sort Naturally"));
QToolButton *expand = new QToolButton(this);
expand->setIcon(QIcon(QLatin1String(":/images/expand.png")));
......@@ -133,7 +148,7 @@ QList<QToolButton *> TestTreeViewWidget::createToolButtons()
connect(collapse, &QToolButton::clicked, m_view, &TestTreeView::collapseAll);
connect(m_sort, &QToolButton::clicked, this, &TestTreeViewWidget::onSortClicked);
list << m_sort << expand << collapse;
list << m_filterButton << m_sort << expand << collapse;
return list;
}
......@@ -167,14 +182,37 @@ void TestTreeViewWidget::onSortClicked()
if (m_sortAlphabetically) {
m_sort->setIcon((QIcon(QLatin1String(":/images/sort.png"))));
m_sort->setToolTip(tr("Sort Alphabetically"));
m_sortFilterModel->setSortMode(TestTreeSortFilterModel::Naturally);
} else {
m_sort->setIcon((QIcon(QLatin1String(":/images/leafsort.png"))));
m_sort->setToolTip(tr("Sort Naturally (not implemented yet)"));