from flask import url_for from bs4 import BeautifulSoup import pytest @pytest.mark.parametrize('title,page_title,expected', [ ('Release notes ', 'Fetched title', 'Release notes '), ('', 'Fetched title', 'Fetched title'), ('', '', 'https://example.com/releases'), ]) def test_diff_header_watch_label(client, title, page_title, expected): """The top line of the menu names the watch, so the page does not print the same URL twice: the watch's own title when it has one, the fetched page title next, and the URL only when neither is set.""" datastore = client.application.config['DATASTORE'] uuid = datastore.add_watch(url='https://example.com/releases', extras={ 'title': title, 'page_title': page_title, 'paused': True, }) watch = datastore.data['watching'][uuid] watch.save_history_blob('First release', 1700000000, 'first') watch.save_history_blob('Second release', 1700000060, 'second') response = client.get(url_for('ui.ui_diff.diff_history_page', uuid=uuid)) assert response.status_code == 200 page = BeautifulSoup(response.data, 'html.parser') heading = page.select_one('.header .current-diff-url') assert heading.get_text(strip=True) == expected assert heading.find('script') is None # Clicking it still goes to the watched page - only the text changed. assert heading.get('href') == 'https://example.com/releases' # The separate heading this replaced is gone, so the bar is one row shorter. assert page.select_one('#diff-watch-title') is None assert page.select_one('#diff-header #diff-form') is not None assert page.select_one('#diff-header .tabs') is not None assert page.select_one('#diff-header #difference') is None assert 'Second release' in page.select_one('#difference').get_text() # The line is one line and fades out when it outgrows the bar, so the # untruncated text has to stay reachable on hover / long-press. assert expected in heading.get('title') def test_diff_header_label_keeps_the_url_reachable(client): """A title covers the URL that used to be printed here, so the hover text has to carry both - otherwise the only way to read the URL is to follow it.""" datastore = client.application.config['DATASTORE'] uuid = datastore.add_watch(url='https://example.com/releases', extras={ 'title': 'Release notes', 'paused': True, }) watch = datastore.data['watching'][uuid] watch.save_history_blob('First release', 1700000000, 'first') watch.save_history_blob('Second release', 1700000060, 'second') page = BeautifulSoup(client.get(url_for('ui.ui_diff.diff_history_page', uuid=uuid)).data, 'html.parser') link = page.select_one('.header .current-diff-url') hover = link.get('title') assert 'Release notes' in hover assert 'https://example.com/releases' in hover # Without a title the line already *is* the URL, so hover must not repeat it. untitled = datastore.add_watch(url='https://example.com/other', extras={'paused': True}) other = datastore.data['watching'][untitled] other.save_history_blob('First', 1700000000, 'first') other.save_history_blob('Second', 1700000060, 'second') page = BeautifulSoup(client.get(url_for('ui.ui_diff.diff_history_page', uuid=untitled)).data, 'html.parser') link = page.select_one('.header .current-diff-url') assert link.get('title') == 'https://example.com/other' def test_diff_header_heart_stays_in_the_markup(client): """The heart is folded away by a media query on this page, not removed from it - so it has to still be in the DOM. Its visibility is in diff.scss and cannot be seen from here.""" datastore = client.application.config['DATASTORE'] uuid = datastore.add_watch(url='https://example.com/releases', extras={'paused': True}) watch = datastore.data['watching'][uuid] watch.save_history_blob('First release', 1700000000, 'first') watch.save_history_blob('Second release', 1700000060, 'second') page = BeautifulSoup(client.get(url_for('ui.ui_diff.diff_history_page', uuid=uuid)).data, 'html.parser') assert page.select_one('#heart-us') is not None def _seeded_diff_page(client, **extras): datastore = client.application.config['DATASTORE'] extras.setdefault('paused', True) uuid = datastore.add_watch(url='https://example.com/releases', extras=extras) watch = datastore.data['watching'][uuid] watch.save_history_blob('First release', 1700000000, 'first') watch.save_history_blob('Second release', 1700000060, 'second') response = client.get(url_for('ui.ui_diff.diff_history_page', uuid=uuid)) assert response.status_code == 200 return BeautifulSoup(response.data, 'html.parser') def test_diff_filters_toggle_degrades_without_javascript(client): """The diff options collapse into a popover, but only once diff-overview.js has taken them over: the toggle ships hidden and the fieldset ships inline and inside the form, so with scripting off the options are still reachable and still submit.""" page = _seeded_diff_page(client) toggle = page.select_one('#diff-form #diff-filters-toggle') assert toggle is not None # Inside a form, anything but type=button submits it. assert toggle.get('type') == 'button' assert toggle.get('aria-expanded') == 'false' assert toggle.get('aria-controls') == 'diff-style' options = page.select_one('#diff-form #diff-style') assert options is not None assert options.get('hidden') is None assert page.select_one('#diff-form #diff-style #ignoreWhitespace') is not None def test_diff_version_arrows_are_named(client): """Only the arrow glyphs are visible in the compact bar, so each link has to carry the wording itself rather than lean on text the CSS hides.""" page = _seeded_diff_page(client) for element_id in ('btn-previous', 'btn-next'): link = page.select_one(f'#keyboard-nav #{element_id}') assert link is not None assert link.get('aria-label') assert link.get('title') == link.get('aria-label') label = link.select_one('.keyboard-nav-label') assert label is not None # The accessible name has to be the same word the hidden label shows, so # that it comes from a msgid the catalogs already translate rather than a # longer phrase invented for this bar that only screen readers ever hear. assert link.get('aria-label') == label.get_text(strip=True) def test_difference_page_class_scopes_sticky_header(client): """The sticky top menu is styled off body.difference-page, so only the diff page may carry that class - the extract page shares the same blueprint.""" datastore = client.application.config['DATASTORE'] uuid = datastore.add_watch(url='https://example.com/releases', extras={'paused': True}) watch = datastore.data['watching'][uuid] watch.save_history_blob('First release', 1700000000, 'first') watch.save_history_blob('Second release', 1700000060, 'second') diff = BeautifulSoup(client.get(url_for('ui.ui_diff.diff_history_page', uuid=uuid)).data, 'html.parser') assert 'difference-page' in diff.select_one('body').get('class') extract = client.get(url_for('ui.ui_diff.diff_history_page_extract_GET', uuid=uuid)) assert extract.status_code == 200 assert 'difference-page' not in BeautifulSoup(extract.data, 'html.parser').select_one('body').get('class') def test_scrollable_title_script_is_global(client): """The top line is a scroll container whose fades are maintained by JS. The restock difference page and the image-SSIM preview page both render that line and neither loads diff-overview.js, so the script belongs in base.html - which is what putting it on a page with no title line at all demonstrates.""" datastore = client.application.config['DATASTORE'] uuid = datastore.add_watch(url='https://example.com/releases', extras={'paused': True}) watch = datastore.data['watching'][uuid] watch.save_history_blob('First release', 1700000000, 'first') watch.save_history_blob('Second release', 1700000060, 'second') def scripts(response): assert response.status_code == 200 page = BeautifulSoup(response.data, 'html.parser') return page, {s['src'] for s in page.select('script[src]')} page, srcs = scripts(client.get(url_for('ui.ui_diff.diff_history_page', uuid=uuid))) assert page.select_one('.header .current-diff-url') is not None script = page.select_one('script[src*="scrollable-title.js"]') assert script is not None # Deferred, so jQuery and the markup both exist by the time it runs. assert script.has_attr('defer') # The watch overview has no title line, and still serves the script: that is # only true of a base.html script, and it is what the other pages rely on. overview, srcs = scripts(client.get(url_for('watchlist.index'))) assert overview.select_one('.header .current-diff-url') is None assert any('scrollable-title.js' in src for src in srcs) assert not any('diff-overview.js' in src for src in srcs)