aboutsummaryrefslogtreecommitdiff
path: root/test/haketilo_test/unit/test_item_list.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/haketilo_test/unit/test_item_list.py')
-rw-r--r--test/haketilo_test/unit/test_item_list.py280
1 files changed, 280 insertions, 0 deletions
diff --git a/test/haketilo_test/unit/test_item_list.py b/test/haketilo_test/unit/test_item_list.py
new file mode 100644
index 0000000..35ed1d5
--- /dev/null
+++ b/test/haketilo_test/unit/test_item_list.py
@@ -0,0 +1,280 @@
+# SPDX-License-Identifier: CC0-1.0
+
+"""
+Haketilo unit tests - displaying list of resources/mappings
+"""
+
+# 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 ..extension_crafting import ExtraHTML
+from ..script_loader import load_script
+from .utils import *
+
+def make_sample_resource(identifier, long_name):
+ return {
+ 'source_name': 'hello',
+ 'source_copyright': [
+ sample_file_ref('report.spdx'),
+ sample_file_ref('LICENSES/CC0-1.0.txt')
+ ],
+ 'type': 'resource',
+ 'identifier': identifier,
+ 'long_name': long_name,
+ 'uuid': 'a6754dcb-58d8-4b7a-a245-24fd7ad4cd68',
+ 'version': [2021, 11, 10],
+ 'revision': 1,
+ 'description': 'greets an apple',
+ 'dependencies': [{'identifier': 'hello-message'}],
+ 'scripts': [
+ sample_file_ref('hello.js'),
+ sample_file_ref('bye.js')
+ ]
+ }
+
+def make_sample_mapping(identifier, long_name):
+ return {
+ 'source_name': 'example-org-fixes-new',
+ 'source_copyright': [
+ sample_file_ref('report.spdx'),
+ sample_file_ref('LICENSES/CC0-1.0.txt')
+ ],
+ 'type': 'mapping',
+ 'identifier': identifier,
+ 'long_name': long_name,
+ 'uuid': '54d23bba-472e-42f5-9194-eaa24c0e3ee7',
+ 'version': [2022, 5, 10],
+ 'description': 'suckless something something',
+ 'payloads': {
+ 'https://example.org/a/*': {
+ 'identifier': 'some-KISS-resource'
+ },
+ 'https://example.org/t/*': {
+ 'identifier': 'another-KISS-resource'
+ }
+ }
+ }
+
+def make_item(item_type, *args):
+ return make_sample_resource(*args) if item_type == 'resource' \
+ else make_sample_mapping(*args)
+
+@pytest.mark.ext_data({
+ 'background_script': broker_js,
+ 'extra_html': ExtraHTML('html/item_list.html', {}),
+ 'navigate_to': 'html/item_list.html'
+})
+@pytest.mark.usefixtures('webextension')
+@pytest.mark.parametrize('item_type', ['resource', 'mapping'])
+def test_item_list_ordering(driver, execute_in_page, item_type):
+ """
+ A test case of items list proper ordering.
+ """
+ execute_in_page(load_script('html/item_list.js'))
+
+ # Choose sample long names so as to test automatic sorting of items.
+ long_names = ['sample', 'sample it', 'Sample it', 'SAMPLE IT',
+ 'test', 'test it', 'Test it', 'TEST IT']
+ # Let's operate on a reverse-sorted copy
+ long_names_reversed = [*long_names]
+ long_names_reversed.reverse()
+
+ items = [make_item(item_type, f'it_{hex(2 * i + copy)[-1]}', name)
+ for i, name in enumerate(long_names_reversed)
+ for copy in (1, 0)]
+ # When adding/updating items this item will be updated at the end and this
+ # last update will be used to verify that a set of opertions completed.
+ extra_item = make_item(item_type, 'extraitem', 'extra item')
+
+ # After this reversal items are sorted in the exact order they are expected
+ # to appear in the HTML list.
+ items.reverse()
+
+ sample_data = {
+ 'resource': {},
+ 'mapping': {},
+ 'file': {
+ 'sha256': sample_files_by_sha256
+ }
+ }
+
+ indexes_added = set()
+ for iteration, to_include in enumerate([
+ set([i for i in range(len(items)) if is_prime(i)]),
+ set([i for i in range(len(items))
+ if not is_prime(i) and i & 1]),
+ set([i for i in range(len(items)) if i % 3 == 0]),
+ set([i for i in range(len(items))
+ if i % 3 and not i & 1 and not is_prime(i)]),
+ set(range(len(items)))
+ ]):
+ # On the last iteration, re-add ALL items but with changed names.
+ if len(to_include) == len(items):
+ for it in items:
+ it['long_name'] = f'somewhat renamed {it["long_name"]}'
+
+ items_to_inclue = [items[i] for i in sorted(to_include)]
+ sample_data[item_type] = sample_data_dict(items_to_inclue)
+ execute_in_page('returnval(haketilodb.save_items(arguments[0]));',
+ sample_data)
+
+ extra_item['long_name'] = f'{iteration} {extra_item["long_name"]}'
+ sample_data[item_type] = sample_data_dict([extra_item])
+ execute_in_page('returnval(haketilodb.save_items(arguments[0]));',
+ sample_data)
+
+ if iteration == 0:
+ execute_in_page(
+ f'''
+ let list_ctx;
+ async function create_list() {{
+ list_ctx = await {item_type}_list();
+ document.body.append(list_ctx.main_div);
+ }}
+ returnval(create_list());
+ ''')
+
+ def lis_ready(driver):
+ return extra_item['long_name'] == execute_in_page(
+ 'returnval(list_ctx.ul.firstElementChild.textContent);'
+ )
+
+ indexes_added.update(to_include)
+ WebDriverWait(driver, 10).until(lis_ready)
+
+ li_texts = execute_in_page(
+ '''
+ var lis = [...list_ctx.ul.children].slice(1);
+ returnval(lis.map(li => li.textContent));
+ ''')
+ assert li_texts == [items[i]['long_name'] for i in indexes_added]
+
+ preview_texts = execute_in_page(
+ '''{
+ const get_texts =
+ li => [li.click(), list_ctx.preview_container.textContent][1];
+ returnval(lis.map(get_texts));
+ }''')
+
+ for i, text in zip(sorted(indexes_added), preview_texts):
+ assert items[i]['identifier'] in text
+ assert items[i]['long_name'] in text
+
+@pytest.mark.ext_data({
+ 'background_script': broker_js,
+ 'extra_html': ExtraHTML('html/item_list.html', {}),
+ 'navigate_to': 'html/item_list.html'
+})
+@pytest.mark.usefixtures('webextension')
+@pytest.mark.parametrize('item_type', ['resource', 'mapping'])
+def test_item_list_displaying(driver, execute_in_page, item_type):
+ """
+ A test case of items list interaction with preview and dialog.
+ """
+ execute_in_page(load_script('html/item_list.js'))
+
+ items = [make_item(item_type, f'item{i}', f'Item {i}') for i in range(3)]
+
+ sample_data = {
+ 'resource': {},
+ 'mapping': {},
+ 'file': {
+ 'sha256': sample_files_by_sha256
+ }
+ }
+ sample_data[item_type] = sample_data_dict(items)
+
+ preview_container, dialog_container, ul = execute_in_page(
+ f'''
+ let list_ctx, sample_data = arguments[0];
+ async function create_list() {{
+ await haketilodb.save_items(sample_data);
+ list_ctx = await {item_type}_list();
+ document.body.append(list_ctx.main_div);
+ return [list_ctx.preview_container, list_ctx.dialog_container,
+ list_ctx.ul];
+ }}
+ returnval(create_list());
+ ''',
+ sample_data)
+
+ assert not preview_container.is_displayed()
+
+ # Check that preview is displayed correctly.
+ for i in range(3):
+ execute_in_page('list_ctx.ul.children[arguments[0]].click();', i)
+ assert preview_container.is_displayed()
+ text = preview_container.text
+ assert f'item{i}' in text
+ assert f'Item {i}' in text
+
+ # Check that item removal confirmation dialog is displayed correctly.
+ execute_in_page('list_ctx.remove_but.click();')
+ WebDriverWait(driver, 10).until(lambda _: dialog_container.is_displayed())
+ assert 'list_disabled' in ul.get_attribute('class')
+ assert not preview_container.is_displayed()
+ msg = execute_in_page('returnval(list_ctx.dialog_ctx.msg.textContent);')
+ assert msg == "Are you sure you want to delete 'item2'?"
+
+ # Check that previewing other item is impossible while dialog is open.
+ execute_in_page('list_ctx.ul.children[0].click();')
+ assert dialog_container.is_displayed()
+ assert 'list_disabled' in ul.get_attribute('class')
+ assert not preview_container.is_displayed()
+
+ # Check that queuing multiple removal confirmation dialogs is impossible.
+ execute_in_page('list_ctx.remove_but.click();')
+
+ # Check that answering "No" causes the item not to be removed and unhides
+ # item preview.
+ execute_in_page('list_ctx.dialog_ctx.no_but.click();')
+ WebDriverWait(driver, 10).until(lambda _: preview_container.is_displayed())
+ assert not dialog_container.is_displayed()
+ assert 'list_disabled' not in ul.get_attribute('class')
+ assert execute_in_page('returnval(list_ctx.ul.children.length);') == 3
+
+ # Check that item removal works properly.
+ def remove_current_item():
+ execute_in_page('list_ctx.remove_but.click();')
+ WebDriverWait(driver, 10)\
+ .until(lambda _: dialog_container.is_displayed())
+ execute_in_page('list_ctx.dialog_ctx.yes_but.click();')
+
+ remove_current_item()
+
+ def item_deleted(driver):
+ return execute_in_page('returnval(list_ctx.ul.children.length);') == 2
+ WebDriverWait(driver, 10).until(item_deleted)
+ assert not dialog_container.is_displayed()
+ assert not preview_container.is_displayed()
+ assert 'list_disabled' not in ul.get_attribute('class')
+
+ execute_in_page('list_ctx.ul.children[1].click();')
+
+ # Check that item removal failure causes the right error dialog to appear.
+ execute_in_page('haketilodb.finalize_transaction = () => {throw "sth";};')
+ remove_current_item()
+ WebDriverWait(driver, 10).until(lambda _: dialog_container.is_displayed())
+ msg = execute_in_page('returnval(list_ctx.dialog_ctx.msg.textContent);')
+ assert msg == "Couldn't remove 'item1' :("
+
+ # Destroy item list.
+ assert True == execute_in_page(
+ '''
+ const main_div = list_ctx.main_div;
+ destroy_list(list_ctx);
+ returnval(main_div.parentElement === null);
+ ''')