./0000755000015600001650000000000012703462032011074 5ustar jenkinsjenkins./doc/0000755000015600001650000000000012703462032011641 5ustar jenkinsjenkins./doc/WebView.qdoc0000644000015600001650000001773612703462031014076 0ustar jenkinsjenkins/* * Copyright 2014-2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \qmltype WebView \inqmlmodule Ubuntu.Web 0.2 \ingroup ubuntu \brief A webview that can be used to render web content in an application. Here is a very simple example of how to use a WebView to render a web page: \qml import QtQuick 2.4 import Ubuntu.Web 0.2 WebView { url: "http://ubuntu.com" } \endqml The \c WebView component defaults to using a \l {SharedWebContext} {shared \c WebContext} that is shared across all \c WebView instances in a given application. \sa SharedWebContext */ /*! \qmlproperty url WebView::url The URL of the current page. */ /*! \qmlproperty string WebView::title The title of the current page. */ /*! \qmlproperty url WebView::icon The URL of the favicon of the current page. */ /*! \qmlproperty bool WebView::canGoBack Whether the navigation history has a previous entry to navigate back. \sa goBack, canGoForward */ /*! \qmlproperty bool WebView::canGoForward Whether the navigation history has a next entry to navigate forward. \sa goForward, canGoBack */ /*! \qmlproperty bool WebView::incognito Whether the WebView is being used in private browsing mode, where no data is persisted across sessions. */ /*! \qmlproperty bool WebView::loading Whether the current page is loading. \sa loadProgress, stop, reload */ /*! \qmlproperty bool WebView::fullscreen Whether the current page requested fullscreen display. */ /*! \qmlproperty int WebView::loadProgress The load progress of the current page (as a integer value between 0 and 100). \sa loading */ /*! \qmlproperty component WebView::alertDialog The QML component that will be instantiated to display a JavaScript alert dialog. \sa confirmDialog, promptDialog, beforeUnloadDialog */ /*! \qmlproperty component WebView::confirmDialog The QML component that will be instantiated to display a JavaScript confirmation dialog. \sa alertDialog, promptDialog, beforeUnloadDialog */ /*! \qmlproperty component WebView::promptDialog The QML component that will be instantiated to display a JavaScript prompt dialog. \sa alertDialog, confirmDialog, beforeUnloadDialog */ /*! \qmlproperty component WebView::beforeUnloadDialog The QML component that will be instantiated to display a JavaScript confirmation when the user initiates a navigation away from the current page, if the page has defined an \c onBeforeUnload handler. \sa alertDialog, confirmDialog, promptDialog */ /*! \qmlproperty component WebView::filePicker The QML component that will be instantiated to let the user select files when the user clicks an \c {} element on the current page. */ /*! \qmlproperty WebContext WebView::context The web context associated to this WebView. By default a \l {SharedWebContext} {shared context} is used which should fit most use cases, do not override unless you really need a finer control over the context. */ /*! \qmlproperty list WebView::navigationHistory The navigation history (back/forward entries) stored as a list model with a \c currentIndex property. Each entry exposes the URL and title of the corresponding page, as well as a timestamp of when it was visited. */ /*! \qmlproperty ActionList WebView::contextualActions A list of actions that the user will be presented with when invoking a context menu (by way of a right click on desktop, or a long press on a touch-enabled device, on an image or a hyperlink). By default the list is empty, and no menu is shown. User-defined actions can access the \l {contextModel} {context model}. Example of user-defined actions: \code import Ubuntu.Components 1.3 import Ubuntu.Web 0.2 WebView { contextualActions: ActionList { Action { text: i18n.tr("Open link in browser") enabled: contextModel && contextModel.linkUrl.toString() onTriggered: Qt.openUrlExternally(contextModel.linkUrl) } } } \endcode \sa contextModel */ /*! \deprecated \qmlproperty QtObject WebView::contextualData This property is deprecated, use the \l {contextModel} property instead. An object that holds the contextual data associated with the current context menu. User-defined \l {contextualActions} {contextual actions} can use this data to process it when triggered. It has the following properties: \list \li href (url): the full URI of the hyperlink, if any \li title (string): the title of the hyperlink, if any \li img (url): the full URI of the image \endlist Note that in the case of an image enclosed inside a hyperlink, both \c href and \c img will be available, allowing a user-defined contextual action to operate on both elements. \sa contextualActions, contextModel */ /*! \qmlproperty QtObject WebView::contextModel An object that holds the contextual data associated with the current context menu, as well as methods to interact with this data. User-defined \l {contextualActions} {contextual actions} can use this data to process it when triggered. It has the following properties: \list \li linkUrl (url): the full URI of the hyperlink, if any \li srcUrl (url): the full URI of the image/media, if any \li mediaType (int): the type of media (one of Oxide.WebView.MediaTypeNone, Oxide.WebView.MediaTypeImage, Oxide.WebView.MediaTypeCanvas, Oxide.WebView.MediaTypeAudio, Oxide.WebView.MediaTypeVideo) \li isEditable (bool): whether the current element is editable \li editFlags (int): for editable elements, an OR-combined list of flags that define the current editing capabilities (Oxide.WebView.UndoCapability, Oxide.WebView.RedoCapability, Oxide.WebView.CutCapability, Oxide.WebView.CopyCapability, Oxide.WebView.PasteCapability, Oxide.WebView.EraseCapability, Oxide.WebView.SelectAllCapability) \endlist It has the following methods: \list \li saveLink(): initiates a download request for the resource pointed to by the hyperlink, if any \li saveMedia(): initiates a download request for the media (image, canvas, audio, video), if any \endlist When there is no active context menu, \c contextModel is null. \sa contextualActions */ /*! \qmlmethod void WebView::goBack() Go back one entry in the navigation history. \sa canGoBack, goForward */ /*! \qmlmethod void WebView::goForward() Go forward one entry in the navigation history. \sa canGoForward, goBack */ /*! \qmlmethod void WebView::stop() Stop loading the current page. Does nothing if there is no page currently loading. \sa reload, loading */ /*! \qmlmethod void WebView::reload() Reload the current page. \sa stop */ /*! \qmlmethod void WebView::loadHtml(string html, url baseUrl) Load HTML content from memory instead of loading it from a URL. The \c baseUrl argument is used to resolve relative URLs in the provided content. */ ./doc/WebContext.qdoc0000644000015600001650000000450112703462031014572 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \qmltype WebContext \inqmlmodule Ubuntu.Web 0.2 \ingroup ubuntu \brief A default context implementation for use with \l WebView. This default WebContext implementation has the default user agent string used by the Ubuntu browser as well as the UA override mechanism that allows sending an overridden user agent for given domains/websites, based on the form factor. It has its data path (for cache and cookie database) set to the standard writable data location for the current application, which is based on the application name (see \c Qt.application.name), for example \e ~/.local/share/myApp/. Note that the \l WebView component already uses this default context, so there is no need to explicitly instantiate a \c WebContext unless you want to override some of its default properties. \sa SharedWebContext */ // Note: only a subset of the properties of Oxide’s WebContext are documented // here. This is intentional, typical applications are not expected to be // needing the full set of properties and methods exposed by Oxide. /*! \qmlproperty string WebContext::userAgent The default user agent string that will be sent with each HTTP request. */ /*! \qmlproperty url WebContext::dataPath The local path where persistent data (such as cookies) will be stored. */ /*! \qmlproperty url WebContext::cachePath The local path where network data will be cached. If not set, it defaults to \l dataPath. */ /*! \qmlproperty string WebContext::acceptLangs The list of accepted languages (the Accept-Language HTTP header), as a comma-separated list of language codes. */ ./doc/ubuntu-web.qdocconf0000644000015600001650000000115212703462031015452 0ustar jenkinsjenkinsproject = UbuntuWeb description = Ubuntu Web module documentation sourcedirs = ./ sources.fileextensions = "*.qdoc" outputdir = html outputformats = HTML version = 0.2 syntaxhightlighting = true sourceencoding = UTF-8 outputencoding = UTF-8 HTML.nonavigationbar = "true" HTML.stylesheets = \ css/base.css \ css/custom.css \ css/qtquick.css HTML.headerstyles = \ "\n" \ "\n" \ "\n" ./doc/ubuntu-web.qdoc0000644000015600001650000000312612703462031014607 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \qmlmodule Ubuntu.Web 0.2 \title Ubuntu Web module \brief WebView and related components This QML module exposes a WebView component that can be used to render web content in an application, as well as related components. This is the preferred way of rendering web content in an Ubuntu application. The rendering is performed by \l {https://launchpad.net/oxide} {Oxide}. The use of QtWebKit is deprecated and unsupported. This is version 0.2 of the module. Note that the module was previously named \c Ubuntu.Components.Extras.Browser, the old namespace has been kept around for backwards compatibility but its use is discouraged, please update your applications to import \c {Ubuntu.Web 0.2}. Also note that importing both \c com.canonical.Oxide (or \c QtWebKit) and \c Ubuntu.Web in the same QML file results in an undefined behaviour, as both expose a \c WebView type. */ ./doc/SharedWebContext.qdoc0000644000015600001650000000234512703462031015725 0ustar jenkinsjenkins/* * Copyright 2013 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \qmltype SharedWebContext \inqmlmodule Ubuntu.Web 0.2 \ingroup ubuntu \brief A singleton that exposes a shared \l WebContext that can be used by several \l WebView instances in the same application. Note that the \l WebView component already uses the shared context. */ /*! \qmlproperty WebContext SharedWebContext::sharedContext The \l WebContext instance that several \l WebView instances can share. */ /*! \qmlproperty string SharedWebContext::customUA An alias to the shared context’s default user agent string. */ ./doc/css/0000755000015600001650000000000012703462032012431 5ustar jenkinsjenkins./doc/css/base.css0000644000015600001650000002706712703462031014070 0ustar jenkinsjenkins/** * Ubuntu Developer base stylesheet * * A base stylesheet containing site-wide styles * * @project Ubuntu Developer * @version 1.0 * @author Canonical Web Team: Steve Edwards * @copyright 2011 Canonical Ltd. */ /** * @section Global */ body { font-family: 'Ubuntu', 'Ubuntu Beta', UbuntuBeta, Ubuntu, 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif; font-size: 13px; line-height: 1.4; color: #333; } a { color: #dd4814; text-decoration: none; outline: 0; } p, dl { margin-bottom: 10px; } strong { font-weight: bold; } em { font-style: italic; } code{ padding: 10px; font-family: 'Ubuntu Mono', 'Consolas', 'Monaco', 'DejaVu Sans Mono', Courier, monospace; background-color: #fdf6f2; display: block; margin-bottom: 10px; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } h1 { font-size: 36px; line-height: 1.1; margin-bottom: 20px; } article h1, h2 { font-size: 24px; line-height: 1.2; margin-bottom: 14px; } h3 { font-size: 16px; line-height: 1.3; margin-bottom: 8px; } h4 { font-weight: bold; } time { color:#999; } /** * @section Structure */ .header-login, .header-navigation div, .header-content div { margin: 0 auto; width: 940px; } .header-content h1{ background-color:#ffffff; display:inline-block; } .header-content h2{ background-color:#ffffff; display:table; } .header-login ul { margin: 4px 0; float: right; } .header-login li { margin-right: 10px; float: left; } .header-login a { color: #333; } .header-navigation { border-top: 2px solid #dd4814; border-bottom: 2px solid #dd4814; background-color: #fff; height: 54px; clear: right; overflow: hidden; } .header-navigation nav ul { border-right: 1px solid #dd4814; float: right; } .header-navigation nav li { border-left: 1px solid #dd4814; float: left; height: 54px; } .header-navigation nav a { padding: 18px 14px 0; font-size: 14px; display: block; height: 36px; } .header-navigation nav a:hover { background-color: #fcece7; } .header-navigation nav .current_page_item a, .header-navigation nav .current_page_parent a, .header-navigation nav .current_page_ancestor a { background-color: #dd4814; color: #fff; } .header-navigation input { margin: 12px 10px 0 10px; padding: 5px; border-top: 1px solid #a1a1a1; border-right: 1px solid #e0e0e0; border-bottom: 1px solid #fff; border-left: 1px solid #e0e0e0; width: 90px; font-style: italic; color: #ccc; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; -moz-box-shadow: inset 0 1px 1px #e0e0e0; -webkit-box-shadow: inset 0 1px 1px #e0e0e0; box-shadow: inset 0 1px 1px #e0e0e0; } .header-navigation h2 { margin: 18px 0 0 6px; text-transform: lowercase; font-size: 22px; color: #dd4814; float: left; } .header-navigation .logo-ubuntu { margin-top: 12px; float: left; } .header-content .header-navigation-secondary { margin-bottom: 40px; padding: 0; position: relative; z-index: 2; } .header-navigation-secondary div { padding: 0; border: 2px solid #dd4814; -moz-border-radius: 0px 0px 4px 4px; -webkit-border-radius: 0px 0px 4px 4px; border-radius: 0px 0px 4px 4px; background: #fff; border-top: 0px; width: 936px; } .header-navigation-secondary nav li { float: left; } .header-navigation-secondary nav li a { color: #333; display: block; height: 25px; padding: 8px 8px 0; } .header-navigation-secondary nav li:hover, .header-navigation-secondary nav .current_page_item a { background: url("../img/sec-nav-hover.gif"); } .header-content { padding-bottom: 30px; border-bottom: 1px solid #e0e0e0; -moz-box-shadow: 0 1px 3px #e0e0e0; -webkit-box-shadow: 0 1px 3px #e0e0e0; box-shadow: 0 1px 3px #e0e0e0; margin-bottom: 3px; position: relative; overflow: hidden; } footer { padding: 10px 10px 40px 10px; position: relative; -moz-border-radius: 0 0 4px 4px; -webkit-border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px; font-size: 12px; background: url("../img/background-footer.png") repeat scroll 0 0 #f7f6f5; } footer div { margin: 0 auto; padding: 0 10px; width: 940px; } footer a { color: #000; } footer nav ul { margin: 10px 17px 30px 0; width: 172px; display: inline-block; vertical-align: top; height: auto; zoom: 1; *display: inline; } footer nav ul.last { margin-right: 0; } footer nav li { margin-bottom: 8px; } footer nav li:first-child { font-weight: bold; } footer p { margin-bottom: 0; } #content { padding-top: 35px; } .arrow-nav { display: none; position: absolute; top: -1px; z-index: 3; } .shadow { margin: 30px 0 3px 0; border-bottom: 1px solid #e0e0e0; -moz-box-shadow: 0 2px 3px #e0e0e0; -webkit-box-shadow: 0 2px 3px #e0e0e0; box-shadow: 0 2px 3px #e0e0e0; height: 3px; } /** * @section Site-wide */ #content h2{ font-size:24px; } .box-orange { padding: 10px; border: 3px solid #dd4814; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } .box-orange .link-action-small { float: right; margin: 0 0 0 20px; } .link-bug { margin-left: 10px; color: #999; } .link-action { float: left; margin-bottom: 20px; padding: 8px 12px; display: block; background-color: #dd4814; color: #fff; -moz-border-radius: 20px; -webkit-border-radius: 20px; border-radius: 20px; font-size: 16px; line-height: 1.3; border-top: 3px solid #e6633a; border-bottom: 3px solid #c03d14; } .link-action2 { float: left; display: block; color: #fff; font-size: 16px; line-height: 1.3; } .link-action2 span{ display:block; float:left; } .link-action2 .cta-left{ background:url(../img/button-cta-left.png) no-repeat; width:22px; height:48px; } .link-action2 .cta-center{ background:url(../img/button-cta-slice.png) repeat-x; line-height:45px; height:48px; } .link-action2 .cta-right{ background:url(../img/button-cta-right.png) no-repeat; width:22px; height:48px; } .link-action-small { float: left; display: block; color: #fff; font-size: 16px; } .link-action-small span{ display:block; float:left; height:42px; } .link-action-small .cta-left{ background:url(../img/button-cta-left-small.png) no-repeat; width:19px; } .link-action-small .cta-center{ background:url(../img/button-cta-slice-small.png) repeat-x; line-height:42px; } .link-action-small .cta-right{ background:url(../img/button-cta-right-small.png) no-repeat; width:19px; } .link-action:active { position: relative; top: 1px; } .link-action2:active { position: relative; top: 1px; } .link-action-small:active { position: relative; top: 1px; } .list-bullets li { margin-bottom: 10px; list-style: disc; list-style-position: inside; } .box { margin-bottom: 30px; padding: 15px; border: 1px solid #aea79f; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } .box-padded { margin-bottom: 30px; padding: 5px; border: 2px solid #aea79f; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; background: url("../img/pattern-featured.gif") repeat scroll 0 0 #ebe9e7; overflow: hidden; } .box-padded h3 { margin: 5px 0 10px 5px; } .box-padded div { padding: 10px; border: 1px solid #aea79f; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; background-color: #fff; overflow: hidden; } .box-padded li { padding: 0 10px; float: left; width: 211px; border-right: 1px dotted #aea79f; } .box-padded li.first { padding: 0; margin-bottom: 0; } .box-padded li.last { border: 0; width: 217px; } .box-padded img { margin: 0 10px 50px 0; float: left; -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; } .box-clear { margin-bottom: 40px; } .box-clear .grid-4.first { margin-right: 15px; padding-right: 15px; } .box-clear .grid-4 { margin-left: 0; margin-right: 10px; padding-right: 10px; width: 298px; } .box-clear time { display: block; border-bottom: 1px dotted #aea79f; padding-bottom: 10px; margin-bottom: 10px; } .box-clear div.first { border-right: 1px dotted #aea79f; } .box-clear a { display: block; } .box-clear .rss { background: url("../img/rss.jpg") no-repeat scroll 0 center; padding-left: 20px; } .box-clear .location { display: block; margin-bottom: 1px; } .box-clear .last { margin: 0; padding-right: 0; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; width: 293px; } /* Widgets */ .ui-state-focus { outline: none; } .ui-accordion { border-bottom: 1px dotted #aea79f; } .ui-accordion a { display: block; } .ui-accordion h3 { margin-bottom: 0; border-top: 1px dotted #aea79f; position: relative; font-size: 13px; font-weight: bold; } .ui-accordion h3 a { padding: 10px 0; color: #333; } .ui-accordion h4 { margin-bottom: 5px; } .ui-accordion div fieldset { padding-bottom: 5px; } .ui-accordion div li, .ui-accordion div input { margin-bottom: 10px; } .ui-accordion .ui-icon { position: absolute; top: 15px; right: 0; display: block; width: 8px; height: 8px; background: url("../img/icon-accordion-inactive.png") 0 0 no-repeat transparent; } .ui-accordion .ui-state-active .ui-icon { background-image: url("../img/icon-accordion-active.png"); } .ui-accordion .current_page_item a { color: #333; } .container-tweet { -moz-border-radius: 4px 4px 4px 4px; -webkit-border-radius: 4px 4px 4px 4px; border-radius: 4px 4px 4px 4px; padding: 10px 10px 10px; background-color: #f7f7f7; } .container-tweet .tweet-follow { margin-top: 10px; margin-bottom: -10px; padding-left: 55px; padding-bottom: 6px; background: url("../img/tweet-follow.png") 0 5px no-repeat; display: block; } .container-tweet .tweet-follow span { font-size: 16px; font-weight: bold; line-height: 1.2; display: block; } .tweet a { display: inline; } .tweet .tweet_text { padding: 10px; background-color: #fff; -moz-border-radius: 4px 4px 4px 4px; -webkit-border-radius: 4px 4px 4px 4px; border-radius: 4px 4px 4px 4px; border: 1px solid #dd4814; font-size: 16px; display: block; clear: both; } .tweet.tweet-small .tweet_text { font-size: inherit; } .tweet .tweet_text a { color: #333; } .tweet .tweet_time, .tweet .tweet_user_and_time { padding: 15px 0 10px 0; position: relative; top: -2px; background: url("../img/tweet-arrow.png") no-repeat; display: block; } .tweet .tweet_odd .tweet_time, .tweet .tweet_odd .tweet_user_and_time { background-position: right 0; float: right; } .tweet .tweet_even .tweet_time, .tweet .tweet_even .tweet_user_and_time { background-position: left 0; float: left; } /* Search */ #content .list-search li { list-style-type:none; border:0px; margin-bottom: 15px; padding-top: 15px; } /* Blog */ .blog-article #nav-single { margin-top: 30px; margin-bottom: 30px; } .blog-article #nav-single .nav-next { float: right; } .blog-article article header .entry-meta { margin-bottom: 20px; } .blog-article article .entry-meta { color: #999; } .blog-article #respond form input[type="submit"] { float: left; cursor: pointer; margin-bottom: 20px; padding: 8px 12px; display: block; background-color: #dd4814; color: #fff; -moz-border-radius: 20px; -webkit-border-radius: 20px; border-radius: 20px; font-size: 16px; line-height: 1.3; border-top: 3px solid #e6633a; border-left: 3px solid #e6633a; border-right: 3px solid #e6633a; border-bottom: 3px solid #c03d14; } .blog-article #respond form input[type="submit"]:active { position: relative; top: 1px; } .alignnone{ float:left; margin:10px 20px 10px 0; } .alignleft{ float:left; margin:10px 20px 10px 0; } .alignright{ float:right; margin:10px 0 10px 20px; } .aligncenter{ float:left; margin:10px 20px 10px 0; } .entry-content h2, .entry-content h3{ margin-top:20px; } .entry-content ul li{ list-style-type: circle; margin-left:16px; } .entry-content hr{ border:none; border-top: 1px dotted #AEA79F; } ./doc/css/custom.css0000644000015600001650000000016412703462031014455 0ustar jenkinsjenkinsli#buildversion { display: none; } div.toc { display: none; } span.type a:visited { color: #dd4814; } ./doc/css/qtquick.css0000644000015600001650000003316012703462031014626 0ustar jenkinsjenkins/* * Copyright 2013 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ @media screen { /* basic elements */ html { color: #000000; background: #FFFFFF; } table { border-collapse: collapse; border-spacing: 0; } fieldset, img { border: 0; max-width:100%; } address, caption, cite, code, dfn, em, strong, th, var, optgroup { font-style: inherit; font-weight: inherit; } del, ins { text-decoration: none; } ol li { list-style: decimal; } ul li { list-style: none; } caption, th { text-align: left; } h1.title { font-weight: bold; font-size: 150%; } h0 { font-weight: bold; font-size: 130%; } h1, h2, h3, h4, h5, h6 { font-size: 100%; } q:before, q:after { content: ''; } abbr, acronym { border: 0; font-variant: normal; } sup, sub { vertical-align: baseline; } tt, .qmlreadonly span, .qmldefault span { word-spacing:0.5em; } legend { color: #000000; } strong { font-weight: bold; } em { font-style: italic; } body { margin: 0 1.5em 0 1.5em; font-family: ubuntu; line-height: normal } a { color: #00732F; text-decoration: none; } hr { background-color: #E6E6E6; border: 1px solid #E6E6E6; height: 1px; width: 100%; text-align: left; margin: 1.5em 0 1.5em 0; } pre { border: 1px solid #DDDDDD; -moz-border-radius: 0.7em 0.7em 0.7em 0.7em; -webkit-border-radius: 0.7em 0.7em 0.7em 0.7em; border-radius: 0.7em 0.7em 0.7em 0.7em; padding: 1em 1em 1em 1em; overflow-x: auto; } table, pre { -moz-border-radius: 0.7em 0.7em 0.7em 0.7em; -webkit-border-radius: 0.7em 0.7em 0.7em 0.7em; border-radius: 0.7em 0.7em 0.7em 0.7em; background-color: #F6F6F6; border: 1px solid #E6E6E6; border-collapse: separate; margin-bottom: 2.5em; } pre { font-size: 90%; display: block; overflow:hidden; } thead { margin-top: 0.5em; font-weight: bold } th { padding: 0.5em 1.5em 0.5em 1em; background-color: #E1E1E1; border-left: 1px solid #E6E6E6; } td { padding: 0.25em 1.5em 0.25em 1em; } td.rightAlign { padding: 0.25em 0.5em 0.25em 1em; } table tr.odd { border-left: 1px solid #E6E6E6; background-color: #F6F6F6; color: black; } table tr.even { border-left: 1px solid #E6E6E6; background-color: #ffffff; color: #202020; } div.float-left { float: left; margin-right: 2em } div.float-right { float: right; margin-left: 2em } span.comment { color: #008B00; } span.string, span.char { color: #000084; } span.number { color: #a46200; } span.operator { color: #202020; } span.keyword { color: #840000; } span.name { color: black } span.type { font-weight: bold } span.type a:visited { color: #0F5300; } span.preprocessor { color: #404040 } /* end basic elements */ /* font style elements */ .heading { font-weight: bold; font-size: 125%; } .subtitle { font-size: 110% } .small-subtitle { font-size: 100% } .red { color:red; } /* end font style elements */ /* global settings*/ .header, .footer { display: block; clear: both; overflow: hidden; } /* end global settings*/ /* header elements */ .header .qtref { color: #00732F; font-weight: bold; font-size: 130%; } .header .content { margin-left: 5px; margin-top: 5px; margin-bottom: 0.5em; } .header .breadcrumb { font-size: 90%; padding: 0.5em 0 0.5em 1em; margin: 0; background-color: #fafafa; height: 1.35em; border-bottom: 1px solid #d1d1d1; } .header .breadcrumb ul { margin: 0; padding: 0; } .header .content { word-wrap: break-word; } .header .breadcrumb ul li { float: left; background: url(../images/breadcrumb.png) no-repeat 0 3px; padding-left: 1.5em; margin-left: 1.5em; } .header .breadcrumb ul li.last { font-weight: normal; } .header .breadcrumb ul li a { color: #00732F; } .header .breadcrumb ul li.first { background-image: none; padding-left: 0; margin-left: 0; } .header .content ol li { background: none; margin-bottom: 1.0em; margin-left: 1.2em; padding-left: 0 } .header .content li { background: url(../images/bullet_sq.png) no-repeat 0 5px; margin-bottom: 1em; padding-left: 1.2em; } /* end header elements */ /* content elements */ .content h1 { font-weight: bold; font-size: 130% } .content h2 { font-weight: bold; font-size: 120%; width: 100%; } .content h3 { font-weight: bold; font-size: 110%; width: 100%; } .content table p { margin: 0 } .content ul { padding-left: 2.5em; } .content li { padding-top: 0.25em; padding-bottom: 0.25em; } .content ul img { vertical-align: middle; } .content a:visited { color: #4c0033; text-decoration: none; } .content a:visited:hover { color: #4c0033; text-decoration: underline; } a:hover { color: #4c0033; text-decoration: underline; } descr p a { text-decoration: underline; } .descr p a:visited { text-decoration: underline; } .alphaChar{ width:95%; background-color:#F6F6F6; border:1px solid #E6E6E6; -moz-border-radius: 7px 7px 7px 7px; border-radius: 7px 7px 7px 7px; -webkit-border-radius: 7px 7px 7px 7px; font-size:12pt; padding-left:10px; margin-top:10px; margin-bottom:10px; } .flowList{ /*vertical-align:top;*/ /*margin:20px auto;*/ column-count:3; -webkit-column-count:3; -moz-column-count:3; /* column-width:100%; -webkit-column-width:200px; -col-column-width:200px; */ column-gap:41px; -webkit-column-gap:41px; -moz-column-gap:41px; column-rule: 1px dashed #ccc; -webkit-column-rule: 1px dashed #ccc; -moz-column-rule: 1px dashed #ccc; } .flowList dl{ } .flowList dd{ /*display:inline-block;*/ margin-left:10px; min-width:250px; line-height: 1.5; min-width:100%; min-height:15px; } .flowList dd a{ } .mainContent { padding-left:5px; } .content .flowList p{ padding:0px; } .content .alignedsummary { margin: 15px; } .qmltype { text-align: center; font-size: 120%; } .qmlreadonly { padding-left: 5px; float: right; color: #254117; } .qmldefault { padding-left: 5px; float: right; color: red; } .qmldoc { } .generic .alphaChar{ margin-top:5px; } .generic .odd .alphaChar{ background-color: #F6F6F6; } .generic .even .alphaChar{ background-color: #FFFFFF; } .memItemRight{ padding: 0.25em 1.5em 0.25em 0; } .highlightedCode { margin: 1.0em; } .annotated td { padding: 0.25em 0.5em 0.25em 0.5em; } .toc { font-size: 80% } .header .content .toc ul { padding-left: 0px; } .content .toc h3 { border-bottom: 0px; margin-top: 0px; } .content .toc h3 a:hover { color: #00732F; text-decoration: none; } .content .toc .level2 { margin-left: 1.5em; } .content .toc .level3 { margin-left: 3.0em; } .content ul li { background: url(../images/bullet_sq.png) no-repeat 0 0.7em; padding-left: 1em } .content .toc li { background: url(../images/bullet_dn.png) no-repeat 0 5px; padding-left: 1em } .relpage { -moz-border-radius: 7px 7px 7px 7px; -webkit-border-radius: 7px 7px 7px 7px; border-radius: 7px 7px 7px 7px; border: 1px solid #DDDDDD; padding: 25px 25px; clear: both; } .relpage ul { float: none; padding: 1.5em; } h3.fn, span.fn { -moz-border-radius:7px 7px 7px 7px; -webkit-border-radius:7px 7px 7px 7px; border-radius:7px 7px 7px 7px; background-color: #F6F6F6; border-width: 1px; border-style: solid; border-color: #E6E6E6; font-weight: bold; word-spacing:3px; padding:3px 5px; } .functionIndex { font-size:12pt; word-spacing:10px; margin-bottom:10px; background-color: #F6F6F6; border-width: 1px; border-style: solid; border-color: #E6E6E6; -moz-border-radius: 7px 7px 7px 7px; -webkit-border-radius: 7px 7px 7px 7px; border-radius: 7px 7px 7px 7px; width:100%; } .centerAlign { text-align:center; } .rightAlign { text-align:right; } .leftAlign { text-align:left; } .topAlign{ vertical-align:top } .functionIndex a{ display:inline-block; } /* end content elements */ /* footer elements */ .footer { color: #393735; font-size: 0.75em; text-align: center; padding-top: 1.5em; padding-bottom: 1em; background-color: #E6E7E8; margin: 0; } .footer p { margin: 0.25em } .small { font-size: 0.5em; } /* end footer elements */ .item { float: left; position: relative; width: 100%; overflow: hidden; } .item .primary { margin-right: 220px; position: relative; } .item hr { margin-left: -220px; } .item .secondary { float: right; width: 200px; position: relative; } .item .cols { clear: both; display: block; } .item .cols .col { float: left; margin-left: 1.5%; } .item .cols .col.first { margin-left: 0; } .item .cols.two .col { width: 45%; } .item .box { margin: 0 0 10px 0; } .item .box h3 { margin: 0 0 10px 0; } .cols.unclear { clear:none; } } /* end of screen media */ /* start of print media */ @media print { input, textarea, .header, .footer, .toolbar, .feedback, .wrapper .hd, .wrapper .bd .sidebar, .wrapper .ft, #feedbackBox, #blurpage, .toc, .breadcrumb, .toolbar, .floatingResult { display: none; background: none; } .content { background: none; display: block; width: 100%; margin: 0; float: none; } } /* end of print media */ /* modify the TOC layouts */ div.toc ul { padding-left: 20px; } div.toc li { padding-left: 4px; } /* Remove the border around images*/ a img { border:none; } /*Add styling to the front pages*/ .threecolumn_area { padding-top: 20px; padding-bottom: 20px; } .threecolumn_piece { display: inline-block; margin-left: 78px; margin-top: 8px; padding: 0; vertical-align: top; width: 25.5%; } div.threecolumn_piece ul { list-style-type: none; padding-left: 0px; margin-top: 2px; } div.threecolumn_piece p { margin-bottom: 7px; color: #5C626E; text-decoration: none; font-weight: bold; } div.threecolumn_piece li { padding-left: 0px; margin-bottom: 5px; } div.threecolumn_piece a { font-weight: normal; } /* Add style to guide page*/ .fourcolumn_area { padding-top: 20px; padding-bottom: 20px; } .fourcolumn_piece { display: inline-block; margin-left: 35px; margin-top: 8px; padding: 0; vertical-align: top; width: 21.3%; } div.fourcolumn_piece ul { list-style-type: none; padding-left: 0px; margin-top: 2px; } div.fourcolumn_piece p { margin-bottom: 7px; color: #40444D; text-decoration: none; font-weight: bold; } div.fourcolumn_piece li { padding-left: 0px; margin-bottom: 5px; } div.fourcolumn_piece a { font-weight: normal; } ./doc/CMakeLists.txt0000644000015600001650000000103012703462031014372 0ustar jenkinsjenkinsproject(ubuntu-web-doc) find_program(QDOC_EXECUTABLE qdoc) if(QDOC_EXECUTABLE STREQUAL "QDOC_EXECUTABLE-NOTFOUND") message(WARNING "qdoc not found, documentation cannot be built") else() add_custom_target(doc ALL COMMAND ${QDOC_EXECUTABLE} -outputdir ${CMAKE_CURRENT_BINARY_DIR}/html ${CMAKE_CURRENT_SOURCE_DIR}/ubuntu-web.qdocconf) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DATADIR}/doc/ubuntu-web) endif() ./cmake/0000755000015600001650000000000012703462032012154 5ustar jenkinsjenkins./cmake/EnableCoverageReport.cmake0000644000015600001650000001535012703462031017217 0ustar jenkinsjenkins# - Creates a special coverage build type and target on GCC. # # Defines a function ENABLE_COVERAGE_REPORT which generates the coverage target # for selected targets. Optional arguments to this function are used to filter # unwanted results using globbing expressions. Moreover targets with tests for # the source code can be specified to trigger regenerating the report if the # test has changed # # ENABLE_COVERAGE_REPORT(TARGETS target... [FILTER filter...] [TESTS test targets...]) # # To generate a coverage report first build the project with # CMAKE_BUILD_TYPE=coverage, then call make test and afterwards make coverage. # # The coverage report is based on gcov. Depending on the availability of lcov # a HTML report will be generated and/or an XML report of gcovr is found. # The generated coverage target executes all found solutions. Special targets # exist to create e.g. only the xml report: coverage-xml. # # Copyright (C) 2010 by Johannes Wienke # # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General # Public License as published by the Free Software Foundation; # either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # INCLUDE(ParseArguments) FIND_PACKAGE(Lcov) FIND_PACKAGE(gcovr) FUNCTION(ENABLE_COVERAGE_REPORT) # argument parsing PARSE_ARGUMENTS(ARG "FILTER;TARGETS;TESTS;EXCLUDES" "" ${ARGN}) SET(COVERAGE_RAW_FILE "${CMAKE_BINARY_DIR}/coverage.raw.info") SET(COVERAGE_FILTERED_FILE "${CMAKE_BINARY_DIR}/coverage.info") SET(COVERAGE_REPORT_DIR "${CMAKE_BINARY_DIR}/coveragereport") SET(COVERAGE_XML_FILE "${CMAKE_BINARY_DIR}/coverage.xml") SET(COVERAGE_XML_COMMAND_FILE "${CMAKE_BINARY_DIR}/coverage-xml.cmake") # decide if there is any tool to create coverage data SET(TOOL_FOUND FALSE) IF(LCOV_FOUND OR GCOVR_FOUND) SET(TOOL_FOUND TRUE) ENDIF() IF(NOT TOOL_FOUND) MESSAGE(STATUS "Cannot enable coverage targets because neither lcov nor gcovr are found.") ENDIF() STRING(TOLOWER "${CMAKE_BUILD_TYPE}" COVERAGE_BUILD_TYPE) IF(CMAKE_COMPILER_IS_GNUCXX AND TOOL_FOUND AND "${COVERAGE_BUILD_TYPE}" MATCHES "coverage") MESSAGE(STATUS "Coverage support enabled for targets: ${ARG_TARGETS}") # create coverage build type SET(CMAKE_CXX_FLAGS_COVERAGE ${CMAKE_CXX_FLAGS_DEBUG} PARENT_SCOPE) SET(CMAKE_C_FLAGS_COVERAGE ${CMAKE_C_FLAGS_DEBUG} PARENT_SCOPE) SET(CMAKE_CONFIGURATION_TYPES ${CMAKE_CONFIGURATION_TYPES} coverage PARENT_SCOPE) # instrument targets SET_TARGET_PROPERTIES(${ARG_TARGETS} PROPERTIES COMPILE_FLAGS --coverage LINK_FLAGS --coverage) # html report IF (LCOV_FOUND) MESSAGE(STATUS "Enabling HTML coverage report") # set up coverage target ADD_CUSTOM_COMMAND(OUTPUT ${COVERAGE_RAW_FILE} COMMAND ${LCOV_EXECUTABLE} -c -d ${CMAKE_BINARY_DIR} -o ${COVERAGE_RAW_FILE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Collecting coverage data" DEPENDS ${ARG_TARGETS} ${ARG_TESTS} VERBATIM) # filter unwanted stuff LIST(LENGTH ARG_FILTER FILTER_LENGTH) IF(${FILTER_LENGTH} GREATER 0) SET(FILTER COMMAND ${LCOV_EXECUTABLE}) FOREACH(F ${ARG_FILTER}) SET(FILTER ${FILTER} -r ${COVERAGE_FILTERED_FILE} ${F}) ENDFOREACH() SET(FILTER ${FILTER} -o ${COVERAGE_FILTERED_FILE}) ELSE() SET(FILTER "") ENDIF() ADD_CUSTOM_COMMAND(OUTPUT ${COVERAGE_FILTERED_FILE} COMMAND ${LCOV_EXECUTABLE} -e ${COVERAGE_RAW_FILE} "${CMAKE_SOURCE_DIR}*" -o ${COVERAGE_FILTERED_FILE} ${FILTER} DEPENDS ${COVERAGE_RAW_FILE} COMMENT "Filtering recorded coverage data for project-relevant entries" VERBATIM) ADD_CUSTOM_COMMAND(OUTPUT ${COVERAGE_REPORT_DIR} COMMAND ${CMAKE_COMMAND} -E make_directory ${COVERAGE_REPORT_DIR} COMMAND ${GENHTML_EXECUTABLE} --legend --show-details -t "${PROJECT_NAME} test coverage" -o ${COVERAGE_REPORT_DIR} ${COVERAGE_FILTERED_FILE} DEPENDS ${COVERAGE_FILTERED_FILE} COMMENT "Generating HTML coverage report in ${COVERAGE_REPORT_DIR}" VERBATIM) ADD_CUSTOM_TARGET(coverage-html DEPENDS ${COVERAGE_REPORT_DIR}) ENDIF() # xml coverage report IF(GCOVR_FOUND) MESSAGE(STATUS "Enabling XML coverage report") # gcovr cannot write directly to a file so the execution needs to # be wrapped in a cmake file that generates the file output FILE(WRITE ${COVERAGE_XML_COMMAND_FILE} "SET(ENV{LANG} en)\n") FILE(APPEND ${COVERAGE_XML_COMMAND_FILE} "EXECUTE_PROCESS(COMMAND \"${GCOVR_EXECUTABLE}\" -x -e \"${ARG_EXCLUDES}\" -r \"${CMAKE_SOURCE_DIR}\" OUTPUT_FILE \"${COVERAGE_XML_FILE}\" WORKING_DIRECTORY \"${CMAKE_BINARY_DIR}\")\n") ADD_CUSTOM_COMMAND(OUTPUT ${COVERAGE_XML_FILE} COMMAND ${CMAKE_COMMAND} ARGS -P ${COVERAGE_XML_COMMAND_FILE} COMMENT "Generating coverage XML report" VERBATIM) ADD_CUSTOM_TARGET(coverage-xml DEPENDS ${COVERAGE_XML_FILE}) ENDIF() # provide a global coverage target executing both steps if available SET(GLOBAL_DEPENDS "") IF(LCOV_FOUND) LIST(APPEND GLOBAL_DEPENDS ${COVERAGE_REPORT_DIR}) ENDIF() IF(GCOVR_FOUND) LIST(APPEND GLOBAL_DEPENDS ${COVERAGE_XML_FILE}) ENDIF() IF(LCOV_FOUND OR GCOVR_FOUND) ADD_CUSTOM_TARGET(coverage DEPENDS ${GLOBAL_DEPENDS}) ENDIF() ENDIF() ENDFUNCTION() ./cmake/Findgcovr.cmake0000644000015600001650000000170212703462031015076 0ustar jenkinsjenkins# - Find gcovr scrip # Will define: # # GCOVR_EXECUTABLE - the gcovr script # # Uses: # # GCOVR_ROOT - root to search for the script # # Copyright (C) 2011 by Johannes Wienke # # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General # Public License as published by the Free Software Foundation; # either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # INCLUDE(FindPackageHandleStandardArgs) FIND_PROGRAM(GCOVR_EXECUTABLE gcovr HINTS ${GCOVR_ROOT} "${GCOVR_ROOT}/bin") FIND_PACKAGE_HANDLE_STANDARD_ARGS(gcovr DEFAULT_MSG GCOVR_EXECUTABLE) # only visible in advanced view MARK_AS_ADVANCED(GCOVR_EXECUTABLE) ./cmake/ubuntu-arm-linux-gnueabihf.cmake0000644000015600001650000000150112703462031020334 0ustar jenkinsjenkins# shamelessly copied over from oxide’s build system # to enable ARM cross compilation set(CMAKE_SYSTEM_NAME Linux CACHE INTERNAL "") find_program(_DPKG_ARCH_EXECUTABLE dpkg-architecture) if(_DPKG_ARCH_EXECUTABLE STREQUAL "DPKG_ARCHITECTURE_EXECUTABLE-NOTFOUND") message(FATAL_ERROR "dpkg-architecture not found") endif() execute_process(COMMAND ${_DPKG_ARCH_EXECUTABLE} -qDEB_BUILD_GNU_TYPE RESULT_VARIABLE _RESULT OUTPUT_VARIABLE HOST_ARCHITECTURE OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT _RESULT EQUAL 0) message(FATAL_ERROR "Failed to determine host architecture") endif() set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc CACHE INTERNAL "") set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++ CACHE INTERNAL "") set(CMAKE_LIBRARY_ARCHITECTURE arm-linux-gnueabihf CACHE INTERNAL "") ./cmake/qt5.cmake0000644000015600001650000000277212703462031013676 0ustar jenkinsjenkins# shamelessly copied over from oxide’s build system # to enable ARM cross compilation if(CMAKE_CROSSCOMPILING) # QT_MOC_EXECUTABLE is set by Qt5CoreConfigExtras, but it sets it to # the target executable rather than the host executable, which is no # use for cross-compiling. For cross-compiling, we have a guess and # override it ourselves if(NOT TARGET Qt5::moc) find_program( QT_MOC_EXECUTABLE moc PATHS /usr/lib/qt5/bin /usr/lib/${HOST_ARCHITECTURE}/qt5/bin NO_DEFAULT_PATH) if(QT_MOC_EXECUTABLE STREQUAL "QT_MOC_EXECUTABLE-NOTFOUND") message(FATAL_ERROR "Can't find a moc executable for the host arch") endif() add_executable(Qt5::moc IMPORTED) set_target_properties(Qt5::moc PROPERTIES IMPORTED_LOCATION "${QT_MOC_EXECUTABLE}") endif() # Dummy targets - not used anywhere, but this stops Qt5CoreConfigExtras.cmake # from creating them and checking if the binary exists, which is broken when # cross-building because it checks for the target system binary. We need the # host system binaries installed, because they are in the same package as the # moc in Ubuntu (qtbase5-dev-tools), which is not currently multi-arch if(NOT TARGET Qt5::qmake) add_executable(Qt5::qmake IMPORTED) endif() if(NOT TARGET Qt5::rcc) add_executable(Qt5::rcc IMPORTED) endif() if(NOT TARGET Qt5::uic) add_executable(Qt5::uic IMPORTED) endif() else() # This should be enough to initialize QT_MOC_EXECUTABLE find_package(Qt5Core) endif() ./cmake/ParseArguments.cmake0000644000015600001650000000340612703462031016120 0ustar jenkinsjenkins# Parse arguments passed to a function into several lists separated by # upper-case identifiers and options that do not have an associated list e.g.: # # SET(arguments # hello OPTION3 world # LIST3 foo bar # OPTION2 # LIST1 fuz baz # ) # PARSE_ARGUMENTS(ARG "LIST1;LIST2;LIST3" "OPTION1;OPTION2;OPTION3" ${arguments}) # # results in 7 distinct variables: # * ARG_DEFAULT_ARGS: hello;world # * ARG_LIST1: fuz;baz # * ARG_LIST2: # * ARG_LIST3: foo;bar # * ARG_OPTION1: FALSE # * ARG_OPTION2: TRUE # * ARG_OPTION3: TRUE # # taken from http://www.cmake.org/Wiki/CMakeMacroParseArguments MACRO(PARSE_ARGUMENTS prefix arg_names option_names) SET(DEFAULT_ARGS) FOREACH(arg_name ${arg_names}) SET(${prefix}_${arg_name}) ENDFOREACH(arg_name) FOREACH(option ${option_names}) SET(${prefix}_${option} FALSE) ENDFOREACH(option) SET(current_arg_name DEFAULT_ARGS) SET(current_arg_list) FOREACH(arg ${ARGN}) SET(larg_names ${arg_names}) LIST(FIND larg_names "${arg}" is_arg_name) IF (is_arg_name GREATER -1) SET(${prefix}_${current_arg_name} ${current_arg_list}) SET(current_arg_name ${arg}) SET(current_arg_list) ELSE (is_arg_name GREATER -1) SET(loption_names ${option_names}) LIST(FIND loption_names "${arg}" is_option) IF (is_option GREATER -1) SET(${prefix}_${arg} TRUE) ELSE (is_option GREATER -1) SET(current_arg_list ${current_arg_list} ${arg}) ENDIF (is_option GREATER -1) ENDIF (is_arg_name GREATER -1) ENDFOREACH(arg) SET(${prefix}_${current_arg_name} ${current_arg_list}) ENDMACRO(PARSE_ARGUMENTS) ./cmake/FindLcov.cmake0000644000015600001650000000172012703462031014661 0ustar jenkinsjenkins# - Find lcov # Will define: # # LCOV_EXECUTABLE - the lcov binary # GENHTML_EXECUTABLE - the genhtml executable # # Copyright (C) 2010 by Johannes Wienke # # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General # Public License as published by the Free Software Foundation; # either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # INCLUDE(FindPackageHandleStandardArgs) FIND_PROGRAM(LCOV_EXECUTABLE lcov) FIND_PROGRAM(GENHTML_EXECUTABLE genhtml) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Lcov DEFAULT_MSG LCOV_EXECUTABLE GENHTML_EXECUTABLE) # only visible in advanced view MARK_AS_ADVANCED(LCOV_EXECUTABLE GENHTML_EXECUTABLE) ./tests/0000755000015600001650000000000012703462031012235 5ustar jenkinsjenkins./tests/unittests/0000755000015600001650000000000012703462031014277 5ustar jenkinsjenkins./tests/unittests/limit-proxy-model/0000755000015600001650000000000012703462032017673 5ustar jenkinsjenkins./tests/unittests/limit-proxy-model/tst_LimitProxyModelTests.cpp0000644000015600001650000001262512703462031025422 0ustar jenkinsjenkins/* * Copyright 2014-2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include #include // local #include "limit-proxy-model.h" class SimpleListModel : public QAbstractListModel { Q_OBJECT public: enum Roles { Index = Qt::UserRole + 1, String }; QHash roleNames() const { static QHash roles; if (roles.isEmpty()) { roles[Index] = "index"; roles[String] = "string"; } return roles; } int rowCount(const QModelIndex& parent=QModelIndex()) const { return m_strings.count(); } QVariant data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case Index: return index.row(); case String: return m_strings.at(index.row()); default: return QVariant(); } } void append(const QStringList& strings) { int index = m_strings.count(); beginInsertRows(QModelIndex(), index, index + strings.count() - 1); m_strings << strings; endInsertRows(); } void remove(int index) { beginRemoveRows(QModelIndex(), index, index); m_strings.removeAt(index); endRemoveRows(); } private: QStringList m_strings; }; class LimitProxyModelTests : public QObject { Q_OBJECT private: SimpleListModel* strings; LimitProxyModel* model; private Q_SLOTS: void init() { strings = new SimpleListModel; model = new LimitProxyModel; model->setSourceModel(strings); } void cleanup() { delete model; delete strings; } void shouldBeInitiallyEmpty() { QCOMPARE(model->rowCount(), 0); } void shouldLimitBeInitiallyMinusOne() { QCOMPARE(model->limit(), -1); } void shouldNotifyWhenChangingSourceModel() { QSignalSpy spy(model, SIGNAL(sourceModelChanged())); model->setSourceModel(strings); QVERIFY(spy.isEmpty()); QStringListModel strings2; model->setSourceModel(&strings2); QCOMPARE(spy.count(), 1); QCOMPARE(model->sourceModel(), &strings2); model->setSourceModel(0); QCOMPARE(spy.count(), 2); QCOMPARE(model->sourceModel(), (QAbstractItemModel*) 0); } void shouldLimitEntriesWithLimitSetBeforePopulating() { model->setLimit(2); strings->append({"a", "b", "c"}); QCOMPARE(model->rowCount(), 2); QCOMPARE(model->unlimitedRowCount(), 3); } void shouldLimitEntriesWithLimitSetAfterPopulating() { strings->append({"a", "b", "c"}); model->setLimit(2); QCOMPARE(model->rowCount(), 2); QCOMPARE(model->unlimitedRowCount(), 3); } void shouldNotLimitEntriesIfLimitIsMinusOne() { model->setLimit(-1); strings->append({"a", "b", "c"}); QCOMPARE(model->unlimitedRowCount(), 3); QCOMPARE(model->rowCount(), model->unlimitedRowCount()); } void shouldNotLimitEntriesIfLimitIsGreaterThanRowCount() { model->setLimit(4); strings->append({"a", "b", "c"}); QCOMPARE(model->unlimitedRowCount(), 3); QCOMPARE(model->rowCount(), model->unlimitedRowCount()); } void shouldUpdateRowCountAndNotifyAfterAnEntryIsRemoved() { model->setLimit(2); strings->append({"a", "b", "c", "d"}); QSignalSpy spyChanged(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); QSignalSpy spyRemoved(model, SIGNAL(rowsRemoved(QModelIndex, int, int))); strings->remove(0); QCOMPARE(spyChanged.count(), 1); QVERIFY(spyRemoved.isEmpty()); QCOMPARE(model->data(model->index(0, 0), SimpleListModel::String).toString(), QString("b")); QCOMPARE(model->data(model->index(1, 0), SimpleListModel::String).toString(), QString("c")); QCOMPARE(model->unlimitedRowCount(), 3); QCOMPARE(model->rowCount(), 2); } void shouldGetItemWithCorrectValues() { strings->append({"a"}); QVariantMap item = model->get(0); QHash roles = model->roleNames(); QCOMPARE(roles.count(), item.count()); Q_FOREACH(int role, roles.keys()) { QString roleName = QString::fromUtf8(roles.value(role)); QCOMPARE(model->data(model->index(0, 0), role), item.value(roleName)); } } void shouldReturnEmptyItemIfGetOutOfBounds() { QVariantMap item = model->get(1); QVERIFY(item.isEmpty()); } }; QTEST_MAIN(LimitProxyModelTests) #include "tst_LimitProxyModelTests.moc" ./tests/unittests/limit-proxy-model/CMakeLists.txt0000644000015600001650000000063012703462031022431 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Sql REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_LimitProxyModelTests) add_executable(${TEST} tst_LimitProxyModelTests.cpp) include_directories(${webbrowser-app_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Sql Qt5::Test webbrowser-app-models ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/cookie-store/0000755000015600001650000000000012703462032016703 5ustar jenkinsjenkins./tests/unittests/cookie-store/tst_CookieStore.cpp0000644000015600001650000000374012703462031022532 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include #include #include #include // local #include "chrome-cookie-store.h" #include "online-accounts-cookie-store.h" uint qHash(const QNetworkCookie &cookie, uint seed) { return qHash(cookie.toRawForm(), seed); } class CookieStoreTest : public QObject { Q_OBJECT private Q_SLOTS: void testChromeProperties(); }; void CookieStoreTest::testChromeProperties() { QTemporaryDir tmpDir; QVERIFY(tmpDir.isValid()); QDir testDir(tmpDir.path()); QTemporaryDir tmpDir2; QVERIFY(tmpDir2.isValid()); QDir testDir2(tmpDir2.path()); ChromeCookieStore store; QSignalSpy dbPathChanged(&store, SIGNAL(dbPathChanged())); QString path = testDir.filePath("cookies.db"); store.setProperty("dbPath", path); QCOMPARE(dbPathChanged.count(), 1); QCOMPARE(store.property("dbPath").toString(), path); dbPathChanged.clear(); QString path2 = testDir2.filePath("cookies.db"); store.setProperty("dbPath", "file://" + path2); QCOMPARE(dbPathChanged.count(), 1); QCOMPARE(store.property("dbPath").toString(), path2); QVERIFY(store.property("cookies").value().isEmpty()); } QTEST_MAIN(CookieStoreTest) #include "tst_CookieStore.moc" ./tests/unittests/cookie-store/CMakeLists.txt0000644000015600001650000000115512703462031021444 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5Sql REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_CookieStoreTests) set(SOURCES ${webapp-container_SOURCE_DIR}/chrome-cookie-store.cpp ${webapp-container_SOURCE_DIR}/cookie-store.cpp ${webapp-container_SOURCE_DIR}/oxide-cookie-helper.cpp tst_CookieStore.cpp ) add_executable(${TEST} ${SOURCES}) include_directories(${webapp-container_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Network Qt5::Sql Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/tabs-model/0000755000015600001650000000000012703462032016327 5ustar jenkinsjenkins./tests/unittests/tabs-model/tst_TabsModelTests.cpp0000644000015600001650000004172012703462031022625 0ustar jenkinsjenkins/* * Copyright 2013-2016 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include #include #include #include #include // local #include "tabs-model.h" class TabsModelTests : public QObject { Q_OBJECT private: QQmlEngine engine; TabsModel* model; QQuickItem* createTab() { QQmlComponent component(&engine); QByteArray data("import QtQuick 2.4\nItem {\nproperty url url\n" "property string title\nproperty url icon\n}"); component.setData(data, QUrl()); QObject* object = component.create(); object->setParent(this); QQuickItem* item = qobject_cast(object); return item; } QQuickItem* createTabWithTitle(const QString& title) { QQuickItem* tab = createTab(); tab->setProperty("title", title); return tab; } void verifyTabsOrder(QStringList orderedTitles) { QCOMPARE(model->rowCount(), orderedTitles.count()); int i = 0; Q_FOREACH(QString title, orderedTitles) { QCOMPARE(model->get(i++)->property("title").toString(), title); } } private Q_SLOTS: void init() { model = new TabsModel; } void cleanup() { while (model->rowCount() > 0) { delete model->remove(0); } delete model; } void shouldBeInitiallyEmpty() { QCOMPARE(model->rowCount(), 0); QCOMPARE(model->currentTab(), (QObject*) nullptr); } void shouldExposeRoleNames() { QList roleNames = model->roleNames().values(); QVERIFY(roleNames.contains("url")); QVERIFY(roleNames.contains("title")); QVERIFY(roleNames.contains("icon")); QVERIFY(roleNames.contains("tab")); } void shouldNotAllowSettingTheIndexToAnInvalidValue_data() { QTest::addColumn("index"); QTest::newRow("zero") << 0; QTest::newRow("too high") << 2; QTest::newRow("too low") << -2; } void shouldNotAllowSettingTheIndexToAnInvalidValue() { QFETCH(int, index); model->setCurrentIndex(index); QCOMPARE(model->currentIndex(), -1); QCOMPARE(model->currentTab(), (QObject*) nullptr); } void shouldNotAddNullTab() { QCOMPARE(model->add(0), -1); QCOMPARE(model->rowCount(), 0); } void shouldReturnIndexWhenAddingTab() { for(int i = 0; i < 3; ++i) { QCOMPARE(model->add(createTab()), i); } } void shouldUpdateCountWhenAddingTab() { QSignalSpy spy(model, SIGNAL(countChanged())); model->add(createTab()); QCOMPARE(spy.count(), 1); QCOMPARE(model->rowCount(), 1); } void shouldNotInsertNullTab() { QCOMPARE(model->insert(0, 0), -1); QCOMPARE(model->rowCount(), 0); } void shouldReturnIndexWhenInsertingTab() { for(int i = 0; i < 3; ++i) { model->add(createTab()); } for(int i = 2; i >= 0; --i) { QCOMPARE(model->insert(createTab(), i), i); } } void shouldUpdateCountWhenInsertingTab() { QSignalSpy spy(model, SIGNAL(countChanged())); model->insert(createTab(), 0); QCOMPARE(spy.count(), 1); QCOMPARE(model->rowCount(), 1); } void shouldInsertAtCorrectIndex() { model->insert(createTabWithTitle("B"), 0); model->insert(createTabWithTitle("A"), 0); verifyTabsOrder(QStringList({"A", "B"})); model->insert(createTabWithTitle("X"), 1); verifyTabsOrder(QStringList({"A", "X", "B"})); model->insert(createTabWithTitle("C"), 3); verifyTabsOrder(QStringList({"A", "X", "B", "C"})); } void shouldClampIndexWhenInsertingTabOutOfBounds() { model->add(createTabWithTitle("A")); model->add(createTabWithTitle("B")); model->insert(createTabWithTitle("C"), 3); verifyTabsOrder(QStringList({"A", "B", "C"})); model->insert(createTabWithTitle("X"), -1); verifyTabsOrder(QStringList({"X", "A", "B", "C"})); } void shouldUpdateCountWhenRemovingTab() { model->add(createTab()); QSignalSpy spy(model, SIGNAL(countChanged())); delete model->remove(0); QCOMPARE(spy.count(), 1); QCOMPARE(model->rowCount(), 0); } void shouldNotAllowRemovingAtInvalidIndex() { QCOMPARE(model->remove(0), (QObject*) nullptr); QCOMPARE(model->remove(2), (QObject*) nullptr); QCOMPARE(model->remove(-2), (QObject*) nullptr); } void shouldReturnTabWhenRemoving() { QQuickItem* tab = createTab(); model->add(tab); QObject* removed = model->remove(0); QCOMPARE(removed, tab); delete removed; } void shouldNotDeleteTabWhenRemoving() { QQuickItem* tab = createTab(); model->add(tab); model->remove(0); QCOMPARE(tab->parent(), this); delete tab; } void shouldNotifyWhenAddingTab() { QSignalSpy spy(model, SIGNAL(rowsInserted(const QModelIndex&, int, int))); for(int i = 0; i < 3; ++i) { model->add(createTab()); QCOMPARE(spy.count(), 1); QList args = spy.takeFirst(); QCOMPARE(args.at(1).toInt(), i); QCOMPARE(args.at(2).toInt(), i); } } void shouldNotifyWhenRemovingTab() { QSignalSpy spy(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int))); for(int i = 0; i < 5; ++i) { model->add(createTab()); } delete model->remove(3); QCOMPARE(spy.count(), 1); QList args = spy.takeFirst(); QCOMPARE(args.at(1).toInt(), 3); QCOMPARE(args.at(2).toInt(), 3); for(int i = 3; i >= 0; --i) { delete model->remove(i); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(1).toInt(), i); QCOMPARE(args.at(2).toInt(), i); } } void shouldNotifyWhenTabPropertiesChange() { qRegisterMetaType >(); QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); QQuickItem* tab = createTab(); model->add(tab); QQmlProperty(tab, "url").write(QUrl("http://ubuntu.com")); QCOMPARE(spy.count(), 1); QList args = spy.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 0); QCOMPARE(args.at(1).toModelIndex().row(), 0); QVector roles = args.at(2).value >(); QCOMPARE(roles.size(), 1); QVERIFY(roles.contains(TabsModel::Url)); QQmlProperty(tab, "title").write(QString("Lorem Ipsum")); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 0); QCOMPARE(args.at(1).toModelIndex().row(), 0); roles = args.at(2).value >(); QCOMPARE(roles.size(), 1); QVERIFY(roles.contains(TabsModel::Title)); QQmlProperty(tab, "icon").write(QUrl("image://webicon/123")); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 0); QCOMPARE(args.at(1).toModelIndex().row(), 0); roles = args.at(2).value >(); QCOMPARE(roles.size(), 1); QVERIFY(roles.contains(TabsModel::Icon)); } void shouldUpdateCurrentTabWhenSettingCurrentIndex() { QQuickItem* tab1 = createTab(); model->add(tab1); QSignalSpy spy(model, SIGNAL(currentTabChanged())); model->setCurrentIndex(0); QCOMPARE(model->currentIndex(), 0); QVERIFY(spy.isEmpty()); QCOMPARE(model->currentTab(), tab1); QQuickItem* tab2 = createTab(); model->add(tab2); model->setCurrentIndex(1); QCOMPARE(model->currentIndex(), 1); QCOMPARE(spy.count(), 1); QCOMPARE(model->currentTab(), tab2); } void shouldSetCurrentTabWhenAddingFirstTab() { // Adding a tab to an empty model should update the current tab // to that tab QSignalSpy spytab(model, SIGNAL(currentTabChanged())); QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); QCOMPARE(model->currentIndex(), -1); QCOMPARE(model->currentTab(), (QObject*) nullptr); QQuickItem* tab1 = createTab(); model->add(tab1); QCOMPARE(spytab.count(), 1); QCOMPARE(spyindex.count(), 1); QCOMPARE(model->currentIndex(), 0); QCOMPARE(model->currentTab(), tab1); // But adding further items should keep the index where it was model->add(createTab()); model->add(createTab()); QCOMPARE(spytab.count(), 1); QCOMPARE(spyindex.count(), 1); QCOMPARE(model->currentIndex(), 0); QCOMPARE(model->currentTab(), tab1); } void shouldSetCurrentTabWhenInsertingFirstTab() { // Inserting a tab to an empty model should update the current tab // to that tab QSignalSpy spytab(model, SIGNAL(currentTabChanged())); QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); QCOMPARE(model->currentIndex(), -1); QCOMPARE(model->currentTab(), (QObject*) nullptr); QQuickItem* tab1 = createTab(); model->insert(tab1, 0); QCOMPARE(spytab.count(), 1); QCOMPARE(spyindex.count(), 1); QCOMPARE(model->currentIndex(), 0); QCOMPARE(model->currentTab(), tab1); } void shouldUpdateCurrentTabWhenInsertingAtCurrentIndex() { QQuickItem* tab1 = createTab(); model->insert(tab1, 0); QCOMPARE(model->currentIndex(), 0); QCOMPARE(model->currentTab(), tab1); QSignalSpy spytab(model, SIGNAL(currentTabChanged())); QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); QQuickItem* tab2 = createTab(); model->insert(tab2, 0); QVERIFY(spyindex.isEmpty()); QCOMPARE(spytab.count(), 1); QCOMPARE(model->currentTab(), tab2); } void shouldUpdateCurrentIndexWhenInsertingBeforeCurrentIndex() { model->insert(createTab(), 0); model->insert(createTab(), 0); model->setCurrentIndex(1); QObject* current = model->currentTab(); QSignalSpy spytab(model, SIGNAL(currentTabChanged())); QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); model->insert(createTab(), 0); QVERIFY(spytab.isEmpty()); QCOMPARE(spyindex.count(), 1); QCOMPARE(model->currentTab(), current); QCOMPARE(model->currentIndex(), 2); } void shouldSetInvalidIndexWhenRemovingLastTab() { // Removing the last item should also set the current index to -1 // and the current tab to null model->add(createTab()); QSignalSpy spytab(model, SIGNAL(currentTabChanged())); QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); delete model->remove(0); QCOMPARE(spytab.count(), 1); QCOMPARE(spyindex.count(), 1); QCOMPARE(model->currentIndex(), -1); QCOMPARE(model->currentTab(), (QObject*) nullptr); } void shouldNotChangeIndexWhenRemovingAfterCurrent() { // When removing a tab after the current one, // the current tab shouldn’t change. QQuickItem* tab1 = createTab(); model->add(tab1); model->add(createTab()); QSignalSpy spytab(model, SIGNAL(currentTabChanged())); QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); delete model->remove(1); QCOMPARE(model->currentTab(), tab1); QVERIFY(spytab.isEmpty()); QVERIFY(spyindex.isEmpty()); } void shouldUpdateIndexWhenRemovingCurrent() { // When removing the current tab, if there is a tab after it, // it becomes the current one. QQuickItem* tab1 = createTab(); QQuickItem* tab2 = createTab(); QQuickItem* tab3 = createTab(); model->add(tab1); model->add(tab2); model->add(tab3); model->setCurrentIndex(1); QCOMPARE(model->currentIndex(), 1); QCOMPARE(model->currentTab(), tab2); QSignalSpy spytab(model, SIGNAL(currentTabChanged())); QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); delete model->remove(1); QCOMPARE(spyindex.count(), 0); QCOMPARE(spytab.count(), 1); QCOMPARE(model->currentTab(), tab3); // If there is no tab after it but one before, that one becomes current delete model->remove(1); QCOMPARE(spyindex.count(), 1); QCOMPARE(spytab.count(), 2); QCOMPARE(model->currentIndex(), 0); QCOMPARE(model->currentTab(), tab1); } void shouldDecreaseIndexWhenRemovingBeforeCurrent() { // When removing a tab before the current tab, the current index // should decrease to match. model->add(createTab()); model->add(createTab()); QQuickItem* tab = createTab(); model->add(tab); model->setCurrentIndex(2); QSignalSpy spytab(model, SIGNAL(currentTabChanged())); QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); delete model->remove(1); QVERIFY(spytab.isEmpty()); QCOMPARE(spyindex.count(), 1); QCOMPARE(model->currentIndex(), 1); QCOMPARE(model->currentTab(), tab); } void shouldReturnData() { QQuickItem* tab = createTab(); QQmlProperty(tab, "url").write(QUrl("http://ubuntu.com/")); QQmlProperty(tab, "title").write(QString("Lorem Ipsum")); QQmlProperty(tab, "icon").write(QUrl("image://webicon/123")); model->add(tab); QVERIFY(!model->data(QModelIndex(), TabsModel::Url).isValid()); QVERIFY(!model->data(model->index(-1, 0), TabsModel::Url).isValid()); QVERIFY(!model->data(model->index(3, 0), TabsModel::Url).isValid()); QCOMPARE(model->data(model->index(0, 0), TabsModel::Url).toUrl(), QUrl("http://ubuntu.com/")); QCOMPARE(model->data(model->index(0, 0), TabsModel::Title).toString(), QString("Lorem Ipsum")); QCOMPARE(model->data(model->index(0, 0), TabsModel::Icon).toUrl(), QUrl("image://webicon/123")); QCOMPARE(model->data(model->index(0, 0), TabsModel::Tab).value(), tab); QVERIFY(!model->data(model->index(0, 0), TabsModel::Tab + 3).isValid()); } void shouldReturnTabAtIndex() { // valid indexes QQuickItem* tab1 = createTab(); model->add(tab1); QQuickItem* tab2 = createTab(); model->add(tab2); QQuickItem* tab3 = createTab(); model->add(tab3); QCOMPARE(model->get(0), tab1); QCOMPARE(model->get(1), tab2); QCOMPARE(model->get(2), tab3); // invalid indexes QCOMPARE(model->get(-1), (QObject*) nullptr); QCOMPARE(model->get(3), (QObject*) nullptr); } private: void moveTabs(int from, int to, bool moved, bool indexChanged, int newIndex) { QSignalSpy spyMoved(model, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int))); QSignalSpy spyIndex(model, SIGNAL(currentIndexChanged())); QSignalSpy spyTab(model, SIGNAL(currentTabChanged())); model->move(from, to); QCOMPARE(spyMoved.count(), moved ? 1 : 0); if (moved) { QList args = spyMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), from); QCOMPARE(args.at(2).toInt(), from); QCOMPARE(args.at(4).toInt(), to); } QCOMPARE(spyIndex.count(), indexChanged ? 1 : 0); QCOMPARE(model->currentIndex(), newIndex); QVERIFY(spyTab.isEmpty()); } private Q_SLOTS: void shouldNotifyWhenMovingTabs() { for (int i = 0; i < 3; ++i) { model->add(createTab()); } moveTabs(1, 1, false, false, 0); moveTabs(4, 1, false, false, 0); moveTabs(1, 4, false, false, 0); moveTabs(0, 2, true, true, 2); moveTabs(1, 0, true, false, 2); moveTabs(2, 1, true, true, 1); moveTabs(2, 0, true, true, 2); moveTabs(2, 1, true, true, 1); moveTabs(0, 2, true, true, 0); } }; QTEST_MAIN(TabsModelTests) #include "tst_TabsModelTests.moc" ./tests/unittests/tabs-model/CMakeLists.txt0000644000015600001650000000106512703462031021070 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Qml REQUIRED) find_package(Qt5Quick REQUIRED) find_package(Qt5Sql REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_TabsModelTests) add_executable(${TEST} tst_TabsModelTests.cpp) include_directories(${webbrowser-app_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Qml Qt5::Quick Qt5::Sql Qt5::Test webbrowser-app-models ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) set_tests_properties(${TEST} PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal") ./tests/unittests/bookmarks-model/0000755000015600001650000000000012703462032017366 5ustar jenkinsjenkins./tests/unittests/bookmarks-model/tst_BookmarksModelTests.cpp0000644000015600001650000002262612703462031024727 0ustar jenkinsjenkins/* * Copyright 2013-2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include #include // local #include "bookmarks-model.h" class BookmarksModelTests : public QObject { Q_OBJECT private: BookmarksModel* model; private Q_SLOTS: void init() { model = new BookmarksModel; model->setDatabasePath(":memory:"); } void cleanup() { delete model; } void shouldBeInitiallyEmpty() { QCOMPARE(model->rowCount(), 0); } void shouldExposeRoleNames() { QList roleNames = model->roleNames().values(); QVERIFY(roleNames.contains("url")); QVERIFY(roleNames.contains("title")); QVERIFY(roleNames.contains("icon")); QVERIFY(roleNames.contains("created")); QVERIFY(roleNames.contains("folder")); } void shouldAddNewEntries() { QSignalSpy spy(model, SIGNAL(rowsInserted(QModelIndex, int, int))); model->add(QUrl("http://example.org/"), "Example Domain", QUrl(), ""); QCOMPARE(model->rowCount(), 1); QCOMPARE(spy.count(), 1); QVariantList args = spy.takeFirst(); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); model->add(QUrl("http://wikipedia.org/"), "Wikipedia", QUrl(), ""); QCOMPARE(model->rowCount(), 2); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); model->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl(), ""); QCOMPARE(model->rowCount(), 3); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); model->add(QUrl("http://example.org/"), "Example Domain", QUrl(), ""); QCOMPARE(model->rowCount(), 3); QVERIFY(spy.isEmpty()); } void shouldRemoveEntries() { QSignalSpy spy(model, SIGNAL(rowsRemoved(QModelIndex, int, int))); model->add(QUrl("http://example.org/"), "Example Domain", QUrl(), ""); model->add(QUrl("http://wikipedia.org/"), "Wikipedia", QUrl(), ""); model->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl(), ""); QCOMPARE(model->rowCount(), 3); QVERIFY(spy.isEmpty()); model->remove(QUrl("http://ubuntu.com/")); QCOMPARE(model->rowCount(), 2); QCOMPARE(spy.count(), 1); QVariantList args = spy.takeFirst(); // Model is chronologically sorted so deleting the last entry added // actually deletes the first item in the model QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); model->remove(QUrl("http://ubuntu.com/")); QCOMPARE(model->rowCount(), 2); QVERIFY(spy.isEmpty()); } void shouldUpdateEntries() { QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); model->add(QUrl("http://example.org/"), "Example Domain", QUrl(), ""); model->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl(), ""); QCOMPARE(model->rowCount(), 2); QVERIFY(spy.isEmpty()); model->update(QUrl("http://example.org/"), "New Domain Title", "SampleFolder"); QCOMPARE(model->rowCount(), 2); QCOMPARE(spy.count(), 1); QList args = spy.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 1); QCOMPARE(args.at(1).toModelIndex().row(), 1); QVector roles = args.at(2).value >(); QVERIFY(roles.size() >= 2); QVERIFY(roles.contains(BookmarksModel::Title)); QVERIFY(roles.contains(BookmarksModel::Folder)); QCOMPARE(model->data(model->index(1, 0), BookmarksModel::Url).toUrl(), QUrl("http://example.org/")); QCOMPARE(model->data(model->index(1, 0), BookmarksModel::Title).toString(), QString("New Domain Title")); QCOMPARE(model->data(model->index(1, 0), BookmarksModel::Icon).toUrl(), QUrl("")); QCOMPARE(model->data(model->index(1, 0), BookmarksModel::Folder).toString(), QString("SampleFolder")); QCOMPARE(model->data(model->index(0, 0), BookmarksModel::Url).toUrl(), QUrl("http://ubuntu.com/")); } void shouldContainEntries() { model->add(QUrl("http://example.org/"), "Example Domain", QUrl(), ""); model->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl(), ""); QVERIFY(model->contains(QUrl("http://ubuntu.com/"))); QVERIFY(!model->contains(QUrl("http://wikipedia.org/"))); } void shouldKeepEntriesSortedChronologically() { model->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl(), ""); model->add(QUrl("http://wikipedia.org/"), "Wikipedia", QUrl(), ""); model->add(QUrl("http://example.org/"), "Example Domain", QUrl(), ""); QCOMPARE(model->data(model->index(0, 0), BookmarksModel::Url).toUrl(), QUrl("http://example.org/")); QCOMPARE(model->data(model->index(1, 0), BookmarksModel::Url).toUrl(), QUrl("http://wikipedia.org/")); QCOMPARE(model->data(model->index(2, 0), BookmarksModel::Url).toUrl(), QUrl("http://ubuntu.com/")); } void shouldReturnData() { model->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl("image://webicon/123"), "SampleFolder"); QVERIFY(!model->data(QModelIndex(), BookmarksModel::Url).isValid()); QVERIFY(!model->data(model->index(-1, 0), BookmarksModel::Url).isValid()); QVERIFY(!model->data(model->index(3, 0), BookmarksModel::Url).isValid()); QCOMPARE(model->data(model->index(0, 0), BookmarksModel::Url).toUrl(), QUrl("http://ubuntu.com/")); QCOMPARE(model->data(model->index(0, 0), BookmarksModel::Title).toString(), QString("Ubuntu")); QCOMPARE(model->data(model->index(0, 0), BookmarksModel::Icon).toUrl(), QUrl("image://webicon/123")); QVERIFY(model->data(model->index(0, 0), BookmarksModel::Created).toDateTime() <= QDateTime::currentDateTime()); QCOMPARE(model->data(model->index(0, 0), BookmarksModel::Folder).toString(), QString("SampleFolder")); QVERIFY(!model->data(model->index(0, 0), BookmarksModel::Folder + 1).isValid()); } void shouldReturnDatabasePath() { QCOMPARE(model->databasePath(), QString(":memory:")); } void shouldNotifyWhenSettingDatabasePath() { QSignalSpy spyPath(model, SIGNAL(databasePathChanged())); QSignalSpy spyReset(model, SIGNAL(modelReset())); model->setDatabasePath(":memory:"); QVERIFY(spyPath.isEmpty()); QVERIFY(spyReset.isEmpty()); model->setDatabasePath(""); QCOMPARE(spyPath.count(), 1); QCOMPARE(spyReset.count(), 1); QCOMPARE(model->databasePath(), QString(":memory:")); } void shouldSerializeOnDisk() { QTemporaryFile tempFile; tempFile.open(); QString fileName = tempFile.fileName(); delete model; model = new BookmarksModel; model->setDatabasePath(fileName); model->add(QUrl("http://example.org/"), "Example Domain", QUrl(), ""); model->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl(), ""); delete model; model = new BookmarksModel; model->setDatabasePath(fileName); QCOMPARE(model->rowCount(), 2); } void shouldCountNumberOfEntries() { QSignalSpy spyCount(model, SIGNAL(rowCountChanged())); QCOMPARE(model->property("count").toInt(), 0); model->add(QUrl("http://example.org/"), "Example Domain", QUrl(), ""); QCOMPARE(model->property("count").toInt(), 1); QCOMPARE(spyCount.count(), 1); model->add(QUrl("http://example.com/"), "Example Domain", QUrl(), ""); QCOMPARE(model->property("count").toInt(), 2); QCOMPARE(spyCount.count(), 2); model->remove(QUrl("http://example.com/")); QCOMPARE(model->property("count").toInt(), 1); QCOMPARE(spyCount.count(), 3); } void shouldPopulateModelWithExistingFolders() { QTemporaryFile tempFile; tempFile.open(); QString fileName = tempFile.fileName(); delete model; model = new BookmarksModel; QSignalSpy spy(model, SIGNAL(folderAdded(QString))); model->setDatabasePath(fileName); model->addFolder("SampleFolder"); model->addFolder("AnotherFolder"); // The empty folder is added by default QCOMPARE(spy.count(), 3); QCOMPARE(model->folders().count(), 3); delete model; model = new BookmarksModel; QSignalSpy spyPopulate(model, SIGNAL(folderAdded(QString))); model->setDatabasePath(fileName); QCOMPARE(spyPopulate.count(), 3); QCOMPARE(model->folders().count(), 3); } }; QTEST_MAIN(BookmarksModelTests) #include "tst_BookmarksModelTests.moc" ./tests/unittests/bookmarks-model/CMakeLists.txt0000644000015600001650000000062612703462031022131 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Sql REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_BookmarksModelTests) add_executable(${TEST} tst_BookmarksModelTests.cpp) include_directories(${webbrowser-app_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Sql Qt5::Test webbrowser-app-models ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/session-storage/0000755000015600001650000000000012703462032017425 5ustar jenkinsjenkins./tests/unittests/session-storage/tst_SessionStorageTests.cpp0000644000015600001650000000735212703462031025024 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include // local #include "session-storage.h" class SessionStorageTests : public QObject { Q_OBJECT private: SessionStorage* session; private Q_SLOTS: void init() { session = new SessionStorage; } void cleanup() { delete session; } void shouldNotDoAnythingWithEmptyDataFile() { QVERIFY(session->dataFile().isEmpty()); QVERIFY(!session->isLocked()); session->store(QString("foobar")); // should be a no-op QVERIFY(session->retrieve().isEmpty()); } void shouldSetDataFile() { QSignalSpy dataFileSpy(session, SIGNAL(dataFileChanged())); QSignalSpy lockedSpy(session, SIGNAL(lockedChanged())); // Set an invalid session file QString invalidDataFile("/f00/bAr/session.json"); session->setDataFile(invalidDataFile); QCOMPARE(session->dataFile(), invalidDataFile); QCOMPARE(dataFileSpy.count(), 1); QCOMPARE(lockedSpy.count(), 0); QVERIFY(!session->isLocked()); dataFileSpy.clear(); // Set a valid session file QTemporaryFile file; QVERIFY(file.open()); file.close(); session->setDataFile(file.fileName()); QCOMPARE(session->dataFile(), file.fileName()); QCOMPARE(dataFileSpy.count(), 1); QCOMPARE(lockedSpy.count(), 1); QVERIFY(session->isLocked()); dataFileSpy.clear(); lockedSpy.clear(); // Reset the session file session->setDataFile(""); QCOMPARE(session->dataFile(), QString("")); QCOMPARE(dataFileSpy.count(), 1); QCOMPARE(lockedSpy.count(), 1); QVERIFY(!session->isLocked()); dataFileSpy.clear(); lockedSpy.clear(); } void shouldStoreAndRetrieveCorrectlyAfterwards() { QTemporaryFile file; QVERIFY(file.open()); file.close(); session->setDataFile(file.fileName()); QVERIFY(session->isLocked()); QString data("f00bAr"); session->store(data); QCOMPARE(session->retrieve(), data); delete session; session = new SessionStorage; session->setDataFile(file.fileName()); QVERIFY(session->isLocked()); QCOMPARE(session->retrieve(), data); } void shouldLockOutSecondInstance() { QTemporaryFile file; QVERIFY(file.open()); file.close(); session->setDataFile(file.fileName()); QVERIFY(session->isLocked()); SessionStorage session2; session2.setDataFile(session->dataFile()); QVERIFY(!session2.isLocked()); // Verify that the session that held the lock going away // doesn’t automagically make the next one acquire it // (this would be undesirable as it would allow the second // instance to overwrite the first session’s data). delete session; session = NULL; QVERIFY(!session2.isLocked()); } }; QTEST_MAIN(SessionStorageTests) #include "tst_SessionStorageTests.moc" ./tests/unittests/session-storage/CMakeLists.txt0000644000015600001650000000065112703462031022166 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_SessionStorageTests) set(SOURCES ${webbrowser-common_SOURCE_DIR}/session-storage.cpp tst_SessionStorageTests.cpp ) add_executable(${TEST} ${SOURCES}) include_directories(${webbrowser-common_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/history-domain-model/0000755000015600001650000000000012703462032020344 5ustar jenkinsjenkins./tests/unittests/history-domain-model/tst_HistoryDomainModelTests.cpp0000644000015600001650000000617412703462031026546 0ustar jenkinsjenkins/* * Copyright 2013-2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include // local #include "history-model.h" #include "history-domain-model.h" class HistoryDomainModelTests : public QObject { Q_OBJECT private: HistoryModel* history; HistoryDomainModel* model; private Q_SLOTS: void init() { history = new HistoryModel; history->setDatabasePath(":memory:"); model = new HistoryDomainModel; model->setSourceModel(history); } void cleanup() { delete model; delete history; } void shouldBeInitiallyEmpty() { QCOMPARE(model->rowCount(), 0); } void shouldNotifyWhenChangingSourceModel() { QSignalSpy spy(model, SIGNAL(sourceModelChanged())); model->setSourceModel(history); QVERIFY(spy.isEmpty()); HistoryModel history2; model->setSourceModel(&history2); QCOMPARE(spy.count(), 1); QCOMPARE(model->sourceModel(), &history2); model->setSourceModel(nullptr); QCOMPARE(spy.count(), 2); QCOMPARE(model->sourceModel(), (HistoryModel*) nullptr); } void shouldNotifyWhenChangingDomain() { QSignalSpy spy(model, SIGNAL(domainChanged())); model->setDomain(QString()); QVERIFY(spy.isEmpty()); model->setDomain(QString("")); QVERIFY(spy.isEmpty()); model->setDomain("example.org"); QCOMPARE(spy.count(), 1); } void shouldMatchAllWhenNoDomainSet() { history->add(QUrl("http://example.org"), "Example Domain", QUrl()); history->add(QUrl("http://example.com"), "Example Domain", QUrl()); QCOMPARE(model->rowCount(), 2); } void shouldFilterOutNonMatchingDomains() { history->add(QUrl("http://example.org/"), "Example Domain", QUrl()); history->add(QUrl("http://example.com/"), "Example Domain", QUrl()); model->setDomain("example.org"); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Url).toUrl(), QUrl("http://example.org/")); model->setDomain("example.com"); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Url).toUrl(), QUrl("http://example.com/")); model->setDomain("ubuntu.com"); QCOMPARE(model->rowCount(), 0); model->setDomain(""); QCOMPARE(model->rowCount(), 2); } }; QTEST_MAIN(HistoryDomainModelTests) #include "tst_HistoryDomainModelTests.moc" ./tests/unittests/history-domain-model/CMakeLists.txt0000644000015600001650000000063612703462031023110 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Sql REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_HistoryDomainModelTests) add_executable(${TEST} tst_HistoryDomainModelTests.cpp) include_directories(${webbrowser-app_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Sql Qt5::Test webbrowser-app-models ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/history-domainlist-model/0000755000015600001650000000000012703462032021240 5ustar jenkinsjenkins./tests/unittests/history-domainlist-model/tst_HistoryDomainListModelTests.cpp0000644000015600001650000002502412703462031030271 0ustar jenkinsjenkins/* * Copyright 2013-2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include // local #include "domain-utils.h" #include "history-model.h" #include "history-domain-model.h" #include "history-domainlist-model.h" class HistoryDomainListModelTests : public QObject { Q_OBJECT private: HistoryModel* history; HistoryDomainListModel* model; void verifyDataChanged(QSignalSpy& spy, int row) { QList args; bool changed = false; while(!changed && !spy.isEmpty()) { args = spy.takeFirst(); int start = args.at(0).toModelIndex().row(); int end = args.at(1).toModelIndex().row(); changed = (start <= row) && (row <= end); } QVERIFY(changed); } private Q_SLOTS: void init() { history = new HistoryModel; history->setDatabasePath(":memory:"); model = new HistoryDomainListModel; model->setSourceModel(history); } void cleanup() { delete model; delete history; } void shouldBeInitiallyEmpty() { QCOMPARE(model->rowCount(), 0); } void shouldUpdateDomainListWhenInsertingEntries() { QSignalSpy spyRowsInserted(model, SIGNAL(rowsInserted(const QModelIndex&, int, int))); qRegisterMetaType >(); QSignalSpy spyDataChanged(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); history->add(QUrl("http://example.org/"), "Example Domain", QUrl()); QVERIFY(spyDataChanged.isEmpty()); QCOMPARE(spyRowsInserted.count(), 1); QList args = spyRowsInserted.takeFirst(); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->data(model->index(0, 0), HistoryDomainListModel::Domain).toString(), QString("example.org")); history->add(QUrl("http://example.com/"), "Example Domain", QUrl()); QVERIFY(spyDataChanged.isEmpty()); QCOMPARE(spyRowsInserted.count(), 1); args = spyRowsInserted.takeFirst(); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); QCOMPARE(model->rowCount(), 2); QCOMPARE(model->data(model->index(0, 0), HistoryDomainListModel::Domain).toString(), QString("example.com")); history->add(QUrl("http://example.org/test.html"), "Test page", QUrl()); QVERIFY(spyRowsInserted.isEmpty()); QVERIFY(!spyDataChanged.isEmpty()); args = spyDataChanged.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 1); QCOMPARE(args.at(1).toModelIndex().row(), 1); QCOMPARE(model->rowCount(), 2); } void shouldUpdateDomainListWhenRemovingEntries() { history->add(QUrl("http://example.org/"), "Example Domain", QUrl()); history->add(QUrl("http://example.com/"), "Example Domain", QUrl()); history->add(QUrl("http://example.org/test"), "Example Domain", QUrl()); QCOMPARE(model->rowCount(), 2); history->removeEntryByUrl(QUrl("http://example.org/test")); QCOMPARE(model->rowCount(), 2); history->removeEntryByUrl(QUrl("http://example.org/")); QCOMPARE(model->rowCount(), 1); } void shouldUpdateDataWhenMovingEntries() { history->add(QUrl("http://example.org/"), "Example Domain", QUrl()); history->add(QUrl("http://example.com/"), "Example Domain", QUrl()); QTest::qWait(100); QSignalSpy spyRowsMoved(model, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int))); qRegisterMetaType >(); QSignalSpy spyDataChanged(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); history->add(QUrl("http://example.org/"), "Example Domain", QUrl()); QVERIFY(spyRowsMoved.isEmpty()); } void shouldUpdateDataWhenDataChanges() { history->add(QUrl("http://example.com/"), "Example Domain", QUrl()); history->add(QUrl("http://example.org/"), "Example Domain", QUrl()); QSignalSpy spyRowsMoved(model, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int))); qRegisterMetaType >(); QSignalSpy spyDataChanged(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); history->add(QUrl("http://example.org/foobar"), "Example Domain", QUrl()); QVERIFY(spyRowsMoved.isEmpty()); QVERIFY(!spyDataChanged.isEmpty()); verifyDataChanged(spyDataChanged, 1); spyDataChanged.clear(); history->add(QUrl("http://example.org/foobar"), "Example Domain 2", QUrl()); QVERIFY(spyRowsMoved.isEmpty()); QVERIFY(!spyDataChanged.isEmpty()); verifyDataChanged(spyDataChanged, 1); } void shouldUpdateWhenChangingSourceModel() { QSignalSpy spy(model, SIGNAL(sourceModelChanged())); history->add(QUrl("http://example.org/"), "Example Domain", QUrl()); history->add(QUrl("http://example.com/"), "Example Domain", QUrl()); history->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl()); QCOMPARE(model->rowCount(), 3); model->setSourceModel(history); QVERIFY(spy.isEmpty()); QCOMPARE(model->rowCount(), 3); model->setSourceModel(nullptr); QCOMPARE(spy.count(), 1); QCOMPARE(model->sourceModel(), (HistoryModel*) nullptr); QCOMPARE(model->rowCount(), 0); HistoryModel history2; model->setSourceModel(&history2); QCOMPARE(spy.count(), 2); QCOMPARE(model->sourceModel(), &history2); QCOMPARE(model->rowCount(), 0); } void shouldKeepDomainsSorted() { history->add(QUrl("http://example.org/"), "Example Domain", QUrl()); history->add(QUrl("http://www.gogle.com/lawnmower"), "Gogle Lawn Mower", QUrl()); history->add(QUrl("http://example.com/"), "Example Domain", QUrl()); history->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl()); history->add(QUrl("file:///tmp/test.html"), "test", QUrl()); history->add(QUrl("http://www.gogle.com/mail"), "Gogle Mail", QUrl()); history->add(QUrl("https://mail.gogle.com/"), "Gogle Mail", QUrl()); history->add(QUrl("https://es.wikipedia.org/wiki/Wikipedia:Portada"), "Wikipedia, la enciclopedia libre", QUrl()); QCOMPARE(model->rowCount(), 6); QStringList domains; domains << DomainUtils::TOKEN_LOCAL << "example.com" << "example.org" << "gogle.com" << "ubuntu.com" << "wikipedia.org"; for (int i = 0; i < domains.count(); ++i) { QModelIndex index = model->index(i, 0); QString domain = model->data(index, HistoryDomainListModel::Domain).toString(); HistoryDomainModel* entries = model->data(index, HistoryDomainListModel::Entries).value(); QVERIFY(!domain.isNull()); QVERIFY(!entries->domain().isNull()); QCOMPARE(domain, domains.at(i)); QCOMPARE(entries->domain(), domain); } } void shouldExposeDomainModels() { history->add(QUrl("http://example.com/"), "Example Domain", QUrl()); history->add(QUrl("http://example.org/"), "Example Domain", QUrl()); QTest::qWait(100); history->add(QUrl("http://example.org/test.html"), "Test Page", QUrl()); history->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl()); QCOMPARE(model->rowCount(), 3); QModelIndex index = model->index(0, 0); QString domain = model->data(index, HistoryDomainListModel::Domain).toString(); QCOMPARE(domain, QString("example.com")); HistoryDomainModel* entries = model->data(index, HistoryDomainListModel::Entries).value(); QCOMPARE(entries->rowCount(), 1); QCOMPARE(entries->data(entries->index(0, 0), HistoryModel::Url).toUrl(), QUrl("http://example.com/")); index = model->index(1, 0); domain = model->data(index, HistoryDomainListModel::Domain).toString(); QCOMPARE(domain, QString("example.org")); entries = model->data(index, HistoryDomainListModel::Entries).value(); QCOMPARE(entries->rowCount(), 2); QCOMPARE(entries->data(entries->index(0, 0), HistoryModel::Url).toUrl(), QUrl("http://example.org/test.html")); QCOMPARE(entries->data(entries->index(1, 0), HistoryModel::Url).toUrl(), QUrl("http://example.org/")); index = model->index(2, 0); domain = model->data(index, HistoryDomainListModel::Domain).toString(); QCOMPARE(domain, QString("ubuntu.com")); entries = model->data(index, HistoryDomainListModel::Entries).value(); QCOMPARE(entries->rowCount(), 1); QCOMPARE(entries->data(entries->index(0, 0), HistoryModel::Url).toUrl(), QUrl("http://ubuntu.com/")); } void shouldReturnData() { QDateTime now = QDateTime::currentDateTimeUtc(); history->add(QUrl("http://example.org/"), "Example Domain", QUrl()); QVERIFY(!model->data(QModelIndex(), HistoryDomainListModel::Domain).isValid()); QVERIFY(!model->data(model->index(-1, 0), HistoryDomainListModel::Domain).isValid()); QVERIFY(!model->data(model->index(3, 0), HistoryDomainListModel::Domain).isValid()); QCOMPARE(model->data(model->index(0, 0), HistoryDomainListModel::Domain).toString(), QString("example.org")); QVERIFY(model->data(model->index(0, 0), HistoryDomainListModel::LastVisit).toDateTime() >= now); HistoryDomainModel* entries = model->data(model->index(0, 0), HistoryDomainListModel::Entries).value(); QVERIFY(entries != 0); QCOMPARE(entries->rowCount(), 1); QVERIFY(!model->data(model->index(0, 0), HistoryDomainListModel::Entries + 3).isValid()); } }; QTEST_MAIN(HistoryDomainListModelTests) #include "tst_HistoryDomainListModelTests.moc" ./tests/unittests/history-domainlist-model/CMakeLists.txt0000644000015600001650000000064612703462031024005 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Sql REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_HistoryDomainListModelTests) add_executable(${TEST} tst_HistoryDomainListModelTests.cpp) include_directories(${webbrowser-app_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Sql Qt5::Test webbrowser-app-models ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/qml/0000755000015600001650000000000012703462032015071 5ustar jenkinsjenkins./tests/unittests/qml/tst_BookmarksFoldersViewWide.qml0000644000015600001650000001733312703462031023417 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtTest 1.0 import "../../../src/app/webbrowser" import webbrowserapp.private 0.1 Item { id: root width: 800 height: 600 property BookmarksFoldersViewWide view property string homepage: "http://example.com/homepage" Component { id: viewComponent BookmarksFoldersViewWide { anchors.fill: parent homeBookmarkUrl: homepage } } SignalSpy { id: bookmarkClickedSpy signalName: "bookmarkClicked" } SignalSpy { id: bookmarkRemovedSpy signalName: "bookmarkRemoved" } WebbrowserTestCase { name: "BookmarksFoldersViewWide" when: windowShown function init() { BookmarksModel.databasePath = ":memory:" view = viewComponent.createObject(root) populate() view.focus = true bookmarkClickedSpy.target = view bookmarkClickedSpy.clear() bookmarkRemovedSpy.target = view bookmarkRemovedSpy.clear() } function populate() { BookmarksModel.add("http://example.com", "Example Com", "", "") BookmarksModel.add("http://example.org/bar", "Example Org Bar", "", "Folder B") BookmarksModel.add("http://example.org/foo", "Example Org Foo", "", "Folder B") BookmarksModel.add("http://example.net/a", "Example Net A", "", "Folder A") BookmarksModel.add("http://example.net/b", "Example Net B", "", "Folder A") } function cleanup() { BookmarksModel.databasePath = "" view.destroy() view = null } function test_folder_list() { var items = getListItems(findChild(view, "foldersList"), "folderItem") compare(items.length, 3) verify(items[0].isAllBookmarksFolder) compare(items[0].model.folder, "") // named folder items should appear alphabetically sorted compare(items[1].model.folder, "Folder A") compare(items[2].model.folder, "Folder B") } function test_all_bookmarks_list() { var items = getListItems(findChild(view, "bookmarksList"), "bookmarkItem") compare(items.length, 2) compare(items[0].url, homepage) compare(items[1].title, "Example Com") } function test_navigate_folders_by_keyboard() { var folders = getListItems(findChild(view, "foldersList"), "folderItem") verify(folders[0].isActiveFolder) keyClick(Qt.Key_Down) verify(!folders[0].isActiveFolder) verify(folders[1].isActiveFolder) // bookmarks within a folder are sorted with the first bookmarked appearing last var bookmarksListView = findChild(view, "bookmarksList") var items = getListItems(bookmarksListView, "bookmarkItem") compare(items[0].title, "Example Net B") compare(items[1].title, "Example Net A") compare(items.length, 2) keyClick(Qt.Key_Down) verify(folders[2].isActiveFolder) items = getListItems(bookmarksListView, "bookmarkItem") compare(items[0].title, "Example Org Foo") compare(items[1].title, "Example Org Bar") compare(items.length, 2) // verify scrolling beyond bottom of list is not allowed keyClick(Qt.Key_Down) verify(folders[2].isActiveFolder) keyClick(Qt.Key_Up) verify(folders[1].isActiveFolder) keyClick(Qt.Key_Up) verify(folders[0].isActiveFolder) keyClick(Qt.Key_Up) } function test_switch_between_folder_and_bookmarks_by_keyboard() { var foldersList = findChild(view, "foldersList") var bookmarks = findChild(view, "bookmarksList") var folders = getListItems(foldersList, "folderItem") verify(folders[0].isActiveFolder) keyClick(Qt.Key_Right) verify(bookmarks.activeFocus) keyClick(Qt.Key_Right) verify(bookmarks.activeFocus) // verify no circular scrolling keyClick(Qt.Key_Left) verify(foldersList.activeFocus) keyClick(Qt.Key_Left) verify(foldersList.activeFocus) // verify no circular scrolling keyClick(Qt.Key_Down) verify(!folders[0].isActiveFolder) verify(folders[1].isActiveFolder) keyClick(Qt.Key_Right) verify(bookmarks.activeFocus) keyClick(Qt.Key_Right) verify(bookmarks.activeFocus) // verify no circular scrolling keyClick(Qt.Key_Left) verify(foldersList.activeFocus) keyClick(Qt.Key_Left) verify(foldersList.activeFocus) // verify no circular scrolling } function test_activate_bookmarks_by_keyboard() { keyClick(Qt.Key_Right) var items = getListItems(findChild(view, "bookmarksList"), "bookmarkItem") keyClick(Qt.Key_Return) compare(bookmarkClickedSpy.count, 1) compare(bookmarkClickedSpy.signalArguments[0][0], homepage) keyClick(Qt.Key_Down) keyClick(Qt.Key_Return) compare(bookmarkClickedSpy.count, 2) compare(bookmarkClickedSpy.signalArguments[1][0], "http://example.com") } function test_activate_bookmarks_by_mouse() { var items = getListItems(findChild(view, "bookmarksList"), "bookmarkItem") clickItem(items[0]) compare(bookmarkClickedSpy.count, 1) compare(bookmarkClickedSpy.signalArguments[0][0], homepage) clickItem(items[1]) compare(bookmarkClickedSpy.count, 2) compare(bookmarkClickedSpy.signalArguments[1][0], "http://example.com") } function test_switch_folders_by_mouse() { var folders = getListItems(findChild(view, "foldersList"), "folderItem") clickItem(folders[1]) var bookmarksListView = findChild(view, "bookmarksList") var items = getListItems(bookmarksListView, "bookmarkItem") compare(items[0].title, "Example Net B") compare(items[1].title, "Example Net A") compare(items.length, 2) clickItem(folders[0]) items = getListItems(bookmarksListView, "bookmarkItem") compare(items[0].url, homepage) compare(items[1].title, "Example Com") compare(items.length, 2) } function test_remove_bookmarks_by_keyboard() { keyClick(Qt.Key_Right) var items = getListItems(findChild(view, "bookmarksList"), "bookmarkItem") // verify that trying to delete the homepage bookmark does not work keyClick(Qt.Key_Delete) compare(bookmarkRemovedSpy.count, 0) keyClick(Qt.Key_Down) keyClick(Qt.Key_Delete) compare(bookmarkRemovedSpy.count, 1) compare(bookmarkRemovedSpy.signalArguments[0][0], items[1].url) } } } ./tests/unittests/qml/tst_SettingsPage.qml0000644000015600001650000000460612703462031021100 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import webbrowserapp.private 0.1 import webbrowsertest.private 0.1 import "../../../src/app/webbrowser" Item { width: 400 height: 600 property var settingsPage: settingsPageLoader.item Loader { id: settingsPageLoader anchors.fill: parent active: false sourceComponent: SettingsPage { anchors.fill: parent // NOTE: the following properties are not necessary for the tests // currently in this file, but if we don't provide them a lot of // warnings will be generated. // Ideally either more tests that use them will be added or the code // in SettingsPage will be refactored to cope with the missing // settings. settingsObject: QtObject { property url homepage property string searchEngine property int newTabDefaultSection: 0 } } } WebbrowserTestCase { name: "TestSettingsPage" when: windowShown function init() { settingsPageLoader.active = true waitForRendering(settingsPageLoader.item) } function cleanup() { settingsPageLoader.active = false } function activateSettingsItem(itemName, pageName) { var item = findChild(settingsPage, itemName) clickItem(item) var page = findChild(settingsPage, pageName) waitForRendering(page) return page } function test_goToMediaAccessPage() { activateSettingsItem("privacy", "privacySettings") return activateSettingsItem("privacy.mediaAccess", "mediaAccessSettings") } } } ./tests/unittests/qml/tst_UbuntuWebView02.qml0000644000015600001650000000664512703462031021425 0ustar jenkinsjenkins/* * Copyright 2013-2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtTest 1.0 import Ubuntu.Test 1.0 import Ubuntu.Components 1.3 import Ubuntu.Web 0.2 Item { id: root width: 200 height: 200 Component { id: webviewComponent WebView { anchors.fill: parent } } ActionList { id: actionList Action { id: action1 } Action { id: action2 } } readonly property string htmlWithHyperlink: '
' UbuntuTestCase { name: "UbuntuWebView02" when: windowShown property var webview: null function init() { webview = webviewComponent.createObject(root) } function cleanup() { webview.destroy() } function test_context_singleton() { var other = webviewComponent.createObject(root) compare(other.context, webview.context) other.destroy() } function rightClickWebview() { var center = centerOf(webview) mouseClick(webview, center.x, center.y, Qt.RightButton) // give the context menu a chance to appear before carrying on wait(500) } function getContextMenu() { return findChild(webview, "contextMenu") } function dismissContextMenu() { var center = centerOf(webview) mouseClick(webview, center.x, center.y) wait(500) compare(getContextMenu(), null) } function test_no_contextual_actions() { webview.loadHtml(root.htmlWithHyperlink, "file:///") tryCompare(webview, "loading", false) rightClickWebview() compare(getContextMenu(), null) } function test_contextual_actions() { webview.contextualActions = actionList webview.loadHtml(root.htmlWithHyperlink, "file:///") tryCompare(webview, "loading", false) rightClickWebview() compare(getContextMenu().actions, actionList) compare(webview.contextualData.href, "http://example.org/") dismissContextMenu() compare(webview.contextualData.href, "") } function test_contextual_actions_all_disabled() { webview.contextualActions = actionList action1.enabled = false action2.enabled = false webview.loadHtml(root.htmlWithHyperlink, "file:///") tryCompare(webview, "loading", false) rightClickWebview() compare(getContextMenu(), null) action1.enabled = true action2.enabled = true } } } ./tests/unittests/qml/tst_BookmarksViewWide.qml0000644000015600001650000000627212703462031022100 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtTest 1.0 import "../../../src/app/webbrowser" import webbrowserapp.private 0.1 Item { id: root width: 800 height: 600 BookmarksViewWide { id: view anchors.fill: parent homepageUrl: "http://example.com/homepage" } SignalSpy { id: bookmarkEntryClickedSpy target: view signalName: "bookmarkEntryClicked" } SignalSpy { id: doneSpy target: view signalName: "done" } SignalSpy { id: newTabClickedSpy target: view signalName: "newTabClicked" } WebbrowserTestCase { name: "BookmarksViewWide" when: windowShown function init() { BookmarksModel.databasePath = ":memory:" populate() view.forceActiveFocus() compare(bookmarkEntryClickedSpy.count, 0) compare(doneSpy.count, 0) compare(newTabClickedSpy.count, 0) } function populate() { BookmarksModel.add("http://example.com", "Example", "", "") BookmarksModel.add("http://example.org/a", "Example a", "", "FolderA") BookmarksModel.add("http://example.org/b", "Example b", "", "FolderB") compare(BookmarksModel.count, 3) } function cleanup() { BookmarksModel.databasePath = "" bookmarkEntryClickedSpy.clear() doneSpy.clear() newTabClickedSpy.clear() } function test_done() { var button = findChild(view, "doneButton") clickItem(button) compare(doneSpy.count, 1) } function test_new_tab() { var action = findChild(view, "newTabAction") clickItem(action) compare(newTabClickedSpy.count, 1) } function test_click_bookmark() { var items = getListItems(findChild(view, "bookmarksList"), "bookmarkItem") clickItem(items[0]) compare(bookmarkEntryClickedSpy.count, 1) compare(bookmarkEntryClickedSpy.signalArguments[0][0], view.homepageUrl) clickItem(items[1]) compare(bookmarkEntryClickedSpy.count, 2) compare(bookmarkEntryClickedSpy.signalArguments[1][0], "http://example.com") } function test_delete_bookmark() { var bookmark = getListItems(findChild(view, "bookmarksList"), "bookmarkItem")[1] swipeToDeleteAndConfirm(bookmark) tryCompare(BookmarksModel, "count", 2) } } } ./tests/unittests/qml/tst_UrlUtils.qml0000644000015600001650000000735612703462031020273 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtTest 1.0 import "../../../src/app/UrlUtils.js" as UrlUtils TestCase { name: "UrlUtils" function test_extractAuthority_data() { return [ {url: "", authority: ""}, {url: "http://example.org/", authority: "example.org"}, {url: "http://www.example.org/", authority: "www.example.org"}, {url: "http://www.example.org/foo/bar", authority: "www.example.org"}, {url: "http://example.org:2442/foo", authority: "example.org:2442"}, {url: "http://user:pwd@example.org/", authority: "user:pwd@example.org"}, {url: "http://user:pwd@example.org:2442/", authority: "user:pwd@example.org:2442"}, {url: "ftp://user:pwd@example.org:21/foo/bar", authority: "user:pwd@example.org:21"} ] } function test_extractAuthority(data) { compare(UrlUtils.extractAuthority(data.url), data.authority) } function test_extractHost_data() { return [ {url: "http://example.org/", host: "example.org"}, {url: "http://www.example.org/", host: "www.example.org"}, {url: "http://www.example.org/foo/bar", host: "www.example.org"}, {url: "http://example.org:2442/foo", host: "example.org"}, {url: "http://user:pwd@example.org/", host: "example.org"}, {url: "http://user:pwd@example.org:2442/", host: "example.org"}, {url: "ftp://user:pwd@example.org:21/foo/bar", host: "example.org"} ] } function test_extractHost(data) { compare(UrlUtils.extractHost(data.url), data.host) } function test_removeScheme_data() { return [ {url: "http://example.org/", removed: "example.org/"}, {url: "file://user:pwd@example.org:2442/", removed: "user:pwd@example.org:2442/"}, {url: "file:///home/foo/bar.txt", removed: "/home/foo/bar.txt"}, {url: "ht+tp://www.example.org/", removed: "www.example.org/"}, {url: "www.example.org", removed: "www.example.org"}, ] } function test_removeScheme(data) { compare(UrlUtils.removeScheme(data.url), data.removed) } function test_looksLikeAUrl_data() { return [ {url: "", looksLike: false}, {url: "http://example.org/", looksLike: true}, {url: "example.org", looksLike: true}, {url: "http://www.example.org?q=foo bar", looksLike: false}, {url: "about:blank", looksLike: true}, {url: "file:///usr/foo/bar", looksLike: true}, {url: "hello://my/name/is/", looksLike: true}, {url: "192.168.1.0", looksLike: true} ] } function test_looksLikeAUrl(data) { compare(UrlUtils.looksLikeAUrl(data.url), data.looksLike) } function test_fixUrl_data() { return [ {url: "About:BLANK", fixed: "about:blank"}, {url: "/usr/bin/", fixed: "file:///usr/bin/"}, {url: "example.org", fixed: "http://example.org"} ] } function test_fixUrl(data) { compare(UrlUtils.fixUrl(data.url), data.fixed) } } ./tests/unittests/qml/tst_BookmarksView.qml0000644000015600001650000001421212703462031021260 0ustar jenkinsjenkins/* * Copyright 2015-2016 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtQuick.Window 2.2 import QtTest 1.0 import "../../../src/app/webbrowser" import webbrowserapp.private 0.1 FocusScope { id: root focus: true width: 300 height: 500 BookmarksView { id: view anchors.fill: parent focus: true homepageUrl: "http://example.com/homepage" } SignalSpy { id: bookmarkEntryClickedSpy target: view signalName: "bookmarkEntryClicked" } SignalSpy { id: doneSpy target: view signalName: "done" } SignalSpy { id: newTabClickedSpy target: view signalName: "newTabClicked" } WebbrowserTestCase { name: "BookmarksView" when: windowShown function init() { BookmarksModel.databasePath = ":memory:" populate() verify(view.activeFocus) compare(bookmarkEntryClickedSpy.count, 0) compare(doneSpy.count, 0) compare(newTabClickedSpy.count, 0) } function populate() { BookmarksModel.add("http://example.com", "Example", "", "") BookmarksModel.add("http://example.org/a", "Example a", "", "FolderA") BookmarksModel.add("http://example.org/b", "Example b", "", "FolderB") compare(BookmarksModel.count, 3) } function cleanup() { BookmarksModel.databasePath = "" bookmarkEntryClickedSpy.clear() doneSpy.clear() newTabClickedSpy.clear() } function test_done() { var button = findChild(view, "doneButton") clickItem(button) compare(doneSpy.count, 1) } function test_new_tab() { var action = findChild(view, "newTabAction") clickItem(action) compare(newTabClickedSpy.count, 1) } function test_click_bookmark() { var items = getListItems(findChild(view, "bookmarksFolderListView"), "bookmarkFolderDelegateLoader") clickItem(findChild(items[0], "urlDelegate_0")) compare(bookmarkEntryClickedSpy.count, 1) compare(bookmarkEntryClickedSpy.signalArguments[0][0], view.homepageUrl) clickItem(findChild(items[0], "urlDelegate_1")) compare(bookmarkEntryClickedSpy.count, 2) compare(bookmarkEntryClickedSpy.signalArguments[1][0], "http://example.com") } function test_delete_bookmark() { var items = getListItems(findChild(view, "bookmarksFolderListView"), "bookmarkFolderDelegateLoader") var bookmark = findChild(items[0], "urlDelegate_1") swipeToDeleteAndConfirm(bookmark) tryCompare(BookmarksModel, "count", 2) } function test_keyboard_navigation() { var listview = findChild(view, "bookmarksFolderListView") waitForRendering(listview) verify(listview.activeFocus) var firstHeader = findChild(listview, "bookmarkFolderHeader") verify(firstHeader.activeFocus) var firstFolder = firstHeader.parent compare(firstFolder.folderName, "") verify(firstFolder.expanded) keyClick(Qt.Key_Up) verify(firstHeader.activeFocus) keyClick(Qt.Key_Space) verify(!firstFolder.expanded) keyClick(Qt.Key_Space) verify(firstFolder.expanded) keyClick(Qt.Key_Up) verify(firstHeader.activeFocus) keyClick(Qt.Key_Down) verify(findChild(firstFolder, "urlDelegate_0").activeFocus) keyClick(Qt.Key_Enter) compare(bookmarkEntryClickedSpy.count, 1) compare(bookmarkEntryClickedSpy.signalArguments[0][0], "http://example.com/homepage") keyClick(Qt.Key_Down) verify(findChild(firstFolder, "urlDelegate_1").activeFocus) keyClick(Qt.Key_Return) compare(bookmarkEntryClickedSpy.count, 2) compare(bookmarkEntryClickedSpy.signalArguments[1][0], "http://example.com") keyClick(Qt.Key_Delete) compare(BookmarksModel.count, 2) verify(findChild(firstFolder, "urlDelegate_0").activeFocus) keyClick(Qt.Key_Down) var secondHeader = root.Window.activeFocusItem compare(secondHeader.objectName, "bookmarkFolderHeader") var secondFolder = secondHeader.parent compare(secondFolder.folderName, "FolderA") verify(!secondFolder.expanded) keyClick(Qt.Key_Down) var thirdHeader = root.Window.activeFocusItem compare(thirdHeader.objectName, "bookmarkFolderHeader") var thirdFolder = thirdHeader.parent compare(thirdFolder.folderName, "FolderB") verify(!thirdFolder.expanded) keyClick(Qt.Key_Down) verify(thirdHeader.activeFocus) keyClick(Qt.Key_Delete) verify(thirdHeader.activeFocus) keyClick(Qt.Key_Space) verify(thirdFolder.expanded) keyClick(Qt.Key_Down) verify(findChild(thirdFolder, "urlDelegate_0").activeFocus) keyClick(Qt.Key_Delete) compare(BookmarksModel.count, 1) verify(!thirdFolder.active) verify(secondFolder.activeFocus) keyClick(Qt.Key_Space) verify(secondFolder.expanded) } } } ./tests/unittests/qml/WebbrowserTestCase.qml0000644000015600001650000000427212703462031021365 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import Ubuntu.Test 1.0 UbuntuTestCase { function clickItem(item, button) { if (button === undefined) button = Qt.LeftButton var center = centerOf(item) mouseClick(item, center.x, center.y, button) } function longPressItem(item, button) { if (button === undefined) button = Qt.LeftButton var center = centerOf(item) mouseLongPress(item, center.x, center.y, button) mouseRelease(item, center.x, center.y, button) } function getListItems(listview, itemName) { waitForRendering(listview) var items = [] if (listview) { // ensure all the delegates are created listview.cacheBuffer = listview.count * 1000 // In some cases the ListView might add other children to the // contentItem, so we filter the list of children to include // only actual delegates (names for delegates in this case // follow the pattern "name_index") var children = listview.contentItem.children for (var i = 0; i < children.length; i++) { if (children[i].objectName.indexOf(itemName) == 0) { items.push(children[i]) } } } return items } function swipeToDeleteAndConfirm(listitem) { flick(listitem, listitem.width / 10, listitem.height / 2, listitem.width / 2, 0) var confirm = findChild(listitem, "actionbutton_leadingAction.delete") clickItem(confirm) } } ./tests/unittests/qml/tst_QmlTests.cpp0000644000015600001650000001547412703462031020255 0ustar jenkinsjenkins/* * Copyright 2013-2016 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include #include #include #include // local #include "bookmarks-model.h" #include "bookmarks-folderlist-model.h" #include "favicon-fetcher.h" #include "file-operations.h" #include "history-domain-model.h" #include "history-domainlist-model.h" #include "history-model.h" #include "history-lastvisitdatelist-model.h" #include "limit-proxy-model.h" #include "searchengine.h" #include "tabs-model.h" #include "text-search-filter-model.h" #include "Unity/InputInfo/qdeclarativeinputdevicemodel_p.h" class TestContext : public QObject { Q_OBJECT Q_PROPERTY(QString testDir1 READ testDir1 CONSTANT) Q_PROPERTY(QString testDir2 READ testDir2 CONSTANT) public: explicit TestContext(QObject* parent=0) : QObject(parent) {} QString testDir1() const { return m_testDir1.path(); } QString testDir2() const { return m_testDir2.path(); } Q_INVOKABLE bool writeSearchEngineDescription( const QString& path, const QString& filename, const QString& name, const QString& description, const QString& urlTemplate) { QFile file(QDir(path).absoluteFilePath(QString("%1.xml").arg(filename))); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(&file); out << ""; out << "" << name << ""; out << "" << description << ""; out << ""; out << ""; file.close(); return true; } else { return false; } } Q_INVOKABLE bool writeInvalidSearchEngineDescription(const QString& path, const QString& filename) { QFile file(QDir(path).absoluteFilePath(QString("%1.xml").arg(filename))); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(&file); out << "invalid"; file.close(); return true; } else { return false; } } Q_INVOKABLE bool deleteSearchEngineDescription(const QString& path, const QString& filename) { return QFile(QDir(path).absoluteFilePath(QString("%1.xml").arg(filename))).remove(); } Q_INVOKABLE bool createFile(const QString& filePath) { // create all the directories necessary for the file to be created QFileInfo fileInfo(filePath); if (!QFileInfo::exists(fileInfo.path())) { QDir::root().mkpath(fileInfo.path()); } QFile file(fileInfo.absoluteFilePath()); return file.open(QIODevice::WriteOnly | QIODevice::Text); } Q_INVOKABLE bool removeDirectory(const QString& path) { QDir dir(path); return dir.removeRecursively(); } private: QTemporaryDir m_testDir1; QTemporaryDir m_testDir2; }; class HistoryModelMock : public HistoryModel { Q_OBJECT public: static bool compareHistoryEntries(const HistoryEntry& a, const HistoryEntry& b) { return a.lastVisit < b.lastVisit; } Q_INVOKABLE int addByDate(const QUrl& url, const QString& title, const QDateTime& date) { int index = getEntryIndex(url); int visitsToAdd = 1; if (index == -1) { add(url, title, QString()); index = getEntryIndex(url); visitsToAdd = 0; } // Since this is useful only for testing and efficiency is not critical // we reorder the model and reset it every time we add a new item by date // to keep things simple. beginResetModel(); HistoryEntry entry = m_entries.takeAt(index); entry.lastVisit = date; entry.visits = entry.visits + visitsToAdd; m_entries.append(entry); std::sort(m_entries.begin(), m_entries.end(), compareHistoryEntries); endResetModel(); updateExistingEntryInDatabase(entry); return entry.visits; } }; #define MAKE_SINGLETON_FACTORY(type) \ static QObject* type##_singleton_factory(QQmlEngine* engine, QJSEngine* scriptEngine) { \ Q_UNUSED(engine); \ Q_UNUSED(scriptEngine); \ return new type(); \ } MAKE_SINGLETON_FACTORY(FileOperations) MAKE_SINGLETON_FACTORY(BookmarksModel) MAKE_SINGLETON_FACTORY(HistoryModelMock) MAKE_SINGLETON_FACTORY(TestContext) int main(int argc, char** argv) { const char* commonUri = "webbrowsercommon.private"; qmlRegisterType(commonUri, 0, 1, "FaviconFetcher"); const char* browserUri = "webbrowserapp.private"; qmlRegisterType(browserUri, 0, 1, "SearchEngine"); qmlRegisterType(browserUri, 0, 1, "TabsModel"); qmlRegisterSingletonType(browserUri, 0, 1, "BookmarksModel", BookmarksModel_singleton_factory); qmlRegisterType(browserUri, 0, 1, "BookmarksFolderListModel"); qmlRegisterSingletonType(browserUri, 0, 1, "HistoryModel", HistoryModelMock_singleton_factory); qmlRegisterType(browserUri, 0, 1, "HistoryDomainModel"); qmlRegisterType(browserUri, 0, 1, "HistoryDomainListModel"); qmlRegisterType(browserUri, 0, 1, "HistoryLastVisitDateListModel"); qmlRegisterType(browserUri, 0, 1, "LimitProxyModel"); qmlRegisterType(browserUri, 0, 1, "TextSearchFilterModel"); qmlRegisterSingletonType(browserUri, 0, 1, "FileOperations", FileOperations_singleton_factory); const char* testUri = "webbrowsertest.private"; qmlRegisterSingletonType(testUri, 0, 1, "TestContext", TestContext_singleton_factory); const char* inputInfoUri = "Unity.InputInfo"; qmlRegisterType(inputInfoUri, 0, 1, "InputDeviceModel"); qmlRegisterType(inputInfoUri, 0, 1, "InputInfo"); return quick_test_main(argc, argv, "QmlTests", nullptr); } #include "tst_QmlTests.moc" ./tests/unittests/qml/tst_BrowserTab.qml0000644000015600001650000001253212703462031020552 0ustar jenkinsjenkins/* * Copyright 2014-2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtTest 1.0 import "../../../src/app/webbrowser" import webbrowserapp.private 0.1 Item { id: root width: 200 height: 200 Component { id: tabComponent BrowserTab { id: tab anchors.fill: parent webviewComponent: Item { anchors.fill: parent property url url property string title property url icon property var request property string currentState property bool incognito: tab.incognito property int reloaded: 0 function reload() { reloaded++ } } readonly property bool webviewPresent: webview } } SignalSpy { id: previewSavedSpy target: PreviewManager signalName: "previewSaved" } TestCase { name: "BrowserTab" when: windowShown function init() { previewSavedSpy.clear() } function test_unique_ids() { var tab = tabComponent.createObject(root) var tab2 = tabComponent.createObject(root) verify(tab.uniqueId) verify(tab2.uniqueId) verify(tab.uniqueId !== tab2.uniqueId) tab.destroy() tab2.destroy() } function test_load_unload() { var tab = tabComponent.createObject(root) verify(!tab.webviewPresent) tab.initialUrl = "http://example.org" tab.load() tryCompare(tab, 'webviewPresent', true) compare(tab.webview.url, "http://example.org") tab.webview.url = "http://ubuntu.com" tab.webview.title = "Ubuntu" tab.webview.currentState = "foobar" tab.unload() tryCompare(tab, 'webviewPresent', false) compare(tab.initialUrl, "http://ubuntu.com") compare(tab.initialTitle, "Ubuntu") compare(tab.restoreState, "foobar") tab.destroy() } function test_reload() { var tab = tabComponent.createObject(root) verify(!tab.webviewPresent) tab.initialUrl = "http://example.org" tab.reload() tryCompare(tab, 'webviewPresent', true) compare(tab.webview.reloaded, 0) tab.reload() verify(tab.webviewPresent) compare(tab.webview.reloaded, 1) tab.destroy() } function test_create_with_request() { var tab = tabComponent.createObject(root, {'request': "foobar"}) tryCompare(tab, 'webviewPresent', true) verify(tab.webviewPresent) compare(tab.webview.request, "foobar") tab.destroy() } function test_save_preview() { var tab = tabComponent.createObject(root) tab.initialUrl = "http://example.org" tab.load() tryCompare(tab, 'webviewPresent', true) tab.current = true tab.current = false tryCompare(previewSavedSpy, "count", 1) verify(!tab.visible) compare(previewSavedSpy.signalArguments[0][0], tab.initialUrl) compare(previewSavedSpy.signalArguments[0][1], Qt.resolvedUrl(PreviewManager.previewPathFromUrl(tab.initialUrl))) compare(tab.preview, Qt.resolvedUrl(PreviewManager.previewPathFromUrl(tab.initialUrl))) tab.destroy() } function test_no_save_preview_when_incognito() { var tab = tabComponent.createObject(root) tab.incognito = true tab.initialUrl = "http://example.org" tab.load() tryCompare(tab, 'webviewPresent', true) tab.current = true tab.current = false // this does not fully guarantee the event won't be emitted later, // but it is a reasonable delay and certainly better than nothing wait(250) compare(previewSavedSpy.count, 0) compare(tab.preview, "") tab.destroy() } function test_delete_preview_on_close() { var url = "http://example.org" var path = Qt.resolvedUrl(PreviewManager.previewPathFromUrl(url)) var tab = tabComponent.createObject(root) tab.initialUrl = url tab.load() tryCompare(tab, 'webviewPresent', true) tab.current = true tab.current = false tryCompare(previewSavedSpy, "count", 1) verify(FileOperations.exists(path)) tab.close() verify(!FileOperations.exists(path)) tab.destroy() } } } ./tests/unittests/qml/tst_PreviewManager.qml0000644000015600001650000000712712703462031021420 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtTest 1.0 import Ubuntu.Test 1.0 import "../../../src/app/webbrowser" import webbrowserapp.private 0.1 import webbrowsertest.private 0.1 Item { id: root width: 800 height: 600 SignalSpy { id: previewSavedSpy target: PreviewManager signalName: "previewSaved" } QtObject { id: grabResultMock function saveToFile(path) { TestContext.createFile(path); return true } } QtObject { id: grabResultFailMock function saveToFile(path) { return false } } UbuntuTestCase { name: "PreviewManager" when: windowShown property string baseUrl: "http://example.com/" function initTestCase() { HistoryModel.databasePath = ":memory:" } function init() { previewSavedSpy.clear() verify(TestContext.removeDirectory(PreviewManager.capturesDir)) } function populate(count, createPreviewFiles) { for (var i = 0; i < 11; i++) { var url = baseUrl + i HistoryModel.add(url, "Example Com" + i, "") if (createPreviewFiles) { var path = PreviewManager.previewPathFromUrl(url) TestContext.createFile(path) } } } function cleanup() { HistoryModel.clearAll() } function test_topsites_not_deleted() { populate(11, true) for (var i = 0; i < 11; i++) { var url = baseUrl + i PreviewManager.checkDelete(url) var path = Qt.resolvedUrl(PreviewManager.previewPathFromUrl(url)) // verify that only the item that is outside of the top 10 list // gets deleted if (i < 10) verify(FileOperations.exists(path)) else verify(!FileOperations.exists(path)) } } function test_save_preview() { var file = Qt.resolvedUrl(PreviewManager.previewPathFromUrl(baseUrl)) PreviewManager.saveToDisk(grabResultMock, baseUrl) verify(FileOperations.exists(file)) compare(previewSavedSpy.count, 1) compare(previewSavedSpy.signalArguments[0][0], baseUrl) compare(previewSavedSpy.signalArguments[0][1], file) } function test_save_preview_fail() { var path = PreviewManager.previewPathFromUrl(baseUrl) var file = Qt.resolvedUrl(path) ignoreWarning("Failed to save preview to disk for %1 (path is %2)".arg(baseUrl).arg(path)) PreviewManager.saveToDisk(grabResultFailMock, baseUrl) verify(!FileOperations.exists(file)) compare(previewSavedSpy.count, 1) compare(previewSavedSpy.signalArguments[0][0], baseUrl) compare(previewSavedSpy.signalArguments[0][1], "") } } } ./tests/unittests/qml/tst_HistoryViewWide.qml0000644000015600001650000004126312703462031021610 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtTest 1.0 import Ubuntu.Components 1.3 import webbrowserapp.private 0.1 import webbrowsertest.private 0.1 import "../../../src/app/webbrowser" Item { id: root width: 700 height: 500 property var historyViewWide: historyViewWideLoader.item property int ctrlFCaptured: 0 Keys.onPressed: { if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_F) ctrlFCaptured++ } Loader { id: historyViewWideLoader anchors.fill: parent active: false focus: true sourceComponent: HistoryViewWide { focus: true } } SignalSpy { id: doneSpy target: historyViewWide signalName: "done" } SignalSpy { id: newTabRequestedSpy target: historyViewWide signalName: "newTabRequested" } SignalSpy { id: historyEntryClickedSpy target: historyViewWide signalName: "historyEntryClicked" } WebbrowserTestCase { name: "HistoryViewWide" when: windowShown function longPressItem(item) { var center = centerOf(item) mousePress(item, center.x, center.y) mouseRelease(item, center.x, center.y, Qt.LeftButton, Qt.NoModifier, 2000) } function initTestCase() { HistoryModel.databasePath = ":memory:" } function init() { historyViewWideLoader.active = true waitForRendering(historyViewWideLoader.item) for (var i = 0; i < 3; ++i) { HistoryModel.add("http://example.org/" + i, "Example Domain " + i, "") } historyViewWide.loadModel() var urlsList = findChild(historyViewWide, "urlsListView") waitForRendering(urlsList) tryCompare(urlsList, "count", 3) } function cleanup() { HistoryModel.clearAll() historyViewWideLoader.active = false ctrlFCaptured = 0 } function test_done_button() { var doneButton = findChild(historyViewWide, "doneButton") verify(doneButton != null) doneSpy.clear() clickItem(doneButton) compare(doneSpy.count, 1) } function test_new_tab_button() { var newTabButton = findChild(historyViewWide, "newTabButton") verify(newTabButton != null) doneSpy.clear() newTabRequestedSpy.clear() clickItem(newTabButton) compare(newTabRequestedSpy.count, 1) compare(doneSpy.count, 1) } function test_history_entry_clicked() { var urlsList = findChild(historyViewWide, "urlsListView") compare(urlsList.count, 3) historyEntryClickedSpy.clear() clickItem(urlsList.children[0]) compare(historyEntryClickedSpy.count, 1) var args = historyEntryClickedSpy.signalArguments[0] var entry = urlsList.model.get(0) compare(args[0], entry.url) } function test_selection_mode() { var urlsList = findChild(historyViewWide, "urlsListView") compare(urlsList.count, 3) var backButton = findChild(historyViewWide, "backButton") var selectButton = findChild(historyViewWide, "selectButton") var deleteButton = findChild(historyViewWide, "deleteButton") verify(!backButton.visible) verify(!selectButton.visible) verify(!deleteButton.visible) longPressItem(urlsList.children[0]) verify(backButton.visible) verify(selectButton.visible) verify(deleteButton.visible) clickItem(backButton) verify(!backButton.visible) verify(!selectButton.visible) verify(!deleteButton.visible) } function test_toggle_select_button() { var urlsList = findChild(historyViewWide, "urlsListView") compare(urlsList.count, 3) longPressItem(urlsList.children[0]) var selectedIndices = urlsList.ViewItems.selectedIndices compare(selectedIndices.length, 1) var selectButton = findChild(historyViewWide, "selectButton") clickItem(selectButton) compare(selectedIndices.length, urlsList.count) clickItem(selectButton) var backButton = findChild(historyViewWide, "backButton") clickItem(backButton) } function test_delete_button() { var urlsList = findChild(historyViewWide, "urlsListView") compare(urlsList.count, 3) var deletedUrl = urlsList.model.get(0).url longPressItem(urlsList.children[0]) var deleteButton = findChild(historyViewWide, "deleteButton") clickItem(deleteButton) compare(urlsList.count, 2) for (var i = 0; i < urlsList.count; ++i) { verify(urlsList.model.get(i).url != deletedUrl) } } function test_keyboard_navigation_between_lists() { var lastVisitDateList = findChild(historyViewWide, "lastVisitDateListView") var urlsList = findChild(historyViewWide, "urlsListView") verify(!lastVisitDateList.activeFocus) verify(urlsList.activeFocus) keyClick(Qt.Key_Left) verify(lastVisitDateList.activeFocus) verify(!urlsList.activeFocus) keyClick(Qt.Key_Right) verify(urlsList.activeFocus) } function test_search_button() { var searchQuery = findChild(historyViewWide, "searchQuery") verify(!searchQuery.visible) var searchButton = findChild(historyViewWide, "searchButton") verify(searchButton.visible) clickItem(searchButton) verify(!searchButton.visible) verify(searchQuery.visible) verify(searchQuery.activeFocus) compare(searchQuery.text, "") var urlsList = findChild(historyViewWide, "urlsListView") compare(urlsList.count, 3) typeString("2") compare(urlsList.count, 1) var backButton = findChild(historyViewWide, "backButton") verify(backButton.visible) clickItem(backButton) verify(!backButton.visible) verify(!searchQuery.visible) verify(searchButton.visible) compare(urlsList.count, 3) clickItem(searchButton) compare(searchQuery.text, "") } function test_keyboard_navigation_for_search() { var urlsList = findChild(historyViewWide, "urlsListView") verify(urlsList.activeFocus) keyClick(Qt.Key_F, Qt.ControlModifier) var searchQuery = findChild(historyViewWide, "searchQuery") verify(searchQuery.activeFocus) keyClick(Qt.Key_Escape) verify(urlsList.activeFocus) keyClick(Qt.Key_F, Qt.ControlModifier) keyClick(Qt.Key_Down) verify(urlsList.activeFocus) keyClick(Qt.Key_Up) verify(searchQuery.activeFocus) keyClick(Qt.Key_Down) keyClick(Qt.Key_Left) keyClick(Qt.Key_Up) verify(searchQuery.activeFocus) } function test_ctrl_f_during_search_returns_to_query() { var urlsList = findChild(historyViewWide, "urlsListView") var datesList = findChild(historyViewWide, "lastVisitDateListView") var searchQuery = findChild(historyViewWide, "searchQuery") verify(urlsList.activeFocus) verify(!historyViewWide.searchMode) keyClick(Qt.Key_F, Qt.ControlModifier) verify(searchQuery.activeFocus) verify(historyViewWide.searchMode) // CTRL+F jumps back to the search box from the urls list... keyClick(Qt.Key_Down) verify(urlsList.activeFocus) keyClick(Qt.Key_F, Qt.ControlModifier) verify(searchQuery.activeFocus) // ...and from the dates list keyClick(Qt.Key_Down) keyClick(Qt.Key_Left) verify(datesList.activeFocus) keyClick(Qt.Key_F, Qt.ControlModifier) verify(searchQuery.activeFocus) } function test_ctrl_f_during_select_is_swallowed() { var urlsList = findChild(historyViewWide, "urlsListView") longPressItem(urlsList.children[0]) verify(historyViewWide.selectMode) keyClick(Qt.Key_F, Qt.ControlModifier) wait(50) // make sure event loop has processed compare(ctrlFCaptured, 0) verify(historyViewWide.selectMode) } function test_history_entry_activated_by_keyboard() { var urlsList = findChild(historyViewWide, "urlsListView") compare(urlsList.count, 3) historyEntryClickedSpy.clear() keyClick(Qt.Key_Enter) compare(historyEntryClickedSpy.count, 1) var args = historyEntryClickedSpy.signalArguments[0] var entry = urlsList.model.get(0) compare(String(args[0]), String(entry.url)) // now try the same during a search historyEntryClickedSpy.clear() keyClick(Qt.Key_F, Qt.ControlModifier) typeString("dom") keyClick(Qt.Key_Down) keyClick(Qt.Key_Enter) compare(historyEntryClickedSpy.count, 1) args = historyEntryClickedSpy.signalArguments[0] entry = urlsList.model.get(0) compare(String(args[0]), String(entry.url)) } function test_search_highlight() { function wraphtml(text) { return "%1".arg(text) } function highlight(term) { return "%2".arg("#752571").arg(term) } var searchButton = findChild(historyViewWide, "searchButton") var searchQuery = findChild(historyViewWide, "searchQuery") var urlsList = findChild(historyViewWide, "urlsListView") clickItem(searchButton) var term = "2" typeString(term) var items = getListItems(urlsList, "historyDelegate") compare(items.length, 1) compare(items[0].title, wraphtml("Example Domain " + highlight(term))) var backButton = findChild(historyViewWide, "backButton") clickItem(backButton) clickItem(searchButton) var terms = ["1", "Example"] typeString(terms.join(" ")) items = getListItems(urlsList, "historyDelegate") compare(items.length, 1) compare(items[0].title, wraphtml("%1 Domain %0" .arg(highlight(terms[0])) .arg(highlight(terms[1])))) } function test_search_updates_dates_list() { function getDateItem(date) { var lastVisitDateList = findChild(historyViewWide, "lastVisitDateListView") var dates = getListItems(lastVisitDateList, "lastVisitDateDelegate") var items = dates.filter(function(item) { return item.lastVisitDate.valueOf() === date.valueOf() }) if (items.length > 0) return items.pop() else return null } function returnToDatesList() { keyClick(Qt.Key_Down) keyClick(Qt.Key_Left) } var searchQuery = findChild(historyViewWide, "searchQuery") var today = new Date() today = new Date(today.getFullYear(), today.getMonth(), today.getDate()) var youngest = new Date(1912, 6, 23) var model = HistoryModel model.addByDate("https://en.wikipedia.org/wiki/Alan_Turing", "Alan Turing", youngest) model.addByDate("https://en.wikipedia.org/wiki/Alonzo_Church", "Alonzo Church", new Date(1903, 6, 14)) var lastVisitDateList = findChild(historyViewWide, "lastVisitDateListView") var dates = getListItems(lastVisitDateList, "lastVisitDateDelegate") var urlsListView = findChild(historyViewWide, "urlsListView") var urls = getListItems(urlsListView, "historyDelegate") compare(dates.length, 4) compare(urls.length, 5) // select a date that has search results in it and verify it is // still the currently selected one after the search. var testItem = getDateItem(youngest) clickItem(testItem) verify(testItem.activeFocus) keyClick(Qt.Key_F, Qt.ControlModifier) typeString("Alan") urls = getListItems(urlsListView, "historyDelegate") compare(urls.length, 1) returnToDatesList() verify(testItem.activeFocus) // change the search terms so that it will display more items, but // since we have a selected date, we will see only one keyClick(Qt.Key_F, Qt.ControlModifier) keyClick(Qt.Key_Backspace) keyClick(Qt.Key_Backspace) compare(searchQuery.text, "Al") returnToDatesList() verify(testItem.activeFocus) urls = getListItems(urlsListView, "historyDelegate") compare(urls.length, 1) // change the search terms so that the current date will not be // included in the results keyClick(Qt.Key_F, Qt.ControlModifier) typeString("onzo") compare(searchQuery.text, "Alonzo") returnToDatesList() testItem = getDateItem(youngest) compare(testItem, null) urls = getListItems(urlsListView, "historyDelegate") compare(urls.length, 1) // verify that the current item has reverted to the first in the // dates list ("all dates") compare(lastVisitDateList.currentIndex, 0) // if widen the search again now, we should see both results again keyClick(Qt.Key_F, Qt.ControlModifier) keyClick(Qt.Key_Backspace) keyClick(Qt.Key_Backspace) keyClick(Qt.Key_Backspace) keyClick(Qt.Key_Backspace) compare(searchQuery.text, "Al") urls = getListItems(urlsListView, "historyDelegate") compare(urls.length, 2) } function test_delete_key_at_urls_list_view() { var urlsList = findChild(historyViewWide, "urlsListView") keyClick(Qt.Key_Right) verify(urlsList.activeFocus) compare(urlsList.count, 3) keyClick(Qt.Key_Delete) compare(urlsList.count, 2) // now try the same while in a search keyClick(Qt.Key_F, Qt.ControlModifier) typeString("dom") keyClick(Qt.Key_Down) keyClick(Qt.Key_Delete) compare(urlsList.count, 1) } function test_delete_key_at_last_visit_date() { var lastVisitDateList = findChild(historyViewWide, "lastVisitDateListView") var urlsList = findChild(historyViewWide, "urlsListView") keyClick(Qt.Key_Left) verify(lastVisitDateList.activeFocus) compare(lastVisitDateList.currentIndex, 0) keyClick(Qt.Key_Down) compare(lastVisitDateList.currentIndex, 1) compare(urlsList.count, 3) keyClick(Qt.Key_Delete) compare(urlsList.count, 0) } function test_delete_key_at_all_history() { var lastVisitDateList = findChild(historyViewWide, "lastVisitDateListView") var urlsList = findChild(historyViewWide, "urlsListView") keyClick(Qt.Key_Left) verify(lastVisitDateList.activeFocus) compare(lastVisitDateList.currentIndex, 0) compare(urlsList.count, 3) keyClick(Qt.Key_Delete) compare(urlsList.count, 0) } } } ./tests/unittests/qml/tst_WebProcessMonitor.qml0000644000015600001650000000651412703462031022127 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtTest 1.0 import com.canonical.Oxide 1.8 as Oxide import "../../../src/app" WebProcessMonitor { id: monitor Item { id: webviewMock property int webProcessStatus property int reloadCalled function reload() { webProcessStatus = Oxide.WebView.WebProcessRunning reloadCalled++ } } TestCase { name: "WebProcessMonitor" function init() { webviewMock.webProcessStatus = Oxide.WebView.WebProcessRunning webviewMock.reloadCalled = 0 } function test_no_webview() { monitor.webview = null compare(monitor.killedRetries, 0) verify(!monitor.killed) verify(!monitor.crashed) } function test_killed() { monitor.webview = webviewMock compare(monitor.killedRetries, 0) webviewMock.webProcessStatus = Oxide.WebView.WebProcessKilled verify(monitor.killed) verify(!monitor.crashed) tryCompare(monitor, "killedRetries", 1) tryCompare(webviewMock, "reloadCalled", 1) verify(!monitor.killed) verify(!monitor.crashed) compare(monitor.killedRetries, 1) webviewMock.webProcessStatus = Oxide.WebView.WebProcessKilled verify(monitor.killed) verify(!monitor.crashed) compare(monitor.killedRetries, 1) compare(webviewMock.reloadCalled, 1) } function test_crashed() { monitor.webview = webviewMock compare(monitor.killedRetries, 0) webviewMock.webProcessStatus = Oxide.WebView.WebProcessCrashed verify(!monitor.killed) verify(monitor.crashed) compare(monitor.killedRetries, 0) compare(webviewMock.reloadCalled, 0) webviewMock.webProcessStatus = Oxide.WebView.WebProcessRunning verify(!monitor.killed) verify(!monitor.crashed) compare(monitor.killedRetries, 0) compare(webviewMock.reloadCalled, 0) } function test_change_webview() { monitor.webview = webviewMock compare(monitor.killedRetries, 0) verify(!monitor.killed) verify(!monitor.crashed) webviewMock.webProcessStatus = Oxide.WebView.WebProcessKilled verify(monitor.killed) verify(!monitor.crashed) tryCompare(monitor, "killedRetries", 1) monitor.webview = null compare(monitor.killedRetries, 0) verify(!monitor.killed) verify(!monitor.crashed) } } } ./tests/unittests/qml/tst_NewTabViewWide.qml0000644000015600001650000001517512703462031021332 0ustar jenkinsjenkins/* * Copyright 2015-2016 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtTest 1.0 import "../../../src/app/webbrowser" import webbrowserapp.private 0.1 Item { id: root width: 800 height: 600 property NewTabViewWide view property var bookmarks property string homepage: "http://example.com/homepage" Component { id: viewComponent NewTabViewWide { anchors.fill: parent settingsObject: QtObject { property url homepage: root.homepage property int newTabDefaultSection: 0 } } } SignalSpy { id: historyEntryClickedSpy signalName: "historyEntryClicked" } SignalSpy { id: bookmarkClickedSpy signalName: "bookmarkClicked" } SignalSpy { id: bookmarkRemovedSpy signalName: "bookmarkRemoved" } WebbrowserTestCase { name: "NewTabViewWide" when: windowShown function init() { BookmarksModel.databasePath = ":memory:" HistoryModel.databasePath = ":memory:" view = viewComponent.createObject(root) populate() view.focus = true historyEntryClickedSpy.target = view historyEntryClickedSpy.clear() bookmarkClickedSpy.target = view bookmarkClickedSpy.clear() bookmarkRemovedSpy.target = view bookmarkRemovedSpy.clear() } function populate() { HistoryModel.add("http://example.com", "Example Com", "") HistoryModel.add("http://example.org", "Example Org", "") HistoryModel.add("http://example.net", "Example Net", "") BookmarksModel.add("http://example.com", "Example Com", "", "") BookmarksModel.add("http://example.org/bar", "Example Org Bar", "", "Folder B") BookmarksModel.add("http://example.org/foo", "Example Org Foo", "", "Folder B") BookmarksModel.add("http://example.net/a", "Example Net A", "", "Folder A") BookmarksModel.add("http://example.net/b", "Example Net B", "", "Folder A") } function cleanup() { BookmarksModel.databasePath = "" HistoryModel.databasePath = "" view.destroy() view = null } function goToBookmarks() { findChild(view, "sections").selectedIndex = 1 } function test_topsites_list() { // add 8 more top sites so that we are beyond the limit of 10 for (var i = 0; i < 8; i++) { HistoryModel.add("http://example.com/" + i, "Example Com " + i, "") } var items = getListItems(findChild(view, "topSitesList"), "topSiteItem") compare(items.length, 10) compare(items[0].title, "Example Com") compare(items[1].title, "Example Org") compare(items[2].title, "Example Net") for (var i = 0; i < 7; i++) { compare(items[i + 3].title, "Example Com " + i) } } function test_switch_sections_by_keyboard() { skip("Would fail due to UITK bug: http://pad.lv/1481233") var sections = findChild(view, "sections") var folders = findChild(view, "foldersList") var bookmarks = findChild(view, "bookmarksList") var topSites = findChild(view, "topSitesList") compare(sections.selectedIndex, 0) verify(topSites.visible) verify(!folders.visible) verify(!bookmarks.visible) keyClick(Qt.Key_Tab) compare(sections.selectedIndex, 1) verify(!topSites.visible) verify(folders.visible) verify(bookmarks.visible) keyClick(Qt.Key_Backtab) compare(sections.selectedIndex, 0) } function test_navigate_topsites_by_keyboard() { var items = getListItems(findChild(view, "topSitesList"), "topSiteItem") var list = findChild(view, "topSitesList") list.currentIndex = 0 keyClick(Qt.Key_Right) compare(list.currentIndex, 1) keyClick(Qt.Key_Right) compare(list.currentIndex, 2) keyClick(Qt.Key_Right) // ensure list does not wrap around compare(list.currentIndex, 2) keyClick(Qt.Key_Left) compare(list.currentIndex, 1) keyClick(Qt.Key_Left) compare(list.currentIndex, 0) keyClick(Qt.Key_Up) compare(list.currentIndex, 0) keyClick(Qt.Key_Left) compare(list.currentIndex, 0) } function test_activate_topsites_by_keyboard() { var items = getListItems(findChild(view, "topSitesList"), "topSiteItem") keyClick(Qt.Key_Return) compare(historyEntryClickedSpy.count, 1) compare(historyEntryClickedSpy.signalArguments[0][0], "http://example.com") keyClick(Qt.Key_Right) keyClick(Qt.Key_Return) compare(historyEntryClickedSpy.count, 2) compare(historyEntryClickedSpy.signalArguments[1][0], "http://example.org") } function test_activate_topsites_by_mouse() { var items = getListItems(findChild(view, "topSitesList"), "topSiteItem") clickItem(items[0]) compare(historyEntryClickedSpy.count, 1) compare(historyEntryClickedSpy.signalArguments[0][0], "http://example.com") clickItem(items[1]) compare(historyEntryClickedSpy.count, 2) compare(historyEntryClickedSpy.signalArguments[1][0], "http://example.org") } function test_remove_top_sites_by_keyboard() { var topSitesListView = findChild(view, "topSitesList") var previous = getListItems(topSitesListView, "topSiteItem") keyClick(Qt.Key_Delete) var items = getListItems(topSitesListView, "topSiteItem") compare(previous.length - 1, items.length) } } } ./tests/unittests/qml/tst_ChromeController.qml0000644000015600001650000003027512703462031021765 0ustar jenkinsjenkins/* * Copyright 2016 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtTest 1.0 import com.canonical.Oxide 1.7 as Oxide import "../../../src/app" ChromeController { id: controller Item { id: webviewMock property bool loading: false readonly property bool loadingState: loading property bool fullscreen: false property var locationBarController: QtObject { property bool animated: false property int mode: controller.defaultMode signal show(bool animate) } signal loadEvent(var event) } SignalSpy { id: showSpy target: webviewMock.locationBarController signalName: "show" } webview: webviewMock TestCase { name: "ChromeController" readonly property int modeAuto: Oxide.LocationBarController.ModeAuto readonly property int modeShown: Oxide.LocationBarController.ModeShown readonly property int modeHidden: Oxide.LocationBarController.ModeHidden function init() { controller.forceHide = false controller.forceShow = false controller.defaultMode = modeAuto webviewMock.loading = false webviewMock.fullscreen = false webviewMock.locationBarController.animated = false webviewMock.locationBarController.mode = controller.defaultMode showSpy.clear() } function test_change_webview_data() { return [ {forceHide: false, forceShow: false, fullscreen: false, mode: modeAuto, shown: true}, {forceHide: false, forceShow: true, fullscreen: false, mode: modeShown, shown: false}, {forceHide: false, forceShow: false, fullscreen: true, mode: modeAuto, shown: false}, {forceHide: false, forceShow: true, fullscreen: true, mode: modeShown, shown: false}, {forceHide: true, forceShow: false, fullscreen: true, mode: modeHidden, shown: false}, {forceHide: true, forceShow: true, fullscreen: false, mode: modeHidden, shown: false}, {forceHide: true, forceShow: true, fullscreen: true, mode: modeHidden, shown: false}, ] } function test_change_webview(data) { controller.webview = null controller.forceHide = data.forceHide controller.forceShow = data.forceShow webviewMock.fullscreen = data.fullscreen showSpy.clear() controller.webview = webviewMock compare(webviewMock.locationBarController.mode, data.mode) compare(showSpy.count, data.shown ? 1 : 0) } function test_change_forceHide_data() { return [ {forceShow: false, fullscreen: false, modes: [modeAuto, modeHidden, modeAuto], shown: 1}, {forceShow: true, fullscreen: false, modes: [modeShown, modeHidden, modeShown], shown: 0}, {forceShow: false, fullscreen: true, modes: [modeHidden, modeHidden, modeHidden], shown: 0}, {forceShow: true, fullscreen: true, modes: [modeHidden, modeHidden, modeShown], shown: 0}, ] } function test_change_forceHide(data) { controller.forceShow = data.forceShow webviewMock.fullscreen = data.fullscreen showSpy.clear() controller.forceHide = false compare(webviewMock.locationBarController.mode, data.modes[0]) controller.forceHide = true compare(webviewMock.locationBarController.mode, data.modes[1]) controller.forceHide = false compare(webviewMock.locationBarController.mode, data.modes[2]) compare(showSpy.count, data.shown) } function test_change_forceShow_data() { return [ {forceHide: false, fullscreen: false, modes: [modeAuto, modeShown, modeAuto], shown: 1}, {forceHide: true, fullscreen: false, modes: [modeHidden, modeHidden, modeHidden], shown: 0}, {forceHide: false, fullscreen: true, modes: [modeHidden, modeShown, modeShown], shown: 0}, {forceHide: true, fullscreen: true, modes: [modeHidden, modeHidden, modeHidden], shown: 0}, ] } function test_change_forceShow(data) { controller.forceHide = data.forceHide webviewMock.fullscreen = data.fullscreen showSpy.clear() controller.forceShow = false compare(webviewMock.locationBarController.mode, data.modes[0]) controller.forceShow = true compare(webviewMock.locationBarController.mode, data.modes[1]) controller.forceShow = false compare(webviewMock.locationBarController.mode, data.modes[2]) compare(showSpy.count, data.shown) } function test_change_fullscreen_data() { return [ {forceHide: false, forceShow: false, defaultMode: modeAuto, mode: modeAuto, shown: true}, {forceHide: false, forceShow: false, defaultMode: modeShown, mode: modeShown, shown: false}, {forceHide: false, forceShow: false, defaultMode: modeHidden, mode: modeHidden, shown: false}, {forceHide: true, forceShow: false, defaultMode: modeAuto, mode: modeHidden, shown: false}, {forceHide: true, forceShow: false, defaultMode: modeShown, mode: modeHidden, shown: false}, {forceHide: true, forceShow: false, defaultMode: modeHidden, mode: modeHidden, shown: false}, {forceHide: false, forceShow: true, defaultMode: modeAuto, mode: modeShown, shown: false}, {forceHide: false, forceShow: true, defaultMode: modeShown, mode: modeShown, shown: false}, {forceHide: false, forceShow: true, defaultMode: modeHidden, mode: modeShown, shown: false}, {forceHide: true, forceShow: true, defaultMode: modeAuto, mode: modeHidden, shown: false}, {forceHide: true, forceShow: true, defaultMode: modeShown, mode: modeHidden, shown: false}, {forceHide: true, forceShow: true, defaultMode: modeHidden, mode: modeHidden, shown: false}, ] } function test_change_fullscreen(data) { webviewMock.fullscreen = false controller.forceHide = data.forceHide controller.forceShow = data.forceShow controller.defaultMode = data.defaultMode showSpy.clear() webviewMock.fullscreen = true compare(webviewMock.locationBarController.mode, modeHidden) compare(showSpy.count, 0) webviewMock.fullscreen = false compare(webviewMock.locationBarController.mode, data.mode) compare(showSpy.count, data.shown ? 1 : 0) } function test_loading_state_changed_data() { return [ {forceHide: false, forceShow: false, fullscreen: false, mode: modeAuto, shown: true}, {forceHide: false, forceShow: false, fullscreen: false, mode: modeShown, shown: false}, {forceHide: false, forceShow: false, fullscreen: false, mode: modeHidden, shown: false}, {forceHide: true, forceShow: false, fullscreen: false, mode: modeHidden, shown: false}, {forceHide: false, forceShow: true, fullscreen: false, mode: modeShown, shown: false}, {forceHide: false, forceShow: false, fullscreen: true, mode: modeHidden, shown: false}, {forceHide: true, forceShow: true, fullscreen: false, mode: modeHidden, shown: false}, {forceHide: true, forceShow: false, fullscreen: true, mode: modeHidden, shown: false}, {forceHide: false, forceShow: true, fullscreen: true, mode: modeShown, shown: false}, {forceHide: true, forceShow: true, fullscreen: true, mode: modeHidden, shown: false}, ] } function test_loading_state_changed(data) { controller.forceHide = data.forceHide controller.forceShow = data.forceShow webviewMock.fullscreen = data.fullscreen webviewMock.locationBarController.mode = data.mode showSpy.clear() webviewMock.loading = true compare(showSpy.count, data.shown ? 1 : 0) compare(webviewMock.locationBarController.mode, data.mode) showSpy.clear() webviewMock.loading = false compare(showSpy.count, 0) compare(webviewMock.locationBarController.mode, data.mode) } function test_load_event_data() { var data = [] var booleanValues = [false, true] var modeValues = [modeAuto, modeHidden, modeShown] for (var i in booleanValues) { for (var j in booleanValues) { for (var k in modeValues) { for (var l in modeValues) { data.push({forceHide: booleanValues[i], forceShow: booleanValues[j], initialMode: modeValues[k], defaultMode: modeValues[l]}) } } } } return data } function test_load_event(data) { // event types var started = Oxide.LoadEvent.TypeStarted var committed = Oxide.LoadEvent.TypeCommitted var succeeded = Oxide.LoadEvent.TypeSucceeded var stopped = Oxide.LoadEvent.TypeStopped var failed = Oxide.LoadEvent.TypeFailed var redirected = Oxide.LoadEvent.TypeRedirected controller.forceHide = data.forceHide controller.forceShow = data.forceShow controller.defaultMode = data.defaultMode webviewMock.locationBarController.mode = data.initialMode showSpy.clear() function test_sequence(sequence, modes) { for (var i in sequence) { webviewMock.loadEvent({type: sequence[i]}) if (data.forceHide || data.forceShow) { compare(webviewMock.locationBarController.mode, data.initialMode) } else { compare(webviewMock.locationBarController.mode, modes[i]) } compare(showSpy.count, 0) } } var sequence = [started, committed, succeeded] var modes = [modeShown, data.defaultMode, data.defaultMode] test_sequence(sequence, modes) sequence = [started, stopped] modes = [modeShown, data.defaultMode] test_sequence(sequence, modes) sequence = [started, failed, committed] modes = [modeShown, modeShown, data.defaultMode] test_sequence(sequence, modes) sequence = [started, redirected, committed, succeeded] modes = [modeShown, modeShown, data.defaultMode, data.defaultMode] test_sequence(sequence, modes) } } } ./tests/unittests/qml/tst_AddressBar.qml0000644000015600001650000003361712703462031020521 0ustar jenkinsjenkins/* * Copyright 2013-2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtTest 1.0 import "../../../src/app/webbrowser" Item { width: 300 height: 60 FocusScope { anchors.fill: parent AddressBar { id: addressBar anchors { top: parent.top left: parent.left right: parent.right } height: parent.height / 2 searchUrl: "http://www.ubuntu.com/search?q={searchTerms}" editing: activeFocus canSimplifyText: true findController: QtObject { property int current property int count } } // only exists to steal focus from the address bar TextInput { id: textInput anchors { bottom: parent.bottom left: parent.left right: parent.right } height: parent.height / 2 } } SignalSpy { id: validatedSpy target: addressBar signalName: "validated" } WebbrowserTestCase { name: "AddressBar" when: windowShown function init() { addressBar.actualUrl = "" validatedSpy.clear() // Ensure the address bar has active focus clickItem(addressBar) verify(addressBar.activeFocus) // Clear it var clearButton = findChild(addressBar, "clear_button") verify(clearButton != null) clickItem(clearButton) compare(addressBar.text, "") // Ensure it still has active focus verify(addressBar.activeFocus) } function test_validUrlShouldNotBeRewritten_data() { return [ {url: "file:///usr/share/doc/ubuntu-online-tour/index.html"}, {url: "http://ubuntu.com"}, {url: "https://google.com"}, {url: "ftp://ubuntu.com"}, {url: "about:blank"}, {url: "data:,A brief note"}, {url: "http://com.google"} ] } function test_validUrlShouldNotBeRewritten(data) { typeString(data.url) compare(addressBar.text, data.url) keyClick(Qt.Key_Return) validatedSpy.wait() compare(addressBar.requestedUrl, data.url) } function test_urlWithoutSchemeShouldBeRewritten_data() { return [ {text: "ubuntu.com", requestedUrl: "http://ubuntu.com"}, {text: "192.168.1.1", requestedUrl: "http://192.168.1.1"}, {text: "192.168.1.1:8000", requestedUrl: "http://192.168.1.1:8000"}, {text: "192.168.1.1:8000/dummy.html", requestedUrl: "http://192.168.1.1:8000/dummy.html"}, {text: "/usr/share/doc/ubuntu-online-tour/index.html", requestedUrl: "file:///usr/share/doc/ubuntu-online-tour/index.html"}, ] } function test_urlWithoutSchemeShouldBeRewritten(data) { typeString(data.text) compare(addressBar.text, data.text) keyClick(Qt.Key_Return) validatedSpy.wait() compare(addressBar.requestedUrl, data.requestedUrl) } function test_leadingAndTrailingWhitespacesShouldBeTrimmed_data() { return [ {text: " http://ubuntu.com", requestedUrl: "http://ubuntu.com"}, {text: "http://ubuntu.com ", requestedUrl: "http://ubuntu.com"}, {text: " http://ubuntu.com ", requestedUrl: "http://ubuntu.com"}, ] } function test_leadingAndTrailingWhitespacesShouldBeTrimmed(data) { typeString(data.text) compare(addressBar.text, data.text) keyClick(Qt.Key_Return) validatedSpy.wait() compare(addressBar.requestedUrl, data.requestedUrl) } function test_searchQueryShouldBeRewritten_data() { return [ {text: "lorem ipsum dolor sit amet", start: "http://www.ubuntu.com", query: "lorem+ipsum+dolor+sit+amet"}, {text: "ubuntu", start: "http://www.ubuntu.com", query: "ubuntu"}, ] } function test_searchQueryShouldBeRewritten(data) { typeString(data.text) compare(addressBar.text, data.text) keyClick(Qt.Key_Return) validatedSpy.wait() compare(addressBar.requestedUrl.toString().indexOf(data.start), 0) verify(addressBar.requestedUrl.toString().indexOf("q=" + data.query) > 0) } function test_htmlEntitiesShouldBeEscapedInSearchQueries_data() { return [ {text: "tom & jerry", escaped: "tom+%26+jerry"}, {text: "a+ rating", escaped: "a%2B+rating"}, {text: "\"kung fu\"", escaped: "%22kung+fu%22"}, {text: "surfin' usa", escaped: "surfin'+usa"}, {text: "to be or not to be?", escaped: "to+be+or+not+to+be%3F"}, ] } function test_htmlEntitiesShouldBeEscapedInSearchQueries(data) { typeString(data.text) compare(addressBar.text, data.text) keyClick(Qt.Key_Return) validatedSpy.wait() verify(addressBar.requestedUrl.toString().indexOf("q=" + data.escaped) > 0) } function test_uppercaseDomainsShouldBeRewritten_data() { return [ {text: "WWW.UBUNTU.COM", requestedUrl: "http://www.ubuntu.com"}, {text: "EN.WIKIPEDIA.ORG/wiki/Ubuntu", requestedUrl: "http://en.wikipedia.org/wiki/Ubuntu"}, {text: "EN.WIKIPEDIA.ORG/wiki/UBUNTU", requestedUrl: "http://en.wikipedia.org/wiki/UBUNTU"}, ] } function test_uppercaseDomainsShouldBeRewritten(data) { typeString(data.text) compare(addressBar.text, data.text) keyClick(Qt.Key_Return) validatedSpy.wait() compare(addressBar.requestedUrl, data.requestedUrl) } function test_uppercaseSchemeShouldBeRewritten_data() { return [ {text: "HTTP://WWW.UBUNTU.COM", requestedUrl: "http://www.ubuntu.com"}, {text: "HTTP://www.ubuntu.com", requestedUrl: "http://www.ubuntu.com"}, {text: "HTTPS://www.ubuntu.com", requestedUrl: "https://www.ubuntu.com"}, {text: "FILE:///usr/share/doc/ubuntu-online-tour/index.html", requestedUrl: "file:///usr/share/doc/ubuntu-online-tour/index.html"}, {text: "FTP://ubuntu.com", requestedUrl: "ftp://ubuntu.com"}, {text: "ABOUT:BLANK", requestedUrl: "about:blank"}, {text: "DATA:,A brief note", requestedUrl: "data:,A brief note"}, {text: "HTTP://com.GOOGLE", requestedUrl: "http://com.google"} ] } function test_uppercaseSchemeShouldBeRewritten(data) { typeString(data.text) compare(addressBar.text, data.text) keyClick(Qt.Key_Return) validatedSpy.wait() compare(addressBar.requestedUrl, data.requestedUrl) } function test_urlShouldBeSimplifiedWhenUnfocused_data() { return [ {input: "http://www.ubuntu.com", simplified: "ubuntu.com", actualUrl: "http://www.ubuntu.com"}, {input: "http://www.ubuntu.com/", simplified: "ubuntu.com", actualUrl: "http://www.ubuntu.com/"}, {input: "http://www.ubuntu.com:80", simplified: "ubuntu.com", actualUrl: "http://www.ubuntu.com:80"}, {input: "http://www.ubuntuwww.com", simplified: "ubuntuwww.com", actualUrl: "http://www.ubuntuwww.com"}, {input: "http://www.com", simplified: "www.com", actualUrl: "http://www.com"}, {input: "http://user@www.ubuntu.com", simplified: "ubuntu.com", actualUrl: "http://user@www.ubuntu.com"}, {input: "http://user:password@www.ubuntu.com", simplified: "ubuntu.com", actualUrl: "http://user:password@www.ubuntu.com"}, {input: "http://user:password@www.ubuntu.com:80", simplified: "ubuntu.com", actualUrl: "http://user:password@www.ubuntu.com:80"}, {input: "file:///home/phablet/", simplified: "file:///home/phablet/", actualUrl: "file:///home/phablet/"}, {input: "http://en.wikipedia.org/wiki/Ubuntu", simplified: "en.wikipedia.org", actualUrl: "http://en.wikipedia.org/wiki/Ubuntu"}, {input: "en.wikipedia.org", simplified: "en.wikipedia.org", actualUrl: "http://en.wikipedia.org"}, {input: "en.wikipedia.org/wiki/Foo", simplified: "en.wikipedia.org", actualUrl: "http://en.wikipedia.org/wiki/Foo"}, {input: "http://com.google", simplified: "com.google", actualUrl: "http://com.google"}, ] } function test_urlShouldBeSimplifiedWhenUnfocused(data) { typeString(data.input) compare(addressBar.text, data.input) keyClick(Qt.Key_Return) validatedSpy.wait() addressBar.actualUrl = addressBar.requestedUrl compare(addressBar.text, data.input) clickItem(textInput) compare(addressBar.text, data.simplified) clickItem(addressBar) compare(addressBar.text, data.actualUrl) } function test_actionButtonShouldBeDisabledWhenEmpty() { verify(!addressBar.__actionButton.enabled) keyClick(Qt.Key_U) verify(addressBar.text != "") verify(addressBar.__actionButton.enabled) } function test_clickingWhenUnfocusedShouldSelectAll() { var url = "http://example.org/" typeString(url) compare(addressBar.text, url) addressBar.actualUrl = url clickItem(textInput) verify(!addressBar.activeFocus) clickItem(addressBar) compare(addressBar.__textField.selectedText, url) } function test_clickingWhenFocusedShouldDeselectText() { var url = "http://example.org/" typeString(url) compare(addressBar.text, url) addressBar.actualUrl = url clickItem(textInput) verify(!addressBar.activeFocus) clickItem(addressBar) compare(addressBar.__textField.selectedText, url) clickItem(addressBar) compare(addressBar.__textField.selectedText, "") verify(addressBar.__textField.cursorPosition > 0) } function test_clickingActionButtonWhenUnfocusedShouldNotSelectAll() { var url = "http://example.org/" typeString(url) compare(addressBar.text, url) clickItem(textInput) verify(!addressBar.activeFocus) clickItem(addressBar.__actionButton) compare(addressBar.__textField.selectedText, "") } function test_shouldNotAllowBookmarkingWhenEmpty() { // focused var toggle = addressBar.__bookmarkToggle verify(!toggle.visible) // and unfocused clickItem(textInput) verify(!toggle.visible) } function test_shouldNotAllowBookmarkingWhileFocused() { addressBar.actualUrl = "http://example.org" var toggle = addressBar.__bookmarkToggle verify(!toggle.visible) clickItem(textInput) verify(toggle.visible) } function test_togglingIndicatorShouldBookmark() { skip("Skipped due to what seems to be a bug in the UITK: https://launchpad.net/bugs/1483279") addressBar.actualUrl = "http://example.org" clickItem(textInput) verify(!addressBar.bookmarked) var toggle = addressBar.__bookmarkToggle clickItem(toggle) verify(addressBar.bookmarked) clickItem(toggle) verify(!addressBar.bookmarked) } function test_unfocusingWhileEditingShouldResetUrl() { var url = "http://example.org/" typeString(url) compare(addressBar.text, url) addressBar.actualUrl = url var clearButton = findChild(addressBar, "clear_button") verify(clearButton != null) clickItem(clearButton) compare(addressBar.text, "") clickItem(textInput) compare(addressBar.text, "example.org") clickItem(addressBar) compare(addressBar.text, url) } function test_exitingFindInPageRestoresUrl() { addressBar.actualUrl = "http://example.org/" addressBar.findInPageMode = true verify(addressBar.activeFocus) compare(addressBar.text, "") typeString("hello") addressBar.findInPageMode = false compare(addressBar.text, "example.org") } } } ./tests/unittests/qml/tst_TabsBar.qml0000644000015600001650000002302012703462031020010 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtTest 1.0 import "../../../src/app/webbrowser" import webbrowserapp.private 0.1 Item { id: root width: 600 height: 200 signal reload(string url) TabsModel { id: tabsModel } Component { id: tabComponent QtObject { id: tab property url url property string title property url icon function close() { destroy() } function reload() { root.reload(tab.url) } } } TabsBar { id: tabs // Make the tabs bar smaller than the window and aligned in the middle // to leave room for the context menu to pop up and have all its items // visible within the screen. anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter height: 50 model: tabsModel onSwitchToTab: model.currentIndex = index onRequestNewTab: insertTab("", "", "", index) function appendTab(url, title, icon) { insertTab(url, title, icon, model.count) model.currentIndex = model.count - 1 } function insertTab(url, title, icon, index) { var tab = tabComponent.createObject(root, {"url": url, "title": title, "icon": icon}) model.insert(tab, index) } } SignalSpy { id: newTabRequestSpy target: tabs signalName: "requestNewTab" } SignalSpy { id: tabClosedSpy target: tabs signalName: "tabClosed" } SignalSpy { id: reloadSpy target: root signalName: "reload" } WebbrowserTestCase { name: "TabsBar" when: windowShown function getMenuItemForAction(menu, actionName) { return findChild(menu, "tab_action_" + actionName + "_button") } function getTabDelegate(index) { var container = findChild(tabs, "tabsContainer") for (var i = 0; i < container.children.length; ++i) { var child = container.children[i] if ((child.objectName == "tabDelegate") && (child.tabIndex == index)) { return child } } return null } function popupMenuOnTab(index) { var tab = getTabDelegate(index) if (tab) { clickItem(tab, Qt.RightButton) var menu = findChild(root, "tabContextualActions") waitForRendering(menu) return menu } else return null } function cleanup() { while (tabsModel.count > 0) { tabsModel.remove(0).destroy() } newTabRequestSpy.clear() reloadSpy.clear() tabClosedSpy.clear() } function populateTabs() { var count = 3 for (var i = 0; i < count; ++i) { tabs.appendTab("", "tab " + i, "") } compare(tabsModel.currentIndex, count - 1) return count } function test_create_new_tab() { var newTabButton = findChild(tabs, "newTabButton") for (var i = 0; i < 3; ++i) { tryCompare(tabsModel, "count", i) clickItem(newTabButton) } compare(newTabRequestSpy.count, 3) compare(newTabRequestSpy.signalArguments[0][0], 0) compare(newTabRequestSpy.signalArguments[1][0], 1) compare(newTabRequestSpy.signalArguments[2][0], 2) } function test_mouse_left_click() { // Left click makes the tab current populateTabs() for (var i = 2; i >= 0; --i) { clickItem(getTabDelegate(i)) compare(tabsModel.currentIndex, i) } } function test_mouse_middle_click() { // Middle click closes the tab var count = populateTabs() for (var i = 0; i < count; i++) { var tab = getTabDelegate(i) clickItem(tab, Qt.MiddleButton) compare(tabClosedSpy.count, i + 1) } } function test_mouse_right_click() { // Right click pops up the contextual actions menu populateTabs() var menu = popupMenuOnTab(0) verify(menu) verify(menu.visible) } function test_mouse_wheel() { // Wheel events cycle through open tabs populateTabs() var tab0 = getTabDelegate(0) var c = centerOf(tab0) function wheelUp() { mouseWheel(tab0, c.x, c.y, 0, 120) } function wheelDown() { mouseWheel(tab0, c.x, c.y, 0, -120) } wheelDown() compare(tabsModel.currentIndex, 2) wheelUp() compare(tabsModel.currentIndex, 1) wheelUp() compare(tabsModel.currentIndex, 0) wheelUp() compare(tabsModel.currentIndex, 0) wheelDown() compare(tabsModel.currentIndex, 1) } function test_close_tabs_data() { return [ {button: Qt.LeftButton}, {button: Qt.MiddleButton} ] } function test_close_tabs(data) { var count = populateTabs() for (var i = 0; i < count; i++) { var tab = getTabDelegate(count - (i + 1)) var closeButton = findChild(tab, "closeButton") clickItem(closeButton, data.button) compare(tabClosedSpy.count, i + 1) compare(tabClosedSpy.signalArguments[i][0], count - (i + 1)) } } function test_drag_tab() { populateTabs() function dragTab(tab, dx, index) { var c = centerOf(tab) mouseDrag(tab, c.x, c.y, dx, 0) compare(getTabDelegate(index), tab) compare(tabsModel.currentIndex, index) wait(500) } // Move the first tab to the right var tab = getTabDelegate(0) dragTab(tab, tab.width * 0.8, 1) // Start a move to the right and release too early dragTab(tab, tab.width * 0.3, 1) // Start a move to the left and release too early dragTab(tab, -tab.width * 0.4, 1) // Move the tab all the way to the right and overshoot dragTab(tab, tab.width * 3, 2) // Move another tab all the way to the left and overshoot tab = getTabDelegate(1) dragTab(tab, -tab.width * 2, 0) } function test_menu_states_on_new_tab() { populateTabs() var menu = popupMenuOnTab(0) var item = getMenuItemForAction(menu, "new_tab") verify(item.enabled) item = getMenuItemForAction(menu, "reload") verify(!item.enabled) item = getMenuItemForAction(menu, "close_tab") verify(item.enabled) } function test_menu_states_on_page() { tabs.appendTab("http://localhost/", "tab", "") var menu = popupMenuOnTab(0) var item = getMenuItemForAction(menu, "new_tab") verify(item.enabled) item = getMenuItemForAction(menu, "reload") verify(item.enabled) item = getMenuItemForAction(menu, "close_tab") verify(item.enabled) } function test_context_menu_close() { populateTabs() var menu = popupMenuOnTab(1) var item = getMenuItemForAction(menu, "close_tab") clickItem(item) compare(tabClosedSpy.count, 1) compare(tabClosedSpy.signalArguments[0][0], 1) } function test_context_menu_reload() { var baseUrl = "http://localhost/" tabs.appendTab(baseUrl + "1", "tab 1", "") tabs.appendTab(baseUrl + "2", "tab 2", "") var menu = popupMenuOnTab(1) var item = getMenuItemForAction(menu, "reload") clickItem(item) compare(reloadSpy.count, 1) compare(reloadSpy.signalArguments[0][0], baseUrl + "2") } function test_context_menu_new_tab() { var baseUrl = "http://localhost/" tabs.appendTab(baseUrl + "1", "tab 1", "") tabs.appendTab(baseUrl + "2", "tab 2", "") var menu = popupMenuOnTab(0) var item = getMenuItemForAction(menu, "new_tab") clickItem(item) compare(newTabRequestSpy.count, 1) compare(newTabRequestSpy.signalArguments[0][0], 1) compare(tabsModel.count, 3) compare(tabsModel.get(0).url, baseUrl + "1") compare(tabsModel.get(1).url, "") compare(tabsModel.get(2).url, baseUrl + "2") } } } ./tests/unittests/qml/tst_NewTabView.qml0000644000015600001650000002340712703462031020516 0ustar jenkinsjenkins/* * Copyright 2016 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtQuick.Window 2.2 import QtTest 1.0 import Qt.labs.settings 1.0 import "../../../src/app/webbrowser" import webbrowserapp.private 0.1 FocusScope { id: root focus: true width: 350 height: 500 Loader { id: newTabViewLoader anchors.fill: parent focus: true active: false sourceComponent: NewTabView { focus: true anchors.fill: parent settingsObject: Settings { property url homepage: "http://example.com/homepage" } } } readonly property Item view: newTabViewLoader.item SignalSpy { id: bookmarkClickedSpy target: view signalName: "bookmarkClicked" } SignalSpy { id: bookmarkRemovedSpy target: view signalName: "bookmarkRemoved" } SignalSpy { id: historyEntryClickedSpy target: view signalName: "historyEntryClicked" } SignalSpy { id: countChangedSpy signalName: "countChanged" } WebbrowserTestCase { name: "NewTabView" when: windowShown function init() { HistoryModel.databasePath = ":memory:" BookmarksModel.databasePath = ":memory:" populate() newTabViewLoader.active = true waitForRendering(view) verify(view.activeFocus) compare(bookmarkClickedSpy.count, 0) compare(bookmarkRemovedSpy.count, 0) compare(historyEntryClickedSpy.count, 0) } function populate() { HistoryModel.add("http://example.com/foo", "foo", "") HistoryModel.add("http://example.org/bar", "bar", "") HistoryModel.add("http://example.net/baz", "baz", "") HistoryModel.add("http://example.net/qux", "qux", "") HistoryModel.add("http://example.net/norf", "norf", "") compare(HistoryModel.count, 5) BookmarksModel.add("http://example.com", "Example", "", "") BookmarksModel.add("http://example.org/a", "Example a", "", "FolderA") BookmarksModel.add("http://example.org/b", "Example b", "", "FolderB") BookmarksModel.add("http://example.org/c", "Example c", "", "FolderC") BookmarksModel.add("http://example.org/d", "Example d", "", "FolderD") compare(BookmarksModel.count, 5) } function cleanup() { newTabViewLoader.active = false BookmarksModel.databasePath = "" HistoryModel.databasePath = "" bookmarkClickedSpy.clear() bookmarkRemovedSpy.clear() historyEntryClickedSpy.clear() } function verify_bookmarks_expanded(expected) { if (expected) { compare(findChild(view, "bookmarksList"), null) compare(findChild(view, "bookmarksFolderListView").objectName, "bookmarksFolderListView") } else { compare(findChild(view, "bookmarksList").objectName, "bookmarksList") compare(findChild(view, "bookmarksFolderListView"), null) } } function check_focused_item(objectName) { var focused = root.Window.activeFocusItem verify(focused !== null) compare(focused.objectName, objectName) return focused } function test_click_bookmark() { var listview = findChild(view, "bookmarksList") var homepage = findChild(listview, "homepageBookmark") clickItem(homepage) compare(bookmarkClickedSpy.count, 1) compare(bookmarkClickedSpy.signalArguments[0][0], view.settingsObject.homepage) var bookmark = findChild(listview, "bookmark_1") clickItem(bookmark) compare(bookmarkClickedSpy.count, 2) compare(bookmarkClickedSpy.signalArguments[1][0], bookmark.url) } function test_delete_bookmark() { var listview = findChild(view, "bookmarksList") var bookmark = findChild(listview, "bookmark_2") swipeToDeleteAndConfirm(bookmark) bookmarkRemovedSpy.wait() compare(bookmarkRemovedSpy.count, 1) compare(bookmarkRemovedSpy.signalArguments[0][0], bookmark.url) } function test_expand_bookmarks() { verify_bookmarks_expanded(false) var button = findChild(view, "bookmarks.moreButton") clickItem(button) verify_bookmarks_expanded(true) clickItem(button) verify_bookmarks_expanded(false) } function test_click_top_site() { var grid = findChild(view, "topSitesList") compare(grid.count, 5) var topsites = getListItems(grid, "topSiteItem") compare(topsites.length, 5) var topsite = topsites[3] clickItem(topsite) compare(historyEntryClickedSpy.count, 1) compare(historyEntryClickedSpy.signalArguments[0][0], topsite.url) } function test_delete_top_site() { var grid = findChild(view, "topSitesList") compare(grid.count, 5) var topsite = getListItems(grid, "topSiteItem")[1] clickItem(topsite, Qt.RightButton) var contextMenu = findChild(root, "urlPreviewDelegate.contextMenu") var action = findChild(contextMenu, "delete_button") clickItem(action) compare(grid.count, 4) } function test_keyboard_navigation() { var bookmarksList = findChild(view, "bookmarksList") verify(bookmarksList.activeFocus) check_focused_item("homepageBookmark") keyClick(Qt.Key_Up) check_focused_item("bookmarkListHeader") verify_bookmarks_expanded(false) keyClick(Qt.Key_Enter) verify_bookmarks_expanded(true) keyClick(Qt.Key_Return) verify_bookmarks_expanded(false) keyClick(Qt.Key_Space) verify_bookmarks_expanded(true) keyClick(Qt.Key_Enter) verify_bookmarks_expanded(false) keyClick(Qt.Key_Up) check_focused_item("bookmarkListHeader") keyClick(Qt.Key_Down) check_focused_item("homepageBookmark") keyClick(Qt.Key_Delete) compare(bookmarkRemovedSpy.count, 0) keyClick(Qt.Key_Enter) compare(bookmarkClickedSpy.count, 1) compare(bookmarkClickedSpy.signalArguments[0][0], view.settingsObject.homepage) keyClick(Qt.Key_Down) var bookmark = check_focused_item("bookmark_1") keyClick(Qt.Key_Delete) compare(bookmarkRemovedSpy.count, 1) compare(bookmarkRemovedSpy.signalArguments[0][0], bookmark.url) keyClick(Qt.Key_Down) keyClick(Qt.Key_Down) keyClick(Qt.Key_Down) check_focused_item("bookmark_4") keyClick(Qt.Key_Down) var grid = findChild(view, "topSitesList") verify(grid.activeFocus) compare(grid.currentIndex, 0) var topsite = check_focused_item("topSiteItem") keyClick(Qt.Key_Enter) compare(historyEntryClickedSpy.count, 1) compare(historyEntryClickedSpy.signalArguments[0][0], topsite.url) keyClick(Qt.Key_Return) compare(historyEntryClickedSpy.count, 2) compare(historyEntryClickedSpy.signalArguments[1][0], topsite.url) keyClick(Qt.Key_Right) compare(grid.currentIndex, 1) check_focused_item("topSiteItem") keyClick(Qt.Key_Down) compare(grid.currentIndex, 3) check_focused_item("topSiteItem") keyClick(Qt.Key_Down) compare(grid.currentIndex, 3) check_focused_item("topSiteItem") keyClick(Qt.Key_Left) compare(grid.currentIndex, 2) check_focused_item("topSiteItem") keyClick(Qt.Key_Down) compare(grid.currentIndex, 4) check_focused_item("topSiteItem") keyClick(Qt.Key_Down) compare(grid.currentIndex, 4) check_focused_item("topSiteItem") keyClick(Qt.Key_Left) compare(grid.currentIndex, 3) check_focused_item("topSiteItem") var notopsiteslabel = findChild(view, "notopsites") verify(!notopsiteslabel.visible) compare(grid.count, 5) countChangedSpy.target = grid for (var i = 4; i >= 0; --i) { keyClick(Qt.Key_Delete) countChangedSpy.wait() compare(grid.count, i) compare(grid.currentIndex, i - 1) } compare(grid.count, 0) verify(notopsiteslabel.visible) bookmarksList = findChild(view, "bookmarksList") verify(bookmarksList.activeFocus) keyClick(Qt.Key_Down) verify(bookmarksList.activeFocus) } } } ./tests/unittests/qml/tst_SearchEngines.qml0000644000015600001650000001130412703462031021212 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtTest 1.0 import "../../../src/app/webbrowser" import webbrowsertest.private 0.1 import webbrowserapp.private 0.1 Item { id: root width: 200 height: 200 SearchEngines { id: searchEngines readonly property int count: engines.count } SearchEngine { id: testEngine searchPaths: searchEngines.searchPaths } TestCase { name: "SearchEngines" function checkEngine(index, filename, name, description, urlTemplate) { compare(searchEngines.engines.get(index).filename, filename) testEngine.filename = filename compare(testEngine.name, name) compare(testEngine.description, description) compare(testEngine.urlTemplate, urlTemplate) testEngine.filename = "" } function test_no_engines() { searchEngines.searchPaths = [] tryCompare(searchEngines, "count", 0) } function test_find_engines() { verify(TestContext.writeSearchEngineDescription( TestContext.testDir1, "engine1", "engine1", "engine1 search", "https://example.org/search1?q={searchTerms}")) verify(TestContext.writeSearchEngineDescription( TestContext.testDir2, "engine2", "engine2", "engine2 search", "https://example.org/search2?q={searchTerms}")) searchEngines.searchPaths = [TestContext.testDir1, TestContext.testDir2] tryCompare(searchEngines, "count", 2) checkEngine(0, "engine1", "engine1", "engine1 search", "https://example.org/search1?q={searchTerms}") checkEngine(1, "engine2", "engine2", "engine2 search", "https://example.org/search2?q={searchTerms}") // override engine2 in dir2 with another description in dir1 verify(TestContext.writeSearchEngineDescription( TestContext.testDir1, "engine2", "engine2-overridden", "engine2-overridden search", "https://example.org/search2-overridden?q={searchTerms}")) compare(searchEngines.count, 2) checkEngine(1, "engine2", "engine2-overridden", "engine2-overridden search", "https://example.org/search2-overridden?q={searchTerms}") // reverse the order of search paths to verify that the order // of precedence is updated searchEngines.searchPaths = [TestContext.testDir2, TestContext.testDir1] tryCompare(searchEngines, "count", 2) checkEngine(0, "engine1", "engine1", "engine1 search", "https://example.org/search1?q={searchTerms}") checkEngine(1, "engine2", "engine2", "engine2 search", "https://example.org/search2?q={searchTerms}") // override engine2 with an invalid description and verify // that it is removed from the list verify(TestContext.deleteSearchEngineDescription( TestContext.testDir2, "engine2")) verify(TestContext.writeInvalidSearchEngineDescription( TestContext.testDir2, "engine2")) tryCompare(searchEngines, "count", 1) checkEngine(0, "engine1", "engine1", "engine1 search", "https://example.org/search1?q={searchTerms}") // remove the invalid description and verify that the other // description re-appears verify(TestContext.deleteSearchEngineDescription( TestContext.testDir2, "engine2")) tryCompare(searchEngines, "count", 2) checkEngine(1, "engine2", "engine2-overridden", "engine2-overridden search", "https://example.org/search2-overridden?q={searchTerms}") // clean up verify(TestContext.deleteSearchEngineDescription( TestContext.testDir1, "engine2")) verify(TestContext.deleteSearchEngineDescription( TestContext.testDir1, "engine1")) } } } ./tests/unittests/qml/tst_FileExtensionMapper.qml0000644000015600001650000000235212703462031022420 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtTest 1.0 import "../../../src/app/FileExtensionMapper.js" as FileExtensionMapper TestCase { name: "FileExtensionMapper" function test_getExtension_data() { return [ {in: "", ext: ""}, {in: "pdf", ext: ""}, {in: ".vimrc", ext: ""}, {in: "example.pdf", ext: "pdf"}, {in: "http://example.org/path/example.pdf", ext: "pdf"}, {in: "EXAMPLE.PDF", ext: "pdf"} ] } function test_getExtension(data) { compare(FileExtensionMapper.getExtension(data.in), data.ext) } } ./tests/unittests/qml/CMakeLists.txt0000644000015600001650000000320412703462031017627 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Gui REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5Qml REQUIRED) find_package(Qt5Quick REQUIRED) find_package(Qt5QuickTest REQUIRED) find_package(Qt5Sql REQUIRED) set(XVFB_COMMAND) find_program(XVFBRUN xvfb-run) if(XVFBRUN) set(XVFB_COMMAND ${XVFBRUN} -s "-screen 0 640x480x24" -a) else() message(WARNING "Cannot find xvfb-run.") endif() set(TEST tst_QmlTests) set(SOURCES ${webbrowser-common_SOURCE_DIR}/favicon-fetcher.cpp ${webbrowser-app_SOURCE_DIR}/bookmarks-model.cpp ${webbrowser-app_SOURCE_DIR}/bookmarks-folder-model.cpp ${webbrowser-app_SOURCE_DIR}/bookmarks-folderlist-model.cpp ${webbrowser-app_SOURCE_DIR}/file-operations.cpp ${webbrowser-app_SOURCE_DIR}/history-domain-model.cpp ${webbrowser-app_SOURCE_DIR}/history-domainlist-model.cpp ${webbrowser-app_SOURCE_DIR}/history-model.cpp ${webbrowser-app_SOURCE_DIR}/history-lastvisitdatelist-model.cpp ${webbrowser-app_SOURCE_DIR}/limit-proxy-model.cpp ${webbrowser-app_SOURCE_DIR}/searchengine.cpp ${webbrowser-app_SOURCE_DIR}/tabs-model.cpp ${webbrowser-app_SOURCE_DIR}/text-search-filter-model.cpp tst_QmlTests.cpp ) add_executable(${TEST} ${SOURCES}) include_directories( ${webbrowser-common_SOURCE_DIR} ${webbrowser-app_SOURCE_DIR} ${unity8_SOURCE_DIR}/plugins ) target_link_libraries(${TEST} Qt5::Core Qt5::Gui Qt5::Network Qt5::Qml Qt5::Quick Qt5::QuickTest Qt5::Sql InputInfo ) add_test(${TEST} ${XVFB_COMMAND} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -input ${CMAKE_CURRENT_SOURCE_DIR} -import ${CMAKE_BINARY_DIR}/src) ./tests/unittests/qml/tst_HistoryView.qml0000644000015600001650000001730312703462031020775 0ustar jenkinsjenkins/* * Copyright 2015-2016 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtQuick.Window 2.2 import QtTest 1.0 import "../../../src/app/webbrowser" import webbrowserapp.private 0.1 FocusScope { id: root focus: true width: 300 height: 500 readonly property Item historyView: historyViewLoader.item Loader { id: historyViewLoader anchors.fill: parent active: false focus: true sourceComponent: HistoryView { focus: true } } SignalSpy { id: seeMoreEntriesClickedSpy target: historyView signalName: "seeMoreEntriesClicked" } SignalSpy { id: newTabRequestedSpy target: historyView signalName: "newTabRequested" } SignalSpy { id: doneSpy target: historyView signalName: "done" } WebbrowserTestCase { name: "HistoryView" when: windowShown function init() { HistoryModel.databasePath = ":memory:" populate() historyViewLoader.active = true historyView.loadModel() waitForRendering(historyView) verify(historyView.activeFocus) var domainsList = findChild(historyView, "domainsListView") waitForRendering(domainsList) tryCompare(domainsList, "count", 3) compare(seeMoreEntriesClickedSpy.count, 0) compare(doneSpy.count, 0) } function populate() { HistoryModel.add("http://example.com/foo", "foo", "") HistoryModel.add("http://example.org/bar", "bar", "") HistoryModel.add("http://example.net/baz", "baz", "") compare(HistoryModel.count, 3) } function cleanup() { historyViewLoader.active = false HistoryModel.databasePath = "" seeMoreEntriesClickedSpy.clear() doneSpy.clear() } function test_done() { var button = findChild(historyView, "doneButton") clickItem(button) compare(doneSpy.count, 1) } function test_new_tab() { var action = findChild(historyView, "newTabAction") clickItem(action) compare(newTabRequestedSpy.count, 1) compare(doneSpy.count, 1) } function test_see_more_entries() { var listview = findChild(historyView, "domainsListView") var domain = getListItems(listview, "historyViewDomainDelegate")[0] clickItem(domain) compare(seeMoreEntriesClickedSpy.count, 1) compare(seeMoreEntriesClickedSpy.signalArguments[0][0].domain, domain.title) } function test_delete_domain() { var listview = findChild(historyView, "domainsListView") var domain = getListItems(listview, "historyViewDomainDelegate")[1] swipeToDeleteAndConfirm(domain) tryCompare(HistoryModel, "count", 2) } function test_exit_select_mode() { var listview = findChild(historyView, "domainsListView") var domains = getListItems(listview, "historyViewDomainDelegate") var first = domains[0] verify(!first.selectMode) longPressItem(first) tryCompare(first, "selectMode", true) var closeButton = findChild(historyView, "closeButton") clickItem(closeButton) tryCompare(first, "selectMode", false) } function test_delete_multiple_domains() { var listview = findChild(historyView, "domainsListView") var domains = getListItems(listview, "historyViewDomainDelegate") var first = domains[0] verify(!first.selectMode) longPressItem(first) tryCompare(first, "selectMode", true) tryCompare(first, "selected", true) var third = domains[2] verify(!third.selected) clickItem(third) tryCompare(third, "selected", true) var deleteButton = findChild(historyView, "deleteButton") clickItem(deleteButton) tryCompare(first, "selectMode", false) tryCompare(HistoryModel, "count", 1) } function test_select_all() { var listview = findChild(historyView, "domainsListView") var domains = getListItems(listview, "historyViewDomainDelegate") var first = domains[0], second = domains[1], third = domains[2] verify(!first.selectMode) longPressItem(first) tryCompare(first, "selectMode", true) tryCompare(first, "selected", true) verify(!second.selected) verify(!third.selected) var deleteButton = findChild(historyView, "deleteButton") verify(deleteButton.enabled) var selectAllButton = findChild(historyView, "selectAllButton") clickItem(selectAllButton) verify(first.selected) tryCompare(second, "selected", true) tryCompare(third, "selected", true) clickItem(selectAllButton) tryCompare(first, "selected", false) tryCompare(second, "selected", false) tryCompare(third, "selected", false) verify(!deleteButton.enabled) clickItem(selectAllButton) tryCompare(first, "selected", true) tryCompare(second, "selected", true) tryCompare(third, "selected", true) verify(deleteButton.enabled) clickItem(deleteButton) tryCompare(HistoryModel, "count", 0) } function test_keyboard_navigation() { var listview = findChild(historyView, "domainsListView") verify(listview.activeFocus) var domains = getListItems(listview, "historyViewDomainDelegate") function check_current(index) { compare(listview.currentIndex, index) var current = root.Window.activeFocusItem compare(current.objectName, "historyViewDomainDelegate") compare(current.modelIndex, index) compare(current.title, domains[index].title) verify(current.activeFocus) return current } var current = check_current(0) keyClick(Qt.Key_Up) verify(current.activeFocus) keyClick(Qt.Key_Enter) compare(seeMoreEntriesClickedSpy.count, 1) compare(seeMoreEntriesClickedSpy.signalArguments[0][0].domain, domains[0].title) keyClick(Qt.Key_Down) current = check_current(1) keyClick(Qt.Key_Return) compare(seeMoreEntriesClickedSpy.count, 2) compare(seeMoreEntriesClickedSpy.signalArguments[1][0].domain, domains[1].title) keyClick(Qt.Key_Down) current = check_current(2) keyClick(Qt.Key_Down) verify(current.activeFocus) keyClick(Qt.Key_Delete) tryCompare(listview, 'currentIndex', 1) current = check_current(1) } } } ./tests/unittests/qml/tst_Suggestions.qml0000644000015600001650000001054212703462031021011 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtTest 1.0 import Ubuntu.Test 1.0 import "../../../src/app/webbrowser" Item { width: 300 height: 300 ListModel { id: model1 readonly property bool displayUrl: true readonly property url icon: "" } readonly property var model2: [] ListModel { id: model3 readonly property bool displayUrl: true readonly property url icon: "" } Suggestions { id: suggestions focus: true anchors.fill: parent models: [model1, model2, model3] searchTerms: [] } SignalSpy { id: activatedSpy target: suggestions signalName: "activated" } UbuntuTestCase { name: "Suggestions" when: windowShown function init() { model1.append({"title": "lorem ipsum", "url": "http://model1/item1"}) model1.append({"title": "a+ rating", "url": "http://model1/item2"}) model2.icon = "" model2.push({"title": "Tom & Jerry", "url": "http://model2/item1"}) model2.push({"title": "$1 in €", "url": "http://model2/item2"}) model2.push({"title": "foo | bar | baz", "url": "http://model2/item3"}) model3.append({"title": "(a+b)^2", "url": "http://model3/item1"}) model3.append({"title": "Çà et là", "url": "http://model3/item2"}) compare(suggestions.count, 7) activatedSpy.clear() } function cleanup() { activatedSpy.clear() model3.clear() model2.splice(0, 3) model1.clear() compare(suggestions.count, 0) } function test_highlighting_data() { function highlight(term) { return "%2".arg("#752571").arg(term) } return [ {terms: [], index: 0, title: "lorem ipsum"}, {terms: ["a+"], index: 1, title: "%1 rating".arg(highlight("a+"))}, {terms: ["a+"], index: 5, title: "(%1b)^2".arg(highlight("a+"))}, {terms: ["tom", "jerry"], index: 2, title: "%1 & %2".arg(highlight("Tom")).arg(highlight("Jerry"))}, {terms: ["$"], index: 3, title: "%991 in €".arg(highlight("$"))}, {terms: ["|"], index: 4, title: "foo %1 bar %1 baz".arg(highlight("|"))}, {terms: ["(", ")"], index: 5, title: "%1a+b%2^2".arg(highlight("(")).arg(highlight(")"))}, {terms: ["à", "ET"], index: 6, title: "Ç%1 %2 l%1".arg(highlight("à")).arg(highlight("et"))}, ] } function test_highlighting(data) { suggestions.searchTerms = data.terms var delegate = findChild(suggestions, "suggestionDelegate_" + data.index) compare(delegate.title, data.title) } function test_mouseActivation() { var delegate = findChild(suggestions, "suggestionDelegate_4") var center = centerOf(delegate) mouseClick(delegate, center.x, center.y) compare(activatedSpy.count, 1) compare(activatedSpy.signalArguments[0][0], "http://model2/item3") } function test_keyboardActivation() { var listview = findChild(suggestions, "suggestionsList") compare(listview.currentIndex, 0) keyClick(Qt.Key_Down) keyClick(Qt.Key_Down) compare(listview.currentIndex, 2) keyClick(Qt.Key_Return) compare(activatedSpy.count, 1) compare(activatedSpy.signalArguments[0][0], "http://model2/item1") } } } ./tests/unittests/webapp-container-hook/0000755000015600001650000000000012703462032020474 5ustar jenkinsjenkins./tests/unittests/webapp-container-hook/tst_WebappContainerHookTests.cpp0000644000015600001650000001650012703462031027020 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include // local #include "click-hooks/hook-utils.h" namespace { void createFileWithContent(const QString& filename, const QString& content) { QFile file(filename); if (!file.open(QIODevice::ReadWrite)) { return; } file.write(content.toUtf8()); file.flush(); } QString readAll(const QString& filename) { QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { return QString(); } return file.readAll(); } } class WebappContainerHookTests : public QObject { Q_OBJECT private Q_SLOTS: void testRemoveVersion_data() { QTest::addColumn("appId"); QTest::addColumn("appIdNoVersion"); QTest::newRow("With version") << QString("com.ubuntu.blabla_blabla_0.2") << QString("com.ubuntu.blabla_blabla"); QTest::newRow("With no version") << QString("com.ubuntu.blabla_blabla") << QString("com.ubuntu.blabla_blabla"); } void testRemoveVersion() { QFETCH(QString, appId); QFETCH(QString, appIdNoVersion); QCOMPARE(HookUtils::removeVersionFrom(appId), appIdNoVersion); } void testClickHookUpdate_data() { QTest::addColumn("processedHookFilename"); QTest::addColumn("processedHookFileContent"); QTest::addColumn("installedHookFilename"); QTest::addColumn("installedHookFileContent"); QTest::addColumn("shouldBeUpdated"); QTest::newRow("Valid hook file") << QString("com.ubuntu.blabla_blabla") << QString("[{}]") << QString("com.ubuntu.blabla_blabla_0.2.webapp") << QString("[{\"uninstall\": { \"delete-cookies\": true, \"delete-cache\": true } }]") << true; QTest::newRow("Valid hook file - no update") << QString("com.ubuntu.blabla_blabla") << QString("[{}]") << QString("com.ubuntu.blabla_blabla_0.2.webapp") << QString("[{\"uninstall\": { \"delete-cookies\": true, \"delete-cache\": true } }]") << false; } void testClickHookUpdate() { QFETCH(QString, processedHookFilename); QFETCH(QString, installedHookFilename); QFETCH(QString, processedHookFileContent); QFETCH(QString, installedHookFileContent); QFETCH(bool, shouldBeUpdated); QTemporaryDir processedHookFiledTmpDir; QTemporaryDir installedHookFiledTmpDir; QVERIFY(!processedHookFilename.isEmpty()); QString processedFilepath = processedHookFiledTmpDir.path() + "/" + processedHookFilename; QVERIFY(!installedHookFilename.isEmpty()); QString installedHookFilepath = installedHookFiledTmpDir.path() + "/" + installedHookFilename; if (shouldBeUpdated) { createFileWithContent(processedFilepath, processedHookFileContent); // Wait in order to have a meaningful lastModifiedData for comparaison QTest::qSleep(1000); createFileWithContent(installedHookFilepath, installedHookFileContent); } else { createFileWithContent(installedHookFilepath, installedHookFileContent); // Wait in order to have a meaningful lastModifiedData for comparaison QTest::qSleep(1000); createFileWithContent(processedFilepath, processedHookFileContent); } QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("WEBAPPCONTAINER_PROCESSED_HOOKS_FOLDER", processedHookFiledTmpDir.path()); env.insert("WEBAPPCONTAINER_INSTALLED_HOOKS_FOLDER", installedHookFiledTmpDir.path()); QProcess process; process.setProcessEnvironment(env); process.start(CLICK_HOOK_EXEC_BIN, QStringList()); process.waitForFinished(); if (shouldBeUpdated) { QCOMPARE(readAll(processedFilepath), readAll(installedHookFilepath)); } else { QCOMPARE(readAll(processedFilepath), processedHookFileContent); } } void testClickHookInstallProcessing() { QString processedHookFilename = "com.ubuntu.blabla_blabla"; QString installedHookFilename = "com.ubuntu.blabla_blabla_0.2.webapp"; QString installedHookFileContent = "[{\"uninstall\": { \"delete-cookies\": true, \"delete-cache\": true } }]"; QTemporaryDir processedHookFiledTmpDir; QTemporaryDir installedHookFiledTmpDir; QString processedFilepath = processedHookFiledTmpDir.path() + "/" + processedHookFilename; QString installedHookFilepath = installedHookFiledTmpDir.path() + "/" + installedHookFilename; createFileWithContent(installedHookFilepath, installedHookFileContent); QVERIFY(!QFile::exists(processedFilepath)); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("WEBAPPCONTAINER_PROCESSED_HOOKS_FOLDER", processedHookFiledTmpDir.path()); env.insert("WEBAPPCONTAINER_INSTALLED_HOOKS_FOLDER", installedHookFiledTmpDir.path()); QProcess process; process.setProcessEnvironment(env); process.start(CLICK_HOOK_EXEC_BIN, QStringList()); process.waitForFinished(); QVERIFY(QFile::exists(processedFilepath)); QCOMPARE(readAll(processedFilepath), readAll(installedHookFilepath)); } void testClickHookUninstall() { QString processedHookFilename = "com.ubuntu.blabla_blabla"; QString processedHookFileContent = "[{\"uninstall\": { \"delete-cookies\": false, \"delete-cache\": false } }]"; QTemporaryDir processedHookFiledTmpDir; QTemporaryDir installedHookFiledTmpDir; QString processedFilepath = processedHookFiledTmpDir.path() + "/" + processedHookFilename; createFileWithContent(processedFilepath, processedHookFileContent); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("WEBAPPCONTAINER_PROCESSED_HOOKS_FOLDER", processedHookFiledTmpDir.path()); env.insert("WEBAPPCONTAINER_INSTALLED_HOOKS_FOLDER", installedHookFiledTmpDir.path()); QProcess process; process.setProcessEnvironment(env); process.start(CLICK_HOOK_EXEC_BIN, QStringList()); process.waitForFinished(); QVERIFY(!QFile::exists(processedFilepath)); } }; QTEST_MAIN(WebappContainerHookTests) #include "tst_WebappContainerHookTests.moc" ./tests/unittests/webapp-container-hook/CMakeLists.txt0000644000015600001650000000106512703462031023235 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_WebappContainerHookTests) set(SOURCES ${CMAKE_SOURCE_DIR}/click-hooks/hook-utils.cpp tst_WebappContainerHookTests.cpp ) set(WEBAPP_CONTAINER_HOOK webapp-container-hook) include_directories(${CMAKE_SOURCE_DIR}) add_definitions(-DCLICK_HOOK_EXEC_BIN="${CMAKE_BINARY_DIR}/click-hooks/${WEBAPP_CONTAINER_HOOK}") add_executable(${TEST} ${SOURCES}) target_link_libraries(${TEST} Qt5::Core Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/search-engine/0000755000015600001650000000000012703462032017010 5ustar jenkinsjenkins./tests/unittests/search-engine/tst_SearchEngineTests.cpp0000644000015600001650000002222212703462031023763 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include #include #include #include // local #include "searchengine.h" class SearchEngineTests : public QObject { Q_OBJECT private: QTemporaryDir* dir1; QTemporaryDir* dir2; SearchEngine* engine; QSignalSpy* searchPathsSpy; QSignalSpy* filenameSpy; QSignalSpy* nameSpy; QSignalSpy* descriptionSpy; QSignalSpy* urlTemplateSpy; QSignalSpy* suggestionsUrlTemplateSpy; QSignalSpy* validSpy; private Q_SLOTS: void init() { dir1 = new QTemporaryDir; QVERIFY(dir1->isValid()); dir2 = new QTemporaryDir; QVERIFY(dir2->isValid()); engine = new SearchEngine; searchPathsSpy = new QSignalSpy(engine, SIGNAL(searchPathsChanged())); filenameSpy = new QSignalSpy(engine, SIGNAL(filenameChanged())); nameSpy = new QSignalSpy(engine, SIGNAL(nameChanged())); descriptionSpy = new QSignalSpy(engine, SIGNAL(descriptionChanged())); urlTemplateSpy = new QSignalSpy(engine, SIGNAL(urlTemplateChanged())); suggestionsUrlTemplateSpy = new QSignalSpy(engine, SIGNAL(suggestionsUrlTemplateChanged())); validSpy = new QSignalSpy(engine, SIGNAL(validChanged())); QFile file(QDir(dir1->path()).absoluteFilePath("engine1.xml")); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); QTextStream out(&file); out << ""; out << "engine1"; out << "engine1 search"; out << ""; out << ""; out << ""; file.close(); file.setFileName(QDir(dir2->path()).absoluteFilePath("engine2.xml")); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); out.setDevice(&file); out << ""; out << "engine2"; out << ""; out << ""; file.close(); file.setFileName(QDir(dir2->path()).absoluteFilePath("invalid.xml")); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); out.setDevice(&file); out << "invalid"; file.close(); engine->setSearchPaths({dir1->path(), dir2->path()}); QCOMPARE(searchPathsSpy->count(), 1); searchPathsSpy->clear(); nameSpy->clear(); descriptionSpy->clear(); urlTemplateSpy->clear(); suggestionsUrlTemplateSpy->clear(); validSpy->clear(); QVERIFY(!engine->isValid()); } void cleanup() { delete validSpy; delete suggestionsUrlTemplateSpy; delete urlTemplateSpy; delete descriptionSpy; delete nameSpy; delete filenameSpy; delete searchPathsSpy; delete engine; delete dir1; delete dir2; } void shouldChangeSearchPaths() { QCOMPARE(engine->searchPaths(), QStringList({dir1->path(), dir2->path()})); engine->setSearchPaths({dir2->path()}); QCOMPARE(searchPathsSpy->count(), 1); QCOMPARE(engine->searchPaths(), QStringList({dir2->path()})); } void shouldChangeFilename() { QVERIFY(engine->filename().isEmpty()); engine->setFilename("engine1"); QCOMPARE(filenameSpy->count(), 1); QCOMPARE(engine->filename(), QString("engine1")); engine->setFilename(""); QCOMPARE(filenameSpy->count(), 2); QVERIFY(engine->filename().isEmpty()); } void shouldParseValidDescriptionWithDescription() { engine->setFilename("engine1"); QCOMPARE(nameSpy->count(), 1); QCOMPARE(engine->name(), QString("engine1")); QCOMPARE(descriptionSpy->count(), 1); QCOMPARE(engine->description(), QString("engine1 search")); QCOMPARE(urlTemplateSpy->count(), 1); QCOMPARE(engine->urlTemplate(), QString("https://example.org/search1?q={searchTerms}")); QCOMPARE(suggestionsUrlTemplateSpy->count(), 1); QCOMPARE(engine->suggestionsUrlTemplate(), QString("https://example.org/suggest1?q={searchTerms}")); QCOMPARE(validSpy->count(), 1); QVERIFY(engine->isValid()); } void shouldParseValidDescriptionWithoutDescriptionAndSuggestionsTemplate() { engine->setFilename("engine2"); QCOMPARE(nameSpy->count(), 1); QCOMPARE(engine->name(), QString("engine2")); QVERIFY(descriptionSpy->isEmpty()); QVERIFY(engine->description().isEmpty()); QCOMPARE(urlTemplateSpy->count(), 1); QCOMPARE(engine->urlTemplate(), QString("https://example.org/search2?q={searchTerms}")); QVERIFY(suggestionsUrlTemplateSpy->isEmpty()); QVERIFY(engine->suggestionsUrlTemplate().isEmpty()); QCOMPARE(validSpy->count(), 1); QVERIFY(engine->isValid()); } void shouldFailToParseInvalidDescription() { engine->setFilename("invalid"); QVERIFY(nameSpy->isEmpty()); QVERIFY(engine->name().isEmpty()); QVERIFY(descriptionSpy->isEmpty()); QVERIFY(engine->description().isEmpty()); QVERIFY(urlTemplateSpy->isEmpty()); QVERIFY(engine->urlTemplate().isEmpty()); QVERIFY(suggestionsUrlTemplateSpy->isEmpty()); QVERIFY(engine->suggestionsUrlTemplate().isEmpty()); QVERIFY(validSpy->isEmpty()); QVERIFY(!engine->isValid()); } void shouldFailToLocateNonexistentDescription() { engine->setFilename("nonexistent"); QVERIFY(nameSpy->isEmpty()); QVERIFY(engine->name().isEmpty()); QVERIFY(descriptionSpy->isEmpty()); QVERIFY(engine->description().isEmpty()); QVERIFY(urlTemplateSpy->isEmpty()); QVERIFY(engine->urlTemplate().isEmpty()); QVERIFY(suggestionsUrlTemplateSpy->isEmpty()); QVERIFY(engine->suggestionsUrlTemplate().isEmpty()); QVERIFY(validSpy->isEmpty()); QVERIFY(!engine->isValid()); } void shouldOverrideExistingDescription() { QFile file(QDir(dir1->path()).absoluteFilePath("engine2.xml")); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); QTextStream out(&file); out << ""; out << "engine2-overridden"; out << "engine2 overridden search"; out << ""; out << ""; out << ""; file.close(); engine->setFilename("engine2"); QCOMPARE(nameSpy->count(), 1); QCOMPARE(engine->name(), QString("engine2-overridden")); QCOMPARE(descriptionSpy->count(), 1); QCOMPARE(engine->description(), QString("engine2 overridden search")); QCOMPARE(urlTemplateSpy->count(), 1); QCOMPARE(engine->urlTemplate(), QString("https://example.org/search2overridden?q={searchTerms}")); QCOMPARE(suggestionsUrlTemplateSpy->count(), 1); QCOMPARE(engine->suggestionsUrlTemplate(), QString("https://example.org/suggest2?q={searchTerms}")); QCOMPARE(validSpy->count(), 1); QVERIFY(engine->isValid()); } void shouldOverrideAndInvalidateDescription() { QFile file(QDir(dir1->path()).absoluteFilePath("engine2.xml")); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); file.close(); engine->setFilename("engine2"); QVERIFY(nameSpy->isEmpty()); QVERIFY(engine->name().isEmpty()); QVERIFY(descriptionSpy->isEmpty()); QVERIFY(engine->description().isEmpty()); QVERIFY(urlTemplateSpy->isEmpty()); QVERIFY(engine->urlTemplate().isEmpty()); QVERIFY(suggestionsUrlTemplateSpy->isEmpty()); QVERIFY(engine->suggestionsUrlTemplate().isEmpty()); QVERIFY(validSpy->isEmpty()); QVERIFY(!engine->isValid()); } }; QTEST_MAIN(SearchEngineTests) #include "tst_SearchEngineTests.moc" ./tests/unittests/search-engine/CMakeLists.txt0000644000015600001650000000063412703462031021552 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_SearchEngineTests) set(SOURCES ${webbrowser-app_SOURCE_DIR}/searchengine.cpp tst_SearchEngineTests.cpp ) add_executable(${TEST} ${SOURCES}) include_directories(${webbrowser-app_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/oxide-cookie-helper/0000755000015600001650000000000012703462032020134 5ustar jenkinsjenkins./tests/unittests/oxide-cookie-helper/tst_OxideCookieHelper.cpp0000644000015600001650000003515112703462031025100 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include #include #include #include #include // local #include "oxide-cookie-helper.h" typedef QList Cookies; /* Fake Oxide backend implementation */ class CookieBackend : public QObject { Q_OBJECT public: CookieBackend(QObject *parent = 0): QObject(parent), m_lastRequestId(0) {} void sendReply(int requestId, const QList& failedCookies) { QVariant failedCookiesVariant = OxideCookieHelper::variantFromCookies(failedCookies); Q_EMIT setCookiesResponse(requestId, failedCookiesVariant); } void setFailUrls(const QSet& urls) { m_failUrls = urls; } public Q_SLOTS: int setNetworkCookies(const QUrl& url, const QList& cookies) { if (m_failUrls.contains(url)) return -1; m_lastRequestId++; /* Handle host cookies */ QList restoredCookies; Q_FOREACH(const QNetworkCookie& cookie, cookies) { QNetworkCookie c(cookie); if (c.domain().isEmpty()) { c.setDomain(url.host()); } restoredCookies.append(c); } Q_EMIT setNetworkCookiesCalled(m_lastRequestId, url, restoredCookies); return m_lastRequestId; } void onTimerTimeout() { QObject *timer = sender(); int requestId = timer->property("requestId").toInt(); Cookies failedCookies = timer->property("failedCookies").value(); sendReply(requestId, failedCookies); } Q_SIGNALS: void setCookiesResponse(int requestId, const QVariant& failedCookies); void setNetworkCookiesCalled(int requestId, const QUrl& url, const QList& cookies); private: int m_lastRequestId; QSet m_failUrls; }; class OxideCookieHelperTest : public QObject { Q_OBJECT private Q_SLOTS: void testSetCookiesSanity(); void testSetCookies_data(); void testSetCookies(); void testSetCookiesSubDomain(); void testImmediateFailure_data(); void testImmediateFailure(); }; void OxideCookieHelperTest::testSetCookiesSanity() { OxideCookieHelper helper; QSignalSpy cookiesSet(&helper, SIGNAL(cookiesSet(const QList&))); QVERIFY(helper.oxideStoreBackend() == 0); Cookies cookies = QNetworkCookie::parseCookies("a=2\nb=3"); QCOMPARE(cookies.count(), 2); helper.setCookies(cookies); QTest::qWait(10); QCOMPARE(cookiesSet.count(), 0); CookieBackend backend; helper.setOxideStoreBackend(&backend); QCOMPARE(helper.oxideStoreBackend(), &backend); QCOMPARE(helper.property("oxideStoreBackend").value(), &backend); } void OxideCookieHelperTest::testSetCookies_data() { QTest::addColumn("domain1"); QTest::addColumn("cookies1"); QTest::addColumn("timeout1"); QTest::addColumn("failedcookies1"); QTest::addColumn("domain2"); QTest::addColumn("cookies2"); QTest::addColumn("timeout2"); QTest::addColumn("failedcookies2"); QTest::addColumn("domain3"); QTest::addColumn("cookies3"); QTest::addColumn("timeout3"); QTest::addColumn("failedcookies3"); QTest::newRow("empty") << QString() << QString() << 0 << QString() << QString() << QString() << 0 << QString() << QString() << QString() << 0 << QString(); QTest::newRow("one domain, success") << "example.org" << "a=0; Domain=example.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT\n" "b=2; Domain=example.org; HttpOnly\n" "c=something; Domain=example.org; Secure" << 10 << QString() << QString() << QString() << 0 << QString() << QString() << QString() << 0 << QString(); QTest::newRow("one domain, with one failure") << "example.org" << "a=0; Domain=example.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT\n" "b=2; Domain=example.org; HttpOnly\n" "c=something; Domain=example.org; Secure" << 10 << "b=2; Domain=example.org; HttpOnly" << QString() << QString() << 0 << QString() << QString() << QString() << 0 << QString(); QTest::newRow("three domains, success") << "example.org" << "a=0; Domain=example.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT\n" "m=2; Domain=example.org; HttpOnly\n" "z=something; Domain=example.org; Secure" << 10 << QString() << "domain.net" << "c=4; Domain=domain.net; HttpOnly\n" "r=no; Domain=domain.net; Expires=Wed, 13 Jan 2021 22:23:01 GMT" << 20 << QString() << "sub.site.org" << "d=yes; Domain=sub.site.org; Secure\n" "e=3; Domain=sub.site.org; HttpOnly\n" "z=last; Domain=sub.site.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT" << 40 << QString(); QTest::newRow("three domains, some failures") << "example.org" << "a=0; Domain=example.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT\n" "m=2; Domain=example.org; HttpOnly\n" "z=something; Domain=example.org; Secure" << 10 << QString() << "domain.net" << "c=4; Domain=domain.net; HttpOnly\n" "r=no; Domain=domain.net; Expires=Wed, 13 Jan 2021 22:23:01 GMT" << 40 << "c=4; Domain=domain.net; HttpOnly" << "sub.site.org" << "d=yes; Domain=sub.site.org; Secure\n" "e=3; Domain=sub.site.org; HttpOnly\n" "z=last; Domain=sub.site.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT" << 20 << "e=3; Domain=sub.site.org; HttpOnly\n" "z=last; Domain=sub.site.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT"; } struct DomainData { DomainData(): timeout(0) {} DomainData(const QString& rawCookies, int t, const QString& rawFailedCookies): cookies(QNetworkCookie::parseCookies(rawCookies.toUtf8())), timeout(t), failedCookies(QNetworkCookie::parseCookies(rawFailedCookies.toUtf8())) {} Cookies cookies; int timeout; Cookies failedCookies; }; static bool cookieCompare(const QNetworkCookie a, const QNetworkCookie b) { return a.name() < b.name(); } void OxideCookieHelperTest::testSetCookies() { QFETCH(QString, domain1); QFETCH(QString, cookies1); QFETCH(int, timeout1); QFETCH(QString, failedcookies1); QFETCH(QString, domain2); QFETCH(QString, cookies2); QFETCH(int, timeout2); QFETCH(QString, failedcookies2); QFETCH(QString, domain3); QFETCH(QString, cookies3); QFETCH(int, timeout3); QFETCH(QString, failedcookies3); /* Build a data structure easier to handle */ QMap domains; if (!domain1.isEmpty()) domains[domain1] = DomainData(cookies1, timeout1, failedcookies1); if (!domain2.isEmpty()) domains[domain2] = DomainData(cookies2, timeout2, failedcookies2); if (!domain3.isEmpty()) domains[domain3] = DomainData(cookies3, timeout3, failedcookies3); OxideCookieHelper helper; QSignalSpy cookiesSet(&helper, SIGNAL(cookiesSet(const QList&))); CookieBackend backend; QSignalSpy setNetworkCookiesCalled(&backend, SIGNAL(setNetworkCookiesCalled(int,const QUrl&,const QList&))); helper.setOxideStoreBackend(&backend); /* Build the list of cookies */ Cookies cookies; Cookies expectedFailedCookies; Q_FOREACH(const DomainData &domainData, domains) { cookies.append(domainData.cookies); expectedFailedCookies.append(domainData.failedCookies); } qSort(cookies.begin(), cookies.end(), cookieCompare); helper.setCookies(cookies); QCOMPARE(setNetworkCookiesCalled.count(), domains.count()); QList setNetworkCookiesCalls = setNetworkCookiesCalled; Q_FOREACH(const QVariantList &args, setNetworkCookiesCalls) { int requestId = args.at(0).toInt(); QUrl url = args.at(1).toUrl(); Cookies domainCookies = args.at(2).value(); QVERIFY(domains.contains(url.host())); /* Compare the cookies lists; we don't care about the order, so let's * sort them before comparing */ const DomainData &domainData = domains[url.host()]; qSort(domainCookies.begin(), domainCookies.end(), cookieCompare); Cookies expectedCookies = domainData.cookies; qSort(expectedCookies.begin(), expectedCookies.end(), cookieCompare); QCOMPARE(domainCookies, expectedCookies); QTimer* timer = new QTimer(&backend); timer->setSingleShot(true); timer->setInterval(domainData.timeout); timer->setProperty("requestId", requestId); timer->setProperty("failedCookies", QVariant::fromValue(domainData.failedCookies)); QObject::connect(timer, SIGNAL(timeout()), &backend, SLOT(onTimerTimeout())); timer->start(); } QVERIFY(cookiesSet.wait()); QCOMPARE(cookiesSet.count(), 1); /* Compare the failed cookies */ qSort(expectedFailedCookies.begin(), expectedFailedCookies.end(), cookieCompare); Cookies failedCookies = cookiesSet.at(0).at(0).value(); qSort(failedCookies.begin(), failedCookies.end(), cookieCompare); QCOMPARE(failedCookies, expectedFailedCookies); } void OxideCookieHelperTest::testSetCookiesSubDomain() { OxideCookieHelper helper; QSignalSpy cookiesSet(&helper, SIGNAL(cookiesSet(const QList&))); Cookies cookies = QNetworkCookie::parseCookies("a=2; Domain=.example.org"); QCOMPARE(cookies.count(), 1); CookieBackend backend; QSignalSpy setNetworkCookiesCalled(&backend, SIGNAL(setNetworkCookiesCalled(int,const QUrl&,const QList&))); helper.setOxideStoreBackend(&backend); helper.setCookies(cookies); QCOMPARE(setNetworkCookiesCalled.count(), 1); QUrl url = setNetworkCookiesCalled.at(0).at(1).toUrl(); QCOMPARE(url.host(), QString("example.org")); } void OxideCookieHelperTest::testImmediateFailure_data() { QTest::addColumn("rawCookies"); QTest::addColumn >("failUrls"); QTest::addColumn("rawFailedcookies"); QSet failUrls; failUrls.insert(QUrl("http://example.org")); QTest::newRow("one domain, one failure") << "a=0; Domain=example.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT\n" "b=2; Domain=example.org; HttpOnly" << failUrls << "a=0; Domain=example.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT\n" "b=2; Domain=example.org; HttpOnly"; failUrls.clear(); failUrls.insert(QUrl("http://domain.net")); QTest::newRow("multiple domains, one failure") << "a=0; Domain=example.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT\n" "b=2; Domain=example.org; HttpOnly\n" "c=4; Domain=domain.net; HttpOnly\n" "d=yes; Domain=sub.site.org; Secure\n" "e=3; Domain=sub.site.org; HttpOnly\n" "z=last; Domain=sub.site.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT" << failUrls << "c=4; Domain=domain.net; HttpOnly"; failUrls.clear(); failUrls.insert(QUrl("http://example.org")); failUrls.insert(QUrl("http://domain.net")); failUrls.insert(QUrl("http://sub.site.org")); QTest::newRow("multiple domains, all failures") << "a=0; Domain=example.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT\n" "b=2; Domain=example.org; HttpOnly\n" "c=4; Domain=domain.net; HttpOnly\n" "d=yes; Domain=sub.site.org; Secure\n" "e=3; Domain=sub.site.org; HttpOnly\n" "z=last; Domain=sub.site.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT" << failUrls << "a=0; Domain=example.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT\n" "b=2; Domain=example.org; HttpOnly\n" "c=4; Domain=domain.net; HttpOnly\n" "d=yes; Domain=sub.site.org; Secure\n" "e=3; Domain=sub.site.org; HttpOnly\n" "z=last; Domain=sub.site.org; Expires=Wed, 13 Jan 2021 22:23:01 GMT"; failUrls.clear(); } void OxideCookieHelperTest::testImmediateFailure() { QFETCH(QString, rawCookies); QFETCH(QSet, failUrls); QFETCH(QString, rawFailedcookies); OxideCookieHelper helper; QSignalSpy cookiesSet(&helper, SIGNAL(cookiesSet(const QList&))); CookieBackend backend; QSignalSpy setNetworkCookiesCalled(&backend, SIGNAL(setNetworkCookiesCalled(int,const QUrl&,const QList&))); helper.setOxideStoreBackend(&backend); backend.setFailUrls(failUrls); /* Build the list of cookies */ Cookies cookies = QNetworkCookie::parseCookies(rawCookies.toUtf8()); Cookies expectedFailedCookies = QNetworkCookie::parseCookies(rawFailedcookies.toUtf8()); helper.setCookies(cookies); /* If there were valid calls, reply to them */ QList setNetworkCookiesCalls = setNetworkCookiesCalled; Q_FOREACH(const QVariantList &args, setNetworkCookiesCalls) { int requestId = args.at(0).toInt(); QUrl url = args.at(1).toUrl(); Cookies domainCookies = args.at(2).value(); QTimer* timer = new QTimer(&backend); timer->setSingleShot(true); timer->setInterval(5); timer->setProperty("requestId", requestId); QObject::connect(timer, SIGNAL(timeout()), &backend, SLOT(onTimerTimeout())); timer->start(); } QVERIFY(cookiesSet.wait()); QCOMPARE(cookiesSet.count(), 1); /* Compare the failed cookies */ qSort(expectedFailedCookies.begin(), expectedFailedCookies.end(), cookieCompare); Cookies failedCookies = cookiesSet.at(0).at(0).value(); qSort(failedCookies.begin(), failedCookies.end(), cookieCompare); QCOMPARE(failedCookies, expectedFailedCookies); } QTEST_MAIN(OxideCookieHelperTest) #include "tst_OxideCookieHelper.moc" ./tests/unittests/oxide-cookie-helper/CMakeLists.txt0000644000015600001650000000073712703462031022702 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_OxideCookieHelperTests) set(SOURCES ${webapp-container_SOURCE_DIR}/oxide-cookie-helper.cpp tst_OxideCookieHelper.cpp ) add_executable(${TEST} ${SOURCES}) include_directories(${webapp-container_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Network Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/single-instance-manager/0000755000015600001650000000000012703462032020773 5ustar jenkinsjenkins./tests/unittests/single-instance-manager/tst_SingleInstanceManagerTests.cpp0000644000015600001650000000404112703462031027613 0ustar jenkinsjenkins/* * Copyright 2016 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include #include // local #include "single-instance-manager.h" class SingleInstanceManagerTests : public QObject { Q_OBJECT private: SingleInstanceManager* singleton; QSignalSpy* newInstanceSpy; private Q_SLOTS: void init() { QStandardPaths::setTestModeEnabled(true); singleton = new SingleInstanceManager(this); newInstanceSpy = new QSignalSpy(singleton, SIGNAL(newInstanceLaunched(const QStringList&))); } void cleanup() { delete newInstanceSpy; delete singleton; QStandardPaths::setTestModeEnabled(false); } void test_cannot_run_twice_same_instance() { QVERIFY(singleton->run(QStringList())); QVERIFY(!singleton->run(QStringList())); QVERIFY(newInstanceSpy->isEmpty()); } void test_arguments_passed_to_already_running_instance() { QVERIFY(singleton->run(QStringList())); SingleInstanceManager other; QStringList args; args << QStringLiteral("foo") << QStringLiteral("bar") << QStringLiteral("baz"); QVERIFY(!other.run(args)); newInstanceSpy->wait(); QCOMPARE(newInstanceSpy->first().at(0).toStringList(), args); } }; QTEST_MAIN(SingleInstanceManagerTests) #include "tst_SingleInstanceManagerTests.moc" ./tests/unittests/single-instance-manager/CMakeLists.txt0000644000015600001650000000110112703462031023523 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_SingleInstanceManagerTests) set(SOURCES ${webbrowser-common_SOURCE_DIR}/single-instance-manager.cpp tst_SingleInstanceManagerTests.cpp ) add_executable(${TEST} ${SOURCES}) include_directories(${webbrowser-common_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Network Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) set_tests_properties(${TEST} PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal") ./tests/unittests/sanity/0000755000015600001650000000000012703462031015606 5ustar jenkinsjenkins./tests/unittests/sanity/CMakeLists.txt0000644000015600001650000000010112703462031020336 0ustar jenkinsjenkinsadd_test(flake8 python3 -m flake8 ${autopilot-tests_SOURCE_DIR}) ./tests/unittests/text-search-filter-model/0000755000015600001650000000000012703462032021110 5ustar jenkinsjenkins./tests/unittests/text-search-filter-model/tst_TextSearchFilterModelTests.cpp0000644000015600001650000001523112703462031027733 0ustar jenkinsjenkins/* * Copyright 2013-2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include // local #include "history-model.h" #include "text-search-filter-model.h" class TextSearchFilterModelTests : public QObject { Q_OBJECT private: HistoryModel* model; TextSearchFilterModel* matches; private Q_SLOTS: void init() { model = new HistoryModel; model->setDatabasePath(":memory:"); matches = new TextSearchFilterModel; matches->setSourceModel(QVariant::fromValue(model)); } void cleanup() { delete matches; delete model; } void shouldBeInitiallyEmpty() { QCOMPARE(matches->rowCount(), 0); } void shouldNotifyWhenChangingSourceModel() { QSignalSpy spy(matches, SIGNAL(sourceModelChanged())); matches->setSourceModel(QVariant::fromValue(model)); QVERIFY(spy.isEmpty()); HistoryModel* model2 = new HistoryModel; matches->setSourceModel(QVariant::fromValue(model2)); QCOMPARE(spy.count(), 1); QCOMPARE(matches->sourceModel(), QVariant::fromValue(model2)); matches->setSourceModel(QVariant()); QCOMPARE(spy.count(), 2); QVERIFY(matches->sourceModel().isNull()); delete model2; } void shouldReturnAllWhileTermsAndOrFieldsEmpty() { model->add(QUrl("http://example.org"), "Example Domain", QUrl()); model->add(QUrl("http://example.com"), "Example Domain", QUrl()); QCOMPARE(matches->rowCount(), 2); matches->setTerms(QStringList({"org"})); QCOMPARE(matches->rowCount(), 2); matches->setSearchFields(QStringList({"url"})); QCOMPARE(matches->rowCount(), 1); matches->setTerms(QStringList()); QCOMPARE(matches->rowCount(), 2); } void shouldRecordTerms() { QVERIFY(matches->terms().isEmpty()); QStringList terms; terms.append("example"); matches->setTerms(terms); QCOMPARE(matches->terms(), terms); } void shouldRecordSearchFields() { QVERIFY(matches->searchFields().isEmpty()); QStringList fields; fields.append("url"); matches->setSearchFields(fields); QCOMPARE(matches->searchFields(), fields); } void shouldMatch() { model->add(QUrl("http://example.org"), "Example Domain", QUrl()); model->add(QUrl("http://example.com"), "Example Domain", QUrl()); matches->setTerms(QStringList({"example"})); matches->setSearchFields(QStringList({"url", "title"})); QCOMPARE(matches->rowCount(), 2); } void shouldMatchSecondField() { model->add(QUrl("http://example.org"), "Example Domain", QUrl()); model->add(QUrl("http://example.com"), "Example Domain", QUrl()); matches->setTerms(QStringList({"domain"})); matches->setSearchFields(QStringList({"url", "title"})); QCOMPARE(matches->rowCount(), 2); } void shouldFilterOutNotMatchingEntries() { model->add(QUrl("http://example.org"), "Example Domain", QUrl()); model->add(QUrl("http://ubuntu.com"), "Home | Ubuntu", QUrl()); model->add(QUrl("http://example.com"), "Example Domain", QUrl()); model->add(QUrl("http://wikipedia.org"), "Wikipedia", QUrl()); matches->setTerms(QStringList({"domain"})); matches->setSearchFields(QStringList({"url", "title"})); QCOMPARE(matches->rowCount(), 2); } void shouldUpdateResultsWhenTermsChange() { model->add(QUrl("http://example.org"), "Example Domain", QUrl()); model->add(QUrl("http://ubuntu.com"), "Home | Ubuntu", QUrl()); model->add(QUrl("http://wikipedia.org"), "Wikipedia", QUrl()); model->add(QUrl("http://ubuntu.com/download"), "Download Ubuntu | Ubuntu", QUrl()); matches->setSearchFields(QStringList({"url", "title"})); matches->setTerms(QStringList({"ubuntu"})); QCOMPARE(matches->rowCount(), 2); matches->setTerms(QStringList({"wiki"})); QCOMPARE(matches->rowCount(), 1); } void shouldUpdateResultsWhenSourceModelUpdates() { model->add(QUrl("http://example.org"), "Example Domain", QUrl()); model->add(QUrl("http://wikipedia.org"), "Wikipedia", QUrl()); matches->setTerms(QStringList({"ubuntu"})); matches->setSearchFields(QStringList({"url"})); QCOMPARE(matches->rowCount(), 0); model->add(QUrl("http://ubuntu.com"), "Home | Ubuntu", QUrl()); QCOMPARE(matches->rowCount(), 1); } void shouldMatchAllTerms() { model->add(QUrl("http://example.org"), "Example Domain", QUrl()); model->add(QUrl("http://example.com"), "Example Domain", QUrl()); matches->setTerms(QStringList({"example", "org"})); matches->setSearchFields(QStringList({"url", "title"})); QCOMPARE(matches->rowCount(), 1); } void shouldMatchTermsInDifferentFields() { model->add(QUrl("http://example.org"), "Example Domain", QUrl()); model->add(QUrl("http://example.com"), "Example Domain", QUrl()); matches->setTerms(QStringList({"org", "domain"})); matches->setSearchFields(QStringList({"url", "title"})); QCOMPARE(matches->rowCount(), 1); } void shouldMatchDuplicateTerms() { model->add(QUrl("http://example.org"), "Example Domain", QUrl()); model->add(QUrl("http://example.com"), "Example Domain", QUrl()); matches->setTerms(QStringList({"org", "org", "org", "org"})); matches->setSearchFields(QStringList({"url", "title"})); QCOMPARE(matches->rowCount(), 1); } void shouldWarnOnInvalidFields() { QTest::ignoreMessage(QtWarningMsg, "Source model does not have role matching field: \"foo\""); model->add(QUrl("http://example.org"), "Example Domain", QUrl()); matches->setTerms(QStringList({"org"})); matches->setSearchFields(QStringList({"url", "foo"})); QCOMPARE(matches->count(), 1); } }; QTEST_MAIN(TextSearchFilterModelTests) #include "tst_TextSearchFilterModelTests.moc" ./tests/unittests/text-search-filter-model/CMakeLists.txt0000644000015600001650000000064412703462031023653 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Sql REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_TextSearchFilterModelTests) add_executable(${TEST} tst_TextSearchFilterModelTests.cpp) include_directories(${webbrowser-app_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Sql Qt5::Test webbrowser-app-models ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/intent-filter/0000755000015600001650000000000012703462032017064 5ustar jenkinsjenkins./tests/unittests/intent-filter/tst_IntentFilterTests.cpp0000644000015600001650000001576212703462031024126 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include // local #include "scheme-filter.h" #include "intent-parser.h" class IntentFilterTests : public QObject { Q_OBJECT private Q_SLOTS: void parseIntentUris_data() { QTest::addColumn("intentUris"); QTest::addColumn("scheme"); QTest::addColumn("package"); QTest::addColumn("uri"); QTest::addColumn("host"); QTest::addColumn("action"); QTest::addColumn("component"); QTest::addColumn("category"); QTest::addColumn("isValid"); QTest::newRow("Valid intent - host only") << "intent://scan/#Intent;component=com;scheme=zxing;category=BROWSABLE;action=com;package=com.google.zxing.client.android;end" << "zxing" << "com.google.zxing.client.android" << "/" << "scan" << "com" << "com" << "BROWSABLE" << true; QTest::newRow("Valid intent - no host w/ uri") << "intent://scan/?a=1/#Intent;component=com;scheme=zxing;category=BROWSABLE;action=com;package=com.google.zxing.client.android;end" << "zxing" << "com.google.zxing.client.android" << "?a=1" << "scan" << "com" << "com" << "BROWSABLE" << true; QTest::newRow("Valid intent - w/ host") << "intent://host/my/long/path?a=1/#Intent;component=com;scheme=zxing;category=BROWSABLE;action=com;package=com.google.zxing.client.android;end" << "zxing" << "com.google.zxing.client.android" << "my/long/path?a=1" << "host" << "com" << "com" << "BROWSABLE" << true; QTest::newRow("Valid intent - w/o host & uri-path") << "intent://#Intent;scheme=trusper.referrertests;package=trusper.referrertests;end" << "trusper.referrertests" << "trusper.referrertests" << "" << "" << "" << "" << "" << true; QTest::newRow("Valid intent - w/o host & uri-path and extra /") << "intent:///#Intent;component=com;scheme=zxing;category=BROWSABLE;action=com;package=com.google.zxing.client.android;end" << "zxing" << "com.google.zxing.client.android" << "/" << "" << "com" << "com" << "BROWSABLE" << true; QTest::newRow("Valid intent - w/o host & uri-path impler syntax") << "intent://#Intent;component=com;scheme=zxing;category=BROWSABLE;action=com;package=com.google.zxing.client.android;end" << "zxing" << "com.google.zxing.client.android" << "" << "" << "com" << "com" << "BROWSABLE" << true; QTest::newRow("Invalid intent") << "intent:///#Inttent;component=com;scheme=zxing;category=BROWSABLE;action=com;package=com.google.zxing.client.android;end" << "" << "" << "" << "" << "" << "" << "" << false; } void parseIntentUris() { QFETCH(QString, intentUris); QFETCH(QString, scheme); QFETCH(QString, package); QFETCH(QString, uri); QFETCH(QString, host); QFETCH(QString, action); QFETCH(QString, component); QFETCH(QString, category); QFETCH(bool, isValid); IntentUriDescription d = parseIntentUri(intentUris); QCOMPARE(d.scheme, scheme); QCOMPARE(d.package, package); QCOMPARE(d.uriPath, uri); QCOMPARE(d.host, host); QCOMPARE(d.action, action); QCOMPARE(d.component, component); QCOMPARE(d.category, category); } void applyFilters_data() { QTest::addColumn("intentUris"); QTest::addColumn("filterFunctionSource"); QTest::addColumn("scheme"); QTest::addColumn("uri"); QTest::addColumn("host"); QTest::newRow("Valid intent - default filter function") << "intent://scan/#Intent;component=com;scheme=zxing;category=BROWSABLE;action=com;package=com.google.zxing.client.android;end" << "" << "zxing" << "/" << "scan"; QTest::newRow("Valid intent - default filter function") << "intent://scan/#Intent;component=com;scheme=zxing;category=BROWSABLE;action=com;package=com.google.zxing.client.android;end" << "(function(result) {return {'scheme': result.scheme+'custom', 'path': result.path+'custom', 'host': result.host+'custom'}; })" << "zxingcustom" << "/custom" << "scancustom"; QTest::newRow("Valid intent - no (optional) host in filter result") << "intent://host/my/long/path?a=1/#Intent;component=com;scheme=zxing;category=BROWSABLE;action=com;package=com.google.zxing.client.android;end" << "(function(result) {return {'scheme': result.scheme+'custom', 'path': result.path+'custom' }; })" << "zxingcustom" << "my/long/path?a=1custom" << ""; } void applyFilters() { QFETCH(QString, intentUris); QFETCH(QString, filterFunctionSource); QFETCH(QString, scheme); QFETCH(QString, uri); QFETCH(QString, host); QMap filters; filters["intent"] = filterFunctionSource; SchemeFilter sf(filters); QVariantMap r = sf.applyFilter(intentUris); QVERIFY(r.contains("scheme")); QVERIFY(r.contains("path")); QCOMPARE(r.value("scheme").toString(), scheme); QCOMPARE(r.value("host").toString(), host); QCOMPARE(r.value("path").toString(), uri); } }; QTEST_MAIN(IntentFilterTests) #include "tst_IntentFilterTests.moc" ./tests/unittests/intent-filter/CMakeLists.txt0000644000015600001650000000100112703462031021613 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Qml REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_IntentFilterTests) set(SOURCES ${webapp-container_SOURCE_DIR}/intent-parser.cpp ${webapp-container_SOURCE_DIR}/scheme-filter.cpp tst_IntentFilterTests.cpp ) include_directories(${webapp-container_SOURCE_DIR}) add_executable(${TEST} ${SOURCES}) target_link_libraries(${TEST} Qt5::Core Qt5::Qml Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/webapp-container-color-helper/0000755000015600001650000000000012703462032022127 5ustar jenkinsjenkins./tests/unittests/webapp-container-color-helper/CMakeLists.txt0000644000015600001650000000106512703462031024670 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Gui REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_WebappContainerColorTests) set(SOURCES ${webapp-container_SOURCE_DIR}/webapp-container-helper.cpp tst_WebappContainerColorTests.cpp ) add_executable(${TEST} ${SOURCES}) include_directories(${webapp-container_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Gui Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) set_tests_properties(${TEST} PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal") ./tests/unittests/webapp-container-color-helper/tst_WebappContainerColorTests.cpp0000644000015600001650000000423312703462031030631 0ustar jenkinsjenkins/* * Copyright 2016 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include // local #include "webapp-container-helper.h" class ContainerColorTests : public QObject { Q_OBJECT private Q_SLOTS: void cssColorToRgbTest_data() { QTest::addColumn("csscolor"); QTest::addColumn("rgb"); QTest::newRow("Empty CSS color") << " " << ""; QTest::newRow("Invalid CSS color") << " invalid " << ""; QTest::newRow("Invalid RGB CSS color") << " rgb (1,1," << ""; QTest::newRow("Invalid RGB CSS color 2") << " rgb (1,1 0)" << ""; QTest::newRow("Invalid RGB CSS color 3") << " rgb (1, e, 0)" << ""; QTest::newRow("Valid SVG name CSS color") << " red " << "255,0,0"; QTest::newRow("Valid RGB CSS color") << " rgb (255, 0, 0) " << "255,0,0"; QTest::newRow("Valid plain RGB CSS color") << " #FF0000 " << "255,0,0"; QTest::newRow("Valid short plain RGB CSS color") << " #36699 " << "3,102,153"; QTest::newRow("Valid shortest plain RGB CSS color") << " #366 " << "51,102,102"; QTest::newRow("Valid very short plain RGB CSS color") << " #000 " << "0,0,0"; QTest::newRow("Valid SVG name CSS color") << " #FF000044 " << "0,0,68"; } void cssColorToRgbTest() { QFETCH(QString, csscolor); QFETCH(QString, rgb); WebappContainerHelper helper; QCOMPARE(helper.rgbColorFromCSSColor(csscolor), rgb); } }; QTEST_MAIN(ContainerColorTests) #include "tst_WebappContainerColorTests.moc" ./tests/unittests/downloads-model/0000755000015600001650000000000012703462032017370 5ustar jenkinsjenkins./tests/unittests/downloads-model/tst_DownloadsModelTests.cpp0000644000015600001650000001626712703462031024737 0ustar jenkinsjenkins/* * Copyright 2015-2016 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include "downloads-model.h" class DownloadsModelTests : public QObject { Q_OBJECT private: DownloadsModel* model; private Q_SLOTS: void init() { model = new DownloadsModel; model->setDatabasePath(":memory:"); } void cleanup() { delete model; } void shouldBeInitiallyEmpty() { QCOMPARE(model->rowCount(), 0); } void shouldExposeRoleNames() { QList roleNames = model->roleNames().values(); QVERIFY(roleNames.contains("downloadId")); QVERIFY(roleNames.contains("url")); QVERIFY(roleNames.contains("path")); QVERIFY(roleNames.contains("filename")); QVERIFY(roleNames.contains("mimetype")); QVERIFY(roleNames.contains("complete")); QVERIFY(roleNames.contains("paused")); QVERIFY(roleNames.contains("error")); QVERIFY(roleNames.contains("created")); } void shouldContainAddedEntries() { QVERIFY(!model->contains(QStringLiteral("testid"))); model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/html")); QVERIFY(model->contains(QStringLiteral("testid"))); } void shouldAddNewEntries() { QSignalSpy spy(model, SIGNAL(added(QString, QUrl, QString))); model->add("testid", QUrl("http://example.org/"), "text/plain"); QCOMPARE(model->rowCount(), 1); QCOMPARE(spy.count(), 1); QVariantList args = spy.takeFirst(); QCOMPARE(args.at(0).toString(), QString("testid")); QCOMPARE(args.at(1).toUrl(), QUrl("http://example.org/")); QCOMPARE(args.at(2).toString(), QString("text/plain")); model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); QCOMPARE(model->rowCount(), 2); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(0).toString(), QString("testid2")); QCOMPARE(args.at(1).toUrl(), QUrl("http://example.org/pdf")); QCOMPARE(args.at(2).toString(), QString("application/pdf")); } void shouldRemoveCancelled() { model->add("testid", QUrl("http://example.org/"), "text/plain"); model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); model->add("testid3", QUrl("https://example.org/secure.png"), "image/png"); QCOMPARE(model->rowCount(), 3); model->cancelDownload("testid2"); QCOMPARE(model->rowCount(), 2); model->cancelDownload("invalid"); QCOMPARE(model->rowCount(), 2); } void shouldCompleteDownloads() { QSignalSpy spy(model, SIGNAL(completeChanged(QString, bool))); model->add("testid", QUrl("http://example.org/"), "text/plain"); QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); model->setComplete("testid", true); QCOMPARE(spy.count(), 1); QVariantList args = spy.takeFirst(); QCOMPARE(args.at(0).toString(), QString("testid")); QCOMPARE(args.at(1).toBool(), true); } void shouldKeepEntriesSortedChronologically() { model->add("testid", QUrl("http://example.org/"), "text/plain"); model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); model->add("testid3", QUrl("https://example.org/secure.png"), "image/png"); QCOMPARE(model->data(model->index(0, 0), DownloadsModel::DownloadId).toString(), QString("testid3")); QCOMPARE(model->data(model->index(1, 0), DownloadsModel::DownloadId).toString(), QString("testid2")); QCOMPARE(model->data(model->index(2, 0), DownloadsModel::DownloadId).toString(), QString("testid")); } void shouldReturnData() { model->add("testid", QUrl("http://example.org/"), "text/plain"); QVERIFY(!model->data(QModelIndex(), DownloadsModel::DownloadId).isValid()); QVERIFY(!model->data(model->index(-1, 0), DownloadsModel::DownloadId).isValid()); QVERIFY(!model->data(model->index(3, 0), DownloadsModel::DownloadId).isValid()); QCOMPARE(model->data(model->index(0, 0), DownloadsModel::DownloadId).toString(), QString("testid")); QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Url).toUrl(), QUrl("http://example.org/")); QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Mimetype).toString(), QString("text/plain")); QVERIFY(model->data(model->index(0, 0), DownloadsModel::Created).toDateTime() <= QDateTime::currentDateTime()); QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); } void shouldReturnDatabasePath() { QCOMPARE(model->databasePath(), QString(":memory:")); } void shouldNotifyWhenSettingDatabasePath() { QSignalSpy spyPath(model, SIGNAL(databasePathChanged())); QSignalSpy spyReset(model, SIGNAL(modelReset())); model->setDatabasePath(":memory:"); QVERIFY(spyPath.isEmpty()); QVERIFY(spyReset.isEmpty()); model->setDatabasePath(""); QCOMPARE(spyPath.count(), 1); QCOMPARE(spyReset.count(), 1); QCOMPARE(model->databasePath(), QString(":memory:")); } void shouldSerializeOnDisk() { QTemporaryFile tempFile; tempFile.open(); QString fileName = tempFile.fileName(); delete model; model = new DownloadsModel; model->setDatabasePath(fileName); model->add("testid", QUrl("http://example.org/"), "text/plain"); model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); delete model; model = new DownloadsModel; model->setDatabasePath(fileName); model->fetchMore(); QCOMPARE(model->rowCount(), 2); } void shouldCountNumberOfEntries() { QCOMPARE(model->property("count").toInt(), 0); QCOMPARE(model->rowCount(), 0); model->add("testid", QUrl("http://example.org/"), "text/plain"); QCOMPARE(model->property("count").toInt(), 1); QCOMPARE(model->rowCount(), 1); model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); QCOMPARE(model->property("count").toInt(), 2); QCOMPARE(model->rowCount(), 2); model->add("testid3", QUrl("https://example.org/secure.png"), "image/png"); QCOMPARE(model->property("count").toInt(), 3); QCOMPARE(model->rowCount(), 3); } }; QTEST_MAIN(DownloadsModelTests) #include "tst_DownloadsModelTests.moc" ./tests/unittests/downloads-model/CMakeLists.txt0000644000015600001650000000062612703462031022133 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Sql REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_DownloadsModelTests) add_executable(${TEST} tst_DownloadsModelTests.cpp) include_directories(${webbrowser-app_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Sql Qt5::Test webbrowser-app-models ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/favicon-fetcher/0000755000015600001650000000000012703462032017343 5ustar jenkinsjenkins./tests/unittests/favicon-fetcher/tst_FaviconFetcherTests.cpp0000644000015600001650000002400312703462031024650 0ustar jenkinsjenkins/* * Copyright 2014-2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // system #include // Qt #include #include #include #include #include #include #include #include #include #include // local #include "favicon-fetcher.h" const unsigned char icon_data[] = { 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x38, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const int icon_data_size = 78; class TestHTTPServer : public QTcpServer { Q_OBJECT public: TestHTTPServer(QObject* parent = 0) : QTcpServer(parent) { connect(this, SIGNAL(newConnection()), SLOT(onNewConnection())); } QString baseURL() const { return "http://" + serverAddress().toString() + ":" + QString::number(serverPort()); } Q_SIGNALS: void gotRequest(const QString& path) const; void gotError() const; private Q_SLOTS: void onNewConnection() { if (hasPendingConnections()) { QTcpSocket* socket = nextPendingConnection(); connect(socket, SIGNAL(readyRead()), SLOT(readClient())); connect(socket, SIGNAL(disconnected()), SLOT(discardClient())); connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), SIGNAL(gotError())); } } void readClient() { QTcpSocket* socket = qobject_cast(sender()); if (!socket) { return; } if (!socket->canReadLine()) { return; } QStringList tokens = QString(socket->readLine()).split(QRegExp("[ \r\n][ \r\n]*")); if (tokens.isEmpty()) { return; } if (tokens.first() != "GET") { return; } QString path = tokens[1]; Q_EMIT gotRequest(path); QTextStream response(socket); response.setAutoDetectUnicode(true); QRegExp icon("/\\w+\\.ico"); QRegExp redirection("^/redirect/(\\d+)/(.*)"); if (icon.exactMatch(path)) { response << "HTTP/1.0 200 OK\r\n" << "Content-Length: " << icon_data_size << "\r\n" << "Content-Type: image/x-icon\r\n\r\n" << QString::fromLocal8Bit((const char*) icon_data, icon_data_size) << "\n"; } else if (redirection.exactMatch(path)) { int n = redirection.cap(1).toInt(); response << "HTTP/1.0 303 See Other\r\n" << "Content-Length: 9\r\n" << "Content-Type: text/plain\r\n" << "Location: " << baseURL(); if (n == 1) { response << "/" << redirection.cap(2); } else { response << "/redirect/" << (n - 1) << "/" << redirection.cap(2); } response << "\r\n\r\n" << "see other\n"; } else { response << "HTTP/1.0 404 Not Found\r\n" << "Content-Length: 9\r\n" << "Content-Type: text/plain\r\n\r\n" << "not found\n"; } response.flush(); socket->waitForBytesWritten(); socket->disconnectFromHost(); } void discardClient() { QTcpSocket* socket = qobject_cast(sender()); if (socket) { socket->deleteLater(); } } }; class FaviconFetcherTests : public QObject { Q_OBJECT private: FaviconFetcher* fetcher; QSignalSpy* fetcherSpy; TestHTTPServer* server; QSignalSpy* serverSpy; QSignalSpy* errorSpy; private Q_SLOTS: void init() { { FaviconFetcher temp; QDir(temp.cacheLocation()).removeRecursively(); } fetcher = new FaviconFetcher; fetcherSpy = new QSignalSpy(fetcher, SIGNAL(localUrlChanged())); server = new TestHTTPServer; server->listen(); serverSpy = new QSignalSpy(server, SIGNAL(gotRequest(const QString&))); errorSpy = new QSignalSpy(server, SIGNAL(gotError())); } void cleanup() { delete errorSpy; delete serverSpy; delete server; delete fetcherSpy; delete fetcher; } void shouldCacheIcon() { QUrl url(server->baseURL() + "/favicon1.ico"); fetcher->setUrl(url); QCOMPARE(fetcher->url(), url); QVERIFY(fetcherSpy->wait()); QCOMPARE(serverSpy->count(), 1); QString cached = fetcher->localUrl().path(); QVERIFY(cached.startsWith(fetcher->cacheLocation())); QVERIFY(QFileInfo::exists(cached)); } void shouldNotCacheLocalIcon() { QUrl url("file:///tmp/favicon.ico"); fetcher->setUrl(url); QCOMPARE(fetcherSpy->count(), 1); QVERIFY(serverSpy->isEmpty()); QCOMPARE(fetcher->localUrl(), url); QDir cache(fetcher->cacheLocation(), "", QDir::Unsorted, QDir::Files | QDir::NoDotAndDotDot); QCOMPARE(cache.count(), (uint) 0); } void shouldNotCacheInvalidIcon() { // First fetch a valid icon to ensure localUrl is initially not empty QUrl url(server->baseURL() + "/favicon1.ico"); fetcher->setUrl(url); QVERIFY(fetcherSpy->wait()); QVERIFY(!fetcher->localUrl().isEmpty()); // Then request an invalid one url = QUrl(server->baseURL() + "/invalid"); fetcher->setUrl(url); QVERIFY(serverSpy->wait()); QVERIFY(fetcher->localUrl().isEmpty()); } void shouldReturnCachedIcon() { // First fetch an icon so that it’s cached QUrl url(server->baseURL() + "/favicon1.ico"); fetcher->setUrl(url); QVERIFY(fetcherSpy->wait()); QUrl localUrl = fetcher->localUrl(); QVERIFY(!localUrl.isEmpty()); // Then fetch another icon fetcher->setUrl(QUrl(server->baseURL() + "/favicon2.ico")); QVERIFY(fetcherSpy->wait()); QVERIFY(!fetcher->localUrl().isEmpty()); // Then fetch the first icon again, and verify it comes from the cache serverSpy->clear(); fetcher->setUrl(url); QCOMPARE(fetcher->localUrl(), localUrl); QVERIFY(serverSpy->isEmpty()); } void shouldNotCacheIcon() { fetcher->setShouldCache(false); QUrl url(server->baseURL() + "/favicon1.ico"); fetcher->setUrl(url); QCOMPARE(fetcher->url(), url); QVERIFY(fetcherSpy->wait()); QCOMPARE(serverSpy->count(), 1); QUrl icon = fetcher->localUrl(); QVERIFY(!icon.isLocalFile()); QCOMPARE(icon.scheme(), QString("data")); QVERIFY(icon.path().startsWith("image/png;base64,")); } void shouldHandleRedirections() { QUrl url(server->baseURL() + "/redirect/3/favicon1.ico"); fetcher->setUrl(url); QVERIFY(fetcherSpy->wait()); QCOMPARE(serverSpy->count(), 4); } void shouldNotHandleTooManyRedirections() { QUrl url(server->baseURL() + "/redirect/8/favicon1.ico"); QString msg("Failed to download %1 : too many redirections"); QTest::ignoreMessage(QtWarningMsg, msg.arg(url.toString()).toUtf8()); fetcher->setUrl(url); QVERIFY(!fetcherSpy->wait(500)); QCOMPARE(serverSpy->count(), 5); } void shouldDiscardOldCachedIcons() { // First fetch an icon, and touch the cached file on disk to ensure // it will be considered out of date next time it’s requested QUrl url(server->baseURL() + "/favicon1.ico"); fetcher->setUrl(url); QVERIFY(fetcherSpy->wait()); QUrl localUrl = fetcher->localUrl(); struct utimbuf ubuf; ubuf.modtime = QDateTime::currentDateTime().addYears(-1).toTime_t(); QCOMPARE(utime(localUrl.path().toUtf8().constData(), &ubuf), 0); // Then fetch another icon fetcher->setUrl(QUrl(server->baseURL() + "/favicon2.ico")); QVERIFY(fetcherSpy->wait()); QVERIFY(!fetcher->localUrl().isEmpty()); // Then fetch the first icon again, and verify it is being re-downloaded serverSpy->clear(); fetcher->setUrl(url); QVERIFY(fetcherSpy->wait()); QCOMPARE(fetcher->localUrl(), localUrl); QCOMPARE(serverSpy->count(), 1); } void shouldCancelRequests() { // Issue several requests rapidly in succession, and verify that // all the previous ones are discarded. Take into account possible // errors (see https://launchpad.net/bugs/1498539). int requests = 100; int errors = 0; for (int i = 1; i <= requests; ++i) { QUrl url(server->baseURL() + "/favicon" + QString::number(i) + ".ico"); fetcher->setUrl(url); QVERIFY(serverSpy->wait(500) || (errorSpy->count() == ++errors)); } QVERIFY(fetcherSpy->wait()); QCOMPARE(serverSpy->count() + errorSpy->count(), requests); QCOMPARE(fetcherSpy->count(), 1); } }; QTEST_MAIN(FaviconFetcherTests) #include "tst_FaviconFetcherTests.moc" ./tests/unittests/favicon-fetcher/CMakeLists.txt0000644000015600001650000000112612703462031022102 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Gui REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_FaviconFetcherTests) set(SOURCES ${webbrowser-common_SOURCE_DIR}/favicon-fetcher.cpp tst_FaviconFetcherTests.cpp ) add_executable(${TEST} ${SOURCES}) include_directories(${webbrowser-common_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Gui Qt5::Network Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) set_tests_properties(${TEST} PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal") ./tests/unittests/bookmarks-folder-model/0000755000015600001650000000000012703462032020637 5ustar jenkinsjenkins./tests/unittests/bookmarks-folder-model/tst_BookmarksFolderModelTests.cpp0000644000015600001650000001067112703462031027331 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include // local #include "bookmarks-model.h" #include "bookmarks-folder-model.h" class BookmarksFolderModelTests : public QObject { Q_OBJECT private: BookmarksModel* bookmarks; BookmarksFolderModel* model; private Q_SLOTS: void init() { bookmarks = new BookmarksModel; bookmarks->setDatabasePath(":memory:"); model = new BookmarksFolderModel; model->setSourceModel(bookmarks); } void cleanup() { delete model; delete bookmarks; } void shouldBeInitiallyEmpty() { QCOMPARE(model->rowCount(), 0); } void shouldNotifyWhenChangingSourceModel() { QSignalSpy spy(model, SIGNAL(sourceModelChanged())); model->setSourceModel(bookmarks); QVERIFY(spy.isEmpty()); BookmarksModel* bookmarks2 = new BookmarksModel; model->setSourceModel(bookmarks2); QCOMPARE(spy.count(), 1); QCOMPARE(model->sourceModel(), bookmarks2); model->setSourceModel(0); QCOMPARE(spy.count(), 2); QCOMPARE(model->sourceModel(), (BookmarksModel*) 0); } void shouldNotifyWhenChangingFolder() { QSignalSpy spy(model, SIGNAL(folderChanged())); model->setFolder(QString()); QVERIFY(spy.isEmpty()); model->setFolder(QString("")); QVERIFY(spy.isEmpty()); model->setFolder("SampleFolder"); QCOMPARE(spy.count(), 1); } void shouldMatchAllNotInFoldersWhenNoFolderSet() { bookmarks->add(QUrl("http://example.org"), "Example Domain", QUrl(), ""); bookmarks->add(QUrl("http://example.com"), "Example Domain", QUrl(), "SampleFolder"); QCOMPARE(model->rowCount(), 1); } void shouldFilterOutNonMatchingFolders() { bookmarks->add(QUrl("http://example.org/"), "Example Domain Org", QUrl(), ""); bookmarks->add(QUrl("http://example.com/"), "Example Domain Com", QUrl(), "SampleFolder01"); bookmarks->add(QUrl("http://example.net/"), "Example Domain Net", QUrl("http://example.net/icon.png"), "SampleFolder02"); model->setFolder(""); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->data(model->index(0, 0), BookmarksModel::Url).toUrl(), QUrl("http://example.org/")); QCOMPARE(model->get(0).value("url").toUrl(), QUrl("http://example.org/")); model->setFolder("SampleFolder01"); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->data(model->index(0, 0), BookmarksModel::Url).toUrl(), QUrl("http://example.com/")); QCOMPARE(model->get(0).value("title").toString(), QString("Example Domain Com")); model->setFolder("SampleFolder02"); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->data(model->index(0, 0), BookmarksModel::Url).toUrl(), QUrl("http://example.net/")); QCOMPARE(model->get(0).value("icon").toUrl(), QUrl("http://example.net/icon.png")); model->setFolder("AnotherFolder"); QCOMPARE(model->rowCount(), 0); } void shouldMatchCaseSensitiveFolderName() { bookmarks->add(QUrl("http://example.org/"), "Example Domain", QUrl(), "SAMPLE"); bookmarks->add(QUrl("http://example.com/"), "Example Domain", QUrl(), "sample"); model->setFolder("SAMPLE"); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->data(model->index(0, 0), BookmarksModel::Url).toUrl(), QUrl("http://example.org/")); model->setFolder("sample"); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->data(model->index(0, 0), BookmarksModel::Url).toUrl(), QUrl("http://example.com/")); model->setFolder("SaMpLe"); QCOMPARE(model->rowCount(), 0); } }; QTEST_MAIN(BookmarksFolderModelTests) #include "tst_BookmarksFolderModelTests.moc" ./tests/unittests/bookmarks-folder-model/CMakeLists.txt0000644000015600001650000000064212703462031023400 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Sql REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_BookmarksFolderModelTests) add_executable(${TEST} tst_BookmarksFolderModelTests.cpp) include_directories(${webbrowser-app_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Sql Qt5::Test webbrowser-app-models ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/bookmarks-folderlist-model/0000755000015600001650000000000012703462032021533 5ustar jenkinsjenkins./tests/unittests/bookmarks-folderlist-model/CMakeLists.txt0000644000015600001650000000065212703462031024275 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Sql REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_BookmarksFolderListModelTests) add_executable(${TEST} tst_BookmarksFolderListModelTests.cpp) include_directories(${webbrowser-app_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Sql Qt5::Test webbrowser-app-models ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/bookmarks-folderlist-model/tst_BookmarksFolderListModelTests.cpp0000644000015600001650000003003512703462031031055 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include // local #include "bookmarks-model.h" #include "bookmarks-folder-model.h" #include "bookmarks-folderlist-model.h" class BookmarksFolderListModelTests : public QObject { Q_OBJECT private: BookmarksModel* bookmarks; BookmarksFolderListModel* model; void verifyDataChanged(QSignalSpy& spy, int row) { QList args; bool changed = false; while(!changed && !spy.isEmpty()) { args = spy.takeFirst(); int start = args.at(0).toModelIndex().row(); int end = args.at(1).toModelIndex().row(); changed = (start <= row) && (row <= end); } QVERIFY(changed); } private Q_SLOTS: void init() { bookmarks = new BookmarksModel; bookmarks->setDatabasePath(":memory:"); model = new BookmarksFolderListModel; model->setSourceModel(bookmarks); } void cleanup() { delete model; delete bookmarks; } void shouldHaveInitiallyOnlyDefaultFolder() { QCOMPARE(model->rowCount(), 1); QCOMPARE(model->data(model->index(0, 0), BookmarksFolderListModel::Folder).toString(), QString("")); } void shouldUpdateFolderListWhenInsertingEntries() { QSignalSpy spyRowsInserted(model, SIGNAL(rowsInserted(const QModelIndex&, int, int))); qRegisterMetaType >(); QSignalSpy spyDataChanged(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); bookmarks->add(QUrl("http://example.org/"), "Example Domain", QUrl(), "SampleFolder"); QVERIFY(!spyDataChanged.isEmpty()); QCOMPARE(spyRowsInserted.count(), 1); QList args = spyRowsInserted.takeFirst(); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(model->rowCount(), 2); QCOMPARE(model->data(model->index(1, 0), BookmarksFolderListModel::Folder).toString(), QString("SampleFolder")); bookmarks->add(QUrl("http://example.com/"), "Example Domain", QUrl(), "AnotherFolder"); QVERIFY(!spyDataChanged.isEmpty()); QCOMPARE(spyRowsInserted.count(), 1); args = spyRowsInserted.takeFirst(); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(model->rowCount(), 3); QCOMPARE(model->data(model->index(1, 0), BookmarksFolderListModel::Folder).toString(), QString("AnotherFolder")); bookmarks->add(QUrl("http://example.org/test.html"), "Test page", QUrl(), "SampleFolder"); QVERIFY(spyRowsInserted.isEmpty()); QVERIFY(!spyDataChanged.isEmpty()); QCOMPARE(model->rowCount(), 3); } void shouldCreateNewEmptyFolder() { model->createNewFolder("SampleFolder"); QCOMPARE(model->rowCount(), 2); QModelIndex index = model->index(1, 0); QCOMPARE(model->data(index, BookmarksFolderListModel::Folder).toString(), QString("SampleFolder")); BookmarksFolderModel* entries = model->data(index, BookmarksFolderListModel::Entries).value(); QCOMPARE(entries->rowCount(), 0); } void shouldNotUpdateFolderListWhenRemovingEntries() { bookmarks->add(QUrl("http://example.org/"), "Example Domain", QUrl(), "SampleFolder"); bookmarks->add(QUrl("http://example.com/"), "Example Domain", QUrl(), "AnotherFolder"); bookmarks->add(QUrl("http://example.org/test"), "Example Domain", QUrl(), "SampleFolder"); QCOMPARE(model->rowCount(), 3); bookmarks->remove(QUrl("http://example.org/test")); QCOMPARE(model->rowCount(), 3); bookmarks->remove(QUrl("http://example.org/")); QCOMPARE(model->rowCount(), 3); QModelIndex index = model->index(2, 0); QString folder = model->data(index, BookmarksFolderListModel::Folder).toString(); QCOMPARE(folder, QString("SampleFolder")); BookmarksFolderModel* entries = model->data(index, BookmarksFolderListModel::Entries).value(); QVERIFY(entries != 0); QCOMPARE(entries->rowCount(), 0); } void shouldUpdateDataWhenMovingEntries() { bookmarks->add(QUrl("http://example.org/"), "Example Domain", QUrl(), "SampleFolder"); bookmarks->add(QUrl("http://example.com/"), "Example Domain", QUrl(), "AnotherFolder"); QTest::qWait(100); QSignalSpy spyRowsMoved(model, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int))); qRegisterMetaType >(); QSignalSpy spyDataChanged(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); bookmarks->add(QUrl("http://example.org/test"), "Example Domain", QUrl(), "SampleFolder"); QVERIFY(spyRowsMoved.isEmpty()); } void shouldUpdateDataWhenDataChanges() { bookmarks->add(QUrl("http://example.com/"), "Example Domain", QUrl(), "SampleFolder"); bookmarks->add(QUrl("http://example.org/"), "Example Domain", QUrl(), "AnotherFolder"); QSignalSpy spyRowsMoved(model, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int))); qRegisterMetaType >(); QSignalSpy spyDataChanged(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); bookmarks->add(QUrl("http://example.org/foobar"), "Example Domain", QUrl(), "SampleFolder"); QVERIFY(spyRowsMoved.isEmpty()); QVERIFY(!spyDataChanged.isEmpty()); verifyDataChanged(spyDataChanged, 2); } void shouldUpdateWhenChangingSourceModel() { QSignalSpy spy(model, SIGNAL(sourceModelChanged())); bookmarks->add(QUrl("http://example.org/"), "Example Domain", QUrl(), "SampleFolder"); bookmarks->add(QUrl("http://example.com/"), "Example Domain", QUrl(), "AnotherFolder"); bookmarks->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl(), ""); QCOMPARE(model->rowCount(), 3); model->setSourceModel(bookmarks); QVERIFY(spy.isEmpty()); QCOMPARE(model->rowCount(), 3); model->setSourceModel(0); QCOMPARE(spy.count(), 1); QCOMPARE(model->sourceModel(), (BookmarksModel*) 0); QCOMPARE(model->rowCount(), 0); BookmarksModel* bookmarks2 = new BookmarksModel(); model->setSourceModel(bookmarks2); QCOMPARE(spy.count(), 2); QCOMPARE(model->sourceModel(), bookmarks2); QCOMPARE(model->rowCount(), 0); } void shouldKeepFolderSorted() { bookmarks->add(QUrl("http://example.org/"), "Example Domain", QUrl(), "Folder02"); bookmarks->add(QUrl("http://www.gogle.com/lawnmower"), "Gogle Lawn Mower", QUrl(), "Folder03"); bookmarks->add(QUrl("http://example.com/"), "Example Domain", QUrl(), "Folder01"); bookmarks->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl(), "Folder04"); bookmarks->add(QUrl("http://www.gogle.com/mail"), "Gogle Mail", QUrl(), "Folder03"); bookmarks->add(QUrl("https://mail.gogle.com/"), "Gogle Mail", QUrl(), "Folder03"); QCOMPARE(model->rowCount(), 5); QStringList folders; folders << "" << "Folder01" << "Folder02" << "Folder03" << "Folder04"; for (int i = 0; i < folders.count(); ++i) { QModelIndex index = model->index(i, 0); QString folder = model->data(index, BookmarksFolderListModel::Folder).toString(); BookmarksFolderModel* entries = model->data(index, BookmarksFolderListModel::Entries).value(); QVERIFY(!folder.isNull()); QCOMPARE(folder, folders.at(i)); QCOMPARE(entries->folder(), folder); } } void shouldExposeFolderModels() { bookmarks->add(QUrl("http://example.com/"), "Example Domain", QUrl(), "SampleFolder"); bookmarks->add(QUrl("http://example.org/"), "Example Domain", QUrl(), "AnotherFolder"); QTest::qWait(100); bookmarks->add(QUrl("http://example.org/test.html"), "Test Page", QUrl(), "AnotherFolder"); bookmarks->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl(), ""); QCOMPARE(model->rowCount(), 3); QModelIndex index = model->index(0, 0); QString folder = model->data(index, BookmarksFolderListModel::Folder).toString(); QCOMPARE(folder, QString("")); BookmarksFolderModel* entries = model->data(index, BookmarksFolderListModel::Entries).value(); QCOMPARE(entries->rowCount(), 1); QCOMPARE(entries->data(entries->index(0, 0), BookmarksModel::Url).toUrl(), QUrl("http://ubuntu.com/")); index = model->index(1, 0); folder = model->data(index, BookmarksFolderListModel::Folder).toString(); QCOMPARE(folder, QString("AnotherFolder")); entries = model->data(index, BookmarksFolderListModel::Entries).value(); QCOMPARE(entries->rowCount(), 2); QCOMPARE(entries->data(entries->index(0, 0), BookmarksModel::Url).toUrl(), QUrl("http://example.org/test.html")); QCOMPARE(entries->data(entries->index(1, 0), BookmarksModel::Url).toUrl(), QUrl("http://example.org/")); index = model->index(2, 0); folder = model->data(index, BookmarksFolderListModel::Folder).toString(); QCOMPARE(folder, QString("SampleFolder")); entries = model->data(index, BookmarksFolderListModel::Entries).value(); QCOMPARE(entries->rowCount(), 1); QCOMPARE(entries->data(entries->index(0, 0), BookmarksModel::Url).toUrl(), QUrl("http://example.com/")); } void shouldReturnData() { QDateTime now = QDateTime::currentDateTimeUtc(); bookmarks->add(QUrl("http://example.org/"), "Example Domain", QUrl(), "SampleFolder"); QVERIFY(!model->data(QModelIndex(), BookmarksFolderListModel::Folder).isValid()); QVERIFY(!model->data(model->index(-1, 0), BookmarksFolderListModel::Folder).isValid()); QVERIFY(!model->data(model->index(3, 0), BookmarksFolderListModel::Folder).isValid()); QCOMPARE(model->data(model->index(1, 0), BookmarksFolderListModel::Folder).toString(), QString("SampleFolder")); BookmarksFolderModel* entries = model->data(model->index(1, 0), BookmarksFolderListModel::Entries).value(); QVERIFY(entries != 0); QCOMPARE(entries->rowCount(), 1); QVERIFY(!model->data(model->index(1, 0), BookmarksFolderListModel::Entries + 1).isValid()); } void shouldReturnDataByIndex() { bookmarks->add(QUrl("http://example.com/"), "Example Domain", QUrl(), "SampleFolder"); bookmarks->add(QUrl("http://example.org/"), "Example Domain", QUrl(), "AnotherFolder"); bookmarks->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl(), ""); QCOMPARE(model->rowCount(), 3); QCOMPARE(model->indexOf("AnotherFolder"), 1); QVariantMap folderMap = model->get(3); QVERIFY(folderMap.isEmpty()); folderMap = model->get(1); QCOMPARE(folderMap.value("folder").toString(), QString("AnotherFolder")); BookmarksFolderModel* entries = folderMap.value("entries").value(); QCOMPARE(entries->rowCount(), 1); QCOMPARE(entries->data(entries->index(0, 0), BookmarksModel::Url).toUrl(), QUrl("http://example.org/")); } }; QTEST_MAIN(BookmarksFolderListModelTests) #include "tst_BookmarksFolderListModelTests.moc" ./tests/unittests/container-url-patterns/0000755000015600001650000000000012703462032020720 5ustar jenkinsjenkins./tests/unittests/container-url-patterns/tst_ContainerUrlPatternsTests.cpp0000644000015600001650000001413612703462031027473 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include // local #include "url-pattern-utils.h" class ContainerUrlPatternsTests : public QObject { Q_OBJECT private Q_SLOTS: void transformedUrlPatterns_data() { QTest::addColumn("pattern"); QTest::addColumn("transformedPattern"); QTest::addColumn("doTransformUrlPath"); // regular patterns QTest::newRow("Valid pattern") << "https?://*.mydomain.com/*" << "https?://[^\\./]*.mydomain.com/[^\\s]*" << true; QTest::newRow("Valid pattern with no tail replacement") << "https?://*.mydomain.com/l.php\\?\\w+=([^&]+).*" << "https?://[^\\./]*.mydomain.com/l.php\\?\\w+=([^&]+).*" << false; QTest::newRow("Valid pattern - short url") << "https?://mydomain.com/*" << "https?://mydomain.com/[^\\s]*" << true; QTest::newRow("Valid pattern - strict url") << "https?://www.mydomain.com/*" << "https?://www.mydomain.com/[^\\s]*" << true; #define WEBAPP_INVALID_URL_PATTERN_TEST(id,invalid_url_pattern) \ QTest::newRow("Invalid pattern " #id) \ << invalid_url_pattern \ << QString() << true WEBAPP_INVALID_URL_PATTERN_TEST(1, "http"); WEBAPP_INVALID_URL_PATTERN_TEST(2, "://"); WEBAPP_INVALID_URL_PATTERN_TEST(3, "file://"); WEBAPP_INVALID_URL_PATTERN_TEST(4, "https?://"); WEBAPP_INVALID_URL_PATTERN_TEST(5, "https?://*"); WEBAPP_INVALID_URL_PATTERN_TEST(6, "https?://foo.*"); WEBAPP_INVALID_URL_PATTERN_TEST(7, "https?://foo.ba*r.com"); WEBAPP_INVALID_URL_PATTERN_TEST(8, "https?://foo.*.com/"); WEBAPP_INVALID_URL_PATTERN_TEST(9, "https?://foo.bar.*/"); WEBAPP_INVALID_URL_PATTERN_TEST(10, "https?://*.bar.*"); WEBAPP_INVALID_URL_PATTERN_TEST(11, "https?://*.bar.*/"); WEBAPP_INVALID_URL_PATTERN_TEST(12, "https?://*.bar.*/"); WEBAPP_INVALID_URL_PATTERN_TEST(13, "httpsfoo?://*.bar.com/"); WEBAPP_INVALID_URL_PATTERN_TEST(14, "httppoo://*.bar.com/"); #undef WEBAPP_INVALID_URL_PATTERN_TEST // Google patterns QTest::newRow("Valid Google pattern") << "https?://mail.google.*/*" << "https?://mail.google.[^\\./]*/[^\\s]*" << true; QTest::newRow("Valid Google com SLD pattern") << "https?://mail.google.com.*/*" << "https?://mail.google.com.[^\\./]*/[^\\s]*" << true; QTest::newRow("Valid Google co SLD pattern") << "https?://mail.google.co.*/*" << "https?://mail.google.co.[^\\./]*/[^\\s]*" << true; QTest::newRow("Valid non Google pattern") << "https://*.google.com/*" << "https://[^\\./]*.google.com/[^\\s]*" << true; #define WEBAPP_INVALID_GOOGLE_URL_PATTERN_TEST(id,invalid_google_url_pattern) \ QTest::newRow("Invalid Google App pattern " #id) \ << invalid_google_url_pattern \ << QString() << true WEBAPP_INVALID_GOOGLE_URL_PATTERN_TEST(1, "https://*.google.*/*"); WEBAPP_INVALID_GOOGLE_URL_PATTERN_TEST(2, "https://service.gooo*gle.com/*"); WEBAPP_INVALID_GOOGLE_URL_PATTERN_TEST(3, "https://service.gooo?gle.com/*"); WEBAPP_INVALID_GOOGLE_URL_PATTERN_TEST(4, "https://service.goo*gle.com/*"); WEBAPP_INVALID_GOOGLE_URL_PATTERN_TEST(5, "https://serv?ice.goo*gle.com/*"); WEBAPP_INVALID_GOOGLE_URL_PATTERN_TEST(6, "https://se*rv?ice.goo*gle.com/*"); WEBAPP_INVALID_GOOGLE_URL_PATTERN_TEST(7, "https://se*rvice.goo*gle.com/*"); WEBAPP_INVALID_GOOGLE_URL_PATTERN_TEST(8, "https://se*rvice.goo*gle.*/*"); WEBAPP_INVALID_GOOGLE_URL_PATTERN_TEST(8, "https://service.google.kom.*/*"); } void transformedUrlPatterns() { QFETCH(QString, pattern); QFETCH(QString, transformedPattern); QFETCH(bool, doTransformUrlPath); QCOMPARE(UrlPatternUtils::transformWebappSearchPatternToSafePattern(pattern, doTransformUrlPath), transformedPattern); } void filteredUrlPatterns_data() { QTest::addColumn("patterns"); QTest::addColumn("filteredPattern"); // regular patterns QTest::newRow("Patterns with empty ones") << (QStringList() << QString("https?://*.mydomain.com/*") << QString() << QString("https?://www.mydomain.com/*") << QString()) << (QStringList() << QString("https?://[^\\./]*.mydomain.com/[^\\s]*") << QString("https?://www.mydomain.com/[^\\s]*")); QTest::newRow("Patterns with invalid ones") << (QStringList() << QString("https?://*.mydomain.com/*") << QString() << QString("https?://*") << QString()) << (QStringList() << QString("https?://[^\\./]*.mydomain.com/[^\\s]*")); } void filteredUrlPatterns() { QFETCH(QStringList, patterns); QFETCH(QStringList, filteredPattern); QCOMPARE(UrlPatternUtils::filterAndTransformUrlPatterns(patterns), filteredPattern); } }; QTEST_MAIN(ContainerUrlPatternsTests) #include "tst_ContainerUrlPatternsTests.moc" ./tests/unittests/container-url-patterns/CMakeLists.txt0000644000015600001650000000066512703462031023466 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_ContainerUrlPatternsTests) set(SOURCES ${webapp-container_SOURCE_DIR}/url-pattern-utils.cpp tst_ContainerUrlPatternsTests.cpp ) add_executable(${TEST} ${SOURCES}) include_directories(${webapp-container_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/meminfo/0000755000015600001650000000000012703462032015732 5ustar jenkinsjenkins./tests/unittests/meminfo/tst_MemInfoTests.cpp0000644000015600001650000000455412703462031021714 0ustar jenkinsjenkins/* * Copyright 2016 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include // local #include "meminfo.h" class MemInfoTests : public QObject { Q_OBJECT private: MemInfo* meminfo; private Q_SLOTS: void init() { meminfo = new MemInfo(this); } void cleanup() { delete meminfo; } void test_active_property() { QVERIFY(meminfo->active()); QSignalSpy spy(meminfo, SIGNAL(activeChanged())); meminfo->setActive(true); QVERIFY(spy.isEmpty()); meminfo->setActive(false); QCOMPARE(spy.count(), 1); QVERIFY(!meminfo->active()); spy.clear(); meminfo->setActive(false); QVERIFY(spy.isEmpty()); meminfo->setActive(true); QCOMPARE(spy.count(), 1); QVERIFY(meminfo->active()); } void test_interval_property() { QCOMPARE(meminfo->interval(), 5000); QSignalSpy spy(meminfo, SIGNAL(intervalChanged())); meminfo->setInterval(5000); QVERIFY(spy.isEmpty()); meminfo->setInterval(1500); QCOMPARE(spy.count(), 1); QCOMPARE(meminfo->interval(), 1500); } void test_initial_values() { QCOMPARE(meminfo->total(), 0); QCOMPARE(meminfo->free(), 0); } void test_update() { QSignalSpy totalSpy(meminfo, SIGNAL(totalChanged())); QSignalSpy freeSpy(meminfo, SIGNAL(freeChanged())); meminfo->setInterval(100); totalSpy.wait(); freeSpy.wait(); QVERIFY(meminfo->total() > 0); QVERIFY(meminfo->free() > 0); QVERIFY(meminfo->total() > meminfo->free()); } }; QTEST_MAIN(MemInfoTests) #include "tst_MemInfoTests.moc" ./tests/unittests/meminfo/CMakeLists.txt0000644000015600001650000000074212703462031020474 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_MemInfoTests) set(SOURCES ${webbrowser-common_SOURCE_DIR}/meminfo.cpp tst_MemInfoTests.cpp ) add_executable(${TEST} ${SOURCES}) include_directories(${webbrowser-common_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) set_tests_properties(${TEST} PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal") ./tests/unittests/history-model/0000755000015600001650000000000012703462032017077 5ustar jenkinsjenkins./tests/unittests/history-model/tst_HistoryModelTests.cpp0000644000015600001650000003513312703462031024146 0ustar jenkinsjenkins/* * Copyright 2013-2016 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include #include // local #include "history-model.h" class HistoryModelTests : public QObject { Q_OBJECT private: HistoryModel* model; private Q_SLOTS: void init() { model = new HistoryModel; model->setDatabasePath(":memory:"); } void cleanup() { delete model; } void shouldBeInitiallyEmpty() { QCOMPARE(model->rowCount(), 0); } void shouldNotAddEmptyUrl() { QCOMPARE(model->add(QUrl(), "empty URL", QUrl()), 0); QCOMPARE(model->rowCount(), 0); } void shouldAddNewEntries() { QCOMPARE(model->add(QUrl("http://example.org/"), "Example Domain", QUrl()), 1); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->add(QUrl("http://example.com/"), "Example Domain", QUrl()), 1); QCOMPARE(model->rowCount(), 2); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Url).toString(), QString("http://example.com/")); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Visits).toInt(), 1); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Hidden).toBool(), false); QCOMPARE(model->data(model->index(1, 0), HistoryModel::Url).toString(), QString("http://example.org/")); QCOMPARE(model->data(model->index(1, 0), HistoryModel::Visits).toInt(), 1); QCOMPARE(model->data(model->index(1, 0), HistoryModel::Hidden).toBool(), false); } void shouldNotifyWhenAddingNewEntries() { QSignalSpy spy(model, SIGNAL(rowsInserted(const QModelIndex&, int, int))); model->add(QUrl("http://example.org/"), "Example Domain", QUrl()); QCOMPARE(spy.count(), 1); QList args = spy.takeFirst(); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); model->add(QUrl("http://example.com/"), "Example Domain", QUrl()); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); } void shouldUpdateExistingEntry() { QCOMPARE(model->add(QUrl("http://example.org/"), "Example Domain", QUrl()), 1); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->add(QUrl("http://example.org/"), "Example Domain", QUrl("image://webicon/123")), 2); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Url).toString(), QString("http://example.org/")); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Visits).toInt(), 2); } void shouldNotifyWhenUpdatingExistingEntry() { QSignalSpy spyMoved(model, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int))); qRegisterMetaType >(); QSignalSpy spyChanged(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); model->add(QUrl("http://example.org/"), "Example Domain", QUrl()); QCOMPARE(spyMoved.count(), 0); QCOMPARE(spyChanged.count(), 0); QTest::qWait(100); model->add(QUrl("http://example.org/"), "Example Domain", QUrl("image://webicon/123")); QCOMPARE(spyMoved.count(), 0); QCOMPARE(spyChanged.count(), 1); QList args = spyChanged.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 0); QCOMPARE(args.at(1).toModelIndex().row(), 0); QVector roles = args.at(2).value >(); QVERIFY(roles.size() >= 2); QVERIFY(roles.contains(HistoryModel::Icon)); QVERIFY(roles.contains(HistoryModel::Visits)); model->add(QUrl("http://example.com/"), "Example Domain", QUrl()); QCOMPARE(spyMoved.count(), 0); QCOMPARE(spyChanged.count(), 0); model->add(QUrl("http://example.org/"), "Example D0ma1n", QUrl("image://webicon/456")); QCOMPARE(spyMoved.count(), 1); args = spyMoved.takeFirst(); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(args.at(4).toInt(), 0); QCOMPARE(spyChanged.count(), 1); args = spyChanged.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 0); QCOMPARE(args.at(1).toModelIndex().row(), 0); roles = args.at(2).value >(); QVERIFY(roles.size() >= 3); QVERIFY(roles.contains(HistoryModel::Title)); QVERIFY(roles.contains(HistoryModel::Icon)); QVERIFY(roles.contains(HistoryModel::Visits)); } void shouldUpdateAttributes() { qRegisterMetaType >(); QSignalSpy spyChanged(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); QUrl url(QStringLiteral("http://example.org/")); QCOMPARE(model->add(url, QStringLiteral(""), QUrl()), 1); QVERIFY(!model->update(QUrl(), QStringLiteral(""), QUrl())); QVERIFY(spyChanged.isEmpty()); QVERIFY(!model->update(QUrl(QStringLiteral("http://example.com/")), QStringLiteral(""), QUrl())); QVERIFY(spyChanged.isEmpty()); QVERIFY(!model->update(url, QStringLiteral(""), QUrl())); QVERIFY(spyChanged.isEmpty()); QVERIFY(model->update(url, QStringLiteral("title update"), QUrl())); QCOMPARE(spyChanged.count(), 1); QList args = spyChanged.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 0); QCOMPARE(args.at(1).toModelIndex().row(), 0); QVector roles = args.at(2).value >(); QCOMPARE(roles.size(), 1); QVERIFY(roles.contains(HistoryModel::Title)); QCOMPARE(model->get(0)[QStringLiteral("visits")].toInt(), 1); QVERIFY(model->update(url, QStringLiteral("title update"), QUrl(QStringLiteral("image://webicon/123")))); QCOMPARE(spyChanged.count(), 1); args = spyChanged.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 0); QCOMPARE(args.at(1).toModelIndex().row(), 0); roles = args.at(2).value >(); QCOMPARE(roles.size(), 1); QVERIFY(roles.contains(HistoryModel::Icon)); QCOMPARE(model->get(0)[QStringLiteral("visits")].toInt(), 1); QVERIFY(model->update(url, QStringLiteral("title update 2"), QUrl(QStringLiteral("image://webicon/1234")))); QCOMPARE(spyChanged.count(), 1); args = spyChanged.takeFirst(); QCOMPARE(args.at(0).toModelIndex().row(), 0); QCOMPARE(args.at(1).toModelIndex().row(), 0); roles = args.at(2).value >(); QCOMPARE(roles.size(), 2); QVERIFY(roles.contains(HistoryModel::Title)); QVERIFY(roles.contains(HistoryModel::Icon)); QCOMPARE(model->get(0)[QStringLiteral("visits")].toInt(), 1); } void shouldNotifyWhenHidingOrUnHidingExistingEntry() { qRegisterMetaType >(); QSignalSpy spyChanged(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); model->add(QUrl("http://example.org/"), "Example Domain", QUrl()); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Hidden).toBool(), false); model->hide(QUrl("http://example.org/")); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Hidden).toBool(), true); QCOMPARE(spyChanged.count(), 1); QList args = spyChanged.takeFirst(); QVector roles = args.at(2).value >(); QVERIFY(roles.size() == 1); QVERIFY(roles.contains(HistoryModel::Hidden)); model->unHide(QUrl("http://example.org/")); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Hidden).toBool(), false); QCOMPARE(spyChanged.count(), 1); args = spyChanged.takeFirst(); roles = args.at(2).value >(); QVERIFY(roles.size() == 1); QVERIFY(roles.contains(HistoryModel::Hidden)); } void shouldUpdateTimestamp() { QDateTime now = QDateTime::currentDateTimeUtc(); QTest::qWait(1001); model->add(QUrl("http://example.org/"), "Example Domain", QUrl()); QTest::qWait(1001); model->add(QUrl("http://example.com/"), "Example Domain", QUrl()); QDateTime ts0 = model->data(model->index(0, 0), HistoryModel::LastVisit).toDateTime(); QDateTime ts1 = model->data(model->index(1, 0), HistoryModel::LastVisit).toDateTime(); QVERIFY(ts0 > ts1); QVERIFY(ts1 > now); } void shouldReturnData() { QDateTime now = QDateTime::currentDateTimeUtc(); model->add(QUrl("http://example.org/"), "Example Domain", QUrl("image://webicon/123")); QVERIFY(!model->data(QModelIndex(), HistoryModel::Url).isValid()); QVERIFY(!model->data(model->index(-1, 0), HistoryModel::Url).isValid()); QVERIFY(!model->data(model->index(3, 0), HistoryModel::Url).isValid()); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Url).toUrl(), QUrl("http://example.org/")); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Domain).toString(), QString("example.org")); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Title).toString(), QString("Example Domain")); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Icon).toUrl(), QUrl("image://webicon/123")); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Visits).toInt(), 1); QVERIFY(model->data(model->index(0, 0), HistoryModel::LastVisit).toDateTime() >= now); QVERIFY(!model->data(model->index(0, 0), HistoryModel::LastVisit + 4).isValid()); } void shouldReturnDatabasePath() { QCOMPARE(model->databasePath(), QString(":memory:")); } void shouldNotifyWhenSettingDatabasePath() { QSignalSpy spyPath(model, SIGNAL(databasePathChanged())); QSignalSpy spyReset(model, SIGNAL(modelReset())); model->setDatabasePath(":memory:"); QVERIFY(spyPath.isEmpty()); QVERIFY(spyReset.isEmpty()); model->setDatabasePath(""); QCOMPARE(spyPath.count(), 1); QCOMPARE(spyReset.count(), 1); QCOMPARE(model->databasePath(), QString(":memory:")); } void shouldSerializeOnDisk() { QTemporaryFile tempFile; tempFile.open(); QString fileName = tempFile.fileName(); delete model; model = new HistoryModel; model->setDatabasePath(fileName); model->add(QUrl("http://example.org/"), "Example Domain", QUrl()); QTest::qWait(1001); model->add(QUrl("http://example.com/"), "Example Domain", QUrl()); model->hide(QUrl("http://example.com/")); delete model; model = new HistoryModel; model->setDatabasePath(fileName); QCOMPARE(model->rowCount(), 2); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Url).toUrl(), QUrl("http://example.com/")); QCOMPARE(model->data(model->index(0, 0), HistoryModel::Hidden).toBool(), true); QCOMPARE(model->data(model->index(1, 0), HistoryModel::Url).toUrl(), QUrl("http://example.org/")); QCOMPARE(model->data(model->index(1, 0), HistoryModel::Hidden).toBool(), false); } void shouldClearAll() { QSignalSpy spyReset(model, SIGNAL(modelReset())); model->add(QUrl("http://example.org/"), "Example Domain", QUrl()); model->add(QUrl("http://example.com/"), "Example Domain", QUrl()); QCOMPARE(model->rowCount(), 2); QVERIFY(spyReset.isEmpty()); model->clearAll(); QCOMPARE(spyReset.count(), 1); QCOMPARE(model->rowCount(), 0); model->clearAll(); QCOMPARE(spyReset.count(), 1); } void shouldRemoveByUrl() { QCOMPARE(model->add(QUrl("http://example.org/"), "Example Domain", QUrl()), 1); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->add(QUrl("http://example.com/"), "Example Domain", QUrl()), 1); QCOMPARE(model->rowCount(), 2); model->removeEntryByUrl(QUrl("http://example.org/")); QCOMPARE(model->rowCount(), 1); model->removeEntryByUrl(QUrl("http://example.com/")); QCOMPARE(model->rowCount(), 0); } void shouldRemoveByDate() { QCOMPARE(model->add(QUrl("http://example.org/"), "Example Domain", QUrl()), 1); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->add(QUrl("http://example.com/"), "Example Domain", QUrl()), 1); QCOMPARE(model->rowCount(), 2); model->removeEntriesByDate(QDate::currentDate()); QCOMPARE(model->rowCount(), 0); } void shouldRemoveByDomain() { QCOMPARE(model->add(QUrl("http://example.org/page1"), "Example Domain Page 1", QUrl()), 1); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->add(QUrl("http://example.org/page2"), "Example Domain Page 2", QUrl()), 1); QCOMPARE(model->rowCount(), 2); QCOMPARE(model->add(QUrl("http://example.com/page1"), "Example Domain Page 1", QUrl()), 1); QCOMPARE(model->rowCount(), 3); QCOMPARE(model->add(QUrl("http://example.com/page2"), "Example Domain Page 2", QUrl()), 1); QCOMPARE(model->rowCount(), 4); model->removeEntriesByDomain("example.org"); QCOMPARE(model->rowCount(), 2); model->removeEntriesByDomain("example.com"); QCOMPARE(model->rowCount(), 0); } void shouldCountNumberOfEntries() { QSignalSpy spyCount(model, SIGNAL(rowCountChanged())); QCOMPARE(model->property("count").toInt(), 0); model->add(QUrl("http://example.org/"), "Example Domain", QUrl()); QCOMPARE(model->property("count").toInt(), 1); QCOMPARE(spyCount.count(), 1); model->add(QUrl("http://example.com/"), "Example Domain", QUrl()); QCOMPARE(model->property("count").toInt(), 2); QCOMPARE(spyCount.count(), 2); model->clearAll(); QCOMPARE(model->property("count").toInt(), 0); QCOMPARE(spyCount.count(), 3); } }; QTEST_MAIN(HistoryModelTests) #include "tst_HistoryModelTests.moc" ./tests/unittests/history-model/CMakeLists.txt0000644000015600001650000000062212703462031021636 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Sql REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_HistoryModelTests) add_executable(${TEST} tst_HistoryModelTests.cpp) include_directories(${webbrowser-app_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Sql Qt5::Test webbrowser-app-models ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/CMakeLists.txt0000644000015600001650000000164712703462031017047 0ustar jenkinsjenkinsadd_subdirectory(sanity) add_subdirectory(qml) add_subdirectory(domain-utils) add_subdirectory(history-model) add_subdirectory(history-domain-model) add_subdirectory(history-domainlist-model) add_subdirectory(history-lastvisitdatelist-model) add_subdirectory(session-utils) add_subdirectory(tabs-model) add_subdirectory(bookmarks-model) add_subdirectory(bookmarks-folder-model) add_subdirectory(bookmarks-folderlist-model) add_subdirectory(limit-proxy-model) add_subdirectory(container-url-patterns) add_subdirectory(cookie-store) add_subdirectory(oxide-cookie-helper) add_subdirectory(session-storage) add_subdirectory(favicon-fetcher) add_subdirectory(webapp-container-hook) add_subdirectory(intent-filter) add_subdirectory(search-engine) add_subdirectory(text-search-filter-model) add_subdirectory(downloads-model) add_subdirectory(single-instance-manager) add_subdirectory(meminfo) add_subdirectory(webapp-container-color-helper) ./tests/unittests/history-lastvisitdatelist-model/0000755000015600001650000000000012703462032022651 5ustar jenkinsjenkins./tests/unittests/history-lastvisitdatelist-model/tst_HistoryLastVisitDateListModelTests.cpp0000644000015600001650000003511412703462031033214 0ustar jenkinsjenkins/* * Copyright 2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include // local #include "bookmarks-model.h" #include "domain-utils.h" #include "history-lastvisitdatelist-model.h" #include "history-model.h" class MockHistoryModel : public HistoryModel { Q_OBJECT public: // reimplemented from HistoryModel int rowCount(const QModelIndex& parent=QModelIndex()) const { Q_UNUSED(parent); return m_entries.count(); } QVariant data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } const HistoryEntry& entry = m_entries.at(index.row()); switch (role) { case Url: return entry.url; case Domain: return entry.domain; case Title: return entry.title; case Icon: return entry.icon; case Visits: return entry.visits; case LastVisit: return entry.lastVisit; case LastVisitDate: return entry.lastVisit.toLocalTime().date(); case Hidden: return entry.hidden; default: return QVariant(); } } void add(const QUrl& url, const QString& title, const QString& domain, const QUrl& icon, const QDateTime& lastVisit) { int index = getEntryIndex(url); if (index == -1) { HistoryEntry entry; entry.url = url; entry.domain = domain; entry.title = title; entry.icon = icon; entry.visits = 1; entry.lastVisit = lastVisit; entry.hidden = false; beginInsertRows(QModelIndex(), 0, 0); m_entries.prepend(entry); endInsertRows(); } else { QVector roles; roles << LastVisit; if (index == 0) { HistoryEntry& entry = m_entries.first(); entry.lastVisit = lastVisit; } else { beginMoveRows(QModelIndex(), index, index, QModelIndex(), 0); HistoryEntry entry = m_entries.takeAt(index); entry.lastVisit = lastVisit; m_entries.prepend(entry); endMoveRows(); } Q_EMIT dataChanged(this->index(0, 0), this->index(0, 0), roles); } } void removeEntryByUrl(const QUrl& url) { if (url.isEmpty()) { return; } for (int i = 0; i < m_entries.count(); ++i) { if (m_entries.at(i).url == url) { beginRemoveRows(QModelIndex(), i, i); m_entries.removeAt(i); endRemoveRows(); Q_EMIT rowCountChanged(); } } } private: struct HistoryEntry { QUrl url; QString domain; QString title; QUrl icon; uint visits; QDateTime lastVisit; bool hidden; }; int getEntryIndex(const QUrl& url) const { for (int i = 0; i < m_entries.count(); ++i) { if (m_entries.at(i).url == url) { return i; } } return -1; } QList m_entries; }; class HistoryLastVisitDateListModelTests : public QObject { Q_OBJECT private: MockHistoryModel* mockHistory; HistoryLastVisitDateListModel* model; BookmarksModel* bookmarks; private Q_SLOTS: void init() { mockHistory = new MockHistoryModel; mockHistory->setDatabasePath(":memory:"); model = new HistoryLastVisitDateListModel; model->setSourceModel(QVariant::fromValue(mockHistory)); bookmarks = new BookmarksModel; bookmarks->setDatabasePath(":memory:"); } void cleanup() { delete model; delete mockHistory; delete bookmarks; } void shouldBeInitiallyEmpty() { QCOMPARE(model->rowCount(), 0); } void shouldUpdateLastVisitDateListWhenInsertingEntries() { QSignalSpy spyRowsInserted(model, SIGNAL(rowsInserted(const QModelIndex&, int, int))); qRegisterMetaType >(); QDateTime dt1 = QDateTime(QDate(1970, 1, 1), QTime(6, 0, 0)); QDateTime dt2 = QDateTime(QDate(1970, 1, 2), QTime(6, 0, 0)); mockHistory->add(QUrl("http://example.org/"), "Example Domain", "example.org", QUrl(), dt1); QCOMPARE(spyRowsInserted.count(), 2); QList args = spyRowsInserted.takeFirst(); QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); args = spyRowsInserted.takeFirst(); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(model->rowCount(), 2); QCOMPARE(model->data(model->index(1, 0), HistoryLastVisitDateListModel::LastVisitDate).toDate(), dt1.date()); mockHistory->add(QUrl("http://example.com/"), "Example Domain", "example.com", QUrl(), dt2); QCOMPARE(spyRowsInserted.count(), 1); args = spyRowsInserted.takeFirst(); QCOMPARE(args.at(1).toInt(), 1); QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(model->rowCount(), 3); QCOMPARE(model->data(model->index(1, 0), HistoryLastVisitDateListModel::LastVisitDate).toDate(), dt2.date()); mockHistory->add(QUrl("http://example.net/"), "Example Domain", "example.net", QUrl(), dt1); QVERIFY(spyRowsInserted.isEmpty()); QCOMPARE(model->rowCount(), 3); } void shouldBeEmptyAfterRemovingAllEntries() { QSignalSpy spyRowsInserted(model, SIGNAL(rowsInserted(const QModelIndex&, int, int))); QSignalSpy spyRowsRemoved(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int))); QDateTime dt1 = QDateTime(QDate(1970, 1, 1), QTime(6, 0, 0)); QDateTime dt2 = QDateTime(QDate(1970, 1, 2), QTime(6, 0, 0)); mockHistory->add(QUrl("http://example.com/"), "Example Domain", "example.com", QUrl(), dt1); mockHistory->add(QUrl("http://example.org/"), "Example Domain", "example.org", QUrl(), dt2); QCOMPARE(spyRowsInserted.count(), 3); QCOMPARE(model->rowCount(), 3); mockHistory->removeEntryByUrl(QUrl("http://example.com/")); mockHistory->removeEntryByUrl(QUrl("http://example.org/")); QCOMPARE(spyRowsRemoved.count(), 3); QCOMPARE(model->rowCount(), 0); } void shouldUpdateLastVisitDateListWhenRemovingEntries() { QSignalSpy spyRowsRemoved(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int))); QDateTime dt1 = QDateTime(QDate(1970, 1, 1), QTime(6, 0, 0)); QDateTime dt2 = QDateTime(QDate(1970, 1, 2), QTime(6, 0, 0)); QDateTime dt3 = QDateTime(QDate(1970, 1, 3), QTime(6, 0, 0)); mockHistory->add(QUrl("http://example.com/"), "Example Domain", "example.com", QUrl(), dt1); mockHistory->add(QUrl("http://example.info/"), "Example Domain", "example.info", QUrl(), dt1); mockHistory->add(QUrl("http://example.org/"), "Example Domain", "example.org", QUrl(), dt2); mockHistory->add(QUrl("http://example.net/"), "Example Domain", "example.net", QUrl(), dt3); QVERIFY(spyRowsRemoved.isEmpty()); QCOMPARE(model->rowCount(), 4); mockHistory->removeEntryByUrl(QUrl("http://example.com/")); QVERIFY(spyRowsRemoved.isEmpty()); QCOMPARE(model->rowCount(), 4); mockHistory->removeEntryByUrl(QUrl("http://example.info/")); QCOMPARE(spyRowsRemoved.count(), 1); QCOMPARE(model->rowCount(), 3); mockHistory->removeEntryByUrl(QUrl("http://example.org/")); QCOMPARE(spyRowsRemoved.count(), 2); QCOMPARE(model->rowCount(), 2); } void shouldUpdateDataWhenMovingEntries() { QDateTime dt1 = QDateTime(QDate(1970, 1, 1), QTime(6, 0, 0)); QDateTime dt2 = QDateTime(QDate(1970, 1, 2), QTime(6, 0, 0)); mockHistory->add(QUrl("http://example.com/"), "Example Domain", "example.com", QUrl(), dt1); mockHistory->add(QUrl("http://example.org/"), "Example Domain", "example.org", QUrl(), dt2); QTest::qWait(100); QSignalSpy spyRowsMoved(model, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int))); qRegisterMetaType >(); QSignalSpy spyDataChanged(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); mockHistory->add(QUrl("http://example.net/"), "Example Domain", "example.net", QUrl(), dt1); QVERIFY(spyRowsMoved.isEmpty()); } void shouldUpdateDataWhenDataChanges() { QSignalSpy spyRowsRemoved(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int))); QDateTime dt1 = QDateTime(QDate(1970, 1, 1), QTime(6, 0, 0)); QDateTime dt2 = QDateTime(QDate(1970, 1, 2), QTime(6, 0, 0)); mockHistory->add(QUrl("http://example.com/"), "Example Domain", "example.com", QUrl(), dt1); mockHistory->add(QUrl("http://example.org/"), "Example Domain", "example.org", QUrl(), dt2); QCOMPARE(model->rowCount(), 3); mockHistory->add(QUrl("http://example.com/"), "Example Domain", "example.com", QUrl(), dt2); QCOMPARE(model->rowCount(), 2); } void shouldUpdateWhenChangingSourceModel() { QDateTime dt1 = QDateTime(QDate(1970, 1, 1), QTime(6, 0, 0)); QDateTime dt2 = QDateTime(QDate(1970, 1, 2), QTime(6, 0, 0)); QDateTime dt3 = QDateTime(QDate(1970, 1, 3), QTime(6, 0, 0)); QSignalSpy spy(model, SIGNAL(sourceModelChanged())); mockHistory->add(QUrl("http://example.com/"), "Example Domain", "example.com", QUrl(), dt1); mockHistory->add(QUrl("http://example.org/"), "Example Domain", "example.org", QUrl(), dt2); mockHistory->add(QUrl("http://example.net/"), "Example Domain", "example.net", QUrl(), dt3); QCOMPARE(model->rowCount(), 4); model->setSourceModel(QVariant::fromValue(mockHistory)); QVERIFY(spy.isEmpty()); QCOMPARE(model->rowCount(), 4); QTest::ignoreMessage(QtWarningMsg, "Only QAbstractItemModel-derived instances and null are allowed as source models"); model->setSourceModel(0); QCOMPARE(spy.count(), 1); QVERIFY(!model->sourceModel().isValid()); QCOMPARE(model->rowCount(), 0); MockHistoryModel mockHistory2; model->setSourceModel(QVariant::fromValue(&mockHistory2)); QCOMPARE(spy.count(), 2); QCOMPARE(model->sourceModel().value(), &mockHistory2); QCOMPARE(model->rowCount(), 0); QTest::ignoreMessage(QtWarningMsg, "Only QAbstractItemModel-derived instances and null are allowed as source models"); model->setSourceModel(QVariant::fromValue(QString("not a model"))); QCOMPARE(spy.count(), 3); QVERIFY(!model->sourceModel().isValid()); QCOMPARE(model->rowCount(), 0); QTest::ignoreMessage(QtWarningMsg, "No results will be returned because the sourceModel does not have a role named \"lastVisitDate\""); bookmarks->add(QUrl("http://example.org/"), "Example Domain", QUrl(), ""); model->setSourceModel(QVariant::fromValue(bookmarks)); QCOMPARE(model->rowCount(), 0); spy.clear(); model->setSourceModel(QVariant()); QCOMPARE(spy.count(), 1); QVERIFY(model->sourceModel().isNull()); } void shouldKeepLastVisitDatesSorted() { QDateTime dt1 = QDateTime(QDate(1970, 1, 1), QTime(6, 0, 0)); QDateTime dt2 = QDateTime(QDate(1970, 1, 2), QTime(6, 0, 0)); QDateTime dt3 = QDateTime(QDate(1970, 1, 3), QTime(6, 0, 0)); QDateTime dt4 = QDateTime(QDate(1970, 1, 4), QTime(6, 0, 0)); QDateTime dt5 = QDateTime(QDate(1970, 1, 5), QTime(6, 0, 0)); QDateTime dt6 = QDateTime(QDate(1970, 1, 6), QTime(6, 0, 0)); mockHistory->add(QUrl("http://example.edu/"), "Example Domain", "example.edu", QUrl(), dt5); mockHistory->add(QUrl("http://example.net/"), "Example Domain", "example.net", QUrl(), dt3); mockHistory->add(QUrl("http://example.gov/"), "Example Domain", "example.gov", QUrl(), dt4); mockHistory->add(QUrl("http://example.com/"), "Example Domain", "example.com", QUrl(), dt1); mockHistory->add(QUrl("http://example.web/"), "Example Domain", "example.web", QUrl(), dt6); mockHistory->add(QUrl("http://example.org/"), "Example Domain", "example.org", QUrl(), dt2); QCOMPARE(model->rowCount(), 7); QList lastVisitDates; lastVisitDates << dt6.date() << dt5.date() << dt4.date() << dt3.date() << dt2.date() << dt1.date(); QModelIndex defaultIndex = model->index(0, 0); QDate defaultDate = model->data(defaultIndex, HistoryLastVisitDateListModel::LastVisitDate).toDate(); QVERIFY(defaultDate.isNull()); for (int i = 1; i < lastVisitDates.count(); ++i) { QModelIndex index = model->index(i, 0); QDate lastVisitDate = model->data(index, HistoryLastVisitDateListModel::LastVisitDate).toDate(); QVERIFY(!lastVisitDate.isNull()); QCOMPARE(lastVisitDate, lastVisitDates.at(i-1)); } } void shouldReturnData() { QDateTime dt1 = QDateTime(QDate(1970, 1, 1), QTime(6, 0, 0)); mockHistory->add(QUrl("http://example.com/"), "Example Domain", "example.com", QUrl(), dt1); QVERIFY(!model->data(QModelIndex(), HistoryLastVisitDateListModel::LastVisitDate).isValid()); QVERIFY(!model->data(model->index(-1, 0), HistoryLastVisitDateListModel::LastVisitDate).isValid()); QVERIFY(!model->data(model->index(3, 0), HistoryLastVisitDateListModel::LastVisitDate).isValid()); QCOMPARE(model->data(model->index(0, 0), HistoryLastVisitDateListModel::LastVisitDate).toDate(), QDate()); QCOMPARE(model->data(model->index(1, 0), HistoryLastVisitDateListModel::LastVisitDate).toDate(), dt1.date()); QVERIFY(!model->data(model->index(1, 0), HistoryLastVisitDateListModel::LastVisitDate + 1).isValid()); } }; QTEST_MAIN(HistoryLastVisitDateListModelTests) #include "tst_HistoryLastVisitDateListModelTests.moc" ./tests/unittests/history-lastvisitdatelist-model/CMakeLists.txt0000644000015600001650000000060712703462031025413 0ustar jenkinsjenkinsfind_package(Qt5Sql REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_HistoryLastVisitDateListModelTests) add_executable(${TEST} tst_HistoryLastVisitDateListModelTests.cpp) include_directories(${webbrowser-app_SOURCE_DIR}) target_link_libraries(${TEST} webbrowser-app-models Qt5::Sql Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/domain-utils/0000755000015600001650000000000012703462032016705 5ustar jenkinsjenkins./tests/unittests/domain-utils/tst_DomainUtilsTests.cpp0000644000015600001650000000573612703462031023570 0ustar jenkinsjenkins/* * Copyright 2013 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include // local #include "domain-utils.h" class DomainUtilsTests : public QObject { Q_OBJECT private Q_SLOTS: void shouldExtractTopLevelDomainName_data() { QTest::addColumn("url"); QTest::addColumn("domain"); QTest::newRow("only SLD") << QUrl("http://ubuntu.com") << QString("ubuntu.com"); QTest::newRow("SLD with www") << QUrl("http://www.ubuntu.com") << QString("ubuntu.com"); QTest::newRow("SLD is www") << QUrl("http://www.com") << QString("www.com"); QTest::newRow("subdomain") << QUrl("https://mail.google.com/foo/bar") << QString("google.com"); QTest::newRow("subdomain with m") << QUrl("http://m.cnet.com") << QString("cnet.com"); QTest::newRow("subdomain with mobile") << QUrl("http://mobile.nytimes.com") << QString("nytimes.com"); QTest::newRow("ftp with subdomain") << QUrl("ftp://user:pwd@ftp.london.ac.uk/home/foobar") << QString("london.ac.uk"); QTest::newRow("two-letter SLD") << QUrl("https://fb.com/foobar") << QString("fb.com"); QTest::newRow("two-letter SLD with www") << QUrl("http://www.fb.com/foobar") << QString("fb.com"); QTest::newRow("two-letter SLD with subdomain") << QUrl("http://m.espn.go.com") << QString("go.com"); QTest::newRow("two-component TLD") << QUrl("http://bbc.co.uk") << QString("bbc.co.uk"); QTest::newRow("another two-component TLD") << QUrl("http://disney.com.es") << QString("disney.com.es"); QTest::newRow("two-component TLD with subdomain") << QUrl("http://www.foobar.bbc.co.uk") << QString("bbc.co.uk"); QTest::newRow("local file") << QUrl("file:///home/foobar/test.txt") << DomainUtils::TOKEN_LOCAL; QTest::newRow("IPv4 address") << QUrl("http://192.168.1.1/config") << QString("192.168.1.1"); QTest::newRow("IPv6 address") << QUrl("http://[2001:db8:85a3::8a2e:370:7334]/bleh") << QString("2001:db8:85a3::8a2e:370:7334"); QTest::newRow("localhost") << QUrl("http://localhost:8080/foobar") << QString("localhost"); } void shouldExtractTopLevelDomainName() { QFETCH(QUrl, url); QVERIFY(url.isValid()); QFETCH(QString, domain); QCOMPARE(DomainUtils::extractTopLevelDomainName(url), domain); } }; QTEST_MAIN(DomainUtilsTests) #include "tst_DomainUtilsTests.moc" ./tests/unittests/domain-utils/CMakeLists.txt0000644000015600001650000000055312703462031021447 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_DomainUtilsTests) add_executable(${TEST} tst_DomainUtilsTests.cpp) include_directories(${webbrowser-app_SOURCE_DIR} ${webbrowser-plugin_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/unittests/session-utils/0000755000015600001650000000000012703462032017121 5ustar jenkinsjenkins./tests/unittests/session-utils/tst_SessionUtilsTests.cpp0000644000015600001650000000572312703462031024214 0ustar jenkinsjenkins/* * Copyright 2014-2015 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ // Qt #include #include #include #include #include #include // local #include "session-utils.h" using namespace SessionUtils; class SessionUtilsTests : public QObject { Q_OBJECT public: SessionUtilsTests(); void clearXdgRuntimeDir(); void startNewSession(); private Q_SLOTS: void initTestCase(); void cleanup(); void testNoSession(); void testSingleSession(); void testSessionRestart(); private: QByteArray m_xdgRuntimeDir; }; SessionUtilsTests::SessionUtilsTests(): QObject(), m_xdgRuntimeDir("/tmp/session-utils-test") { } void SessionUtilsTests::clearXdgRuntimeDir() { QDir xdgRuntimeDir(m_xdgRuntimeDir); xdgRuntimeDir.removeRecursively(); xdgRuntimeDir.mkpath("."); } void SessionUtilsTests::startNewSession() { QDir upstartSessionDir(QString(m_xdgRuntimeDir + "/upstart/sessions")); upstartSessionDir.mkpath("."); QString sessionFile = QString("%1.session").arg(qrand()); QFile file(upstartSessionDir.filePath(sessionFile)); file.open(QIODevice::WriteOnly); file.close(); /* Wait for some time, because the times on the files are only as * accurate as a second. */ QTest::qWait(1001); } void SessionUtilsTests::initTestCase() { qputenv("XDG_RUNTIME_DIR", m_xdgRuntimeDir); clearXdgRuntimeDir(); } void SessionUtilsTests::cleanup() { clearXdgRuntimeDir(); } void SessionUtilsTests::testNoSession() { QVERIFY(firstRun("myapp")); QVERIFY(firstRun("yourapp")); // If the Unity session never started, firstRun() should always return true QVERIFY(firstRun("myapp")); QVERIFY(firstRun("yourapp")); } void SessionUtilsTests::testSingleSession() { startNewSession(); QVERIFY(firstRun("myapp")); QVERIFY(firstRun("yourapp")); QVERIFY(!firstRun("myapp")); QVERIFY(!firstRun("yourapp")); } void SessionUtilsTests::testSessionRestart() { startNewSession(); QVERIFY(firstRun("myapp")); QVERIFY(!firstRun("myapp")); QTest::qWait(1001); startNewSession(); QVERIFY(firstRun("myapp")); QVERIFY(firstRun("yourapp")); QVERIFY(!firstRun("myapp")); QVERIFY(!firstRun("yourapp")); } QTEST_MAIN(SessionUtilsTests) #include "tst_SessionUtilsTests.moc" ./tests/unittests/session-utils/CMakeLists.txt0000644000015600001650000000064112703462031021661 0ustar jenkinsjenkinsfind_package(Qt5Core REQUIRED) find_package(Qt5Test REQUIRED) set(TEST tst_SessionUtilsTests) set(SOURCES ${webapp-container_SOURCE_DIR}/session-utils.cpp tst_SessionUtilsTests.cpp ) add_executable(${TEST} ${SOURCES}) include_directories(${webapp-container_SOURCE_DIR}) target_link_libraries(${TEST} Qt5::Core Qt5::Test ) add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) ./tests/autopilot/0000755000015600001650000000000012703462031014255 5ustar jenkinsjenkins./tests/autopilot/webbrowser_app/0000755000015600001650000000000012703462031017276 5ustar jenkinsjenkins./tests/autopilot/webbrowser_app/__init__.py0000644000015600001650000000252612703462031021414 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright 2013-2015 Canonical # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """webbrowser-app autopilot tests and emulators - top level package.""" import ubuntuuitoolkit as uitk from autopilot import introspection from webbrowser_app.emulators import browser class Webbrowser(uitk.UbuntuUIToolkitCustomProxyObjectBase): """Autopilot custom proxy object for the webbrowser app.""" @classmethod def validate_dbus_object(cls, path, state): name = introspection.get_classname_from_path(path) if name == b'webbrowser-app': if state['applicationName'][1] == 'webbrowser-app': return True return False @property def main_window(self): return self.select_single(browser.Browser) ./tests/autopilot/webbrowser_app/tests/0000755000015600001650000000000012703462036020445 5ustar jenkinsjenkins./tests/autopilot/webbrowser_app/tests/test_addressbar_states.py0000644000015600001650000000676012703462031025557 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright 2013-2015 Canonical # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import testtools from testtools.matchers import Equals from autopilot.matchers import Eventually from autopilot.platform import model from webbrowser_app.tests import StartOpenRemotePageTestCaseBase class TestAddressBarStates(StartOpenRemotePageTestCaseBase): def test_cancel_state_loading(self): address_bar = self.main_window.address_bar url = self.base_url + "/wait/5" self.main_window.go_to_url(url) address_bar.loading.wait_for(True) address_bar.click_action_button() address_bar.loading.wait_for(False) def test_state_editing(self): address_bar = self.main_window.address_bar self.pointing_device.click_object(address_bar) address_bar.activeFocus.wait_for(True) self.keyboard.press_and_release("Enter") address_bar.activeFocus.wait_for(False) def test_looses_focus_when_loading_starts(self): address_bar = self.main_window.address_bar self.pointing_device.click_object(address_bar) address_bar.activeFocus.wait_for(True) url = self.base_url + "/test2" self.main_window.go_to_url(url) address_bar.activeFocus.wait_for(False) def test_looses_focus_when_reloading(self): address_bar = self.main_window.address_bar self.pointing_device.click_object(address_bar) address_bar.activeFocus.wait_for(True) # Work around https://launchpad.net/bugs/1417118 by clearing the # address bar and typing again the current URL to enable the reload # button. address_bar.clear() address_bar.write(self.url) address_bar.click_action_button() address_bar.activeFocus.wait_for(False) # http://pad.lv/1456199 @testtools.skipIf(model() != "Desktop", "on desktop only") def test_clears_when_actual_url_changed(self): address_bar = self.main_window.address_bar self.pointing_device.click_object(address_bar) address_bar.activeFocus.wait_for(True) url = self.base_url + "/test1" self.main_window.go_to_url(url) self.main_window.wait_until_page_loaded(url) self.pointing_device.click_object(address_bar) address_bar.activeFocus.wait_for(True) self.new_tab_view = self.open_new_tab(open_tabs_view=True) self.assertThat(address_bar.text, Eventually(Equals(""))) # http://pad.lv/1487713 def test_does_not_clear_when_typing_while_loading(self): address_bar = self.main_window.address_bar self.pointing_device.click_object(address_bar) address_bar.activeFocus.wait_for(True) url = self.base_url + "/wait/3" self.main_window.go_to_url(url) self.pointing_device.click_object(address_bar) address_bar.write("x") self.main_window.wait_until_page_loaded(url) self.assertThat(address_bar.text, Equals("x")) ./tests/autopilot/webbrowser_app/tests/test_content_pick.py0000644000015600001650000000252712703462031024537 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright 2014-2015 Canonical # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from webbrowser_app.tests import StartOpenRemotePageTestCaseBase from autopilot.matchers import Eventually from autopilot.platform import model from testtools.matchers import Equals import unittest class TestContentPick(StartOpenRemotePageTestCaseBase): def setUp(self): super(TestContentPick, self).setUp(path="/uploadform") @unittest.skipIf(model() == "Desktop", "on devices only") def test_picker_dialog_shows_up(self): webview = self.main_window.get_current_webview() self.pointing_device.click_object(webview) dialog = self.main_window.get_content_picker_dialog() self.assertThat(dialog.visible, Eventually(Equals(True))) ./tests/autopilot/webbrowser_app/tests/test_new_tab_view.py0000644000015600001650000006007312703462031024530 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright 2015-2016 Canonical # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os.path import sqlite3 import time import testtools from autopilot.platform import model from autopilot.matchers import Eventually from testtools.matchers import Equals, NotEquals from webbrowser_app.tests import StartOpenRemotePageTestCaseBase class TestNewTabViewLifetime(StartOpenRemotePageTestCaseBase): def test_new_tab_view_destroyed_when_browsing(self): new_tab_view = self.open_new_tab(open_tabs_view=True) self.main_window.go_to_url(self.base_url + "/test2") new_tab_view.wait_until_destroyed() def test_new_tab_view_destroyed_when_closing_tab(self): new_tab_view = self.open_new_tab(open_tabs_view=True) if self.main_window.wide: self.main_window.chrome.get_tabs_bar().close_tab(1) else: tabs_view = self.open_tabs_view() tabs_view.get_previews()[0].close() toolbar = self.main_window.get_recent_view_toolbar() toolbar.click_button("doneButton") new_tab_view.wait_until_destroyed() def test_new_tab_view_is_shared_between_tabs(self): # Open one new tab new_tab_view = self.open_new_tab(open_tabs_view=True) # Open a second new tab new_tab_view_2 = self.open_new_tab(open_tabs_view=True) # Verify that they share the same NewTabView instance self.assertThat(new_tab_view_2.id, Equals(new_tab_view.id)) # Close the second new tab, and verify that the NewTabView instance # is still there if self.main_window.wide: self.main_window.chrome.get_tabs_bar().close_tab(2) else: tabs_view = self.open_tabs_view() tabs_view.get_previews()[0].close() toolbar = self.main_window.get_recent_view_toolbar() toolbar.click_button("doneButton") tabs_view.visible.wait_for(False) self.assertThat(new_tab_view.visible, Equals(True)) # Close the first new tab, and verify that the NewTabView instance # is destroyed if self.main_window.wide: self.main_window.chrome.get_tabs_bar().close_tab(1) else: tabs_view = self.open_tabs_view() tabs_view.get_previews()[0].close() toolbar = self.main_window.get_recent_view_toolbar() toolbar.click_button("doneButton") new_tab_view.wait_until_destroyed() @testtools.skipIf(model() == "Desktop", "Closing the last open tab on desktop quits the app") def test_new_tab_view_not_destroyed_when_closing_last_open_tab(self): if self.main_window.wide: self.main_window.chrome.get_tabs_bar().close_tab(0) else: tabs_view = self.open_tabs_view() tabs_view.get_previews()[0].close() tabs_view.visible.wait_for(False) new_tab_view = self.main_window.get_new_tab_view() # Verify that the new tab view is not destroyed and then re-created # when closing the last open tab if it was a blank one if self.main_window.wide: self.main_window.chrome.get_tabs_bar().close_tab(0) else: tabs_view = self.open_tabs_view() tabs_view.get_previews()[0].close() tabs_view.visible.wait_for(False) self.assertThat(new_tab_view.visible, Equals(True)) class TestNewPrivateTabViewLifetime(StartOpenRemotePageTestCaseBase): def test_new_private_tab_view_destroyed_when_browsing(self): self.main_window.enter_private_mode() new_private_tab_view = self.main_window.get_new_private_tab_view() self.main_window.go_to_url(self.base_url + "/test2") new_private_tab_view.wait_until_destroyed() def test_new_private_tab_view_destroyed_when_leaving_private_mode(self): self.main_window.enter_private_mode() new_private_tab_view = self.main_window.get_new_private_tab_view() self.main_window.leave_private_mode() new_private_tab_view.wait_until_destroyed() def test_new_private_tab_view_is_shared_between_tabs(self): self.main_window.enter_private_mode() new_private_tab_view = self.main_window.get_new_private_tab_view() self.main_window.go_to_url(self.base_url + "/test2") new_private_tab_view.wait_until_destroyed() # Open one new private tab new_private_tab_view = self.open_new_tab(open_tabs_view=True) # Open a second new private tab new_private_tab_view_2 = self.open_new_tab(open_tabs_view=True) # Verify that they share the same NewPrivateTabView instance self.assertThat(new_private_tab_view_2.id, Equals(new_private_tab_view.id)) # Close the second new private tab, and verify that the # NewPrivateTabView instance is still there if self.main_window.wide: self.main_window.chrome.get_tabs_bar().close_tab(2) else: tabs_view = self.open_tabs_view() tabs_view.get_previews()[0].close() toolbar = self.main_window.get_recent_view_toolbar() toolbar.click_button("doneButton") tabs_view.visible.wait_for(False) self.assertThat(new_private_tab_view.visible, Equals(True)) # Close the first new private tab, and verify that the # NewPrivateTabView instance is destroyed if self.main_window.wide: self.main_window.chrome.get_tabs_bar().close_tab(1) else: tabs_view = self.open_tabs_view() tabs_view.get_previews()[0].close() toolbar = self.main_window.get_recent_view_toolbar() toolbar.click_button("doneButton") new_private_tab_view.wait_until_destroyed() class TestNewTabViewContentsBase(StartOpenRemotePageTestCaseBase): def setUp(self): self.create_temporary_profile() self.populate_config() self.populate_bookmarks() super(TestNewTabViewContentsBase, self).setUp() self.new_tab_view = self.open_new_tab(open_tabs_view=True) def populate_config(self): self.homepage = "http://test/test2" config_file = os.path.join(self.config_location, "webbrowser-app.conf") with open(config_file, "w") as f: f.write("[General]\n") f.write("homepage={}".format(self.homepage)) def populate_bookmarks(self): db_path = os.path.join(self.data_location, "bookmarks.sqlite") connection = sqlite3.connect(db_path) connection.execute("""CREATE TABLE IF NOT EXISTS folders (folderId INTEGER PRIMARY KEY, folder VARCHAR);""") rows = [ "Actinide", "NobleGas", ] for row in rows: query = "INSERT INTO folders (folder) VALUES ('{}');" query = query.format(row) connection.execute(query) foldersId = dict(connection.execute("""SELECT folder, folderId FROM folders;""")) connection.execute("""CREATE TABLE IF NOT EXISTS bookmarks (url VARCHAR, title VARCHAR, icon VARCHAR, created INTEGER, folderId INTEGER);""") rows = [ ("http://test/periodic-table/element/24/chromium", "Chromium - Element Information", 0), ("http://test/periodic-table/element/77/iridium", "Iridium - Element Information", 0), ("http://test/periodic-table/element/31/gallium", "Gallium - Element Information", 0), ("http://test/periodic-table/element/116/livermorium", "Livermorium - Element Information", 0), ("http://test/periodic-table/element/89/actinium", "Actinium - Element Information", foldersId['Actinide']), ("http://test/periodic-table/element/2/helium", "Helium - Element Information", foldersId['NobleGas']), ] for i, row in enumerate(rows): timestamp = int(time.time()) - i * 10 query = "INSERT INTO bookmarks \ VALUES ('{}', '{}', '', {}, {});" query = query.format(row[0], row[1], timestamp, row[2]) connection.execute(query) connection.commit() connection.close() class TestNewTabViewContentsNarrow(TestNewTabViewContentsBase): def setUp(self): super(TestNewTabViewContentsNarrow, self).setUp() if self.main_window.wide: self.skipTest("Only on narrow form factors") def test_default_home_bookmark(self): homepage_bookmark = self.new_tab_view.get_homepage_bookmark() self.assertThat(homepage_bookmark.url, Equals(self.homepage)) self.pointing_device.click_object(homepage_bookmark) self.new_tab_view.wait_until_destroyed() self.main_window.wait_until_page_loaded(self.homepage) def test_open_top_site(self): top_sites = self.new_tab_view.get_top_sites_list() self.assertThat(lambda: len(top_sites.get_delegates()), Eventually(Equals(1))) top_site = top_sites.get_delegates()[0] url = top_site.url self.pointing_device.click_object(top_site) self.new_tab_view.wait_until_destroyed() self.main_window.wait_until_page_loaded(url) def test_open_bookmark(self): bookmark = self.new_tab_view.get_bookmark_delegates()[2] url = bookmark.url self.pointing_device.click_object(bookmark) self.new_tab_view.wait_until_destroyed() self.main_window.wait_until_page_loaded(url) def test_open_bookmark_when_expanded(self): more_button = self.new_tab_view.get_bookmarks_more_button() self.assertThat(more_button.visible, Equals(True)) self.pointing_device.click_object(more_button) folders = self.new_tab_view.get_bookmarks_folder_list_view() folder_delegate = folders.get_folder_delegate("") self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(5))) bookmark = folders.get_urls_from_folder(folder_delegate)[0] url = bookmark.url self.pointing_device.click_object(bookmark) self.new_tab_view.wait_until_destroyed() self.main_window.wait_until_page_loaded(url) def test_bookmarks_section_expands_and_collapses(self): bookmarks = self.new_tab_view.get_bookmarks_list() top_sites = self.new_tab_view.get_top_sites_list() self.assertThat(top_sites.visible, Equals(True)) # When the bookmarks list is collapsed, it shows a maximum of 5 entries self.assertThat(bookmarks.count, Eventually(Equals(5))) # When expanded, it shows all entries more_button = self.new_tab_view.get_bookmarks_more_button() self.assertThat(more_button.visible, Equals(True)) self.pointing_device.click_object(more_button) folders = self.new_tab_view.get_bookmarks_folder_list_view() folder_delegate = folders.get_folder_delegate("") self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(5))) self.assertThat(top_sites.visible, Eventually(Equals(False))) # Collapse again self.assertThat(more_button.visible, Equals(True)) self.pointing_device.click_object(more_button) bookmarks = self.new_tab_view.get_bookmarks_list() self.assertThat(bookmarks.count, Eventually(Equals(5))) self.assertThat(top_sites.visible, Eventually(Equals(True))) def _remove_first_bookmark(self): bookmark = self.new_tab_view.get_bookmark_delegates()[1] url = bookmark.url bookmark.trigger_leading_action("leadingAction.delete", lambda: None) self.assertThat( lambda: self.new_tab_view.get_bookmark_delegates()[1].url, Eventually(NotEquals(url))) def _remove_first_bookmark_from_folder(self, folder): folders = self.new_tab_view.get_bookmarks_folder_list_view() folder_delegate = folders.get_folder_delegate(folder) delegate = folders.get_urls_from_folder(folder_delegate)[0] url = delegate.url count = len(folders.get_urls_from_folder(folder_delegate)) delegate.trigger_leading_action("leadingAction.delete", delegate.wait_until_destroyed) if ((count - 1) > 4): self.assertThat( lambda: folders.get_urls_from_folder(folder_delegate)[0], Eventually(NotEquals(url))) def _toggle_bookmark_folder(self, folder): folders = self.new_tab_view.get_bookmarks_folder_list_view() folder_delegate = folders.get_folder_delegate(folder) self.pointing_device.click_object( folders.get_header_from_folder(folder_delegate)) def test_remove_bookmarks_when_collapsed(self): bookmarks = self.new_tab_view.get_bookmarks_list() self.assertThat(bookmarks.count, Eventually(Equals(5))) more_button = self.new_tab_view.get_bookmarks_more_button() for i in range(3): self._remove_first_bookmark() self.assertThat(more_button.visible, Eventually(Equals(i < 1))) self.assertThat(bookmarks.count, Equals(5 if (i < 2) else 4)) def test_remove_bookmarks_when_expanded(self): more_button = self.new_tab_view.get_bookmarks_more_button() self.assertThat(more_button.visible, Equals(True)) self.pointing_device.click_object(more_button) folders = self.new_tab_view.get_bookmarks_folder_list_view() folder_delegate = folders.get_folder_delegate("") self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(5))) more_button = self.new_tab_view.get_bookmarks_more_button() top_sites = self.new_tab_view.get_top_sites_list() self._toggle_bookmark_folder("Actinide") self._remove_first_bookmark_from_folder("Actinide") self._toggle_bookmark_folder("NobleGas") self._remove_first_bookmark_from_folder("NobleGas") self.assertThat(more_button.visible, Eventually(Equals(False))) self.assertThat(top_sites.visible, Eventually(Equals(True))) def test_show_bookmarks_folders_when_expanded(self): more_button = self.new_tab_view.get_bookmarks_more_button() self.assertThat(more_button.visible, Equals(True)) self.pointing_device.click_object(more_button) folders = self.new_tab_view.get_bookmarks_folder_list_view() self.assertThat(lambda: len(folders.get_delegates()), Eventually(Equals(3))) folder_delegate = folders.get_folder_delegate("") self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(5))) self._toggle_bookmark_folder("Actinide") folder_delegate = folders.get_folder_delegate("Actinide") self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(1))) self._toggle_bookmark_folder("NobleGas") folder_delegate = folders.get_folder_delegate("NobleGas") self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(1))) def test_collapsed_bookmarks_folders_when_expanded(self): more_button = self.new_tab_view.get_bookmarks_more_button() self.assertThat(more_button.visible, Equals(True)) self.pointing_device.click_object(more_button) folders = self.new_tab_view.get_bookmarks_folder_list_view() self.assertThat(lambda: len(folders.get_delegates()), Eventually(Equals(3))) folder_delegate = folders.get_folder_delegate("") self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(5))) folder_delegate = folders.get_folder_delegate("Actinide") self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(0))) folder_delegate = folders.get_folder_delegate("NobleGas") self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(0))) def test_hide_empty_bookmarks_folders_when_expanded(self): more_button = self.new_tab_view.get_bookmarks_more_button() self.assertThat(more_button.visible, Equals(True)) self.pointing_device.click_object(more_button) folders = self.new_tab_view.get_bookmarks_folder_list_view() self.assertThat(lambda: len(folders.get_delegates()), Eventually(Equals(3))) self._toggle_bookmark_folder("Actinide") folder_delegate = folders.get_folder_delegate("Actinide") self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(1))) self._remove_first_bookmark_from_folder("Actinide") self.assertThat(lambda: len(folders.get_delegates()), Eventually(Equals(2))) folder_delegate = folders.get_folder_delegate("") self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(5))) self._toggle_bookmark_folder("NobleGas") folder_delegate = folders.get_folder_delegate("NobleGas") self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(1))) def test_bookmarks_folder_expands_and_collapses(self): more_button = self.new_tab_view.get_bookmarks_more_button() self.assertThat(more_button.visible, Equals(True)) self.pointing_device.click_object(more_button) folders = self.new_tab_view.get_bookmarks_folder_list_view() self.assertThat(lambda: len(folders.get_delegates()), Eventually(Equals(3))) folder_delegate = folders.get_folder_delegate("") self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(5))) self.pointing_device.click_object( folders.get_header_from_folder(folder_delegate)) self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(0))) self.pointing_device.click_object( folders.get_header_from_folder(folder_delegate)) self.assertThat(lambda: len(folders.get_urls_from_folder( folder_delegate)), Eventually(Equals(5))) def test_remove_top_sites(self): top_sites = self.new_tab_view.get_top_sites_list() self.assertThat(lambda: len(top_sites.get_delegates()), Eventually(Equals(1))) notopsites_label = self.new_tab_view.get_notopsites_label() self.assertThat(notopsites_label.visible, Eventually(Equals(False))) delegate = top_sites.get_delegates()[0] delegate.hide_from_history(self.main_window) self.assertThat(lambda: len(top_sites.get_delegates()), Eventually(Equals(0))) self.assertThat(notopsites_label.visible, Eventually(Equals(True))) class TestNewTabViewContentsWide(TestNewTabViewContentsBase): def setUp(self): super(TestNewTabViewContentsWide, self).setUp() if not self.main_window.wide: self.skipTest("Only on wide form factors") def test_remove_bookmark(self): view = self.new_tab_view bookmarks = view.get_bookmarks_list() previous_count = len(bookmarks) bookmark = bookmarks[1] bookmark.trigger_leading_action("leadingAction.delete", bookmark.wait_until_destroyed) bookmarks = view.get_bookmarks_list() self.assertThat(len(bookmarks), Equals(previous_count - 1)) def test_remove_top_sites(self): view = self.new_tab_view topsites = view.get_top_site_items() previous_count = len(topsites) topsites[0].hide_from_history(self.main_window) self.assertThat(len(view.get_top_site_items()), Equals(previous_count - 1)) def test_drag_bookmarks(self): view = self.new_tab_view folders = view.get_folders_list() bookmarks = view.get_bookmarks_list() previous_count = len(bookmarks) bookmark = bookmarks[1] title = bookmark.title grip = bookmark.get_grip() rect = grip.globalRect # Test that when hovering normal bookmarks item the grip appears self.assertThat(grip.opacity, Equals(0)) self.pointing_device.move_to_object(bookmark) self.assertThat(grip.opacity, Eventually(Equals(1.0))) # Test that an item bounces back when dragged within the list itself self.pointing_device.drag(rect.x, rect.y, rect.x, rect.y + 200) self.assertThat(grip.globalRect, Eventually(Equals(rect))) # Test that an item bounces back when dragged to the same folder folder = folders[0] folder_cx = folder.globalRect.x + folder.width / 2 folder_cy = folder.globalRect.y + folder.height / 2 # Work around https://launchpad.net/bugs/1499437 by dragging downwards # a little bit first, then to the target folder. self.pointing_device.move_to_object(grip) pos = self.pointing_device.position() self.pointing_device.press() self.pointing_device.move(pos[0], pos[1] + 20) self.pointing_device.move(folder_cx, folder_cy) self.pointing_device.release() self.assertThat(grip.globalRect, Eventually(Equals(rect))) # Test that dragging an item to another folder removes it from this one # and adds it to the target folder folder = folders[2] folder_cx = folder.globalRect.x + folder.width / 2 folder_cy = folder.globalRect.y + folder.height / 2 # Work around https://launchpad.net/bugs/1499437 by dragging downwards # a little bit first, then to the target folder. self.pointing_device.move_to_object(grip) pos = self.pointing_device.position() self.pointing_device.press() self.pointing_device.move(pos[0], pos[1] + 20) # Move the cursor to a few pixels below the vertical center of the # folder to ensure that the folder above doesn’t get targetted instead. self.pointing_device.move(folder_cx, folder_cy + 5) self.pointing_device.release() self.assertThat(lambda: len(view.get_bookmarks_list()), Eventually(NotEquals(previous_count))) # Verify that the item has been added to the top of the target folder self.pointing_device.click_object(folder) self.assertThat(lambda: len(view.get_bookmarks_list()), Eventually(Equals(2))) self.assertThat(view.get_bookmarks_list()[0].title, Equals(title)) ./tests/autopilot/webbrowser_app/tests/test_private.py0000644000015600001650000001222612703462031023526 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright 2015-2016 Canonical # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from testtools.matchers import Equals, NotEquals from autopilot.matchers import Eventually from autopilot.platform import model from webbrowser_app.tests import StartOpenRemotePageTestCaseBase class TestPrivateView(StartOpenRemotePageTestCaseBase): def test_going_in_and_out_private_mode(self): address_bar = self.main_window.address_bar self.main_window.enter_private_mode() self.assertThat(self.main_window.is_in_private_mode, Eventually(Equals(True))) self.assert_number_incognito_webviews_eventually(1) self.assertTrue(self.main_window.is_new_private_tab_view_visible()) self.assertThat(address_bar.activeFocus, Eventually(Equals(model() == 'Desktop'))) self.assertThat(address_bar.text, Eventually(Equals(""))) self.main_window.leave_private_mode() self.assertThat(self.main_window.is_in_private_mode, Eventually(Equals(False))) self.assert_number_incognito_webviews_eventually(0) self.assertThat(address_bar.text, Eventually(NotEquals(""))) self.assertThat(address_bar.activeFocus, Eventually(Equals(False))) def test_leaving_private_mode_with_multiples_tabs_ask_confirmation(self): self.main_window.enter_private_mode() self.assertThat(self.main_window.is_in_private_mode, Eventually(Equals(True))) self.assertTrue(self.main_window.is_new_private_tab_view_visible()) if not self.main_window.wide: self.open_tabs_view() self.open_new_tab() self.main_window.leave_private_mode_with_confirmation() self.assertThat(self.main_window.is_in_private_mode, Eventually(Equals(False))) def test_cancel_leaving_private_mode(self): self.main_window.enter_private_mode() self.assertThat(self.main_window.is_in_private_mode, Eventually(Equals(True))) self.assertTrue(self.main_window.is_new_private_tab_view_visible()) if not self.main_window.wide: self.open_tabs_view() self.open_new_tab() self.main_window.leave_private_mode_with_confirmation(confirm=False) self.assertThat(self.main_window.is_in_private_mode, Eventually(Equals(True))) self.assertTrue(self.main_window.is_new_private_tab_view_visible()) def test_url_showing_in_top_sites_in_and_out_private_mode(self): new_tab = self.open_new_tab(open_tabs_view=True) urls = [site.url for site in new_tab.get_top_site_items()] self.assertIn(self.url, urls) self.main_window.enter_private_mode() self.assertThat(self.main_window.is_in_private_mode, Eventually(Equals(True))) url = self.base_url + "/test2" self.main_window.go_to_url(url) self.main_window.wait_until_page_loaded(url) self.main_window.leave_private_mode() self.assertThat(self.main_window.is_in_private_mode, Eventually(Equals(False))) new_tab = self.open_new_tab(open_tabs_view=True) urls = [site.url for site in new_tab.get_top_site_items()] self.assertNotIn(url, urls) def test_public_tabs_should_not_be_visible_in_private_mode(self): self.open_new_tab(open_tabs_view=True) new_tab_view = self.main_window.get_new_tab_view() url = self.base_url + "/test2" self.main_window.go_to_url(url) new_tab_view.wait_until_destroyed() if self.main_window.wide: tabs = self.main_window.chrome.get_tabs_bar().get_tabs() self.assertThat(len(tabs), Equals(2)) else: tabs_view = self.open_tabs_view() previews = tabs_view.get_previews() self.assertThat(len(previews), Equals(2)) toolbar = self.main_window.get_recent_view_toolbar() toolbar.click_button("doneButton") tabs_view.visible.wait_for(False) self.main_window.enter_private_mode() self.assertThat(self.main_window.is_in_private_mode, Eventually(Equals(True))) self.assertTrue(self.main_window.is_new_private_tab_view_visible()) if self.main_window.wide: tabs = self.main_window.chrome.get_tabs_bar().get_tabs() self.assertThat(len(tabs), Equals(1)) else: tabs_view = self.open_tabs_view() previews = tabs_view.get_previews() self.assertThat(len(previews), Equals(1)) ./tests/autopilot/webbrowser_app/tests/test_addressbar_bookmark.py0000644000015600001650000000630512703462031026054 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright 2014-2015 Canonical # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from testtools.matchers import Equals from autopilot.matchers import Eventually from webbrowser_app.tests import StartOpenRemotePageTestCaseBase class TestAddressBarBookmark(StartOpenRemotePageTestCaseBase): def test_switching_tabs_updates_bookmark_toggle(self): chrome = self.main_window.chrome address_bar = self.main_window.address_bar bookmark_toggle = address_bar.get_bookmark_toggle() self.pointing_device.click_object(bookmark_toggle) bookmark_options = self.main_window.get_bookmark_options() bookmark_options.click_dismiss_button() bookmark_options.wait_until_destroyed() self.assertThat(chrome.bookmarked, Eventually(Equals(True))) self.open_new_tab(open_tabs_view=True) url = self.base_url + "/test2" self.main_window.go_to_url(url) self.main_window.wait_until_page_loaded(url) self.assertThat(chrome.bookmarked, Eventually(Equals(False))) if self.main_window.wide: self.main_window.chrome.get_tabs_bar().select_tab(0) else: tabs_view = self.open_tabs_view() tabs_view.get_previews()[1].select() tabs_view.visible.wait_for(False) self.assertThat(chrome.bookmarked, Eventually(Equals(True))) if self.main_window.wide: self.main_window.chrome.get_tabs_bar().select_tab(1) else: tabs_view = self.open_tabs_view() tabs_view.get_previews()[1].select() tabs_view.visible.wait_for(False) self.assertThat(chrome.bookmarked, Eventually(Equals(False))) def test_cannot_bookmark_empty_page(self): self.open_new_tab(open_tabs_view=True) if self.main_window.wide: self.main_window.chrome.get_tabs_bar().select_tab(0) else: tabs_view = self.open_tabs_view() tabs_view.get_previews()[1].select() tabs_view.visible.wait_for(False) webview = self.main_window.get_current_webview() self.pointing_device.click_object(webview) address_bar = self.main_window.address_bar bookmark_toggle = address_bar.get_bookmark_toggle() self.assertThat(bookmark_toggle.visible, Eventually(Equals(True))) if self.main_window.wide: self.main_window.chrome.get_tabs_bar().select_tab(1) else: tabs_view = self.open_tabs_view() tabs_view.get_previews()[1].select() tabs_view.visible.wait_for(False) self.assertThat(bookmark_toggle.visible, Eventually(Equals(False))) ./tests/autopilot/webbrowser_app/tests/__init__.py0000644000015600001650000002412412703462031022554 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright 2013-2016 Canonical # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """webbrowser-app autopilot tests.""" import os import shutil import signal import tempfile import time import urllib.request import fixtures import psutil from testtools.matchers import Equals, NotEquals from autopilot.matchers import Eventually from autopilot.platform import model from autopilot.testcase import AutopilotTestCase from . import http_server import ubuntuuitoolkit as uitk class BrowserTestCaseBase(AutopilotTestCase): """ A common test case class that provides several useful methods for webbrowser-app tests. """ local_location = "../../src/app/webbrowser/webbrowser-app" d_f = "--desktop_file_hint=/usr/share/applications/webbrowser-app.desktop" ARGS = ["--new-session"] def create_temporary_profile(self): # This method is meant to be called exactly once, in setUp(). # Tests that need to pre-populate the profile may call it earlier. if hasattr(self, '_temp_xdg_dir'): return self._temp_xdg_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self._temp_xdg_dir) appname = 'webbrowser-app' xdg_data = os.path.join(self._temp_xdg_dir, 'data') self.useFixture(fixtures.EnvironmentVariable( 'XDG_DATA_HOME', xdg_data)) self.data_location = os.path.join(xdg_data, appname) if not os.path.exists(self.data_location): os.makedirs(self.data_location) xdg_config = os.path.join(self._temp_xdg_dir, 'config') self.useFixture(fixtures.EnvironmentVariable( 'XDG_CONFIG_HOME', xdg_config)) self.config_location = os.path.join(xdg_config, appname) if not os.path.exists(self.config_location): os.makedirs(self.config_location) xdg_cache = os.path.join(self._temp_xdg_dir, 'cache') self.useFixture(fixtures.EnvironmentVariable( 'XDG_CACHE_HOME', xdg_cache)) self.cache_location = os.path.join(xdg_cache, appname) if not os.path.exists(self.cache_location): os.makedirs(self.cache_location) def setUp(self, launch=True): self.create_temporary_profile() self.pointing_device = uitk.get_pointing_device() super(BrowserTestCaseBase, self).setUp() if (launch): self.launch_app() def launch_app(self): if os.path.exists(self.local_location): self.app = self.launch_test_local() else: self.app = self.launch_test_installed() self.main_window.visible.wait_for(True) def launch_test_local(self): return self.launch_test_application( self.local_location, *self.ARGS, emulator_base=uitk.UbuntuUIToolkitCustomProxyObjectBase) def launch_test_installed(self): if model() == 'Desktop': return self.launch_test_application( "webbrowser-app", *self.ARGS, emulator_base=uitk.UbuntuUIToolkitCustomProxyObjectBase) else: return self.launch_test_application( "webbrowser-app", self.d_f, *self.ARGS, app_type='qt', emulator_base=uitk.UbuntuUIToolkitCustomProxyObjectBase) @property def main_window(self): return self.app.main_window def drag_bottom_edge_upwards(self, fraction): self.assertThat(model(), NotEquals('Desktop')) handleRect = self.main_window.get_bottom_edge_handle().globalRect x = handleRect.x + handleRect.width // 2 y0 = handleRect.y + handleRect.height // 2 y1 = y0 - int(self.main_window.height * fraction) self.pointing_device.drag(x, y0, x, y1) def open_tabs_view(self): self.assertFalse(self.main_window.wide) if model() == 'Desktop': bar = self.main_window.get_bottom_edge_bar() self.pointing_device.click_object(bar) else: self.drag_bottom_edge_upwards(0.75) tabs_view = self.main_window.get_tabs_view() # Give some time for the view to settle so that all previews reached # their initial position (the animation has a duration of # UbuntuAnimation.BriskDuration, i.e. 333ms, so 1s should be plenty). time.sleep(1) return tabs_view def open_new_tab(self, open_tabs_view=False, expand_view=False): if (self.main_window.incognito): count = len(self.main_window.get_incognito_webviews()) else: count = len(self.main_window.get_webviews()) if self.main_window.wide: self.main_window.chrome.get_tabs_bar().click_new_tab_button() else: if open_tabs_view: self.open_tabs_view() tabs_view = self.main_window.get_tabs_view() toolbar = self.main_window.get_recent_view_toolbar() toolbar.click_action("newTabButton") tabs_view.visible.wait_for(False) if (self.main_window.incognito): self.assert_number_incognito_webviews_eventually(count + 1) new_tab_view = self.main_window.get_new_private_tab_view() else: self.assert_number_webviews_eventually(count + 1) new_tab_view = self.main_window.get_new_tab_view() if self.main_window.wide: self.assertThat(self.main_window.address_bar.activeFocus, Eventually(Equals(True))) if not self.main_window.wide and expand_view: more_button = new_tab_view.get_bookmarks_more_button() self.assertThat(more_button.visible, Equals(True)) self.pointing_device.click_object(more_button) return new_tab_view def open_settings(self): chrome = self.main_window.chrome drawer_button = chrome.get_drawer_button() self.pointing_device.click_object(drawer_button) chrome.get_drawer() settings_action = chrome.get_drawer_action("settings") self.pointing_device.click_object(settings_action) return self.main_window.get_settings_page() def open_bookmarks(self): chrome = self.main_window.chrome drawer_button = chrome.get_drawer_button() self.pointing_device.click_object(drawer_button) chrome.get_drawer() bookmarks_action = chrome.get_drawer_action("bookmarks") self.pointing_device.click_object(bookmarks_action) return self.main_window.get_bookmarks_view() def open_history(self): chrome = self.main_window.chrome drawer_button = chrome.get_drawer_button() self.pointing_device.click_object(drawer_button) chrome.get_drawer() history_action = chrome.get_drawer_action("history") self.pointing_device.click_object(history_action) return self.main_window.get_history_view() def open_downloads(self): chrome = self.main_window.chrome drawer_button = chrome.get_drawer_button() self.pointing_device.click_object(drawer_button) chrome.get_drawer() downloads_action = chrome.get_drawer_action("downloads") self.pointing_device.click_object(downloads_action) return self.main_window.get_downloads_page() def assert_number_webviews_eventually(self, count): self.assertThat(lambda: len(self.main_window.get_webviews()), Eventually(Equals(count))) def assert_number_incognito_webviews_eventually(self, count): self.assertThat(lambda: len(self.main_window.get_incognito_webviews()), Eventually(Equals(count))) def ping_server(self, server): url = "http://localhost:{}/ping".format(server.port) ping = urllib.request.urlopen(url) self.assertThat(ping.read(), Equals(b"pong")) def kill_web_processes(self, signal=signal.SIGKILL): children = psutil.Process(self.app.pid).children(True) for child in children: if child.name() == 'oxide-renderer': for arg in child.cmdline(): if '--type=renderer' in arg: os.kill(child.pid, signal) break class StartOpenRemotePageTestCaseBase(BrowserTestCaseBase): """ Helper test class that opens the browser at a remote URL instead of defaulting to the homepage. This class should be preferred to the base test case class, as it doesn’t rely on a connection to the outside world (to open the default homepage), and because it ensures the initial page is fully loaded before the tests are executed, thus making them more robust. """ def setUp(self, path="/test1", launch=True): self.http_server = http_server.HTTPServerInAThread() self.ping_server(self.http_server) self.addCleanup(self.http_server.cleanup) self.useFixture(fixtures.EnvironmentVariable( 'UBUNTU_WEBVIEW_HOST_MAPPING_RULES', "MAP test:80 localhost:{}".format(self.http_server.port))) self.base_domain = "test" self.base_url = "http://" + self.base_domain self.url = self.base_url + path self.ARGS = self.ARGS + [self.url] super(StartOpenRemotePageTestCaseBase, self).setUp(launch) if (launch): self.assert_home_page_eventually_loaded() def launch_and_wait_for_page_loaded(self): self.launch_app() self.assert_home_page_eventually_loaded() def assert_home_page_eventually_loaded(self): self.main_window.wait_until_page_loaded(self.url) ./tests/autopilot/webbrowser_app/tests/test_site_previews.py0000644000015600001650000001532012703462031024742 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright 2015-2016 Canonical # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import hashlib import os from os import path as path import sqlite3 import time from autopilot.matchers import Eventually from testtools.matchers import Not, Equals, DirExists, DirContains from webbrowser_app.tests import StartOpenRemotePageTestCaseBase class TestSitePreviewsBase(StartOpenRemotePageTestCaseBase): def setUp(self): super(TestSitePreviewsBase, self).setUp(launch=False) self.captures_dir = path.join(self.cache_location, "captures") def file_in_dir(self, file, dir): return path.exists(path.join(dir, file)) def capture_file(self, url): return hashlib.md5(url.encode()).hexdigest() + ".png" class TestSitePreviewsNoLaunch(TestSitePreviewsBase): def populate_captures_dir(self, capture_names): # captures dir should not exist on fresh run self.assertThat(self.captures_dir, Not(DirExists())) # create some random files and ensure they get cleaned up os.mkdir(self.captures_dir) for capture_name in capture_names: open(path.join(self.captures_dir, capture_name), 'w').close() self.assertThat(self.captures_dir, DirContains(capture_names)) def populate_history(self): self.countries = [ "Japan", "Russia", "France", "Italy", "Argentina", "Canada", "Mexico", "Peru", "Congo", "Brazil", "China", "Mali", "Morocco" ] db_path = os.path.join(self.data_location, "history.sqlite") connection = sqlite3.connect(db_path) connection.execute("""CREATE TABLE IF NOT EXISTS history (url VARCHAR, domain VARCHAR, title VARCHAR, icon VARCHAR, visits INTEGER, lastVisit DATETIME);""") visits = 50 for country in self.countries: timestamp = int(time.time()) query = "INSERT INTO history \ VALUES ('{}', '{}', '{}', '', {}, {});" query = query.format("http://en.wikipedia.org/wiki/" + country, "wikipedia.org", country, visits, timestamp) connection.execute(query) visits -= 1 connection.commit() connection.close() def test_cleanup_previews_on_startup(self): self.populate_history() # populate the captures dir with correct thumbnail names for all # the sites in history... history = ["http://en.wikipedia.org/wiki/" + c for c in self.countries] history = [self.capture_file(url) for url in history] # ...plus some other files to verify possible corner cases other_url = self.capture_file("http://google.com/") not_hash = "not_a_preview.jpg" not_image = "not_an_image.xxx" self.populate_captures_dir(history + [other_url, not_hash, not_image]) self.launch_app() time.sleep(1) # wait for file system to settle # verify that non-image files and top 10 sites are left alone, # everything else is cleaned up topsites = history[0:10] current_tab = self.capture_file(self.url) self.assertThat(self.captures_dir, DirContains(topsites + [not_image, current_tab])) class TestSitePreviews(TestSitePreviewsBase): def setUp(self): super(TestSitePreviews, self).setUp() self.launch_and_wait_for_page_loaded() def close_tab(self, index): if self.main_window.wide: self.main_window.chrome.get_tabs_bar().close_tab(index) else: tabs_view = self.open_tabs_view() tabs_view.get_previews()[index].close() toolbar = self.main_window.get_recent_view_toolbar() toolbar.click_button("doneButton") def get_captures(self): if not path.exists(self.captures_dir): return [] all = os.listdir(self.captures_dir) cap = [f for f in all if path.isfile(path.join(self.captures_dir, f))] return cap def remove_top_site(self, new_tab_view, url): top_sites = new_tab_view.get_top_site_items() top_sites = [d for d in top_sites if d.url == url] self.assertThat(len(top_sites), Equals(1)) delegate = top_sites[0] delegate.hide_from_history(self.main_window) def test_save_on_switch_tab_and_not_delete_if_topsite(self): previous = self.main_window.get_current_webview().url # switching away from tab should save a capture self.open_new_tab(open_tabs_view=True) self.assertThat(self.captures_dir, DirContains([self.capture_file(previous)])) # closing the captured tab should not delete the capture since it is # now part of the top sites (being the only one we opened so far) self.close_tab(0) time.sleep(0.5) # wait for file system to settle self.assertThat(self.captures_dir, DirContains([self.capture_file(previous)])) def test_save_on_switch_tab_and_delete_if_not_topsite(self): previous = self.main_window.get_current_webview().url new_tab_view = self.open_new_tab(open_tabs_view=True) self.remove_top_site(new_tab_view, previous) self.close_tab(0) self.assertThat(lambda: self.file_in_dir(self.capture_file(previous), self.captures_dir), Eventually(Equals(False))) def test_delete_when_tab_closed_and_removed_from_topsites(self): previous = self.main_window.get_current_webview().url capture = self.capture_file(previous) new_tab_view = self.open_new_tab(open_tabs_view=True) self.close_tab(0) time.sleep(0.5) # wait for file system to settle self.assertThat(self.captures_dir, DirContains([capture])) if not self.main_window.wide: new_tab_view = self.open_new_tab(open_tabs_view=True) self.remove_top_site(new_tab_view, previous) self.assertThat(lambda: self.file_in_dir(capture, self.captures_dir), Eventually(Equals(False))) ./tests/autopilot/webbrowser_app/tests/http_server.py0000644000015600001650000003056012703462031023363 0ustar jenkinsjenkins# -*- coding: utf-8 -*- # # Copyright 2013-2016 Canonical # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. from base64 import b64decode import http.server as http import json import logging import threading import time logger = logging.getLogger(__name__) class HTTPRequestHandler(http.BaseHTTPRequestHandler): """ A custom HTTP request handler that serves GET resources. """ suggestions_data = {} base64_png_data = \ "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAIAAACRXR/mAAAACXBIWXMAAAsTAAALEwE" \ "AmpwYAAAAOUlEQVRYw+3OAQ0AAAgDoGv/zlpDN0hATS7qaGlpaWlpaWlpaWlpaWlpaW" \ "lpaWlpaWlpaWlpab1qLUGqAWNyFWTYAAAAAElFTkSuQmCC" def make_html(self, title, body): html = "{}{}" return html.format(title, body) def send_html(self, html): self.send_header("Content-Type", "text/html") self.end_headers() self.wfile.write(html.encode()) def send_auth_request(self): self.send_response(401) self.send_header("WWW-Authenticate", "Basic realm=\"Enter Password\"") self.end_headers() self.send_html("Not Authorized") def do_GET(self): if self.path == "/ping": self.send_response(200) self.send_header("Content-Type", "text/plain") self.end_headers() self.wfile.write(b"pong") elif self.path == "/test1": self.send_response(200) title = "test page 1" body = "

test page 1

" html = self.make_html(title, body) self.send_html(html) elif self.path == "/test2": self.send_response(200) title = "test page 2" body = "

test page 2

" html = self.make_html(title, body) self.send_html(html) elif self.path == "/link": self.send_response(200) html = '' html += '
' html += '' self.send_html(html) elif self.path.startswith("/wait/"): delay = int(self.path[6:]) self.send_response(200) title = "waiting {} seconds".format(delay) body = "

this page took {} seconds to load

".format(delay) html = self.make_html(title, body) time.sleep(delay) self.send_html(html) elif self.path == "/blanktargetlink": # craft a page that accepts clicks anywhere inside its window # and that requests opening another page in a new tab self.send_response(200) html = '' html += '' html += '
' html += '' self.send_html(html) elif self.path == "/fulliframewithblanktargetlink": # iframe that takes up the whole page and that contains # the page above self.send_response(200) html = '' html += '