Merge lp:~raoul-snyman/openlp/bug-1608194 into lp:openlp
- bug-1608194
- Merge into trunk
Proposed by
Raoul Snyman
Status: | Merged |
---|---|
Merged at revision: | 2694 |
Proposed branch: | lp:~raoul-snyman/openlp/bug-1608194 |
Merge into: | lp:openlp |
Diff against target: |
1080 lines (+670/-89) 7 files modified
.bzrignore (+1/-0) openlp/core/ui/media/systemplayer.py (+13/-13) openlp/plugins/songs/lib/__init__.py (+8/-9) openlp/plugins/songs/lib/songselect.py (+76/-41) tests/functional/openlp_core_ui/test_slidecontroller.py (+2/-2) tests/functional/openlp_core_ui_media/test_systemplayer.py (+529/-0) tests/functional/openlp_plugins/songs/test_songselect.py (+41/-24) |
To merge this branch: | bzr merge lp:~raoul-snyman/openlp/bug-1608194 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Bentley | Approve | ||
Review via email: mp+302867@code.launchpad.net |
Commit message
Description of the change
Fix bug #1608194 by updated OpenLP to use the new SongSelect website.
Add this to your merge proposal:
-------
lp:~raoul-snyman/openlp/bug-1608194 (revision 2694)
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
To post a comment you must log in.
Revision history for this message
Tim Bentley (trb143) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.bzrignore' | |||
2 | --- .bzrignore 2016-05-17 08:48:19 +0000 | |||
3 | +++ .bzrignore 2016-08-13 15:05:55 +0000 | |||
4 | @@ -46,3 +46,4 @@ | |||
5 | 46 | coverage | 46 | coverage |
6 | 47 | tags | 47 | tags |
7 | 48 | output | 48 | output |
8 | 49 | htmlcov | ||
9 | 49 | 50 | ||
10 | === modified file 'openlp/core/ui/media/systemplayer.py' | |||
11 | --- openlp/core/ui/media/systemplayer.py 2016-04-13 19:15:53 +0000 | |||
12 | +++ openlp/core/ui/media/systemplayer.py 2016-08-13 15:05:55 +0000 | |||
13 | @@ -83,17 +83,17 @@ | |||
14 | 83 | elif mime_type.startswith('video/'): | 83 | elif mime_type.startswith('video/'): |
15 | 84 | self._add_to_list(self.video_extensions_list, mime_type) | 84 | self._add_to_list(self.video_extensions_list, mime_type) |
16 | 85 | 85 | ||
18 | 86 | def _add_to_list(self, mime_type_list, mimetype): | 86 | def _add_to_list(self, mime_type_list, mime_type): |
19 | 87 | """ | 87 | """ |
20 | 88 | Add mimetypes to the provided list | 88 | Add mimetypes to the provided list |
21 | 89 | """ | 89 | """ |
22 | 90 | # Add all extensions which mimetypes provides us for supported types. | 90 | # Add all extensions which mimetypes provides us for supported types. |
24 | 91 | extensions = mimetypes.guess_all_extensions(str(mimetype)) | 91 | extensions = mimetypes.guess_all_extensions(mime_type) |
25 | 92 | for extension in extensions: | 92 | for extension in extensions: |
26 | 93 | ext = '*%s' % extension | 93 | ext = '*%s' % extension |
27 | 94 | if ext not in mime_type_list: | 94 | if ext not in mime_type_list: |
28 | 95 | mime_type_list.append(ext) | 95 | mime_type_list.append(ext) |
30 | 96 | log.info('MediaPlugin: %s extensions: %s' % (mimetype, ' '.join(extensions))) | 96 | log.info('MediaPlugin: %s extensions: %s', mime_type, ' '.join(extensions)) |
31 | 97 | 97 | ||
32 | 98 | def setup(self, display): | 98 | def setup(self, display): |
33 | 99 | """ | 99 | """ |
34 | @@ -284,25 +284,25 @@ | |||
35 | 284 | :return: True if file can be played otherwise False | 284 | :return: True if file can be played otherwise False |
36 | 285 | """ | 285 | """ |
37 | 286 | thread = QtCore.QThread() | 286 | thread = QtCore.QThread() |
43 | 287 | check_media_player = CheckMedia(path) | 287 | check_media_worker = CheckMediaWorker(path) |
44 | 288 | check_media_player.setVolume(0) | 288 | check_media_worker.setVolume(0) |
45 | 289 | check_media_player.moveToThread(thread) | 289 | check_media_worker.moveToThread(thread) |
46 | 290 | check_media_player.finished.connect(thread.quit) | 290 | check_media_worker.finished.connect(thread.quit) |
47 | 291 | thread.started.connect(check_media_player.play) | 291 | thread.started.connect(check_media_worker.play) |
48 | 292 | thread.start() | 292 | thread.start() |
49 | 293 | while thread.isRunning(): | 293 | while thread.isRunning(): |
50 | 294 | self.application.processEvents() | 294 | self.application.processEvents() |
55 | 295 | return check_media_player.result | 295 | return check_media_worker.result |
56 | 296 | 296 | ||
57 | 297 | 297 | ||
58 | 298 | class CheckMedia(QtMultimedia.QMediaPlayer): | 298 | class CheckMediaWorker(QtMultimedia.QMediaPlayer): |
59 | 299 | """ | 299 | """ |
60 | 300 | Class used to check if a media file is playable | 300 | Class used to check if a media file is playable |
61 | 301 | """ | 301 | """ |
62 | 302 | finished = QtCore.pyqtSignal() | 302 | finished = QtCore.pyqtSignal() |
63 | 303 | 303 | ||
64 | 304 | def __init__(self, path): | 304 | def __init__(self, path): |
66 | 305 | super(CheckMedia, self).__init__(None, QtMultimedia.QMediaPlayer.VideoSurface) | 305 | super(CheckMediaWorker, self).__init__(None, QtMultimedia.QMediaPlayer.VideoSurface) |
67 | 306 | self.result = None | 306 | self.result = None |
68 | 307 | 307 | ||
69 | 308 | self.error.connect(functools.partial(self.signals, 'error')) | 308 | self.error.connect(functools.partial(self.signals, 'error')) |
70 | 309 | 309 | ||
71 | === modified file 'openlp/plugins/songs/lib/__init__.py' | |||
72 | --- openlp/plugins/songs/lib/__init__.py 2016-05-27 08:13:14 +0000 | |||
73 | +++ openlp/plugins/songs/lib/__init__.py 2016-08-13 15:05:55 +0000 | |||
74 | @@ -31,9 +31,8 @@ | |||
75 | 31 | 31 | ||
76 | 32 | from openlp.core.common import AppLocation, CONTROL_CHARS | 32 | from openlp.core.common import AppLocation, CONTROL_CHARS |
77 | 33 | from openlp.core.lib import translate | 33 | from openlp.core.lib import translate |
81 | 34 | from openlp.plugins.songs.lib.db import MediaFile, Song | 34 | from openlp.plugins.songs.lib.db import Author, MediaFile, Song, Topic |
82 | 35 | from .db import Author | 35 | from openlp.plugins.songs.lib.ui import SongStrings |
80 | 36 | from .ui import SongStrings | ||
83 | 37 | 36 | ||
84 | 38 | log = logging.getLogger(__name__) | 37 | log = logging.getLogger(__name__) |
85 | 39 | 38 | ||
86 | @@ -315,8 +314,8 @@ | |||
87 | 315 | ] | 314 | ] |
88 | 316 | recommended_index = -1 | 315 | recommended_index = -1 |
89 | 317 | if recommendation: | 316 | if recommendation: |
92 | 318 | for index in range(len(encodings)): | 317 | for index, encoding in enumerate(encodings): |
93 | 319 | if recommendation == encodings[index][0]: | 318 | if recommendation == encoding[0]: |
94 | 320 | recommended_index = index | 319 | recommended_index = index |
95 | 321 | break | 320 | break |
96 | 322 | if recommended_index > -1: | 321 | if recommended_index > -1: |
97 | @@ -442,7 +441,7 @@ | |||
98 | 442 | # Encoded buffer. | 441 | # Encoded buffer. |
99 | 443 | ebytes = bytearray() | 442 | ebytes = bytearray() |
100 | 444 | for match in PATTERN.finditer(text): | 443 | for match in PATTERN.finditer(text): |
102 | 445 | iinu, word, arg, hex, char, brace, tchar = match.groups() | 444 | iinu, word, arg, hex_, char, brace, tchar = match.groups() |
103 | 446 | # \x (non-alpha character) | 445 | # \x (non-alpha character) |
104 | 447 | if char: | 446 | if char: |
105 | 448 | if char in '\\{}': | 447 | if char in '\\{}': |
106 | @@ -450,7 +449,7 @@ | |||
107 | 450 | else: | 449 | else: |
108 | 451 | word = char | 450 | word = char |
109 | 452 | # Flush encoded buffer to output buffer | 451 | # Flush encoded buffer to output buffer |
111 | 453 | if ebytes and not hex and not tchar: | 452 | if ebytes and not hex_ and not tchar: |
112 | 454 | failed = False | 453 | failed = False |
113 | 455 | while True: | 454 | while True: |
114 | 456 | try: | 455 | try: |
115 | @@ -507,11 +506,11 @@ | |||
116 | 507 | elif iinu: | 506 | elif iinu: |
117 | 508 | ignorable = True | 507 | ignorable = True |
118 | 509 | # \'xx | 508 | # \'xx |
120 | 510 | elif hex: | 509 | elif hex_: |
121 | 511 | if curskip > 0: | 510 | if curskip > 0: |
122 | 512 | curskip -= 1 | 511 | curskip -= 1 |
123 | 513 | elif not ignorable: | 512 | elif not ignorable: |
125 | 514 | ebytes.append(int(hex, 16)) | 513 | ebytes.append(int(hex_, 16)) |
126 | 515 | elif tchar: | 514 | elif tchar: |
127 | 516 | if curskip > 0: | 515 | if curskip > 0: |
128 | 517 | curskip -= 1 | 516 | curskip -= 1 |
129 | 518 | 517 | ||
130 | === modified file 'openlp/plugins/songs/lib/songselect.py' | |||
131 | --- openlp/plugins/songs/lib/songselect.py 2016-05-27 08:13:14 +0000 | |||
132 | +++ openlp/plugins/songs/lib/songselect.py 2016-08-13 15:05:55 +0000 | |||
133 | @@ -23,7 +23,8 @@ | |||
134 | 23 | The :mod:`~openlp.plugins.songs.lib.songselect` module contains the SongSelect importer itself. | 23 | The :mod:`~openlp.plugins.songs.lib.songselect` module contains the SongSelect importer itself. |
135 | 24 | """ | 24 | """ |
136 | 25 | import logging | 25 | import logging |
138 | 26 | import sys | 26 | import random |
139 | 27 | import re | ||
140 | 27 | from http.cookiejar import CookieJar | 28 | from http.cookiejar import CookieJar |
141 | 28 | from urllib.parse import urlencode | 29 | from urllib.parse import urlencode |
142 | 29 | from urllib.request import HTTPCookieProcessor, URLError, build_opener | 30 | from urllib.request import HTTPCookieProcessor, URLError, build_opener |
143 | @@ -32,14 +33,21 @@ | |||
144 | 32 | 33 | ||
145 | 33 | from bs4 import BeautifulSoup, NavigableString | 34 | from bs4 import BeautifulSoup, NavigableString |
146 | 34 | 35 | ||
148 | 35 | from openlp.plugins.songs.lib import Song, VerseType, clean_song, Author | 36 | from openlp.plugins.songs.lib import Song, Author, Topic, VerseType, clean_song |
149 | 36 | from openlp.plugins.songs.lib.openlyricsxml import SongXML | 37 | from openlp.plugins.songs.lib.openlyricsxml import SongXML |
150 | 37 | 38 | ||
156 | 38 | USER_AGENT = 'Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; GT-I9000 ' \ | 39 | USER_AGENTS = [ |
157 | 39 | 'Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 ' \ | 40 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) ' |
158 | 40 | 'Mobile Safari/534.30' | 41 | 'Chrome/52.0.2743.116 Safari/537.36', |
159 | 41 | BASE_URL = 'https://mobile.songselect.com' | 42 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36', |
160 | 42 | LOGIN_URL = BASE_URL + '/account/login' | 43 | 'Mozilla/5.0 (X11; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0', |
161 | 44 | 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0', | ||
162 | 45 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:47.0) Gecko/20100101 Firefox/47.0' | ||
163 | 46 | ] | ||
164 | 47 | BASE_URL = 'https://songselect.ccli.com' | ||
165 | 48 | LOGIN_PAGE = 'https://profile.ccli.com/account/signin?appContext=SongSelect&returnUrl='\ | ||
166 | 49 | 'https%3a%2f%2fsongselect.ccli.com%2f' | ||
167 | 50 | LOGIN_URL = 'https://profile.ccli.com/' | ||
168 | 43 | LOGOUT_URL = BASE_URL + '/account/logout' | 51 | LOGOUT_URL = BASE_URL + '/account/logout' |
169 | 44 | SEARCH_URL = BASE_URL + '/search/results' | 52 | SEARCH_URL = BASE_URL + '/search/results' |
170 | 45 | 53 | ||
171 | @@ -60,7 +68,7 @@ | |||
172 | 60 | self.db_manager = db_manager | 68 | self.db_manager = db_manager |
173 | 61 | self.html_parser = HTMLParser() | 69 | self.html_parser = HTMLParser() |
174 | 62 | self.opener = build_opener(HTTPCookieProcessor(CookieJar())) | 70 | self.opener = build_opener(HTTPCookieProcessor(CookieJar())) |
176 | 63 | self.opener.addheaders = [('User-Agent', USER_AGENT)] | 71 | self.opener.addheaders = [('User-Agent', random.choice(USER_AGENTS))] |
177 | 64 | self.run_search = True | 72 | self.run_search = True |
178 | 65 | 73 | ||
179 | 66 | def login(self, username, password, callback=None): | 74 | def login(self, username, password, callback=None): |
180 | @@ -76,27 +84,27 @@ | |||
181 | 76 | if callback: | 84 | if callback: |
182 | 77 | callback() | 85 | callback() |
183 | 78 | try: | 86 | try: |
187 | 79 | login_page = BeautifulSoup(self.opener.open(LOGIN_URL).read(), 'lxml') | 87 | login_page = BeautifulSoup(self.opener.open(LOGIN_PAGE).read(), 'lxml') |
188 | 80 | except (TypeError, URLError) as e: | 88 | except (TypeError, URLError) as error: |
189 | 81 | log.exception('Could not login to SongSelect, {error}'.format(error=e)) | 89 | log.exception('Could not login to SongSelect, {error}'.format(error=error)) |
190 | 82 | return False | 90 | return False |
191 | 83 | if callback: | 91 | if callback: |
192 | 84 | callback() | 92 | callback() |
193 | 85 | token_input = login_page.find('input', attrs={'name': '__RequestVerificationToken'}) | 93 | token_input = login_page.find('input', attrs={'name': '__RequestVerificationToken'}) |
194 | 86 | data = urlencode({ | 94 | data = urlencode({ |
195 | 87 | '__RequestVerificationToken': token_input['value'], | 95 | '__RequestVerificationToken': token_input['value'], |
198 | 88 | 'UserName': username, | 96 | 'emailAddress': username, |
199 | 89 | 'Password': password, | 97 | 'password': password, |
200 | 90 | 'RememberMe': 'false' | 98 | 'RememberMe': 'false' |
201 | 91 | }) | 99 | }) |
202 | 92 | try: | 100 | try: |
203 | 93 | posted_page = BeautifulSoup(self.opener.open(LOGIN_URL, data.encode('utf-8')).read(), 'lxml') | 101 | posted_page = BeautifulSoup(self.opener.open(LOGIN_URL, data.encode('utf-8')).read(), 'lxml') |
206 | 94 | except (TypeError, URLError) as e: | 102 | except (TypeError, URLError) as error: |
207 | 95 | log.exception('Could not login to SongSelect, {error}'.format(error=e)) | 103 | log.exception('Could not login to SongSelect, {error}'.format(error=error)) |
208 | 96 | return False | 104 | return False |
209 | 97 | if callback: | 105 | if callback: |
210 | 98 | callback() | 106 | callback() |
212 | 99 | return not posted_page.find('input', attrs={'name': '__RequestVerificationToken'}) | 107 | return posted_page.find('input', id='SearchText') is not None |
213 | 100 | 108 | ||
214 | 101 | def logout(self): | 109 | def logout(self): |
215 | 102 | """ | 110 | """ |
216 | @@ -104,8 +112,8 @@ | |||
217 | 104 | """ | 112 | """ |
218 | 105 | try: | 113 | try: |
219 | 106 | self.opener.open(LOGOUT_URL) | 114 | self.opener.open(LOGOUT_URL) |
222 | 107 | except (TypeError, URLError) as e: | 115 | except (TypeError, URLError) as error: |
223 | 108 | log.exception('Could not log of SongSelect, {error}'.format(error=e)) | 116 | log.exception('Could not log of SongSelect, {error}'.format(error=error)) |
224 | 109 | 117 | ||
225 | 110 | def search(self, search_text, max_results, callback=None): | 118 | def search(self, search_text, max_results, callback=None): |
226 | 111 | """ | 119 | """ |
227 | @@ -117,7 +125,15 @@ | |||
228 | 117 | :return: List of songs | 125 | :return: List of songs |
229 | 118 | """ | 126 | """ |
230 | 119 | self.run_search = True | 127 | self.run_search = True |
232 | 120 | params = {'allowredirect': 'false', 'SearchTerm': search_text} | 128 | params = { |
233 | 129 | 'SongContent': '', | ||
234 | 130 | 'PrimaryLanguage': '', | ||
235 | 131 | 'Keys': '', | ||
236 | 132 | 'Themes': '', | ||
237 | 133 | 'List': '', | ||
238 | 134 | 'Sort': '', | ||
239 | 135 | 'SearchText': search_text | ||
240 | 136 | } | ||
241 | 121 | current_page = 1 | 137 | current_page = 1 |
242 | 122 | songs = [] | 138 | songs = [] |
243 | 123 | while self.run_search: | 139 | while self.run_search: |
244 | @@ -125,17 +141,17 @@ | |||
245 | 125 | params['page'] = current_page | 141 | params['page'] = current_page |
246 | 126 | try: | 142 | try: |
247 | 127 | results_page = BeautifulSoup(self.opener.open(SEARCH_URL + '?' + urlencode(params)).read(), 'lxml') | 143 | results_page = BeautifulSoup(self.opener.open(SEARCH_URL + '?' + urlencode(params)).read(), 'lxml') |
251 | 128 | search_results = results_page.find_all('li', 'result pane') | 144 | search_results = results_page.find_all('div', 'song-result') |
252 | 129 | except (TypeError, URLError) as e: | 145 | except (TypeError, URLError) as error: |
253 | 130 | log.exception('Could not search SongSelect, {error}'.format(error=e)) | 146 | log.exception('Could not search SongSelect, {error}'.format(error=error)) |
254 | 131 | search_results = None | 147 | search_results = None |
255 | 132 | if not search_results: | 148 | if not search_results: |
256 | 133 | break | 149 | break |
257 | 134 | for result in search_results: | 150 | for result in search_results: |
258 | 135 | song = { | 151 | song = { |
262 | 136 | 'title': unescape(result.find('h3').string), | 152 | 'title': unescape(result.find('p', 'song-result-title').find('a').string).strip(), |
263 | 137 | 'authors': [unescape(author.string) for author in result.find_all('li')], | 153 | 'authors': unescape(result.find('p', 'song-result-subtitle').string).strip().split(', '), |
264 | 138 | 'link': BASE_URL + result.find('a')['href'] | 154 | 'link': BASE_URL + result.find('p', 'song-result-title').find('a')['href'] |
265 | 139 | } | 155 | } |
266 | 140 | if callback: | 156 | if callback: |
267 | 141 | callback(song) | 157 | callback(song) |
268 | @@ -157,33 +173,43 @@ | |||
269 | 157 | callback() | 173 | callback() |
270 | 158 | try: | 174 | try: |
271 | 159 | song_page = BeautifulSoup(self.opener.open(song['link']).read(), 'lxml') | 175 | song_page = BeautifulSoup(self.opener.open(song['link']).read(), 'lxml') |
274 | 160 | except (TypeError, URLError) as e: | 176 | except (TypeError, URLError) as error: |
275 | 161 | log.exception('Could not get song from SongSelect, {error}'.format(error=e)) | 177 | log.exception('Could not get song from SongSelect, {error}'.format(error=error)) |
276 | 162 | return None | 178 | return None |
277 | 163 | if callback: | 179 | if callback: |
278 | 164 | callback() | 180 | callback() |
279 | 165 | try: | 181 | try: |
281 | 166 | lyrics_page = BeautifulSoup(self.opener.open(song['link'] + '/lyrics').read(), 'lxml') | 182 | lyrics_page = BeautifulSoup(self.opener.open(song['link'] + '/viewlyrics').read(), 'lxml') |
282 | 167 | except (TypeError, URLError): | 183 | except (TypeError, URLError): |
283 | 168 | log.exception('Could not get lyrics from SongSelect') | 184 | log.exception('Could not get lyrics from SongSelect') |
284 | 169 | return None | 185 | return None |
285 | 170 | if callback: | 186 | if callback: |
286 | 171 | callback() | 187 | callback() |
290 | 172 | song['copyright'] = '/'.join([li.string for li in song_page.find('ul', 'copyright').find_all('li')]) | 188 | copyright_elements = [] |
291 | 173 | song['copyright'] = unescape(song['copyright']) | 189 | theme_elements = [] |
292 | 174 | song['ccli_number'] = song_page.find('ul', 'info').find('li').string.split(':')[1].strip() | 190 | copyrights_regex = re.compile(r'\bCopyrights\b') |
293 | 191 | themes_regex = re.compile(r'\bThemes\b') | ||
294 | 192 | for ul in song_page.find_all('ul', 'song-meta-list'): | ||
295 | 193 | if ul.find('li', string=copyrights_regex): | ||
296 | 194 | copyright_elements.extend(ul.find_all('li')[1:]) | ||
297 | 195 | if ul.find('li', string=themes_regex): | ||
298 | 196 | theme_elements.extend(ul.find_all('li')[1:]) | ||
299 | 197 | song['copyright'] = '/'.join([unescape(li.string).strip() for li in copyright_elements]) | ||
300 | 198 | song['topics'] = [unescape(li.string).strip() for li in theme_elements] | ||
301 | 199 | song['ccli_number'] = song_page.find('div', 'song-content-data').find('ul').find('li')\ | ||
302 | 200 | .find('strong').string.strip() | ||
303 | 175 | song['verses'] = [] | 201 | song['verses'] = [] |
309 | 176 | verses = lyrics_page.find('section', 'lyrics').find_all('p') | 202 | verses = lyrics_page.find('div', 'song-viewer lyrics').find_all('p') |
310 | 177 | verse_labels = lyrics_page.find('section', 'lyrics').find_all('h3') | 203 | verse_labels = lyrics_page.find('div', 'song-viewer lyrics').find_all('h3') |
311 | 178 | for counter in range(len(verses)): | 204 | for verse, label in zip(verses, verse_labels): |
312 | 179 | verse = {'label': verse_labels[counter].string, 'lyrics': ''} | 205 | song_verse = {'label': unescape(label.string).strip(), 'lyrics': ''} |
313 | 180 | for v in verses[counter].contents: | 206 | for v in verse.contents: |
314 | 181 | if isinstance(v, NavigableString): | 207 | if isinstance(v, NavigableString): |
316 | 182 | verse['lyrics'] = verse['lyrics'] + v.string | 208 | song_verse['lyrics'] += unescape(v.string).strip() |
317 | 183 | else: | 209 | else: |
321 | 184 | verse['lyrics'] += '\n' | 210 | song_verse['lyrics'] += '\n' |
322 | 185 | verse['lyrics'] = verse['lyrics'].strip(' \n\r\t') | 211 | song_verse['lyrics'] = song_verse['lyrics'].strip(' \n\r\t') |
323 | 186 | song['verses'].append(unescape(verse)) | 212 | song['verses'].append(song_verse) |
324 | 187 | for counter, author in enumerate(song['authors']): | 213 | for counter, author in enumerate(song['authors']): |
325 | 188 | song['authors'][counter] = unescape(author) | 214 | song['authors'][counter] = unescape(author) |
326 | 189 | return song | 215 | return song |
327 | @@ -199,7 +225,11 @@ | |||
328 | 199 | song_xml = SongXML() | 225 | song_xml = SongXML() |
329 | 200 | verse_order = [] | 226 | verse_order = [] |
330 | 201 | for verse in song['verses']: | 227 | for verse in song['verses']: |
332 | 202 | verse_type, verse_number = verse['label'].split(' ')[:2] | 228 | if ' ' in verse['label']: |
333 | 229 | verse_type, verse_number = verse['label'].split(' ', 1) | ||
334 | 230 | else: | ||
335 | 231 | verse_type = verse['label'] | ||
336 | 232 | verse_number = 1 | ||
337 | 203 | verse_type = VerseType.from_loose_input(verse_type) | 233 | verse_type = VerseType.from_loose_input(verse_type) |
338 | 204 | verse_number = int(verse_number) | 234 | verse_number = int(verse_number) |
339 | 205 | song_xml.add_verse_to_lyrics(VerseType.tags[verse_type], verse_number, verse['lyrics']) | 235 | song_xml.add_verse_to_lyrics(VerseType.tags[verse_type], verse_number, verse['lyrics']) |
340 | @@ -220,6 +250,11 @@ | |||
341 | 220 | last_name = name_parts[1] | 250 | last_name = name_parts[1] |
342 | 221 | author = Author.populate(first_name=first_name, last_name=last_name, display_name=author_name) | 251 | author = Author.populate(first_name=first_name, last_name=last_name, display_name=author_name) |
343 | 222 | db_song.add_author(author) | 252 | db_song.add_author(author) |
344 | 253 | for topic_name in song.get('topics', []): | ||
345 | 254 | topic = self.db_manager.get_object_filtered(Topic, Topic.name == topic_name) | ||
346 | 255 | if not topic: | ||
347 | 256 | topic = Topic.populate(name=topic_name) | ||
348 | 257 | db_song.topics.append(topic) | ||
349 | 223 | self.db_manager.save_object(db_song) | 258 | self.db_manager.save_object(db_song) |
350 | 224 | return db_song | 259 | return db_song |
351 | 225 | 260 | ||
352 | 226 | 261 | ||
353 | === modified file 'tests/functional/openlp_core_ui/test_slidecontroller.py' | |||
354 | --- tests/functional/openlp_core_ui/test_slidecontroller.py 2016-07-16 16:51:08 +0000 | |||
355 | +++ tests/functional/openlp_core_ui/test_slidecontroller.py 2016-08-13 15:05:55 +0000 | |||
356 | @@ -243,7 +243,7 @@ | |||
357 | 243 | mocked_service_item = MagicMock() | 243 | mocked_service_item = MagicMock() |
358 | 244 | mocked_service_item.from_service = False | 244 | mocked_service_item.from_service = False |
359 | 245 | mocked_preview_widget.current_slide_number.return_value = 1 | 245 | mocked_preview_widget.current_slide_number.return_value = 1 |
361 | 246 | mocked_preview_widget.slide_count.return_value = 2 | 246 | mocked_preview_widget.slide_count = MagicMock(return_value=2) |
362 | 247 | mocked_live_controller.preview_widget = MagicMock() | 247 | mocked_live_controller.preview_widget = MagicMock() |
363 | 248 | Registry.create() | 248 | Registry.create() |
364 | 249 | Registry().register('live_controller', mocked_live_controller) | 249 | Registry().register('live_controller', mocked_live_controller) |
365 | @@ -273,7 +273,7 @@ | |||
366 | 273 | mocked_service_item.from_service = True | 273 | mocked_service_item.from_service = True |
367 | 274 | mocked_service_item.unique_identifier = 42 | 274 | mocked_service_item.unique_identifier = 42 |
368 | 275 | mocked_preview_widget.current_slide_number.return_value = 1 | 275 | mocked_preview_widget.current_slide_number.return_value = 1 |
370 | 276 | mocked_preview_widget.slide_count.return_value = 2 | 276 | mocked_preview_widget.slide_count = MagicMock(return_value=2) |
371 | 277 | mocked_live_controller.preview_widget = MagicMock() | 277 | mocked_live_controller.preview_widget = MagicMock() |
372 | 278 | Registry.create() | 278 | Registry.create() |
373 | 279 | Registry().register('live_controller', mocked_live_controller) | 279 | Registry().register('live_controller', mocked_live_controller) |
374 | 280 | 280 | ||
375 | === added file 'tests/functional/openlp_core_ui_media/test_systemplayer.py' | |||
376 | --- tests/functional/openlp_core_ui_media/test_systemplayer.py 1970-01-01 00:00:00 +0000 | |||
377 | +++ tests/functional/openlp_core_ui_media/test_systemplayer.py 2016-08-13 15:05:55 +0000 | |||
378 | @@ -0,0 +1,529 @@ | |||
379 | 1 | # -*- coding: utf-8 -*- | ||
380 | 2 | # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 | ||
381 | 3 | |||
382 | 4 | ############################################################################### | ||
383 | 5 | # OpenLP - Open Source Lyrics Projection # | ||
384 | 6 | # --------------------------------------------------------------------------- # | ||
385 | 7 | # Copyright (c) 2008-2016 OpenLP Developers # | ||
386 | 8 | # --------------------------------------------------------------------------- # | ||
387 | 9 | # This program is free software; you can redistribute it and/or modify it # | ||
388 | 10 | # under the terms of the GNU General Public License as published by the Free # | ||
389 | 11 | # Software Foundation; version 2 of the License. # | ||
390 | 12 | # # | ||
391 | 13 | # This program is distributed in the hope that it will be useful, but WITHOUT # | ||
392 | 14 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # | ||
393 | 15 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # | ||
394 | 16 | # more details. # | ||
395 | 17 | # # | ||
396 | 18 | # You should have received a copy of the GNU General Public License along # | ||
397 | 19 | # with this program; if not, write to the Free Software Foundation, Inc., 59 # | ||
398 | 20 | # Temple Place, Suite 330, Boston, MA 02111-1307 USA # | ||
399 | 21 | ############################################################################### | ||
400 | 22 | """ | ||
401 | 23 | Package to test the openlp.core.ui.media.systemplayer package. | ||
402 | 24 | """ | ||
403 | 25 | from unittest import TestCase | ||
404 | 26 | |||
405 | 27 | from PyQt5 import QtCore, QtMultimedia | ||
406 | 28 | |||
407 | 29 | from openlp.core.common import Registry | ||
408 | 30 | from openlp.core.ui.media import MediaState | ||
409 | 31 | from openlp.core.ui.media.systemplayer import SystemPlayer, CheckMediaWorker, ADDITIONAL_EXT | ||
410 | 32 | |||
411 | 33 | from tests.functional import MagicMock, call, patch | ||
412 | 34 | |||
413 | 35 | |||
414 | 36 | class TestSystemPlayer(TestCase): | ||
415 | 37 | """ | ||
416 | 38 | Test the system media player | ||
417 | 39 | """ | ||
418 | 40 | @patch('openlp.core.ui.media.systemplayer.mimetypes') | ||
419 | 41 | @patch('openlp.core.ui.media.systemplayer.QtMultimedia.QMediaPlayer') | ||
420 | 42 | def test_constructor(self, MockQMediaPlayer, mocked_mimetypes): | ||
421 | 43 | """ | ||
422 | 44 | Test the SystemPlayer constructor | ||
423 | 45 | """ | ||
424 | 46 | # GIVEN: The SystemPlayer class and a mockedQMediaPlayer | ||
425 | 47 | mocked_media_player = MagicMock() | ||
426 | 48 | mocked_media_player.supportedMimeTypes.return_value = [ | ||
427 | 49 | 'application/postscript', | ||
428 | 50 | 'audio/aiff', | ||
429 | 51 | 'audio/x-aiff', | ||
430 | 52 | 'text/html', | ||
431 | 53 | 'video/animaflex', | ||
432 | 54 | 'video/x-ms-asf' | ||
433 | 55 | ] | ||
434 | 56 | mocked_mimetypes.guess_all_extensions.side_effect = [ | ||
435 | 57 | ['.aiff'], | ||
436 | 58 | ['.aiff'], | ||
437 | 59 | ['.afl'], | ||
438 | 60 | ['.asf'] | ||
439 | 61 | ] | ||
440 | 62 | MockQMediaPlayer.return_value = mocked_media_player | ||
441 | 63 | |||
442 | 64 | # WHEN: An object is created from it | ||
443 | 65 | player = SystemPlayer(self) | ||
444 | 66 | |||
445 | 67 | # THEN: The correct initial values should be set up | ||
446 | 68 | self.assertEqual('system', player.name) | ||
447 | 69 | self.assertEqual('System', player.original_name) | ||
448 | 70 | self.assertEqual('&System', player.display_name) | ||
449 | 71 | self.assertEqual(self, player.parent) | ||
450 | 72 | self.assertEqual(ADDITIONAL_EXT, player.additional_extensions) | ||
451 | 73 | MockQMediaPlayer.assert_called_once_with(None, QtMultimedia.QMediaPlayer.VideoSurface) | ||
452 | 74 | mocked_mimetypes.init.assert_called_once_with() | ||
453 | 75 | mocked_media_player.service.assert_called_once_with() | ||
454 | 76 | mocked_media_player.supportedMimeTypes.assert_called_once_with() | ||
455 | 77 | self.assertEqual(['*.aiff'], player.audio_extensions_list) | ||
456 | 78 | self.assertEqual(['*.afl', '*.asf'], player.video_extensions_list) | ||
457 | 79 | |||
458 | 80 | @patch('openlp.core.ui.media.systemplayer.QtMultimediaWidgets.QVideoWidget') | ||
459 | 81 | @patch('openlp.core.ui.media.systemplayer.QtMultimedia.QMediaPlayer') | ||
460 | 82 | def test_setup(self, MockQMediaPlayer, MockQVideoWidget): | ||
461 | 83 | """ | ||
462 | 84 | Test the setup() method of SystemPlayer | ||
463 | 85 | """ | ||
464 | 86 | # GIVEN: A SystemPlayer instance and a mock display | ||
465 | 87 | player = SystemPlayer(self) | ||
466 | 88 | mocked_display = MagicMock() | ||
467 | 89 | mocked_display.size.return_value = [1, 2, 3, 4] | ||
468 | 90 | mocked_video_widget = MagicMock() | ||
469 | 91 | mocked_media_player = MagicMock() | ||
470 | 92 | MockQVideoWidget.return_value = mocked_video_widget | ||
471 | 93 | MockQMediaPlayer.return_value = mocked_media_player | ||
472 | 94 | |||
473 | 95 | # WHEN: setup() is run | ||
474 | 96 | player.setup(mocked_display) | ||
475 | 97 | |||
476 | 98 | # THEN: The player should have a display widget | ||
477 | 99 | MockQVideoWidget.assert_called_once_with(mocked_display) | ||
478 | 100 | self.assertEqual(mocked_video_widget, mocked_display.video_widget) | ||
479 | 101 | mocked_display.size.assert_called_once_with() | ||
480 | 102 | mocked_video_widget.resize.assert_called_once_with([1, 2, 3, 4]) | ||
481 | 103 | MockQMediaPlayer.assert_called_with(mocked_display) | ||
482 | 104 | self.assertEqual(mocked_media_player, mocked_display.media_player) | ||
483 | 105 | mocked_media_player.setVideoOutput.assert_called_once_with(mocked_video_widget) | ||
484 | 106 | mocked_video_widget.raise_.assert_called_once_with() | ||
485 | 107 | mocked_video_widget.hide.assert_called_once_with() | ||
486 | 108 | self.assertTrue(player.has_own_widget) | ||
487 | 109 | |||
488 | 110 | def test_check_available(self): | ||
489 | 111 | """ | ||
490 | 112 | Test the check_available() method on SystemPlayer | ||
491 | 113 | """ | ||
492 | 114 | # GIVEN: A SystemPlayer instance | ||
493 | 115 | player = SystemPlayer(self) | ||
494 | 116 | |||
495 | 117 | # WHEN: check_available is run | ||
496 | 118 | result = player.check_available() | ||
497 | 119 | |||
498 | 120 | # THEN: it should be available | ||
499 | 121 | self.assertTrue(result) | ||
500 | 122 | |||
501 | 123 | def test_load_valid_media(self): | ||
502 | 124 | """ | ||
503 | 125 | Test the load() method of SystemPlayer with a valid media file | ||
504 | 126 | """ | ||
505 | 127 | # GIVEN: A SystemPlayer instance and a mocked display | ||
506 | 128 | player = SystemPlayer(self) | ||
507 | 129 | mocked_display = MagicMock() | ||
508 | 130 | mocked_display.controller.media_info.volume = 1 | ||
509 | 131 | mocked_display.controller.media_info.file_info.absoluteFilePath.return_value = '/path/to/file' | ||
510 | 132 | |||
511 | 133 | # WHEN: The load() method is run | ||
512 | 134 | with patch.object(player, 'check_media') as mocked_check_media, \ | ||
513 | 135 | patch.object(player, 'volume') as mocked_volume: | ||
514 | 136 | mocked_check_media.return_value = True | ||
515 | 137 | result = player.load(mocked_display) | ||
516 | 138 | |||
517 | 139 | # THEN: the file is sent to the video widget | ||
518 | 140 | mocked_display.controller.media_info.file_info.absoluteFilePath.assert_called_once_with() | ||
519 | 141 | mocked_check_media.assert_called_once_with('/path/to/file') | ||
520 | 142 | mocked_display.media_player.setMedia.assert_called_once_with( | ||
521 | 143 | QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile('/path/to/file'))) | ||
522 | 144 | mocked_volume.assert_called_once_with(mocked_display, 1) | ||
523 | 145 | self.assertTrue(result) | ||
524 | 146 | |||
525 | 147 | def test_load_invalid_media(self): | ||
526 | 148 | """ | ||
527 | 149 | Test the load() method of SystemPlayer with an invalid media file | ||
528 | 150 | """ | ||
529 | 151 | # GIVEN: A SystemPlayer instance and a mocked display | ||
530 | 152 | player = SystemPlayer(self) | ||
531 | 153 | mocked_display = MagicMock() | ||
532 | 154 | mocked_display.controller.media_info.volume = 1 | ||
533 | 155 | mocked_display.controller.media_info.file_info.absoluteFilePath.return_value = '/path/to/file' | ||
534 | 156 | |||
535 | 157 | # WHEN: The load() method is run | ||
536 | 158 | with patch.object(player, 'check_media') as mocked_check_media, \ | ||
537 | 159 | patch.object(player, 'volume') as mocked_volume: | ||
538 | 160 | mocked_check_media.return_value = False | ||
539 | 161 | result = player.load(mocked_display) | ||
540 | 162 | |||
541 | 163 | # THEN: stuff | ||
542 | 164 | mocked_display.controller.media_info.file_info.absoluteFilePath.assert_called_once_with() | ||
543 | 165 | mocked_check_media.assert_called_once_with('/path/to/file') | ||
544 | 166 | self.assertFalse(result) | ||
545 | 167 | |||
546 | 168 | def test_resize(self): | ||
547 | 169 | """ | ||
548 | 170 | Test the resize() method of the SystemPlayer | ||
549 | 171 | """ | ||
550 | 172 | # GIVEN: A SystemPlayer instance and a mocked display | ||
551 | 173 | player = SystemPlayer(self) | ||
552 | 174 | mocked_display = MagicMock() | ||
553 | 175 | mocked_display.size.return_value = [1, 2, 3, 4] | ||
554 | 176 | |||
555 | 177 | # WHEN: The resize() method is called | ||
556 | 178 | player.resize(mocked_display) | ||
557 | 179 | |||
558 | 180 | # THEN: The player is resized | ||
559 | 181 | mocked_display.size.assert_called_once_with() | ||
560 | 182 | mocked_display.video_widget.resize.assert_called_once_with([1, 2, 3, 4]) | ||
561 | 183 | |||
562 | 184 | @patch('openlp.core.ui.media.systemplayer.functools') | ||
563 | 185 | def test_play_is_live(self, mocked_functools): | ||
564 | 186 | """ | ||
565 | 187 | Test the play() method of the SystemPlayer on the live display | ||
566 | 188 | """ | ||
567 | 189 | # GIVEN: A SystemPlayer instance and a mocked display | ||
568 | 190 | mocked_functools.partial.return_value = 'function' | ||
569 | 191 | player = SystemPlayer(self) | ||
570 | 192 | mocked_display = MagicMock() | ||
571 | 193 | mocked_display.controller.is_live = True | ||
572 | 194 | mocked_display.controller.media_info.start_time = 1 | ||
573 | 195 | mocked_display.controller.media_info.volume = 1 | ||
574 | 196 | |||
575 | 197 | # WHEN: play() is called | ||
576 | 198 | with patch.object(player, 'get_live_state') as mocked_get_live_state, \ | ||
577 | 199 | patch.object(player, 'seek') as mocked_seek, \ | ||
578 | 200 | patch.object(player, 'volume') as mocked_volume, \ | ||
579 | 201 | patch.object(player, 'set_state') as mocked_set_state: | ||
580 | 202 | mocked_get_live_state.return_value = QtMultimedia.QMediaPlayer.PlayingState | ||
581 | 203 | result = player.play(mocked_display) | ||
582 | 204 | |||
583 | 205 | # THEN: the media file is played | ||
584 | 206 | mocked_get_live_state.assert_called_once_with() | ||
585 | 207 | mocked_display.media_player.play.assert_called_once_with() | ||
586 | 208 | mocked_seek.assert_called_once_with(mocked_display, 1000) | ||
587 | 209 | mocked_volume.assert_called_once_with(mocked_display, 1) | ||
588 | 210 | mocked_display.media_player.durationChanged.connect.assert_called_once_with('function') | ||
589 | 211 | mocked_set_state.assert_called_once_with(MediaState.Playing, mocked_display) | ||
590 | 212 | mocked_display.video_widget.raise_.assert_called_once_with() | ||
591 | 213 | self.assertTrue(result) | ||
592 | 214 | |||
593 | 215 | @patch('openlp.core.ui.media.systemplayer.functools') | ||
594 | 216 | def test_play_is_preview(self, mocked_functools): | ||
595 | 217 | """ | ||
596 | 218 | Test the play() method of the SystemPlayer on the preview display | ||
597 | 219 | """ | ||
598 | 220 | # GIVEN: A SystemPlayer instance and a mocked display | ||
599 | 221 | mocked_functools.partial.return_value = 'function' | ||
600 | 222 | player = SystemPlayer(self) | ||
601 | 223 | mocked_display = MagicMock() | ||
602 | 224 | mocked_display.controller.is_live = False | ||
603 | 225 | mocked_display.controller.media_info.start_time = 1 | ||
604 | 226 | mocked_display.controller.media_info.volume = 1 | ||
605 | 227 | |||
606 | 228 | # WHEN: play() is called | ||
607 | 229 | with patch.object(player, 'get_preview_state') as mocked_get_preview_state, \ | ||
608 | 230 | patch.object(player, 'seek') as mocked_seek, \ | ||
609 | 231 | patch.object(player, 'volume') as mocked_volume, \ | ||
610 | 232 | patch.object(player, 'set_state') as mocked_set_state: | ||
611 | 233 | mocked_get_preview_state.return_value = QtMultimedia.QMediaPlayer.PlayingState | ||
612 | 234 | result = player.play(mocked_display) | ||
613 | 235 | |||
614 | 236 | # THEN: the media file is played | ||
615 | 237 | mocked_get_preview_state.assert_called_once_with() | ||
616 | 238 | mocked_display.media_player.play.assert_called_once_with() | ||
617 | 239 | mocked_seek.assert_called_once_with(mocked_display, 1000) | ||
618 | 240 | mocked_volume.assert_called_once_with(mocked_display, 1) | ||
619 | 241 | mocked_display.media_player.durationChanged.connect.assert_called_once_with('function') | ||
620 | 242 | mocked_set_state.assert_called_once_with(MediaState.Playing, mocked_display) | ||
621 | 243 | mocked_display.video_widget.raise_.assert_called_once_with() | ||
622 | 244 | self.assertTrue(result) | ||
623 | 245 | |||
624 | 246 | def test_pause_is_live(self): | ||
625 | 247 | """ | ||
626 | 248 | Test the pause() method of the SystemPlayer on the live display | ||
627 | 249 | """ | ||
628 | 250 | # GIVEN: A SystemPlayer instance | ||
629 | 251 | player = SystemPlayer(self) | ||
630 | 252 | mocked_display = MagicMock() | ||
631 | 253 | mocked_display.controller.is_live = True | ||
632 | 254 | |||
633 | 255 | # WHEN: The pause method is called | ||
634 | 256 | with patch.object(player, 'get_live_state') as mocked_get_live_state, \ | ||
635 | 257 | patch.object(player, 'set_state') as mocked_set_state: | ||
636 | 258 | mocked_get_live_state.return_value = QtMultimedia.QMediaPlayer.PausedState | ||
637 | 259 | player.pause(mocked_display) | ||
638 | 260 | |||
639 | 261 | # THEN: The video is paused | ||
640 | 262 | mocked_display.media_player.pause.assert_called_once_with() | ||
641 | 263 | mocked_get_live_state.assert_called_once_with() | ||
642 | 264 | mocked_set_state.assert_called_once_with(MediaState.Paused, mocked_display) | ||
643 | 265 | |||
644 | 266 | def test_pause_is_preview(self): | ||
645 | 267 | """ | ||
646 | 268 | Test the pause() method of the SystemPlayer on the preview display | ||
647 | 269 | """ | ||
648 | 270 | # GIVEN: A SystemPlayer instance | ||
649 | 271 | player = SystemPlayer(self) | ||
650 | 272 | mocked_display = MagicMock() | ||
651 | 273 | mocked_display.controller.is_live = False | ||
652 | 274 | |||
653 | 275 | # WHEN: The pause method is called | ||
654 | 276 | with patch.object(player, 'get_preview_state') as mocked_get_preview_state, \ | ||
655 | 277 | patch.object(player, 'set_state') as mocked_set_state: | ||
656 | 278 | mocked_get_preview_state.return_value = QtMultimedia.QMediaPlayer.PausedState | ||
657 | 279 | player.pause(mocked_display) | ||
658 | 280 | |||
659 | 281 | # THEN: The video is paused | ||
660 | 282 | mocked_display.media_player.pause.assert_called_once_with() | ||
661 | 283 | mocked_get_preview_state.assert_called_once_with() | ||
662 | 284 | mocked_set_state.assert_called_once_with(MediaState.Paused, mocked_display) | ||
663 | 285 | |||
664 | 286 | def test_stop(self): | ||
665 | 287 | """ | ||
666 | 288 | Test the stop() method of the SystemPlayer | ||
667 | 289 | """ | ||
668 | 290 | # GIVEN: A SystemPlayer instance | ||
669 | 291 | player = SystemPlayer(self) | ||
670 | 292 | mocked_display = MagicMock() | ||
671 | 293 | |||
672 | 294 | # WHEN: The stop method is called | ||
673 | 295 | with patch.object(player, 'set_visible') as mocked_set_visible, \ | ||
674 | 296 | patch.object(player, 'set_state') as mocked_set_state: | ||
675 | 297 | player.stop(mocked_display) | ||
676 | 298 | |||
677 | 299 | # THEN: The video is stopped | ||
678 | 300 | mocked_display.media_player.stop.assert_called_once_with() | ||
679 | 301 | mocked_set_visible.assert_called_once_with(mocked_display, False) | ||
680 | 302 | mocked_set_state.assert_called_once_with(MediaState.Stopped, mocked_display) | ||
681 | 303 | |||
682 | 304 | def test_volume(self): | ||
683 | 305 | """ | ||
684 | 306 | Test the volume() method of the SystemPlayer | ||
685 | 307 | """ | ||
686 | 308 | # GIVEN: A SystemPlayer instance | ||
687 | 309 | player = SystemPlayer(self) | ||
688 | 310 | mocked_display = MagicMock() | ||
689 | 311 | mocked_display.has_audio = True | ||
690 | 312 | |||
691 | 313 | # WHEN: The stop method is called | ||
692 | 314 | player.volume(mocked_display, 2) | ||
693 | 315 | |||
694 | 316 | # THEN: The video is stopped | ||
695 | 317 | mocked_display.media_player.setVolume.assert_called_once_with(2) | ||
696 | 318 | |||
697 | 319 | def test_seek(self): | ||
698 | 320 | """ | ||
699 | 321 | Test the seek() method of the SystemPlayer | ||
700 | 322 | """ | ||
701 | 323 | # GIVEN: A SystemPlayer instance | ||
702 | 324 | player = SystemPlayer(self) | ||
703 | 325 | mocked_display = MagicMock() | ||
704 | 326 | |||
705 | 327 | # WHEN: The stop method is called | ||
706 | 328 | player.seek(mocked_display, 2) | ||
707 | 329 | |||
708 | 330 | # THEN: The video is stopped | ||
709 | 331 | mocked_display.media_player.setPosition.assert_called_once_with(2) | ||
710 | 332 | |||
711 | 333 | def test_reset(self): | ||
712 | 334 | """ | ||
713 | 335 | Test the reset() method of the SystemPlayer | ||
714 | 336 | """ | ||
715 | 337 | # GIVEN: A SystemPlayer instance | ||
716 | 338 | player = SystemPlayer(self) | ||
717 | 339 | mocked_display = MagicMock() | ||
718 | 340 | |||
719 | 341 | # WHEN: reset() is called | ||
720 | 342 | with patch.object(player, 'set_state') as mocked_set_state, \ | ||
721 | 343 | patch.object(player, 'set_visible') as mocked_set_visible: | ||
722 | 344 | player.reset(mocked_display) | ||
723 | 345 | |||
724 | 346 | # THEN: The media player is reset | ||
725 | 347 | mocked_display.media_player.stop() | ||
726 | 348 | mocked_display.media_player.setMedia.assert_called_once_with(QtMultimedia.QMediaContent()) | ||
727 | 349 | mocked_set_visible.assert_called_once_with(mocked_display, False) | ||
728 | 350 | mocked_display.video_widget.setVisible.assert_called_once_with(False) | ||
729 | 351 | mocked_set_state.assert_called_once_with(MediaState.Off, mocked_display) | ||
730 | 352 | |||
731 | 353 | def test_set_visible(self): | ||
732 | 354 | """ | ||
733 | 355 | Test the set_visible() method on the SystemPlayer | ||
734 | 356 | """ | ||
735 | 357 | # GIVEN: A SystemPlayer instance and a mocked display | ||
736 | 358 | player = SystemPlayer(self) | ||
737 | 359 | player.has_own_widget = True | ||
738 | 360 | mocked_display = MagicMock() | ||
739 | 361 | |||
740 | 362 | # WHEN: set_visible() is called | ||
741 | 363 | player.set_visible(mocked_display, True) | ||
742 | 364 | |||
743 | 365 | # THEN: The widget should be visible | ||
744 | 366 | mocked_display.video_widget.setVisible.assert_called_once_with(True) | ||
745 | 367 | |||
746 | 368 | def test_set_duration(self): | ||
747 | 369 | """ | ||
748 | 370 | Test the set_duration() method of the SystemPlayer | ||
749 | 371 | """ | ||
750 | 372 | # GIVEN: a mocked controller | ||
751 | 373 | mocked_controller = MagicMock() | ||
752 | 374 | mocked_controller.media_info.length = 5 | ||
753 | 375 | |||
754 | 376 | # WHEN: The set_duration() is called. NB: the 10 here is ignored by the code | ||
755 | 377 | SystemPlayer.set_duration(mocked_controller, 10) | ||
756 | 378 | |||
757 | 379 | # THEN: The maximum length of the slider should be set | ||
758 | 380 | mocked_controller.seek_slider.setMaximum.assert_called_once_with(5) | ||
759 | 381 | |||
760 | 382 | def test_update_ui(self): | ||
761 | 383 | """ | ||
762 | 384 | Test the update_ui() method on the SystemPlayer | ||
763 | 385 | """ | ||
764 | 386 | # GIVEN: A SystemPlayer instance | ||
765 | 387 | player = SystemPlayer(self) | ||
766 | 388 | player.state = MediaState.Playing | ||
767 | 389 | mocked_display = MagicMock() | ||
768 | 390 | mocked_display.media_player.state.return_value = QtMultimedia.QMediaPlayer.PausedState | ||
769 | 391 | mocked_display.controller.media_info.end_time = 1 | ||
770 | 392 | mocked_display.media_player.position.return_value = 2 | ||
771 | 393 | mocked_display.controller.seek_slider.isSliderDown.return_value = False | ||
772 | 394 | |||
773 | 395 | # WHEN: update_ui() is called | ||
774 | 396 | with patch.object(player, 'stop') as mocked_stop, \ | ||
775 | 397 | patch.object(player, 'set_visible') as mocked_set_visible: | ||
776 | 398 | player.update_ui(mocked_display) | ||
777 | 399 | |||
778 | 400 | # THEN: The UI is updated | ||
779 | 401 | expected_stop_calls = [call(mocked_display), call(mocked_display)] | ||
780 | 402 | expected_position_calls = [call(), call()] | ||
781 | 403 | expected_block_signals_calls = [call(True), call(False)] | ||
782 | 404 | mocked_display.media_player.state.assert_called_once_with() | ||
783 | 405 | self.assertEqual(2, mocked_stop.call_count) | ||
784 | 406 | self.assertEqual(expected_stop_calls, mocked_stop.call_args_list) | ||
785 | 407 | self.assertEqual(2, mocked_display.media_player.position.call_count) | ||
786 | 408 | self.assertEqual(expected_position_calls, mocked_display.media_player.position.call_args_list) | ||
787 | 409 | mocked_set_visible.assert_called_once_with(mocked_display, False) | ||
788 | 410 | mocked_display.controller.seek_slider.isSliderDown.assert_called_once_with() | ||
789 | 411 | self.assertEqual(expected_block_signals_calls, | ||
790 | 412 | mocked_display.controller.seek_slider.blockSignals.call_args_list) | ||
791 | 413 | mocked_display.controller.seek_slider.setSliderPosition.assert_called_once_with(2) | ||
792 | 414 | |||
793 | 415 | def test_get_media_display_css(self): | ||
794 | 416 | """ | ||
795 | 417 | Test the get_media_display_css() method of the SystemPlayer | ||
796 | 418 | """ | ||
797 | 419 | # GIVEN: A SystemPlayer instance | ||
798 | 420 | player = SystemPlayer(self) | ||
799 | 421 | |||
800 | 422 | # WHEN: get_media_display_css() is called | ||
801 | 423 | result = player.get_media_display_css() | ||
802 | 424 | |||
803 | 425 | # THEN: The css should be empty | ||
804 | 426 | self.assertEqual('', result) | ||
805 | 427 | |||
806 | 428 | def test_get_info(self): | ||
807 | 429 | """ | ||
808 | 430 | Test the get_info() method of the SystemPlayer | ||
809 | 431 | """ | ||
810 | 432 | # GIVEN: A SystemPlayer instance | ||
811 | 433 | player = SystemPlayer(self) | ||
812 | 434 | |||
813 | 435 | # WHEN: get_info() is called | ||
814 | 436 | result = player.get_info() | ||
815 | 437 | |||
816 | 438 | # THEN: The info should be correct | ||
817 | 439 | expected_info = 'This media player uses your operating system to provide media capabilities.<br/> ' \ | ||
818 | 440 | '<strong>Audio</strong><br/>[]<br/><strong>Video</strong><br/>[]<br/>' | ||
819 | 441 | self.assertEqual(expected_info, result) | ||
820 | 442 | |||
821 | 443 | @patch('openlp.core.ui.media.systemplayer.CheckMediaWorker') | ||
822 | 444 | @patch('openlp.core.ui.media.systemplayer.QtCore.QThread') | ||
823 | 445 | def test_check_media(self, MockQThread, MockCheckMediaWorker): | ||
824 | 446 | """ | ||
825 | 447 | Test the check_media() method of the SystemPlayer | ||
826 | 448 | """ | ||
827 | 449 | # GIVEN: A SystemPlayer instance and a mocked thread | ||
828 | 450 | valid_file = '/path/to/video.ogv' | ||
829 | 451 | mocked_application = MagicMock() | ||
830 | 452 | Registry().create() | ||
831 | 453 | Registry().register('application', mocked_application) | ||
832 | 454 | player = SystemPlayer(self) | ||
833 | 455 | mocked_thread = MagicMock() | ||
834 | 456 | mocked_thread.isRunning.side_effect = [True, False] | ||
835 | 457 | mocked_thread.quit = 'quit' # actually supposed to be a slot, but it's all mocked out anyway | ||
836 | 458 | MockQThread.return_value = mocked_thread | ||
837 | 459 | mocked_check_media_worker = MagicMock() | ||
838 | 460 | mocked_check_media_worker.play = 'play' | ||
839 | 461 | mocked_check_media_worker.result = True | ||
840 | 462 | MockCheckMediaWorker.return_value = mocked_check_media_worker | ||
841 | 463 | |||
842 | 464 | # WHEN: check_media() is called with a valid media file | ||
843 | 465 | result = player.check_media(valid_file) | ||
844 | 466 | |||
845 | 467 | # THEN: It should return True | ||
846 | 468 | MockQThread.assert_called_once_with() | ||
847 | 469 | MockCheckMediaWorker.assert_called_once_with(valid_file) | ||
848 | 470 | mocked_check_media_worker.setVolume.assert_called_once_with(0) | ||
849 | 471 | mocked_check_media_worker.moveToThread.assert_called_once_with(mocked_thread) | ||
850 | 472 | mocked_check_media_worker.finished.connect.assert_called_once_with('quit') | ||
851 | 473 | mocked_thread.started.connect.assert_called_once_with('play') | ||
852 | 474 | mocked_thread.start.assert_called_once_with() | ||
853 | 475 | self.assertEqual(2, mocked_thread.isRunning.call_count) | ||
854 | 476 | mocked_application.processEvents.assert_called_once_with() | ||
855 | 477 | self.assertTrue(result) | ||
856 | 478 | |||
857 | 479 | |||
858 | 480 | class TestCheckMediaWorker(TestCase): | ||
859 | 481 | """ | ||
860 | 482 | Test the CheckMediaWorker class | ||
861 | 483 | """ | ||
862 | 484 | def test_constructor(self): | ||
863 | 485 | """ | ||
864 | 486 | Test the constructor of the CheckMediaWorker class | ||
865 | 487 | """ | ||
866 | 488 | # GIVEN: A file path | ||
867 | 489 | path = 'file.ogv' | ||
868 | 490 | |||
869 | 491 | # WHEN: The CheckMediaWorker object is instantiated | ||
870 | 492 | worker = CheckMediaWorker(path) | ||
871 | 493 | |||
872 | 494 | # THEN: The correct values should be set up | ||
873 | 495 | self.assertIsNotNone(worker) | ||
874 | 496 | |||
875 | 497 | def test_signals_media(self): | ||
876 | 498 | """ | ||
877 | 499 | Test the signals() signal of the CheckMediaWorker class with a "media" origin | ||
878 | 500 | """ | ||
879 | 501 | # GIVEN: A CheckMediaWorker instance | ||
880 | 502 | worker = CheckMediaWorker('file.ogv') | ||
881 | 503 | |||
882 | 504 | # WHEN: signals() is called with media and BufferedMedia | ||
883 | 505 | with patch.object(worker, 'stop') as mocked_stop, \ | ||
884 | 506 | patch.object(worker, 'finished') as mocked_finished: | ||
885 | 507 | worker.signals('media', worker.BufferedMedia) | ||
886 | 508 | |||
887 | 509 | # THEN: The worker should exit and the result should be True | ||
888 | 510 | mocked_stop.assert_called_once_with() | ||
889 | 511 | mocked_finished.emit.assert_called_once_with() | ||
890 | 512 | self.assertTrue(worker.result) | ||
891 | 513 | |||
892 | 514 | def test_signals_error(self): | ||
893 | 515 | """ | ||
894 | 516 | Test the signals() signal of the CheckMediaWorker class with a "error" origin | ||
895 | 517 | """ | ||
896 | 518 | # GIVEN: A CheckMediaWorker instance | ||
897 | 519 | worker = CheckMediaWorker('file.ogv') | ||
898 | 520 | |||
899 | 521 | # WHEN: signals() is called with error and BufferedMedia | ||
900 | 522 | with patch.object(worker, 'stop') as mocked_stop, \ | ||
901 | 523 | patch.object(worker, 'finished') as mocked_finished: | ||
902 | 524 | worker.signals('error', None) | ||
903 | 525 | |||
904 | 526 | # THEN: The worker should exit and the result should be True | ||
905 | 527 | mocked_stop.assert_called_once_with() | ||
906 | 528 | mocked_finished.emit.assert_called_once_with() | ||
907 | 529 | self.assertFalse(worker.result) | ||
908 | 0 | 530 | ||
909 | === modified file 'tests/functional/openlp_plugins/songs/test_songselect.py' | |||
910 | --- tests/functional/openlp_plugins/songs/test_songselect.py 2016-05-31 21:40:13 +0000 | |||
911 | +++ tests/functional/openlp_plugins/songs/test_songselect.py 2016-08-13 15:05:55 +0000 | |||
912 | @@ -1,5 +1,6 @@ | |||
913 | 1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
914 | 2 | # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 | 2 | # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
915 | 3 | # pylint: disable=protected-access | ||
916 | 3 | 4 | ||
917 | 4 | ############################################################################### | 5 | ############################################################################### |
918 | 5 | # OpenLP - Open Source Lyrics Projection # | 6 | # OpenLP - Open Source Lyrics Projection # |
919 | @@ -28,14 +29,13 @@ | |||
920 | 28 | 29 | ||
921 | 29 | from PyQt5 import QtWidgets | 30 | from PyQt5 import QtWidgets |
922 | 30 | 31 | ||
923 | 31 | from tests.helpers.songfileimport import SongImportTestHelper | ||
924 | 32 | from openlp.core import Registry | 32 | from openlp.core import Registry |
925 | 33 | from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker | 33 | from openlp.plugins.songs.forms.songselectform import SongSelectForm, SearchWorker |
926 | 34 | from openlp.plugins.songs.lib import Song | 34 | from openlp.plugins.songs.lib import Song |
927 | 35 | from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGOUT_URL, BASE_URL | 35 | from openlp.plugins.songs.lib.songselect import SongSelectImport, LOGOUT_URL, BASE_URL |
928 | 36 | from openlp.plugins.songs.lib.importers.cclifile import CCLIFileImport | ||
929 | 37 | 36 | ||
930 | 38 | from tests.functional import MagicMock, patch, call | 37 | from tests.functional import MagicMock, patch, call |
931 | 38 | from tests.helpers.songfileimport import SongImportTestHelper | ||
932 | 39 | from tests.helpers.testmixin import TestMixin | 39 | from tests.helpers.testmixin import TestMixin |
933 | 40 | 40 | ||
934 | 41 | TEST_PATH = os.path.abspath( | 41 | TEST_PATH = os.path.abspath( |
935 | @@ -71,7 +71,7 @@ | |||
936 | 71 | mocked_opener = MagicMock() | 71 | mocked_opener = MagicMock() |
937 | 72 | mocked_build_opener.return_value = mocked_opener | 72 | mocked_build_opener.return_value = mocked_opener |
938 | 73 | mocked_login_page = MagicMock() | 73 | mocked_login_page = MagicMock() |
940 | 74 | mocked_login_page.find.return_value = {'value': 'blah'} | 74 | mocked_login_page.find.side_effect = [{'value': 'blah'}, None] |
941 | 75 | MockedBeautifulSoup.return_value = mocked_login_page | 75 | MockedBeautifulSoup.return_value = mocked_login_page |
942 | 76 | mock_callback = MagicMock() | 76 | mock_callback = MagicMock() |
943 | 77 | importer = SongSelectImport(None) | 77 | importer = SongSelectImport(None) |
944 | @@ -112,7 +112,7 @@ | |||
945 | 112 | mocked_opener = MagicMock() | 112 | mocked_opener = MagicMock() |
946 | 113 | mocked_build_opener.return_value = mocked_opener | 113 | mocked_build_opener.return_value = mocked_opener |
947 | 114 | mocked_login_page = MagicMock() | 114 | mocked_login_page = MagicMock() |
949 | 115 | mocked_login_page.find.side_effect = [{'value': 'blah'}, None] | 115 | mocked_login_page.find.side_effect = [{'value': 'blah'}, MagicMock()] |
950 | 116 | MockedBeautifulSoup.return_value = mocked_login_page | 116 | MockedBeautifulSoup.return_value = mocked_login_page |
951 | 117 | mock_callback = MagicMock() | 117 | mock_callback = MagicMock() |
952 | 118 | importer = SongSelectImport(None) | 118 | importer = SongSelectImport(None) |
953 | @@ -165,7 +165,7 @@ | |||
954 | 165 | self.assertEqual(0, mock_callback.call_count, 'callback should not have been called') | 165 | self.assertEqual(0, mock_callback.call_count, 'callback should not have been called') |
955 | 166 | self.assertEqual(1, mocked_opener.open.call_count, 'open should have been called once') | 166 | self.assertEqual(1, mocked_opener.open.call_count, 'open should have been called once') |
956 | 167 | self.assertEqual(1, mocked_results_page.find_all.call_count, 'find_all should have been called once') | 167 | self.assertEqual(1, mocked_results_page.find_all.call_count, 'find_all should have been called once') |
958 | 168 | mocked_results_page.find_all.assert_called_with('li', 'result pane') | 168 | mocked_results_page.find_all.assert_called_with('div', 'song-result') |
959 | 169 | self.assertEqual([], results, 'The search method should have returned an empty list') | 169 | self.assertEqual([], results, 'The search method should have returned an empty list') |
960 | 170 | 170 | ||
961 | 171 | @patch('openlp.plugins.songs.lib.songselect.build_opener') | 171 | @patch('openlp.plugins.songs.lib.songselect.build_opener') |
962 | @@ -177,12 +177,18 @@ | |||
963 | 177 | # GIVEN: A bunch of mocked out stuff and an importer object | 177 | # GIVEN: A bunch of mocked out stuff and an importer object |
964 | 178 | # first search result | 178 | # first search result |
965 | 179 | mocked_result1 = MagicMock() | 179 | mocked_result1 = MagicMock() |
968 | 180 | mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}] | 180 | mocked_result1.find.side_effect = [ |
969 | 181 | mocked_result1.find_all.return_value = [MagicMock(string='Author 1-1'), MagicMock(string='Author 1-2')] | 181 | MagicMock(find=MagicMock(return_value=MagicMock(string='Title 1'))), |
970 | 182 | MagicMock(string='James, John'), | ||
971 | 183 | MagicMock(find=MagicMock(return_value={'href': '/url1'})) | ||
972 | 184 | ] | ||
973 | 182 | # second search result | 185 | # second search result |
974 | 183 | mocked_result2 = MagicMock() | 186 | mocked_result2 = MagicMock() |
977 | 184 | mocked_result2.find.side_effect = [MagicMock(string='Title 2'), {'href': '/url2'}] | 187 | mocked_result2.find.side_effect = [ |
978 | 185 | mocked_result2.find_all.return_value = [MagicMock(string='Author 2-1'), MagicMock(string='Author 2-2')] | 188 | MagicMock(find=MagicMock(return_value=MagicMock(string='Title 2'))), |
979 | 189 | MagicMock(string='Philip'), | ||
980 | 190 | MagicMock(find=MagicMock(return_value={'href': '/url2'})) | ||
981 | 191 | ] | ||
982 | 186 | # rest of the stuff | 192 | # rest of the stuff |
983 | 187 | mocked_opener = MagicMock() | 193 | mocked_opener = MagicMock() |
984 | 188 | mocked_build_opener.return_value = mocked_opener | 194 | mocked_build_opener.return_value = mocked_opener |
985 | @@ -199,10 +205,10 @@ | |||
986 | 199 | self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice') | 205 | self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice') |
987 | 200 | self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice') | 206 | self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice') |
988 | 201 | self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice') | 207 | self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice') |
990 | 202 | mocked_results_page.find_all.assert_called_with('li', 'result pane') | 208 | mocked_results_page.find_all.assert_called_with('div', 'song-result') |
991 | 203 | expected_list = [ | 209 | expected_list = [ |
994 | 204 | {'title': 'Title 1', 'authors': ['Author 1-1', 'Author 1-2'], 'link': BASE_URL + '/url1'}, | 210 | {'title': 'Title 1', 'authors': ['James', 'John'], 'link': BASE_URL + '/url1'}, |
995 | 205 | {'title': 'Title 2', 'authors': ['Author 2-1', 'Author 2-2'], 'link': BASE_URL + '/url2'} | 211 | {'title': 'Title 2', 'authors': ['Philip'], 'link': BASE_URL + '/url2'} |
996 | 206 | ] | 212 | ] |
997 | 207 | self.assertListEqual(expected_list, results, 'The search method should have returned two songs') | 213 | self.assertListEqual(expected_list, results, 'The search method should have returned two songs') |
998 | 208 | 214 | ||
999 | @@ -215,16 +221,25 @@ | |||
1000 | 215 | # GIVEN: A bunch of mocked out stuff and an importer object | 221 | # GIVEN: A bunch of mocked out stuff and an importer object |
1001 | 216 | # first search result | 222 | # first search result |
1002 | 217 | mocked_result1 = MagicMock() | 223 | mocked_result1 = MagicMock() |
1005 | 218 | mocked_result1.find.side_effect = [MagicMock(string='Title 1'), {'href': '/url1'}] | 224 | mocked_result1.find.side_effect = [ |
1006 | 219 | mocked_result1.find_all.return_value = [MagicMock(string='Author 1-1'), MagicMock(string='Author 1-2')] | 225 | MagicMock(find=MagicMock(return_value=MagicMock(string='Title 1'))), |
1007 | 226 | MagicMock(string='James, John'), | ||
1008 | 227 | MagicMock(find=MagicMock(return_value={'href': '/url1'})) | ||
1009 | 228 | ] | ||
1010 | 220 | # second search result | 229 | # second search result |
1011 | 221 | mocked_result2 = MagicMock() | 230 | mocked_result2 = MagicMock() |
1014 | 222 | mocked_result2.find.side_effect = [MagicMock(string='Title 2'), {'href': '/url2'}] | 231 | mocked_result2.find.side_effect = [ |
1015 | 223 | mocked_result2.find_all.return_value = [MagicMock(string='Author 2-1'), MagicMock(string='Author 2-2')] | 232 | MagicMock(find=MagicMock(return_value=MagicMock(string='Title 2'))), |
1016 | 233 | MagicMock(string='Philip'), | ||
1017 | 234 | MagicMock(find=MagicMock(return_value={'href': '/url2'})) | ||
1018 | 235 | ] | ||
1019 | 224 | # third search result | 236 | # third search result |
1020 | 225 | mocked_result3 = MagicMock() | 237 | mocked_result3 = MagicMock() |
1023 | 226 | mocked_result3.find.side_effect = [MagicMock(string='Title 3'), {'href': '/url3'}] | 238 | mocked_result3.find.side_effect = [ |
1024 | 227 | mocked_result3.find_all.return_value = [MagicMock(string='Author 3-1'), MagicMock(string='Author 3-2')] | 239 | MagicMock(find=MagicMock(return_value=MagicMock(string='Title 3'))), |
1025 | 240 | MagicMock(string='Luke, Matthew'), | ||
1026 | 241 | MagicMock(find=MagicMock(return_value={'href': '/url3'})) | ||
1027 | 242 | ] | ||
1028 | 228 | # rest of the stuff | 243 | # rest of the stuff |
1029 | 229 | mocked_opener = MagicMock() | 244 | mocked_opener = MagicMock() |
1030 | 230 | mocked_build_opener.return_value = mocked_opener | 245 | mocked_build_opener.return_value = mocked_opener |
1031 | @@ -241,9 +256,9 @@ | |||
1032 | 241 | self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice') | 256 | self.assertEqual(2, mock_callback.call_count, 'callback should have been called twice') |
1033 | 242 | self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice') | 257 | self.assertEqual(2, mocked_opener.open.call_count, 'open should have been called twice') |
1034 | 243 | self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice') | 258 | self.assertEqual(2, mocked_results_page.find_all.call_count, 'find_all should have been called twice') |
1038 | 244 | mocked_results_page.find_all.assert_called_with('li', 'result pane') | 259 | mocked_results_page.find_all.assert_called_with('div', 'song-result') |
1039 | 245 | expected_list = [{'title': 'Title 1', 'authors': ['Author 1-1', 'Author 1-2'], 'link': BASE_URL + '/url1'}, | 260 | expected_list = [{'title': 'Title 1', 'authors': ['James', 'John'], 'link': BASE_URL + '/url1'}, |
1040 | 246 | {'title': 'Title 2', 'authors': ['Author 2-1', 'Author 2-2'], 'link': BASE_URL + '/url2'}] | 261 | {'title': 'Title 2', 'authors': ['Philip'], 'link': BASE_URL + '/url2'}] |
1041 | 247 | self.assertListEqual(expected_list, results, 'The search method should have returned two songs') | 262 | self.assertListEqual(expected_list, results, 'The search method should have returned two songs') |
1042 | 248 | 263 | ||
1043 | 249 | @patch('openlp.plugins.songs.lib.songselect.build_opener') | 264 | @patch('openlp.plugins.songs.lib.songselect.build_opener') |
1044 | @@ -337,7 +352,7 @@ | |||
1045 | 337 | self.assertIsNotNone(result, 'The get_song() method should have returned a song dictionary') | 352 | self.assertIsNotNone(result, 'The get_song() method should have returned a song dictionary') |
1046 | 338 | self.assertEqual(2, mocked_lyrics_page.find.call_count, 'The find() method should have been called twice') | 353 | self.assertEqual(2, mocked_lyrics_page.find.call_count, 'The find() method should have been called twice') |
1047 | 339 | self.assertEqual(2, mocked_find_all.call_count, 'The find_all() method should have been called twice') | 354 | self.assertEqual(2, mocked_find_all.call_count, 'The find_all() method should have been called twice') |
1049 | 340 | self.assertEqual([call('section', 'lyrics'), call('section', 'lyrics')], | 355 | self.assertEqual([call('div', 'song-viewer lyrics'), call('div', 'song-viewer lyrics')], |
1050 | 341 | mocked_lyrics_page.find.call_args_list, | 356 | mocked_lyrics_page.find.call_args_list, |
1051 | 342 | 'The find() method should have been called with the right arguments') | 357 | 'The find() method should have been called with the right arguments') |
1052 | 343 | self.assertEqual([call('p'), call('h3')], mocked_find_all.call_args_list, | 358 | self.assertEqual([call('p'), call('h3')], mocked_find_all.call_args_list, |
1053 | @@ -348,8 +363,9 @@ | |||
1054 | 348 | self.assertEqual(3, len(result['verses']), 'Three verses should have been returned') | 363 | self.assertEqual(3, len(result['verses']), 'Three verses should have been returned') |
1055 | 349 | 364 | ||
1056 | 350 | @patch('openlp.plugins.songs.lib.songselect.clean_song') | 365 | @patch('openlp.plugins.songs.lib.songselect.clean_song') |
1057 | 366 | @patch('openlp.plugins.songs.lib.songselect.Topic') | ||
1058 | 351 | @patch('openlp.plugins.songs.lib.songselect.Author') | 367 | @patch('openlp.plugins.songs.lib.songselect.Author') |
1060 | 352 | def test_save_song_new_author(self, MockedAuthor, mocked_clean_song): | 368 | def test_save_song_new_author(self, MockedAuthor, MockedTopic, mocked_clean_song): |
1061 | 353 | """ | 369 | """ |
1062 | 354 | Test that saving a song with a new author performs the correct actions | 370 | Test that saving a song with a new author performs the correct actions |
1063 | 355 | """ | 371 | """ |
1064 | @@ -366,6 +382,7 @@ | |||
1065 | 366 | 'ccli_number': '123456' | 382 | 'ccli_number': '123456' |
1066 | 367 | } | 383 | } |
1067 | 368 | MockedAuthor.display_name.__eq__.return_value = False | 384 | MockedAuthor.display_name.__eq__.return_value = False |
1068 | 385 | MockedTopic.name.__eq__.return_value = False | ||
1069 | 369 | mocked_db_manager = MagicMock() | 386 | mocked_db_manager = MagicMock() |
1070 | 370 | mocked_db_manager.get_object_filtered.return_value = None | 387 | mocked_db_manager.get_object_filtered.return_value = None |
1071 | 371 | importer = SongSelectImport(mocked_db_manager) | 388 | importer = SongSelectImport(mocked_db_manager) |
1072 | @@ -848,7 +865,7 @@ | |||
1073 | 848 | 865 | ||
1074 | 849 | # WHEN: The start() method is called | 866 | # WHEN: The start() method is called |
1075 | 850 | with patch.object(worker, 'found_song') as mocked_found_song: | 867 | with patch.object(worker, 'found_song') as mocked_found_song: |
1077 | 851 | worker._found_song_callback(song) | 868 | worker._found_song_callback(song) # pylint: disable=protected-access |
1078 | 852 | 869 | ||
1079 | 853 | # THEN: The "found_song" signal should have been emitted | 870 | # THEN: The "found_song" signal should have been emitted |
1080 | 854 | mocked_found_song.emit.assert_called_with(song) | 871 | mocked_found_song.emit.assert_called_with(song) |