diff options
author | Wojtek Kosior <koszko@koszko.org> | 2022-02-16 22:01:38 +0100 |
---|---|---|
committer | Wojtek Kosior <koszko@koszko.org> | 2022-02-16 22:01:38 +0100 |
commit | fd9f2fc4783cc606734e61116185c032a63d54a0 (patch) | |
tree | ddc162b1df608c3ae51d74f19fbffc92e5cfc3e3 /test/haketilo_test/unit/test_text_entry_list.py | |
parent | 7965f1b455144220c137bcb25c4967283a6b7ff3 (diff) | |
download | browser-extension-fd9f2fc4783cc606734e61116185c032a63d54a0.tar.gz browser-extension-fd9f2fc4783cc606734e61116185c032a63d54a0.zip |
fix out-of-source builds
Diffstat (limited to 'test/haketilo_test/unit/test_text_entry_list.py')
-rw-r--r-- | test/haketilo_test/unit/test_text_entry_list.py | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/test/haketilo_test/unit/test_text_entry_list.py b/test/haketilo_test/unit/test_text_entry_list.py new file mode 100644 index 0000000..3135a59 --- /dev/null +++ b/test/haketilo_test/unit/test_text_entry_list.py @@ -0,0 +1,387 @@ +# 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 * + +list_code_template = '(await blocking_allowing_lists(%%s))[%d]' +mode_parameters = [ + #add_action del_action instantiate_code + ('set_repo', 'del_repo', 'await repo_list(%s)'), + ('set_disallowed', 'set_default_allowing', list_code_template % 0), + ('set_disallowed', 'set_allowed', list_code_template % 0), + ('set_allowed', 'set_default_allowing', list_code_template % 1), + ('set_allowed', 'set_disallowed', list_code_template % 1) +] + +def instantiate_list(to_return): + instantiate_code = inspect.stack()[1].frame.f_locals['instantiate_code'] + return inspect.stack()[1].frame.f_locals['execute_in_page']( + f''' + let dialog_ctx = dialog.make(() => {{}}, () => {{}}), list; + async function make_list() {{ + list = {instantiate_code % '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') +@pytest.mark.parametrize('mode', mode_parameters) +def test_text_entry_list_ordering(driver, execute_in_page, mode): + """ + A test case of ordering of repo URLs or URL patterns in the list. + """ + add_action, del_action, instantiate_code = mode + + 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, add_action) { + for (const url of urls) + await haketilodb[add_action](url); + } + returnval(add_urls(...arguments)); + }''', + urls, add_action) + + def wait_for_completed(wait_id): + """ + We add an extra 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[arguments[1]](arguments[0])); + ''', + url, add_action) + 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, del_action) { + for (const url of urls) + await haketilodb[del_action](url); + } + returnval(remove_urls(...arguments)); + }''', + urls, del_action) + 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') +@pytest.mark.parametrize('mode', [mp for mp in mode_parameters + if mp[1] != 'set_default_allowing']) +def test_text_entry_list_editing(driver, execute_in_page, mode): + """ + A test case of editing entries in repo URLs list. + """ + add_action, _, instantiate_code = mode + + 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']) + + if 'allow' in add_action: + assert last_loader_msg() == ['Loading script blocking settings...'] + else: + 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 + if 'disallow' in add_action: + assert last_loader_msg() == \ + ["Blocking scripts on 'https://example.com/'..."] + elif 'allow' in add_action: + assert last_loader_msg() == \ + ["Allowing scripts on 'https://example.com/'..."] + else: + assert last_loader_msg() == \ + ["Adding repository 'https://example.com/'..."] + + 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. Also test url pattern + # normalization. + 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//a//b' + + Keys.ENTER) + WebDriverWait(driver, 10).until(lambda _: 'example.org' in list_div.text) + assert execute_in_page('returnval(list.list_div.children.length);') == 1 + if 'disallow' in add_action: + assert last_loader_msg() == ['Rewriting script blocking rule...'] + elif 'allow' in add_action: + assert last_loader_msg() == ['Rewriting script allowing rule...'] + else: + assert last_loader_msg() == ['Replacing repository...'] + + # Test entry removal. + existing('remove_but').click() + WebDriverWait(driver, 10).until(lambda _: 'xample.org' not in list_div.text) + assert execute_in_page('returnval(list.list_div.children.length);') == 0 + if 'allow' in add_action: + assert last_loader_msg() == \ + ["Setting default scripts blocking policy on 'https://example.org/a/b'..."] + else: + assert last_loader_msg() == ["Removing repository 'https://example.org//a//b/'..."] + + # The rest of this test remains the same regardless of mode. No point + # testing the same thing multiple times. + if 'repo' not in add_action: + return + + # Test that clicking hidden buttons of item not being edited does nothing. + new_entry_but.click() + active('input').send_keys('https://example.foo' + Keys.ENTER) + WebDriverWait(driver, 10).until(lambda _: 'xample.foo/' in list_div.text) + 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 first 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() + +@pytest.mark.ext_data(dialog_html_test_ext_data) +@pytest.mark.usefixtures('webextension') +@pytest.mark.parametrize('mode', [mp for mp in mode_parameters + if mp[1] != 'set_default_allowing']) +def test_text_entry_list_errors(driver, execute_in_page, mode): + """ + A test case of error dialogs shown by repo URL list. + """ + add_action, _, instantiate_code = mode + + 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) + execute_in_page('dialog.close(dialog_ctx);') + + if 'allow' in add_action: + assert "'ws://example' is not a valid URL pattern. See here for more details." \ + in dialog_div.text + assert patterns_doc_url == \ + driver.find_element_by_link_text('here').get_attribute('href') + continue + else: + assert 'Repository URLs shoud use https:// schema.' \ + in dialog_div.text + + 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", "set_allowed", "set_default_allowing" + ]) + haketilodb[action] = () => {throw "reckless, limitless scope";}; + ''') + + # Check database error dialogs. + def check_reported_failure(txt): + fail = lambda _: txt 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) + if 'disallow' in add_action: + check_reported_failure('Failed to rewrite blocking rule :(') + elif 'allow' in add_action: + check_reported_failure('Failed to rewrite allowing rule :(') + else: + check_reported_failure('Failed to replace repository :(') + + active('cancel_but').click() + existing('remove_but').click() + if 'allow' in add_action: + check_reported_failure("Failed to remove rule for 'https://example.com' :(") + else: + check_reported_failure("Failed to remove repository 'https://example.com/' :(") + + new_entry_but.click() + active('input').send_keys('https://example.org' + Keys.ENTER) + if 'disallow' in add_action: + check_reported_failure("Failed to write blocking rule for 'https://example.org' :(") + elif 'allow' in add_action: + check_reported_failure("Failed to write allowing rule for 'https://example.org' :(") + else: + check_reported_failure("Failed to add repository 'https://example.org/' :(") |