1 /***************************************************************************
2     testqgslayoutgui.cpp
3      ----------------------
4     Date                 : October 2017
5     Copyright            : (C) 2017 Nyall Dawson
6     Email                : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 
17 
18 
19 #include <QtTest/QTest>
20 
21 #include "qgsmapsettings.h"
22 #include "qgslayout.h"
23 #include "qgslayoutitemshape.h"
24 #include "qgslayoutitemmap.h"
25 #include "qgslayoutmodel.h"
26 #include "qgslayoutitemcombobox.h"
27 
28 #include <QApplication>
29 #include <QMainWindow>
30 #include <QtTest/QSignalSpy>
31 
32 
33 class TestQgsLayoutGui: public QObject
34 {
35     Q_OBJECT
36   private slots:
37     void initTestCase(); // will be called before the first testfunction is executed.
38     void cleanupTestCase(); // will be called after the last testfunction was executed.
39     void init(); // will be called before each testfunction is executed.
40     void cleanup(); // will be called after every testfunction.
41 
42     void itemTypeComboBox();
43     void testProxyCrash();
44 
45   private:
46 
47 };
48 
initTestCase()49 void TestQgsLayoutGui::initTestCase()
50 {
51 }
52 
cleanupTestCase()53 void TestQgsLayoutGui::cleanupTestCase()
54 {
55 }
56 
init()57 void TestQgsLayoutGui::init()
58 {
59 }
60 
cleanup()61 void TestQgsLayoutGui::cleanup()
62 {
63 }
64 
itemTypeComboBox()65 void TestQgsLayoutGui::itemTypeComboBox()
66 {
67   // test QgsLayoutItemComboBox
68   QgsLayout *layout = new QgsLayout( QgsProject::instance() );
69 
70   // create a layout item combobox
71   QgsLayoutItemComboBox *cb = new QgsLayoutItemComboBox( nullptr, layout );
72   QgsLayoutItemComboBox *cb2 = new QgsLayoutItemComboBox( nullptr, layout );
73   cb2->setItemType( QgsLayoutItemRegistry::LayoutShape );
74 
75   qRegisterMetaType<QgsLayoutItem *>();
76   const QSignalSpy spy1( cb, &QgsLayoutItemComboBox::itemChanged );
77   const QSignalSpy spy2( cb2, &QgsLayoutItemComboBox::itemChanged );
78 
79   // add some items to layout
80   QgsLayoutItemMap *item1 = new QgsLayoutItemMap( layout );
81   item1->setId( "item 1" );
82   layout->addLayoutItem( item1 );
83 
84   QCOMPARE( cb->count(), 1 );
85   QCOMPARE( cb2->count(), 0 );
86   QCOMPARE( cb->itemText( 0 ), QStringLiteral( "item 1" ) );
87   QCOMPARE( cb->item( 0 ), item1 );
88   QCOMPARE( cb->currentItem(), item1 );
89   QVERIFY( !cb2->currentItem() );
90 
91   int expectedSpy1Count = 2;// ideally only one, but we'll settle for 2
92   int expectedSpy2Count = 0;
93   QCOMPARE( spy1.count(), 2 );
94   QCOMPARE( qvariant_cast<QObject *>( spy1.at( 1 ).at( 0 ) ), item1 );
95   QCOMPARE( spy2.count(), 0 );
96 
97   QgsLayoutItemShape *item2 = new QgsLayoutItemShape( layout );
98   item2->setId( "item 2" );
99   layout->addLayoutItem( item2 );
100 
101   QCOMPARE( cb->count(), 2 );
102   QCOMPARE( cb2->count(), 1 );
103   QCOMPARE( cb->itemText( 0 ), QStringLiteral( "item 1" ) );
104   QCOMPARE( cb->itemText( 1 ), QStringLiteral( "item 2" ) );
105   QCOMPARE( cb2->itemText( 0 ), QStringLiteral( "item 2" ) );
106   QCOMPARE( cb->item( 0 ), item1 );
107   QCOMPARE( cb->item( 1 ), item2 );
108   QCOMPARE( cb2->item( 0 ), item2 );
109   QCOMPARE( cb->currentItem(), item1 );
110   QCOMPARE( cb2->currentItem(), item2 );
111 
112   QCOMPARE( spy1.count(), expectedSpy1Count ); // must be unchanged from earlier
113   expectedSpy2Count = 2;// ideally only one, but we'll settle for 2
114   QCOMPARE( spy2.count(), expectedSpy2Count );
115   QCOMPARE( qvariant_cast<QObject *>( spy2.at( 1 ).at( 0 ) ), item2 );
116 
117   QgsLayoutItemMap *item3 = new QgsLayoutItemMap( layout );
118   item3->setId( "item 3" );
119   layout->addLayoutItem( item3 );
120 
121   QCOMPARE( cb->count(), 3 );
122   QCOMPARE( cb2->count(), 1 );
123   QCOMPARE( cb->itemText( 0 ), QStringLiteral( "item 1" ) );
124   QCOMPARE( cb->itemText( 1 ), QStringLiteral( "item 2" ) );
125   QCOMPARE( cb->itemText( 2 ), QStringLiteral( "item 3" ) );
126   QCOMPARE( cb2->itemText( 0 ), QStringLiteral( "item 2" ) );
127   QCOMPARE( cb->item( 0 ), item1 );
128   QCOMPARE( cb->item( 1 ), item2 );
129   QCOMPARE( cb->item( 2 ), item3 );
130   QCOMPARE( cb2->item( 0 ), item2 );
131   QCOMPARE( cb->currentItem(), item1 );
132   QCOMPARE( cb2->currentItem(), item2 );
133   QCOMPARE( spy1.count(), expectedSpy1Count ); // must be unchanged from earlier
134   QCOMPARE( spy2.count(), expectedSpy2Count );
135 
136   item3->setId( "a" );
137   QCOMPARE( cb->itemText( 0 ), QStringLiteral( "a" ) );
138   QCOMPARE( cb->itemText( 1 ), QStringLiteral( "item 1" ) );
139   QCOMPARE( cb->itemText( 2 ), QStringLiteral( "item 2" ) );
140   item3->setId( "item 3" );
141   QCOMPARE( cb->itemText( 0 ), QStringLiteral( "item 1" ) );
142   QCOMPARE( cb->itemText( 1 ), QStringLiteral( "item 2" ) );
143   QCOMPARE( cb->itemText( 2 ), QStringLiteral( "item 3" ) );
144 
145   //manually change item
146   cb->setItem( item3 );
147   expectedSpy1Count++;
148   QCOMPARE( spy1.count(), expectedSpy1Count );
149   QCOMPARE( qvariant_cast<QObject *>( spy1.at( expectedSpy1Count - 1 ).at( 0 ) ), item3 );
150   QCOMPARE( cb->currentItem(), item3 );
151 
152   cb2->setItem( nullptr );
153   QVERIFY( !cb2->currentItem() );
154   expectedSpy2Count++;
155   QCOMPARE( spy2.count(), expectedSpy2Count );
156   QVERIFY( !qvariant_cast<QObject *>( spy2.at( expectedSpy2Count - 1 ).at( 0 ) ) );
157 
158   // remove item
159   layout->removeLayoutItem( item3 );
160   expectedSpy1Count++;
161   QCOMPARE( spy1.count(), expectedSpy1Count );
162   QCOMPARE( qvariant_cast<QObject *>( spy1.at( expectedSpy1Count - 1 ).at( 0 ) ), item2 );
163   QCOMPARE( cb->currentItem(), item2 );
164   QCOMPARE( spy2.count(), expectedSpy2Count );
165   QCOMPARE( cb->count(), 2 );
166   QCOMPARE( cb2->count(), 1 );
167 
168   layout->removeLayoutItem( item2 );
169   expectedSpy1Count += 2; // ideally just one signal, but we want at LEAST 1...
170   QCOMPARE( spy1.count(), expectedSpy1Count );
171   QCOMPARE( qvariant_cast<QObject *>( spy1.at( expectedSpy1Count - 1 ).at( 0 ) ), item1 );
172   QCOMPARE( cb->currentItem(), item1 );
173   QCOMPARE( cb->count(), 1 );
174   QCOMPARE( cb2->count(), 0 );
175   expectedSpy2Count++;
176   QCOMPARE( spy2.count(), expectedSpy2Count );
177   QVERIFY( !qvariant_cast<QObject *>( spy2.at( expectedSpy2Count - 1 ).at( 0 ) ) );
178   QVERIFY( !cb2->currentItem() );
179 
180   layout->removeLayoutItem( item1 );
181   expectedSpy1Count += 2; // ideally just one signal, but we want at LEAST 1...
182   QCOMPARE( spy1.count(), expectedSpy1Count );
183   QVERIFY( !qvariant_cast<QObject *>( spy1.at( expectedSpy1Count - 1 ).at( 0 ) ) );
184   QVERIFY( !cb->currentItem() );
185   QCOMPARE( cb->count(), 0 );
186   QCOMPARE( cb2->count(), 0 );
187   QCOMPARE( spy2.count(), expectedSpy2Count );
188 }
189 
testProxyCrash()190 void TestQgsLayoutGui::testProxyCrash()
191 {
192   // test for a possible crash when using QgsLayoutProxyModel and reordering items
193   QgsLayout *layout = new QgsLayout( QgsProject::instance() );
194 
195   // create a layout item combobox
196   QgsLayoutItemComboBox *cb = new QgsLayoutItemComboBox( nullptr, layout );
197   QgsLayoutItemComboBox *cb2 = new QgsLayoutItemComboBox( nullptr, layout );
198   QgsLayoutItemComboBox *cb3 = new QgsLayoutItemComboBox( nullptr, layout );
199   QgsLayoutItemComboBox *cb4 = new QgsLayoutItemComboBox( nullptr, layout );
200 
201   // add some items to layout
202   QgsLayoutItemMap *item1 = new QgsLayoutItemMap( layout );
203   layout->addLayoutItem( item1 );
204   QCOMPARE( cb->count(), 1 );
205   QgsLayoutItemShape *item2 = new QgsLayoutItemShape( layout );
206   layout->addLayoutItem( item2 );
207   QCOMPARE( cb->count(), 2 );
208   QgsLayoutItemMap *item3 = new QgsLayoutItemMap( layout );
209   layout->addLayoutItem( item3 );
210 
211   QCOMPARE( cb->count(), 3 );
212   cb->setItemType( QgsLayoutItemRegistry::LayoutMap );
213   QCOMPARE( cb->count(), 2 );
214 
215   cb4->setItemType( QgsLayoutItemRegistry::LayoutShape );
216 
217   cb->setItem( item1 );
218   QCOMPARE( cb->currentItem(), item1 );
219   cb2->setItem( item3 );
220   QCOMPARE( cb2->currentItem(), item3 );
221   cb3->setItem( item2 );
222   QCOMPARE( cb3->currentItem(), item2 );
223 
224   // reorder items - expect no crash!
225   // we do this by calling the private members, in order to simulate what
226   // happens when a drag and drop reorder occurs
227   layout->itemsModel()->mItemZList.removeOne( item1 );
228   layout->itemsModel()->mItemZList.insert( 1, item1 );
229   layout->itemsModel()->rebuildSceneItemList();
230 
231   QCOMPARE( cb->currentItem(), item1 );
232   QCOMPARE( cb2->currentItem(), item3 );
233 
234   layout->itemsModel()->mItemZList.removeOne( item1 );
235   layout->itemsModel()->mItemZList.insert( 0, item1 );
236   layout->itemsModel()->rebuildSceneItemList();
237 
238   delete layout;
239 }
240 
241 
242 QTEST_MAIN( TestQgsLayoutGui )
243 #include "testqgslayoutgui.moc"
244