LeechCraft 0.6.70-19424-g027e980fd1
Modular cross-platform feature rich live environment.
Loading...
Searching...
No Matches
flattofoldersproxymodeltest.cpp
Go to the documentation of this file.
1/**********************************************************************
2 * LeechCraft - modular cross-platform feature rich internet client.
3 * Copyright (C) 2006-2014 Georg Rudoy
4 *
5 * Distributed under the Boost Software License, Version 1.0.
6 * (See accompanying file LICENSE or copy at https://www.boost.org/LICENSE_1_0.txt)
7 **********************************************************************/
8
10#include <algorithm>
11#include <memory>
12#include <optional>
13#include <QAbstractItemModelTester>
14#include <QHash>
15#include <QMimeData>
16#include <QRandomGenerator>
17#include <QSignalSpy>
18#include <QSortFilterProxyModel>
19#include <QStandardItemModel>
20#include <QTest>
22
24
25namespace LC::Util
26{
27 namespace
28 {
29 constexpr int IdRole = Qt::UserRole + 1;
30 constexpr int KeysRole = Qt::UserRole + 2;
31 const auto DropFormat = QStringLiteral ("x-test/folder");
32 const auto Unkeyed = QStringLiteral ("unkeyed");
33
34 struct Source : QStandardItemModel
35 {
36 using QStandardItemModel::persistentIndexList;
37
38 mutable QList<int> LastDraggedIds_;
39 std::optional<QByteArray> LastDropKey_;
40
41 void Append (int id, const QString& text, const QStringList& keys)
42 {
43 const auto first = new QStandardItem { text };
44 first->setData (id, IdRole);
45 first->setData (keys, KeysRole);
46 appendRow ({ first, new QStandardItem { QString::number (id * 10) } });
47 }
48
49 void SetKeys (int row, const QStringList& keys)
50 {
51 item (row)->setData (keys, KeysRole);
52 }
53
54 QMimeData* mimeData (const QModelIndexList& indexes) const override
55 {
56 LastDraggedIds_.clear ();
57 for (const auto& index : indexes)
58 LastDraggedIds_ << index.data (IdRole).toInt ();
59 std::ranges::sort (LastDraggedIds_);
60 return QStandardItemModel::mimeData (indexes);
61 }
62
63 bool dropMimeData (const QMimeData *data, Qt::DropAction, int, int, const QModelIndex&) override
64 {
65 LastDropKey_ = data->hasFormat (DropFormat) ?
66 std::optional { data->data (DropFormat) } :
67 std::nullopt;
68 return true;
69 }
70 };
71
72 struct Filter : QSortFilterProxyModel
73 {
74 using QSortFilterProxyModel::persistentIndexList;
75 };
76
77 // the keys stored in the source rows map to themselves unless Aliases_ says otherwise,
78 // which is how a key gets replaced behind the source's back
79 struct Fixture
80 {
81 QHash<QString, QStringList> Aliases_;
82 Source Source_;
83 Filter Filter_;
84 QAbstractItemModel& Upstream_;
85 FlatToFoldersProxyModel Proxy_ { MakeConfig () };
86 QAbstractItemModelTester Tester_ { &Proxy_, QAbstractItemModelTester::FailureReportingMode::QtTest };
87
88 explicit Fixture (bool viaFilter)
89 : Upstream_ { viaFilter ? static_cast<QAbstractItemModel&> (Filter_) : Source_ }
90 {
91 Filter_.setSourceModel (&Source_);
92 }
93
94 FlatToFoldersProxyModel::Config MakeConfig () const
95 {
96 return
97 {
98 .KeysOf_ = [this] (const QModelIndex& index) { return KeysOf (index); },
99 .UnkeyedLabel_ = Unkeyed,
100 .DropFormat_ = DropFormat,
101 };
102 }
103
104 QStringList KeysOf (const QModelIndex& index) const
105 {
106 QStringList keys;
107 for (const auto& stored : index.data (KeysRole).toStringList ())
108 keys += Aliases_.value (stored, { stored });
109 return keys;
110 }
111
112 void FillDefault ()
113 {
114 Source_.Append (1, "a", { "t1" });
115 Source_.Append (2, "b", { "t1", "t2" });
116 Source_.Append (3, "c", {});
117 }
118
119 void Attach ()
120 {
121 Proxy_.SetSourceModel (&Upstream_);
122 }
123
124 void ReplaceKey (const QString& old, const QStringList& newKeys)
125 {
126 for (int row = 0; row < Source_.rowCount (); ++row)
127 for (const auto& stored : Source_.index (row, 0).data (KeysRole).toStringList ())
128 {
129 auto keys = Aliases_.value (stored, { stored });
130 if (!keys.contains (old))
131 continue;
132
133 keys.removeAll (old);
134 for (const auto& key : newKeys)
135 if (!keys.contains (key))
136 keys << key;
137 Aliases_ [stored] = keys;
138 }
139 Proxy_.ReplaceKey (old, newKeys);
140 }
141
142 int UpstreamPersistentCount () const
143 {
144 return (&Upstream_ == &Filter_ ? Filter_.persistentIndexList () : Source_.persistentIndexList ()).size ();
145 }
146 };
147
148 struct Spies
149 {
150 QSignalSpy Inserted_;
151 QSignalSpy Removed_;
152 QSignalSpy Reset_;
153 QSignalSpy Layout_;
154 QSignalSpy Data_;
155
156 explicit Spies (QAbstractItemModel& model)
157 : Inserted_ { &model, &QAbstractItemModel::rowsInserted }
158 , Removed_ { &model, &QAbstractItemModel::rowsRemoved }
159 , Reset_ { &model, &QAbstractItemModel::modelReset }
160 , Layout_ { &model, &QAbstractItemModel::layoutChanged }
161 , Data_ { &model, &QAbstractItemModel::dataChanged }
162 {
163 }
164
165 int Structural () const
166 {
167 return Inserted_.size () + Removed_.size () + Reset_.size () + Layout_.size ();
168 }
169 };
170
171 QStringList ExpectedKeysOf (const Fixture& fixture, const QModelIndex& index)
172 {
173 auto keys = fixture.KeysOf (index);
174 keys.removeDuplicates ();
175 if (keys.isEmpty ())
176 keys << Unkeyed;
177 return keys;
178 }
179
180 QString FolderName (const QModelIndex& folder)
181 {
182 return folder.data ().toString ();
183 }
184
185 using Picture = QMap<QString, QSet<int>>;
186
187 Picture Expected (const Fixture& fixture)
188 {
189 Picture result;
190 for (int row = 0; row < fixture.Upstream_.rowCount (); ++row)
191 {
192 const auto& index = fixture.Upstream_.index (row, 0);
193 for (const auto& key : ExpectedKeysOf (fixture, index))
194 result [key] << index.data (IdRole).toInt ();
195 }
196 return result;
197 }
198
199 QString Describe (const Picture& picture)
200 {
201 QStringList folders;
202 for (const auto& [key, ids] : picture.asKeyValueRange ())
203 {
204 auto sorted = ids.values ();
205 std::ranges::sort (sorted);
206 QStringList idStrings;
207 for (const auto id : sorted)
208 idStrings << QString::number (id);
209 folders << key + " {" + idStrings.join (' ') + '}';
210 }
211 return folders.join ("; ");
212 }
213
214 QStringList Dump (const FlatToFoldersProxyModel& proxy)
215 {
216 QStringList result;
217 for (int f = 0; f < proxy.rowCount (); ++f)
218 {
219 const auto& folder = proxy.index (f, 0);
220 QStringList ids;
221 for (int l = 0; l < proxy.rowCount (folder); ++l)
222 ids << QString::number (proxy.index (l, 0, folder).data (IdRole).toInt ());
223 result << FolderName (folder) + ": " + ids.join (' ');
224 }
225 return result;
226 }
227
228 void CheckTree (const Fixture& fixture)
229 {
230 const auto& proxy = fixture.Proxy_;
231 const auto& upstream = fixture.Upstream_;
232 QCOMPARE (proxy.columnCount (), upstream.columnCount ());
233
234 Picture actual;
235 int leaves = 0;
236 for (int f = 0; f < proxy.rowCount (); ++f)
237 {
238 const auto& folderIdx = proxy.index (f, 0);
239 QVERIFY (proxy.IsFolder (folderIdx));
240 QCOMPARE (proxy.parent (folderIdx), QModelIndex {});
241 if (upstream.columnCount () > 1)
242 QCOMPARE (proxy.rowCount (folderIdx.siblingAtColumn (1)), 0);
243
244 const auto& key = FolderName (folderIdx);
245 QVERIFY2 (!actual.contains (key), qPrintable ("duplicate folder for key " + key));
246 QVERIFY2 (proxy.rowCount (folderIdx) > 0, qPrintable ("empty folder for key " + key));
247
248 auto& ids = actual [key];
249 for (int l = 0; l < proxy.rowCount (folderIdx); ++l)
250 {
251 ++leaves;
252 const auto& leafIdx = proxy.index (l, 0, folderIdx);
253 QVERIFY (!proxy.IsFolder (leafIdx));
254 QCOMPARE (proxy.rowCount (leafIdx), 0);
255 QCOMPARE (proxy.parent (leafIdx), folderIdx);
256
257 const auto& source = proxy.MapToSource (leafIdx);
258 QVERIFY (source.isValid ());
259 QCOMPARE (source.model (), &upstream);
260 QVERIFY (ExpectedKeysOf (fixture, source).contains (key));
261 QVERIFY (proxy.MapFromSource (source).contains (leafIdx));
262 for (int col = 0; col < upstream.columnCount (); ++col)
263 QCOMPARE (proxy.index (l, col, folderIdx).data (), source.siblingAtColumn (col).data ());
264
265 ids << leafIdx.data (IdRole).toInt ();
266 }
267 }
268
269 const auto& expected = Expected (fixture);
270 QVERIFY2 (actual == expected,
271 qPrintable ("tree is " + Describe (actual) + ", source says " + Describe (expected)));
272
273 int expectedLeaves = 0;
274 for (int row = 0; row < upstream.rowCount (); ++row)
275 expectedLeaves += ExpectedKeysOf (fixture, upstream.index (row, 0)).size ();
276 QCOMPARE (leaves, expectedLeaves);
277 }
278
279 QModelIndex Parent (const QList<QVariant>& signalArgs)
280 {
281 return signalArgs.at (0).value<QModelIndex> ();
282 }
283 }
284
285 void FlatToFoldersProxyModelTest::initTestCase ()
286 {
287 // The fixture attaches its own tester in QtTest reporting mode.
288 qunsetenv ("LC_MODEL_SANITIZER_MODE");
289 }
290
291 void FlatToFoldersProxyModelTest::initTestCase_data ()
292 {
293 QTest::addColumn<bool> ("viaFilter");
294 QTest::newRow ("direct source") << false;
295 QTest::newRow ("through a QSortFilterProxyModel") << true;
296 }
297
298 void FlatToFoldersProxyModelTest::testInitialTree ()
299 {
300 QFETCH_GLOBAL (bool, viaFilter);
301 Fixture f { viaFilter };
302 f.FillDefault ();
303 f.Attach ();
304 CheckTree (f);
305
306 QCOMPARE (Dump (f.Proxy_), (QStringList { "t1: 1 2", "t2: 2", "unkeyed: 3" }));
307 QCOMPARE (f.Proxy_.headerData (0, Qt::Horizontal, Qt::DisplayRole), f.Upstream_.headerData (0, Qt::Horizontal, Qt::DisplayRole));
308 QCOMPARE (f.Proxy_.flags ({}), Qt::ItemIsDropEnabled);
309 }
310
311 void FlatToFoldersProxyModelTest::testFoldersFollowKeys ()
312 {
313 QFETCH_GLOBAL (bool, viaFilter);
314 Fixture f { viaFilter };
315 f.Source_.Append (1, "a", { "t1" });
316 f.Source_.Append (2, "b", { "t1" });
317 f.Source_.Append (3, "c", { "x", "y" });
318 f.Source_.Append (4, "d", {});
319 f.Source_.Append (5, "e", { "t1", "t1" });
320 f.Attach ();
321 CheckTree (f);
322 QCOMPARE (Dump (f.Proxy_), (QStringList { "t1: 1 2 5", "x: 3", "y: 3", "unkeyed: 4" }));
323 Spies spies { f.Proxy_ };
324
325 f.Source_.SetKeys (3, { "t1" });
326 QCOMPARE (spies.Inserted_.size (), 1);
327 QCOMPARE (spies.Removed_.size (), 2);
328 CheckTree (f);
329 QCOMPARE (Dump (f.Proxy_), (QStringList { "t1: 1 2 5 4", "x: 3", "y: 3" }));
330 }
331
332 void FlatToFoldersProxyModelTest::testInsertCreatesFoldersOnDemand ()
333 {
334 QFETCH_GLOBAL (bool, viaFilter);
335 Fixture f { viaFilter };
336 f.FillDefault ();
337 f.Attach ();
338 Spies spies { f.Proxy_ };
339
340 f.Source_.Append (4, "d", { "t3" });
341 QCOMPARE (spies.Inserted_.size (), 2);
342 QCOMPARE (Parent (spies.Inserted_.at (0)), QModelIndex {});
343 QVERIFY (Parent (spies.Inserted_.at (1)).isValid ());
344
345 f.Source_.Append (5, "e", { "t1" });
346 QCOMPARE (spies.Inserted_.size (), 3);
347 QCOMPARE (spies.Structural (), 3);
348 CheckTree (f);
349 }
350
351 void FlatToFoldersProxyModelTest::testRekeyKeepsTheSurvivingLeaf ()
352 {
353 QFETCH_GLOBAL (bool, viaFilter);
354 Fixture f { viaFilter };
355 f.FillDefault ();
356 f.Attach ();
357 Spies spies { f.Proxy_ };
358
359 f.Source_.SetKeys (1, { "t2", "t3" });
360
361 QCOMPARE (spies.Data_.size (), 1);
362 const auto& args = spies.Data_.first ();
363 const auto& topLeft = args.at (0).value<QModelIndex> ();
364 QCOMPARE (topLeft.data (IdRole).toInt (), 2);
365 QCOMPARE (FolderName (topLeft.parent ()), QString { "t2" });
366 QCOMPARE (args.at (1).value<QModelIndex> ().column (), f.Upstream_.columnCount () - 1);
367 QCOMPARE (args.at (2).value<QList<int>> (), (QList<int> { KeysRole }));
368
369 QCOMPARE (spies.Removed_.size (), 1);
370 QCOMPARE (spies.Inserted_.size (), 2);
371 CheckTree (f);
372 }
373
374 void FlatToFoldersProxyModelTest::testRemovingTheLastLeafRemovesTheFolder ()
375 {
376 QFETCH_GLOBAL (bool, viaFilter);
377 Fixture f { viaFilter };
378 f.FillDefault ();
379 f.Attach ();
380 Spies spies { f.Proxy_ };
381
382 f.Source_.removeRow (2);
383
384 QCOMPARE (spies.Removed_.size (), 2);
385 QVERIFY (Parent (spies.Removed_.at (0)).isValid ());
386 QCOMPARE (Parent (spies.Removed_.at (1)), QModelIndex {});
387 QCOMPARE (f.Proxy_.rowCount (), 2);
388 CheckTree (f);
389 }
390
391 void FlatToFoldersProxyModelTest::testRemovalFollowsOwnBookkeeping ()
392 {
393 QFETCH_GLOBAL (bool, viaFilter);
394 Fixture f { viaFilter };
395 f.FillDefault ();
396 f.Attach ();
397
398 f.Source_.blockSignals (true);
399 f.Source_.SetKeys (2, { "zz" });
400 f.Source_.blockSignals (false);
401 f.Source_.removeRow (2);
402
403 QCOMPARE (f.Proxy_.rowCount (), 2);
404 CheckTree (f);
405 }
406
407 void FlatToFoldersProxyModelTest::testSourceSortLeavesTheTreeAlone ()
408 {
409 QFETCH_GLOBAL (bool, viaFilter);
410 Fixture f { viaFilter };
411 f.FillDefault ();
412 f.Attach ();
413 const auto& before = Dump (f.Proxy_);
414 Spies spies { f.Proxy_ };
415
416 f.Source_.sort (0, Qt::DescendingOrder);
417
418 QCOMPARE (spies.Layout_.size (), 0);
419 QCOMPARE (spies.Structural (), 0);
420 QCOMPARE (Dump (f.Proxy_), before);
421 CheckTree (f);
422 }
423
424 void FlatToFoldersProxyModelTest::testSourceResetIsOneReset ()
425 {
426 QFETCH_GLOBAL (bool, viaFilter);
427 Fixture f { viaFilter };
428 f.FillDefault ();
429 f.Attach ();
430 Spies spies { f.Proxy_ };
431
432 f.Source_.clear ();
433 QCOMPARE (spies.Reset_.size (), 1);
434 QCOMPARE (spies.Structural (), 1);
435 QCOMPARE (f.Proxy_.rowCount (), 0);
436
437 f.Source_.Append (4, "d", { "t1", "t2" });
438 QCOMPARE (spies.Inserted_.size (), 4);
439 CheckTree (f);
440 }
441
442 void FlatToFoldersProxyModelTest::testAttachAndDetach ()
443 {
444 QFETCH_GLOBAL (bool, viaFilter);
445 Fixture f { viaFilter };
446 f.FillDefault ();
447 Spies spies { f.Proxy_ };
448
449 f.Attach ();
450 QCOMPARE (spies.Reset_.size (), 1);
451 QCOMPARE (spies.Structural (), 1);
452 QCOMPARE (f.Proxy_.rowCount (), 3);
453 CheckTree (f);
454
455 f.Proxy_.SetSourceModel (nullptr);
456 QCOMPARE (spies.Reset_.size (), 2);
457 QCOMPARE (spies.Structural (), 2);
458 QCOMPARE (f.Proxy_.rowCount (), 0);
459 QCOMPARE (f.Proxy_.columnCount (), 0);
460 QCOMPARE (f.Proxy_.GetSourceModel (), nullptr);
461
462 f.Source_.Append (4, "d", { "t9" });
463 QCOMPARE (spies.Structural (), 2);
464 }
465
466 void FlatToFoldersProxyModelTest::testDragAndDropMapThroughFolders ()
467 {
468 QFETCH_GLOBAL (bool, viaFilter);
469 Fixture f { viaFilter };
470 f.FillDefault ();
471 f.Attach ();
472 const auto& t1 = f.Proxy_.index (0, 0);
473
474 std::unique_ptr<QMimeData> folderDrag { f.Proxy_.mimeData ({ t1 }) };
475 QVERIFY (folderDrag);
476 QCOMPARE (f.Source_.LastDraggedIds_, (QList<int> { 1, 2 }));
477
478 std::unique_ptr<QMimeData> leafDrag { f.Proxy_.mimeData ({ f.Proxy_.index (1, 0, t1) }) };
479 QVERIFY (leafDrag);
480 QCOMPARE (f.Source_.LastDraggedIds_, (QList<int> { 2 }));
481
482 QMimeData payload;
483 payload.setText ("payload");
484 QVERIFY (f.Proxy_.dropMimeData (&payload, Qt::CopyAction, -1, -1, t1));
485 QVERIFY (f.Source_.LastDropKey_.has_value ());
486 QCOMPARE (*f.Source_.LastDropKey_, QByteArray { "t1" });
487
488 QVERIFY (f.Proxy_.dropMimeData (&payload, Qt::CopyAction, -1, -1, f.Proxy_.index (2, 0)));
489 QVERIFY (f.Source_.LastDropKey_.has_value ());
490 QVERIFY (f.Source_.LastDropKey_->isEmpty ());
491
492 QVERIFY (f.Proxy_.dropMimeData (&payload, Qt::CopyAction, -1, -1, {}));
493 QVERIFY (!f.Source_.LastDropKey_.has_value ());
494 }
495
496 void FlatToFoldersProxyModelTest::testReleasesUpstreamIndexes ()
497 {
498 QFETCH_GLOBAL (bool, viaFilter);
499 Fixture f { viaFilter };
500 f.FillDefault ();
501 const auto baseline = f.UpstreamPersistentCount ();
502
503 Source other;
504 other.Append (7, "x", { "t1" });
505 {
506 FlatToFoldersProxyModel proxy { f.MakeConfig () };
507 QAbstractItemModelTester tester { &proxy, QAbstractItemModelTester::FailureReportingMode::QtTest };
508
509 proxy.SetSourceModel (&f.Upstream_);
510 QCOMPARE (f.UpstreamPersistentCount (), baseline + 3);
511
512 proxy.SetSourceModel (&other);
513 QCOMPARE (f.UpstreamPersistentCount (), baseline);
514
515 proxy.SetSourceModel (&f.Upstream_);
516 QCOMPARE (f.UpstreamPersistentCount (), baseline + 3);
517 }
518 QCOMPARE (f.UpstreamPersistentCount (), baseline);
519 }
520
521 void FlatToFoldersProxyModelTest::testRenameKeepsTheFolder ()
522 {
523 QFETCH_GLOBAL (bool, viaFilter);
524 Fixture f { viaFilter };
525 f.FillDefault ();
526 f.Attach ();
527 Spies spies { f.Proxy_ };
528
529 f.ReplaceKey ("t1", { "renamed" });
530
531 QCOMPARE (spies.Structural (), 0);
532 QCOMPARE (spies.Data_.size (), 1);
533 const auto& args = spies.Data_.first ();
534 QCOMPARE (args.at (0).value<QModelIndex> (), f.Proxy_.index (0, 0));
535 QCOMPARE (args.at (1).value<QModelIndex> (), f.Proxy_.index (0, 0));
536 QCOMPARE (args.at (2).value<QList<int>> (), (QList<int> { Qt::DisplayRole }));
537 QCOMPARE (Dump (f.Proxy_), (QStringList { "renamed: 1 2", "t2: 2", "unkeyed: 3" }));
538 CheckTree (f);
539 }
540
541 void FlatToFoldersProxyModelTest::testRenameOntoAnExistingKeyMerges ()
542 {
543 QFETCH_GLOBAL (bool, viaFilter);
544 Fixture f { viaFilter };
545 f.FillDefault ();
546 f.Attach ();
547 Spies spies { f.Proxy_ };
548
549 f.ReplaceKey ("t1", { "t2" });
550
551 QCOMPARE (spies.Inserted_.size (), 1);
552 QCOMPARE (spies.Removed_.size (), 3);
553 QCOMPARE (Dump (f.Proxy_), (QStringList { "t2: 2 1", "unkeyed: 3" }));
554 CheckTree (f);
555 }
556
557 void FlatToFoldersProxyModelTest::testSplitSpreadsTheRows ()
558 {
559 QFETCH_GLOBAL (bool, viaFilter);
560 Fixture f { viaFilter };
561 f.FillDefault ();
562 f.Attach ();
563
564 f.ReplaceKey ("t1", { "x", "y" });
565
566 QCOMPARE (Dump (f.Proxy_), (QStringList { "t2: 2", "unkeyed: 3", "x: 1 2", "y: 1 2" }));
567 CheckTree (f);
568 }
569
570 void FlatToFoldersProxyModelTest::testRemovedKeyMovesRowsToUnkeyed ()
571 {
572 QFETCH_GLOBAL (bool, viaFilter);
573 Fixture f { viaFilter };
574 f.FillDefault ();
575 f.Attach ();
576
577 f.ReplaceKey ("t1", {});
578
579 QCOMPARE (Dump (f.Proxy_), (QStringList { "t2: 2", "unkeyed: 3 1" }));
580 CheckTree (f);
581 }
582
583 void FlatToFoldersProxyModelTest::testKeepingTheOldKeyAddsFolders ()
584 {
585 QFETCH_GLOBAL (bool, viaFilter);
586 Fixture f { viaFilter };
587 f.FillDefault ();
588 f.Attach ();
589
590 f.ReplaceKey ("t2", { "t2", "t3" });
591
592 QCOMPARE (Dump (f.Proxy_), (QStringList { "t1: 1 2", "t2: 2", "unkeyed: 3", "t3: 2" }));
593 CheckTree (f);
594 }
595
596 void FlatToFoldersProxyModelTest::testUnknownKeyChangesNothing ()
597 {
598 QFETCH_GLOBAL (bool, viaFilter);
599 Fixture f { viaFilter };
600 f.FillDefault ();
601 f.Attach ();
602 Spies spies { f.Proxy_ };
603
604 f.ReplaceKey ("nope", { "t1" });
605
606 QCOMPARE (spies.Structural (), 0);
607 QCOMPARE (spies.Data_.size (), 0);
608 QCOMPARE (Dump (f.Proxy_), (QStringList { "t1: 1 2", "t2: 2", "unkeyed: 3" }));
609 }
610
611 void FlatToFoldersProxyModelTest::testRandomWalk ()
612 {
613 QFETCH_GLOBAL (bool, viaFilter);
614 Fixture f { viaFilter };
615 f.Attach ();
616
617 constexpr quint32 seed = 20261017;
618 QRandomGenerator gen { seed };
619 const QStringList pool { "t1", "t2", "t3", "t4" };
620 QStringList known { "t1", "t2", "t3", "t4" };
621 const auto randomKeys = [&]
622 {
623 QStringList keys;
624 for (const auto& key : pool)
625 if (!gen.bounded (3))
626 keys << key;
627 if (!keys.isEmpty () && !gen.bounded (4))
628 keys << keys.first ();
629 return keys;
630 };
631 const auto randomText = [&] { return QString { QChar { 'a' + static_cast<int> (gen.bounded (6)) } }; };
632
633 int nextId = 1;
634 for (int step = 0; step < 400; ++step)
635 {
636 const auto rows = f.Source_.rowCount ();
637 switch (rows ? gen.bounded (7) : 0)
638 {
639 case 0:
640 f.Source_.Append (nextId++, randomText (), randomKeys ());
641 break;
642 case 1:
643 f.Source_.removeRow (gen.bounded (rows));
644 break;
645 case 2:
646 f.Source_.SetKeys (gen.bounded (rows), randomKeys ());
647 break;
648 case 3:
649 f.Source_.item (gen.bounded (rows))->setText (randomText ());
650 break;
651 case 4:
652 if (gen.bounded (2))
653 f.Source_.sort (0, gen.bounded (2) ? Qt::AscendingOrder : Qt::DescendingOrder);
654 else
655 f.Filter_.setFilterFixedString (gen.bounded (2) ? QString {} : randomText ());
656 break;
657 case 5:
658 if (!gen.bounded (20))
659 f.Source_.clear ();
660 break;
661 case 6:
662 {
663 const auto old = known [gen.bounded (static_cast<int> (known.size ()))];
664 QStringList newKeys;
665 for (const auto& key : known)
666 if (!gen.bounded (3))
667 newKeys << key;
668 if (gen.bounded (2))
669 newKeys << known.emplaceBack ("r" + QString::number (step));
670 f.ReplaceKey (old, newKeys);
671 break;
672 }
673 }
674
675 CheckTree (f);
676 if (QTest::currentTestFailed ())
677 {
678 qWarning () << "random walk failed at step" << step << "with seed" << seed << "upstream:" << Describe (Expected (f));
679 return;
680 }
681 }
682 }
683}
void Append(Res &result, T &&val) noexcept
Definition prelude.h:65
constexpr detail::ExprTree< detail::ExprType::LeafStaticPlaceholder, detail::MemberPtrs< Ptr > > f
Definition oral.h:1079
Container< T > Filter(const Container< T > &c, F f)
Definition prelude.h:120