tests/auto/qcontactmanager/tst_qcontactmanager.cpp
changeset 0 876b1a06bc25
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/auto/qcontactmanager/tst_qcontactmanager.cpp	Wed Aug 25 15:49:42 2010 +0300
@@ -0,0 +1,3325 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Mobility Components.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+
+#include "qtcontacts.h"
+#include "qcontactchangeset.h"
+#include "qcontactmanagerdataholder.h"
+
+QTM_USE_NAMESPACE
+// Eventually these will make it into qtestcase.h
+// but we might need to tweak the timeout values here.
+#ifndef QTRY_COMPARE
+#define QTRY_COMPARE(__expr, __expected) \
+    do { \
+        const int __step = 50; \
+        const int __timeout = 5000; \
+        if ((__expr) != (__expected)) { \
+            QTest::qWait(0); \
+        } \
+        for (int __i = 0; __i < __timeout && ((__expr) != (__expected)); __i+=__step) { \
+            QTest::qWait(__step); \
+        } \
+        QCOMPARE(__expr, __expected); \
+    } while(0)
+#endif
+
+#ifndef QTRY_VERIFY
+#define QTRY_VERIFY(__expr) \
+        do { \
+        const int __step = 50; \
+        const int __timeout = 5000; \
+        if (!(__expr)) { \
+            QTest::qWait(0); \
+        } \
+        for (int __i = 0; __i < __timeout && !(__expr); __i+=__step) { \
+            QTest::qWait(__step); \
+        } \
+        QVERIFY(__expr); \
+    } while(0)
+#endif
+
+
+#define QTRY_WAIT(code, __expr) \
+        do { \
+        const int __step = 50; \
+        const int __timeout = 5000; \
+        if (!(__expr)) { \
+            QTest::qWait(0); \
+        } \
+        for (int __i = 0; __i < __timeout && !(__expr); __i+=__step) { \
+            do { code } while(0); \
+            QTest::qWait(__step); \
+        } \
+    } while(0)
+
+#define QCONTACTMANAGER_REMOVE_VERSIONS_FROM_URI(params)  params.remove(QString::fromAscii(QTCONTACTS_VERSION_NAME)); \
+                                                          params.remove(QString::fromAscii(QTCONTACTS_IMPLEMENTATION_VERSION_NAME))
+
+//TESTED_CLASS=
+//TESTED_FILES=
+
+// to get QFETCH to work with the template expression...
+typedef QMap<QString,QString> tst_QContactManager_QStringMap;
+Q_DECLARE_METATYPE(tst_QContactManager_QStringMap)
+Q_DECLARE_METATYPE(QList<QContactLocalId>)
+
+/* A class that no backend can support */
+class UnsupportedMetatype {
+    int foo;
+};
+Q_DECLARE_METATYPE(UnsupportedMetatype)
+Q_DECLARE_METATYPE(QContact)
+Q_DECLARE_METATYPE(QContactManager::Error)
+
+class tst_QContactManager : public QObject
+{
+Q_OBJECT
+
+public:
+    tst_QContactManager();
+    virtual ~tst_QContactManager();
+
+private:
+    void dumpContactDifferences(const QContact& a, const QContact& b);
+    void dumpContact(const QContact &c);
+    void dumpContacts(QContactManager *cm);
+    bool isSuperset(const QContact& ca, const QContact& cb);
+    QList<QContactDetail> removeAllDefaultDetails(const QList<QContactDetail>& details);
+    void addManagers(); // add standard managers to the data
+    QContact createContact(QContactDetailDefinition nameDef, QString firstName, QString lastName, QString phoneNumber);
+    void saveContactName(QContact *contact, QContactDetailDefinition nameDef, QContactName *contactName, const QString &name) const;
+
+    QScopedPointer<QContactManagerDataHolder> managerDataHolder;
+
+public slots:
+    void initTestCase();
+    void cleanupTestCase();
+private slots:
+
+    void doDump();
+    void doDump_data() {addManagers();}
+
+    void doDumpSchema();
+    void doDumpSchema_data() {addManagers();}
+
+    /* Special test with special data */
+    void uriParsing();
+    void nameSynthesis();
+    void compatibleContact();
+
+    /* Tests that are run on all managers */
+    void metadata();
+    void nullIdOperations();
+    void add();
+    void update();
+    void remove();
+    void batch();
+    void signalEmission();
+    void detailDefinitions();
+    void displayName();
+    void actionPreferences();
+    void selfContactId();
+    void detailOrders();
+    void relationships();
+    void contactType();
+
+    /* Tests that take no data */
+    void contactValidation();
+    void errorStayingPut();
+    void ctors();
+    void invalidManager();
+    void memoryManager();
+    void changeSet();
+    void fetchHint();
+
+    /* Special test with special data */
+    void uriParsing_data();
+    void nameSynthesis_data();
+    void compatibleContact_data();
+    /* Tests that are run on all managers */
+    void metadata_data() {addManagers();}
+    void nullIdOperations_data() {addManagers();}
+    void add_data() {addManagers();}
+    void update_data() {addManagers();}
+    void remove_data() {addManagers();}
+    void batch_data() {addManagers();}
+    void signalEmission_data() {addManagers();}
+    void detailDefinitions_data() {addManagers();}
+    void displayName_data() {addManagers();}
+    void actionPreferences_data() {addManagers();}
+    void selfContactId_data() {addManagers();}
+    void detailOrders_data() {addManagers();}
+    void relationships_data() {addManagers();}
+    void contactType_data() {addManagers();}
+};
+
+tst_QContactManager::tst_QContactManager()
+{
+}
+
+tst_QContactManager::~tst_QContactManager()
+{
+}
+
+void tst_QContactManager::initTestCase()
+{
+    managerDataHolder.reset(new QContactManagerDataHolder());
+
+    /* Make sure these other test plugins are NOT loaded by default */
+    // These are now removed from the list of managers in addManagers()
+    //QVERIFY(!QContactManager::availableManagers().contains("testdummy"));
+    //QVERIFY(!QContactManager::availableManagers().contains("teststaticdummy"));
+    //QVERIFY(!QContactManager::availableManagers().contains("maliciousplugin"));
+}
+
+void tst_QContactManager::cleanupTestCase()
+{
+    managerDataHolder.reset(0);
+}
+
+void tst_QContactManager::dumpContactDifferences(const QContact& ca, const QContact& cb)
+{
+    // Try to narrow down the differences
+    QContact a(ca);
+    QContact b(cb);
+
+    QContactName n1 = a.detail(QContactName::DefinitionName);
+    QContactName n2 = b.detail(QContactName::DefinitionName);
+
+    // Check the name components in more detail
+    QCOMPARE(n1.firstName(), n2.firstName());
+    QCOMPARE(n1.middleName(), n2.middleName());
+    QCOMPARE(n1.lastName(), n2.lastName());
+    QCOMPARE(n1.prefix(), n2.prefix());
+    QCOMPARE(n1.suffix(), n2.suffix());
+    QCOMPARE(n1.customLabel(), n2.customLabel());
+
+    // Check the display label
+    QCOMPARE(a.displayLabel(), b.displayLabel());
+
+    // Now look at the rest
+    QList<QContactDetail> aDetails = a.details();
+    QList<QContactDetail> bDetails = b.details();
+
+    // They can be in any order, so loop
+    // First remove any matches
+    foreach(QContactDetail d, aDetails) {
+        foreach(QContactDetail d2, bDetails) {
+            if(d == d2) {
+                a.removeDetail(&d);
+                b.removeDetail(&d2);
+                break;
+            }
+        }
+    }
+
+    // Now dump the extra details that were unmatched in A (note that DisplayLabel and Type are always present).
+    aDetails = a.details();
+    bDetails = b.details();
+    foreach(QContactDetail d, aDetails) {
+        if (d.definitionName() != QContactDisplayLabel::DefinitionName && d.definitionName() != QContactType::DefinitionName)
+            qDebug() << "A contact had extra detail:" << d.definitionName() << d.variantValues();
+    }
+    // and same for B
+    foreach(QContactDetail d, bDetails) {
+        if (d.definitionName() != QContactDisplayLabel::DefinitionName && d.definitionName() != QContactType::DefinitionName)
+            qDebug() << "B contact had extra detail:" << d.definitionName() << d.variantValues();
+    }
+
+    // now test specifically the display label and the type
+    if (a.displayLabel() != b.displayLabel()) {
+        qDebug() << "A contact display label =" << a.displayLabel();
+        qDebug() << "B contact display label =" << b.displayLabel();
+    }
+    if (a.type() != b.type()) {
+        qDebug() << "A contact type =" << a.type();
+        qDebug() << "B contact type =" << b.type();
+    }
+}
+
+bool tst_QContactManager::isSuperset(const QContact& ca, const QContact& cb)
+{
+    // returns true if contact ca is a superset of contact cb
+    // we use this test instead of equality because dynamic information
+    // such as presence/location, and synthesised information such as
+    // display label and (possibly) type, may differ between a contact
+    // in memory and the contact in the managed store.
+
+    QContact a(ca);
+    QContact b(cb);
+    QList<QContactDetail> aDetails = a.details();
+    QList<QContactDetail> bDetails = b.details();
+
+    // They can be in any order, so loop
+    // First remove any matches
+    foreach(QContactDetail d, aDetails) {
+        foreach(QContactDetail d2, bDetails) {
+            if(d == d2) {
+                a.removeDetail(&d);
+                b.removeDetail(&d2);
+                break;
+            }
+        }
+    }
+
+    // Second remove any superset matches (eg, backend adds a field)
+    aDetails = a.details();
+    bDetails = b.details();
+    foreach (QContactDetail d, aDetails) {
+        foreach (QContactDetail d2, bDetails) {
+            if (d.definitionName() == d2.definitionName()) {
+                bool canRemove = true;
+                QMap<QString, QVariant> d2map = d2.variantValues();
+                foreach (QString key, d2map.keys()) {
+                    if (d.value(key) != d2.value(key)) {
+                        // d can have _more_ keys than d2,
+                        // but not _less_; and it cannot
+                        // change the value.
+                        canRemove = false;
+                    }
+                }
+
+                if (canRemove) {
+                    // if we get to here, we can remove the details.
+                    a.removeDetail(&d);
+                    b.removeDetail(&d2);
+                    break;
+                }
+            }
+        }
+    }
+
+    // check for contact type updates
+    if (!a.type().isEmpty())
+        if (!b.type().isEmpty())
+            if (a.type() != b.type())
+                return false; // nonempty type is different.
+
+    // Now check to see if b has any details remaining; if so, a is not a superset.
+    // Note that the DisplayLabel and Type can never be removed.
+    if (b.details().size() > 2
+            || (b.details().size() == 2 && (b.details().value(0).definitionName() != QContactDisplayLabel::DefinitionName
+                                            || b.details().value(1).definitionName() != QContactType::DefinitionName)))
+        return false;
+    return true;
+}
+
+void tst_QContactManager::dumpContact(const QContact& contact)
+{
+    QContactManager m;
+    qDebug() << "Contact: " << contact.id().localId() << "(" << m.synthesizedContactDisplayLabel(contact) << ")";
+    QList<QContactDetail> details = contact.details();
+    foreach(QContactDetail d, details) {
+        qDebug() << "  " << d.definitionName() << ":";
+        qDebug() << "    Vals:" << d.variantValues();
+    }
+}
+
+void tst_QContactManager::dumpContacts(QContactManager *cm)
+{
+    QList<QContactLocalId> ids = cm->contactIds();
+
+    qDebug() << "There are" << ids.count() << "contacts in" << cm->managerUri();
+
+    foreach(QContactLocalId id, ids) {
+        QContact c = cm->contact(id);
+        dumpContact(c);
+    }
+}
+
+void tst_QContactManager::uriParsing_data()
+{
+    QTest::addColumn<QString>("uri");
+    QTest::addColumn<bool>("good"); // is this a good uri or not
+    QTest::addColumn<QString>("manager");
+    QTest::addColumn<QMap<QString, QString> >("parameters");
+
+    QMap<QString, QString> inparameters;
+    inparameters.insert("foo", "bar");
+    inparameters.insert("bazflag", QString());
+    inparameters.insert("bar", "glob");
+
+    QMap<QString, QString> inparameters2;
+    inparameters2.insert("this has spaces", QString());
+    inparameters2.insert("and& an", " &amp;");
+    inparameters2.insert("and an ", "=quals");
+
+    QTest::newRow("built") << QContactManager::buildUri("manager", inparameters) << true << "manager" << inparameters;
+    QTest::newRow("built with escaped parameters") << QContactManager::buildUri("manager", inparameters2) << true << "manager" << inparameters2;
+    QTest::newRow("no scheme") << "this should not split" << false << QString() << tst_QContactManager_QStringMap();
+    QTest::newRow("wrong scheme") << "invalidscheme:foo bar" << false << QString() << tst_QContactManager_QStringMap();
+    QTest::newRow("right scheme, no colon") << "qtcontacts" << false << QString() << tst_QContactManager_QStringMap();
+    QTest::newRow("no manager, colon, no params") << "qtcontacts::" << false  << "manager" << tst_QContactManager_QStringMap();
+    QTest::newRow("yes manager, no colon, no params") << "qtcontacts:manager" << true << "manager" << tst_QContactManager_QStringMap();
+    QTest::newRow("yes manager, yes colon, no params") << "qtcontacts:manager:" << true << "manager"<< tst_QContactManager_QStringMap();
+    QTest::newRow("yes params") << "qtcontacts:manager:foo=bar&bazflag=&bar=glob" << true << "manager" << inparameters;
+    QTest::newRow("yes params but misformed") << "qtcontacts:manager:foo=bar&=gloo&bar=glob" << false << "manager" << inparameters;
+    QTest::newRow("yes params but misformed 2") << "qtcontacts:manager:=&=gloo&bar=glob" << false << "manager" << inparameters;
+    QTest::newRow("yes params but misformed 3") << "qtcontacts:manager:==" << false << "manager" << inparameters;
+    QTest::newRow("yes params but misformed 4") << "qtcontacts:manager:&&" << false << "manager" << inparameters;
+    QTest::newRow("yes params but misformed 5") << "qtcontacts:manager:&goo=bar" << false << "manager" << inparameters;
+    QTest::newRow("yes params but misformed 6") << "qtcontacts:manager:goo&bar" << false << "manager" << inparameters;
+    QTest::newRow("yes params but misformed 7") << "qtcontacts:manager:goo&bar&gob" << false << "manager" << inparameters;
+    QTest::newRow("yes params but misformed 8") << "qtcontacts:manager:==&&==&goo=bar" << false << "manager" << inparameters;
+    QTest::newRow("yes params but misformed 9") << "qtcontacts:manager:foo=bar=baz" << false << "manager" << inparameters;
+    QTest::newRow("yes params but misformed 10") << "qtcontacts:manager:foo=bar=baz=glob" << false << "manager" << inparameters;
+    QTest::newRow("no manager but yes params") << "qtcontacts::foo=bar&bazflag=&bar=glob" << false << QString() << inparameters;
+    QTest::newRow("no manager or params") << "qtcontacts::" << false << QString() << inparameters;
+    QTest::newRow("no manager or params or colon") << "qtcontacts:" << false << QString() << inparameters;
+}
+
+void tst_QContactManager::addManagers()
+{
+    QTest::addColumn<QString>("uri");
+
+    QStringList managers = QContactManager::availableManagers();
+
+    /* Known one that will not pass */
+    managers.removeAll("invalid");
+    managers.removeAll("testdummy");
+    managers.removeAll("teststaticdummy");
+    managers.removeAll("maliciousplugin");
+
+    foreach(QString mgr, managers) {
+        QMap<QString, QString> params;
+        QTest::newRow(QString("mgr='%1'").arg(mgr).toLatin1().constData()) << QContactManager::buildUri(mgr, params);
+        if (mgr == "memory") {
+            params.insert("id", "tst_QContactManager");
+            QTest::newRow(QString("mgr='%1', params").arg(mgr).toLatin1().constData()) << QContactManager::buildUri(mgr, params);
+        }
+    }
+}
+
+/*
+ * Helper method for creating a QContact instance with name and phone number
+ * details. Name is generated according to the detail definition assuming that
+ * either first and last name or custom label is supported.
+ */
+QContact tst_QContactManager::createContact(
+    QContactDetailDefinition nameDef,
+    QString firstName,
+    QString lastName,
+    QString phoneNumber)
+{
+    QContact contact;
+
+    if(!firstName.isEmpty() || !lastName.isEmpty()) {
+        QContactName n;
+
+        if(nameDef.fields().contains(QContactName::FieldFirstName)
+            && nameDef.fields().contains(QContactName::FieldFirstName)) {
+            n.setFirstName(firstName);
+            n.setLastName(lastName);
+        } else if(nameDef.fields().contains(QContactName::FieldCustomLabel)) {
+            n.setCustomLabel(firstName + " " + lastName);
+        } else {
+            // assume that either first and last name or custom label is supported
+            QTest::qWarn("Neither custom label nor first name/last name supported!");
+            return QContact();
+        }
+        contact.saveDetail(&n);
+    }
+
+    if (!phoneNumber.isEmpty()) {
+        QContactPhoneNumber ph;
+        ph.setNumber(phoneNumber);
+        contact.saveDetail(&ph);
+    }
+
+    return contact;
+}
+
+void tst_QContactManager::saveContactName(QContact *contact, QContactDetailDefinition nameDef, QContactName *contactName, const QString &name) const
+{
+    // check which name fields are supported in the following order:
+    // 1. custom label, 2. first name, 3. last name
+    if(nameDef.fields().contains(QContactName::FieldCustomLabel)) {
+        contactName->setCustomLabel(name);
+    } else if(nameDef.fields().contains(QContactName::FieldFirstName)) {
+        contactName->setFirstName(name);
+    } else if(nameDef.fields().contains(QContactName::FieldLastName)) {
+        contactName->setLastName(name);
+    } else {
+        // Assume that at least one of the above name fields is supported by the backend
+        QVERIFY(false);
+    }
+    contact->saveDetail(contactName);
+}
+
+void tst_QContactManager::metadata()
+{
+    // ensure that the backend is publishing its metadata (name / parameters / uri) correctly
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> cm(new QContactManager("memory"));
+    QVERIFY(QContactManager::buildUri(cm->managerName(), cm->managerParameters()) == cm->managerUri());
+}
+
+
+void tst_QContactManager::nullIdOperations()
+{
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> cm(new QContactManager("memory"));
+    QVERIFY(!cm->removeContact(QContactLocalId()));
+    QVERIFY(cm->error() == QContactManager::DoesNotExistError);
+
+
+    QContact c = cm->contact(QContactLocalId());
+    QVERIFY(c.id() == QContactId());
+    QVERIFY(c.isEmpty());
+    QVERIFY(cm->error() == QContactManager::DoesNotExistError);
+}
+
+void tst_QContactManager::uriParsing()
+{
+    QFETCH(QString, uri);
+    QFETCH(bool, good);
+    QFETCH(QString, manager);
+    QFETCH(tst_QContactManager_QStringMap, parameters);
+
+    QString outmanager;
+    QMap<QString, QString> outparameters;
+
+    if (good) {
+        /* Good split */
+        /* Test splitting */
+        QVERIFY(QContactManager::parseUri(uri, 0, 0)); // no out parms
+
+        // 1 out param
+        QVERIFY(QContactManager::parseUri(uri, &outmanager, 0));
+        QCOMPARE(manager, outmanager);
+        QVERIFY(QContactManager::parseUri(uri, 0, &outparameters));
+
+        QCONTACTMANAGER_REMOVE_VERSIONS_FROM_URI(outparameters);
+
+        QCOMPARE(parameters, outparameters);
+
+        outmanager.clear();
+        outparameters.clear();
+        QVERIFY(QContactManager::parseUri(uri, &outmanager, &outparameters));
+
+        QCONTACTMANAGER_REMOVE_VERSIONS_FROM_URI(outparameters);
+
+        QCOMPARE(manager, outmanager);
+        QCOMPARE(parameters, outparameters);
+    } else {
+        /* bad splitting */
+        outmanager.clear();
+        outparameters.clear();
+        QVERIFY(QContactManager::parseUri(uri, 0, 0) == false);
+        QVERIFY(QContactManager::parseUri(uri, &outmanager, 0) == false);
+        QVERIFY(outmanager.isEmpty());
+        QVERIFY(QContactManager::parseUri(uri, 0, &outparameters) == false);
+        QCONTACTMANAGER_REMOVE_VERSIONS_FROM_URI(outparameters);
+        QVERIFY(outparameters.isEmpty());
+
+        /* make sure the in parameters don't change with a bad split */
+        outmanager = manager;
+        outparameters = parameters;
+        QVERIFY(QContactManager::parseUri(uri, &outmanager, 0) == false);
+        QCOMPARE(manager, outmanager);
+        QVERIFY(QContactManager::parseUri(uri, 0, &outparameters) == false);
+        QCONTACTMANAGER_REMOVE_VERSIONS_FROM_URI(outparameters);
+        QCOMPARE(parameters, outparameters);
+    }
+}
+
+void tst_QContactManager::ctors()
+{
+    /* test the different ctors to make sure we end up with the same uri */
+    QVERIFY(QContactManager::availableManagers().count() > 1); // invalid + something else
+    QVERIFY(QContactManager::availableManagers().contains("invalid"));
+    QString defaultStore = QContactManager::availableManagers().value(0);
+    QVERIFY(defaultStore != "invalid");
+
+    qDebug() << "Available managers:" << QContactManager::availableManagers();
+
+    QMap<QString, QString> randomParameters;
+    randomParameters.insert("something", "old");
+    randomParameters.insert("something...", "new");
+    randomParameters.insert("something ", "borrowed");
+    randomParameters.insert(" something", "blue");
+
+    QObject parent;
+
+    QContactManager cm; // default
+    QContactManager cm2(defaultStore);
+    QContactManager cm3(defaultStore, QMap<QString, QString>());
+    QContactManager cm4(cm.managerUri()); // should fail
+
+    QScopedPointer<QContactManager> cm5(QContactManager::fromUri(QContactManager::buildUri(defaultStore, QMap<QString, QString>())));
+    QScopedPointer<QContactManager> cm6(QContactManager::fromUri(cm.managerUri())); // uri is not a name; should fail.
+    QScopedPointer<QContactManager> cm9(QContactManager::fromUri(QString(), &parent));
+
+    QVERIFY(cm9->parent() == &parent);
+
+    /* OLD TEST WAS THIS: */
+    //QCOMPARE(cm.managerUri(), cm2.managerUri());
+    //QCOMPARE(cm.managerUri(), cm3.managerUri());
+    //QCOMPARE(cm.managerUri(), cm5->managerUri());
+    //QCOMPARE(cm.managerUri(), cm6->managerUri());
+    //QCOMPARE(cm.managerUri(), cm9->managerUri());
+    /* NEW TEST IS THIS: Test that the names of the managers are the same */
+    QCOMPARE(cm.managerName(), cm2.managerName());
+    QCOMPARE(cm.managerName(), cm3.managerName());
+    QCOMPARE(cm.managerName(), cm5->managerName());
+    QCOMPARE(cm.managerName(), cm6->managerName());
+    QCOMPARE(cm.managerName(), cm9->managerName());
+
+    QVERIFY(cm.managerUri() != cm4.managerUri()); // don't pass a uri to the ctor
+
+    /* Test that we get invalid stores when we do silly things */
+    QContactManager em("non existent");
+    QContactManager em2("non existent", QMap<QString, QString>());
+    QContactManager em3("memory", randomParameters);
+
+    /* Also invalid, since we don't have one of these anyway */
+    QScopedPointer<QContactManager> em4(QContactManager::fromUri("invalid uri"));
+    QScopedPointer<QContactManager> em5(QContactManager::fromUri(QContactManager::buildUri("nonexistent", QMap<QString, QString>())));
+    QScopedPointer<QContactManager> em6(QContactManager::fromUri(em3.managerUri()));
+
+
+    /*
+     * Sets of stores that should be equivalent:
+     * - 1, 2, 4, 5
+     * - 3, 6
+     */
+
+    /* First some URI testing for equivalent stores */
+    QVERIFY(em.managerUri() == em2.managerUri());
+    QVERIFY(em.managerUri() == em5->managerUri());
+    QVERIFY(em.managerUri() == em4->managerUri());
+    QVERIFY(em2.managerUri() == em4->managerUri());
+    QVERIFY(em2.managerUri() == em5->managerUri());
+    QVERIFY(em4->managerUri() == em5->managerUri());
+
+    QVERIFY(em3.managerUri() == em6->managerUri());
+
+    /* Test the stores that should not be the same */
+    QVERIFY(em.managerUri() != em3.managerUri());
+    QVERIFY(em.managerUri() != em6->managerUri());
+
+    /* now the components */
+    QCOMPARE(em.managerName(), QString("invalid"));
+    QCOMPARE(em2.managerName(), QString("invalid"));
+    QCOMPARE(em3.managerName(), QString("memory"));
+    QCOMPARE(em4->managerName(), QString("invalid"));
+    QCOMPARE(em5->managerName(), QString("invalid"));
+    QCOMPARE(em6->managerName(), QString("memory"));
+    QCOMPARE(em.managerParameters(), tst_QContactManager_QStringMap());
+    QCOMPARE(em2.managerParameters(), tst_QContactManager_QStringMap());
+    QCOMPARE(em4->managerParameters(), tst_QContactManager_QStringMap());
+    QCOMPARE(em5->managerParameters(), tst_QContactManager_QStringMap());
+    QCOMPARE(em3.managerParameters(), em6->managerParameters()); // memory engine discards the given params, replaces with id.
+
+
+    // Finally test the platform specific engines are actually the defaults
+#if defined(Q_OS_SYMBIAN)
+    QCOMPARE(defaultStore, QString("symbian"));
+#elif defined(Q_WS_MAEMO_6)
+    QCOMPARE(defaultStore, QString("tracker"));
+#elif defined(Q_WS_MAEMO_5)
+    QCOMPARE(defaultStore, QString("maemo5"));
+#elif defined(Q_OS_WINCE)
+    QCOMPARE(defaultStore, QString("wince"));
+#else
+    QCOMPARE(defaultStore, QString("memory"));
+#endif
+}
+
+void tst_QContactManager::doDump()
+{
+    // Only do this if it has been explicitly selected
+    if (QCoreApplication::arguments().contains("doDump")) {
+        QFETCH(QString, uri);
+        QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
+
+        dumpContacts(cm.data());
+    }
+}
+
+Q_DECLARE_METATYPE(QVariant)
+
+void tst_QContactManager::doDumpSchema()
+{
+    // Only do this if it has been explicitly selected
+    if (QCoreApplication::arguments().contains("doDumpSchema")) {
+        QFETCH(QString, uri);
+        QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
+
+        // Get the schema for each supported type
+        foreach(QString type, cm->supportedContactTypes()) {
+            QMap<QString, QContactDetailDefinition> defs = cm->detailDefinitions(type);
+
+            foreach(QContactDetailDefinition def, defs.values()) {
+                if (def.isUnique())
+                    qDebug() << QString("%2::%1 (Unique) {").arg(def.name()).arg(type).toAscii().constData();
+                else
+                    qDebug() << QString("%2::%1 {").arg(def.name()).arg(type).toAscii().constData();
+                QMap<QString, QContactDetailFieldDefinition> fields = def.fields();
+
+                foreach(QString fname, fields.keys()) {
+                    QContactDetailFieldDefinition field = fields.value(fname);
+
+                    if (field.allowableValues().count() > 0) {
+                        // Make some pretty output
+                        QStringList allowedList;
+                        foreach(QVariant var, field.allowableValues()) {
+                            QString allowed;
+                            if (var.type() == QVariant::String)
+                                allowed = QString("'%1'").arg(var.toString());
+                            else if (var.type() == QVariant::StringList)
+                                allowed = QString("'%1'").arg(var.toStringList().join(","));
+                            else {
+                                // use the textstream <<
+                                QDebug dbg(&allowed);
+                                dbg << var;
+                            }
+                            allowedList.append(allowed);
+                        }
+
+                        qDebug() << QString("   %2 %1 {%3}").arg(fname).arg(QMetaType::typeName(field.dataType())).arg(allowedList.join(",")).toAscii().constData();
+                    } else
+                        qDebug() << QString("   %2 %1").arg(fname).arg(QMetaType::typeName(field.dataType())).toAscii().constData();
+                }
+
+                qDebug() << "}";
+            }
+        }
+    }
+}
+
+void tst_QContactManager::add()
+{
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
+
+    QContactDetailDefinition nameDef = cm->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact);
+    QContact alice = createContact(nameDef, "Alice", "inWonderland", "1234567");
+    int currCount = cm->contactIds().count();
+    QVERIFY(cm->saveContact(&alice));
+    QVERIFY(cm->error() == QContactManager::NoError);
+
+    QVERIFY(!alice.id().managerUri().isEmpty());
+    QVERIFY(alice.id().localId() != 0);
+    QCOMPARE(cm->contactIds().count(), currCount+1);
+
+    QContact added = cm->contact(alice.id().localId());
+    QVERIFY(added.id() == alice.id());
+    
+    if (!isSuperset(added, alice)) {
+        dumpContacts(cm.data());
+        dumpContactDifferences(added, alice);
+        QCOMPARE(added, alice);
+    }
+
+    // now try adding a contact that does not exist in the database with non-zero id
+    if (cm->managerName() == "symbiansim") {
+        // TODO: symbiansim backend fails this test currently. Will be fixed later.
+        QWARN("This manager has a known issue with saving a non-zero id contact. Skipping this test step.");
+    } else {
+        QContact nonexistent = createContact(nameDef, "nonexistent", "contact", "");
+        QVERIFY(cm->saveContact(&nonexistent));       // should work
+        QVERIFY(cm->removeContact(nonexistent.id().localId())); // now nonexistent has an id which does not exist
+        QVERIFY(!cm->saveContact(&nonexistent));      // hence, should fail
+        QCOMPARE(cm->error(), QContactManager::DoesNotExistError);
+        nonexistent.setId(QContactId());
+        QVERIFY(cm->saveContact(&nonexistent));       // after setting id to zero, should save
+        QVERIFY(cm->removeContact(nonexistent.id().localId()));
+    }
+
+    // now try adding a "megacontact"
+    // - get list of all definitions supported by the manager
+    // - add one detail of each definition to a contact
+    // - save the contact
+    // - read it back
+    // - ensure that it's the same.
+    QContact megacontact;
+    QMap<QString, QContactDetailDefinition> defmap = cm->detailDefinitions();
+    QList<QContactDetailDefinition> defs = defmap.values();
+    foreach (const QContactDetailDefinition def, defs) {
+
+        // Leave these warnings here - might need an API for this
+        // XXX FIXME: access constraint reporting as moved to the detail itself
+        //if (def.accessConstraint() == QContactDetailDefinition::ReadOnly) {
+        //    continue;
+        //}
+
+        if (cm->managerName() == "maemo5") {
+            // The maemo5 backend only supports reading of Guid and QCOA
+            if (def.name() == QContactGuid::DefinitionName)
+                continue;
+            if (def.name() == QContactOnlineAccount::DefinitionName)
+                continue;
+	    if (def.name() == QContactPresence::DefinitionName)
+                continue;
+        }
+
+        // This is probably read-only
+        if (def.name() == QContactTimestamp::DefinitionName)
+            continue;
+
+        // otherwise, create a new detail of the given type and save it to the contact
+        QContactDetail det(def.name());
+        QMap<QString, QContactDetailFieldDefinition> fieldmap = def.fields();
+        QStringList fieldKeys = fieldmap.keys();
+        foreach (const QString& fieldKey, fieldKeys) {
+            // get the field, and check to see that it's not constrained.
+            QContactDetailFieldDefinition currentField = fieldmap.value(fieldKey);
+            
+            // Don't test detail uris as these are manager specific
+            if (fieldKey == QContactDetail::FieldDetailUri)
+                continue;
+
+            // Special case: phone number.
+            if (def.name() == QContactPhoneNumber::DefinitionName &&
+                fieldKey == QContactPhoneNumber::FieldNumber) {
+                det.setValue(fieldKey, "+3581234567890");
+                continue;
+            }
+
+            // Attempt to create a worthy value
+            if (!currentField.allowableValues().isEmpty()) {
+                // we want to save a value that will be accepted.
+                if (currentField.dataType() == QVariant::StringList)
+                    det.setValue(fieldKey, QStringList() << currentField.allowableValues().first().toString());
+                else if (currentField.dataType() == QVariant::List)
+                    det.setValue(fieldKey, QVariantList() << currentField.allowableValues().first());
+                else
+                    det.setValue(fieldKey, currentField.allowableValues().first());
+            } else {
+                // any value of the correct type will be accepted
+                bool savedSuccessfully = false;
+                QVariant dummyValue = QVariant(fieldKey); // try to get some unique string data
+                if (dummyValue.canConvert(currentField.dataType())) {
+                    savedSuccessfully = dummyValue.convert(currentField.dataType());
+                    if (savedSuccessfully) {
+                        // we have successfully created a (supposedly) valid field for this detail.
+                        det.setValue(fieldKey, dummyValue);
+                        continue;
+                    }
+                }
+
+                // nope, couldn't save the string value (test); try a date.
+                dummyValue = QVariant(QDate::currentDate());
+                if (dummyValue.canConvert(currentField.dataType())) {
+                    savedSuccessfully = dummyValue.convert(currentField.dataType());
+                    if (savedSuccessfully) {
+                        // we have successfully created a (supposedly) valid field for this detail.
+                        det.setValue(fieldKey, dummyValue);
+                        continue;
+                    }
+                }
+
+                // nope, couldn't convert a string or a date - try the integer value (42)
+                dummyValue = QVariant(42);
+                if (dummyValue.canConvert(currentField.dataType())) {
+                    savedSuccessfully = dummyValue.convert(currentField.dataType());
+                    if (savedSuccessfully) {
+                        // we have successfully created a (supposedly) valid field for this detail.
+                        det.setValue(fieldKey, dummyValue);
+                        continue;
+                    }
+                }
+
+                // if we get here, we don't know what sort of value can be saved...
+            }
+        }
+        if (!det.isEmpty())
+            megacontact.saveDetail(&det);
+    }
+
+    QVERIFY(cm->saveContact(&megacontact)); // must be able to save since built from definitions.
+    QContact retrievedMegacontact = cm->contact(megacontact.id().localId());
+    if (!isSuperset(retrievedMegacontact, megacontact)) {
+        dumpContactDifferences(megacontact, retrievedMegacontact);
+        QEXPECT_FAIL("mgr='wince'", "Address Display Label mismatch", Continue);
+        QCOMPARE(megacontact, retrievedMegacontact);
+    }
+
+    // now a contact with many details of a particular definition
+    // if the detail is not unique it should then support minumum of two of the same kind
+    const int nrOfdetails = 2;
+    QContact veryContactable = createContact(nameDef, "Very", "Contactable", "");
+    for (int i = 0; i < nrOfdetails; i++) {
+        QString phnStr = QString::number(i);
+        QContactPhoneNumber vcphn;
+        vcphn.setNumber(phnStr);
+        QVERIFY(veryContactable.saveDetail(&vcphn));
+    }
+
+    // check that all the numbers were added successfully
+    QVERIFY(veryContactable.details(QContactPhoneNumber::DefinitionName).size() == nrOfdetails);
+    
+    // check if it can be saved
+    QContactDetailDefinition def = cm->detailDefinition(QContactPhoneNumber::DefinitionName);
+    if (def.isUnique()) {    
+        QVERIFY(!cm->saveContact(&veryContactable));
+    }
+    else {
+        QVERIFY(cm->saveContact(&veryContactable));
+        
+        // verify save
+        QContact retrievedContactable = cm->contact(veryContactable.id().localId());
+        if (!isSuperset(retrievedContactable, veryContactable)) {
+            dumpContactDifferences(veryContactable, retrievedContactable);
+            QEXPECT_FAIL("mgr='wince'", "Number of phones supported mismatch", Continue);
+            QCOMPARE(veryContactable, retrievedContactable);
+        }
+    }
+}
+
+void tst_QContactManager::update()
+{
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
+
+    if (cm->managerName() == QString(QLatin1String("maemo5"))) {
+        // we specifically want to test the update semantics of the maemo5 backend
+        // since there are various complexities relating to roster contacts.
+        QContact mt;
+        QContactName mtn;
+        mtn.setFirstName("test");
+        mtn.setLastName("maemo");
+        QContactPhoneNumber pn;
+        pn.setNumber("12345");
+
+        mt.saveDetail(&mtn);
+        cm->saveContact(&mt);
+        mt = cm->contact(mt.localId()); // force reload of (persisted) contact
+        QVERIFY(mt.details<QContactPhoneNumber>().count() == 0);
+
+        // now save a single phonenumber
+        mt.saveDetail(&pn);
+        cm->saveContact(&mt);
+        mt = cm->contact(mt.localId()); // force reload of (persisted) contact
+        QVERIFY(mt.details<QContactPhoneNumber>().count() == 1);
+
+        // edit some other existing detail and save (shouldn't duplicate the phone number)
+        mtn.setMiddleName("middle");
+        mt.saveDetail(&mtn);
+        cm->saveContact(&mt);
+        mt = cm->contact(mt.localId()); // force reload of (persisted) contact
+        QCOMPARE(mt.details<QContactPhoneNumber>().count(), 1);
+
+        // add some other detail and save (shouldn't duplicate the phone number)
+        QContactEmailAddress mte;
+        mte.setEmailAddress("test@test.com");
+        mt.saveDetail(&mte);
+        cm->saveContact(&mt);
+        mt = cm->contact(mt.localId()); // force reload of (persisted) contact
+        QCOMPARE(mt.details<QContactPhoneNumber>().count(), 1);
+
+        // add another phone number detail and save (should create a single other phone number)
+        QContactPhoneNumber pn2;
+        pn2.setNumber("98765");
+        mt.saveDetail(&pn2);
+        cm->saveContact(&mt);
+        mt = cm->contact(mt.localId()); // force reload of (persisted) contact
+        QCOMPARE(mt.details<QContactPhoneNumber>().count(), 2);
+
+        // here we do something tricky: we save one of the previously saved phone numbers
+        // in a _different_ contact, and see if that causes problems with the overwrite vs new detail code.
+        QContactPhoneNumber pn2Copy = pn2;
+        QContact mt2;
+        QContactName mt2n;
+        mt2n.setFirstName("test2");
+        mt2.saveDetail(&mt2n);
+        QContactPhoneNumber shouldBeNew = pn;
+        mt2.saveDetail(&shouldBeNew);
+        QVERIFY(cm->saveContact(&mt2));
+        mt2 = cm->contact(mt2.localId());
+        QCOMPARE(mt2.details<QContactPhoneNumber>().count(), 1);
+        mt2.saveDetail(&pn2);
+        QVERIFY(cm->saveContact(&mt2));
+        mt2 = cm->contact(mt2.localId());
+        QCOMPARE(mt2.details<QContactPhoneNumber>().count(), 2);
+        pn2 = pn2Copy; // reset just in case backend added some fields.
+
+        // remove the other phone number detail, shouldn't cause side effects to the first...
+        // NOTE: we need to reload the details before attempting to remove/edit them
+        // because the backend can change the ids.
+        QList<QContactPhoneNumber> pnums = mt.details<QContactPhoneNumber>();
+        foreach (const QContactPhoneNumber& pd, pnums) {
+            if (pd.number() == pn2.number())
+                pn2 = pd;
+            else if (pd.number() == pn.number())
+                pn = pd;
+        }
+        mt.removeDetail(&pn2);
+        cm->saveContact(&mt);
+        mt = cm->contact(mt.localId()); // force reload of (persisted) contact
+        QCOMPARE(mt.details<QContactPhoneNumber>().count(), 1);
+
+        // edit the original phone number detail, shouldn't duplicate the phone number
+        // NOTE: we need to reload the details before attempting to remove/edit them
+        // because the backend can change the ids.
+        pnums = mt.details<QContactPhoneNumber>();
+        foreach (const QContactPhoneNumber& pd, pnums) {
+            if (pd.number() == pn2.number())
+                pn2 = pd;
+            else if (pd.number() == pn.number())
+                pn = pd;
+        }
+        pn.setNumber("54321");
+        mt.saveDetail(&pn);
+        cm->saveContact(&mt);
+        mt = cm->contact(mt.localId());
+        QCOMPARE(mt.details<QContactPhoneNumber>().count(), 1);
+        QVERIFY(mt.detail<QContactPhoneNumber>() == pn);
+
+        // we also should do the same test for other details (for example, gender).
+        // if the backend cannot save multiple copies of a detail (eg, gender always overwrites)
+        // it should FAIL the save operation if the contact has multiple of that detail type,
+        // and set error to QContactManager::LimitReachedError.
+        QContactGender mtg, mtg2;
+        mtg.setGender(QContactGender::GenderFemale);
+        mtg2.setGender(QContactGender::GenderMale);
+        mt.saveDetail(&mtg);
+        QVERIFY(cm->saveContact(&mt)); // one gender is fine
+        mt.saveDetail(&mtg2);
+        QVERIFY(!cm->saveContact(&mt)); // two is not
+        //QCOMPARE(cm->error(), QContactManager::LimitReachedError); // should be LimitReachedError.
+        mt = cm->contact(mt.localId());
+        QVERIFY(mt.details<QContactGender>().count() == 1);
+    }
+
+    /* Save a new contact first */
+    int contactCount = cm->contacts().size();
+    QContactDetailDefinition nameDef = cm->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact);
+    QContact alice = createContact(nameDef, "Alice", "inWonderland", "1234567");
+    QVERIFY(cm->saveContact(&alice));
+    QVERIFY(cm->error() == QContactManager::NoError);
+    contactCount += 1; // added a new contact.
+    QCOMPARE(cm->contacts().size(), contactCount);
+
+    /* Update name */
+    QContactName name = alice.detail(QContactName::DefinitionName);
+    saveContactName(&alice, nameDef, &name, "updated");
+    QVERIFY(cm->saveContact(&alice));
+    QVERIFY(cm->error() == QContactManager::NoError);
+    saveContactName(&alice, nameDef, &name, "updated2");
+    QVERIFY(cm->saveContact(&alice));
+    QVERIFY(cm->error() == QContactManager::NoError);
+    alice = cm->contact(alice.localId()); // force reload of (persisted) alice
+    QContact updated = cm->contact(alice.localId());
+    QContactName updatedName = updated.detail(QContactName::DefinitionName);
+    QCOMPARE(updatedName, name);
+    QCOMPARE(cm->contacts().size(), contactCount); // contact count should be the same, no new contacts
+
+    /* Test that adding a new detail doesn't cause unwanted side effects */
+    int detailCount = alice.details().size();
+    QContactEmailAddress email;
+    email.setEmailAddress("test@example.com");
+    alice.saveDetail(&email);
+    QVERIFY(cm->saveContact(&alice));
+    QCOMPARE(cm->contacts().size(), contactCount); // contact count shoudl be the same, no new contacts
+
+    // This test is dangerous, since backends can add timestamps etc...
+    detailCount += 1;
+    QCOMPARE(detailCount, alice.details().size()); // adding a detail should cause the detail count to increase by one.
+
+    /* Test that removal of fields in a detail works */
+    QContactPhoneNumber phn = alice.detail<QContactPhoneNumber>();
+    phn.setNumber("1234567");
+    phn.setContexts(QContactDetail::ContextHome);
+    alice.saveDetail(&phn);
+    QVERIFY(cm->saveContact(&alice));
+    alice = cm->contact(alice.localId()); // force reload of (persisted) alice
+    QVERIFY(alice.detail<QContactPhoneNumber>().contexts().contains(QContactDetail::ContextHome)); // check context saved.
+    phn = alice.detail<QContactPhoneNumber>(); // reload the detail, since it's key could have changed
+    phn.setContexts(QStringList()); // remove context field.
+    alice.saveDetail(&phn);
+    QVERIFY(cm->saveContact(&alice));
+    alice = cm->contact(alice.localId()); // force reload of (persisted) alice
+    QVERIFY(alice.detail<QContactPhoneNumber>().contexts().isEmpty()); // check context removed.
+    QCOMPARE(cm->contacts().size(), contactCount); // removal of a field of a detail shouldn't affect the contact count
+
+    // This test is dangerous, since backends can add timestamps etc...
+    QCOMPARE(detailCount, alice.details().size()); // removing a field from a detail should affect the detail count
+
+    /* Test that removal of details works */
+    phn = alice.detail<QContactPhoneNumber>(); // reload the detail, since it's key could have changed
+    alice.removeDetail(&phn);
+    QVERIFY(cm->saveContact(&alice));
+    alice = cm->contact(alice.localId()); // force reload of (persisted) alice
+    QVERIFY(alice.details<QContactPhoneNumber>().isEmpty()); // no such detail.
+    QCOMPARE(cm->contacts().size(), contactCount); // removal of a detail shouldn't affect the contact count
+
+    // This test is dangerous, since backends can add timestamps etc...
+    //detailCount -= 1;
+    //QCOMPARE(detailCount, alice.details().size()); // removing a detail should cause the detail count to decrease by one.
+
+    if (cm->hasFeature(QContactManager::Groups)) {
+        // Try changing types - not allowed
+        // from contact -> group
+        alice.setType(QContactType::TypeGroup);
+        QContactName na = alice.detail(QContactName::DefinitionName);
+        alice.removeDetail(&na);
+        QVERIFY(!cm->saveContact(&alice));
+        QVERIFY(cm->error() == QContactManager::AlreadyExistsError);
+
+        // from group -> contact
+        QContact jabberwock = createContact(nameDef, "", "", "1234567890");
+        jabberwock.setType(QContactType::TypeGroup);
+        QVERIFY(cm->saveContact(&jabberwock));
+        jabberwock.setType(QContactType::TypeContact);
+        QVERIFY(!cm->saveContact(&jabberwock));
+        QVERIFY(cm->error() == QContactManager::AlreadyExistsError);
+    }
+}
+
+void tst_QContactManager::remove()
+{
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
+
+    /* Save a new contact first */
+    QContactDetailDefinition nameDef = cm->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact);
+    QContact alice = createContact(nameDef, "Alice", "inWonderland", "1234567");
+    QVERIFY(cm->saveContact(&alice));
+    QVERIFY(cm->error() == QContactManager::NoError);
+    QVERIFY(alice.id() != QContactId());
+
+    /* Remove the created contact */
+    const int contactCount = cm->contactIds().count();
+    QVERIFY(cm->removeContact(alice.localId()));
+    QCOMPARE(cm->contactIds().count(), contactCount - 1);
+    QVERIFY(cm->contact(alice.localId()).isEmpty());
+    QCOMPARE(cm->error(), QContactManager::DoesNotExistError);
+}
+
+void tst_QContactManager::batch()
+{
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
+
+    /* First test null pointer operations */
+    QVERIFY(!cm->saveContacts(NULL, NULL));
+    QVERIFY(cm->error() == QContactManager::BadArgumentError);
+
+    QVERIFY(!cm->removeContacts(QList<QContactLocalId>(), NULL));
+    QVERIFY(cm->error() == QContactManager::BadArgumentError);
+    
+    // Get supported name field
+    QString nameField = QContactName::FieldFirstName;
+    QContactDetailDefinition def = cm->detailDefinition(QContactName::DefinitionName);
+    if (!def.fields().contains(QContactName::FieldFirstName)) {
+        if(def.fields().contains(QContactName::FieldCustomLabel))
+            nameField = QLatin1String(QContactName::FieldCustomLabel);
+        else
+            QSKIP("This backend does not support the required name field!", SkipSingle);
+    }
+
+    /* Now add 3 contacts, all valid */
+    QContact a;
+    QContactName na;
+    na.setValue(nameField, "XXXXXX Albert");
+    a.saveDetail(&na);
+
+    QContact b;
+    QContactName nb;
+    nb.setValue(nameField, "XXXXXX Bob");
+    b.saveDetail(&nb);
+
+    QContact c;
+    QContactName nc;
+    nc.setValue(nameField, "XXXXXX Carol");
+    c.saveDetail(&nc);
+
+    QList<QContact> contacts;
+    contacts << a << b << c;
+
+    QMap<int, QContactManager::Error> errorMap;
+    // Add one dummy error to test if the errors are reset
+    errorMap.insert(0, QContactManager::NoError);
+    QVERIFY(cm->saveContacts(&contacts, &errorMap));
+    QVERIFY(cm->error() == QContactManager::NoError);
+    QVERIFY(errorMap.count() == 0);
+
+    /* Make sure our contacts got updated too */
+    QVERIFY(contacts.count() == 3);
+    QVERIFY(contacts.at(0).id() != QContactId());
+    QVERIFY(contacts.at(1).id() != QContactId());
+    QVERIFY(contacts.at(2).id() != QContactId());
+
+    QVERIFY(contacts.at(0).detail(QContactName::DefinitionName) == na);
+    QVERIFY(contacts.at(1).detail(QContactName::DefinitionName) == nb);
+    QVERIFY(contacts.at(2).detail(QContactName::DefinitionName) == nc);
+
+    /* Retrieve again */
+    a = cm->contact(contacts.at(0).id().localId());
+    b = cm->contact(contacts.at(1).id().localId());
+    c = cm->contact(contacts.at(2).id().localId());
+    QVERIFY(contacts.at(0).detail(QContactName::DefinitionName) == na);
+    QVERIFY(contacts.at(1).detail(QContactName::DefinitionName) == nb);
+    QVERIFY(contacts.at(2).detail(QContactName::DefinitionName) == nc);
+
+    /* Save again, with a null error map */
+    QVERIFY(cm->saveContacts(&contacts, NULL));
+    QVERIFY(cm->error() == QContactManager::NoError);
+
+    /* Now make an update to them all */
+    QContactPhoneNumber number;
+    number.setNumber("1234567");
+
+    QVERIFY(contacts[0].saveDetail(&number));
+    number.setNumber("234567");
+    QVERIFY(contacts[1].saveDetail(&number));
+    number.setNumber("34567");
+    QVERIFY(contacts[2].saveDetail(&number));
+
+    QVERIFY(cm->saveContacts(&contacts, &errorMap));
+    QVERIFY(cm->error() == QContactManager::NoError);
+    QVERIFY(errorMap.count() == 0);
+
+    /* Retrieve them and check them again */
+    a = cm->contact(contacts.at(0).id().localId());
+    b = cm->contact(contacts.at(1).id().localId());
+    c = cm->contact(contacts.at(2).id().localId());
+    QVERIFY(contacts.at(0).detail(QContactName::DefinitionName) == na);
+    QVERIFY(contacts.at(1).detail(QContactName::DefinitionName) == nb);
+    QVERIFY(contacts.at(2).detail(QContactName::DefinitionName) == nc);
+
+    QVERIFY(a.details<QContactPhoneNumber>().count() == 1);
+    QVERIFY(b.details<QContactPhoneNumber>().count() == 1);
+    QVERIFY(c.details<QContactPhoneNumber>().count() == 1);
+
+    QVERIFY(a.details<QContactPhoneNumber>().at(0).number() == "1234567");
+    QVERIFY(b.details<QContactPhoneNumber>().at(0).number() == "234567");
+    QVERIFY(c.details<QContactPhoneNumber>().at(0).number() == "34567");
+
+    /* Now delete them all */
+    QList<QContactLocalId> ids;
+    ids << a.id().localId() << b.id().localId() << c.id().localId();
+    QVERIFY(cm->removeContacts(ids, &errorMap));
+    QVERIFY(errorMap.count() == 0);
+    QVERIFY(cm->error() == QContactManager::NoError);
+
+    /* Make sure the contacts really don't exist any more */
+    QVERIFY(cm->contact(a.id().localId()).id() == QContactId());
+    QVERIFY(cm->contact(a.id().localId()).isEmpty());
+    QVERIFY(cm->error() == QContactManager::DoesNotExistError);
+    QVERIFY(cm->contact(b.id().localId()).id() == QContactId());
+    QVERIFY(cm->contact(b.id().localId()).isEmpty());
+    QVERIFY(cm->error() == QContactManager::DoesNotExistError);
+    QVERIFY(cm->contact(c.id().localId()).id() == QContactId());
+    QVERIFY(cm->contact(c.id().localId()).isEmpty());
+    QVERIFY(cm->error() == QContactManager::DoesNotExistError);
+
+    /* Now try removing with all invalid ids (e.g. the ones we just removed) */
+    ids.clear();
+    ids << a.id().localId() << b.id().localId() << c.id().localId();
+    QVERIFY(!cm->removeContacts(ids, &errorMap));
+    QVERIFY(cm->error() == QContactManager::DoesNotExistError);
+    QVERIFY(errorMap.count() == 3);
+    QVERIFY(errorMap.values().at(0) == QContactManager::DoesNotExistError);
+    QVERIFY(errorMap.values().at(1) == QContactManager::DoesNotExistError);
+    QVERIFY(errorMap.values().at(2) == QContactManager::DoesNotExistError);
+
+    /* And again with a null error map */
+    QVERIFY(!cm->removeContacts(ids, NULL));
+    QVERIFY(cm->error() == QContactManager::DoesNotExistError);
+
+    /* Try adding some new ones again, this time one with an error */
+    contacts.clear();
+    a.setId(QContactId());
+    b.setId(QContactId());
+    c.setId(QContactId());
+
+    /* Make B the bad guy */
+    QContactDetail bad("does not exist and will break if you add it");
+    bad.setValue("This is also bad", "Very bad");
+    b.saveDetail(&bad);
+
+    contacts << a << b << c;
+    QVERIFY(!cm->saveContacts(&contacts, &errorMap));
+    /* We can't really say what the error will be.. maybe bad argument, maybe invalid detail */
+    QVERIFY(cm->error() != QContactManager::NoError);
+
+    /* It's permissible to fail all the adds, or to add the successful ones */
+    QVERIFY(errorMap.count() > 0);
+    QVERIFY(errorMap.count() <= 3);
+
+    // A might have gone through
+    if (errorMap.keys().contains(0)) {
+        QVERIFY(errorMap.value(0) != QContactManager::NoError);
+        QVERIFY(contacts.at(0).id() == QContactId());
+    } else {
+        QVERIFY(contacts.at(0).id() != QContactId());
+    }
+
+    // B should have failed
+    QVERIFY(errorMap.value(1) == QContactManager::InvalidDetailError);
+    QVERIFY(contacts.at(1).id() == QContactId());
+
+    // C might have gone through
+    if (errorMap.keys().contains(2)) {
+        QVERIFY(errorMap.value(2) != QContactManager::NoError);
+        QVERIFY(contacts.at(2).id() == QContactId());
+    } else {
+        QVERIFY(contacts.at(2).id() != QContactId());
+    }
+
+    /* Fix up B and re save it */
+    QVERIFY(contacts[1].removeDetail(&bad));
+    QVERIFY(cm->saveContacts(&contacts, &errorMap));
+    QVERIFY(errorMap.count() == 0);
+    QVERIFY(cm->error() == QContactManager::NoError);
+    
+    // Save and remove a fourth contact. Store the id.
+    a.setId(QContactId());
+    QVERIFY(cm->saveContact(&a));
+    QContactLocalId removedId = a.localId();
+    QVERIFY(cm->removeContact(removedId));
+
+    /* Now delete 3 items, but with one bad argument */
+    ids.clear();
+    ids << contacts.at(0).id().localId();
+    ids << removedId;
+    ids << contacts.at(2).id().localId();
+
+    QVERIFY(!cm->removeContacts(ids, &errorMap));
+    QVERIFY(cm->error() != QContactManager::NoError);
+
+    /* Again, the backend has the choice of either removing the successful ones, or not */
+    QVERIFY(errorMap.count() > 0);
+    QVERIFY(errorMap.count() <= 3);
+
+    // A might have gone through
+    if (errorMap.keys().contains(0)) {
+        QVERIFY(errorMap.value(0) != QContactManager::NoError);
+        QVERIFY(contacts.at(0).id() == QContactId());
+    } else {
+        QVERIFY(contacts.at(0).id() != QContactId());
+    }
+
+    /* B should definitely have failed */
+    QVERIFY(errorMap.value(1) == QContactManager::DoesNotExistError);
+    QVERIFY(ids.at(1) == removedId);
+
+    // A might have gone through
+    if (errorMap.keys().contains(2)) {
+        QVERIFY(errorMap.value(2) != QContactManager::NoError);
+        QVERIFY(contacts.at(2).id() == QContactId());
+    } else {
+        QVERIFY(contacts.at(2).id() != QContactId());
+    }
+}
+
+void tst_QContactManager::invalidManager()
+{
+    /* Create an invalid manager */
+    QContactManager manager("this should never work");
+    QVERIFY(manager.managerName() == "invalid");
+    QVERIFY(manager.managerVersion() == 0);
+
+    /* also, test the other ctor behaviour is sane also */
+    QContactManager anotherManager("this should never work", 15);
+    QVERIFY(anotherManager.managerName() == "invalid");
+    QVERIFY(anotherManager.managerVersion() == 0);
+
+    /* Now test that all the operations fail */
+    QVERIFY(manager.contactIds().count() == 0);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+
+    QContact foo;
+    QContactName nf;
+    nf.setLastName("Lastname");
+    foo.saveDetail(&nf);
+
+    QVERIFY(manager.synthesizedContactDisplayLabel(foo).isEmpty());
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+
+    QVERIFY(manager.saveContact(&foo) == false);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+    QVERIFY(foo.id() == QContactId());
+    QVERIFY(manager.contactIds().count() == 0);
+
+    QVERIFY(manager.contact(foo.id().localId()).id() == QContactId());
+    QVERIFY(manager.contact(foo.id().localId()).isEmpty());
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+
+    QVERIFY(manager.removeContact(foo.id().localId()) == false);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+
+    QMap<int, QContactManager::Error> errorMap;
+    errorMap.insert(0, QContactManager::NoError);
+    QVERIFY(!manager.saveContacts(0, &errorMap));
+    QVERIFY(errorMap.count() == 0);
+    QVERIFY(manager.error() == QContactManager::BadArgumentError);
+
+    /* filters */
+    QContactFilter f; // matches everything
+    QContactDetailFilter df;
+    df.setDetailDefinitionName(QContactDisplayLabel::DefinitionName, QContactDisplayLabel::FieldLabel);
+    QVERIFY(manager.contactIds(QContactFilter()).count() == 0);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+    QVERIFY(manager.contactIds(df).count() == 0);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+    QVERIFY(manager.contactIds(f | f).count() == 0);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+    QVERIFY(manager.contactIds(df | df).count() == 0);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+
+    QVERIFY(manager.isFilterSupported(f) == false);
+    QVERIFY(manager.isFilterSupported(df) == false);
+
+    QList<QContact> list;
+    list << foo;
+
+    QVERIFY(!manager.saveContacts(&list, &errorMap));
+    QVERIFY(errorMap.count() == 0);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+
+    QVERIFY(!manager.removeContacts(QList<QContactLocalId>(), &errorMap));
+    QVERIFY(errorMap.count() == 0);
+    QVERIFY(manager.error() == QContactManager::BadArgumentError);
+
+    QList<QContactLocalId> idlist;
+    idlist << foo.id().localId();
+    QVERIFY(!manager.removeContacts(idlist, &errorMap));
+    QVERIFY(errorMap.count() == 0);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+
+    /* Detail definitions */
+    QVERIFY(manager.detailDefinitions().count() == 0);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError);
+
+    QContactDetailDefinition def;
+    def.setUnique(true);
+    def.setName("new field");
+    QMap<QString, QContactDetailFieldDefinition> fields;
+    QContactDetailFieldDefinition currField;
+    currField.setDataType(QVariant::String);
+    fields.insert("value", currField);
+    def.setFields(fields);
+
+    QVERIFY(manager.saveDetailDefinition(def, QContactType::TypeContact) == false);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError);
+    QVERIFY(manager.saveDetailDefinition(def) == false);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError);
+    QVERIFY(manager.detailDefinitions().count(QContactType::TypeContact) == 0);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError);
+    QVERIFY(manager.detailDefinitions().count() == 0);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError);
+    QVERIFY(manager.detailDefinition("new field").name() == QString());
+    QVERIFY(manager.removeDetailDefinition(def.name(), QContactType::TypeContact) == false);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError);
+    QVERIFY(manager.removeDetailDefinition(def.name()) == false);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError);
+    QVERIFY(manager.detailDefinitions().count() == 0);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::InvalidContactTypeError);
+
+    /* Self contact id */
+    QVERIFY(!manager.setSelfContactId(QContactLocalId(12)));
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+    QVERIFY(manager.selfContactId() == QContactLocalId());
+    QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::DoesNotExistError);
+
+    /* Relationships */
+    QContactId idOne, idTwo;
+    idOne.setLocalId(QContactLocalId(15));
+    idOne.setManagerUri(QString());
+    idTwo.setLocalId(QContactLocalId(22));
+    idTwo.setManagerUri(QString());
+    QContactRelationship invalidRel;
+    invalidRel.setFirst(idOne);
+    invalidRel.setSecond(idTwo);
+    QList<QContactRelationship> invalidRelList;
+    invalidRelList << invalidRel;
+    QVERIFY(!manager.saveRelationship(&invalidRel));
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+    QVERIFY(manager.relationships().isEmpty());
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+    manager.saveRelationships(&invalidRelList, NULL);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError);
+    manager.removeRelationships(invalidRelList, NULL);
+    QVERIFY(manager.error() == QContactManager::NotSupportedError || manager.error() == QContactManager::DoesNotExistError);
+
+    /* Capabilities */
+    QVERIFY(manager.supportedDataTypes().count() == 0);
+    QVERIFY(!manager.hasFeature(QContactManager::ActionPreferences));
+    QVERIFY(!manager.hasFeature(QContactManager::MutableDefinitions));
+}
+
+void tst_QContactManager::memoryManager()
+{
+    QMap<QString, QString> params;
+    QContactManager m1("memory");
+    params.insert("random", "shouldNotBeUsed");
+    QContactManager m2("memory", params);
+    params.insert("id", "shouldBeUsed");
+    QContactManager m3("memory", params);
+    QContactManager m4("memory", params);
+    params.insert("id", QString(""));
+    QContactManager m5("memory", params); // should be another anonymous
+
+    QVERIFY(m1.hasFeature(QContactManager::ActionPreferences));
+    QVERIFY(m1.hasFeature(QContactManager::MutableDefinitions));
+    QVERIFY(m1.hasFeature(QContactManager::Anonymous));
+
+    // add a contact to each of m1, m2, m3
+    QContact c;
+    QContactName nc;
+    nc.setFirstName("John");
+    nc.setLastName("Civilian");
+    c.saveDetail(&nc);
+    m1.saveContact(&c);
+    c.setId(QContactId());
+    QContact c2;
+    QContactName nc2 = c2.detail(QContactName::DefinitionName);
+    c2 = c;
+    nc2.setMiddleName("Public");
+    c2.saveDetail(&nc2);
+    m2.saveContact(&c2);            // save c2 first; c will be given a higher id
+    m2.saveContact(&c);             // save c to m2
+    c.setId(QContactId());
+    nc.setSuffix("MD");
+    c.saveDetail(&nc);
+    m3.saveContact(&c);
+
+    /* test that m1 != m2 != m3 and that m3 == m4 */
+
+    // check the counts are correct - especially note m4 and m3.
+    QCOMPARE(m1.contactIds().count(), 1);
+    QCOMPARE(m2.contactIds().count(), 2);
+    QCOMPARE(m3.contactIds().count(), 1);
+    QCOMPARE(m4.contactIds().count(), 1);
+    QCOMPARE(m5.contactIds().count(), 0);
+
+    // remove c2 from m2 - ensure that this doesn't affect any other manager.
+    m2.removeContact(c2.id().localId());
+    QCOMPARE(m1.contactIds().count(), 1);
+    QCOMPARE(m2.contactIds().count(), 1);
+    QCOMPARE(m3.contactIds().count(), 1);
+    QCOMPARE(m4.contactIds().count(), 1);
+    QCOMPARE(m5.contactIds().count(), 0);
+
+    // check that the contacts contained within are different.
+    // note that in the m1->m2 case, only the id will be different!
+    QVERIFY(m1.contact(m1.contactIds().at(0)) != m2.contact(m2.contactIds().at(0)));
+    QVERIFY(m1.contact(m1.contactIds().at(0)) != m3.contact(m3.contactIds().at(0)));
+    QVERIFY(m2.contact(m2.contactIds().at(0)) != m3.contact(m3.contactIds().at(0)));
+    QVERIFY(m3.contact(m3.contactIds().at(0)) == m4.contact(m4.contactIds().at(0)));
+
+    // now, we should be able to remove from m4, and have m3 empty
+    QVERIFY(m4.removeContact(c.id().localId()));
+    QCOMPARE(m3.contactIds().count(), 0);
+    QCOMPARE(m4.contactIds().count(), 0);
+    QCOMPARE(m5.contactIds().count(), 0);
+}
+
+void tst_QContactManager::nameSynthesis_data()
+{
+    QTest::addColumn<QString>("expected");
+
+    QTest::addColumn<bool>("addname");
+    QTest::addColumn<QString>("prefix");
+    QTest::addColumn<QString>("first");
+    QTest::addColumn<QString>("middle");
+    QTest::addColumn<QString>("last");
+    QTest::addColumn<QString>("suffix");
+
+    QTest::addColumn<bool>("addcompany");
+    QTest::addColumn<QString>("company");
+
+    QTest::addColumn<bool>("addname2");
+    QTest::addColumn<QString>("secondprefix");
+    QTest::addColumn<QString>("secondfirst");
+    QTest::addColumn<QString>("secondmiddle");
+    QTest::addColumn<QString>("secondlast");
+    QTest::addColumn<QString>("secondsuffix");
+
+    QTest::addColumn<bool>("addcompany2");
+    QTest::addColumn<QString>("secondcompany");
+
+    QString e; // empty string.. gets a work out
+
+    /* Various empty ones */
+    QTest::newRow("empty contact") << e
+            << false << e << e << e << e << e
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("empty name") << e
+            << true << e << e << e << e << e
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("empty names") << e
+            << true << e << e << e << e << e
+            << false << e
+            << true << e << e << e << e << e
+            << false << e;
+    QTest::newRow("empty org") << e
+            << false << e << e << e << e << e
+            << true << e
+            << false << e << e << e << e << e
+            << true << e;
+    QTest::newRow("empty orgs") << e
+            << false << e << e << e << e << e
+            << true << e
+            << false << e << e << e << e << e
+            << true << e;
+    QTest::newRow("empty orgs and names") << e
+            << true << e << e << e << e << e
+            << true << e
+            << true << e << e << e << e << e
+            << true << e;
+
+    /* Single values */
+    QTest::newRow("prefix") << "Prefix"
+            << true << "Prefix" << e << e << e << e
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("first") << "First"
+            << true << e << "First" << e << e << e
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("middle") << "Middle"
+            << true << e << e << "Middle" << e << e
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("last") << "Last"
+            << true << e << e << e << "Last" << e
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("suffix") << "Suffix"
+            << true << e << e << e << e << "Suffix"
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+
+    /* Single values in the second name */
+    QTest::newRow("prefix in second") << "Prefix"
+            << false << "Prefix" << e << e << e << e
+            << false << e
+            << true << "Prefix" << e << e << e << e
+            << false << e;
+    QTest::newRow("first in second") << "First"
+            << false << e << "First" << e << e << e
+            << false << e
+            << true << e << "First" << e << e << e
+            << false << e;
+    QTest::newRow("middle in second") << "Middle"
+            << false << e << e << "Middle" << e << e
+            << false << e
+            << true << e << e << "Middle" << e << e
+            << false << e;
+    QTest::newRow("last in second") << "Last"
+            << false << e << e << e << "Last" << e
+            << false << e
+            << true << e << e << e << "Last" << e
+            << false << e;
+    QTest::newRow("suffix in second") << "Suffix"
+            << false << e << e << e << e << "Suffix"
+            << false << e
+            << true << e << e << e << e << "Suffix"
+            << false << e;
+
+    /* Multiple name values */
+    QTest::newRow("prefix first") << "Prefix First"
+            << true << "Prefix" << "First" << e << e << e
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("prefix middle") << "Prefix Middle"
+            << true << "Prefix" << e << "Middle" << e << e
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("prefix last") << "Prefix Last"
+            << true << "Prefix" << e << e << "Last" << e
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("prefix suffix") << "Prefix Suffix"
+            << true << "Prefix" << e << e << e << "Suffix"
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("first middle") << "First Middle"
+            << true << e << "First" << "Middle" << e << e
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("first last") << "First Last"
+            << true << e << "First" << e << "Last" << e
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("first suffix") << "First Suffix"
+            << true << e << "First" << e << e << "Suffix"
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("middle last") << "Middle Last"
+            << true << e << e << "Middle" << "Last" << e
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("middle suffix") << "Middle Suffix"
+            << true << e << e << "Middle" << e << "Suffix"
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("last suffix") << "Last Suffix"
+            << true << e << e << e << "Last" << "Suffix"
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+
+    /* Everything.. */
+    QTest::newRow("all name") << "Prefix First Middle Last Suffix"
+            << true << "Prefix" << "First" << "Middle" << "Last" << "Suffix"
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("all name second") << "Prefix First Middle Last Suffix"
+            << false << "Prefix" << "First" << "Middle" << "Last" << "Suffix"
+            << false << e
+            << true << "Prefix" << "First" << "Middle" << "Last" << "Suffix"
+            << false << e;
+
+    /* Org */
+    QTest::newRow("org") << "Company"
+            << false << e << e << e << e << e
+            << true << "Company"
+            << false << e << e << e << e << e
+            << false << e;
+    QTest::newRow("second org") << "Company"
+            << false << e << e << e << e << e
+            << false << e
+            << false << e << e << e << e << e
+            << true << "Company";
+
+    /* Mix */
+    QTest::newRow("org and empty name") << "Company"
+            << true << e << e << e << e << e
+            << true << "Company"
+            << false << e << e << e << e << e
+            << false << e;
+
+    QTest::newRow("name and empty org") << "Prefix First Middle Last Suffix"
+            << true << "Prefix" << "First" << "Middle" << "Last" << "Suffix"
+            << false << e
+            << false << e << e << e << e << e
+            << false << e;
+
+    /* names are preferred to orgs */
+    QTest::newRow("name and org") << "Prefix First Middle Last Suffix"
+            << true << "Prefix" << "First" << "Middle" << "Last" << "Suffix"
+            << true << "Company"
+            << false << e << e << e << e << e
+            << false << e;
+
+}
+
+void tst_QContactManager::nameSynthesis()
+{
+    QContactManager cm("memory");
+
+    QFETCH(QString, expected);
+
+    QFETCH(QString, prefix);
+    QFETCH(QString, first);
+    QFETCH(QString, middle);
+    QFETCH(QString, last);
+    QFETCH(QString, suffix);
+    QFETCH(QString, company);
+
+    QFETCH(QString, secondprefix);
+    QFETCH(QString, secondfirst);
+    QFETCH(QString, secondmiddle);
+    QFETCH(QString, secondlast);
+    QFETCH(QString, secondsuffix);
+    QFETCH(QString, secondcompany);
+
+    QFETCH(bool, addname);
+    QFETCH(bool, addname2);
+    QFETCH(bool, addcompany);
+    QFETCH(bool, addcompany2);
+
+    /* Test the default name synthesis code */
+    QContact c;
+
+    QContactName name, name2;
+    QContactOrganization org, org2;
+
+    name.setPrefix(prefix);
+    name.setFirstName(first);
+    name.setMiddleName(middle);
+    name.setLastName(last);
+    name.setSuffix(suffix);
+
+    name2.setPrefix(secondprefix);
+    name2.setFirstName(secondfirst);
+    name2.setMiddleName(secondmiddle);
+    name2.setLastName(secondlast);
+    name2.setSuffix(secondsuffix);
+
+    org.setName(company);
+    org2.setName(secondcompany);
+
+    if (addname)
+        c.saveDetail(&name);
+    if (addname2)
+        c.saveDetail(&name2);
+    if (addcompany)
+        c.saveDetail(&org);
+    if (addcompany2)
+        c.saveDetail(&org2);
+
+    // Finally!
+    QCOMPARE(cm.synthesizedContactDisplayLabel(c), expected);
+}
+
+void tst_QContactManager::compatibleContact_data()
+{
+    QTest::addColumn<QContact>("input");
+    QTest::addColumn<QContact>("expected");
+    QTest::addColumn<QContactManager::Error>("error");
+
+    QContact baseContact;
+    QContactName name;
+    name.setFirstName(QLatin1String("First"));
+    baseContact.saveDetail(&name);
+
+    {
+        QTest::newRow("already compatible") << baseContact << baseContact << QContactManager::NoError;
+    }
+
+    {
+        QContact contact(baseContact);
+        QContactDetail detail("UnknownDetail");
+        detail.setValue("Key", QLatin1String("Value"));
+        contact.saveDetail(&detail);
+        QTest::newRow("unknown detail") << contact << baseContact << QContactManager::NoError;
+    }
+
+    {
+        QContact contact(baseContact);
+        QContactType type1;
+        type1.setType(QContactType::TypeContact);
+        contact.saveDetail(&type1);
+        QContactType type2;
+        type2.setType(QContactType::TypeGroup);
+        contact.saveDetail(&type2);
+        QContact expected(baseContact);
+        expected.saveDetail(&type2);
+        QTest::newRow("duplicate unique field") << contact << expected << QContactManager::NoError;
+    }
+
+    {
+        QContact contact(baseContact);
+        QContactPhoneNumber phoneNumber;
+        phoneNumber.setValue("UnknownKey", "Value");
+        contact.saveDetail(&phoneNumber);
+        QTest::newRow("unknown field") << contact << baseContact << QContactManager::NoError;
+    }
+
+    {
+        QContact contact(baseContact);
+        QContactDisplayLabel displayLabel;
+        displayLabel.setValue(QContactDisplayLabel::FieldLabel, QStringList("Value"));
+        contact.saveDetail(&displayLabel);
+        QTest::newRow("wrong type") << contact << baseContact << QContactManager::NoError;
+    }
+
+    {
+        QContact contact(baseContact);
+        QContactPhoneNumber phoneNumber1;
+        phoneNumber1.setNumber(QLatin1String("1234"));
+        phoneNumber1.setSubTypes(QStringList()
+                                << QContactPhoneNumber::SubTypeMobile
+                                << QContactPhoneNumber::SubTypeVoice
+                                << QLatin1String("InvalidSubtype"));
+        contact.saveDetail(&phoneNumber1);
+        QContact expected(baseContact);
+        QContactPhoneNumber phoneNumber2;
+        phoneNumber2.setNumber(QLatin1String("1234"));
+        phoneNumber2.setSubTypes(QStringList()
+                                << QContactPhoneNumber::SubTypeMobile
+                                << QContactPhoneNumber::SubTypeVoice);
+        expected.saveDetail(&phoneNumber2);
+        QTest::newRow("bad value (list)") << contact << expected << QContactManager::NoError;
+    }
+
+    {
+        QContact contact(baseContact);
+        QContactPhoneNumber phoneNumber1;
+        phoneNumber1.setNumber(QLatin1String("1234"));
+        phoneNumber1.setSubTypes(QStringList(QLatin1String("InvalidSubtype")));
+        contact.saveDetail(&phoneNumber1);
+        QContact expected(baseContact);
+        QContactPhoneNumber phoneNumber2;
+        phoneNumber2.setNumber(QLatin1String("1234"));
+        expected.saveDetail(&phoneNumber2);
+        QTest::newRow("all bad value (list)") << contact << expected << QContactManager::NoError;
+    }
+
+    {
+        QContact contact(baseContact);
+        QContactGender gender;
+        gender.setGender(QLatin1String("UnknownGender"));
+        contact.saveDetail(&gender);
+        QTest::newRow("bad value (string)") << contact << baseContact << QContactManager::NoError;
+    }
+
+    {
+        QContact contact;
+        QContactGender gender;
+        gender.setGender(QLatin1String("UnknownGender"));
+        contact.saveDetail(&gender);
+        QTest::newRow("bad value (string)") << contact << QContact() << QContactManager::DoesNotExistError;
+    }
+}
+
+void tst_QContactManager::compatibleContact()
+{
+    QContactManager cm("memory");
+
+    QFETCH(QContact, input);
+    QFETCH(QContact, expected);
+    QFETCH(QContactManager::Error, error);
+    QCOMPARE(cm.compatibleContact(input), expected);
+    QCOMPARE(cm.error(), error);
+}
+
+void tst_QContactManager::contactValidation()
+{
+    /* Use the memory engine as a reference (validation is not engine specific) */
+    QScopedPointer<QContactManager> cm(new QContactManager("memory"));
+    QContact c;
+
+    /*
+     * Add some definitions for testing:
+     *
+     * 1) a unique detail
+     * 2) a detail with restricted values
+     * 3) a create only detail
+     * 4) a unique create only detail
+     */
+    QContactDetailDefinition uniqueDef;
+    QMap<QString, QContactDetailFieldDefinition> fields;
+    QContactDetailFieldDefinition field;
+    field.setDataType(QVariant::String);
+    fields.insert("value", field);
+
+    uniqueDef.setName("UniqueDetail");
+    uniqueDef.setFields(fields);
+    uniqueDef.setUnique(true);
+
+    QVERIFY(cm->saveDetailDefinition(uniqueDef));
+
+    QContactDetailDefinition restrictedDef;
+    restrictedDef.setName("RestrictedDetail");
+    fields.clear();
+    field.setAllowableValues(QVariantList() << "One" << "Two" << "Three");
+    fields.insert("value", field);
+    restrictedDef.setFields(fields);
+
+    QVERIFY(cm->saveDetailDefinition(restrictedDef));
+
+    // first, test an invalid definition
+    QContactDetail d1 = QContactDetail("UnknownDefinition");
+    d1.setValue("test", "test");
+    c.saveDetail(&d1);
+    QVERIFY(!cm->saveContact(&c));
+    QCOMPARE(cm->error(), QContactManager::InvalidDetailError);
+    c.removeDetail(&d1);
+
+    // second, test an invalid uniqueness constraint
+    QContactDetail d2 = QContactDetail("UniqueDetail");
+    d2.setValue("value", "test");
+    c.saveDetail(&d2);
+
+    // One unique should be ok
+    QVERIFY(cm->saveContact(&c));
+    QCOMPARE(cm->error(), QContactManager::NoError);
+
+    // Two uniques should not be ok
+    QContactDetail d3 = QContactDetail("UniqueDetail");
+    d3.setValue("value", "test2");
+    c.saveDetail(&d3);
+    QVERIFY(!cm->saveContact(&c));
+    QCOMPARE(cm->error(), QContactManager::AlreadyExistsError);
+    c.removeDetail(&d3);
+    c.removeDetail(&d2);
+
+    // third, test an invalid field name
+    QContactDetail d4 = QContactDetail(QContactPhoneNumber::DefinitionName);
+    d4.setValue("test", "test");
+    c.saveDetail(&d4);
+    QVERIFY(!cm->saveContact(&c));
+    QCOMPARE(cm->error(), QContactManager::InvalidDetailError);
+    c.removeDetail(&d4);
+
+    // fourth, test an invalid field data type
+    QContactDetail d5 = QContactDetail(QContactPhoneNumber::DefinitionName);
+    d5.setValue(QContactPhoneNumber::FieldNumber, QDateTime::currentDateTime());
+    c.saveDetail(&d5);
+    QVERIFY(!cm->saveContact(&c));
+    QCOMPARE(cm->error(), QContactManager::InvalidDetailError);
+    c.removeDetail(&d5);
+
+    // fifth, test an invalid field value (not in the allowed list)
+    QContactDetail d6 = QContactDetail("RestrictedDetail");
+    d6.setValue("value", "Seven"); // not in One, Two or Three
+    c.saveDetail(&d6);
+    QVERIFY(!cm->saveContact(&c));
+    QCOMPARE(cm->error(), QContactManager::InvalidDetailError);
+    c.removeDetail(&d6);
+
+    /* Now a valid value */
+    d6.setValue("value", "Two");
+    c.saveDetail(&d6);
+    QVERIFY(cm->saveContact(&c));
+    QCOMPARE(cm->error(), QContactManager::NoError);
+    c.removeDetail(&d6);
+
+    // Test a completely valid one.
+    QContactDetail d7 = QContactDetail(QContactPhoneNumber::DefinitionName);
+    d7.setValue(QContactPhoneNumber::FieldNumber, "0123456");
+    c.saveDetail(&d7);
+    QVERIFY(cm->saveContact(&c));
+    QCOMPARE(cm->error(), QContactManager::NoError);
+    c.removeDetail(&d7);
+}
+
+void tst_QContactManager::signalEmission()
+{
+    QTest::qWait(500); // clear the signal queue
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> m1(QContactManager::fromUri(uri));
+
+    qRegisterMetaType<QContactLocalId>("QContactLocalId");
+    qRegisterMetaType<QList<QContactLocalId> >("QList<QContactLocalId>");
+    QSignalSpy spyCA(m1.data(), SIGNAL(contactsAdded(QList<QContactLocalId>)));
+    QSignalSpy spyCM(m1.data(), SIGNAL(contactsChanged(QList<QContactLocalId>)));
+    QSignalSpy spyCR(m1.data(), SIGNAL(contactsRemoved(QList<QContactLocalId>)));
+
+    QList<QVariant> args;
+    QList<QContactLocalId> arg;
+    QContact c;
+    QList<QContact> batchAdd;
+    QList<QContactLocalId> batchRemove;
+    QList<QContactLocalId> sigids;
+    int addSigCount = 0; // the expected signal counts.
+    int modSigCount = 0;
+    int remSigCount = 0;
+
+    QContactDetailDefinition nameDef = m1->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact);
+
+    // verify add emits signal added
+    QContactName nc;
+    saveContactName(&c, nameDef, &nc, "John");
+    QVERIFY(m1->saveContact(&c));
+    QContactLocalId cid = c.id().localId();
+    addSigCount += 1;
+    QTRY_COMPARE(spyCA.count(), addSigCount);
+    args = spyCA.takeFirst();
+    addSigCount -= 1;
+    arg = args.first().value<QList<quint32> >();
+    QVERIFY(arg.count() == 1);
+    QCOMPARE(QContactLocalId(arg.at(0)), cid);
+
+    // verify save modified emits signal changed
+    saveContactName(&c, nameDef, &nc, "Citizen");
+    QVERIFY(m1->saveContact(&c));
+    modSigCount += 1;
+    QTRY_COMPARE(spyCM.count(), modSigCount);
+    args = spyCM.takeFirst();
+    modSigCount -= 1;
+    arg = args.first().value<QList<quint32> >();
+    QVERIFY(arg.count() == 1);
+    QCOMPARE(QContactLocalId(arg.at(0)), cid);
+
+    // verify remove emits signal removed
+    m1->removeContact(c.id().localId());
+    remSigCount += 1;
+    QTRY_COMPARE(spyCR.count(), remSigCount);
+    args = spyCR.takeFirst();
+    remSigCount -= 1;
+    arg = args.first().value<QList<quint32> >();
+    QVERIFY(arg.count() == 1);
+    QCOMPARE(QContactLocalId(arg.at(0)), cid);
+
+    // verify multiple adds works as advertised
+    QContact c2, c3;
+    QContactName nc2, nc3;
+    saveContactName(&c2, nameDef, &nc2, "Mark");
+    saveContactName(&c3, nameDef, &nc3, "Garry");
+#if defined(Q_OS_SYMBIAN)
+    // TODO: symbiansim backend fails this test currently. Commented out for
+    // now. Will be fixed later.
+    if(!uri.contains("symbiansim")) {
+        QVERIFY(!m1->saveContact(&c)); // saving contact with nonexistent id fails
+    }
+#endif
+    QVERIFY(m1->saveContact(&c2));
+    addSigCount += 1;
+    QVERIFY(m1->saveContact(&c3));
+    addSigCount += 1;
+    QTRY_COMPARE(spyCM.count(), modSigCount);
+    QTRY_COMPARE(spyCA.count(), addSigCount);
+
+    // verify multiple modifies works as advertised
+    saveContactName(&c2, nameDef, &nc2, "M.");
+    QVERIFY(m1->saveContact(&c2));
+    modSigCount += 1;
+    saveContactName(&c2, nameDef, &nc2, "Mark");
+    saveContactName(&c3, nameDef, &nc3, "G.");
+    QVERIFY(m1->saveContact(&c2));
+    modSigCount += 1;
+    QVERIFY(m1->saveContact(&c3));
+    modSigCount += 1;
+    QTRY_COMPARE(spyCM.count(), modSigCount);
+
+    // verify multiple removes works as advertised
+    m1->removeContact(c3.id().localId());
+    remSigCount += 1;
+    m1->removeContact(c2.id().localId());
+    remSigCount += 1;
+    QTRY_COMPARE(spyCR.count(), remSigCount);
+
+    QVERIFY(!m1->removeContact(c.id().localId())); // not saved.
+
+    /* Now test the batch equivalents */
+    spyCA.clear();
+    spyCM.clear();
+    spyCR.clear();
+
+    /* Batch adds - set ids to zero so add succeeds. */
+    c.setId(QContactId());
+    c2.setId(QContactId());
+    c3.setId(QContactId());
+    batchAdd << c << c2 << c3;
+    QMap<int, QContactManager::Error> errorMap;
+    QVERIFY(m1->saveContacts(&batchAdd, &errorMap));
+
+    QVERIFY(batchAdd.count() == 3);
+    c = batchAdd.at(0);
+    c2 = batchAdd.at(1);
+    c3 = batchAdd.at(2);
+
+    /* We basically loop, processing events, until we've seen an Add signal for each contact */
+    sigids.clear();
+
+    QTRY_WAIT( while(spyCA.size() > 0) {sigids += spyCA.takeFirst().at(0).value<QList<QContactLocalId> >(); }, sigids.contains(c.localId()) && sigids.contains(c2.localId()) && sigids.contains(c3.localId()));
+    QTRY_COMPARE(spyCM.count(), 0);
+    QTRY_COMPARE(spyCR.count(), 0);
+
+    /* Batch modifies */
+    QContactName modifiedName = c.detail(QContactName::DefinitionName);
+    saveContactName(&c, nameDef, &modifiedName, "Modified number 1");
+    modifiedName = c2.detail(QContactName::DefinitionName);
+    saveContactName(&c2, nameDef, &modifiedName, "Modified number 2");
+    modifiedName = c3.detail(QContactName::DefinitionName);
+    saveContactName(&c3, nameDef, &modifiedName, "Modified number 3");
+
+    batchAdd.clear();
+    batchAdd << c << c2 << c3;
+    QVERIFY(m1->saveContacts(&batchAdd, &errorMap));
+
+    sigids.clear();
+    QTRY_WAIT( while(spyCM.size() > 0) {sigids += spyCM.takeFirst().at(0).value<QList<QContactLocalId> >(); }, sigids.contains(c.localId()) && sigids.contains(c2.localId()) && sigids.contains(c3.localId()));
+
+    /* Batch removes */
+    batchRemove << c.id().localId() << c2.id().localId() << c3.id().localId();
+    QVERIFY(m1->removeContacts(batchRemove, &errorMap));
+
+    sigids.clear();
+    QTRY_WAIT( while(spyCR.size() > 0) {sigids += spyCR.takeFirst().at(0).value<QList<QContactLocalId> >(); }, sigids.contains(c.localId()) && sigids.contains(c2.localId()) && sigids.contains(c3.localId()));
+
+    QTRY_COMPARE(spyCA.count(), 0);
+    QTRY_COMPARE(spyCM.count(), 0);
+
+    QScopedPointer<QContactManager> m2(QContactManager::fromUri(uri));
+    
+    // During construction SIM backend (m2) will try writing contacts with 
+    // nickname, email and additional number to find out if the SIM card
+    // will support these fields. The other backend (m1) will then receive
+    // signals about that. These need to be caught so they don't interfere
+    // with the tests. (This trial and error method is used because existing
+    // API for checking the availability of these fields is not public.)
+	// NOTE: This applies only to pre 10.1 platforms (S60 3.1, 3.2, ect.)
+    if (uri.contains("symbiansim")) {
+        QTest::qWait(0);
+        spyCA.clear();
+        spyCM.clear();
+        spyCR.clear();
+    }
+
+    QVERIFY(m1->hasFeature(QContactManager::Anonymous) ==
+        m2->hasFeature(QContactManager::Anonymous));
+
+    /* Now some cross manager testing */
+    if (!m1->hasFeature(QContactManager::Anonymous)) {
+        // verify that signals are emitted for modifications made to other managers (same id).
+        QContactName ncs = c.detail(QContactName::DefinitionName);
+        saveContactName(&c, nameDef, &ncs, "Test");
+        c.setId(QContactId()); // reset id so save can succeed.
+        QVERIFY(m2->saveContact(&c));
+        saveContactName(&c, nameDef, &ncs, "Test2");
+        QVERIFY(m2->saveContact(&c));
+        QTRY_COMPARE(spyCA.count(), 1); // check that we received the update signals.
+        QTRY_COMPARE(spyCM.count(), 1); // check that we received the update signals.
+        m2->removeContact(c.localId());
+        QTRY_COMPARE(spyCR.count(), 1); // check that we received the remove signal.
+    }
+}
+
+void tst_QContactManager::errorStayingPut()
+{
+    /* Make sure that when we clone a manager, we don't clone the error */
+    QMap<QString, QString> params;
+    params.insert("id", "error isolation test");
+    QContactManager m1("memory",params);
+
+    QVERIFY(m1.error() == QContactManager::NoError);
+
+    /* Remove an invalid contact to get an error */
+    QVERIFY(m1.removeContact(0) == false);
+    QVERIFY(m1.error() == QContactManager::DoesNotExistError);
+
+    /* Create a new manager with hopefully the same backend */
+    QContactManager m2("memory", params);
+
+    QVERIFY(m1.error() == QContactManager::DoesNotExistError);
+    QVERIFY(m2.error() == QContactManager::NoError);
+
+    /* Cause an error on the other ones and check the first is not affected */
+    m2.saveContacts(0, 0);
+    QVERIFY(m1.error() == QContactManager::DoesNotExistError);
+    QVERIFY(m2.error() == QContactManager::BadArgumentError);
+
+    QContact c;
+    QContactDetail d("This does not exist and if it does this will break");
+    d.setValue("Value that also doesn't exist", 5);
+    c.saveDetail(&d);
+
+    QVERIFY(m1.saveContact(&c) == false);
+    QVERIFY(m1.error() == QContactManager::InvalidDetailError);
+    QVERIFY(m2.error() == QContactManager::BadArgumentError);
+}
+
+void tst_QContactManager::detailDefinitions()
+{
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
+    QMap<QString, QContactDetailDefinition> defs = cm->detailDefinitions();
+
+    /* Validate the existing definitions */
+
+    // Do some sanity checking on the definitions first
+    if (defs.keys().count() != defs.uniqueKeys().count()) {
+        qDebug() << "ERROR - duplicate definitions with the same name:";
+
+        QList<QString> defkeys = defs.keys();
+        foreach(QString uniq, defs.uniqueKeys()) {
+            if (defkeys.count(uniq) > 1) {
+                qDebug() << QString(" %1").arg(uniq).toAscii().constData();
+                defkeys.removeAll(uniq);
+            }
+        }
+        QVERIFY(defs.keys().count() == defs.uniqueKeys().count());
+    }
+
+    foreach(QContactDetailDefinition def, defs.values()) {
+        QMap<QString, QContactDetailFieldDefinition> fields = def.fields();
+
+        // Again some sanity checking
+        if (fields.keys().count() != fields.uniqueKeys().count()) {
+            qDebug() << "ERROR - duplicate fields with the same name:";
+
+            QList<QString> defkeys = fields.keys();
+            foreach(QString uniq, fields.uniqueKeys()) {
+                if (defkeys.count(uniq) > 1) {
+                    qDebug() << QString(" %2::%1").arg(uniq).arg(def.name()).toAscii().constData();
+                    defkeys.removeAll(uniq);
+                }
+            }
+            QVERIFY(fields.keys().count() == fields.uniqueKeys().count());
+        }
+
+        foreach(QContactDetailFieldDefinition field, def.fields().values()) {
+            // Sanity check the allowed values
+            if (field.allowableValues().count() > 0) {
+                if (field.dataType() == QVariant::StringList) {
+                    // We accept QString or QStringList allowed values
+                    foreach(QVariant var, field.allowableValues()) {
+                        if (var.type() != QVariant::String && var.type() != QVariant::StringList) {
+                            QString foo;
+                            QDebug dbg(&foo);
+                            dbg.nospace() << var;
+                            qDebug().nospace() << "Field " << QString("%1::%2").arg(def.name()).arg(def.fields().key(field)).toAscii().constData() << " allowable value '" << foo.simplified().toAscii().constData() << "' not supported for field type " << QMetaType::typeName(field.dataType());
+                        }
+                        QVERIFY(var.type() == QVariant::String || var.type() == QVariant::StringList);
+                    }
+                } else if (field.dataType() == QVariant::List || field.dataType() == QVariant::Map || field.dataType() == (QVariant::Type) qMetaTypeId<QVariant>()) {
+                    // Well, anything goes
+                } else {
+                    // The type of each allowed value must match the data type
+                    foreach(QVariant var, field.allowableValues()) {
+                        if (var.type() != field.dataType()) {
+                            QString foo;
+                            QDebug dbg(&foo);
+                            dbg.nospace() << var;
+                            qDebug().nospace() << "Field " << QString("%1::%2").arg(def.name()).arg(def.fields().key(field)).toAscii().constData() << " allowable value '" << foo.simplified().toAscii().constData() << "' not supported for field type " << QMetaType::typeName(field.dataType());
+                        }
+                        QVERIFY(var.type() == field.dataType());
+                    }
+                }
+            }
+        }
+    }
+
+
+    /* Try to make a credible definition */
+    QContactDetailDefinition newDef;
+    QContactDetailFieldDefinition field;
+    QMap<QString, QContactDetailFieldDefinition> fields;
+    field.setDataType(cm->supportedDataTypes().value(0));
+    fields.insert("New Value", field);
+    newDef.setName("New Definition");
+    newDef.setFields(fields);
+
+    /* Updated version of an existing definition */
+    QContactDetailDefinition updatedDef = defs.begin().value(); // XXX TODO Fixme
+    fields = updatedDef.fields();
+    fields.insert("New Value", field);
+    updatedDef.setFields(fields);
+
+    /* A detail definition with valid allowed values (or really just one) */
+    QContactDetailDefinition allowedDef = newDef;
+    field.setAllowableValues(field.allowableValues() << (QVariant(field.dataType())));
+    fields.clear();
+    fields.insert("Restricted value", field);
+    allowedDef.setFields(fields);
+
+    /* Many invalid definitions */
+    QContactDetailDefinition noIdDef;
+    noIdDef.setFields(fields);
+
+    QContactDetailDefinition noFieldsDef;
+    noFieldsDef.setName("No fields");
+
+    QContactDetailDefinition invalidFieldKeyDef;
+    invalidFieldKeyDef.setName("Invalid field key");
+    QMap<QString, QContactDetailFieldDefinition> badfields;
+    badfields.insert(QString(), field);
+    invalidFieldKeyDef.setFields(badfields);
+
+    QContactDetailDefinition invalidFieldTypeDef;
+    invalidFieldTypeDef.setName("Invalid field type");
+    badfields.clear();
+    QContactDetailFieldDefinition badfield;
+    badfield.setDataType((QVariant::Type) qMetaTypeId<UnsupportedMetatype>());
+    badfields.insert("Bad type", badfield);
+    invalidFieldTypeDef.setFields(badfields);
+
+    QContactDetailDefinition invalidAllowedValuesDef;
+    invalidAllowedValuesDef.setName("Invalid field allowed values");
+    badfields.clear();
+    badfield.setDataType(field.dataType()); // use a supported type
+    badfield.setAllowableValues(QList<QVariant>() << "String" << 5); // but unsupported value
+    badfields.insert("Bad allowed", badfield);
+    invalidAllowedValuesDef.setFields(badfields);
+
+    /* XXX Multiply defined fields.. depends on semantichangeSet. */
+
+    if (cm->hasFeature(QContactManager::MutableDefinitions)) {
+        /* First do some negative testing */
+
+        /* Bad add class */
+        QVERIFY(cm->saveDetailDefinition(QContactDetailDefinition()) == false);
+        QVERIFY(cm->error() == QContactManager::BadArgumentError);
+
+        /* Bad remove string */
+        QVERIFY(cm->removeDetailDefinition(QString()) == false);
+        QVERIFY(cm->error() == QContactManager::BadArgumentError);
+
+        QVERIFY(cm->saveDetailDefinition(noIdDef) == false);
+        QVERIFY(cm->error() == QContactManager::BadArgumentError);
+
+        QVERIFY(cm->saveDetailDefinition(noFieldsDef) == false);
+        QVERIFY(cm->error() == QContactManager::BadArgumentError);
+
+        QVERIFY(cm->saveDetailDefinition(invalidFieldKeyDef) == false);
+        QVERIFY(cm->error() == QContactManager::BadArgumentError);
+
+        QVERIFY(cm->saveDetailDefinition(invalidFieldTypeDef) == false);
+        QVERIFY(cm->error() == QContactManager::BadArgumentError);
+
+        QVERIFY(cm->saveDetailDefinition(invalidAllowedValuesDef) == false);
+        QVERIFY(cm->error() == QContactManager::BadArgumentError);
+
+        /* Check that our new definition doesn't already exist */
+        QVERIFY(cm->detailDefinition(newDef.name()).isEmpty());
+        QVERIFY(cm->error() == QContactManager::DoesNotExistError);
+
+        QVERIFY(cm->removeDetailDefinition(newDef.name()) == false);
+        QVERIFY(cm->error() == QContactManager::DoesNotExistError);
+
+        /* Add a new definition */
+        QVERIFY(cm->saveDetailDefinition(newDef) == true);
+        QVERIFY(cm->error() == QContactManager::NoError);
+
+        /* Now retrieve it */
+        QContactDetailDefinition def = cm->detailDefinition(newDef.name());
+        QVERIFY(def == newDef);
+
+        /* Update it */
+        QMap<QString, QContactDetailFieldDefinition> newFields = def.fields();
+        newFields.insert("Another new value", field);
+        newDef.setFields(newFields);
+
+        QVERIFY(cm->saveDetailDefinition(newDef) == true);
+        QVERIFY(cm->error() == QContactManager::NoError);
+
+        QVERIFY(cm->detailDefinition(newDef.name()) == newDef);
+
+        /* Remove it */
+        QVERIFY(cm->removeDetailDefinition(newDef.name()) == true);
+        QVERIFY(cm->error() == QContactManager::NoError);
+
+        /* and make sure it does not exist any more */
+        QVERIFY(cm->detailDefinition(newDef.name()) == QContactDetailDefinition());
+        QVERIFY(cm->error() == QContactManager::DoesNotExistError);
+
+        /* Add the other good one */
+        QVERIFY(cm->saveDetailDefinition(allowedDef) == true);
+        QVERIFY(cm->error() == QContactManager::NoError);
+
+        QVERIFY(allowedDef == cm->detailDefinition(allowedDef.name()));
+
+        /* and remove it */
+        QVERIFY(cm->removeDetailDefinition(allowedDef.name()) == true);
+        QVERIFY(cm->detailDefinition(allowedDef.name()) == QContactDetailDefinition());
+        QVERIFY(cm->error() == QContactManager::DoesNotExistError);
+
+    } else {
+        /* Bad add class */
+        QVERIFY(cm->saveDetailDefinition(QContactDetailDefinition()) == false);
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+
+        /* Make sure we can't add/remove/modify detail definitions */
+        QVERIFY(cm->removeDetailDefinition(QString()) == false);
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+
+        /* Try updating an existing definition */
+        QVERIFY(cm->saveDetailDefinition(updatedDef) == false);
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+
+        /* Try removing an existing definition */
+        QVERIFY(cm->removeDetailDefinition(updatedDef.name()) == false);
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+    }
+}
+
+void tst_QContactManager::displayName()
+{
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
+
+    /*
+     * Very similar to the tst_QContact functions, except we test
+     * saving and retrieving contacts updates the display label
+     */
+
+    /* Try to make this a bit more consistent by using a single name */
+    QContact d;
+    QContactName name;
+    saveContactName(&d, cm->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact), &name, "Wesley");
+
+    QVERIFY(d.displayLabel().isEmpty());
+
+    QString synth = cm->synthesizedContactDisplayLabel(d);
+
+    // Make sure this doesn't crash
+    cm->synthesizeContactDisplayLabel(0);
+
+    // Make sure this gives the same results
+    cm->synthesizeContactDisplayLabel(&d);
+    QCOMPARE(d.displayLabel(), synth);
+
+    /*
+     * The display label is not updated until you save the contact or call synthCDL
+     */
+    QVERIFY(cm->saveContact(&d));
+    d = cm->contact(d.id().localId());
+    QVERIFY(!d.isEmpty());
+    QCOMPARE(d.displayLabel(), synth);
+
+    /* Remove the detail via removeDetail */
+    QContactDisplayLabel old;
+    int count = d.details().count();
+    QVERIFY(!d.removeDetail(&old)); // should fail.
+    QVERIFY(d.isEmpty() == false);
+    QVERIFY(d.details().count() == count); // it should not be removed!
+
+    /* Save the contact again */
+    QVERIFY(cm->saveContact(&d));
+    d = cm->contact(d.id().localId());
+    QVERIFY(!d.isEmpty());
+
+    /* Make sure the label is still the synth version */
+    QCOMPARE(d.displayLabel(), synth);
+
+    /* And delete the contact */
+    QVERIFY(cm->removeContact(d.id().localId()));
+}
+
+void tst_QContactManager::actionPreferences()
+{
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
+
+    // early out if the manager doesn't support action preference saving.
+    if (!cm->hasFeature(QContactManager::ActionPreferences)) {
+        QSKIP("Manager does not support action preferences", SkipSingle);
+    }
+
+    // create a sample contact
+    QContactAvatar a;
+    a.setImageUrl(QUrl("test.png"));
+    QContactPhoneNumber p1;
+    p1.setNumber("12345");
+    QContactPhoneNumber p2;
+    p2.setNumber("34567");
+    QContactPhoneNumber p3;
+    p3.setNumber("56789");
+    QContactUrl u;
+    u.setUrl("http://test.nokia.com");
+    QContactName n;
+    QContact c;
+    saveContactName(&c, cm->detailDefinition(QContactName::DefinitionName, QContactType::TypeContact), &n, "TestContact");
+    c.saveDetail(&a);
+    c.saveDetail(&p1);
+    c.saveDetail(&p2);
+    c.saveDetail(&p3);
+    c.saveDetail(&u);
+
+    QVERIFY(cm->saveContact(&c));          // save the contact
+    QContact loaded = cm->contact(c.id().localId()); // reload the contact
+    cm->removeContact(c.id().localId());
+}
+
+void tst_QContactManager::changeSet()
+{
+    QContactLocalId id(1);
+
+    QContactChangeSet changeSet;
+    QVERIFY(changeSet.addedContacts().isEmpty());
+    QVERIFY(changeSet.changedContacts().isEmpty());
+    QVERIFY(changeSet.removedContacts().isEmpty());
+
+    changeSet.insertAddedContact(id);
+    QVERIFY(!changeSet.addedContacts().isEmpty());
+    QVERIFY(changeSet.changedContacts().isEmpty());
+    QVERIFY(changeSet.removedContacts().isEmpty());
+    QVERIFY(changeSet.addedContacts().contains(id));
+
+    changeSet.insertChangedContact(id);
+    changeSet.insertChangedContacts(QList<QContactLocalId>() << id);
+    QVERIFY(changeSet.changedContacts().size() == 1); // set, should only be added once.
+    QVERIFY(!changeSet.addedContacts().isEmpty());
+    QVERIFY(!changeSet.changedContacts().isEmpty());
+    QVERIFY(changeSet.removedContacts().isEmpty());
+    QVERIFY(changeSet.changedContacts().contains(id));
+    changeSet.clearChangedContacts();
+    QVERIFY(changeSet.changedContacts().isEmpty());
+
+    changeSet.insertRemovedContacts(QList<QContactLocalId>() << id);
+    QVERIFY(changeSet.removedContacts().contains(id));
+    changeSet.clearRemovedContacts();
+    QVERIFY(changeSet.removedContacts().isEmpty());
+
+    QVERIFY(changeSet.dataChanged() == false);
+    QContactChangeSet changeSet2;
+    changeSet2 = changeSet;
+    QVERIFY(changeSet.addedContacts() == changeSet2.addedContacts());
+    changeSet.emitSignals(0);
+
+    changeSet2.clearAddedContacts();
+    QVERIFY(changeSet2.addedContacts().isEmpty());
+    changeSet2.insertAddedContacts(changeSet.addedContacts().toList());
+    QVERIFY(changeSet.addedContacts() == changeSet2.addedContacts());
+
+    changeSet2.clearAll();
+    QVERIFY(changeSet.addedContacts() != changeSet2.addedContacts());
+
+    QContactChangeSet changeSet3(changeSet2);
+    QVERIFY(changeSet.addedContacts() != changeSet3.addedContacts());
+    QVERIFY(changeSet2.addedContacts() == changeSet3.addedContacts());
+
+    changeSet.setDataChanged(true);
+    QVERIFY(changeSet.dataChanged() == true);
+    QVERIFY(changeSet.dataChanged() != changeSet2.dataChanged());
+    QVERIFY(changeSet.dataChanged() != changeSet3.dataChanged());
+    changeSet.emitSignals(0);
+
+    changeSet.addedRelationshipsContacts().insert(id);
+    changeSet.insertAddedRelationshipsContacts(QList<QContactLocalId>() << id);
+    QVERIFY(changeSet.addedRelationshipsContacts().contains(id));
+    changeSet.clearAddedRelationshipsContacts();
+    QVERIFY(changeSet.addedRelationshipsContacts().isEmpty());
+    changeSet.insertRemovedRelationshipsContacts(QList<QContactLocalId>() << id);
+    QVERIFY(changeSet.removedRelationshipsContacts().contains(id));
+    changeSet.clearRemovedRelationshipsContacts();
+    QVERIFY(changeSet.removedRelationshipsContacts().isEmpty());
+    changeSet.emitSignals(0);
+    changeSet.removedRelationshipsContacts().insert(id);
+    changeSet.emitSignals(0);
+
+    changeSet.setOldAndNewSelfContactId(QPair<QContactLocalId, QContactLocalId>(QContactLocalId(0), id));
+    changeSet2 = changeSet;
+    QVERIFY(changeSet2.addedRelationshipsContacts() == changeSet.addedRelationshipsContacts());
+    QVERIFY(changeSet2.removedRelationshipsContacts() == changeSet.removedRelationshipsContacts());
+    QVERIFY(changeSet2.oldAndNewSelfContactId() == changeSet.oldAndNewSelfContactId());
+    changeSet.emitSignals(0);
+    changeSet.setOldAndNewSelfContactId(QPair<QContactLocalId, QContactLocalId>(id, QContactLocalId(0)));
+    QVERIFY(changeSet2.oldAndNewSelfContactId() != changeSet.oldAndNewSelfContactId());
+    changeSet.setDataChanged(true);
+    changeSet.emitSignals(0);
+}
+
+void tst_QContactManager::fetchHint()
+{
+    QContactFetchHint hint;
+    hint.setOptimizationHints(QContactFetchHint::NoBinaryBlobs);
+    QCOMPARE(hint.optimizationHints(), QContactFetchHint::NoBinaryBlobs);
+    QStringList rels;
+    rels << QString(QLatin1String(QContactRelationship::HasMember));
+    hint.setRelationshipTypesHint(rels);
+    QCOMPARE(hint.relationshipTypesHint(), rels);
+}
+
+void tst_QContactManager::selfContactId()
+{
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
+
+    // early out if the manager doesn't support self contact id saving
+    QContactLocalId selfContact = cm->selfContactId();
+    if (!cm->hasFeature(QContactManager::SelfContact)) {
+        // ensure that the error codes / return values are meaningful failures.
+        QEXPECT_FAIL("mgr='maemo5'", "maemo5 supports getting the self contact but not setting it.", Continue);
+        QVERIFY(cm->error() == QContactManager::DoesNotExistError);
+        QVERIFY(!cm->setSelfContactId(QContactLocalId(123)));
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+        QSKIP("Manager does not support the concept of a self-contact", SkipSingle);
+    }
+
+    // create a new "self" contact and retrieve its Id
+    QVERIFY(cm->error() == QContactManager::NoError || cm->error() == QContactManager::DoesNotExistError);
+    QContact self;
+    QContactPhoneNumber selfPhn;
+    selfPhn.setNumber("12345");
+    self.saveDetail(&selfPhn);
+    if (!cm->saveContact(&self)) {
+        QSKIP("Unable to save the generated self contact", SkipSingle);
+    }
+    QContactLocalId newSelfContact = self.localId();
+
+    // Setup signal spy
+    qRegisterMetaType<QContactLocalId>("QContactLocalId");
+    QSignalSpy spy(cm.data(), SIGNAL(selfContactIdChanged(QContactLocalId,QContactLocalId)));
+
+    // Set new self contact
+    QVERIFY(cm->setSelfContactId(newSelfContact));
+    QVERIFY(cm->error() == QContactManager::NoError);
+    QTRY_VERIFY(spy.count() == 1);
+    QVERIFY(spy.at(0).count() == 2);
+    // note: for some reason qvariant_cast<QContactLocalId>(spy.at(0).at(0)) returns always zero
+    // because the type is not recognized. Hence the ugly casting below.
+    QVERIFY(*((const QContactLocalId*) spy.at(0).at(0).constData()) == selfContact);
+    QVERIFY(*((const QContactLocalId*) spy.at(0).at(1).constData()) == newSelfContact);
+    QVERIFY(cm->selfContactId() == newSelfContact);
+
+    // Remove self contact
+    if(!cm->removeContact(self.localId())) {
+        QSKIP("Unable to remove self contact", SkipSingle);
+    }
+    QTRY_VERIFY(spy.count() == 2);
+    QVERIFY(spy.at(1).count() == 2);
+    QVERIFY(*((const QContactLocalId*) spy.at(1).at(0).constData()) == newSelfContact);
+    QVERIFY(*((const QContactLocalId*) spy.at(1).at(1).constData()) == QContactLocalId(0));
+    QVERIFY(cm->selfContactId() == QContactLocalId(0)); // ensure reset after removed.
+
+    // reset to original state.
+    cm->setSelfContactId(selfContact);
+}
+
+QList<QContactDetail> tst_QContactManager::removeAllDefaultDetails(const QList<QContactDetail>& details)
+{
+    QList<QContactDetail> newlist;
+    foreach (const QContactDetail d, details) {
+        if (d.definitionName() != QContactDisplayLabel::DefinitionName
+                && d.definitionName() != QContactType::DefinitionName
+                && d.definitionName() != QContactTimestamp::DefinitionName) {
+            newlist << d;
+        }
+    }
+    return newlist;
+}
+
+void tst_QContactManager::detailOrders()
+{
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
+
+    if (!cm->hasFeature(QContactManager::DetailOrdering))
+        QSKIP("Skipping: This manager does not support detail ordering!", SkipSingle);
+
+    QContact a;
+    //phone numbers
+
+    QContactDetailDefinition d = cm->detailDefinition(QContactPhoneNumber::DefinitionName, QContactType::TypeContact);
+    QContactDetailFieldDefinition supportedContexts = d.fields().value(QContactDetail::FieldContext);
+    QString contextOther = QContactDetail::ContextOther;
+    if (!supportedContexts.allowableValues().contains(contextOther)) {
+        contextOther = QString();
+    }    
+    
+    QContactPhoneNumber number1, number2, number3;
+    
+    number1.setNumber("11111111");
+    number1.setContexts(QContactPhoneNumber::ContextHome);
+
+    number2.setNumber("22222222");
+    number2.setContexts(QContactPhoneNumber::ContextWork);
+
+    number3.setNumber("33333333");
+    if (!contextOther.isEmpty())
+        number3.setContexts(contextOther);
+
+    a.saveDetail(&number1);
+    a.saveDetail(&number2);
+    a.saveDetail(&number3);
+
+    QVERIFY(cm->saveContact(&a));
+    a = cm->contact(a.id().localId());
+    
+    QList<QContactDetail> details = a.details(QContactPhoneNumber::DefinitionName);
+    QVERIFY(details.count() == 3);
+    QVERIFY(details.at(0).value(QContactPhoneNumber::FieldContext) == QContactPhoneNumber::ContextHome);
+    QVERIFY(details.at(1).value(QContactPhoneNumber::FieldContext) == QContactPhoneNumber::ContextWork);
+    QVERIFY(details.at(2).value(QContactPhoneNumber::FieldContext) == contextOther);
+    
+    QVERIFY(a.removeDetail(&number2));
+    QVERIFY(cm->saveContact(&a));
+    a = cm->contact(a.id().localId());
+    details = a.details(QContactPhoneNumber::DefinitionName);
+    QVERIFY(details.count() == 2);
+    QVERIFY(details.at(0).value(QContactPhoneNumber::FieldContext) == QContactPhoneNumber::ContextHome);
+    QVERIFY(details.at(1).value(QContactPhoneNumber::FieldContext) == contextOther);
+
+    a.saveDetail(&number2);
+    QVERIFY(cm->saveContact(&a));
+    a = cm->contact(a.id().localId());
+    
+    details = a.details(QContactPhoneNumber::DefinitionName);
+    QVERIFY(details.count() == 3);
+    QVERIFY(details.at(0).value(QContactPhoneNumber::FieldContext) == QContactPhoneNumber::ContextHome);
+    QVERIFY(details.at(1).value(QContactPhoneNumber::FieldContext) == contextOther);
+    QVERIFY(details.at(2).value(QContactPhoneNumber::FieldContext) == QContactPhoneNumber::ContextWork);
+
+    //addresses
+    
+    d = cm->detailDefinition(QContactAddress::DefinitionName, QContactType::TypeContact);
+    supportedContexts = d.fields().value(QContactDetail::FieldContext);
+    contextOther = QString(QLatin1String(QContactDetail::ContextOther));
+    if (!supportedContexts.allowableValues().contains(contextOther)) {
+        contextOther = QString();
+    }     
+    
+    QContactAddress address1, address2, address3;
+    
+    address1.setStreet("Brandl St");
+    address1.setRegion("Brisbane");
+    address3 = address2 = address1;
+
+    address1.setContexts(QContactAddress::ContextHome);
+    address2.setContexts(QContactAddress::ContextWork);
+    if (!contextOther.isEmpty())
+        address3.setContexts(contextOther);
+
+    a.saveDetail(&address1);
+    a.saveDetail(&address2);
+    a.saveDetail(&address3);
+
+    QVERIFY(cm->saveContact(&a));
+    a = cm->contact(a.id().localId());
+    
+    details = a.details(QContactAddress::DefinitionName);
+    QVERIFY(details.count() == 3);
+    
+    QVERIFY(details.at(0).value(QContactAddress::FieldContext) == QContactAddress::ContextHome);
+    QVERIFY(details.at(1).value(QContactAddress::FieldContext) == QContactAddress::ContextWork);
+    QVERIFY(details.at(2).value(QContactAddress::FieldContext) == contextOther);
+
+    QVERIFY(a.removeDetail(&address2));
+    QVERIFY(cm->saveContact(&a));
+    a = cm->contact(a.id().localId());
+    details = a.details(QContactAddress::DefinitionName);
+    QVERIFY(details.count() == 2);
+    QVERIFY(details.at(0).value(QContactAddress::FieldContext) == QContactAddress::ContextHome);
+    QVERIFY(details.at(1).value(QContactAddress::FieldContext) == contextOther);
+
+    a.saveDetail(&address2);
+    QVERIFY(cm->saveContact(&a));
+    a = cm->contact(a.id().localId());
+    
+    details = a.details(QContactAddress::DefinitionName);
+    QVERIFY(details.count() == 3);
+    QVERIFY(details.at(0).value(QContactAddress::FieldContext) == QContactAddress::ContextHome);
+    QVERIFY(details.at(1).value(QContactAddress::FieldContext) == contextOther);
+    QVERIFY(details.at(2).value(QContactAddress::FieldContext) == QContactAddress::ContextWork);
+
+
+    //emails
+    d = cm->detailDefinition(QContactEmailAddress::DefinitionName, QContactType::TypeContact);
+    supportedContexts = d.fields().value(QContactDetail::FieldContext);
+    contextOther = QString(QLatin1String(QContactDetail::ContextOther));
+    if (!supportedContexts.allowableValues().contains(contextOther)) {
+        contextOther = QString();
+    }      
+    
+    QContactEmailAddress email1, email2, email3;
+
+    email1.setEmailAddress("aaron@example.com");
+    email3 = email2 = email1;
+    email1.setContexts(QContactEmailAddress::ContextHome);
+    email2.setContexts(QContactEmailAddress::ContextWork);
+    if (!contextOther.isEmpty())
+        email3.setContexts(contextOther);
+
+    a.saveDetail(&email1);
+    a.saveDetail(&email2);
+    a.saveDetail(&email3);
+
+    QVERIFY(cm->saveContact(&a));
+    a = cm->contact(a.id().localId());
+    
+    details = a.details(QContactEmailAddress::DefinitionName);
+    QVERIFY(details.count() == 3);
+    
+    QVERIFY(details.at(0).value(QContactEmailAddress::FieldContext) == QContactEmailAddress::ContextHome);
+    QVERIFY(details.at(1).value(QContactEmailAddress::FieldContext) == QContactEmailAddress::ContextWork);
+    QVERIFY(details.at(2).value(QContactEmailAddress::FieldContext) == contextOther);
+
+    QVERIFY(a.removeDetail(&email2));
+    QVERIFY(cm->saveContact(&a));
+    a = cm->contact(a.id().localId());
+    details = a.details(QContactEmailAddress::DefinitionName);
+    QVERIFY(details.count() == 2);
+    QVERIFY(details.at(0).value(QContactEmailAddress::FieldContext) == QContactEmailAddress::ContextHome);
+    QVERIFY(details.at(1).value(QContactEmailAddress::FieldContext) == contextOther);
+
+    a.saveDetail(&email2);
+    QVERIFY(cm->saveContact(&a));
+    a = cm->contact(a.id().localId());
+    
+    details = a.details(QContactEmailAddress::DefinitionName);
+    QVERIFY(details.count() == 3);
+    QVERIFY(details.at(0).value(QContactEmailAddress::FieldContext) == QContactEmailAddress::ContextHome);
+    QVERIFY(details.at(1).value(QContactEmailAddress::FieldContext) == contextOther);
+    QVERIFY(details.at(2).value(QContactEmailAddress::FieldContext) == QContactEmailAddress::ContextWork);
+
+    QVERIFY(cm->removeContact(a.id().localId()));
+    QVERIFY(cm->error() == QContactManager::NoError);
+}
+
+void tst_QContactManager::relationships()
+{
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
+
+    // save some contacts
+    QContact source;
+    QContact dest1, dest2, dest3, dest4;
+    QContactPhoneNumber n1, n2, n3, n4;
+    n1.setNumber("1");
+    n2.setNumber("2");
+    n3.setNumber("3");
+    n4.setNumber("4");
+
+    dest1.saveDetail(&n1);
+    dest2.saveDetail(&n2);
+    dest3.saveDetail(&n3);
+    dest4.saveDetail(&n3);
+
+    cm->saveContact(&source);
+    cm->saveContact(&dest1);
+    cm->saveContact(&dest2);
+    cm->saveContact(&dest3);
+    cm->saveContact(&dest4);
+
+    // check if manager supports relationships
+    if (!cm->hasFeature(QContactManager::Relationships)) {
+        // ensure that the operations all fail as required.
+        QContactRelationship r1, r2, r3;
+        r1.setFirst(source.id());
+        r1.setSecond(dest1.id());
+        r1.setRelationshipType(QContactRelationship::HasManager);
+        r2.setFirst(source.id());
+        r2.setSecond(dest2.id());
+        r2.setRelationshipType(QContactRelationship::HasManager);
+        r3.setFirst(source.id());
+        r3.setSecond(dest3.id());
+        r3.setRelationshipType(QContactRelationship::HasManager);
+
+        QList<QContactRelationship> batchList;
+        batchList << r2 << r3;
+
+        // test save and remove
+        QVERIFY(!cm->saveRelationship(&r1));
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+        QVERIFY(!cm->removeRelationship(r1));
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+        cm->saveRelationships(&batchList, NULL);
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+
+        // test retrieval
+        QList<QContactRelationship> retrieveList;
+        retrieveList = cm->relationships(source.id(), QContactRelationship::First);
+        QVERIFY(retrieveList.isEmpty());
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+        retrieveList = cm->relationships(source.id(), QContactRelationship::Second);
+        QVERIFY(retrieveList.isEmpty());
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+        retrieveList = cm->relationships(source.id(), QContactRelationship::Either); // Either
+        QVERIFY(retrieveList.isEmpty());
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+
+
+        retrieveList = cm->relationships(QContactRelationship::HasManager, source.id(), QContactRelationship::First);
+        QVERIFY(retrieveList.isEmpty());
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+        retrieveList = cm->relationships(QContactRelationship::HasManager, source.id(), QContactRelationship::Second);
+        QVERIFY(retrieveList.isEmpty());
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+        retrieveList = cm->relationships(QContactRelationship::HasManager, source.id(), QContactRelationship::Either);
+        QVERIFY(retrieveList.isEmpty());
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+        retrieveList = cm->relationships(QContactRelationship::HasManager, source.id());
+        QVERIFY(retrieveList.isEmpty());
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+        retrieveList = cm->relationships(QContactRelationship::HasManager);
+        QVERIFY(retrieveList.isEmpty());
+        QVERIFY(cm->error() == QContactManager::NotSupportedError);
+        return;
+    }
+    
+    // Get supported relationship types
+    QStringList availableRelationshipTypes;
+    if (cm->isRelationshipTypeSupported(QContactRelationship::HasMember))
+        availableRelationshipTypes << QContactRelationship::HasMember;    
+    if (cm->isRelationshipTypeSupported(QContactRelationship::HasAssistant))
+        availableRelationshipTypes << QContactRelationship::HasAssistant;
+    if (cm->isRelationshipTypeSupported(QContactRelationship::HasManager))
+        availableRelationshipTypes << QContactRelationship::HasManager;
+    if (cm->isRelationshipTypeSupported(QContactRelationship::HasSpouse))
+        availableRelationshipTypes << QContactRelationship::HasSpouse;
+    if (cm->isRelationshipTypeSupported(QContactRelationship::IsSameAs))
+        availableRelationshipTypes << QContactRelationship::IsSameAs;
+
+    
+    // Check arbitary relationship support
+    if (cm->hasFeature(QContactManager::ArbitraryRelationshipTypes)) {
+        // add some arbitary type for testing
+        if (availableRelationshipTypes.count())
+            availableRelationshipTypes.insert(0, "test-arbitrary-relationship-type");
+        else {
+            availableRelationshipTypes.append("test-arbitrary-relationship-type");
+            availableRelationshipTypes.append(QContactRelationship::HasMember);
+            availableRelationshipTypes.append(QContactRelationship::HasAssistant);
+        }
+    }
+    
+    // Verify that we have relationship types. If there are none then the manager
+    // is saying it supports relationships but does not actually implement any 
+    // relationship type.
+    QVERIFY(!availableRelationshipTypes.isEmpty());
+    
+    // Some backends (eg. symbian) require that when type is "HasMember" 
+    // then "first" contact must be a group.
+    if (availableRelationshipTypes.at(0) == QContactRelationship::HasMember) {
+        cm->removeContact(source.localId());
+        source.setId(QContactId());
+        source.setType(QContactType::TypeGroup);
+        cm->saveContact(&source);
+    }
+
+    // Create some common contact id's for testing
+    QContactId dest1Uri = dest1.id();
+    QContactId dest1EmptyUri;
+    dest1EmptyUri.setManagerUri(QString());
+    dest1EmptyUri.setLocalId(dest1.id().localId());
+    QContactId dest2Uri = dest2.id();
+    QContactId dest3Uri = dest3.id();
+    QContactId dest3EmptyUri;
+    dest3EmptyUri.setManagerUri(QString());
+    dest3EmptyUri.setLocalId(dest3.id().localId());
+
+    // build our relationship - source is the manager all of the dest contacts.
+    QContactRelationship customRelationshipOne;
+    customRelationshipOne.setFirst(source.id());
+    customRelationshipOne.setSecond(dest1EmptyUri);
+    customRelationshipOne.setRelationshipType(availableRelationshipTypes.at(0));
+    QCOMPARE(customRelationshipOne.first(), source.id());
+    QCOMPARE(customRelationshipOne.second(), dest1EmptyUri);
+    QVERIFY(customRelationshipOne.relationshipType() == availableRelationshipTypes.at(0));
+
+    // save the relationship
+    int managerRelationshipsCount = cm->relationships(availableRelationshipTypes.at(0)).count();
+    QVERIFY(cm->saveRelationship(&customRelationshipOne));
+    // test that the empty manager URI has been updated.
+    QCOMPARE(customRelationshipOne.second(), dest1Uri); // updated with correct manager URI
+
+    // test our accessors.
+    QCOMPARE(cm->relationships(availableRelationshipTypes.at(0)).count(), (managerRelationshipsCount + 1));
+    QVERIFY(cm->relationships(availableRelationshipTypes.at(0), source.id()).count() == 1);
+
+    // remove the dest1 contact, relationship should be removed.
+    cm->removeContact(dest1.localId());
+    QCOMPARE(cm->relationships(availableRelationshipTypes.at(0), dest1Uri, QContactRelationship::Second).count(), 0);
+
+    // modify and save the relationship
+    customRelationshipOne.setSecond(dest2Uri);
+    QVERIFY(cm->saveRelationship(&customRelationshipOne));
+
+    // attempt to save the relationship again.  XXX TODO: what should the result be?  currently succeeds (overwrites)
+    int relationshipsCount = cm->relationships().count();
+    QVERIFY(cm->saveRelationship(&customRelationshipOne));    // succeeds, but just overwrites
+    QCOMPARE(relationshipsCount, cm->relationships().count()); // shouldn't change; save should have overwritten.
+
+    // removing the source contact should result in removal of the relationship.
+    QVERIFY(cm->removeContact(source.id().localId()));
+    QCOMPARE(cm->relationships().count(), relationshipsCount - 1); // the relationship should have been removed.
+
+    // now ensure that qcontact relationship caching works as required - perhaps this should be in tst_QContact?
+    source.setId(QContactId());         // reset id so we can resave
+    QVERIFY(cm->saveContact(&source));  // save source again.
+    customRelationshipOne.setFirst(source.id());
+    customRelationshipOne.setSecond(dest2.id());
+    QVERIFY(cm->saveRelationship(&customRelationshipOne));
+
+    // Add a second relationship
+    QContactRelationship customRelationshipTwo;
+    customRelationshipTwo.setFirst(source.id());
+    if (availableRelationshipTypes.count() > 1)
+        customRelationshipTwo.setRelationshipType(availableRelationshipTypes.at(1));
+    else
+        customRelationshipTwo.setRelationshipType(availableRelationshipTypes.at(0));
+    customRelationshipTwo.setSecond(dest3.id());
+    QVERIFY(cm->saveRelationship(&customRelationshipTwo));
+
+    // currently, the contacts are "stale" - no cached relationships
+    QVERIFY(dest3.relatedContacts().isEmpty());
+    QVERIFY(dest3.relationships().isEmpty());
+    QVERIFY(dest2.relatedContacts().isEmpty());
+    QVERIFY(dest2.relationships().isEmpty());
+
+    // now refresh the contacts
+    dest3 = cm->contact(dest3.localId());
+    dest2 = cm->contact(dest2.localId());
+    source = cm->contact(source.localId());
+
+    // and test again.
+    QVERIFY(source.relatedContacts(QString(), QContactRelationship::First).isEmpty()); // source is always the first, so this should be empty.
+    QVERIFY(source.relatedContacts(QString(), QContactRelationship::Second).contains(dest2.id()));
+    QVERIFY(source.relatedContacts(QString(), QContactRelationship::Either).contains(dest2.id()));
+    QVERIFY(source.relatedContacts(QString(), QContactRelationship::Second).contains(dest3.id()));
+    QVERIFY(source.relatedContacts(QString(), QContactRelationship::Either).contains(dest3.id()));
+    QVERIFY(source.relatedContacts(availableRelationshipTypes.at(0), QContactRelationship::Second).contains(dest2.id()));
+    QVERIFY(source.relatedContacts(availableRelationshipTypes.at(0), QContactRelationship::First).isEmpty());
+
+    QVERIFY(dest2.relatedContacts().contains(source.id()));
+    QVERIFY(dest2.relationships().contains(customRelationshipOne));
+    QVERIFY(!dest2.relationships().contains(customRelationshipTwo));
+    QVERIFY(dest2.relationships(availableRelationshipTypes.at(0)).contains(customRelationshipOne));
+    QVERIFY(!dest2.relationships(availableRelationshipTypes.at(0)).contains(customRelationshipTwo));
+    QVERIFY(dest2.relatedContacts(availableRelationshipTypes.at(0)).contains(source.id()));
+    QVERIFY(dest2.relatedContacts(availableRelationshipTypes.at(0), QContactRelationship::First).contains(source.id()));
+    QVERIFY(dest2.relatedContacts(availableRelationshipTypes.at(0), QContactRelationship::Second).isEmpty());
+    QVERIFY(!dest2.relatedContacts(availableRelationshipTypes.at(0), QContactRelationship::Second).contains(source.id()));
+    
+    QVERIFY(dest3.relatedContacts().contains(source.id()));
+    QVERIFY(!dest3.relationships().contains(customRelationshipOne));
+    QVERIFY(dest3.relationships().contains(customRelationshipTwo));
+    QVERIFY(!dest3.relationships(availableRelationshipTypes.at(0)).contains(customRelationshipOne));
+
+    // Test iteration
+    QList<QContactRelationship> relats = source.relationships();
+    QList<QContactRelationship>::iterator it = relats.begin();
+
+    while (it != relats.end()) {
+        QContactId firstId = it->first();
+        QVERIFY(firstId == source.id());
+        QVERIFY(it->second() == dest2.id() || it->second() == dest3.id());
+        it++;
+    }
+    
+    if (availableRelationshipTypes.count() > 1) {
+        QVERIFY(source.relatedContacts(availableRelationshipTypes.at(1), QContactRelationship::Second).contains(dest3.id()));
+        QVERIFY(source.relatedContacts(availableRelationshipTypes.at(1), QContactRelationship::First).isEmpty());
+        
+        QVERIFY(dest2.relationships(availableRelationshipTypes.at(1)).isEmpty());
+        
+        QVERIFY(!dest3.relationships(availableRelationshipTypes.at(0)).contains(customRelationshipTwo));
+        QVERIFY(dest3.relationships(availableRelationshipTypes.at(1)).contains(customRelationshipTwo));
+        QVERIFY(!dest3.relationships(availableRelationshipTypes.at(1)).contains(customRelationshipOne));
+        QVERIFY(dest3.relatedContacts(availableRelationshipTypes.at(1)).contains(source.id()));
+        QVERIFY(!dest3.relatedContacts(availableRelationshipTypes.at(0)).contains(source.id()));
+        QVERIFY(dest3.relatedContacts(availableRelationshipTypes.at(1)).contains(source.id())); // role = either
+        QVERIFY(!dest3.relatedContacts(availableRelationshipTypes.at(1), QContactRelationship::Second).contains(source.id()));
+        QVERIFY(dest3.relatedContacts(availableRelationshipTypes.at(1), QContactRelationship::First).contains(source.id()));
+        QVERIFY(dest2.relatedContacts(availableRelationshipTypes.at(1)).isEmpty());
+    }
+    else {
+        QVERIFY(source.relatedContacts(availableRelationshipTypes.at(0), QContactRelationship::Second).contains(dest3.id()));
+    }
+    
+    // Cleanup a bit
+    QMap<int, QContactManager::Error> errorMap;
+    QList<QContactRelationship> moreRels;
+
+    moreRels << customRelationshipOne << customRelationshipTwo;
+    errorMap.insert(5, QContactManager::BadArgumentError);
+    QVERIFY(cm->removeRelationships(moreRels, &errorMap));
+    QVERIFY(errorMap.count() == 0);
+
+    // test batch API and ordering in contacts
+    QList<QContactRelationship> currentRelationships = cm->relationships(source.id(), QContactRelationship::First);
+    QList<QContactRelationship> batchList;
+    QContactRelationship br1, br2, br3;
+    br1.setFirst(source.id());
+    br1.setSecond(dest2.id());
+    br1.setRelationshipType(availableRelationshipTypes.at(0));    
+    br2.setFirst(source.id());
+    br2.setSecond(dest3.id());
+    br2.setRelationshipType(availableRelationshipTypes.at(0));
+    if (availableRelationshipTypes.count() > 1)
+    {
+        br3.setFirst(source.id());
+        br3.setSecond(dest3.id());
+        br3.setRelationshipType(availableRelationshipTypes.at(1));
+    }
+    else
+    {
+        br3.setFirst(source.id());
+        br3.setSecond(dest4.id());
+        br3.setRelationshipType(availableRelationshipTypes.at(0));
+    }
+    batchList << br1 << br2 << br3;
+
+    // ensure that the batch save works properly
+    cm->saveRelationships(&batchList, NULL);
+    QCOMPARE(cm->error(), QContactManager::NoError);
+    QList<QContactRelationship> batchRetrieve = cm->relationships(source.id(), QContactRelationship::First);
+    QVERIFY(batchRetrieve.contains(br1));
+    QVERIFY(batchRetrieve.contains(br2));
+    QVERIFY(batchRetrieve.contains(br3));
+    
+    // remove a single relationship
+    QVERIFY(cm->removeRelationship(br3));
+    batchRetrieve = cm->relationships(source.id(), QContactRelationship::First);
+    QVERIFY(batchRetrieve.contains(br1));
+    QVERIFY(batchRetrieve.contains(br2));
+    QVERIFY(!batchRetrieve.contains(br3)); // has already been removed.
+
+    // now ensure that the batch remove works and we get returned to the original state.
+    batchList.removeOne(br3);
+    cm->removeRelationships(batchList, NULL);
+    QVERIFY(cm->error() == QContactManager::NoError);
+    QCOMPARE(cm->relationships(source.id(), QContactRelationship::First), currentRelationships);
+
+    // attempt to save relationships between an existing source but non-existent destination
+    QContactId nonexistentDest;
+    quint32 idSeed = 0x5544;
+    QContactLocalId nonexistentLocalId = QContactLocalId(idSeed);
+    nonexistentDest.setManagerUri(cm->managerUri());
+    while (true) {
+        nonexistentLocalId = cm->contact(nonexistentLocalId).localId();
+        if (nonexistentLocalId == QContactLocalId(0)) {
+            // found a "spare" local id (no contact with that id)
+            break;
+        }
+
+        // keep looking...
+        idSeed += 1;
+        nonexistentLocalId = QContactLocalId(idSeed);
+        QVERIFY(nonexistentLocalId != QContactLocalId(0)); // integer overflow check.
+    }
+    nonexistentDest.setLocalId(nonexistentLocalId);
+    QContactRelationship maliciousRel;
+    maliciousRel.setFirst(source.id());
+    maliciousRel.setSecond(nonexistentDest);
+    maliciousRel.setRelationshipType("nokia-test-invalid-relationship-type");
+    QVERIFY(!cm->saveRelationship(&maliciousRel));
+
+    // attempt to save a circular relationship - should fail!
+    maliciousRel.setFirst(source.id());
+    maliciousRel.setSecond(source.id());
+    maliciousRel.setRelationshipType(availableRelationshipTypes.at(0));
+    QVERIFY(!cm->saveRelationship(&maliciousRel));
+
+    // more negative testing, but force manager to recognise the empty URI
+    QContactId circularId = source.id();
+    circularId.setManagerUri(QString());
+    maliciousRel.setFirst(circularId);
+    maliciousRel.setSecond(circularId);
+    maliciousRel.setRelationshipType(availableRelationshipTypes.at(0));
+    QVERIFY(!cm->saveRelationship(&maliciousRel));
+    maliciousRel.setFirst(source.id());
+    maliciousRel.setSecond(circularId);
+    maliciousRel.setRelationshipType(availableRelationshipTypes.at(0));
+    QVERIFY(!cm->saveRelationship(&maliciousRel));
+    maliciousRel.setFirst(circularId);
+    maliciousRel.setSecond(source.id());
+    maliciousRel.setRelationshipType(availableRelationshipTypes.at(0));
+    QVERIFY(!cm->saveRelationship(&maliciousRel));
+
+    // attempt to save a relationship where the source contact comes from another manager
+    circularId.setManagerUri("test-nokia-invalid-manager-uri");
+    maliciousRel.setFirst(circularId);   // an invalid source contact
+    maliciousRel.setSecond(dest2.id());       // a valid destination contact
+    maliciousRel.setRelationshipType(availableRelationshipTypes.at(0));
+    QVERIFY(!cm->saveRelationship(&maliciousRel));
+
+    // remove the nonexistent relationship
+    relationshipsCount = cm->relationships().count();
+    QVERIFY(!cm->removeRelationship(maliciousRel));         // does not exist; fail remove.
+    QVERIFY(cm->error() == QContactManager::DoesNotExistError || cm->error() == QContactManager::InvalidRelationshipError);
+    QCOMPARE(cm->relationships().count(), relationshipsCount); // should be unchanged.
+
+    // now we want to ensure that a relationship is removed if one of the contacts is removed.
+    customRelationshipOne.setFirst(source.id());
+    customRelationshipOne.setSecond(dest2.id());
+    customRelationshipOne.setRelationshipType(availableRelationshipTypes.at(0));
+
+    // Test batch save with an error map
+    moreRels.clear();
+    moreRels << customRelationshipOne;
+    errorMap.insert(0, QContactManager::BadArgumentError);
+    QVERIFY(cm->saveRelationships(&moreRels, &errorMap));
+    QVERIFY(cm->error() == QContactManager::NoError);
+    QVERIFY(errorMap.count() == 0); // should be reset
+    source = cm->contact(source.localId());
+    dest2 = cm->contact(dest2.localId());
+    QVERIFY(cm->removeContact(dest2.localId())); // remove dest2, the relationship should be removed
+    QVERIFY(cm->relationships(availableRelationshipTypes.at(0), dest2.id(), QContactRelationship::Second).isEmpty());
+    source = cm->contact(source.localId());
+    QVERIFY(!source.relatedContacts().contains(dest2.id())); // and it shouldn't appear in cache.
+
+    // now clean up and remove our dests.
+    QVERIFY(cm->removeContact(source.localId()));
+    QVERIFY(cm->removeContact(dest3.localId()));
+
+    // attempt to save relationships with nonexistent contacts
+    QVERIFY(!cm->saveRelationship(&br1));
+    QVERIFY(cm->error() == QContactManager::InvalidRelationshipError);
+    cm->saveRelationships(&batchList, NULL);
+    QVERIFY(cm->error() == QContactManager::InvalidRelationshipError);
+    QVERIFY(!cm->removeRelationship(br1));
+    QVERIFY(cm->error() == QContactManager::DoesNotExistError || cm->error() == QContactManager::InvalidRelationshipError);
+    cm->removeRelationships(batchList, NULL);
+    QVERIFY(cm->error() == QContactManager::DoesNotExistError || cm->error() == QContactManager::InvalidRelationshipError);
+}
+
+void tst_QContactManager::contactType()
+{
+    QFETCH(QString, uri);
+    QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
+
+    if (!cm->hasFeature(QContactManager::Groups))
+        QSKIP("Skipping: This manager does not support group contacts!", SkipSingle);
+
+    QContact g1, g2, c;
+    g1.setType(QContactType::TypeGroup);
+    g2.setType(QContactType::TypeGroup);
+
+    QContactPhoneNumber g1p, g2p, cp;
+    g1p.setNumber("22222");
+    g2p.setNumber("11111");
+    cp.setNumber("33333");
+
+    g1.saveDetail(&g1p);
+    g2.saveDetail(&g2p);
+    c.saveDetail(&cp);
+
+    QVERIFY(cm->saveContact(&g1));
+    QVERIFY(cm->saveContact(&g2));
+    QVERIFY(cm->saveContact(&c));
+
+    // test that the accessing by type works properly
+    QContactDetailFilter groupFilter;
+    groupFilter.setDetailDefinitionName(QContactType::DefinitionName, QContactType::FieldType);
+    groupFilter.setValue(QString(QLatin1String(QContactType::TypeGroup)));
+    QVERIFY(cm->contactIds(groupFilter).contains(g1.localId()));
+    QVERIFY(cm->contactIds(groupFilter).contains(g2.localId()));
+    QVERIFY(!cm->contactIds(groupFilter).contains(c.localId()));
+
+    QList<QContactSortOrder> sortOrders;
+    QContactSortOrder byPhoneNumber;
+    byPhoneNumber.setDetailDefinitionName(QContactPhoneNumber::DefinitionName, QContactPhoneNumber::FieldNumber);
+    sortOrders.append(byPhoneNumber);
+
+    // and ensure that sorting works properly with typed contacts also
+    QList<QContactLocalId> sortedIds = cm->contactIds(groupFilter, sortOrders);
+    QVERIFY(sortedIds.indexOf(g2.localId()) < sortedIds.indexOf(g1.localId()));
+
+    cm->removeContact(g1.localId());
+    cm->removeContact(g2.localId());
+    cm->removeContact(c.localId());
+}
+
+QTEST_MAIN(tst_QContactManager)
+#include "tst_qcontactmanager.moc"