When loading a QML object into a C++ application, it can be useful to embed some C++ data directly that can be used from within the QML code. This makes it possible, for example, to invoke a C++ method on the embedded object, or use a C++ object instance as a data model for a QML view.
The ability to inject C++ data into a QML object is made possible by the QQmlContext class. This class exposes data to the context of a QML object so that the data can be referred to directly from within the scope of the QML code.
QQmlContext Properties and Squish
When testing a QML object which relies on embedded C++ data, it is possible to perform verifications and modify its values, by obtaining a QQmlContext instance and using its contextProperty function.
Obtaining the QQmlContext
There are different ways of obtaining a QQmlContext in a Squish test script.
The most straightforward way to access the so-called rootContext of a QQmlEngine is by obtaining a reference to the engine and invoking its rootContext function.
Squish exposes a global function named qmlEngine which accepts a QObject and returns the QQmlEngine associated with the object if any.
def getRootContextFromEngine(qObject): engine = qmlEngine(qObject) test.verify(not isNull(engine), "Engine fetched from QObject is valid") return engine.rootContext()
An alternative way to obtaining the QQmlContext for a given object is by using the global function named qmlContext which accepts a QObject and returns the QQmlContext associated with the object if any.
def getContextFromQObject(qObject): context = qmlContext(qObject) test.verify(not isNull(context), "Context fetched from QObject is valid") return context
It is also possible to obtain the rootContext associated with a QObject. This is achieved by first getting the QQmlContext and then traversing the parentContext hierarchy up to the root.
def getRootContextFromQObject(qObject): context = getContextFromQObject(qObject) while not isNull(context.parentContext()): test.log("Going up one QML context") context = context.parentContext() return context
There is yet another way, though this works only in combination with QQuickView and its sub-types.
QQuickView exposes a function named rootContext, which can be used to retrieve the view’s rootContext as shown in the example in the following section.
Working with the QQmlContext
Once obtained, the QQmlContext can be useful for a variety of things, the most useful in Squish test scripts is accessing embedded C++ data.
When accessing C++ data through the contextProperty function on a QQmlContext, a QVariant is returned which can simply be unpacked with object.convertTo as shown in the examples.
An example usage could be to verify that the ListView associated with a ListModel has the same number of entries.
Suppose we want to test an animalList application which has a ListView to display a list of animals, where the animals are provided through a C++ class named animalModel, which is exposed to QML.
import names def main(): startApplication("animalList") rootContext = waitForObject(names.o_QQuickView).rootContext() animalModel = object.convertTo(rootContext.contextProperty("animalModel"), "QObject") test.compare(animalModel.rowCount(), waitForObjectExists(names.o_ListView).count, "Verify all model entries are contained in the ListView")
The above example is a rather theoretical example as it tests if Qt works correctly, but it could be useful in an application where you have your own ListModel/View implementation.
Yet another example usage could be to ensure a ListView is visually updated whenever its ListModel content changes.
import names def main(): startApplication("animalList") rootContext = getRootContextFromEngine(waitForObject(names.o_ListView)) animalModel = object.convertTo(rootContext.contextProperty("animalModel"), "QObject") animalModel.removeRow(0) test.vp("VP1", "Verify UI is updated when model is")
Accessing the QQmlContext and its properties using Squish’s built-in functions is easier than one might expect, especially unpacking the QVariant returned by the contextProperty function to its actual type.
It is enough to use « QObject » as the second argument to object.convertTo, because the QVariant knows to which type to expand.
The global function castToQObject sounds quite similar, although can lead to crashes in some cases. It is therefore recommended to stick with object.convertTo.
This technique should be used with caution, otherwise you might end up with test cases that are bound to the internals of your Application Under Test.