# 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); ''')