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