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