Merge lp:~phill-ridout/openlp/ftw-json-theme-list into lp:openlp
- ftw-json-theme-list
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~phill-ridout/openlp/ftw-json-theme-list |
Merge into: | lp:openlp |
Diff against target: |
961 lines (+395/-269) 6 files modified
openlp/core/common/httputils.py (+47/-1) openlp/core/ui/firsttimeform.py (+118/-186) openlp/core/ui/firsttimewizard.py (+50/-15) openlp/core/ui/icons.py (+2/-0) tests/functional/openlp_core/ui/test_firsttimeform.py (+90/-67) tests/interfaces/openlp_core/ui/test_firsttimeform.py (+88/-0) |
To merge this branch: | bzr merge lp:~phill-ridout/openlp/ftw-json-theme-list |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenLP Core | Pending | ||
Review via email: mp+363275@code.launchpad.net |
This proposal has been superseded by a proposal from 2019-02-16.
Commit message
move ftw to new json config format. spruce up theme list page
Description of the change
lp:~phill-ridout/openlp/ftw-json-theme-list (revision 2852)
https:/
https:/
https:/
https:/
https:/
https:/
https:/
https:/
Raoul Snyman (raoul-snyman) wrote : | # |
Raoul Snyman (raoul-snyman) wrote : | # |
Linting passed!
Raoul Snyman (raoul-snyman) wrote : | # |
macOS tests passed!
Unmerged revisions
Preview Diff
1 | === modified file 'openlp/core/common/httputils.py' | |||
2 | --- openlp/core/common/httputils.py 2019-02-14 15:09:09 +0000 | |||
3 | +++ openlp/core/common/httputils.py 2019-02-16 08:58:10 +0000 | |||
4 | @@ -27,12 +27,16 @@ | |||
5 | 27 | import sys | 27 | import sys |
6 | 28 | import time | 28 | import time |
7 | 29 | from random import randint | 29 | from random import randint |
8 | 30 | from tempfile import gettempdir | ||
9 | 30 | 31 | ||
10 | 31 | import requests | 32 | import requests |
11 | 33 | from PyQt5 import QtCore | ||
12 | 32 | 34 | ||
13 | 33 | from openlp.core.common import trace_error_handler | 35 | from openlp.core.common import trace_error_handler |
14 | 36 | from openlp.core.common.path import Path | ||
15 | 34 | from openlp.core.common.registry import Registry | 37 | from openlp.core.common.registry import Registry |
16 | 35 | from openlp.core.common.settings import ProxyMode, Settings | 38 | from openlp.core.common.settings import ProxyMode, Settings |
17 | 39 | from openlp.core.threading import ThreadWorker | ||
18 | 36 | 40 | ||
19 | 37 | 41 | ||
20 | 38 | log = logging.getLogger(__name__ + '.__init__') | 42 | log = logging.getLogger(__name__ + '.__init__') |
21 | @@ -227,4 +231,46 @@ | |||
22 | 227 | return True | 231 | return True |
23 | 228 | 232 | ||
24 | 229 | 233 | ||
26 | 230 | __all__ = ['get_web_page'] | 234 | class DownloadWorker(ThreadWorker): |
27 | 235 | """ | ||
28 | 236 | This worker allows a file to be downloaded in a thread | ||
29 | 237 | """ | ||
30 | 238 | download_failed = QtCore.pyqtSignal() | ||
31 | 239 | download_succeeded = QtCore.pyqtSignal(Path) | ||
32 | 240 | |||
33 | 241 | def __init__(self, base_url, file_name): | ||
34 | 242 | """ | ||
35 | 243 | Set up the worker object | ||
36 | 244 | """ | ||
37 | 245 | self._base_url = base_url | ||
38 | 246 | self._file_name = file_name | ||
39 | 247 | self._download_cancelled = False | ||
40 | 248 | super().__init__() | ||
41 | 249 | |||
42 | 250 | def start(self): | ||
43 | 251 | """ | ||
44 | 252 | Download the url to the temporary directory | ||
45 | 253 | """ | ||
46 | 254 | if self._download_cancelled: | ||
47 | 255 | self.quit.emit() | ||
48 | 256 | return | ||
49 | 257 | try: | ||
50 | 258 | dest_path = Path(gettempdir()) / 'openlp' / self._file_name | ||
51 | 259 | url = f'{self._base_url}{self._file_name}' | ||
52 | 260 | is_success = download_file(self, url, dest_path) | ||
53 | 261 | if is_success and not self._download_cancelled: | ||
54 | 262 | self.download_succeeded.emit(dest_path) | ||
55 | 263 | else: | ||
56 | 264 | self.download_failed.emit() | ||
57 | 265 | except: # noqa | ||
58 | 266 | log.exception('Unable to download %s', url) | ||
59 | 267 | self.download_failed.emit() | ||
60 | 268 | finally: | ||
61 | 269 | self.quit.emit() | ||
62 | 270 | |||
63 | 271 | @QtCore.pyqtSlot() | ||
64 | 272 | def cancel_download(self): | ||
65 | 273 | """ | ||
66 | 274 | A slot to allow the download to be cancelled from outside of the thread | ||
67 | 275 | """ | ||
68 | 276 | self._download_cancelled = True | ||
69 | 231 | 277 | ||
70 | === modified file 'openlp/core/ui/firsttimeform.py' | |||
71 | --- openlp/core/ui/firsttimeform.py 2019-02-14 15:09:09 +0000 | |||
72 | +++ openlp/core/ui/firsttimeform.py 2019-02-16 08:58:10 +0000 | |||
73 | @@ -22,19 +22,19 @@ | |||
74 | 22 | """ | 22 | """ |
75 | 23 | This module contains the first time wizard. | 23 | This module contains the first time wizard. |
76 | 24 | """ | 24 | """ |
77 | 25 | import json | ||
78 | 25 | import logging | 26 | import logging |
79 | 26 | import time | 27 | import time |
80 | 27 | import urllib.error | 28 | import urllib.error |
81 | 28 | import urllib.parse | 29 | import urllib.parse |
82 | 29 | import urllib.request | 30 | import urllib.request |
83 | 30 | from configparser import ConfigParser, MissingSectionHeaderError, NoOptionError, NoSectionError | ||
84 | 31 | from tempfile import gettempdir | 31 | from tempfile import gettempdir |
85 | 32 | 32 | ||
86 | 33 | from PyQt5 import QtCore, QtWidgets | 33 | from PyQt5 import QtCore, QtWidgets |
87 | 34 | 34 | ||
88 | 35 | from openlp.core.common import clean_button_text, trace_error_handler | 35 | from openlp.core.common import clean_button_text, trace_error_handler |
89 | 36 | from openlp.core.common.applocation import AppLocation | 36 | from openlp.core.common.applocation import AppLocation |
91 | 37 | from openlp.core.common.httputils import download_file, get_url_file_size, get_web_page | 37 | from openlp.core.common.httputils import DownloadWorker, download_file, get_url_file_size, get_web_page |
92 | 38 | from openlp.core.common.i18n import translate | 38 | from openlp.core.common.i18n import translate |
93 | 39 | from openlp.core.common.mixins import RegistryProperties | 39 | from openlp.core.common.mixins import RegistryProperties |
94 | 40 | from openlp.core.common.path import Path, create_paths | 40 | from openlp.core.common.path import Path, create_paths |
95 | @@ -43,57 +43,50 @@ | |||
96 | 43 | from openlp.core.lib import build_icon | 43 | from openlp.core.lib import build_icon |
97 | 44 | from openlp.core.lib.plugin import PluginStatus | 44 | from openlp.core.lib.plugin import PluginStatus |
98 | 45 | from openlp.core.lib.ui import critical_error_message_box | 45 | from openlp.core.lib.ui import critical_error_message_box |
100 | 46 | from openlp.core.threading import ThreadWorker, get_thread_worker, is_thread_finished, run_thread | 46 | from openlp.core.threading import get_thread_worker, is_thread_finished, run_thread |
101 | 47 | from openlp.core.ui.firsttimewizard import FirstTimePage, UiFirstTimeWizard | 47 | from openlp.core.ui.firsttimewizard import FirstTimePage, UiFirstTimeWizard |
102 | 48 | from openlp.core.ui.icons import UiIcons | ||
103 | 48 | 49 | ||
104 | 49 | 50 | ||
105 | 50 | log = logging.getLogger(__name__) | 51 | log = logging.getLogger(__name__) |
106 | 51 | 52 | ||
107 | 52 | 53 | ||
152 | 53 | class ThemeScreenshotWorker(ThreadWorker): | 54 | class ThemeListWidgetItem(QtWidgets.QListWidgetItem): |
153 | 54 | """ | 55 | """ |
154 | 55 | This thread downloads a theme's screenshot | 56 | Subclass a QListWidgetItem to allow dynamic loading of thumbnails from an online resource |
155 | 56 | """ | 57 | """ |
156 | 57 | screenshot_downloaded = QtCore.pyqtSignal(str, str, str) | 58 | def __init__(self, themes_url, sample_theme_data, ftw, *args, **kwargs): |
157 | 58 | 59 | super().__init__(*args, **kwargs) | |
158 | 59 | def __init__(self, themes_url, title, filename, sha256, screenshot): | 60 | title = sample_theme_data['title'] |
159 | 60 | """ | 61 | thumbnail = sample_theme_data['thumbnail'] |
160 | 61 | Set up the worker object | 62 | self.file_name = sample_theme_data['file_name'] |
161 | 62 | """ | 63 | self.sha256 = sample_theme_data['sha256'] |
162 | 63 | self.was_cancelled = False | 64 | self.setIcon(UiIcons().picture) # Set a place holder icon whilst the thumbnails download |
163 | 64 | self.themes_url = themes_url | 65 | self.setText(title) |
164 | 65 | self.title = title | 66 | self.setToolTip(title) |
165 | 66 | self.filename = filename | 67 | worker = DownloadWorker(themes_url, thumbnail) |
166 | 67 | self.sha256 = sha256 | 68 | worker.download_failed.connect(self._on_download_failed) |
167 | 68 | self.screenshot = screenshot | 69 | worker.download_succeeded.connect(self._on_thumbnail_downloaded) |
168 | 69 | super().__init__() | 70 | thread_name = f'thumbnail_download_{thumbnail}' |
169 | 70 | 71 | run_thread(worker, thread_name) | |
170 | 71 | def start(self): | 72 | ftw.thumbnail_download_threads.append(thread_name) |
171 | 72 | """ | 73 | |
172 | 73 | Run the worker | 74 | def _on_download_failed(self): |
173 | 74 | """ | 75 | """ |
174 | 75 | if self.was_cancelled: | 76 | Set an icon to indicate that the thumbnail download has failed. |
175 | 76 | return | 77 | |
176 | 77 | try: | 78 | :rtype: None |
177 | 78 | download_path = Path(gettempdir()) / 'openlp' / self.screenshot | 79 | """ |
178 | 79 | is_success = download_file(self, '{host}{name}'.format(host=self.themes_url, name=self.screenshot), | 80 | self.setIcon(UiIcons().exception) |
179 | 80 | download_path) | 81 | |
180 | 81 | if is_success and not self.was_cancelled: | 82 | def _on_thumbnail_downloaded(self, thumbnail_path): |
181 | 82 | # Signal that the screenshot has been downloaded | 83 | """ |
182 | 83 | self.screenshot_downloaded.emit(self.title, self.filename, self.sha256) | 84 | Load the thumbnail as the icon when it has downloaded. |
183 | 84 | except: # noqa | 85 | |
184 | 85 | log.exception('Unable to download screenshot') | 86 | :param Path thumbnail_path: Path to the file to use as a thumbnail |
185 | 86 | finally: | 87 | :rtype: None |
186 | 87 | self.quit.emit() | 88 | """ |
187 | 88 | 89 | self.setIcon(build_icon(thumbnail_path)) | |
144 | 89 | @QtCore.pyqtSlot(bool) | ||
145 | 90 | def set_download_canceled(self, toggle): | ||
146 | 91 | """ | ||
147 | 92 | Externally set if the download was canceled | ||
148 | 93 | |||
149 | 94 | :param toggle: Set if the download was canceled or not | ||
150 | 95 | """ | ||
151 | 96 | self.was_download_cancelled = toggle | ||
188 | 97 | 90 | ||
189 | 98 | 91 | ||
190 | 99 | class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): | 92 | class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): |
191 | @@ -110,6 +103,9 @@ | |||
192 | 110 | self.web_access = True | 103 | self.web_access = True |
193 | 111 | self.web = '' | 104 | self.web = '' |
194 | 112 | self.setup_ui(self) | 105 | self.setup_ui(self) |
195 | 106 | self.themes_list_widget.itemSelectionChanged.connect(self.on_themes_list_widget_selection_changed) | ||
196 | 107 | self.themes_deselect_all_button.clicked.connect(self.themes_list_widget.clearSelection) | ||
197 | 108 | self.themes_select_all_button.clicked.connect(self.themes_list_widget.selectAll) | ||
198 | 113 | 109 | ||
199 | 114 | def get_next_page_id(self): | 110 | def get_next_page_id(self): |
200 | 115 | """ | 111 | """ |
201 | @@ -144,18 +140,7 @@ | |||
202 | 144 | return -1 | 140 | return -1 |
203 | 145 | elif self.currentId() == FirstTimePage.NoInternet: | 141 | elif self.currentId() == FirstTimePage.NoInternet: |
204 | 146 | return FirstTimePage.Progress | 142 | return FirstTimePage.Progress |
217 | 147 | elif self.currentId() == FirstTimePage.Themes: | 143 | return self.get_next_page_id() |
206 | 148 | self.application.set_busy_cursor() | ||
207 | 149 | while not all([is_thread_finished(thread_name) for thread_name in self.theme_screenshot_threads]): | ||
208 | 150 | time.sleep(0.1) | ||
209 | 151 | self.application.process_events() | ||
210 | 152 | # Build the screenshot icons, as this can not be done in the thread. | ||
211 | 153 | self._build_theme_screenshots() | ||
212 | 154 | self.application.set_normal_cursor() | ||
213 | 155 | self.theme_screenshot_threads = [] | ||
214 | 156 | return self.get_next_page_id() | ||
215 | 157 | else: | ||
216 | 158 | return self.get_next_page_id() | ||
218 | 159 | 144 | ||
219 | 160 | def exec(self): | 145 | def exec(self): |
220 | 161 | """ | 146 | """ |
221 | @@ -172,104 +157,76 @@ | |||
222 | 172 | """ | 157 | """ |
223 | 173 | self.screens = screens | 158 | self.screens = screens |
224 | 174 | self.was_cancelled = False | 159 | self.was_cancelled = False |
226 | 175 | self.theme_screenshot_threads = [] | 160 | self.thumbnail_download_threads = [] |
227 | 176 | self.has_run_wizard = False | 161 | self.has_run_wizard = False |
228 | 177 | 162 | ||
229 | 178 | self.themes_list_widget.itemChanged.connect(self.on_theme_selected) | ||
230 | 179 | |||
231 | 180 | def _download_index(self): | 163 | def _download_index(self): |
232 | 181 | """ | 164 | """ |
233 | 182 | Download the configuration file and kick off the theme screenshot download threads | 165 | Download the configuration file and kick off the theme screenshot download threads |
234 | 183 | """ | 166 | """ |
235 | 184 | # check to see if we have web access | 167 | # check to see if we have web access |
236 | 185 | self.web_access = False | 168 | self.web_access = False |
238 | 186 | self.config = ConfigParser() | 169 | self.config = '' |
239 | 170 | web_config = None | ||
240 | 187 | user_agent = 'OpenLP/' + Registry().get('application').applicationVersion() | 171 | user_agent = 'OpenLP/' + Registry().get('application').applicationVersion() |
241 | 188 | self.application.process_events() | 172 | self.application.process_events() |
242 | 189 | try: | 173 | try: |
244 | 190 | web_config = get_web_page('{host}{name}'.format(host=self.web, name='download.cfg'), | 174 | web_config = get_web_page('{host}{name}'.format(host=self.web, name='download_3.0.json'), |
245 | 191 | headers={'User-Agent': user_agent}) | 175 | headers={'User-Agent': user_agent}) |
246 | 192 | except ConnectionError: | 176 | except ConnectionError: |
247 | 193 | QtWidgets.QMessageBox.critical(self, translate('OpenLP.FirstTimeWizard', 'Network Error'), | 177 | QtWidgets.QMessageBox.critical(self, translate('OpenLP.FirstTimeWizard', 'Network Error'), |
248 | 194 | translate('OpenLP.FirstTimeWizard', 'There was a network error attempting ' | 178 | translate('OpenLP.FirstTimeWizard', 'There was a network error attempting ' |
249 | 195 | 'to connect to retrieve initial configuration information'), | 179 | 'to connect to retrieve initial configuration information'), |
250 | 196 | QtWidgets.QMessageBox.Ok) | 180 | QtWidgets.QMessageBox.Ok) |
263 | 197 | web_config = False | 181 | if web_config and self._parse_config(web_config): |
264 | 198 | if web_config: | 182 | self.web_access = True |
253 | 199 | try: | ||
254 | 200 | self.config.read_string(web_config) | ||
255 | 201 | self.web = self.config.get('general', 'base url') | ||
256 | 202 | self.songs_url = self.web + self.config.get('songs', 'directory') + '/' | ||
257 | 203 | self.bibles_url = self.web + self.config.get('bibles', 'directory') + '/' | ||
258 | 204 | self.themes_url = self.web + self.config.get('themes', 'directory') + '/' | ||
259 | 205 | self.web_access = True | ||
260 | 206 | except (NoSectionError, NoOptionError, MissingSectionHeaderError): | ||
261 | 207 | log.debug('A problem occurred while parsing the downloaded config file') | ||
262 | 208 | trace_error_handler(log) | ||
265 | 209 | self.application.process_events() | 183 | self.application.process_events() |
266 | 210 | self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading {name}...') | 184 | self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading {name}...') |
267 | 211 | if self.has_run_wizard: | ||
268 | 212 | self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active()) | ||
269 | 213 | self.bible_check_box.setChecked(self.plugin_manager.get_plugin_by_name('bibles').is_active()) | ||
270 | 214 | self.presentation_check_box.setChecked(self.plugin_manager.get_plugin_by_name('presentations').is_active()) | ||
271 | 215 | self.image_check_box.setChecked(self.plugin_manager.get_plugin_by_name('images').is_active()) | ||
272 | 216 | self.media_check_box.setChecked(self.plugin_manager.get_plugin_by_name('media').is_active()) | ||
273 | 217 | self.custom_check_box.setChecked(self.plugin_manager.get_plugin_by_name('custom').is_active()) | ||
274 | 218 | self.song_usage_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songusage').is_active()) | ||
275 | 219 | self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active()) | ||
276 | 220 | self.application.set_normal_cursor() | 185 | self.application.set_normal_cursor() |
282 | 221 | # Sort out internet access for downloads | 186 | |
283 | 222 | if self.web_access: | 187 | def _parse_config(self, web_config): |
284 | 223 | songs = self.config.get('songs', 'languages') | 188 | try: |
285 | 224 | songs = songs.split(',') | 189 | config = json.loads(web_config) |
286 | 225 | for song in songs: | 190 | meta = config['_meta'] |
287 | 191 | self.web = meta['base_url'] | ||
288 | 192 | self.songs_url = self.web + meta['songs_dir'] + '/' | ||
289 | 193 | self.bibles_url = self.web + meta['bibles_dir'] + '/' | ||
290 | 194 | self.themes_url = self.web + meta['themes_dir'] + '/' | ||
291 | 195 | for song in config['songs'].values(): | ||
292 | 226 | self.application.process_events() | 196 | self.application.process_events() |
298 | 227 | title = self.config.get('songs_{song}'.format(song=song), 'title') | 197 | item = QtWidgets.QListWidgetItem(song['title'], self.songs_list_widget) |
299 | 228 | filename = self.config.get('songs_{song}'.format(song=song), 'filename') | 198 | item.setData(QtCore.Qt.UserRole, (song['file_name'], song['sha256'])) |
295 | 229 | sha256 = self.config.get('songs_{song}'.format(song=song), 'sha256', fallback='') | ||
296 | 230 | item = QtWidgets.QListWidgetItem(title, self.songs_list_widget) | ||
297 | 231 | item.setData(QtCore.Qt.UserRole, (filename, sha256)) | ||
300 | 232 | item.setCheckState(QtCore.Qt.Unchecked) | 199 | item.setCheckState(QtCore.Qt.Unchecked) |
301 | 233 | item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) | 200 | item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) |
305 | 234 | bible_languages = self.config.get('bibles', 'languages') | 201 | for lang in config['bibles'].values(): |
303 | 235 | bible_languages = bible_languages.split(',') | ||
304 | 236 | for lang in bible_languages: | ||
306 | 237 | self.application.process_events() | 202 | self.application.process_events() |
312 | 238 | language = self.config.get('bibles_{lang}'.format(lang=lang), 'title') | 203 | lang_item = QtWidgets.QTreeWidgetItem(self.bibles_tree_widget, [lang['title']]) |
313 | 239 | lang_item = QtWidgets.QTreeWidgetItem(self.bibles_tree_widget, [language]) | 204 | for translation in lang['translations'].values(): |
309 | 240 | bibles = self.config.get('bibles_{lang}'.format(lang=lang), 'translations') | ||
310 | 241 | bibles = bibles.split(',') | ||
311 | 242 | for bible in bibles: | ||
314 | 243 | self.application.process_events() | 205 | self.application.process_events() |
320 | 244 | title = self.config.get('bible_{bible}'.format(bible=bible), 'title') | 206 | item = QtWidgets.QTreeWidgetItem(lang_item, [translation['title']]) |
321 | 245 | filename = self.config.get('bible_{bible}'.format(bible=bible), 'filename') | 207 | item.setData(0, QtCore.Qt.UserRole, (translation['file_name'], translation['sha256'])) |
317 | 246 | sha256 = self.config.get('bible_{bible}'.format(bible=bible), 'sha256', fallback='') | ||
318 | 247 | item = QtWidgets.QTreeWidgetItem(lang_item, [title]) | ||
319 | 248 | item.setData(0, QtCore.Qt.UserRole, (filename, sha256)) | ||
322 | 249 | item.setCheckState(0, QtCore.Qt.Unchecked) | 208 | item.setCheckState(0, QtCore.Qt.Unchecked) |
323 | 250 | item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) | 209 | item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) |
324 | 251 | self.bibles_tree_widget.expandAll() | 210 | self.bibles_tree_widget.expandAll() |
325 | 252 | self.application.process_events() | 211 | self.application.process_events() |
338 | 253 | # Download the theme screenshots | 212 | for theme in config['themes'].values(): |
339 | 254 | themes = self.config.get('themes', 'files').split(',') | 213 | ThemeListWidgetItem(self.themes_url, theme, self, self.themes_list_widget) |
328 | 255 | for theme in themes: | ||
329 | 256 | title = self.config.get('theme_{theme}'.format(theme=theme), 'title') | ||
330 | 257 | filename = self.config.get('theme_{theme}'.format(theme=theme), 'filename') | ||
331 | 258 | sha256 = self.config.get('theme_{theme}'.format(theme=theme), 'sha256', fallback='') | ||
332 | 259 | screenshot = self.config.get('theme_{theme}'.format(theme=theme), 'screenshot') | ||
333 | 260 | worker = ThemeScreenshotWorker(self.themes_url, title, filename, sha256, screenshot) | ||
334 | 261 | worker.screenshot_downloaded.connect(self.on_screenshot_downloaded) | ||
335 | 262 | thread_name = 'theme_screenshot_{title}'.format(title=title) | ||
336 | 263 | run_thread(worker, thread_name) | ||
337 | 264 | self.theme_screenshot_threads.append(thread_name) | ||
340 | 265 | self.application.process_events() | 214 | self.application.process_events() |
341 | 215 | except Exception: | ||
342 | 216 | log.exception('Unable to parse sample config file %s', web_config) | ||
343 | 217 | critical_error_message_box( | ||
344 | 218 | translate('OpenLP.FirstTimeWizard', 'Invalid index file'), | ||
345 | 219 | translate('OpenLP.FirstTimeWizard', 'OpenLP was unable to read the resource index file. ' | ||
346 | 220 | 'Please try again later.')) | ||
347 | 221 | return False | ||
348 | 222 | return True | ||
349 | 266 | 223 | ||
350 | 267 | def set_defaults(self): | 224 | def set_defaults(self): |
351 | 268 | """ | 225 | """ |
352 | 269 | Set up display at start of theme edit. | 226 | Set up display at start of theme edit. |
353 | 270 | """ | 227 | """ |
354 | 271 | self.restart() | 228 | self.restart() |
356 | 272 | self.web = 'http://openlp.org/files/frw/' | 229 | self.web = 'https://get.openlp.org/ftw/' |
357 | 273 | self.cancel_button.clicked.connect(self.on_cancel_button_clicked) | 230 | self.cancel_button.clicked.connect(self.on_cancel_button_clicked) |
358 | 274 | self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked) | 231 | self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked) |
359 | 275 | self.no_internet_cancel_button.clicked.connect(self.on_no_internet_cancel_button_clicked) | 232 | self.no_internet_cancel_button.clicked.connect(self.on_no_internet_cancel_button_clicked) |
360 | @@ -282,9 +239,18 @@ | |||
361 | 282 | create_paths(Path(gettempdir(), 'openlp')) | 239 | create_paths(Path(gettempdir(), 'openlp')) |
362 | 283 | self.theme_combo_box.clear() | 240 | self.theme_combo_box.clear() |
363 | 284 | if self.has_run_wizard: | 241 | if self.has_run_wizard: |
364 | 242 | self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active()) | ||
365 | 243 | self.bible_check_box.setChecked(self.plugin_manager.get_plugin_by_name('bibles').is_active()) | ||
366 | 244 | self.presentation_check_box.setChecked( | ||
367 | 245 | self.plugin_manager.get_plugin_by_name('presentations').is_active()) | ||
368 | 246 | self.image_check_box.setChecked(self.plugin_manager.get_plugin_by_name('images').is_active()) | ||
369 | 247 | self.media_check_box.setChecked(self.plugin_manager.get_plugin_by_name('media').is_active()) | ||
370 | 248 | self.custom_check_box.setChecked(self.plugin_manager.get_plugin_by_name('custom').is_active()) | ||
371 | 249 | self.song_usage_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songusage').is_active()) | ||
372 | 250 | self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active()) | ||
373 | 285 | # Add any existing themes to list. | 251 | # Add any existing themes to list. |
376 | 286 | for theme in self.theme_manager.get_themes(): | 252 | self.theme_combo_box.insertSeparator(0) |
377 | 287 | self.theme_combo_box.addItem(theme) | 253 | self.theme_combo_box.addItems(sorted(self.theme_manager.get_themes())) |
378 | 288 | default_theme = Settings().value('themes/global theme') | 254 | default_theme = Settings().value('themes/global theme') |
379 | 289 | # Pre-select the current default theme. | 255 | # Pre-select the current default theme. |
380 | 290 | index = self.theme_combo_box.findText(default_theme) | 256 | index = self.theme_combo_box.findText(default_theme) |
381 | @@ -335,49 +301,34 @@ | |||
382 | 335 | Process the triggering of the cancel button. | 301 | Process the triggering of the cancel button. |
383 | 336 | """ | 302 | """ |
384 | 337 | self.was_cancelled = True | 303 | self.was_cancelled = True |
387 | 338 | if self.theme_screenshot_threads: | 304 | if self.thumbnail_download_threads: # TODO: Use main thread list |
388 | 339 | for thread_name in self.theme_screenshot_threads: | 305 | for thread_name in self.thumbnail_download_threads: |
389 | 340 | worker = get_thread_worker(thread_name) | 306 | worker = get_thread_worker(thread_name) |
390 | 341 | if worker: | 307 | if worker: |
392 | 342 | worker.set_download_canceled(True) | 308 | worker.cancel_download() |
393 | 343 | # Was the thread created. | 309 | # Was the thread created. |
396 | 344 | if self.theme_screenshot_threads: | 310 | if self.thumbnail_download_threads: |
397 | 345 | while any([not is_thread_finished(thread_name) for thread_name in self.theme_screenshot_threads]): | 311 | while any([not is_thread_finished(thread_name) for thread_name in self.thumbnail_download_threads]): |
398 | 346 | time.sleep(0.1) | 312 | time.sleep(0.1) |
399 | 347 | self.application.set_normal_cursor() | 313 | self.application.set_normal_cursor() |
400 | 348 | 314 | ||
420 | 349 | def on_screenshot_downloaded(self, title, filename, sha256): | 315 | def on_themes_list_widget_selection_changed(self): |
421 | 350 | """ | 316 | """ |
422 | 351 | Add an item to the list when a theme has been downloaded | 317 | Update the `theme_combo_box` with the selected items |
423 | 352 | 318 | ||
405 | 353 | :param title: The title of the theme | ||
406 | 354 | :param filename: The filename of the theme | ||
407 | 355 | """ | ||
408 | 356 | self.themes_list_widget.blockSignals(True) | ||
409 | 357 | item = QtWidgets.QListWidgetItem(title, self.themes_list_widget) | ||
410 | 358 | item.setData(QtCore.Qt.UserRole, (filename, sha256)) | ||
411 | 359 | item.setCheckState(QtCore.Qt.Unchecked) | ||
412 | 360 | item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) | ||
413 | 361 | self.themes_list_widget.blockSignals(False) | ||
414 | 362 | |||
415 | 363 | def on_theme_selected(self, item): | ||
416 | 364 | """ | ||
417 | 365 | Add or remove a de/selected sample theme from the theme_combo_box | ||
418 | 366 | |||
419 | 367 | :param QtWidgets.QListWidgetItem item: The item that has been de/selected | ||
424 | 368 | :rtype: None | 319 | :rtype: None |
425 | 369 | """ | 320 | """ |
437 | 370 | theme_name = item.text() | 321 | existing_themes = [] |
438 | 371 | if self.theme_manager and theme_name in self.theme_manager.get_themes(): | 322 | if self.theme_manager: |
439 | 372 | return True | 323 | existing_themes = self.theme_manager.get_themes() |
440 | 373 | if item.checkState() == QtCore.Qt.Checked: | 324 | for list_index in range(self.themes_list_widget.count()): |
441 | 374 | self.theme_combo_box.addItem(theme_name) | 325 | item = self.themes_list_widget.item(list_index) |
442 | 375 | return True | 326 | if item.text() not in existing_themes: |
443 | 376 | else: | 327 | cbox_index = self.theme_combo_box.findText(item.text()) |
444 | 377 | index = self.theme_combo_box.findText(theme_name) | 328 | if item.isSelected() and cbox_index == -1: |
445 | 378 | if index != -1: | 329 | self.theme_combo_box.insertItem(0, item.text()) |
446 | 379 | self.theme_combo_box.removeItem(index) | 330 | elif not item.isSelected() and cbox_index != -1: |
447 | 380 | return True | 331 | self.theme_combo_box.removeItem(cbox_index) |
448 | 381 | 332 | ||
449 | 382 | def on_no_internet_finish_button_clicked(self): | 333 | def on_no_internet_finish_button_clicked(self): |
450 | 383 | """ | 334 | """ |
451 | @@ -396,18 +347,6 @@ | |||
452 | 396 | self.was_cancelled = True | 347 | self.was_cancelled = True |
453 | 397 | self.close() | 348 | self.close() |
454 | 398 | 349 | ||
455 | 399 | def _build_theme_screenshots(self): | ||
456 | 400 | """ | ||
457 | 401 | This method builds the theme screenshots' icons for all items in the ``self.themes_list_widget``. | ||
458 | 402 | """ | ||
459 | 403 | themes = self.config.get('themes', 'files') | ||
460 | 404 | themes = themes.split(',') | ||
461 | 405 | for index, theme in enumerate(themes): | ||
462 | 406 | screenshot = self.config.get('theme_{theme}'.format(theme=theme), 'screenshot') | ||
463 | 407 | item = self.themes_list_widget.item(index) | ||
464 | 408 | if item: | ||
465 | 409 | item.setIcon(build_icon(Path(gettempdir(), 'openlp', screenshot))) | ||
466 | 410 | |||
467 | 411 | def update_progress(self, count, block_size): | 350 | def update_progress(self, count, block_size): |
468 | 412 | """ | 351 | """ |
469 | 413 | Calculate and display the download progress. This method is called by download_file(). | 352 | Calculate and display the download progress. This method is called by download_file(). |
470 | @@ -456,13 +395,9 @@ | |||
471 | 456 | self.max_progress += size | 395 | self.max_progress += size |
472 | 457 | iterator += 1 | 396 | iterator += 1 |
473 | 458 | # Loop through the themes list and increase for each selected item | 397 | # Loop through the themes list and increase for each selected item |
481 | 459 | for i in range(self.themes_list_widget.count()): | 398 | for item in self.themes_list_widget.selectedItems(): |
482 | 460 | self.application.process_events() | 399 | size = get_url_file_size(f'{self.themes_url}{item.file_name}') |
483 | 461 | item = self.themes_list_widget.item(i) | 400 | self.max_progress += size |
477 | 462 | if item.checkState() == QtCore.Qt.Checked: | ||
478 | 463 | filename, sha256 = item.data(QtCore.Qt.UserRole) | ||
479 | 464 | size = get_url_file_size('{path}{name}'.format(path=self.themes_url, name=filename)) | ||
480 | 465 | self.max_progress += size | ||
484 | 466 | except urllib.error.URLError: | 401 | except urllib.error.URLError: |
485 | 467 | trace_error_handler(log) | 402 | trace_error_handler(log) |
486 | 468 | critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'), | 403 | critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'), |
487 | @@ -579,15 +514,12 @@ | |||
488 | 579 | missed_files.append('Bible: {name}'.format(name=bible)) | 514 | missed_files.append('Bible: {name}'.format(name=bible)) |
489 | 580 | bibles_iterator += 1 | 515 | bibles_iterator += 1 |
490 | 581 | # Download themes | 516 | # Download themes |
500 | 582 | for i in range(self.themes_list_widget.count()): | 517 | for item in self.themes_list_widget.selectedItems(): |
501 | 583 | item = self.themes_list_widget.item(i) | 518 | self._increment_progress_bar(self.downloading.format(name=item.file_name), 0) |
502 | 584 | if item.checkState() == QtCore.Qt.Checked: | 519 | self.previous_size = 0 |
503 | 585 | theme, sha256 = item.data(QtCore.Qt.UserRole) | 520 | if not download_file( |
504 | 586 | self._increment_progress_bar(self.downloading.format(name=theme), 0) | 521 | self, f'{self.themes_url}{item.file_name}', themes_destination_path / item.file_name, item.sha256): |
505 | 587 | self.previous_size = 0 | 522 | missed_files.append(f'Theme: {item.file_name}') |
497 | 588 | if not download_file(self, '{path}{name}'.format(path=self.themes_url, name=theme), | ||
498 | 589 | themes_destination_path / theme, sha256): | ||
499 | 590 | missed_files.append('Theme: {name}'.format(name=theme)) | ||
506 | 591 | if missed_files: | 523 | if missed_files: |
507 | 592 | file_list = '' | 524 | file_list = '' |
508 | 593 | for entry in missed_files: | 525 | for entry in missed_files: |
509 | 594 | 526 | ||
510 | === modified file 'openlp/core/ui/firsttimewizard.py' | |||
511 | --- openlp/core/ui/firsttimewizard.py 2019-02-14 15:09:09 +0000 | |||
512 | +++ openlp/core/ui/firsttimewizard.py 2019-02-16 08:58:10 +0000 | |||
513 | @@ -49,6 +49,39 @@ | |||
514 | 49 | Progress = 8 | 49 | Progress = 8 |
515 | 50 | 50 | ||
516 | 51 | 51 | ||
517 | 52 | class ThemeListWidget(QtWidgets.QListWidget): | ||
518 | 53 | """ | ||
519 | 54 | Subclass a QListWidget so we can make it look better when it resizes. | ||
520 | 55 | """ | ||
521 | 56 | def __init__(self, *args, **kwargs): | ||
522 | 57 | super().__init__(*args, **kwargs) | ||
523 | 58 | self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) | ||
524 | 59 | self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) | ||
525 | 60 | self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) | ||
526 | 61 | self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) | ||
527 | 62 | self.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) | ||
528 | 63 | self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) | ||
529 | 64 | self.setIconSize(QtCore.QSize(133, 100)) | ||
530 | 65 | self.setMovement(QtWidgets.QListView.Static) | ||
531 | 66 | self.setFlow(QtWidgets.QListView.LeftToRight) | ||
532 | 67 | self.setProperty("isWrapping", True) | ||
533 | 68 | self.setResizeMode(QtWidgets.QListView.Adjust) | ||
534 | 69 | self.setViewMode(QtWidgets.QListView.IconMode) | ||
535 | 70 | self.setUniformItemSizes(True) | ||
536 | 71 | |||
537 | 72 | def resizeEvent(self, event): | ||
538 | 73 | """ | ||
539 | 74 | Resize the grid so the list looks better when its resized/ | ||
540 | 75 | |||
541 | 76 | :param QtGui.QResizeEvent event: Not used | ||
542 | 77 | :return: None | ||
543 | 78 | """ | ||
544 | 79 | nominal_width = 141 # Icon width of 133 + 4 each side | ||
545 | 80 | max_items_per_row = self.viewport().width() // nominal_width or 1 # or 1 to avoid divide by 0 errors | ||
546 | 81 | col_size = (self.viewport().width() - 1) / max_items_per_row | ||
547 | 82 | self.setGridSize(QtCore.QSize(col_size, 140)) | ||
548 | 83 | |||
549 | 84 | |||
550 | 52 | class UiFirstTimeWizard(object): | 85 | class UiFirstTimeWizard(object): |
551 | 53 | """ | 86 | """ |
552 | 54 | The UI widgets for the first time wizard. | 87 | The UI widgets for the first time wizard. |
553 | @@ -175,27 +208,26 @@ | |||
554 | 175 | self.themes_page = QtWidgets.QWizardPage() | 208 | self.themes_page = QtWidgets.QWizardPage() |
555 | 176 | self.themes_page.setObjectName('themes_page') | 209 | self.themes_page.setObjectName('themes_page') |
556 | 177 | self.themes_layout = QtWidgets.QVBoxLayout(self.themes_page) | 210 | self.themes_layout = QtWidgets.QVBoxLayout(self.themes_page) |
557 | 178 | self.themes_layout.setContentsMargins(20, 50, 20, 60) | ||
558 | 179 | self.themes_layout.setObjectName('themes_layout') | 211 | self.themes_layout.setObjectName('themes_layout') |
568 | 180 | self.themes_list_widget = QtWidgets.QListWidget(self.themes_page) | 212 | self.themes_list_widget = ThemeListWidget(self.themes_page) |
560 | 181 | self.themes_list_widget.setViewMode(QtWidgets.QListView.IconMode) | ||
561 | 182 | self.themes_list_widget.setMovement(QtWidgets.QListView.Static) | ||
562 | 183 | self.themes_list_widget.setFlow(QtWidgets.QListView.LeftToRight) | ||
563 | 184 | self.themes_list_widget.setSpacing(4) | ||
564 | 185 | self.themes_list_widget.setUniformItemSizes(True) | ||
565 | 186 | self.themes_list_widget.setIconSize(QtCore.QSize(133, 100)) | ||
566 | 187 | self.themes_list_widget.setWrapping(False) | ||
567 | 188 | self.themes_list_widget.setObjectName('themes_list_widget') | ||
569 | 189 | self.themes_layout.addWidget(self.themes_list_widget) | 213 | self.themes_layout.addWidget(self.themes_list_widget) |
570 | 214 | self.theme_options_layout = QtWidgets.QHBoxLayout() | ||
571 | 190 | self.default_theme_layout = QtWidgets.QHBoxLayout() | 215 | self.default_theme_layout = QtWidgets.QHBoxLayout() |
572 | 191 | self.theme_label = QtWidgets.QLabel(self.themes_page) | 216 | self.theme_label = QtWidgets.QLabel(self.themes_page) |
573 | 192 | self.default_theme_layout.addWidget(self.theme_label) | 217 | self.default_theme_layout.addWidget(self.theme_label) |
574 | 193 | self.theme_combo_box = QtWidgets.QComboBox(self.themes_page) | 218 | self.theme_combo_box = QtWidgets.QComboBox(self.themes_page) |
575 | 194 | self.theme_combo_box.setEditable(False) | 219 | self.theme_combo_box.setEditable(False) |
580 | 195 | self.theme_combo_box.setInsertPolicy(QtWidgets.QComboBox.NoInsert) | 220 | self.default_theme_layout.addWidget(self.theme_combo_box, stretch=1) |
581 | 196 | self.theme_combo_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents) | 221 | self.theme_options_layout.addLayout(self.default_theme_layout, stretch=1) |
582 | 197 | self.default_theme_layout.addWidget(self.theme_combo_box) | 222 | self.select_buttons_layout = QtWidgets.QHBoxLayout() |
583 | 198 | self.themes_layout.addLayout(self.default_theme_layout) | 223 | self.themes_select_all_button = QtWidgets.QToolButton(self.themes_page) |
584 | 224 | self.themes_select_all_button.setIcon(UiIcons().select_all) | ||
585 | 225 | self.select_buttons_layout.addWidget(self.themes_select_all_button, stretch=1, alignment=QtCore.Qt.AlignRight) | ||
586 | 226 | self.themes_deselect_all_button = QtWidgets.QToolButton(self.themes_page) | ||
587 | 227 | self.themes_deselect_all_button.setIcon(UiIcons().select_none) | ||
588 | 228 | self.select_buttons_layout.addWidget(self.themes_deselect_all_button) | ||
589 | 229 | self.theme_options_layout.addLayout(self.select_buttons_layout, stretch=1) | ||
590 | 230 | self.themes_layout.addLayout(self.theme_options_layout) | ||
591 | 199 | first_time_wizard.setPage(FirstTimePage.Themes, self.themes_page) | 231 | first_time_wizard.setPage(FirstTimePage.Themes, self.themes_page) |
592 | 200 | # Progress page | 232 | # Progress page |
593 | 201 | self.progress_page = QtWidgets.QWizardPage() | 233 | self.progress_page = QtWidgets.QWizardPage() |
594 | @@ -271,9 +303,12 @@ | |||
595 | 271 | self.songs_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download public domain songs.')) | 303 | self.songs_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download public domain songs.')) |
596 | 272 | self.bibles_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Bibles')) | 304 | self.bibles_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Bibles')) |
597 | 273 | self.bibles_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download free Bibles.')) | 305 | self.bibles_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download free Bibles.')) |
598 | 306 | # Themes Page | ||
599 | 274 | self.themes_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Themes')) | 307 | self.themes_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Themes')) |
600 | 275 | self.themes_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download sample themes.')) | 308 | self.themes_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download sample themes.')) |
602 | 276 | self.theme_label.setText(translate('OpenLP.FirstTimeWizard', 'Select default theme:')) | 309 | self.theme_label.setText(translate('OpenLP.FirstTimeWizard', 'Default theme:')) |
603 | 310 | self.themes_select_all_button.setToolTip(translate('OpenLP.FirstTimeWizard', 'Select all')) | ||
604 | 311 | self.themes_deselect_all_button.setToolTip(translate('OpenLP.FirstTimeWizard', 'Deselect all')) | ||
605 | 277 | self.progress_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Downloading and Configuring')) | 312 | self.progress_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Downloading and Configuring')) |
606 | 278 | self.progress_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please wait while resources are downloaded ' | 313 | self.progress_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please wait while resources are downloaded ' |
607 | 279 | 'and OpenLP is configured.')) | 314 | 'and OpenLP is configured.')) |
608 | 280 | 315 | ||
609 | === modified file 'openlp/core/ui/icons.py' | |||
610 | --- openlp/core/ui/icons.py 2019-02-14 15:09:09 +0000 | |||
611 | +++ openlp/core/ui/icons.py 2019-02-16 08:58:10 +0000 | |||
612 | @@ -138,6 +138,8 @@ | |||
613 | 138 | 'search_plus': {'icon': 'fa.search-plus'}, | 138 | 'search_plus': {'icon': 'fa.search-plus'}, |
614 | 139 | 'search_ref': {'icon': 'fa.institution'}, | 139 | 'search_ref': {'icon': 'fa.institution'}, |
615 | 140 | 'search_text': {'icon': 'op.search-text'}, | 140 | 'search_text': {'icon': 'op.search-text'}, |
616 | 141 | 'select_all': {'icon': 'fa.check-square-o'}, | ||
617 | 142 | 'select_none': {'icon': 'fa.square-o'}, | ||
618 | 141 | 'settings': {'icon': 'fa.cogs'}, | 143 | 'settings': {'icon': 'fa.cogs'}, |
619 | 142 | 'shortcuts': {'icon': 'fa.wrench'}, | 144 | 'shortcuts': {'icon': 'fa.wrench'}, |
620 | 143 | 'song_usage': {'icon': 'fa.line-chart'}, | 145 | 'song_usage': {'icon': 'fa.line-chart'}, |
621 | 144 | 146 | ||
622 | === modified file 'tests/functional/openlp_core/ui/test_firsttimeform.py' | |||
623 | --- tests/functional/openlp_core/ui/test_firsttimeform.py 2019-02-14 15:09:09 +0000 | |||
624 | +++ tests/functional/openlp_core/ui/test_firsttimeform.py 2019-02-16 08:58:10 +0000 | |||
625 | @@ -25,40 +25,69 @@ | |||
626 | 25 | import os | 25 | import os |
627 | 26 | import tempfile | 26 | import tempfile |
628 | 27 | from unittest import TestCase | 27 | from unittest import TestCase |
630 | 28 | from unittest.mock import MagicMock, call, patch | 28 | from unittest.mock import MagicMock, call, patch, DEFAULT |
631 | 29 | 29 | ||
632 | 30 | from openlp.core.common.path import Path | 30 | from openlp.core.common.path import Path |
633 | 31 | from openlp.core.common.registry import Registry | 31 | from openlp.core.common.registry import Registry |
635 | 32 | from openlp.core.ui.firsttimeform import FirstTimeForm | 32 | from openlp.core.ui.firsttimeform import FirstTimeForm, ThemeListWidgetItem |
636 | 33 | from tests.helpers.testmixin import TestMixin | 33 | from tests.helpers.testmixin import TestMixin |
637 | 34 | 34 | ||
638 | 35 | 35 | ||
665 | 36 | FAKE_CONFIG = """ | 36 | INVALID_CONFIG = """ |
666 | 37 | [general] | 37 | { |
667 | 38 | base url = http://example.com/frw/ | 38 | "_comments": "The most recent version should be added to https://openlp.org/files/frw/download_3.0.json", |
668 | 39 | [songs] | 39 | "_meta": { |
669 | 40 | directory = songs | 40 | } |
670 | 41 | [bibles] | 41 | """ |
671 | 42 | directory = bibles | 42 | |
672 | 43 | [themes] | 43 | |
673 | 44 | directory = themes | 44 | class TestThemeListWidgetItem(TestCase): |
674 | 45 | """ | 45 | """ |
675 | 46 | 46 | Test the :class:`ThemeListWidgetItem` class | |
676 | 47 | FAKE_BROKEN_CONFIG = """ | 47 | """ |
677 | 48 | [general] | 48 | def setUp(self): |
678 | 49 | base url = http://example.com/frw/ | 49 | self.sample_theme_data = {'file_name': 'BlueBurst.otz', 'sha256': 'sha_256_hash', |
679 | 50 | [songs] | 50 | 'thumbnail': 'BlueBurst.png', 'title': 'Blue Burst'} |
680 | 51 | directory = songs | 51 | download_worker_patcher = patch('openlp.core.ui.firsttimeform.DownloadWorker') |
681 | 52 | [bibles] | 52 | self.addCleanup(download_worker_patcher.stop) |
682 | 53 | directory = bibles | 53 | self.mocked_download_worker = download_worker_patcher.start() |
683 | 54 | """ | 54 | run_thread_patcher = patch('openlp.core.ui.firsttimeform.run_thread') |
684 | 55 | 55 | self.addCleanup(run_thread_patcher.stop) | |
685 | 56 | FAKE_INVALID_CONFIG = """ | 56 | self.mocked_run_thread = run_thread_patcher.start() |
686 | 57 | <html> | 57 | |
687 | 58 | <head><title>This is not a config file</title></head> | 58 | def test_init_sample_data(self): |
688 | 59 | <body>Some text</body> | 59 | """ |
689 | 60 | </html> | 60 | Test that the theme data is loaded correctly in to a ThemeListWidgetItem object when instantiated |
690 | 61 | """ | 61 | """ |
691 | 62 | # GIVEN: A sample theme dictanary object | ||
692 | 63 | # WHEN: Creating an instance of `ThemeListWidgetItem` | ||
693 | 64 | instance = ThemeListWidgetItem('url', self.sample_theme_data, MagicMock()) | ||
694 | 65 | |||
695 | 66 | # THEN: The data should have been set correctly | ||
696 | 67 | assert instance.file_name == 'BlueBurst.otz' | ||
697 | 68 | assert instance.sha256 == 'sha_256_hash' | ||
698 | 69 | assert instance.text() == 'Blue Burst' | ||
699 | 70 | assert instance.toolTip() == 'Blue Burst' | ||
700 | 71 | self.mocked_download_worker.assert_called_once_with('url', 'BlueBurst.png') | ||
701 | 72 | |||
702 | 73 | def test_init_download_worker(self): | ||
703 | 74 | """ | ||
704 | 75 | Test that the `DownloadWorker` worker is set up correctly and that the thread is started. | ||
705 | 76 | """ | ||
706 | 77 | # GIVEN: A sample theme dictanary object | ||
707 | 78 | mocked_ftw = MagicMock(spec=FirstTimeForm) | ||
708 | 79 | mocked_ftw.thumbnail_download_threads = [] | ||
709 | 80 | |||
710 | 81 | # WHEN: Creating an instance of `ThemeListWidgetItem` | ||
711 | 82 | instance = ThemeListWidgetItem('url', self.sample_theme_data, mocked_ftw) | ||
712 | 83 | |||
713 | 84 | # THEN: The `DownloadWorker` should have been set up with the appropriate data | ||
714 | 85 | self.mocked_download_worker.assert_called_once_with('url', 'BlueBurst.png') | ||
715 | 86 | self.mocked_download_worker.download_failed.connect.called_once_with(instance._on_download_failed()) | ||
716 | 87 | self.mocked_download_worker.download_succeeded.connect.called_once_with(instance._on_thumbnail_downloaded) | ||
717 | 88 | self.mocked_run_thread.assert_called_once_with( | ||
718 | 89 | self.mocked_download_worker(), 'thumbnail_download_BlueBurst.png') | ||
719 | 90 | assert mocked_ftw.thumbnail_download_threads == ['thumbnail_download_BlueBurst.png'] | ||
720 | 62 | 91 | ||
721 | 63 | 92 | ||
722 | 64 | class TestFirstTimeForm(TestCase, TestMixin): | 93 | class TestFirstTimeForm(TestCase, TestMixin): |
723 | @@ -92,7 +121,7 @@ | |||
724 | 92 | assert expected_screens == frw.screens, 'The screens should be correct' | 121 | assert expected_screens == frw.screens, 'The screens should be correct' |
725 | 93 | assert frw.web_access is True, 'The default value of self.web_access should be True' | 122 | assert frw.web_access is True, 'The default value of self.web_access should be True' |
726 | 94 | assert frw.was_cancelled is False, 'The default value of self.was_cancelled should be False' | 123 | assert frw.was_cancelled is False, 'The default value of self.was_cancelled should be False' |
728 | 95 | assert [] == frw.theme_screenshot_threads, 'The list of threads should be empty' | 124 | assert [] == frw.thumbnail_download_threads, 'The list of threads should be empty' |
729 | 96 | assert frw.has_run_wizard is False, 'has_run_wizard should be False' | 125 | assert frw.has_run_wizard is False, 'has_run_wizard should be False' |
730 | 97 | 126 | ||
731 | 98 | def test_set_defaults(self): | 127 | def test_set_defaults(self): |
732 | @@ -109,6 +138,7 @@ | |||
733 | 109 | patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \ | 138 | patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \ |
734 | 110 | patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \ | 139 | patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \ |
735 | 111 | patch.object(frw, 'theme_combo_box') as mocked_theme_combo_box, \ | 140 | patch.object(frw, 'theme_combo_box') as mocked_theme_combo_box, \ |
736 | 141 | patch.object(frw, 'songs_check_box') as mocked_songs_check_box, \ | ||
737 | 112 | patch.object(Registry, 'register_function') as mocked_register_function, \ | 142 | patch.object(Registry, 'register_function') as mocked_register_function, \ |
738 | 113 | patch('openlp.core.ui.firsttimeform.Settings', return_value=mocked_settings), \ | 143 | patch('openlp.core.ui.firsttimeform.Settings', return_value=mocked_settings), \ |
739 | 114 | patch('openlp.core.ui.firsttimeform.gettempdir', return_value='temp') as mocked_gettempdir, \ | 144 | patch('openlp.core.ui.firsttimeform.gettempdir', return_value='temp') as mocked_gettempdir, \ |
740 | @@ -122,7 +152,7 @@ | |||
741 | 122 | 152 | ||
742 | 123 | # THEN: The default values should have been set | 153 | # THEN: The default values should have been set |
743 | 124 | mocked_restart.assert_called_once() | 154 | mocked_restart.assert_called_once() |
745 | 125 | assert 'http://openlp.org/files/frw/' == frw.web, 'The default URL should be set' | 155 | assert 'https://get.openlp.org/ftw/' == frw.web, 'The default URL should be set' |
746 | 126 | mocked_cancel_button.clicked.connect.assert_called_once_with(frw.on_cancel_button_clicked) | 156 | mocked_cancel_button.clicked.connect.assert_called_once_with(frw.on_cancel_button_clicked) |
747 | 127 | mocked_no_internet_finish_btn.clicked.connect.assert_called_once_with( | 157 | mocked_no_internet_finish_btn.clicked.connect.assert_called_once_with( |
748 | 128 | frw.on_no_internet_finish_button_clicked) | 158 | frw.on_no_internet_finish_button_clicked) |
749 | @@ -134,6 +164,7 @@ | |||
750 | 134 | mocked_create_paths.assert_called_once_with(Path('temp', 'openlp')) | 164 | mocked_create_paths.assert_called_once_with(Path('temp', 'openlp')) |
751 | 135 | mocked_theme_combo_box.clear.assert_called_once() | 165 | mocked_theme_combo_box.clear.assert_called_once() |
752 | 136 | mocked_theme_manager.assert_not_called() | 166 | mocked_theme_manager.assert_not_called() |
753 | 167 | mocked_songs_check_box.assert_not_called() | ||
754 | 137 | 168 | ||
755 | 138 | def test_set_defaults_rerun(self): | 169 | def test_set_defaults_rerun(self): |
756 | 139 | """ | 170 | """ |
757 | @@ -150,12 +181,17 @@ | |||
758 | 150 | patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \ | 181 | patch.object(frw, 'no_internet_finish_button') as mocked_no_internet_finish_btn, \ |
759 | 151 | patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \ | 182 | patch.object(frw, 'currentIdChanged') as mocked_currentIdChanged, \ |
760 | 152 | patch.object(frw, 'theme_combo_box', **{'findText.return_value': 3}) as mocked_theme_combo_box, \ | 183 | patch.object(frw, 'theme_combo_box', **{'findText.return_value': 3}) as mocked_theme_combo_box, \ |
761 | 184 | patch.multiple(frw, songs_check_box=DEFAULT, bible_check_box=DEFAULT, presentation_check_box=DEFAULT, | ||
762 | 185 | image_check_box=DEFAULT, media_check_box=DEFAULT, custom_check_box=DEFAULT, | ||
763 | 186 | song_usage_check_box=DEFAULT, alert_check_box=DEFAULT), \ | ||
764 | 153 | patch.object(Registry, 'register_function') as mocked_register_function, \ | 187 | patch.object(Registry, 'register_function') as mocked_register_function, \ |
765 | 154 | patch('openlp.core.ui.firsttimeform.Settings', return_value=mocked_settings), \ | 188 | patch('openlp.core.ui.firsttimeform.Settings', return_value=mocked_settings), \ |
766 | 155 | patch('openlp.core.ui.firsttimeform.gettempdir', return_value='temp') as mocked_gettempdir, \ | 189 | patch('openlp.core.ui.firsttimeform.gettempdir', return_value='temp') as mocked_gettempdir, \ |
767 | 156 | patch('openlp.core.ui.firsttimeform.create_paths') as mocked_create_paths, \ | 190 | patch('openlp.core.ui.firsttimeform.create_paths') as mocked_create_paths, \ |
768 | 157 | patch.object(frw.application, 'set_normal_cursor'): | 191 | patch.object(frw.application, 'set_normal_cursor'): |
770 | 158 | mocked_theme_manager = MagicMock(**{'get_themes.return_value': ['a', 'b', 'c']}) | 192 | mocked_plugin_manager = MagicMock() |
771 | 193 | mocked_theme_manager = MagicMock(**{'get_themes.return_value': ['b', 'a', 'c']}) | ||
772 | 194 | Registry().register('plugin_manager', mocked_plugin_manager) | ||
773 | 159 | Registry().register('theme_manager', mocked_theme_manager) | 195 | Registry().register('theme_manager', mocked_theme_manager) |
774 | 160 | 196 | ||
775 | 161 | # WHEN: The set_defaults() method is run | 197 | # WHEN: The set_defaults() method is run |
776 | @@ -163,7 +199,7 @@ | |||
777 | 163 | 199 | ||
778 | 164 | # THEN: The default values should have been set | 200 | # THEN: The default values should have been set |
779 | 165 | mocked_restart.assert_called_once() | 201 | mocked_restart.assert_called_once() |
781 | 166 | assert 'http://openlp.org/files/frw/' == frw.web, 'The default URL should be set' | 202 | assert 'https://get.openlp.org/ftw/' == frw.web, 'The default URL should be set' |
782 | 167 | mocked_cancel_button.clicked.connect.assert_called_once_with(frw.on_cancel_button_clicked) | 203 | mocked_cancel_button.clicked.connect.assert_called_once_with(frw.on_cancel_button_clicked) |
783 | 168 | mocked_no_internet_finish_btn.clicked.connect.assert_called_once_with( | 204 | mocked_no_internet_finish_btn.clicked.connect.assert_called_once_with( |
784 | 169 | frw.on_no_internet_finish_button_clicked) | 205 | frw.on_no_internet_finish_button_clicked) |
785 | @@ -173,9 +209,13 @@ | |||
786 | 173 | mocked_settings.value.assert_has_calls([call('core/has run wizard'), call('themes/global theme')]) | 209 | mocked_settings.value.assert_has_calls([call('core/has run wizard'), call('themes/global theme')]) |
787 | 174 | mocked_gettempdir.assert_called_once() | 210 | mocked_gettempdir.assert_called_once() |
788 | 175 | mocked_create_paths.assert_called_once_with(Path('temp', 'openlp')) | 211 | mocked_create_paths.assert_called_once_with(Path('temp', 'openlp')) |
790 | 176 | mocked_theme_manager.assert_not_called() | 212 | mocked_theme_manager.get_themes.assert_called_once() |
791 | 177 | mocked_theme_combo_box.clear.assert_called_once() | 213 | mocked_theme_combo_box.clear.assert_called_once() |
793 | 178 | mocked_theme_combo_box.addItem.assert_has_calls([call('a'), call('b'), call('c')]) | 214 | mocked_plugin_manager.get_plugin_by_name.assert_has_calls( |
794 | 215 | [call('songs'), call('bibles'), call('presentations'), call('images'), call('media'), call('custom'), | ||
795 | 216 | call('songusage'), call('alerts')], any_order=True) | ||
796 | 217 | mocked_plugin_manager.get_plugin_by_name.assert_has_calls([call().is_active()] * 8, any_order=True) | ||
797 | 218 | mocked_theme_combo_box.addItems.assert_called_once_with(['a', 'b', 'c']) | ||
798 | 179 | mocked_theme_combo_box.findText.assert_called_once_with('Default Theme') | 219 | mocked_theme_combo_box.findText.assert_called_once_with('Default Theme') |
799 | 180 | mocked_theme_combo_box.setCurrentIndex(3) | 220 | mocked_theme_combo_box.setCurrentIndex(3) |
800 | 181 | 221 | ||
801 | @@ -192,7 +232,7 @@ | |||
802 | 192 | mocked_is_thread_finished.side_effect = [False, True] | 232 | mocked_is_thread_finished.side_effect = [False, True] |
803 | 193 | frw = FirstTimeForm(None) | 233 | frw = FirstTimeForm(None) |
804 | 194 | frw.initialize(MagicMock()) | 234 | frw.initialize(MagicMock()) |
806 | 195 | frw.theme_screenshot_threads = ['test_thread'] | 235 | frw.thumbnail_download_threads = ['test_thread'] |
807 | 196 | with patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor: | 236 | with patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor: |
808 | 197 | 237 | ||
809 | 198 | # WHEN: on_cancel_button_clicked() is called | 238 | # WHEN: on_cancel_button_clicked() is called |
810 | @@ -201,43 +241,26 @@ | |||
811 | 201 | # THEN: The right things should be called in the right order | 241 | # THEN: The right things should be called in the right order |
812 | 202 | assert frw.was_cancelled is True, 'The was_cancelled property should have been set to True' | 242 | assert frw.was_cancelled is True, 'The was_cancelled property should have been set to True' |
813 | 203 | mocked_get_thread_worker.assert_called_once_with('test_thread') | 243 | mocked_get_thread_worker.assert_called_once_with('test_thread') |
815 | 204 | mocked_worker.set_download_canceled.assert_called_with(True) | 244 | mocked_worker.cancel_download.assert_called_once() |
816 | 205 | mocked_is_thread_finished.assert_called_with('test_thread') | 245 | mocked_is_thread_finished.assert_called_with('test_thread') |
817 | 206 | assert mocked_is_thread_finished.call_count == 2, 'isRunning() should have been called twice' | 246 | assert mocked_is_thread_finished.call_count == 2, 'isRunning() should have been called twice' |
818 | 207 | mocked_time.sleep.assert_called_once_with(0.1) | 247 | mocked_time.sleep.assert_called_once_with(0.1) |
819 | 208 | mocked_set_normal_cursor.assert_called_once_with() | 248 | mocked_set_normal_cursor.assert_called_once_with() |
820 | 209 | 249 | ||
852 | 210 | def test_broken_config(self): | 250 | @patch('openlp.core.ui.firsttimeform.critical_error_message_box') |
853 | 211 | """ | 251 | def test__parse_config_invalid_config(self, mocked_critical_error_message_box): |
854 | 212 | Test if we can handle an config file with missing data | 252 | """ |
855 | 213 | """ | 253 | Test `FirstTimeForm._parse_config` when called with invalid data |
856 | 214 | # GIVEN: A mocked get_web_page, a First Time Wizard, an expected screen object, and a mocked broken config file | 254 | """ |
857 | 215 | with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page: | 255 | # GIVEN: An instance of `FirstTimeForm` |
858 | 216 | first_time_form = FirstTimeForm(None) | 256 | first_time_form = FirstTimeForm(None) |
859 | 217 | first_time_form.initialize(MagicMock()) | 257 | |
860 | 218 | mocked_get_web_page.return_value = FAKE_BROKEN_CONFIG | 258 | # WHEN: Calling _parse_config with a string containing invalid data |
861 | 219 | 259 | result = first_time_form._parse_config(INVALID_CONFIG) | |
862 | 220 | # WHEN: The First Time Wizard is downloads the config file | 260 | |
863 | 221 | first_time_form._download_index() | 261 | # THEN: _parse_data should return False and the user should have should have been informed. |
864 | 222 | 262 | assert result is False | |
865 | 223 | # THEN: The First Time Form should not have web access | 263 | mocked_critical_error_message_box.assert_called_once() |
835 | 224 | assert first_time_form.web_access is False, 'There should not be web access with a broken config file' | ||
836 | 225 | |||
837 | 226 | def test_invalid_config(self): | ||
838 | 227 | """ | ||
839 | 228 | Test if we can handle an config file in invalid format | ||
840 | 229 | """ | ||
841 | 230 | # GIVEN: A mocked get_web_page, a First Time Wizard, an expected screen object, and a mocked invalid config file | ||
842 | 231 | with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page: | ||
843 | 232 | first_time_form = FirstTimeForm(None) | ||
844 | 233 | first_time_form.initialize(MagicMock()) | ||
845 | 234 | mocked_get_web_page.return_value = FAKE_INVALID_CONFIG | ||
846 | 235 | |||
847 | 236 | # WHEN: The First Time Wizard is downloads the config file | ||
848 | 237 | first_time_form._download_index() | ||
849 | 238 | |||
850 | 239 | # THEN: The First Time Form should not have web access | ||
851 | 240 | assert first_time_form.web_access is False, 'There should not be web access with an invalid config file' | ||
866 | 241 | 264 | ||
867 | 242 | @patch('openlp.core.ui.firsttimeform.get_web_page') | 265 | @patch('openlp.core.ui.firsttimeform.get_web_page') |
868 | 243 | @patch('openlp.core.ui.firsttimeform.QtWidgets.QMessageBox') | 266 | @patch('openlp.core.ui.firsttimeform.QtWidgets.QMessageBox') |
869 | 244 | 267 | ||
870 | === added file 'tests/interfaces/openlp_core/ui/test_firsttimeform.py' | |||
871 | --- tests/interfaces/openlp_core/ui/test_firsttimeform.py 1970-01-01 00:00:00 +0000 | |||
872 | +++ tests/interfaces/openlp_core/ui/test_firsttimeform.py 2019-02-16 08:58:10 +0000 | |||
873 | @@ -0,0 +1,88 @@ | |||
874 | 1 | # -*- coding: utf-8 -*- | ||
875 | 2 | # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 | ||
876 | 3 | |||
877 | 4 | ############################################################################### | ||
878 | 5 | # OpenLP - Open Source Lyrics Projection # | ||
879 | 6 | # --------------------------------------------------------------------------- # | ||
880 | 7 | # Copyright (c) 2008-2019 OpenLP Developers # | ||
881 | 8 | # --------------------------------------------------------------------------- # | ||
882 | 9 | # This program is free software; you can redistribute it and/or modify it # | ||
883 | 10 | # under the terms of the GNU General Public License as published by the Free # | ||
884 | 11 | # Software Foundation; version 2 of the License. # | ||
885 | 12 | # # | ||
886 | 13 | # This program is distributed in the hope that it will be useful, but WITHOUT # | ||
887 | 14 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # | ||
888 | 15 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # | ||
889 | 16 | # more details. # | ||
890 | 17 | # # | ||
891 | 18 | # You should have received a copy of the GNU General Public License along # | ||
892 | 19 | # with this program; if not, write to the Free Software Foundation, Inc., 59 # | ||
893 | 20 | # Temple Place, Suite 330, Boston, MA 02111-1307 USA # | ||
894 | 21 | ############################################################################### | ||
895 | 22 | """ | ||
896 | 23 | Package to test the openlp.core.ui.firsttimeform package. | ||
897 | 24 | """ | ||
898 | 25 | from unittest import TestCase | ||
899 | 26 | from unittest.mock import MagicMock, call, patch | ||
900 | 27 | |||
901 | 28 | from openlp.core.common.path import Path | ||
902 | 29 | from openlp.core.common.registry import Registry | ||
903 | 30 | from openlp.core.ui.firsttimeform import ThemeListWidgetItem | ||
904 | 31 | from openlp.core.ui.icons import UiIcons | ||
905 | 32 | from tests.helpers.testmixin import TestMixin | ||
906 | 33 | |||
907 | 34 | |||
908 | 35 | class TestThemeListWidgetItem(TestCase, TestMixin): | ||
909 | 36 | def setUp(self): | ||
910 | 37 | self.sample_theme_data = {'file_name': 'BlueBurst.otz', 'sha256': 'sha_256_hash', | ||
911 | 38 | 'thumbnail': 'BlueBurst.png', 'title': 'Blue Burst'} | ||
912 | 39 | Registry.create() | ||
913 | 40 | self.registry = Registry() | ||
914 | 41 | mocked_app = MagicMock() | ||
915 | 42 | mocked_app.worker_threads = {} | ||
916 | 43 | Registry().register('application', mocked_app) | ||
917 | 44 | self.setup_application() | ||
918 | 45 | |||
919 | 46 | move_to_thread_patcher = patch('openlp.core.ui.firsttimeform.DownloadWorker.moveToThread') | ||
920 | 47 | self.addCleanup(move_to_thread_patcher.stop) | ||
921 | 48 | move_to_thread_patcher.start() | ||
922 | 49 | set_icon_patcher = patch('openlp.core.ui.firsttimeform.ThemeListWidgetItem.setIcon') | ||
923 | 50 | self.addCleanup(set_icon_patcher.stop) | ||
924 | 51 | self.mocked_set_icon = set_icon_patcher.start() | ||
925 | 52 | q_thread_patcher = patch('openlp.core.ui.firsttimeform.QtCore.QThread') | ||
926 | 53 | self.addCleanup(q_thread_patcher.stop) | ||
927 | 54 | q_thread_patcher.start() | ||
928 | 55 | |||
929 | 56 | def test_failed_download(self): | ||
930 | 57 | """ | ||
931 | 58 | Test that icon get set to indicate a failure when `DownloadWorker` emits the download_failed signal | ||
932 | 59 | """ | ||
933 | 60 | # GIVEN: An instance of `DownloadWorker` | ||
934 | 61 | instance = ThemeListWidgetItem('url', self.sample_theme_data, MagicMock()) # noqa Overcome GC issue | ||
935 | 62 | worker_threads = Registry().get('application').worker_threads | ||
936 | 63 | worker = worker_threads['thumbnail_download_BlueBurst.png']['worker'] | ||
937 | 64 | |||
938 | 65 | # WHEN: `DownloadWorker` emits the `download_failed` signal | ||
939 | 66 | worker.download_failed.emit() | ||
940 | 67 | |||
941 | 68 | # THEN: Then the initial loading icon should have been replaced by the exception icon | ||
942 | 69 | self.mocked_set_icon.assert_has_calls([call(UiIcons().picture), call(UiIcons().exception)]) | ||
943 | 70 | |||
944 | 71 | @patch('openlp.core.ui.firsttimeform.build_icon') | ||
945 | 72 | def test_successful_download(self, mocked_build_icon): | ||
946 | 73 | """ | ||
947 | 74 | Test that the downloaded thumbnail is set as the icon when `DownloadWorker` emits the `download_succeeded` | ||
948 | 75 | signal | ||
949 | 76 | """ | ||
950 | 77 | # GIVEN: An instance of `DownloadWorker` | ||
951 | 78 | instance = ThemeListWidgetItem('url', self.sample_theme_data, MagicMock()) # noqa Overcome GC issue | ||
952 | 79 | worker_threads = Registry().get('application').worker_threads | ||
953 | 80 | worker = worker_threads['thumbnail_download_BlueBurst.png']['worker'] | ||
954 | 81 | test_path = Path('downlaoded', 'file') | ||
955 | 82 | |||
956 | 83 | # WHEN: `DownloadWorker` emits the `download_succeeded` signal | ||
957 | 84 | worker.download_succeeded.emit(test_path) | ||
958 | 85 | |||
959 | 86 | # THEN: An icon should have been built from the downloaded file and used to replace the loading icon | ||
960 | 87 | mocked_build_icon.assert_called_once_with(test_path) | ||
961 | 88 | self.mocked_set_icon.assert_has_calls([call(UiIcons().picture), call(mocked_build_icon())]) |
Linux tests passed!