aboutsummaryrefslogtreecommitdiff
path: root/test/unit/test_text_entry_list.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/unit/test_text_entry_list.py')
-rw-r--r--test/unit/test_text_entry_list.py310
1 files changed, 310 insertions, 0 deletions
diff --git a/test/unit/test_text_entry_list.py b/test/unit/test_text_entry_list.py
new file mode 100644
index 0000000..1951d53
--- /dev/null
+++ b/test/unit/test_text_entry_list.py
@@ -0,0 +1,310 @@
+# SPDX-License-Identifier: CC0-1.0
+
+"""
+Haketilo unit tests - list of editable entries
+"""
+
+# This file is part of Haketilo
+#
+# Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the CC0 1.0 Universal License as published by
+# the Creative Commons Corporation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# CC0 1.0 Universal License for more details.
+
+import pytest
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.common.keys import Keys
+import inspect
+
+from ..extension_crafting import ExtraHTML
+from ..script_loader import load_script
+from .utils import *
+
+broker_js = lambda: load_script('background/broadcast_broker.js') + ';start();'
+
+def instantiate_list(to_return):
+ return inspect.stack()[1].frame.f_locals['execute_in_page'](
+ f'''
+ let dialog_ctx = dialog.make(() => {{}}, () => {{}}), list;
+ async function make_list() {{
+ list = await repo_list(dialog_ctx);
+ document.body.append(list.main_div, dialog_ctx.main_div);
+ return [{', '.join(to_return)}];
+ }}
+ returnval(make_list());
+ ''')
+
+dialog_html_append = {'html/text_entry_list.html': '#INCLUDE html/dialog.html'}
+dialog_html_test_ext_data = {
+ 'background_script': broker_js,
+ 'extra_html': ExtraHTML('html/text_entry_list.html', dialog_html_append),
+ 'navigate_to': 'html/text_entry_list.html'
+}
+
+@pytest.mark.ext_data(dialog_html_test_ext_data)
+@pytest.mark.usefixtures('webextension')
+def test_text_entry_list_ordering(driver, execute_in_page):
+ """
+ A test case of ordering of repo URLs in the list.
+ """
+ execute_in_page(load_script('html/text_entry_list.js'))
+
+ endings = ['hyd/', 'hydrilla/', 'Hydrilla/', 'HYDRILLA/',
+ 'test/', 'test^it/', 'Test^it/', 'TEST^IT/']
+
+ indexes_added = set()
+
+ for iteration, to_include in enumerate([
+ set([i for i in range(len(endings)) if is_prime(i)]),
+ set([i for i in range(len(endings))
+ if not is_prime(i) and i & 1]),
+ set([i for i in range(len(endings)) if i % 3 == 0]),
+ set([i for i in range(len(endings))
+ if i % 3 and not i & 1 and not is_prime(i)]),
+ set(range(len(endings)))
+ ]):
+ endings_to_include = [endings[i] for i in sorted(to_include)]
+ urls = [f'https://example.com/{e}' for e in endings_to_include]
+
+ def add_urls():
+ execute_in_page(
+ '''{
+ async function add_urls(urls) {
+ for (const url of urls)
+ await haketilodb.set_repo(url);
+ }
+ returnval(add_urls(arguments[0]));
+ }''',
+ urls)
+
+ def wait_for_completed(wait_id):
+ """
+ We add an extra repo url to IndexedDB and wait for it to appear in
+ the DOM list. Once this happes, we know other operations must have
+ also finished.
+ """
+ url = f'https://example.org/{iteration}/{wait_id}'
+ execute_in_page('returnval(haketilodb.set_repo(arguments[0]));',
+ url)
+ WebDriverWait(driver, 10).until(lambda _: url in list_div.text)
+
+ def assert_order(indexes_present, empty_entry_expected=False):
+ entries_texts = execute_in_page(
+ '''
+ returnval([...list.list_div.children].map(n => n.textContent));
+ ''')
+
+ if empty_entry_expected:
+ assert 'example' not in entries_texts[0]
+ entries_texts.pop(0)
+
+ for i, et in zip(sorted(indexes_present), entries_texts):
+ assert f'https://example.com/{endings[i]}' in et
+
+ for et in entries_texts[len(indexes_present):]:
+ assert 'example.org' in et
+
+ add_urls()
+
+ if iteration == 0:
+ list_div, new_entry_but = \
+ instantiate_list(['list.list_div', 'list.new_but'])
+
+ indexes_added.update(to_include)
+ wait_for_completed(0)
+ assert_order(indexes_added)
+
+ execute_in_page(
+ '''{
+ async function remove_urls(urls) {
+ for (const url of urls)
+ await haketilodb.del_repo(url);
+ }
+ returnval(remove_urls(arguments[0]));
+ }''',
+ urls)
+ wait_for_completed(1)
+ assert_order(indexes_added.difference(to_include))
+
+ # On the last iteration, add a new editable entry before re-additions.
+ if len(to_include) == len(endings):
+ new_entry_but.click()
+ add_urls()
+ wait_for_completed(2)
+ assert_order(indexes_added, empty_entry_expected=True)
+ else:
+ add_urls()
+
+def active(id):
+ return inspect.stack()[1].frame.f_locals['execute_in_page']\
+ (f'returnval(list.active_entry.{id});')
+def existing(id, entry_nr=0):
+ return inspect.stack()[1].frame.f_locals['execute_in_page'](
+ '''
+ returnval(list.entries_by_text.get(list.shown_texts[arguments[0]])\
+ [arguments[1]]);
+ ''',
+ entry_nr, id)
+
+@pytest.mark.ext_data(dialog_html_test_ext_data)
+@pytest.mark.usefixtures('webextension')
+def test_text_entry_list_editing(driver, execute_in_page):
+ """
+ A test case of editing entries in repo URLs list.
+ """
+ execute_in_page(load_script('html/text_entry_list.js'))
+
+ execute_in_page(
+ '''
+ let original_loader = dialog.loader, last_loader_msg;
+ dialog.loader = (ctx, ...msg) => {
+ last_loader_msg = msg;
+ return original_loader(ctx, ...msg);
+ }
+ ''')
+ last_loader_msg = lambda: execute_in_page('returnval(last_loader_msg);')
+
+ list_div, new_entry_but = \
+ instantiate_list(['list.list_div', 'list.new_but'])
+
+ assert last_loader_msg() == ['Loading repositories...']
+ assert execute_in_page('returnval(dialog_ctx.shown);') == False
+
+ # Test adding new item. Submit via button click.
+ new_entry_but.click()
+ assert not active('noneditable_view').is_displayed()
+ assert not active('save_but').is_displayed()
+ assert active('add_but').is_displayed()
+ assert active('cancel_but').is_displayed()
+ active('input').send_keys('https://example.com///')
+ active('add_but').click()
+ WebDriverWait(driver, 10).until(lambda _: 'example.com/' in list_div.text)
+ assert execute_in_page('returnval(list.list_div.children.length);') == 1
+ assert last_loader_msg() == ['Adding repository...']
+
+ assert not existing('editable_view').is_displayed()
+ assert existing('text').is_displayed()
+ assert existing('remove_but').is_displayed()
+
+ # Test editing item. Submit via 'Enter' hit.
+ existing('text').click()
+ assert not active('noneditable_view').is_displayed()
+ assert not active('add_but').is_displayed()
+ assert active('save_but').is_displayed()
+ assert active('cancel_but').is_displayed()
+ assert active('input.value') == 'https://example.com/'
+ active('input').send_keys(Keys.BACKSPACE * 30 + 'https://example.org//'
+ + Keys.ENTER)
+ WebDriverWait(driver, 10).until(lambda _: 'example.org/' in list_div.text)
+ assert execute_in_page('returnval(list.list_div.children.length);') == 1
+ assert last_loader_msg() == ['Replacing repository...']
+
+ # Test that clicking hidden buttons of item not being edited does nothing.
+ existing('add_but.click()')
+ existing('save_but.click()')
+ existing('cancel_but.click()')
+ assert execute_in_page('returnval(dialog_ctx.shown);') == False
+ assert execute_in_page('returnval(list.list_div.children.length);') == 1
+ assert not existing('editable_view').is_displayed()
+
+ # Test that clicking hidden buttons of item being edited does nothing.
+ existing('text').click()
+ active('remove_but.click()')
+ active('add_but.click()')
+ assert execute_in_page('returnval(dialog_ctx.shown);') == False
+ assert execute_in_page('returnval(list.list_div.children.length);') == 1
+ assert not active('noneditable_view').is_displayed()
+
+ # Test that creating a new entry makes the other one noneditable again.
+ new_entry_but.click()
+ assert existing('text').is_displayed()
+
+ # Test that clicking hidden buttons of new item entry does nothing.
+ active('remove_but.click()')
+ active('save_but.click()')
+ assert execute_in_page('returnval(dialog_ctx.shown);') == False
+ assert execute_in_page('returnval(list.list_div.children.length);') == 2
+ assert not active('noneditable_view').is_displayed()
+
+ # Test that starting edit of another entry removes the new entry.
+ existing('text').click()
+ assert existing('editable_view').is_displayed()
+ assert execute_in_page('returnval(list.list_div.children.length);') == 1
+
+ # Test that starting edit of another entry cancels edit of the other entry.
+ new_entry_but.click()
+ active('input').send_keys('https://example.net' + Keys.ENTER)
+ WebDriverWait(driver, 10).until(lambda _: 'example.net/' in list_div.text)
+ assert execute_in_page('returnval(list.list_div.children.length);') == 2
+ existing('text', 0).click()
+ assert existing('editable_view', 0).is_displayed()
+ assert not existing('editable_view', 1).is_displayed()
+ existing('text', 1).click()
+ assert not existing('editable_view', 0).is_displayed()
+ assert existing('editable_view', 1).is_displayed()
+
+ # Test entry removal.
+ existing('remove_but', 0).click()
+ WebDriverWait(driver, 10).until(lambda _: 'mple.net/' not in list_div.text)
+ assert execute_in_page('returnval(list.list_div.children.length);') == 1
+ assert last_loader_msg() == ['Removing repository...']
+
+@pytest.mark.ext_data(dialog_html_test_ext_data)
+@pytest.mark.usefixtures('webextension')
+def test_text_entry_list_errors(driver, execute_in_page):
+ """
+ A test case of error dialogs shown by repo URL list.
+ """
+ execute_in_page(load_script('html/text_entry_list.js'))
+
+ to_return = ['list.list_div', 'list.new_but', 'dialog_ctx.main_div']
+ list_div, new_entry_but, dialog_div = instantiate_list(to_return)
+
+ # Prepare one entry to use later.
+ new_entry_but.click()
+ active('input').send_keys('https://example.com' + Keys.ENTER)
+
+ # Check invalid URL errors.
+ for clickable in (existing('text'), new_entry_but):
+ clickable.click()
+ active('input').send_keys(Keys.BACKSPACE * 30 + 'ws://example'
+ + Keys.ENTER)
+ assert 'Repository URLs shoud use https:// schema.' in dialog_div.text
+ execute_in_page('dialog.close(dialog_ctx);')
+
+ active('input').send_keys(Keys.BACKSPACE * 30 + 'https://example'
+ + Keys.ENTER)
+ assert 'Provided URL is not valid.' in dialog_div.text
+ execute_in_page('dialog.close(dialog_ctx);')
+
+ # Mock errors to force error messages to appear.
+ execute_in_page(
+ '''
+ for (const action of ["set_repo", "del_repo"])
+ haketilodb[action] = () => {throw "reckless, limitless scope";};
+ ''')
+
+ # Check database error dialogs.
+ def check_reported_failure(what):
+ fail = lambda _: f'Failed to {what} repository :(' in dialog_div.text
+ WebDriverWait(driver, 10).until(fail)
+ execute_in_page('dialog.close(dialog_ctx);')
+
+ existing('text').click()
+ active('input').send_keys(Keys.BACKSPACE * 30 + 'https://example.org'
+ + Keys.ENTER)
+ check_reported_failure('replace')
+
+ active('cancel_but').click()
+ existing('remove_but').click()
+ check_reported_failure('remove')
+
+ new_entry_but.click()
+ active('input').send_keys('https://example.org' + Keys.ENTER)
+ check_reported_failure('add')