1 /***************************************************************************
2 Copyright (C) 2021 Robby Stephenson <robby@periapsis.org>
3 ***************************************************************************/
4
5 /***************************************************************************
6 * *
7 * This program is free software; you can redistribute it and/or *
8 * modify it under the terms of the GNU General Public License as *
9 * published by the Free Software Foundation; either version 2 of *
10 * the License or (at your option) version 3 or any later version *
11 * accepted by the membership of KDE e.V. (or its successor approved *
12 * by the membership of KDE e.V.), which shall act as a proxy *
13 * defined in Section 14 of version 3 of the license. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
22 * *
23 ***************************************************************************/
24
25
26 #include "fieldwidgettest.h"
27 #include "../gui/boolfieldwidget.h"
28 #include "../gui/choicefieldwidget.h"
29 #include "../gui/datefieldwidget.h"
30 #include "../gui/datewidget.h"
31 #include "../gui/linefieldwidget.h"
32 #include "../gui/lineedit.h"
33 #include "../gui/numberfieldwidget.h"
34 #include "../gui/spinbox.h"
35 #include "../gui/parafieldwidget.h"
36 #include "../gui/ratingwidget.h"
37 #include "../gui/ratingfieldwidget.h"
38 #include "../gui/tablefieldwidget.h"
39 #include "../gui/urlfieldwidget.h"
40 #include "../document.h"
41 #include "../images/imagefactory.h"
42 #include "../collections/bookcollection.h"
43 #include "../collectionfactory.h"
44
45 #include <KTextEdit>
46 #include <KUrlRequester>
47
48 #include <QTest>
49 #include <QCheckBox>
50 #include <QComboBox>
51 #include <QTableWidget>
52 #include <QSignalSpy>
53
54 // needs a GUI
QTEST_MAIN(FieldWidgetTest)55 QTEST_MAIN( FieldWidgetTest )
56
57 void FieldWidgetTest::initTestCase() {
58 Tellico::RegisterCollection<Tellico::Data::BookCollection> registerBook(Tellico::Data::Collection::Book, "book");
59 Tellico::ImageFactory::init();
60 }
61
testBool()62 void FieldWidgetTest::testBool() {
63 Tellico::Data::FieldPtr field(new Tellico::Data::Field(QStringLiteral("bool"),
64 QStringLiteral("bool"),
65 Tellico::Data::Field::Bool));
66 field->setDefaultValue(QStringLiteral("true"));
67 Tellico::GUI::BoolFieldWidget w(field, nullptr);
68 QSignalSpy spy(&w, &Tellico::GUI::FieldWidget::valueChanged);
69 QVERIFY(!w.expands());
70 QVERIFY(w.text().isEmpty());
71 QCOMPARE(spy.count(), 0);
72
73 w.setText(QStringLiteral("true"));
74 QCOMPARE(w.text(), QStringLiteral("true"));
75 QCOMPARE(spy.count(), 0); // since the value was set explicitly, no valueChanged signal is made
76 auto cb = dynamic_cast<QCheckBox*>(w.widget());
77 QVERIFY(cb);
78 QVERIFY(cb->isChecked());
79
80 // any non-empty text is interpreted as true (for better or worse)
81 w.setText(QStringLiteral("false"));
82 QCOMPARE(w.text(), QStringLiteral("true"));
83 QVERIFY(cb->isChecked());
84
85 w.clear();
86 QVERIFY(w.text().isEmpty());
87 QVERIFY(!cb->isChecked());
88 QCOMPARE(spy.count(), 0);
89
90 cb->setChecked(true);
91 QCOMPARE(w.text(), QStringLiteral("true"));
92 QCOMPARE(spy.count(), 1);
93
94 w.clear();
95 QVERIFY(w.text().isEmpty());
96 QVERIFY(!cb->isChecked());
97
98 w.insertDefault();
99 QCOMPARE(w.text(), QStringLiteral("true"));
100 QVERIFY(cb->isChecked());
101 }
102
testChoice()103 void FieldWidgetTest::testChoice() {
104 // create a Choice field
105 Tellico::Data::FieldPtr field(new Tellico::Data::Field(QStringLiteral("f"),
106 QStringLiteral("f"),
107 QStringList()));
108 field->setAllowed(QStringList() << QStringLiteral("choice1"));
109 Tellico::GUI::ChoiceFieldWidget w(field, nullptr);
110 QSignalSpy spy(&w, &Tellico::GUI::FieldWidget::valueChanged);
111 QVERIFY(!w.expands());
112 QVERIFY(w.text().isEmpty());
113 auto cb = dynamic_cast<QComboBox*>(w.widget());
114 QVERIFY(cb);
115 QCOMPARE(cb->count(), 2); // one empty value
116
117 field->setAllowed(QStringList() << QStringLiteral("choice1") << QStringLiteral("choice2"));
118 w.updateField(field, field);
119 QVERIFY(w.text().isEmpty());
120 QCOMPARE(spy.count(), 0);
121 QCOMPARE(cb->count(), 3);
122
123 w.setText(QStringLiteral("choice2"));
124 QCOMPARE(w.text(), QStringLiteral("choice2"));
125
126 field->setAllowed(QStringList() << QStringLiteral("choice1") << QStringLiteral("choice2") << QStringLiteral("choice3"));
127 w.updateField(field, field);
128 // selected value should remain same
129 QCOMPARE(w.text(), QStringLiteral("choice2"));
130 QCOMPARE(spy.count(), 0);
131
132 cb->setCurrentIndex(1);
133 QCOMPARE(w.text(), QStringLiteral("choice1"));
134 QCOMPARE(spy.count(), 1);
135
136 w.clear();
137 QVERIFY(w.text().isEmpty());
138
139 // set value to something not in the list
140 w.setText(QStringLiteral("choice4"));
141 QCOMPARE(w.text(), QStringLiteral("choice4"));
142 QCOMPARE(cb->count(), 5);
143
144 w.insertDefault();
145 QVERIFY(w.text().isEmpty());
146 }
147
testDate()148 void FieldWidgetTest::testDate() {
149 Tellico::Data::FieldPtr field(new Tellico::Data::Field(QStringLiteral("d"),
150 QStringLiteral("d"),
151 Tellico::Data::Field::Date));
152 Tellico::GUI::DateFieldWidget w(field, nullptr);
153 QSignalSpy spy(&w, &Tellico::GUI::FieldWidget::valueChanged);
154 QVERIFY(w.expands());
155 auto dw = dynamic_cast<Tellico::GUI::DateWidget*>(w.widget());
156 QVERIFY(dw);
157 QVERIFY(w.text().isEmpty());
158 QVERIFY(dw->date().isNull());
159 QCOMPARE(spy.count(), 0);
160
161 QDate moon(1969, 7, 20);
162 w.setText(QStringLiteral("1969-07-20"));
163 QCOMPARE(w.text(), QStringLiteral("1969-07-20"));
164 QCOMPARE(dw->date(), moon);
165 // test without leading zero
166 w.setText(QStringLiteral("1969-7-20"));
167 QCOMPARE(w.text(), QStringLiteral("1969-07-20"));
168 QCOMPARE(dw->date(), moon);
169 QCOMPARE(spy.count(), 0);
170
171 w.setText(QString());
172 QVERIFY(w.text().isEmpty());
173 QVERIFY(dw->date().isNull());
174 QCOMPARE(spy.count(), 0);
175
176 w.setText(QStringLiteral("1969"));
177 // adds dashes
178 QCOMPARE(w.text(), QStringLiteral("1969--"));
179 QVERIFY(dw->date().isNull());
180 QCOMPARE(spy.count(), 0);
181
182 QDate sputnik(1957, 10, 4);
183 dw->setDate(sputnik);
184 QCOMPARE(w.text(), QStringLiteral("1957-10-04"));
185 QCOMPARE(dw->date(), sputnik);
186 QCOMPARE(spy.count(), 1);
187
188 w.clear();
189 QVERIFY(w.text().isEmpty());
190 QVERIFY(dw->date().isNull());
191 }
192
testLine()193 void FieldWidgetTest::testLine() {
194 Tellico::Data::FieldPtr field(new Tellico::Data::Field(QStringLiteral("f"),
195 QStringLiteral("f")));
196 field->setFlags(Tellico::Data::Field::AllowMultiple | Tellico::Data::Field::AllowCompletion);
197 Tellico::GUI::LineFieldWidget w(field, nullptr);
198 QSignalSpy spy(&w, &Tellico::GUI::FieldWidget::valueChanged);
199 QVERIFY(w.expands());
200 QVERIFY(w.text().isEmpty());
201
202 w.setText(QStringLiteral("true"));
203 QCOMPARE(w.text(), QStringLiteral("true"));
204 auto le = dynamic_cast<Tellico::GUI::LineEdit*>(w.widget());
205 QVERIFY(le);
206 QVERIFY(!le->validator());
207 QCOMPARE(spy.count(), 0);
208
209 w.addCompletionObjectItem(QStringLiteral("new text"));
210 le->setText(QStringLiteral("new"));
211 QCOMPARE(spy.count(), 1);
212 QCOMPARE(le->completionObject()->makeCompletion(le->text()), QStringLiteral("new text"));
213
214 le->setText(QStringLiteral("new text"));
215 QCOMPARE(w.text(), QStringLiteral("new text"));
216 QCOMPARE(spy.count(), 2);
217
218 le->setText(QStringLiteral("text1;text2"));
219 QCOMPARE(w.text(), QStringLiteral("text1; text2"));
220 QCOMPARE(spy.count(), 3);
221
222 field->setFlags(Tellico::Data::Field::AllowMultiple);
223 w.updateField(field, field);
224 // verify completion object is removed
225 QVERIFY(!le->compObj()); // don't call completionObject() since it recreates it
226
227 w.clear();
228 QVERIFY(w.text().isEmpty());
229 }
230
testPara()231 void FieldWidgetTest::testPara() {
232 Tellico::Data::FieldPtr field(new Tellico::Data::Field(QStringLiteral("f"),
233 QStringLiteral("f"),
234 Tellico::Data::Field::Para));
235 Tellico::GUI::ParaFieldWidget w(field, nullptr);
236 QSignalSpy spy(&w, &Tellico::GUI::FieldWidget::valueChanged);
237 QVERIFY(w.expands());
238 QVERIFY(w.text().isEmpty());
239
240 w.setText(QStringLiteral("true"));
241 QCOMPARE(w.text(), QStringLiteral("true"));
242 auto edit = dynamic_cast<KTextEdit*>(w.widget());
243 QVERIFY(edit);
244 QCOMPARE(spy.count(), 0);
245
246 // test replacing EOL
247 edit->setText(QLatin1String("test1\ntest2"));
248 QCOMPARE(w.text(), QStringLiteral("test1<br/>test2"));
249 QCOMPARE(spy.count(), 1);
250
251 w.setText(QLatin1String("test1<br>test2"));
252 QCOMPARE(edit->toPlainText(), QStringLiteral("test1\ntest2"));
253 QCOMPARE(w.text(), QStringLiteral("test1<br/>test2"));
254
255 w.clear();
256 QVERIFY(w.text().isEmpty());
257 }
258
testNumber()259 void FieldWidgetTest::testNumber() {
260 Tellico::Data::FieldPtr field(new Tellico::Data::Field(QStringLiteral("f"),
261 QStringLiteral("f"),
262 Tellico::Data::Field::Number));
263 Tellico::GUI::NumberFieldWidget w(field, nullptr);
264 QSignalSpy spy(&w, &Tellico::GUI::FieldWidget::valueChanged);
265 QVERIFY(w.expands());
266 QVERIFY(w.text().isEmpty());
267 // spin box since AllowMultiple is not set
268 QVERIFY(w.isSpinBox());
269 auto sb = dynamic_cast<Tellico::GUI::SpinBox*>(w.widget());
270 QVERIFY(sb);
271 w.setText(QStringLiteral("1"));
272 QCOMPARE(w.text(), QStringLiteral("1"));
273 QCOMPARE(sb->value(), 1);
274 w.setText(QStringLiteral("1; 2"));
275 QCOMPARE(w.text(), QStringLiteral("1"));
276 w.clear();
277 QVERIFY(w.text().isEmpty());
278 QCOMPARE(spy.count(), 0);
279
280 sb->setValue(3);
281 QCOMPARE(w.text(), QStringLiteral("3"));
282 QCOMPARE(spy.count(), 1);
283
284 // now set AllowMultiple and check that the spinbox is deleted and a line edit is used
285 field->setFlags(Tellico::Data::Field::AllowMultiple);
286 w.setText(QStringLiteral("1"));
287 w.updateField(field, field);
288 QVERIFY(!w.isSpinBox());
289 auto le = dynamic_cast<QLineEdit*>(w.widget());
290 QVERIFY(le);
291 // value should be unchanged
292 QCOMPARE(w.text(), QStringLiteral("1"));
293 QCOMPARE(spy.count(), 1);
294 w.setText(QStringLiteral("1;2"));
295 QCOMPARE(w.text(), QStringLiteral("1; 2"));
296 QCOMPARE(spy.count(), 1);
297
298 le->setText(QStringLiteral("2"));
299 QCOMPARE(w.text(), QStringLiteral("2"));
300 QCOMPARE(spy.count(), 2);
301
302 w.clear();
303 QVERIFY(w.text().isEmpty());
304 }
305
testRating()306 void FieldWidgetTest::testRating() {
307 Tellico::Data::FieldPtr field(new Tellico::Data::Field(QStringLiteral("f"),
308 QStringLiteral("f"),
309 Tellico::Data::Field::Rating));
310 Tellico::GUI::RatingFieldWidget w(field, nullptr);
311 QSignalSpy spy(&w, &Tellico::GUI::FieldWidget::valueChanged);
312 QVERIFY(!w.expands());
313 QVERIFY(w.text().isEmpty());
314 auto rating = dynamic_cast<Tellico::GUI::RatingWidget*>(w.widget());
315 QVERIFY(rating);
316
317 w.setText(QStringLiteral("1"));
318 QCOMPARE(w.text(), QStringLiteral("1"));
319 w.setText(QStringLiteral("1; 2"));
320 QCOMPARE(w.text(), QStringLiteral("1"));
321 w.clear();
322 QVERIFY(w.text().isEmpty());
323 QCOMPARE(spy.count(), 0);
324
325 field->setProperty(QStringLiteral("minimum"), QStringLiteral("5"));
326 field->setProperty(QStringLiteral("maximum"), QStringLiteral("7"));
327 w.setText(QStringLiteral("4"));
328 w.updateField(field, field);
329 QVERIFY(w.text().isEmpty()); // empty since 4 is less than minimum
330 QCOMPARE(spy.count(), 0);
331 w.setText(QStringLiteral("8"));
332 QVERIFY(w.text().isEmpty());
333 QCOMPARE(spy.count(), 0);
334
335 rating->setText(QStringLiteral("6"));
336 QCOMPARE(w.text(), QStringLiteral("6"));
337 QCOMPARE(spy.count(), 0);
338 }
339
testTable()340 void FieldWidgetTest::testTable() {
341 Tellico::Data::FieldPtr field(new Tellico::Data::Field(QStringLiteral("url"),
342 QStringLiteral("url"),
343 Tellico::Data::Field::Table));
344 field->setProperty(QStringLiteral("columns"), QStringLiteral("2"));
345 Tellico::GUI::TableFieldWidget w(field, nullptr);
346 QSignalSpy spy(&w, &Tellico::GUI::FieldWidget::valueChanged);
347 QSignalSpy fieldSpy(&w, &Tellico::GUI::FieldWidget::fieldChanged);
348 QVERIFY(w.expands());
349 QVERIFY(w.text().isEmpty());
350 QCOMPARE(w.m_columns, 2);
351
352 auto tw = dynamic_cast<QTableWidget*>(w.widget());
353 Q_ASSERT(tw);
354 QCOMPARE(tw->columnCount(), 2);
355 QCOMPARE(tw->rowCount(), 5); // minimum row count is 5
356
357 w.setText(QStringLiteral("true"));
358 QCOMPARE(w.text(), QStringLiteral("true"));
359 QCOMPARE(spy.count(), 0);
360 QCOMPARE(tw->rowCount(), 5);
361
362 w.slotInsertRow();
363 tw->setItem(0, 1, new QTableWidgetItem(QStringLiteral("new text")));
364 QCOMPARE(w.text(), QStringLiteral("true::new text"));
365 QCOMPARE(spy.count(), 1);
366 QVERIFY(!w.emptyRow(0));
367 QCOMPARE(tw->rowCount(), 5);
368
369 w.m_row = 1;
370 w.slotInsertRow();
371 QCOMPARE(tw->rowCount(), 6);
372 w.slotRemoveRow();
373 QCOMPARE(w.text(), QStringLiteral("true::new text"));
374 QCOMPARE(tw->rowCount(), 5);
375 QCOMPARE(spy.count(), 1);
376 QVERIFY(w.emptyRow(1));
377 QVERIFY(!w.emptyRow(0));
378
379 QCOMPARE(w.text(), QStringLiteral("true::new text"));
380 w.m_row = 0;
381 w.slotMoveRowDown();
382 QCOMPARE(spy.count(), 2);
383 QCOMPARE(w.text(), Tellico::FieldFormat::rowDelimiterString() + QStringLiteral("true::new text"));
384 w.m_row = 1;
385 w.slotMoveRowUp();
386 QCOMPARE(spy.count(), 3);
387 QCOMPARE(w.text(), QStringLiteral("true::new text"));
388
389 w.m_col = 0;
390 w.renameColumn(QStringLiteral("col name"));
391 QCOMPARE(tw->horizontalHeaderItem(0)->text(), QStringLiteral("col name"));
392
393 field->setProperty(QStringLiteral("columns"), QStringLiteral("4"));
394 w.updateField(field, field);
395 QCOMPARE(tw->columnCount(), 4);
396 QCOMPARE(w.text(), QStringLiteral("true::new text"));
397 QCOMPARE(spy.count(), 3);
398
399 w.clear();
400 QVERIFY(w.text().isEmpty());
401 }
402
testUrl()403 void FieldWidgetTest::testUrl() {
404 Tellico::Data::FieldPtr field(new Tellico::Data::Field(QStringLiteral("url"),
405 QStringLiteral("url"),
406 Tellico::Data::Field::URL));
407 Tellico::GUI::URLFieldWidget w(field, nullptr);
408 QSignalSpy spy(&w, &Tellico::GUI::FieldWidget::valueChanged);
409 QVERIFY(w.expands());
410
411 auto requester = dynamic_cast<KUrlRequester*>(w.widget());
412 Q_ASSERT(requester);
413
414 QUrl base = QUrl::fromLocalFile(QFINDTESTDATA("data/relative-link.xml"));
415 Tellico::Data::Document::self()->setURL(base); // set the base url
416 QUrl link = QUrl::fromLocalFile(QFINDTESTDATA("fieldwidgettest.cpp"));
417 requester->setUrl(link);
418 QCOMPARE(w.text(), link.url());
419 QCOMPARE(spy.count(), 1);
420
421 field->setProperty(QStringLiteral("relative"), QStringLiteral("true"));
422 w.updateField(field, field);
423 // will be exactly up one level
424 QCOMPARE(w.text(), QStringLiteral("../fieldwidgettest.cpp"));
425
426 // check completion
427 QCOMPARE(requester->lineEdit()->completionObject()->makeCompletion(QStringLiteral("../fieldwidgettest.c")),
428 QStringLiteral("../fieldwidgettest.cpp"));
429
430 // verify value after setting the relative link explicitly
431 w.setText(QStringLiteral("../fieldwidgettest.cpp"));
432 QCOMPARE(w.text(), QStringLiteral("../fieldwidgettest.cpp"));
433 QCOMPARE(spy.count(), 1);
434
435 field->setProperty(QStringLiteral("relative"), QStringLiteral("false"));
436 w.updateField(field, field);
437 // will be exactly up one level
438 QCOMPARE(w.text(), link.url());
439
440 w.clear();
441 QVERIFY(w.text().isEmpty());
442 QVERIFY(requester->url().isEmpty());
443 }
444