1/*
2    Copyright (C) 2008 Holger Hans Peter Freyther
3
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17    Boston, MA 02110-1301, USA.
18*/
19
20#include <QtTest/QtTest>
21#include <QAction>
22
23#include "qwebpage.h"
24#include "qwebview.h"
25#include "qwebframe.h"
26#include "qwebhistory.h"
27#include "qdebug.h"
28
29class tst_QWebHistory : public QObject
30{
31    Q_OBJECT
32
33public:
34    tst_QWebHistory();
35    virtual ~tst_QWebHistory();
36
37protected :
38    void loadPage(int nr)
39    {
40        frame->load(QUrl("qrc:/resources/page" + QString::number(nr) + ".html"));
41        waitForLoadFinished.exec();
42    }
43
44public slots:
45    void init();
46    void cleanup();
47
48private slots:
49    void title();
50    void count();
51    void back();
52    void forward();
53    void itemAt();
54    void goToItem();
55    void items();
56    void serialize_1(); //QWebHistory countity
57    void serialize_2(); //QWebHistory index
58    void serialize_3(); //QWebHistoryItem
59    void saveAndRestore_crash_1();
60    void saveAndRestore_crash_2();
61    void saveAndRestore_crash_3();
62    void popPushState_data();
63    void popPushState();
64    void clear();
65
66
67private:
68    QWebPage* page;
69    QWebFrame* frame;
70    QWebHistory* hist;
71    QEventLoop waitForLoadFinished;  //operation on history are asynchronous!
72    int histsize;
73};
74
75tst_QWebHistory::tst_QWebHistory()
76{
77}
78
79tst_QWebHistory::~tst_QWebHistory()
80{
81}
82
83void tst_QWebHistory::init()
84{
85    page = new QWebPage(this);
86    frame = page->mainFrame();
87    connect(page, SIGNAL(loadFinished(bool)), &waitForLoadFinished, SLOT(quit()), Qt::QueuedConnection);
88
89    for (int i = 1;i < 6;i++) {
90        loadPage(i);
91    }
92    hist = page->history();
93    histsize = 5;
94}
95
96void tst_QWebHistory::cleanup()
97{
98    delete page;
99}
100
101/**
102  * Check QWebHistoryItem::title() method
103  */
104void tst_QWebHistory::title()
105{
106    QCOMPARE(hist->currentItem().title(), QString("page5"));
107}
108
109/**
110  * Check QWebHistory::count() method
111  */
112void tst_QWebHistory::count()
113{
114    QCOMPARE(hist->count(), histsize);
115}
116
117/**
118  * Check QWebHistory::back() method
119  */
120void tst_QWebHistory::back()
121{
122    for (int i = histsize;i > 1;i--) {
123        QCOMPARE(page->mainFrame()->toPlainText(), QString("page") + QString::number(i));
124        hist->back();
125        waitForLoadFinished.exec();
126    }
127    //try one more time (too many). crash test
128    hist->back();
129    QCOMPARE(page->mainFrame()->toPlainText(), QString("page1"));
130}
131
132/**
133  * Check QWebHistory::forward() method
134  */
135void tst_QWebHistory::forward()
136{
137    //rewind history :-)
138    while (hist->canGoBack()) {
139        hist->back();
140        waitForLoadFinished.exec();
141    }
142
143    for (int i = 1;i < histsize;i++) {
144        QCOMPARE(page->mainFrame()->toPlainText(), QString("page") + QString::number(i));
145        hist->forward();
146        waitForLoadFinished.exec();
147    }
148    //try one more time (too many). crash test
149    hist->forward();
150    QCOMPARE(page->mainFrame()->toPlainText(), QString("page") + QString::number(histsize));
151}
152
153/**
154  * Check QWebHistory::itemAt() method
155  */
156void tst_QWebHistory::itemAt()
157{
158    for (int i = 1;i < histsize;i++) {
159        QCOMPARE(hist->itemAt(i - 1).title(), QString("page") + QString::number(i));
160        QVERIFY(hist->itemAt(i - 1).isValid());
161    }
162    //check out of range values
163    QVERIFY(!hist->itemAt(-1).isValid());
164    QVERIFY(!hist->itemAt(histsize).isValid());
165}
166
167/**
168  * Check QWebHistory::goToItem() method
169  */
170void tst_QWebHistory::goToItem()
171{
172    QWebHistoryItem current = hist->currentItem();
173    hist->back();
174    waitForLoadFinished.exec();
175    hist->back();
176    waitForLoadFinished.exec();
177    QVERIFY(hist->currentItem().title() != current.title());
178    hist->goToItem(current);
179    waitForLoadFinished.exec();
180    QCOMPARE(hist->currentItem().title(), current.title());
181}
182
183/**
184  * Check QWebHistory::items() method
185  */
186void tst_QWebHistory::items()
187{
188    QList<QWebHistoryItem> items = hist->items();
189    //check count
190    QCOMPARE(histsize, items.count());
191
192    //check order
193    for (int i = 1;i <= histsize;i++) {
194        QCOMPARE(items.at(i - 1).title(), QString("page") + QString::number(i));
195    }
196}
197
198/**
199  * Check history state after serialization (pickle, persistent..) method
200  * Checks history size, history order
201  */
202void tst_QWebHistory::serialize_1()
203{
204    QByteArray tmp;  //buffer
205    QDataStream save(&tmp, QIODevice::WriteOnly); //here data will be saved
206    QDataStream load(&tmp, QIODevice::ReadOnly); //from here data will be loaded
207
208    save << *hist;
209    QVERIFY(save.status() == QDataStream::Ok);
210    QCOMPARE(hist->count(), histsize);
211
212    //check size of history
213    //load next page to find differences
214    loadPage(6);
215    QCOMPARE(hist->count(), histsize + 1);
216    load >> *hist;
217    QVERIFY(load.status() == QDataStream::Ok);
218    QCOMPARE(hist->count(), histsize);
219
220    //check order of historyItems
221    QList<QWebHistoryItem> items = hist->items();
222    for (int i = 1;i <= histsize;i++) {
223        QCOMPARE(items.at(i - 1).title(), QString("page") + QString::number(i));
224    }
225}
226
227/**
228  * Check history state after serialization (pickle, persistent..) method
229  * Checks history currentIndex value
230  */
231void tst_QWebHistory::serialize_2()
232{
233    QByteArray tmp;  //buffer
234    QDataStream save(&tmp, QIODevice::WriteOnly); //here data will be saved
235    QDataStream load(&tmp, QIODevice::ReadOnly); //from here data will be loaded
236
237    int oldCurrentIndex = hist->currentItemIndex();
238
239    hist->back();
240    waitForLoadFinished.exec();
241    hist->back();
242    waitForLoadFinished.exec();
243    //check if current index was changed (make sure that it is not last item)
244    QVERIFY(hist->currentItemIndex() != oldCurrentIndex);
245    //save current index
246    oldCurrentIndex = hist->currentItemIndex();
247
248    save << *hist;
249    QVERIFY(save.status() == QDataStream::Ok);
250    load >> *hist;
251    QVERIFY(load.status() == QDataStream::Ok);
252
253    //check current index
254    QCOMPARE(hist->currentItemIndex(), oldCurrentIndex);
255}
256
257/**
258  * Check history state after serialization (pickle, persistent..) method
259  * Checks QWebHistoryItem public property after serialization
260  */
261void tst_QWebHistory::serialize_3()
262{
263    QByteArray tmp;  //buffer
264    QDataStream save(&tmp, QIODevice::WriteOnly); //here data will be saved
265    QDataStream load(&tmp, QIODevice::ReadOnly); //from here data will be loaded
266
267    //prepare two different history items
268    QWebHistoryItem a = hist->currentItem();
269    a.setUserData("A - user data");
270
271    //check properties BEFORE serialization
272    QString title(a.title());
273    QDateTime lastVisited(a.lastVisited());
274    QUrl originalUrl(a.originalUrl());
275    QUrl url(a.url());
276    QVariant userData(a.userData());
277
278    save << *hist;
279    QVERIFY(save.status() == QDataStream::Ok);
280    QVERIFY(!load.atEnd());
281    hist->clear();
282    QVERIFY(hist->count() == 1);
283    load >> *hist;
284    QVERIFY(load.status() == QDataStream::Ok);
285    QWebHistoryItem b = hist->currentItem();
286
287    //check properties AFTER serialization
288    QCOMPARE(b.title(), title);
289    QCOMPARE(b.lastVisited(), lastVisited);
290    QCOMPARE(b.originalUrl(), originalUrl);
291    QCOMPARE(b.url(), url);
292    QCOMPARE(b.userData(), userData);
293
294    //Check if all data was read
295    QVERIFY(load.atEnd());
296}
297
298static void saveHistory(QWebHistory* history, QByteArray* in)
299{
300    in->clear();
301    QDataStream save(in, QIODevice::WriteOnly);
302    save << *history;
303}
304
305static void restoreHistory(QWebHistory* history, QByteArray* out)
306{
307    QDataStream load(out, QIODevice::ReadOnly);
308    load >> *history;
309}
310
311/** The test shouldn't crash */
312void tst_QWebHistory::saveAndRestore_crash_1()
313{
314    QByteArray buffer;
315    saveHistory(hist, &buffer);
316    for (unsigned i = 0; i < 5; i++) {
317        restoreHistory(hist, &buffer);
318        saveHistory(hist, &buffer);
319    }
320}
321
322/** The test shouldn't crash */
323void tst_QWebHistory::saveAndRestore_crash_2()
324{
325    QByteArray buffer;
326    saveHistory(hist, &buffer);
327    QWebPage* page2 = new QWebPage(this);
328    QWebHistory* hist2 = page2->history();
329    for (unsigned i = 0; i < 5; i++) {
330        restoreHistory(hist2, &buffer);
331        saveHistory(hist2, &buffer);
332    }
333    delete page2;
334}
335
336/** The test shouldn't crash */
337void tst_QWebHistory::saveAndRestore_crash_3()
338{
339    QByteArray buffer;
340    saveHistory(hist, &buffer);
341    QWebPage* page2 = new QWebPage(this);
342    QWebHistory* hist1 = hist;
343    QWebHistory* hist2 = page2->history();
344    for (unsigned i = 0; i < 5; i++) {
345        restoreHistory(hist1, &buffer);
346        restoreHistory(hist2, &buffer);
347        QVERIFY(hist1->count() == hist2->count());
348        QVERIFY(hist1->count() == histsize);
349        hist2->back();
350        saveHistory(hist2, &buffer);
351        hist2->clear();
352    }
353    delete page2;
354}
355
356void tst_QWebHistory::popPushState_data()
357{
358    QTest::addColumn<QString>("script");
359    QTest::newRow("pushState") << "history.pushState(123, \"foo\");";
360    QTest::newRow("replaceState") << "history.replaceState(\"a\", \"b\");";
361    QTest::newRow("back") << "history.back();";
362    QTest::newRow("forward") << "history.forward();";
363    QTest::newRow("clearState") << "history.clearState();";
364}
365
366/** Crash test, WebKit bug 38840 (https://bugs.webkit.org/show_bug.cgi?id=38840) */
367void tst_QWebHistory::popPushState()
368{
369    QFETCH(QString, script);
370    QWebPage page;
371    page.mainFrame()->setHtml("<html><body>long live Qt!</body></html>");
372    page.mainFrame()->evaluateJavaScript(script);
373}
374
375/** ::clear */
376void tst_QWebHistory::clear()
377{
378    QByteArray buffer;
379
380    QAction* actionBack = page->action(QWebPage::Back);
381    QVERIFY(actionBack->isEnabled());
382    saveHistory(hist, &buffer);
383    QVERIFY(hist->count() > 1);
384    hist->clear();
385    QVERIFY(hist->count() == 1);  // Leave current item.
386    QVERIFY(!actionBack->isEnabled());
387
388    QWebPage* page2 = new QWebPage(this);
389    QWebHistory* hist2 = page2->history();
390    QVERIFY(hist2->count() == 0);
391    hist2->clear();
392    QVERIFY(hist2->count() == 0); // Do not change anything.
393    delete page2;
394}
395
396QTEST_MAIN(tst_QWebHistory)
397#include "tst_qwebhistory.moc"
398