1 /***************************************************************************
2                          testqgsprocessingmodel.cpp
3                          --------------------------
4     begin                : July 2018
5     copyright            : (C) 2018 by Nyall Dawson
6     email                : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgsprocessingregistry.h"
19 #include "qgsprocessingtoolboxmodel.h"
20 #include "qgsprocessingrecentalgorithmlog.h"
21 #include "qgsprocessingtoolboxtreeview.h"
22 #include "qgssettings.h"
23 
24 #include <QtTest/QSignalSpy>
25 #include "qgstest.h"
26 
27 #ifdef ENABLE_MODELTEST
28 #include "modeltest.h"
29 #endif
30 
31 
32 class DummyAlgorithm : public QgsProcessingAlgorithm
33 {
34   public:
35 
DummyAlgorithm(const QString & name,const QString & group,QgsProcessingAlgorithm::Flags flags=QgsProcessingAlgorithm::Flags (),const QString & tags=QString (),const QString & shortDescription=QString (),const QString & displayName=QString ())36     DummyAlgorithm( const QString &name, const QString &group,
37                     QgsProcessingAlgorithm::Flags flags = QgsProcessingAlgorithm::Flags(),
38                     const QString &tags = QString(),
39                     const QString &shortDescription = QString(),
40                     const QString &displayName = QString() )
41       : mName( name )
42       , mDisplayName( displayName )
43       , mGroup( group )
44       , mFlags( flags )
45       , mTags( tags.split( ',' ) )
46       , mShortDescription( shortDescription )
47     {}
48 
initAlgorithm(const QVariantMap &=QVariantMap ())49     void initAlgorithm( const QVariantMap & = QVariantMap() ) override {}
flags() const50     QgsProcessingAlgorithm::Flags flags() const override { return mFlags; }
name() const51     QString name() const override { return mName; }
displayName() const52     QString displayName() const override { return mDisplayName.isEmpty() ? mName : mDisplayName; }
group() const53     QString group() const override { return mGroup; }
groupId() const54     QString groupId() const override { return mGroup; }
shortDescription() const55     QString shortDescription() const override { return mShortDescription; }
tags() const56     QStringList tags() const override { return mTags; }
processAlgorithm(const QVariantMap &,QgsProcessingContext &,QgsProcessingFeedback *)57     QVariantMap processAlgorithm( const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * ) override { return QVariantMap(); }
58 
createInstance() const59     DummyAlgorithm *createInstance() const override { return new DummyAlgorithm( mName, mGroup, mFlags, mTags.join( ',' ), mShortDescription, mDisplayName ); }
60 
61     QString mName;
62     QString mDisplayName;
63     QString mGroup;
64     QgsProcessingAlgorithm::Flags mFlags = QgsProcessingAlgorithm::Flags();
65     QStringList mTags;
66     QString mShortDescription;
67 
68 };
69 //dummy provider for testing
70 class DummyProvider : public QgsProcessingProvider // clazy:exclude=missing-qobject-macro
71 {
72   public:
73 
DummyProvider(const QString & id,const QString & name,const QList<QgsProcessingAlgorithm * > algs=QList<QgsProcessingAlgorithm * > ())74     DummyProvider( const QString &id, const QString &name, const QList< QgsProcessingAlgorithm *> algs = QList< QgsProcessingAlgorithm *>() )
75       : mId( id )
76       , mName( name )
77       , mAlgs( algs )
78     {
79 
80     }
~DummyProvider()81     ~DummyProvider() override
82     {
83       qDeleteAll( mAlgs );
84     }
85 
id() const86     QString id() const override { return mId; }
isActive() const87     bool isActive() const override { return mActive; }
88 
name() const89     QString name() const override { return mName; }
longName() const90     QString longName() const override { return QStringLiteral( "long name %1" ).arg( mName );}
91     bool mActive = true;
92   protected:
loadAlgorithms()93     void loadAlgorithms() override
94     {
95       for ( QgsProcessingAlgorithm *alg : mAlgs )
96         addAlgorithm( alg->create() );
97     }
98     QString mId;
99     QString mName;
100 
101     QList< QgsProcessingAlgorithm *> mAlgs;
102 
103 };
104 
105 
106 class TestQgsProcessingModel: public QObject
107 {
108     Q_OBJECT
109 
110   private slots:
111     void initTestCase();// will be called before the first testfunction is executed.
112     void cleanupTestCase(); // will be called after the last testfunction was executed.
init()113     void init() {} // will be called before each testfunction is executed.
cleanup()114     void cleanup() {} // will be called after every testfunction.
115     void testModel();
116     void testKnownIssues();
117     void testProxyModel();
118     void testView();
119 };
120 
121 
initTestCase()122 void TestQgsProcessingModel::initTestCase()
123 {
124   QgsApplication::init();
125   QgsApplication::initQgis();
126 
127   // Set up the QgsSettings environment
128   QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
129   QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
130   QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
131 
132   QgsSettings().clear();
133 }
134 
cleanupTestCase()135 void TestQgsProcessingModel::cleanupTestCase()
136 {
137   QgsApplication::exitQgis();
138 }
139 
testModel()140 void TestQgsProcessingModel::testModel()
141 {
142   QgsProcessingRegistry registry;
143   QgsProcessingRecentAlgorithmLog recentLog;
144   QgsProcessingToolboxModel model( nullptr, &registry, &recentLog );
145 
146 #ifdef ENABLE_MODELTEST
147   new ModelTest( &model, this ); // for model validity checking
148 #endif
149 
150   QCOMPARE( model.columnCount(), 1 );
151   QCOMPARE( model.rowCount(), 1 );
152   QVERIFY( model.hasChildren() );
153   QCOMPARE( model.data( model.index( 0, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "Recently used" ) );
154   QCOMPARE( model.rowCount( model.index( 0, 0, QModelIndex() ) ), 0 );
155   QVERIFY( !model.providerForIndex( model.index( 0, 0, QModelIndex() ) ) );
156   QVERIFY( !model.providerForIndex( model.index( 1, 0, QModelIndex() ) ) );
157   QVERIFY( !model.indexForProvider( nullptr ).isValid() );
158   QVERIFY( model.index2node( QModelIndex() ) ); // root node
159   QCOMPARE( model.index2node( QModelIndex() )->nodeType(), QgsProcessingToolboxModelNode::NodeGroup );
160   QVERIFY( model.index2node( model.index( -1, 0, QModelIndex() ) ) ); // root node
161   QCOMPARE( model.index2node( QModelIndex() ), model.index2node( model.index( -1, 0, QModelIndex() ) ) );
162 
163   // add a provider
164   DummyProvider *p1 = new DummyProvider( "p1", "provider1" );
165   registry.addProvider( p1 );
166   QCOMPARE( model.rowCount(), 2 );
167   QVERIFY( model.hasChildren() );
168 
169   QVERIFY( model.index( 0, 0, QModelIndex() ).isValid() );
170   QCOMPARE( model.data( model.index( 0, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "Recently used" ) );
171   QCOMPARE( model.providerForIndex( model.index( 1, 0, QModelIndex() ) ), p1 );
172   QVERIFY( !model.providerForIndex( model.index( 2, 0, QModelIndex() ) ) );
173   QCOMPARE( model.indexForProvider( p1->id() ), model.index( 1, 0, QModelIndex() ) );
174   QVERIFY( !model.indexForProvider( nullptr ).isValid() );
175   QCOMPARE( model.rowCount( model.index( 1, 0, QModelIndex() ) ), 0 );
176   QVERIFY( !model.hasChildren( model.index( 1, 0, QModelIndex() ) ) );
177   QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider1" ) );
178   QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::ToolTipRole ).toString(), QStringLiteral( "long name provider1" ) );
179   QVERIFY( !model.data( model.index( 2, 0, QModelIndex() ), Qt::DisplayRole ).isValid() );
180   QVERIFY( !model.data( model.index( 2, 0, QModelIndex() ), Qt::ToolTipRole ).isValid() );
181 
182   // second provider
183   DummyProvider *p2 = new DummyProvider( "p2", "provider2" );
184   registry.addProvider( p2 );
185   QCOMPARE( model.rowCount(), 3 );
186   QVERIFY( model.hasChildren() );
187 
188   QVERIFY( model.index( 2, 0, QModelIndex() ).isValid() );
189   QCOMPARE( model.providerForIndex( model.index( 1, 0, QModelIndex() ) ), p1 );
190   QCOMPARE( model.providerForIndex( model.index( 2, 0, QModelIndex() ) ), p2 );
191   QVERIFY( !model.providerForIndex( model.index( 3, 0, QModelIndex() ) ) );
192   QCOMPARE( model.indexForProvider( p1->id() ), model.index( 1, 0, QModelIndex() ) );
193   QCOMPARE( model.indexForProvider( p2->id() ), model.index( 2, 0, QModelIndex() ) );
194   QVERIFY( !model.indexForProvider( nullptr ).isValid() );
195   QVERIFY( !model.hasChildren( model.index( 2, 0, QModelIndex() ) ) );
196 
197   QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider1" ) );
198   QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::ToolTipRole ).toString(), QStringLiteral( "long name provider1" ) );
199   QCOMPARE( model.data( model.index( 2, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider2" ) );
200   QCOMPARE( model.data( model.index( 2, 0, QModelIndex() ), Qt::ToolTipRole ).toString(), QStringLiteral( "long name provider2" ) );
201   QVERIFY( !model.data( model.index( 3, 0, QModelIndex() ), Qt::DisplayRole ).isValid() );
202   QVERIFY( !model.data( model.index( 3, 0, QModelIndex() ), Qt::ToolTipRole ).isValid() );
203 
204   // provider with algs and groups
205   DummyAlgorithm *a1 = new DummyAlgorithm( "a1", "group1", QgsProcessingAlgorithm::FlagHideFromModeler, QStringLiteral( "tag1,tag2" ), QStringLiteral( "short desc a" ) );
206   DummyAlgorithm *a2 = new DummyAlgorithm( "a2", "group2", QgsProcessingAlgorithm::FlagHideFromToolbox );
207   DummyProvider *p3 = new DummyProvider( "p3", "provider3", QList< QgsProcessingAlgorithm * >() << a1 << a2 );
208   registry.addProvider( p3 );
209 
210   QCOMPARE( model.rowCount(), 4 );
211   QVERIFY( model.hasChildren() );
212   QVERIFY( model.index( 3, 0, QModelIndex() ).isValid() );
213   QCOMPARE( model.providerForIndex( model.index( 3, 0, QModelIndex() ) ), p3 );
214   QCOMPARE( model.indexForProvider( p1->id() ), model.index( 1, 0, QModelIndex() ) );
215   QCOMPARE( model.indexForProvider( p2->id() ), model.index( 2, 0, QModelIndex() ) );
216   QCOMPARE( model.indexForProvider( p3->id() ), model.index( 3, 0, QModelIndex() ) );
217   QCOMPARE( model.rowCount( model.index( 2, 0, QModelIndex() ) ), 0 );
218   QCOMPARE( model.rowCount( model.index( 3, 0, QModelIndex() ) ), 2 );
219   QVERIFY( !model.hasChildren( model.index( 2, 0, QModelIndex() ) ) );
220   QVERIFY( model.hasChildren( model.index( 3, 0, QModelIndex() ) ) );
221   QModelIndex providerIndex = model.index( 3, 0, QModelIndex() );
222   QVERIFY( !model.providerForIndex( model.index( 1, 0, providerIndex ) ) );
223   QVERIFY( !model.providerForIndex( model.index( 2, 0, providerIndex ) ) );
224 
225   QCOMPARE( model.data( model.index( 0, 0, providerIndex ), Qt::DisplayRole ).toString(), QStringLiteral( "group1" ) );
226   QCOMPARE( model.data( model.index( 0, 0, providerIndex ), Qt::ToolTipRole ).toString(), QStringLiteral( "group1" ) );
227   QCOMPARE( model.data( model.index( 1, 0, providerIndex ), Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
228   QCOMPARE( model.data( model.index( 1, 0, providerIndex ), Qt::ToolTipRole ).toString(), QStringLiteral( "group2" ) );
229 
230   QModelIndex group1Index = model.index( 0, 0, providerIndex );
231   QVERIFY( !model.providerForIndex( group1Index ) );
232   QVERIFY( !model.algorithmForIndex( group1Index ) );
233   QCOMPARE( model.rowCount( group1Index ), 1 );
234   QVERIFY( model.hasChildren( group1Index ) );
235   QModelIndex alg1Index = model.index( 0, 0, group1Index );
236   QVERIFY( !model.providerForIndex( alg1Index ) );
237   QCOMPARE( model.data( alg1Index, Qt::DisplayRole ).toString(), QStringLiteral( "a1" ) );
238   QCOMPARE( model.data( alg1Index, Qt::ToolTipRole ).toString(), QStringLiteral( u"<p><b>a1</b></p><p>short desc a</p><p>Algorithm ID: \u2018<i>p3:a1</i>\u2019</p>" ) );
239   QCOMPARE( model.data( alg1Index, QgsProcessingToolboxModel::RoleAlgorithmFlags ).toInt(), static_cast< int >( QgsProcessingAlgorithm::FlagHideFromModeler ) );
240   QCOMPARE( model.data( alg1Index, QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p3:a1" ) );
241   QCOMPARE( model.data( alg1Index, QgsProcessingToolboxModel::RoleAlgorithmName ).toString(), QStringLiteral( "a1" ) );
242   QCOMPARE( model.data( alg1Index, QgsProcessingToolboxModel::RoleAlgorithmTags ).toStringList().join( ',' ), QStringLiteral( "tag1,tag2" ) );
243   QCOMPARE( model.data( alg1Index, QgsProcessingToolboxModel::RoleAlgorithmShortDescription ).toString(), QStringLiteral( "short desc a" ) );
244 
245   QCOMPARE( model.algorithmForIndex( alg1Index )->id(), QStringLiteral( "p3:a1" ) );
246 
247   QModelIndex group2Index = model.index( 1, 0, providerIndex );
248   QCOMPARE( model.rowCount( group2Index ), 1 );
249   QVERIFY( model.hasChildren( group2Index ) );
250   QModelIndex alg2Index = model.index( 0, 0, group2Index );
251   QCOMPARE( model.data( alg2Index, Qt::DisplayRole ).toString(), QStringLiteral( "a2" ) );
252   QCOMPARE( model.data( alg2Index, Qt::ToolTipRole ).toString(), QStringLiteral( u"<p><b>a2</b></p><p>Algorithm ID: \u2018<i>p3:a2</i>\u2019</p>" ) );
253   QCOMPARE( model.data( alg2Index, QgsProcessingToolboxModel::RoleAlgorithmFlags ).toInt(), static_cast< int >( QgsProcessingAlgorithm::FlagHideFromToolbox ) );
254   QCOMPARE( model.data( alg2Index, QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p3:a2" ) );
255   QCOMPARE( model.data( alg2Index, QgsProcessingToolboxModel::RoleAlgorithmName ).toString(), QStringLiteral( "a2" ) );
256   QCOMPARE( model.data( alg2Index, QgsProcessingToolboxModel::RoleAlgorithmTags ).toStringList().join( ',' ), QString() );
257   QCOMPARE( model.data( alg2Index, QgsProcessingToolboxModel::RoleAlgorithmShortDescription ).toString(), QString() );
258   QCOMPARE( model.algorithmForIndex( alg2Index )->id(), QStringLiteral( "p3:a2" ) );
259 
260   // combined groups
261   DummyAlgorithm *a3 = new DummyAlgorithm( "a3", "group1" );
262   DummyAlgorithm *a4 = new DummyAlgorithm( "a4", "group1" );
263   DummyProvider *p4 = new DummyProvider( "p4", "provider4", QList< QgsProcessingAlgorithm * >() << a3 << a4 );
264   registry.addProvider( p4 );
265   const QModelIndex p4ProviderIndex = model.indexForProvider( p4->id() );
266   QModelIndex groupIndex = model.index( 0, 0, p4ProviderIndex );
267   QCOMPARE( model.rowCount( groupIndex ), 2 );
268   QCOMPARE( model.algorithmForIndex( model.index( 0, 0, groupIndex ) )->id(), QStringLiteral( "p4:a3" ) );
269   QCOMPARE( model.algorithmForIndex( model.index( 1, 0, groupIndex ) )->id(), QStringLiteral( "p4:a4" ) );
270 
271   // provider with algs with no groups
272   DummyAlgorithm *a5 = new DummyAlgorithm( "a5", "group1" );
273   DummyAlgorithm *a6 = new DummyAlgorithm( "a6", QString() );
274   DummyAlgorithm *a7 = new DummyAlgorithm( "a7", "group2" );
275   DummyProvider *p5 = new DummyProvider( "p5", "provider5", QList< QgsProcessingAlgorithm * >() << a5 << a6 << a7 );
276   registry.addProvider( p5 );
277   QCOMPARE( model.rowCount(), 6 );
278   QModelIndex p5ProviderIndex = model.indexForProvider( p5->id() );
279   QCOMPARE( model.rowCount( p5ProviderIndex ), 3 );
280 
281   groupIndex = model.index( 0, 0, p5ProviderIndex );
282   QCOMPARE( model.data( groupIndex, Qt::DisplayRole ).toString(), QStringLiteral( "group1" ) );
283   QCOMPARE( model.rowCount( groupIndex ), 1 );
284   QCOMPARE( model.algorithmForIndex( model.index( 0, 0, groupIndex ) )->id(), QStringLiteral( "p5:a5" ) );
285 
286   QCOMPARE( model.algorithmForIndex( model.index( 1, 0, p5ProviderIndex ) )->id(), QStringLiteral( "p5:a6" ) );
287 
288   groupIndex = model.index( 2, 0, p5ProviderIndex );
289   QCOMPARE( model.data( groupIndex, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
290   QCOMPARE( model.rowCount( groupIndex ), 1 );
291   QCOMPARE( model.algorithmForIndex( model.index( 0, 0, groupIndex ) )->id(), QStringLiteral( "p5:a7" ) );
292 
293   // reload provider
294   p5->refreshAlgorithms();
295   QCOMPARE( model.rowCount(), 6 );
296   p5ProviderIndex = model.indexForProvider( p5->id() );
297   QCOMPARE( model.rowCount( p5ProviderIndex ), 3 );
298 
299   groupIndex = model.index( 0, 0, p5ProviderIndex );
300   QCOMPARE( model.data( groupIndex, Qt::DisplayRole ).toString(), QStringLiteral( "group1" ) );
301   QCOMPARE( model.rowCount( groupIndex ), 1 );
302   QCOMPARE( model.algorithmForIndex( model.index( 0, 0, groupIndex ) )->id(), QStringLiteral( "p5:a5" ) );
303   QCOMPARE( model.algorithmForIndex( model.index( 1, 0, p5ProviderIndex ) )->id(), QStringLiteral( "p5:a6" ) );
304   groupIndex = model.index( 2, 0, p5ProviderIndex );
305   QCOMPARE( model.data( groupIndex, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
306   QCOMPARE( model.rowCount( groupIndex ), 1 );
307   QCOMPARE( model.algorithmForIndex( model.index( 0, 0, groupIndex ) )->id(), QStringLiteral( "p5:a7" ) );
308 
309   p3->refreshAlgorithms();
310   providerIndex = model.indexForProvider( p3->id() );
311   group1Index = model.index( 0, 0, providerIndex );
312   QCOMPARE( model.rowCount( group1Index ), 1 );
313   alg1Index = model.index( 0, 0, group1Index );
314   QCOMPARE( model.algorithmForIndex( alg1Index )->id(), QStringLiteral( "p3:a1" ) );
315   group2Index = model.index( 1, 0, providerIndex );
316   QCOMPARE( model.rowCount( group2Index ), 1 );
317   alg2Index = model.index( 0, 0, group2Index );
318   QCOMPARE( model.algorithmForIndex( alg2Index )->id(), QStringLiteral( "p3:a2" ) );
319 
320   // recent algorithms
321   QModelIndex recentIndex = model.index( 0, 0, QModelIndex() );
322   QCOMPARE( model.data( recentIndex, Qt::DisplayRole ).toString(), QStringLiteral( "Recently used" ) );
323   QCOMPARE( model.rowCount( recentIndex ), 0 );
324   recentLog.push( QStringLiteral( "p5:a5" ) );
325   QCOMPARE( model.rowCount( recentIndex ), 1 );
326   QCOMPARE( model.data( model.index( 0, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p5:a5" ) );
327   recentLog.push( QStringLiteral( "not valid" ) );
328   QCOMPARE( model.rowCount( recentIndex ), 1 );
329   QCOMPARE( model.data( model.index( 0, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p5:a5" ) );
330   recentLog.push( QStringLiteral( "p4:a3" ) );
331   QCOMPARE( model.rowCount( recentIndex ), 2 );
332   QCOMPARE( model.data( model.index( 0, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p4:a3" ) );
333   QCOMPARE( model.data( model.index( 1, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p5:a5" ) );
334 
335   // remove a provider
336   registry.removeProvider( p1 );
337   QCOMPARE( model.rowCount(), 5 );
338   QVERIFY( model.index( 0, 0, QModelIndex() ).isValid() );
339   QCOMPARE( model.providerForIndex( model.index( 1, 0, QModelIndex() ) ), p2 );
340   QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider2" ) );
341   registry.removeProvider( p5 );
342   QCOMPARE( model.rowCount(), 4 );
343   recentIndex = model.index( 0, 0, QModelIndex() );
344   QCOMPARE( model.rowCount( recentIndex ), 1 );
345   QCOMPARE( model.data( model.index( 0, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p4:a3" ) );
346   registry.removeProvider( p2 );
347   QCOMPARE( model.rowCount(), 3 );
348   registry.removeProvider( p3 );
349   QCOMPARE( model.rowCount(), 2 );
350   recentIndex = model.index( 0, 0, QModelIndex() );
351   QCOMPARE( model.rowCount( recentIndex ), 1 );
352   registry.removeProvider( p4 );
353   recentIndex = model.index( 0, 0, QModelIndex() );
354   QCOMPARE( model.rowCount( recentIndex ), 0 );
355   QCOMPARE( model.rowCount(), 1 );
356   QCOMPARE( model.columnCount(), 1 );
357   QVERIFY( model.hasChildren() );
358   QCOMPARE( model.data( model.index( 0, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "Recently used" ) );
359   QVERIFY( !model.providerForIndex( model.index( 0, 0, QModelIndex() ) ) );
360   QVERIFY( !model.providerForIndex( model.index( 1, 0, QModelIndex() ) ) );
361   QVERIFY( !model.indexForProvider( nullptr ).isValid() );
362 
363   // qgis native algorithms put groups at top level
364   QgsProcessingRegistry registry2;
365   const QgsProcessingToolboxModel model2( nullptr, &registry2 );
366   DummyAlgorithm *qgisA1 = new DummyAlgorithm( "a1", "group1" );
367   DummyAlgorithm *qgisA2 = new DummyAlgorithm( "a2", "group2" );
368   DummyAlgorithm *qgisA3 = new DummyAlgorithm( "a3", "group1" );
369   DummyAlgorithm *qgisA4 = new DummyAlgorithm( "a4", "group3" );
370   DummyProvider *qgisP = new DummyProvider( "qgis", "qgis_provider", QList< QgsProcessingAlgorithm * >() << qgisA1 << qgisA2 << qgisA3 << qgisA4 );
371   registry2.addProvider( qgisP );
372 
373   QCOMPARE( model2.rowCount(), 3 );
374   group1Index = model2.index( 0, 0 );
375   group2Index = model2.index( 1, 0 );
376   const QModelIndex group3Index = model2.index( 2, 0 );
377   QCOMPARE( model2.data( group1Index, Qt::DisplayRole ).toString(), QStringLiteral( "group1" ) );
378   QCOMPARE( model2.data( group2Index, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
379   QCOMPARE( model2.data( group3Index, Qt::DisplayRole ).toString(), QStringLiteral( "group3" ) );
380 
381   QCOMPARE( model2.rowCount( group1Index ), 2 );
382   QCOMPARE( model2.algorithmForIndex( model2.index( 0, 0, group1Index ) )->id(), QStringLiteral( "qgis:a1" ) );
383   QCOMPARE( model2.algorithmForIndex( model2.index( 1, 0, group1Index ) )->id(), QStringLiteral( "qgis:a3" ) );
384   QCOMPARE( model2.rowCount( group2Index ), 1 );
385   QCOMPARE( model2.algorithmForIndex( model2.index( 0, 0, group2Index ) )->id(), QStringLiteral( "qgis:a2" ) );
386   QCOMPARE( model2.rowCount( group3Index ), 1 );
387   QCOMPARE( model2.algorithmForIndex( model2.index( 0, 0, group3Index ) )->id(), QStringLiteral( "qgis:a4" ) );
388 }
389 
testProxyModel()390 void TestQgsProcessingModel::testProxyModel()
391 {
392   QgsSettings().clear();
393   QgsProcessingRegistry registry;
394   QgsProcessingRecentAlgorithmLog recentLog;
395   QgsProcessingToolboxProxyModel model( nullptr, &registry, &recentLog );
396 
397 #ifdef ENABLE_MODELTEST
398   new ModelTest( &model, this ); // for model validity checking
399 #endif
400 
401   // add a provider
402   DummyAlgorithm *a1 = new DummyAlgorithm( "a1", "group2", QgsProcessingAlgorithm::FlagHideFromModeler );
403   DummyProvider *p1 = new DummyProvider( "p2", "provider2", QList< QgsProcessingAlgorithm * >() << a1 );
404   registry.addProvider( p1 );
405   // second provider
406   DummyAlgorithm *a2 = new DummyAlgorithm( "a2", "group2", QgsProcessingAlgorithm::FlagHideFromModeler,
407       QStringLiteral( "buffer,vector" ), QStringLiteral( "short desc" ), QStringLiteral( "algorithm2" ) );
408   DummyProvider *p2 = new DummyProvider( "p1", "provider1", QList< QgsProcessingAlgorithm * >() << a2 );
409   registry.addProvider( p2 );
410 
411   QCOMPARE( model.data( model.index( 0, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider1" ) );
412   QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider2" ) );
413 
414   // top level groups come first
415   DummyAlgorithm *qgisA1 = new DummyAlgorithm( "a1", "group2", QgsProcessingAlgorithm::FlagHideFromModeler );
416   DummyAlgorithm *qgisA2 = new DummyAlgorithm( "a2", "group1", QgsProcessingAlgorithm::FlagHideFromToolbox );
417   DummyProvider *qgisP = new DummyProvider( "qgis", "qgis_provider", QList< QgsProcessingAlgorithm * >() << qgisA1 << qgisA2 );
418   registry.addProvider( qgisP );
419 
420   QModelIndex group1Index = model.index( 0, 0, QModelIndex() );
421   QModelIndex group2Index = model.index( 1, 0, QModelIndex() );
422   QCOMPARE( model.data( group1Index, Qt::DisplayRole ).toString(), QStringLiteral( "group1" ) );
423   QCOMPARE( model.data( group2Index, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
424   QCOMPARE( model.data( model.index( 2, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider1" ) );
425   QCOMPARE( model.data( model.index( 3, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider2" ) );
426   QCOMPARE( model.rowCount(), 4 );
427 
428   QModelIndex alg1Index = model.index( 0, 0, group2Index );
429   QCOMPARE( model.data( alg1Index, Qt::DisplayRole ).toString(), QStringLiteral( "a1" ) );
430   const QModelIndex alg2Index = model.index( 0, 0, group1Index );
431   QCOMPARE( model.data( alg2Index, Qt::DisplayRole ).toString(), QStringLiteral( "a2" ) );
432 
433   // empty providers/groups should not be shown
434   model.setFilters( QgsProcessingToolboxProxyModel::FilterModeler );
435   group1Index = model.index( 0, 0, QModelIndex() );
436   QCOMPARE( model.rowCount(), 1 );
437   QCOMPARE( model.rowCount( group1Index ), 1 );
438   QCOMPARE( model.data( group1Index, Qt::DisplayRole ).toString(), QStringLiteral( "group1" ) );
439   QCOMPARE( model.data( model.index( 0, 0, group1Index ), Qt::DisplayRole ).toString(), QStringLiteral( "a2" ) );
440   model.setFilters( QgsProcessingToolboxProxyModel::FilterToolbox );
441   group2Index = model.index( 0, 0, QModelIndex() );
442   QCOMPARE( model.rowCount(), 3 );
443   QCOMPARE( model.rowCount( group2Index ), 1 );
444   QCOMPARE( model.data( group2Index, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
445   QCOMPARE( model.rowCount( group2Index ), 1 );
446   QCOMPARE( model.data( model.index( 0, 0, group1Index ), Qt::DisplayRole ).toString(), QStringLiteral( "a1" ) );
447 
448   // test filter strings
449   model.setFilters( QgsProcessingToolboxProxyModel::Filters() );
450   // filter by algorithm id
451   model.setFilterString( "a1" );
452   QCOMPARE( model.rowCount(), 2 );
453   group1Index = model.index( 0, 0, QModelIndex() );
454   QModelIndex provider2Index = model.index( 1, 0, QModelIndex() );
455   QCOMPARE( model.rowCount( group1Index ), 1 );
456   QCOMPARE( model.data( group1Index, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
457   QCOMPARE( model.data( model.index( 0, 0, group1Index ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "qgis:a1" ) );
458   QCOMPARE( model.rowCount( group2Index ), 1 );
459   QCOMPARE( model.data( provider2Index, Qt::DisplayRole ).toString(), QStringLiteral( "provider2" ) );
460   QCOMPARE( model.rowCount( provider2Index ), 1 );
461   group2Index = model.index( 0, 0, provider2Index );
462   QCOMPARE( model.rowCount( group2Index ), 1 );
463   QCOMPARE( model.data( group2Index, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
464   QCOMPARE( model.data( model.index( 0, 0, group2Index ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p2:a1" ) );
465 
466   // filter by algorithm display name
467   model.setFilterString( QStringLiteral( "ALGOR" ) );
468   QCOMPARE( model.rowCount(), 1 );
469   QModelIndex provider1Index = model.index( 0, 0, QModelIndex() );
470   QCOMPARE( model.rowCount( provider1Index ), 1 );
471   QCOMPARE( model.data( provider1Index, Qt::DisplayRole ).toString(), QStringLiteral( "provider1" ) );
472   QCOMPARE( model.rowCount( provider1Index ), 1 );
473   group2Index = model.index( 0, 0, provider1Index );
474   QCOMPARE( model.data( group2Index, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
475   QCOMPARE( model.rowCount( group2Index ), 1 );
476   QCOMPARE( model.data( model.index( 0, 0, group2Index ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p1:a2" ) );
477 
478   // filter by algorithm tags
479   model.setFilterString( QStringLiteral( "buff CTOR" ) );
480   QCOMPARE( model.rowCount(), 1 );
481   provider1Index = model.index( 0, 0, QModelIndex() );
482   QCOMPARE( model.rowCount( provider1Index ), 1 );
483   QCOMPARE( model.data( provider1Index, Qt::DisplayRole ).toString(), QStringLiteral( "provider1" ) );
484   QCOMPARE( model.rowCount( provider1Index ), 1 );
485   group2Index = model.index( 0, 0, provider1Index );
486   QCOMPARE( model.data( group2Index, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
487   QCOMPARE( model.rowCount( group2Index ), 1 );
488   QCOMPARE( model.data( model.index( 0, 0, group2Index ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p1:a2" ) );
489 
490   // filter by algorithm short desc
491   model.setFilterString( QStringLiteral( "buff CTOR desc" ) );
492   QCOMPARE( model.rowCount(), 1 );
493   provider1Index = model.index( 0, 0, QModelIndex() );
494   QCOMPARE( model.rowCount( provider1Index ), 1 );
495   QCOMPARE( model.data( provider1Index, Qt::DisplayRole ).toString(), QStringLiteral( "provider1" ) );
496   QCOMPARE( model.rowCount( provider1Index ), 1 );
497   group2Index = model.index( 0, 0, provider1Index );
498   QCOMPARE( model.data( group2Index, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
499   QCOMPARE( model.rowCount( group2Index ), 1 );
500   QCOMPARE( model.data( model.index( 0, 0, group2Index ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p1:a2" ) );
501 
502   // filter by group
503   model.setFilterString( QStringLiteral( "group2" ) );
504   QCOMPARE( model.rowCount(), 3 );
505   group2Index = model.index( 0, 0, QModelIndex() );
506   QCOMPARE( model.rowCount( group2Index ), 1 );
507   QCOMPARE( model.data( group2Index, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
508   alg1Index = model.index( 0, 0, group2Index );
509   QCOMPARE( model.data( alg1Index, QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "qgis:a1" ) );
510   provider1Index = model.index( 1, 0, QModelIndex() );
511   QCOMPARE( model.rowCount( provider1Index ), 1 );
512   QCOMPARE( model.data( provider1Index, Qt::DisplayRole ).toString(), QStringLiteral( "provider1" ) );
513   QCOMPARE( model.rowCount( provider1Index ), 1 );
514   group2Index = model.index( 0, 0, provider1Index );
515   QCOMPARE( model.data( group2Index, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
516   QCOMPARE( model.rowCount( group2Index ), 1 );
517   QCOMPARE( model.data( model.index( 0, 0, group2Index ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p1:a2" ) );
518   provider2Index = model.index( 2, 0, QModelIndex() );
519   QCOMPARE( model.rowCount( provider2Index ), 1 );
520   QCOMPARE( model.data( provider2Index, Qt::DisplayRole ).toString(), QStringLiteral( "provider2" ) );
521   group2Index = model.index( 0, 0, provider2Index );
522   QCOMPARE( model.data( group2Index, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
523   QCOMPARE( model.rowCount( group2Index ), 1 );
524   QCOMPARE( model.data( model.index( 0, 0, group2Index ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p2:a1" ) );
525 
526   model.setFilterString( QString() );
527   QCOMPARE( model.rowCount(), 4 );
528 
529   // check sort order of recent algorithms
530   recentLog.push( QStringLiteral( "p2:a1" ) );
531   QCOMPARE( model.rowCount(), 5 );
532   const QModelIndex recentIndex = model.index( 0, 0, QModelIndex() );
533   QCOMPARE( model.data( recentIndex, Qt::DisplayRole ).toString(), QStringLiteral( "Recently used" ) );
534   QCOMPARE( model.rowCount( recentIndex ), 1 );
535   QCOMPARE( model.data( model.index( 0, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p2:a1" ) );
536   recentLog.push( QStringLiteral( "p1:a2" ) );
537   QCOMPARE( model.rowCount( recentIndex ), 2 );
538   QCOMPARE( model.data( model.index( 0, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p1:a2" ) );
539   QCOMPARE( model.data( model.index( 1, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p2:a1" ) );
540   recentLog.push( QStringLiteral( "qgis:a1" ) );
541   QCOMPARE( model.rowCount( recentIndex ), 3 );
542   QCOMPARE( model.data( model.index( 0, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "qgis:a1" ) );
543   QCOMPARE( model.data( model.index( 1, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p1:a2" ) );
544   QCOMPARE( model.data( model.index( 2, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p2:a1" ) );
545   recentLog.push( QStringLiteral( "p2:a1" ) );
546   QCOMPARE( model.rowCount( recentIndex ), 3 );
547   QCOMPARE( model.data( model.index( 0, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p2:a1" ) );
548   QCOMPARE( model.data( model.index( 1, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "qgis:a1" ) );
549   QCOMPARE( model.data( model.index( 2, 0, recentIndex ), QgsProcessingToolboxModel::RoleAlgorithmId ).toString(), QStringLiteral( "p1:a2" ) );
550 
551   // inactive provider - should not be visible
552   QCOMPARE( model.rowCount(), 5 );
553   DummyAlgorithm *qgisA31 = new DummyAlgorithm( "a3", "group1" );
554   DummyProvider *p3 = new DummyProvider( "p3", "provider3", QList< QgsProcessingAlgorithm * >() << qgisA31 );
555   p3->mActive = false;
556   registry.addProvider( p3 );
557   QCOMPARE( model.rowCount(), 5 );
558 }
559 
testView()560 void TestQgsProcessingModel::testView()
561 {
562   QgsSettings().clear();
563   QgsProcessingRegistry registry;
564   QgsProcessingRecentAlgorithmLog recentLog;
565   QgsProcessingToolboxTreeView view( nullptr, &registry, &recentLog );
566 
567   // Check view model consistency
568   QVERIFY( view.mModel );
569   QVERIFY( view.mToolboxModel );
570   QCOMPARE( view.mModel->toolboxModel(), view.mToolboxModel );
571 
572   QVERIFY( !view.algorithmForIndex( QModelIndex() ) );
573 
574   // add a provider
575   DummyAlgorithm *a1 = new DummyAlgorithm( "a1", "group2", QgsProcessingAlgorithm::FlagHideFromToolbox );
576   DummyProvider *p1 = new DummyProvider( "p2", "provider2", QList< QgsProcessingAlgorithm * >() << a1 );
577   registry.addProvider( p1 );
578   // second provider
579   DummyAlgorithm *a2 = new DummyAlgorithm( "a2", "group2", QgsProcessingAlgorithm::FlagHideFromModeler,
580       QStringLiteral( "buffer,vector" ), QStringLiteral( "short desc" ), QStringLiteral( "algorithm2" ) );
581   DummyProvider *p2 = new DummyProvider( "p1", "provider1", QList< QgsProcessingAlgorithm * >() << a2 );
582   registry.addProvider( p2 );
583 
584   QModelIndex provider1Index = view.model()->index( 0, 0, QModelIndex() );
585   QVERIFY( !view.algorithmForIndex( provider1Index ) );
586   QModelIndex group2Index = view.model()->index( 0, 0, provider1Index );
587   QCOMPARE( view.model()->data( group2Index, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
588   QVERIFY( !view.algorithmForIndex( group2Index ) );
589   QModelIndex alg2Index = view.model()->index( 0, 0, group2Index );
590   QCOMPARE( view.algorithmForIndex( alg2Index )->id(), QStringLiteral( "p1:a2" ) );
591   QModelIndex provider2Index = view.model()->index( 1, 0, QModelIndex() );
592   QVERIFY( !view.algorithmForIndex( provider2Index ) );
593   QCOMPARE( view.model()->data( provider2Index, Qt::DisplayRole ).toString(), QStringLiteral( "provider2" ) );
594   group2Index = view.model()->index( 0, 0, provider2Index );
595   QCOMPARE( view.model()->data( group2Index, Qt::DisplayRole ).toString(), QStringLiteral( "group2" ) );
596   const QModelIndex alg1Index = view.model()->index( 0, 0, group2Index );
597   QCOMPARE( view.algorithmForIndex( alg1Index )->id(), QStringLiteral( "p2:a1" ) );
598 
599   // empty providers/groups should not be shown
600   view.setFilters( QgsProcessingToolboxProxyModel::FilterModeler );
601   QCOMPARE( view.filters(), QgsProcessingToolboxProxyModel::FilterModeler );
602   QCOMPARE( view.model()->rowCount(), 1 );
603   provider2Index = view.model()->index( 0, 0, QModelIndex() );
604   QCOMPARE( view.model()->data( provider2Index, Qt::DisplayRole ).toString(), QStringLiteral( "provider2" ) );
605   QCOMPARE( view.model()->rowCount( provider2Index ), 1 );
606   group2Index = view.model()->index( 0, 0, provider2Index );
607   QCOMPARE( view.model()->rowCount( group2Index ), 1 );
608   QCOMPARE( view.algorithmForIndex( view.model()->index( 0, 0, group2Index ) )->id(), QStringLiteral( "p2:a1" ) );
609   view.setFilters( QgsProcessingToolboxProxyModel::FilterToolbox );
610   QCOMPARE( view.model()->rowCount(), 1 );
611   provider1Index = view.model()->index( 0, 0, QModelIndex() );
612   QCOMPARE( view.model()->data( provider1Index, Qt::DisplayRole ).toString(), QStringLiteral( "provider1" ) );
613   QCOMPARE( view.model()->rowCount( provider1Index ), 1 );
614   group2Index = view.model()->index( 0, 0, provider1Index );
615   QCOMPARE( view.model()->rowCount( group2Index ), 1 );
616   QCOMPARE( view.algorithmForIndex( view.model()->index( 0, 0, group2Index ) )->id(), QStringLiteral( "p1:a2" ) );
617 
618   view.setFilters( QgsProcessingToolboxProxyModel::Filters() );
619   QCOMPARE( view.filters(), QgsProcessingToolboxProxyModel::Filters() );
620   // test filter strings
621   view.setFilterString( "a1" );
622   provider2Index = view.model()->index( 0, 0, QModelIndex() );
623   QCOMPARE( view.model()->data( provider2Index, Qt::DisplayRole ).toString(), QStringLiteral( "provider2" ) );
624   QCOMPARE( view.model()->rowCount(), 1 );
625   QCOMPARE( view.model()->rowCount( provider2Index ), 1 );
626   group2Index = view.model()->index( 0, 0, provider2Index );
627   QCOMPARE( view.model()->rowCount( group2Index ), 1 );
628   QCOMPARE( view.algorithmForIndex( view.model()->index( 0, 0, group2Index ) )->id(), QStringLiteral( "p2:a1" ) );
629 
630   view.setFilterString( QString() );
631 
632   // selected algorithm
633   provider1Index = view.model()->index( 0, 0, QModelIndex() );
634   view.selectionModel()->clear();
635   QVERIFY( !view.selectedAlgorithm() );
636   view.selectionModel()->select( provider1Index, QItemSelectionModel::ClearAndSelect );
637   QVERIFY( !view.selectedAlgorithm() );
638   group2Index = view.model()->index( 0, 0, provider1Index );
639   view.selectionModel()->select( group2Index, QItemSelectionModel::ClearAndSelect );
640   QVERIFY( !view.selectedAlgorithm() );
641   alg2Index = view.model()->index( 0, 0, group2Index );
642   view.selectionModel()->select( alg2Index, QItemSelectionModel::ClearAndSelect );
643   QCOMPARE( view.selectedAlgorithm()->id(), QStringLiteral( "p1:a2" ) );
644 
645   // when a filter string removes the selected algorithm, the next matching algorithm should be auto-selected
646   view.setFilterString( QStringLiteral( "p2:a1" ) );
647   QCOMPARE( view.selectedAlgorithm()->id(), QStringLiteral( "p2:a1" ) );
648   // but if it doesn't remove the selected one, that algorithm should not be deselected
649   view.setFilterString( QStringLiteral( "a" ) );
650   QCOMPARE( view.selectedAlgorithm()->id(), QStringLiteral( "p2:a1" ) );
651 
652   // Check view model consistency after resetting registry
653   view.setRegistry( &registry );
654   QVERIFY( view.mModel );
655   QVERIFY( view.mToolboxModel );
656   QCOMPARE( view.mModel->toolboxModel(), view.mToolboxModel );
657 }
658 
testKnownIssues()659 void TestQgsProcessingModel::testKnownIssues()
660 {
661   QgsProcessingRegistry registry;
662   QgsProcessingRecentAlgorithmLog recentLog;
663   const QgsProcessingToolboxModel model( nullptr, &registry, &recentLog );
664   DummyAlgorithm *a1 = new DummyAlgorithm( "a1", "group1", QgsProcessingAlgorithm::FlagKnownIssues, QStringLiteral( "tag1,tag2" ), QStringLiteral( "short desc a" ) );
665   DummyAlgorithm *a2 = new DummyAlgorithm( "b1", "group1", QgsProcessingAlgorithm::Flags(), QStringLiteral( "tag1,tag2" ), QStringLiteral( "short desc b" ) );
666   DummyProvider *p = new DummyProvider( "p3", "provider3", QList< QgsProcessingAlgorithm * >() << a1 << a2 );
667   registry.addProvider( p );
668 
669   QModelIndex providerIndex = model.index( 1, 0, QModelIndex() );
670   QModelIndex group1Index = model.index( 0, 0, providerIndex );
671   QCOMPARE( model.data( model.index( 0, 0, group1Index ), Qt::DisplayRole ).toString(), QStringLiteral( "a1" ) );
672   QVERIFY( model.data( model.index( 0, 0, group1Index ), Qt::ToolTipRole ).toString().contains( QStringLiteral( "known issues" ) ) );
673   QCOMPARE( model.data( model.index( 0, 0, group1Index ), Qt::ForegroundRole ).value< QBrush >().color().name(), QStringLiteral( "#ff0000" ) );
674   QCOMPARE( model.data( model.index( 1, 0, group1Index ), Qt::DisplayRole ).toString(), QStringLiteral( "b1" ) );
675   QVERIFY( !model.data( model.index( 1, 0, group1Index ), Qt::ToolTipRole ).toString().contains( QStringLiteral( "known issues" ) ) );
676   QCOMPARE( model.data( model.index( 1, 0, group1Index ), Qt::ForegroundRole ).value< QBrush >().color().name(), QStringLiteral( "#000000" ) );
677 
678   QgsProcessingToolboxProxyModel proxyModel( nullptr, &registry, &recentLog );
679   providerIndex = proxyModel.index( 0, 0, QModelIndex() );
680   group1Index = proxyModel.index( 0, 0, providerIndex );
681   // by default known issues are filtered out
682   QCOMPARE( proxyModel.rowCount( group1Index ), 1 );
683   QCOMPARE( proxyModel.data( proxyModel.index( 0, 0, group1Index ), Qt::DisplayRole ).toString(), QStringLiteral( "b1" ) );
684   QVERIFY( !proxyModel.data( proxyModel.index( 0, 0, group1Index ), Qt::ToolTipRole ).toString().contains( QStringLiteral( "known issues" ) ) );
685   QCOMPARE( proxyModel.data( proxyModel.index( 0, 0, group1Index ), Qt::ForegroundRole ).value< QBrush >().color().name(), QStringLiteral( "#000000" ) );
686   proxyModel.setFilters( QgsProcessingToolboxProxyModel::Filters( QgsProcessingToolboxProxyModel::FilterToolbox | QgsProcessingToolboxProxyModel::FilterShowKnownIssues ) );
687   QCOMPARE( proxyModel.rowCount( group1Index ), 2 );
688   QCOMPARE( proxyModel.data( proxyModel.index( 0, 0, group1Index ), Qt::DisplayRole ).toString(), QStringLiteral( "a1" ) );
689   QVERIFY( proxyModel.data( proxyModel.index( 0, 0, group1Index ), Qt::ToolTipRole ).toString().contains( QStringLiteral( "known issues" ) ) );
690   QCOMPARE( proxyModel.data( proxyModel.index( 0, 0, group1Index ), Qt::ForegroundRole ).value< QBrush >().color().name(), QStringLiteral( "#ff0000" ) );
691   QCOMPARE( proxyModel.data( proxyModel.index( 1, 0, group1Index ), Qt::DisplayRole ).toString(), QStringLiteral( "b1" ) );
692   QVERIFY( !proxyModel.data( proxyModel.index( 1, 0, group1Index ), Qt::ToolTipRole ).toString().contains( QStringLiteral( "known issues" ) ) );
693   QCOMPARE( proxyModel.data( proxyModel.index( 1, 0, group1Index ), Qt::ForegroundRole ).value< QBrush >().color().name(), QStringLiteral( "#000000" ) );
694 
695 
696 }
697 
698 QGSTEST_MAIN( TestQgsProcessingModel )
699 #include "testqgsprocessingmodel.moc"
700