From 19304cd1ae4e4ba4f6dcf4f1db14de1e4e70c250 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Mon, 10 Jan 2022 23:38:56 +0100 Subject: improve item list styling; add payload creation form; exend dialog mechanism --- test/unit/test_dialog.py | 45 +++++++++------ test/unit/test_indexeddb.py | 46 +-------------- test/unit/test_item_list.py | 42 +++++++------- test/unit/test_payload_create.py | 121 +++++++++++++++++++++++++++++++++++++++ test/unit/utils.py | 53 +++++++++++++++++ 5 files changed, 226 insertions(+), 81 deletions(-) create mode 100644 test/unit/test_payload_create.py (limited to 'test/unit') diff --git a/test/unit/test_dialog.py b/test/unit/test_dialog.py index 384a889..63af79e 100644 --- a/test/unit/test_dialog.py +++ b/test/unit/test_dialog.py @@ -29,31 +29,35 @@ from ..script_loader import load_script @pytest.mark.usefixtures('webextension') def test_dialog_show_close(driver, execute_in_page): """ - A test case of basing dialog showing/closing. + A test case of basic dialog showing/closing. """ execute_in_page(load_script('html/dialog.js')) - execute_in_page( + buts = execute_in_page( ''' let cb_calls, call_prom; const dialog_context = make(() => cb_calls.push("show"), () => cb_calls.push("hide")); document.body.append(dialog_context.main_div); + const buts = {}; + for (const but of document.getElementsByTagName("button")) + buts[but.textContent] = but; + returnval(buts); ''') - buts = driver.find_elements_by_tag_name('button') - buts = dict([(but.text, but) for but in buts]) - - for i, (dialog_function, button_text, expected_result) in enumerate([ - ('info', 'Ok', None), - ('error', 'Ok', None), - ('ask', 'Yes', True), - ('ask', 'No', False) + for i, (dialog_function, but_text, hidden, expected_result) in enumerate([ + ('info', 'Ok', ['Yes', 'No'], None), + ('error', 'Ok', ['Yes', 'No'], None), + ('error', None, ['Yes', 'No'], None), + ('loader', None, ['Yes', 'No', 'Ok'], None), + ('ask', 'Yes', ['Ok'], True), + ('ask', None, ['Ok'], None), + ('ask', 'No', ['Ok'], False) ]): cb_calls, is_shown = execute_in_page( f''' cb_calls = []; call_prom = {dialog_function}(dialog_context, - `sample_text_${{arguments[0]}}`); + `sample_text_${{arguments[0]}}`); returnval([cb_calls, dialog_context.shown]); ''', i) @@ -64,14 +68,23 @@ def test_dialog_show_close(driver, execute_in_page): assert f'sample_text_{i}' in page_source assert f'sample_text_{i - 1}' not in page_source - assert any([not but.is_displayed() for but in buts.values()]) - - assert buts[button_text].is_displayed() - buts[button_text].click() + # Verify the right buttons are displayed. + for text, but in buts.items(): + if text in hidden: + assert not but.is_displayed() + # Verify clicking a hidden button does nothing. + execute_in_page('buts[arguments[0]].click();', text) + assert execute_in_page('returnval(cb_calls);') == cb_calls + else: + assert but.is_displayed() + + if but_text is None: + execute_in_page('close_dialog(dialog_context);') + else: + buts[but_text].click() cb_calls, result, is_shown = execute_in_page( '''{ - console.error(dialog_context.msg.textContent); const values_cb = r => [cb_calls, r, dialog_context.shown]; returnval(call_prom.then(values_cb)); }''') diff --git a/test/unit/test_indexeddb.py b/test/unit/test_indexeddb.py index 9dfbe63..0c0e7a0 100644 --- a/test/unit/test_indexeddb.py +++ b/test/unit/test_indexeddb.py @@ -6,7 +6,7 @@ Haketilo unit tests - IndexedDB access # This file is part of Haketilo # -# Copyright (C) 2021, Wojtek Kosior +# Copyright (C) 2021,2022 Wojtek Kosior # # 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 @@ -25,7 +25,7 @@ from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import WebDriverException from ..script_loader import load_script -from .utils import sample_files, sample_files_by_hash, sample_file_ref +from .utils import * indexeddb_js = lambda: load_script('common/indexeddb.js') broker_js = lambda: load_script('background/broadcast_broker.js') + ';start();' @@ -54,48 +54,6 @@ def make_sample_mapping(): 'identifier': 'helloapple' } -def clear_indexeddb(execute_in_page): - execute_in_page( - '''{ - async function delete_db() { - if (db) { - db.close(); - db = null; - } - let resolve, reject; - const result = new Promise((...cbs) => [resolve, reject] = cbs); - const request = indexedDB.deleteDatabase("haketilo"); - [request.onsuccess, request.onerror] = [resolve, reject]; - await result; - } - - returnval(delete_db()); - }''' - ) - -def get_db_contents(execute_in_page): - # Facilitate retrieving all IndexedDB contents. - return execute_in_page( - '''{ - async function get_database_contents() - { - const db = await get_db(); - - const transaction = db.transaction(db.objectStoreNames); - const store_names_reqs = [...db.objectStoreNames] - .map(sn => [sn, transaction.objectStore(sn).getAll()]) - - const promises = store_names_reqs - .map(([_, req]) => wait_request(req)); - await Promise.all(promises); - - const result = {}; - store_names_reqs.forEach(([sn, req]) => result[sn] = req.result); - return result; - } - returnval(get_database_contents()); - }''') - def mock_broadcast(execute_in_page): execute_in_page( '''{ diff --git a/test/unit/test_item_list.py b/test/unit/test_item_list.py index e2e1af8..62ec84e 100644 --- a/test/unit/test_item_list.py +++ b/test/unit/test_item_list.py @@ -26,6 +26,27 @@ from .utils import * broker_js = lambda: load_script('background/broadcast_broker.js') + ';start();' +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': ['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', @@ -49,27 +70,6 @@ def make_sample_mapping(identifier, long_name): } } -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': ['hello-message'], - 'scripts': [ - sample_file_ref('hello.js'), - sample_file_ref('bye.js') - ] - } - def make_item(item_type, *args): return make_sample_resource(*args) if item_type == 'resource' \ else make_sample_mapping(*args) diff --git a/test/unit/test_payload_create.py b/test/unit/test_payload_create.py new file mode 100644 index 0000000..cd08d43 --- /dev/null +++ b/test/unit/test_payload_create.py @@ -0,0 +1,121 @@ +# SPDX-License-Identifier: CC0-1.0 + +""" +Haketilo unit tests - using a form to create simple site payload +""" + +# This file is part of Haketilo +# +# Copyright (C) 2022 Wojtek Kosior +# +# 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 +import re +from selenium.webdriver.support.ui import WebDriverWait + +from ..extension_crafting import ExtraHTML +from ..script_loader import load_script +from .utils import clear_indexeddb, get_db_contents, sample_files + +broker_js = lambda: load_script('background/broadcast_broker.js') + ';start();' + +uuidv4_re = re.compile( + r'^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$', + re.IGNORECASE +) + +sample_patterns = ''' +http://example.com/*** + +https://*.example.org/**''' + +sample_form_data = { + 'identifier': 'someid', + 'long_name': 'Some Name', + 'description': 'blah blah blah', + 'patterns': sample_patterns, + 'script': sample_files['hello.js']['contents'] +} + +def fill_form_with_sample_data(execute_in_page, sample_data_override={}, + form_ctx='form_ctx'): + form_data = sample_form_data.copy() + form_data.update(sample_data_override) + execute_in_page( + f''' + for (const [key, value] of Object.entries(arguments[0])) + {form_ctx}[key].value = value; + ''', + form_data) + +@pytest.mark.ext_data({ + 'background_script': broker_js, + 'extra_html': ExtraHTML('html/payload_create.html', {}), + 'navigate_to': 'html/payload_create.html' +}) +@pytest.mark.usefixtures('webextension') +def test_payload_create(driver, execute_in_page): + """ + A test case of creating a simple payload using a form. + """ + clear_indexeddb(execute_in_page) + execute_in_page(load_script('html/payload_create.js')) + + create_but, main_div = execute_in_page( + ''' + const form_ctx = payload_create_form(); + document.body.append(form_ctx.main_div); + returnval([form_ctx.create_but, form_ctx.main_div]); + ''') + + fill_form_with_sample_data(execute_in_page) + + create_but.click() + + def success_reported(driver): + return 'Successfully saved payload' in main_div.text + + WebDriverWait(driver, 10).until(success_reported) + + db_contents = get_db_contents(execute_in_page) + + assert uuidv4_re.match(db_contents['resources'][0]['uuid']) + assert db_contents['resources'] == [{ + 'source_name': 'local-someid', + 'source_copyright': [], + 'type': 'resource', + 'identifier': 'local-someid', + 'long_name': 'Some Name', + 'uuid': db_contents['resources'][0]['uuid'], + 'version': [1], + 'description': 'blah blah blah', + 'dependencies': [], + 'scripts': [{ + 'file': 'payload.js', + 'hash_key': sample_files['hello.js']['hash_key'] + }] + }] + + assert uuidv4_re.match(db_contents['mappings'][0]['uuid']) + assert db_contents['mappings'] == [{ + 'source_name': 'local-someid', + 'source_copyright': [], + 'type': 'mapping', + 'identifier': 'local-someid', + 'long_name': 'Some Name', + 'uuid': db_contents['mappings'][0]['uuid'], + 'version': [1], + 'description': 'blah blah blah', + 'payloads': { + 'http://example.com/***': {'identifier': 'local-someid'}, + 'https://*.example.org/**': {'identifier': 'local-someid'} + } + }] diff --git a/test/unit/utils.py b/test/unit/utils.py index b6b389f..a61e215 100644 --- a/test/unit/utils.py +++ b/test/unit/utils.py @@ -66,3 +66,56 @@ def sample_data_dict(items): """ return dict([(it['identifier'], {item_version_string(it): it}) for it in items]) + +def clear_indexeddb(execute_in_page): + """ + Remove Haketilo data from IndexedDB. If variables from common/indexeddb.js + are in the global scope, this function will handle closing the opened + database instance (if any). Otherwise, the caller is responsible for making + sure the database being deleted is not opened anywhere. + """ + execute_in_page( + '''{ + async function delete_db() { + if (typeof db !== "undefined" && db) { + db.close(); + db = null; + } + let resolve, reject; + const result = new Promise((...cbs) => [resolve, reject] = cbs); + const request = indexedDB.deleteDatabase("haketilo"); + [request.onsuccess, request.onerror] = [resolve, reject]; + await result; + } + + returnval(delete_db()); + }''' + ) + +def get_db_contents(execute_in_page): + """ + Retrieve all IndexedDB contents. It is expected that either variables from + common/indexeddb.js are in the global scope or common/indexeddb.js is + imported as haketilodb. + """ + return execute_in_page( + '''{ + async function get_database_contents() + { + const db_getter = + typeof haketilodb === "undefined" ? get_db : haketilodb.get; + const db = await db_getter(); + + const transaction = db.transaction(db.objectStoreNames); + const result = {}; + + for (const store_name of db.objectStoreNames) { + const req = transaction.objectStore(store_name).getAll(); + await new Promise(cb => req.onsuccess = cb); + result[store_name] = req.result; + } + + return result; + } + returnval(get_database_contents()); + }''') -- cgit v1.2.3