<commit_before>import {setFocus} from '../components/fef-flying-focus';
import {FefStorage} from '../classes/fef-storage';

export function init() {
    $('.js-search').each((i, elem) => {
        new SrfSearch(elem);
    });
}

const DEFAULT_MAX_SUGGESTIONS = 7;
const DEFAULT_MIN_SEARCH_LENGTH = 2;
const KEYCODES = {
    'enter': 13,
    'tab': 9,
    'escape': 27,
    'up': 38,
    'down': 40
};
const ACTIVE_CLASS = 'search--active';
const OUTSIDE_CLICK_LISTENER_NAME = 'click.search-destroyer';
const LOCAL_STORAGE_KEY = 'srf:search:history';

export class SrfSearch {

    constructor(element, options) {
        this.$element = $(element);
        this.$inputField = this.$element.find('.js-search-input');
        this.$searchResults = this.$element.find('.js-search-results');
        this.$closeIcon = this.$element.find('.js-search-close');

        this.options = $.extend({}, {
            maxSuggestionCount: DEFAULT_MAX_SUGGESTIONS,
            minSearchLength: DEFAULT_MIN_SEARCH_LENGTH
        }, options);

        this.typeaheadUrl = this.$element.attr('data-typeahead-url');
        this.clickedSuggestionsEnabled = this.$element.data('suggestion-history');
        this.typeaheadData = null;
        this.suggestionUrl = '';
        this.currTimeout = null;

        // search field is hidden before document.ready (events firing before document.ready can get lost)
        this.$inputField.addClass('search__input--loaded');
        this.registerListeners();
    }

    registerListeners() {
        // e.g. in the search modal, the whole element will be focused -> set focus to inputfield
        this.$element.on('focus', (e) => {
            setTimeout(() => {
                this.$inputField.focus();
            }, 0);
        });

        this.$inputField.on('focus', (e) => {

            this.setSearchActive();

            if (this.clickedSuggestionsEnabled
                && FefStorage.isLocalStorageAvailable()
                && FefStorage.hasItem(LOCAL_STORAGE_KEY)
                && this.$inputField.val() === '') {
                const storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                this.renderResults(storedResults, '');
            }
        }).on('keyup', (e) => {
            this.onKeyUp(e);
        }).on('keydown', (e) => {
            this.onKeyDown(e);
        }).on('click', (e) => {
            e.stopPropagation();
        });

        this.$closeIcon.on('click', (e) => {
            this.setSearchInactive();
        }).on('keydown', (e) => {
            this.onCloseIconKeyDown(e);
        }).on('click', e => {
            e.preventDefault();
            this.setSearchInactive();
        });

        this.$searchResults.on('keydown', (e) => {
            this.onResultsKeyDown(e);
        });
    }

    onKeyUp(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                break;
            case KEYCODES.escape:
                this.clearInput();
                this.$inputField.blur();
                this.setSearchInactive();
                break;
            default:
                let query = this.$inputField.val().toString().toLowerCase();
                this.lookup(query);
        }
    }

    onKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                if (e.shiftKey) {
                    // Shift-Tabbing out of the search component
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    onCloseIconKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                if (this.$searchResults.children().length === 0) {
                    this.setSearchInactive();
                }
                break;
            default:
                break;
        }
    }

    onResultsKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                // tabbing away from the last result
                if ($(e.target).parents('li').is(':last-child') && !e.shiftKey) {
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    setSearchActive() {
        if (this.$element.hasClass(ACTIVE_CLASS)) {
            return false;
        }

        this.$element.addClass(ACTIVE_CLASS);
        // Listen to clicks outside of the element --> deactivates this search component
        $(document).on(OUTSIDE_CLICK_LISTENER_NAME, (e) => {
            // Disable this search if the click was not a descendant of any .js-search or if it's a descendant of a different search component or on the search results page.
            if (!$(e.target).parents('.js-search').length && $(e.target).parents('.js-search') !== this.$element) {
                this.setSearchInactive();
            }
        });

        if (this.typeaheadData === null) {
            $.getJSON(this.typeaheadUrl, (data) => {
                this.typeaheadData = data;

                if (this.clickedSuggestionsEnabled
                    && FefStorage.isLocalStorageAvailable
                    && FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                    let items = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    items = items.filter((storedItem) => {
                        return this.typeaheadData.find((fetchedItem) => { return (fetchedItem.name === storedItem.name && fetchedItem.url === storedItem.url); });
                    });

                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, items);
                }
            });
        }

        this.$closeIcon.show();
    }

    setSearchInactive() {
        this.clearInput();
        this.hideResults();
        this.$element.removeClass(ACTIVE_CLASS);
        $(document).off(OUTSIDE_CLICK_LISTENER_NAME);
        this.$element.find('.search-result__alert').empty();
    }

    moveUpInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex - 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex < 0) {
            nextIndex = $suggestions.length - 1;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    moveDownInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex + 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex >= $suggestions.length) {
            nextIndex = 0;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    hideResults() {
        this.$inputField.attr('aria-expanded', false);
        this.$searchResults.hide().html('').removeClass('search__results--showed-results');
        this.suggestionUrl = '';
        this.$element.find('.search-result__alert').empty();
    }

    clearInput() {
        // if data-keep-keyword-on-blur is set, don't remove content
        if (this.$inputField.data('keep-keyword-on-blur') === undefined) {
            this.$inputField.val('');
        }
        this.$closeIcon.hide();
        this.suggestionUrl = '';
    }

    lookup(query) {
        let results = [];

        if (this.typeaheadData === null) {
            return true;
        }

        if (query.length < this.options.minSearchLength) {
            this.hideResults();
            return true;
        }

        this.typeaheadData.forEach((item) => {
            let matchIndex = item.name.toString().toLowerCase().indexOf(query);
            if (matchIndex >= 0) {
                results.push({
                    name: item.name,
                    url: item.url,
                    matchIndex: matchIndex
                });
            }
        });

        if (results.length > 0) {
            results = results.slice(0, this.options.maxSuggestionCount);
            this.renderResults(results, query);
        } else {
            this.hideResults();
        }
    }

    /**
     * Renders the received search results into the results list.
     * For the Screen-Reader's sake it'll be rendered twice, once with the found substring highlighted and once readable.
     *
     * If we're replacing previously shown search results, make sure they're not animated anymore.
     *
     * @param {Object} results
     * @param {String} query
     */
    renderResults(results, query) {
        let html = '';
        const wasAlreadyShowingResults = this.$searchResults.children('li').length > 0;
        const resultLength = results.length;

        this.$searchResults.html('');

        results.map((result, index) => {
            let highlightedResult = this.highlightQuery(query, result.name);
            const $li = $('<li>', { class: `typeahead-suggestion ${wasAlreadyShowingResults ? 'typeahead-suggestion--no-animation' : ''}`});
            const $link = $('<a>', { class: 'search-result__link', href: result.url, 'data-referrer-track' : 'srg_mod1=search_suggestions&srg_mod2=search_result&srg_mod3=' + query + '&srg_mod4=' + resultLength + '-' + (index + 1)});

            $link.on('click', () => {

                this.$inputField.val('');

                if (this.clickedSuggestionsEnabled && FefStorage.isLocalStorageAvailable()) {
                    let storedResults = [];
                    if (FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                        storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    }

                    storedResults = storedResults.filter((item) => {
                        return (item.name !== result.name && item.url !== result.url);
                    });

                    storedResults.unshift(result);
                    storedResults = storedResults.slice(0, this.options.maxSuggestionCount);
                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, storedResults);
                }

            });

            $('<span>', { role: 'presentation', 'aria-hidden': true }).append(highlightedResult).appendTo($link);
            $('<span>', { class: 'h-offscreen' }).text(result.name).appendTo($link);

            $li.append($link);
            $li.appendTo(this.$searchResults);
        });

        this.$searchResults.show();

        // additional screenreader info (same behavior like meteo search field)
        let $result_alert = $('.search-result__alert');
        let result_html = `${results.length} ${this.$inputField.data('result-alert-text')} <span>${query}</span>`;

        if ($result_alert.length === 0) {
            this.$searchResults.before(`<span class="h-offscreen search-result__alert" role="alert">${result_html}</span>`);
        } else {
            $result_alert.html(result_html);
        }

        this.$inputField.attr('aria-expanded', true);
    }

    /**
     * Replaces the query string in a search result with the same string in bold  e.g.
     * "Tag" in "Tagesschau" after searching for "Tag"
     *
     * @param {String} query
     * @param {String} name
     * @returns {String}
     */
    highlightQuery(query, name) {
        query = query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
        return name.replace(new RegExp('(' + query + ')', 'ig'), ($1, match) => `<strong>${match}</strong>`);
    }
}
<commit_msg>decouple input clearing from setting input inactive<commit_after>import {setFocus} from '../components/fef-flying-focus';
import {FefStorage} from '../classes/fef-storage';

export function init() {
    $('.js-search').each((i, elem) => {
        new SrfSearch(elem);
    });
}

const DEFAULT_MAX_SUGGESTIONS = 7;
const DEFAULT_MIN_SEARCH_LENGTH = 2;
const KEYCODES = {
    'enter': 13,
    'tab': 9,
    'escape': 27,
    'up': 38,
    'down': 40
};
const ACTIVE_CLASS = 'search--active';
const OUTSIDE_CLICK_LISTENER_NAME = 'click.search-destroyer';
const LOCAL_STORAGE_KEY = 'srf:search:history';

export class SrfSearch {

    constructor(element, options) {
        this.$element = $(element);
        this.$inputField = this.$element.find('.js-search-input');
        this.$searchResults = this.$element.find('.js-search-results');
        this.$closeIcon = this.$element.find('.js-search-close');

        this.options = $.extend({}, {
            maxSuggestionCount: DEFAULT_MAX_SUGGESTIONS,
            minSearchLength: DEFAULT_MIN_SEARCH_LENGTH
        }, options);

        this.typeaheadUrl = this.$element.attr('data-typeahead-url');
        this.clickedSuggestionsEnabled = this.$element.data('suggestion-history');
        this.typeaheadData = null;
        this.suggestionUrl = '';
        this.currTimeout = null;

        // search field is hidden before document.ready (events firing before document.ready can get lost)
        this.$inputField.addClass('search__input--loaded');
        this.registerListeners();
    }

    registerListeners() {
        // e.g. in the search modal, the whole element will be focused -> set focus to inputfield
        this.$element.on('focus', (e) => {
            setTimeout(() => {
                this.$inputField.focus();
            }, 0);
        });

        this.$inputField.on('focus', (e) => {
            this.setSearchActive();

            if (this.clickedSuggestionsEnabled
                && FefStorage.isLocalStorageAvailable()
                && FefStorage.hasItem(LOCAL_STORAGE_KEY)
                && this.$inputField.val() === '') {
                const storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                this.renderResults(storedResults, '');
            }
        }).on('keyup', (e) => {
            this.onKeyUp(e);
        }).on('keydown', (e) => {
            this.onKeyDown(e);
        }).on('click', (e) => {
            e.stopPropagation();
        });

        this.$closeIcon.on('click', (e) => {
            e.preventDefault();
            this.setSearchInactive();
            this.clearInput();
        }).on('keydown', (e) => {
            this.onCloseIconKeyDown(e);
        });

        this.$searchResults.on('keydown', (e) => {
            this.onResultsKeyDown(e);
        });
    }

    onKeyUp(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                break;
            case KEYCODES.escape:
                this.$inputField.blur();
                this.setSearchInactive();
                this.clearInput();
                break;
            default:
                let query = this.$inputField.val().toString().toLowerCase();
                this.lookup(query);
        }
    }

    onKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                if (e.shiftKey) {
                    // Shift-Tabbing out of the search component
                    this.setSearchInactive();
                    this.clearInputIfAllowed();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    onCloseIconKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                this.clearInput();
                break;
            case KEYCODES.tab:
                if (this.$searchResults.children().length === 0) {
                    this.setSearchInactive();
                    this.clearInputIfAllowed();
                }
                break;
            default:
                break;
        }
    }

    onResultsKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                this.clearInputIfAllowed();
                break;
            case KEYCODES.tab:
                // tabbing away from the last result
                if ($(e.target).parents('li').is(':last-child') && !e.shiftKey) {
                    this.setSearchInactive();
                    this.clearInputIfAllowed();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    setSearchActive() {
        if (this.$element.hasClass(ACTIVE_CLASS)) {
            return false;
        }

        this.$element.addClass(ACTIVE_CLASS);
        // Listen to clicks outside of the element --> deactivates this search component
        $(document).on(OUTSIDE_CLICK_LISTENER_NAME, (e) => {
            // Disable this search if the click was not a descendant of any .js-search or if it's a descendant of a different search component or on the search results page.
            if (!$(e.target).parents('.js-search').length && $(e.target).parents('.js-search') !== this.$element) {
                this.setSearchInactive();
            }
        });

        if (this.typeaheadData === null) {
            $.getJSON(this.typeaheadUrl, (data) => {
                this.typeaheadData = data;

                if (this.clickedSuggestionsEnabled
                    && FefStorage.isLocalStorageAvailable
                    && FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                    let items = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    items = items.filter((storedItem) => {
                        return this.typeaheadData.find((fetchedItem) => { return (fetchedItem.name === storedItem.name && fetchedItem.url === storedItem.url); });
                    });

                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, items);
                }
            });
        }

        this.$closeIcon.show();
    }

    setSearchInactive() {
        this.hideResults();
        this.$element.removeClass(ACTIVE_CLASS);
        $(document).off(OUTSIDE_CLICK_LISTENER_NAME);
        this.$element.find('.search-result__alert').empty();
    }

    moveUpInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex - 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex < 0) {
            nextIndex = $suggestions.length - 1;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    moveDownInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex + 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex >= $suggestions.length) {
            nextIndex = 0;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    hideResults() {
        this.$inputField.attr('aria-expanded', false);
        this.$searchResults.hide().html('').removeClass('search__results--showed-results');
        this.suggestionUrl = '';
        this.$element.find('.search-result__alert').empty();
    }

    clearInput() {
        this.$inputField.val('');
        this.$closeIcon.hide();
        this.suggestionUrl = '';
    }

    clearInputIfAllowed() {
        // clear input if data-keep-keyword-on-blur was not set
        if (this.$inputField.data('keep-keyword-on-blur') === undefined) {
            this.clearInput();
        }
    }

    lookup(query) {
        let results = [];

        if (this.typeaheadData === null) {
            return true;
        }

        if (query.length < this.options.minSearchLength) {
            this.hideResults();
            return true;
        }

        this.typeaheadData.forEach((item) => {
            let matchIndex = item.name.toString().toLowerCase().indexOf(query);
            if (matchIndex >= 0) {
                results.push({
                    name: item.name,
                    url: item.url,
                    matchIndex: matchIndex
                });
            }
        });

        if (results.length > 0) {
            results = results.slice(0, this.options.maxSuggestionCount);
            this.renderResults(results, query);
        } else {
            this.hideResults();
        }
    }

    /**
     * Renders the received search results into the results list.
     * For the Screen-Reader's sake it'll be rendered twice, once with the found substring highlighted and once readable.
     *
     * If we're replacing previously shown search results, make sure they're not animated anymore.
     *
     * @param {Object} results
     * @param {String} query
     */
    renderResults(results, query) {
        let html = '';
        const wasAlreadyShowingResults = this.$searchResults.children('li').length > 0;
        const resultLength = results.length;

        this.$searchResults.html('');

        results.map((result, index) => {
            let highlightedResult = this.highlightQuery(query, result.name);
            const $li = $('<li>', { class: `typeahead-suggestion ${wasAlreadyShowingResults ? 'typeahead-suggestion--no-animation' : ''}`});
            const $link = $('<a>', { class: 'search-result__link', href: result.url, 'data-referrer-track' : 'srg_mod1=search_suggestions&srg_mod2=search_result&srg_mod3=' + query + '&srg_mod4=' + resultLength + '-' + (index + 1)});

            $link.on('click', () => {

                this.$inputField.val('');

                if (this.clickedSuggestionsEnabled && FefStorage.isLocalStorageAvailable()) {
                    let storedResults = [];
                    if (FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                        storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    }

                    storedResults = storedResults.filter((item) => {
                        return (item.name !== result.name && item.url !== result.url);
                    });

                    storedResults.unshift(result);
                    storedResults = storedResults.slice(0, this.options.maxSuggestionCount);
                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, storedResults);
                }

            });

            $('<span>', { role: 'presentation', 'aria-hidden': true }).append(highlightedResult).appendTo($link);
            $('<span>', { class: 'h-offscreen' }).text(result.name).appendTo($link);

            $li.append($link);
            $li.appendTo(this.$searchResults);
        });

        this.$searchResults.show();

        // additional screenreader info (same behavior like meteo search field)
        let $result_alert = $('.search-result__alert');
        let result_html = `${results.length} ${this.$inputField.data('result-alert-text')} <span>${query}</span>`;

        if ($result_alert.length === 0) {
            this.$searchResults.before(`<span class="h-offscreen search-result__alert" role="alert">${result_html}</span>`);
        } else {
            $result_alert.html(result_html);
        }

        this.$inputField.attr('aria-expanded', true);
    }

    /**
     * Replaces the query string in a search result with the same string in bold  e.g.
     * "Tag" in "Tagesschau" after searching for "Tag"
     *
     * @param {String} query
     * @param {String} name
     * @returns {String}
     */
    highlightQuery(query, name) {
        query = query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
        return name.replace(new RegExp('(' + query + ')', 'ig'), ($1, match) => `<strong>${match}</strong>`);
    }
}
<|endoftext|>


<commit_before>import {setFocus} from '../components/fef-flying-focus';
import {FefStorage} from '../classes/fef-storage';

export function init() {
    $('.js-search').each((i, elem) => {
        new SrfSearch(elem);
    });
}

const DEFAULT_MAX_SUGGESTIONS = 7;
const DEFAULT_MIN_SEARCH_LENGTH = 2;
const KEYCODES = {
    'enter': 13,
    'tab': 9,
    'escape': 27,
    'up': 38,
    'down': 40
};
const ACTIVE_CLASS = 'search--active';
const OUTSIDE_CLICK_LISTENER_NAME = 'click.search-destroyer';
const LOCAL_STORAGE_KEY = 'srf:search:history';

export class SrfSearch {

    constructor(element, options) {
        this.$element = $(element);
        this.$inputField = this.$element.find('.js-search-input');
        this.$searchResults = this.$element.find('.js-search-results');
        this.$closeIcon = this.$element.find('.js-search-close');

        this.options = $.extend({}, {
            maxSuggestionCount: DEFAULT_MAX_SUGGESTIONS,
            minSearchLength: DEFAULT_MIN_SEARCH_LENGTH
        }, options);

        this.typeaheadUrl = this.$element.attr('data-typeahead-url');
        this.clickedSuggestionsEnabled = this.$element.data('suggestion-history');
        this.typeaheadData = null;
        this.suggestionUrl = '';
        this.currTimeout = null;

        // search field is hidden before document.ready (events firing before document.ready can get lost)
        this.$inputField.addClass('search__input--loaded');
        this.registerListeners();
    }

    registerListeners() {
        // e.g. in the search modal, the whole element will be focused -> set focus to inputfield
        this.$element.on('focus', (e) => {
            setTimeout(() => {
                this.$inputField.focus();
            }, 0);
        });

        this.$inputField.on('focus', (e) => {

            this.setSearchActive();

            if (this.clickedSuggestionsEnabled
                && FefStorage.isLocalStorageAvailable()
                && FefStorage.hasItem(LOCAL_STORAGE_KEY)
                && this.$inputField.val() === '') {
                const storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                this.renderResults(storedResults, '');
            }
        }).on('keyup', (e) => {
            this.onKeyUp(e);
        }).on('keydown', (e) => {
            this.onKeyDown(e);
        }).on('click', (e) => {
            e.stopPropagation();
        });

        this.$closeIcon.on('click', (e) => {
            this.setSearchInactive();
        }).on('keydown', (e) => {
            this.onCloseIconKeyDown(e);
        }).on('click', e => {
            e.preventDefault();
            this.setSearchInactive();
        });

        this.$searchResults.on('keydown', (e) => {
            this.onResultsKeyDown(e);
        });

        this.$element.on('submit', (e) => {
            let mod_obj = {};
            this.$inputField.data('referrer-track').split('&').forEach((item, index) => {
                let parts = item.split('=');
                mod_obj[parts[0]] = parts[1];
            });
            mod_obj['srg_mod3'] = this.$inputField.val(); // keyword
            mod_obj['srg_mod4'] = this.$searchResults.find('li').length; // typeahead length

            // TODO: the following has changed on test :/
            FefStorage.setItem('srf_analytics_mod', JSON.stringify(mod_obj)); // => srf:analytics:mod
            FefStorage.setItem('srf:analytics:mod', JSON.stringify(mod_obj));
            FefStorage.setItem('srf_analytics_mod_app', JSON.stringify(mod_obj)); // => srf:analytics:mod:app
            FefStorage.setItem('srf:analytics:mod:app', JSON.stringify(mod_obj));
        });
    }

    onKeyUp(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                break;
            case KEYCODES.escape:
                this.clearInput();
                this.$inputField.blur();
                this.setSearchInactive();
                break;
            default:
                let query = this.$inputField.val().toString().toLowerCase();
                this.lookup(query);
        }
    }

    onKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                if (e.shiftKey) {
                    // Shift-Tabbing out of the search component
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    onCloseIconKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                if (this.$searchResults.children().length === 0) {
                    this.setSearchInactive();
                }
                break;
            default:
                break;
        }
    }

    onResultsKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                // tabbing away from the last result
                if ($(e.target).parents('li').is(':last-child') && !e.shiftKey) {
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    setSearchActive() {
        if (this.$element.hasClass(ACTIVE_CLASS)) {
            return false;
        }

        this.$element.addClass(ACTIVE_CLASS);
        // Listen to clicks outside of the element --> deactivates this search component
        $(document).on(OUTSIDE_CLICK_LISTENER_NAME, (e) => {
            // Disable this search if the click was not a descendant of any .js-search or if it's a descendant of a different search component or on the search results page.
            if (!$(e.target).parents('.js-search').length && $(e.target).parents('.js-search') !== this.$element && !this.$element.is('.search--resultpage')) {
                this.setSearchInactive();
            }
        });

        if (this.typeaheadData === null) {
            $.getJSON(this.typeaheadUrl, (data) => {
                this.typeaheadData = data;

                if (this.clickedSuggestionsEnabled
                    && FefStorage.isLocalStorageAvailable
                    && FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                    let items = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    items = items.filter((storedItem) => {
                        return this.typeaheadData.find((fetchedItem) => { return (fetchedItem.name === storedItem.name && fetchedItem.url === storedItem.url); });
                    });

                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, items);
                }
            });
        }

        this.$closeIcon.show();
    }

    setSearchInactive() {
        this.clearInput();
        this.hideResults();
        this.$element.removeClass(ACTIVE_CLASS);
        $(document).off(OUTSIDE_CLICK_LISTENER_NAME);
        this.$element.find('.search-result__alert').empty();
    }

    moveUpInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex - 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex < 0) {
            nextIndex = $suggestions.length - 1;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    moveDownInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex + 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex >= $suggestions.length) {
            nextIndex = 0;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    hideResults() {
        this.$inputField.attr('aria-expanded', false);
        this.$searchResults.hide().html('').removeClass('search__results--showed-results');
        this.suggestionUrl = '';
        this.$element.find('.search-result__alert').empty();
    }

    clearInput() {
        this.$inputField.val('');
        this.$closeIcon.hide();
        this.suggestionUrl = '';
    }

    lookup(query) {
        let results = [];

        if (this.typeaheadData === null) {
            return true;
        }

        if (query.length < this.options.minSearchLength) {
            this.hideResults();
            return true;
        }

        this.typeaheadData.forEach((item) => {
            let matchIndex = item.name.toString().toLowerCase().indexOf(query);
            if (matchIndex >= 0) {
                results.push({
                    name: item.name,
                    url: item.url,
                    matchIndex: matchIndex
                });
            }
        });

        if (results.length > 0) {
            results = results.slice(0, this.options.maxSuggestionCount);
            this.renderResults(results, query);
        } else {
            this.hideResults();
        }
    }

    /**
     * Renders the received search results into the results list.
     * For the Screen-Reader's sake it'll be rendered twice, once with the found substring highlighted and once readable.
     *
     * If we're replacing previously shown search results, make sure they're not animated anymore.
     *
     * @param {Object} results
     * @param {String} query
     */
    renderResults(results, query) {
        let html = '';
        const wasAlreadyShowingResults = this.$searchResults.children('li').length > 0;

        this.$searchResults.html('');

        results.map((result) => {
            let highlightedResult = this.highlightQuery(query, result.name);
            const $li = $('<li>', { class: `typeahead-suggestion ${wasAlreadyShowingResults ? 'typeahead-suggestion--no-animation' : ''}`});
            const $link = $('<a>', { class: 'search-result__link', href: result.url });
            $link.on('click', () => {

                this.$inputField.val('');

                if (this.clickedSuggestionsEnabled && FefStorage.isLocalStorageAvailable()) {
                    let storedResults = [];
                    if (FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                        storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    }

                    storedResults = storedResults.filter((item) => {
                        return (item.name !== result.name && item.url !== result.url);
                    });

                    storedResults.unshift(result);
                    storedResults = storedResults.slice(0, this.options.maxSuggestionCount);
                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, storedResults);
                }

            });

            $('<span>', { role: 'presentation', 'aria-hidden': true }).append(highlightedResult).appendTo($link);
            $('<span>', { class: 'h-offscreen' }).text(result.name).appendTo($link);

            $li.append($link);
            $li.appendTo(this.$searchResults);
        });

        this.$searchResults.show();

        // additional screenreader info (same behavior like meteo search field)
        let $result_alert = $('.search-result__alert');
        let result_html = `${results.length} ${this.$inputField.data('result-alert-text')} <span>${query}</span>`;

        if ($result_alert.length === 0) {
            this.$searchResults.before(`<span class="h-offscreen search-result__alert" role="alert">${result_html}</span>`);
        } else {
            $result_alert.html(result_html);
        }

        this.$inputField.attr('aria-expanded', true);
    }

    /**
     * Replaces the query string in a search result with the same string in bold  e.g.
     * "Tag" in "Tagesschau" after searching for "Tag"
     *
     * @param {String} query
     * @param {String} name
     * @returns {String}
     */
    highlightQuery(query, name) {
        query = query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
        return name.replace(new RegExp('(' + query + ')', 'ig'), ($1, match) => `<strong>${match}</strong>`);
    }
}
<commit_msg>considering the custom event for the result page input field<commit_after>import {setFocus} from '../components/fef-flying-focus';
import {FefStorage} from '../classes/fef-storage';

export function init() {
    $('.js-search').each((i, elem) => {
        new SrfSearch(elem);
    });
}

const DEFAULT_MAX_SUGGESTIONS = 7;
const DEFAULT_MIN_SEARCH_LENGTH = 2;
const KEYCODES = {
    'enter': 13,
    'tab': 9,
    'escape': 27,
    'up': 38,
    'down': 40
};
const ACTIVE_CLASS = 'search--active';
const OUTSIDE_CLICK_LISTENER_NAME = 'click.search-destroyer';
const LOCAL_STORAGE_KEY = 'srf:search:history';

export class SrfSearch {

    constructor(element, options) {
        this.$element = $(element);
        this.$inputField = this.$element.find('.js-search-input');
        this.$searchResults = this.$element.find('.js-search-results');
        this.$closeIcon = this.$element.find('.js-search-close');

        this.options = $.extend({}, {
            maxSuggestionCount: DEFAULT_MAX_SUGGESTIONS,
            minSearchLength: DEFAULT_MIN_SEARCH_LENGTH
        }, options);

        this.typeaheadUrl = this.$element.attr('data-typeahead-url');
        this.clickedSuggestionsEnabled = this.$element.data('suggestion-history');
        this.typeaheadData = null;
        this.suggestionUrl = '';
        this.currTimeout = null;

        // search field is hidden before document.ready (events firing before document.ready can get lost)
        this.$inputField.addClass('search__input--loaded');
        this.registerListeners();
    }

    registerListeners() {
        // e.g. in the search modal, the whole element will be focused -> set focus to inputfield
        this.$element.on('focus', (e) => {
            setTimeout(() => {
                this.$inputField.focus();
            }, 0);
        });

        this.$inputField.on('focus', (e) => {

            this.setSearchActive();

            if (this.clickedSuggestionsEnabled
                && FefStorage.isLocalStorageAvailable()
                && FefStorage.hasItem(LOCAL_STORAGE_KEY)
                && this.$inputField.val() === '') {
                const storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                this.renderResults(storedResults, '');
            }
        }).on('keyup', (e) => {
            this.onKeyUp(e);
        }).on('keydown', (e) => {
            this.onKeyDown(e);
        }).on('click', (e) => {
            e.stopPropagation();
        });

        this.$closeIcon.on('click', (e) => {
            this.setSearchInactive();
        }).on('keydown', (e) => {
            this.onCloseIconKeyDown(e);
        }).on('click', e => {
            e.preventDefault();
            this.setSearchInactive();
        });

        this.$searchResults.on('keydown', (e) => {
            this.onResultsKeyDown(e);
        });

        this.$element.on('submit', (e) => {
            let mod_obj = {};
            this.$inputField.data('referrer-track').split('&').forEach((item, index) => {
                let parts = item.split('=');
                mod_obj[parts[0]] = parts[1];
            });

            // field on result page uses a custom event for the same:
            if (mod_obj['srg_mod1'] !== 'search_result') { //
                mod_obj['srg_mod3'] = this.$inputField.val(); // keyword
                mod_obj['srg_mod4'] = this.$searchResults.find('li').length; // typeahead length

                // TODO: the following has changed on test :/
                FefStorage.setItem('srf_analytics_mod', JSON.stringify(mod_obj)); // => srf:analytics:mod
                FefStorage.setItem('srf:analytics:mod', JSON.stringify(mod_obj));
                FefStorage.setItem('srf_analytics_mod_app', JSON.stringify(mod_obj)); // => srf:analytics:mod:app
                FefStorage.setItem('srf:analytics:mod:app', JSON.stringify(mod_obj));
            }
        });
    }

    onKeyUp(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                break;
            case KEYCODES.escape:
                this.clearInput();
                this.$inputField.blur();
                this.setSearchInactive();
                break;
            default:
                let query = this.$inputField.val().toString().toLowerCase();
                this.lookup(query);
        }
    }

    onKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.tab:
                if (e.shiftKey) {
                    // Shift-Tabbing out of the search component
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    onCloseIconKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                if (this.$searchResults.children().length === 0) {
                    this.setSearchInactive();
                }
                break;
            default:
                break;
        }
    }

    onResultsKeyDown(e) {
        switch (e.keyCode) {
            case KEYCODES.escape:
                this.setSearchInactive();
                break;
            case KEYCODES.tab:
                // tabbing away from the last result
                if ($(e.target).parents('li').is(':last-child') && !e.shiftKey) {
                    this.setSearchInactive();
                }
                break;
            case KEYCODES.up:
                this.moveUpInMenu();
                e.preventDefault();
                break;
            case KEYCODES.down:
                this.moveDownInMenu();
                e.preventDefault();
                break;
            default:
                break;
        }
    }

    setSearchActive() {
        if (this.$element.hasClass(ACTIVE_CLASS)) {
            return false;
        }

        this.$element.addClass(ACTIVE_CLASS);
        // Listen to clicks outside of the element --> deactivates this search component
        $(document).on(OUTSIDE_CLICK_LISTENER_NAME, (e) => {
            // Disable this search if the click was not a descendant of any .js-search or if it's a descendant of a different search component or on the search results page.
            if (!$(e.target).parents('.js-search').length && $(e.target).parents('.js-search') !== this.$element && !this.$element.is('.search--resultpage')) {
                this.setSearchInactive();
            }
        });

        if (this.typeaheadData === null) {
            $.getJSON(this.typeaheadUrl, (data) => {
                this.typeaheadData = data;

                if (this.clickedSuggestionsEnabled
                    && FefStorage.isLocalStorageAvailable
                    && FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                    let items = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    items = items.filter((storedItem) => {
                        return this.typeaheadData.find((fetchedItem) => { return (fetchedItem.name === storedItem.name && fetchedItem.url === storedItem.url); });
                    });

                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, items);
                }
            });
        }

        this.$closeIcon.show();
    }

    setSearchInactive() {
        this.clearInput();
        this.hideResults();
        this.$element.removeClass(ACTIVE_CLASS);
        $(document).off(OUTSIDE_CLICK_LISTENER_NAME);
        this.$element.find('.search-result__alert').empty();
    }

    moveUpInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex - 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex < 0) {
            nextIndex = $suggestions.length - 1;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    moveDownInMenu() {
        let $suggestions = this.$searchResults.find('.search-result__link'),
            $currentlyFocused = $('.search-result__link:focus'),
            currentIndex = $suggestions.index($currentlyFocused),
            nextIndex = currentIndex + 1;

        if ($suggestions.length === 0) {
            return;
        }

        if (nextIndex >= $suggestions.length) {
            nextIndex = 0;
        }

        setFocus($suggestions.eq(nextIndex));
    }

    hideResults() {
        this.$inputField.attr('aria-expanded', false);
        this.$searchResults.hide().html('').removeClass('search__results--showed-results');
        this.suggestionUrl = '';
        this.$element.find('.search-result__alert').empty();
    }

    clearInput() {
        this.$inputField.val('');
        this.$closeIcon.hide();
        this.suggestionUrl = '';
    }

    lookup(query) {
        let results = [];

        if (this.typeaheadData === null) {
            return true;
        }

        if (query.length < this.options.minSearchLength) {
            this.hideResults();
            return true;
        }

        this.typeaheadData.forEach((item) => {
            let matchIndex = item.name.toString().toLowerCase().indexOf(query);
            if (matchIndex >= 0) {
                results.push({
                    name: item.name,
                    url: item.url,
                    matchIndex: matchIndex
                });
            }
        });

        if (results.length > 0) {
            results = results.slice(0, this.options.maxSuggestionCount);
            this.renderResults(results, query);
        } else {
            this.hideResults();
        }
    }

    /**
     * Renders the received search results into the results list.
     * For the Screen-Reader's sake it'll be rendered twice, once with the found substring highlighted and once readable.
     *
     * If we're replacing previously shown search results, make sure they're not animated anymore.
     *
     * @param {Object} results
     * @param {String} query
     */
    renderResults(results, query) {
        let html = '';
        const wasAlreadyShowingResults = this.$searchResults.children('li').length > 0;

        this.$searchResults.html('');

        results.map((result) => {
            let highlightedResult = this.highlightQuery(query, result.name);
            const $li = $('<li>', { class: `typeahead-suggestion ${wasAlreadyShowingResults ? 'typeahead-suggestion--no-animation' : ''}`});
            const $link = $('<a>', { class: 'search-result__link', href: result.url });
            $link.on('click', () => {

                this.$inputField.val('');

                if (this.clickedSuggestionsEnabled && FefStorage.isLocalStorageAvailable()) {
                    let storedResults = [];
                    if (FefStorage.hasItem(LOCAL_STORAGE_KEY)) {
                        storedResults = FefStorage.getItemJsonParsed(LOCAL_STORAGE_KEY);
                    }

                    storedResults = storedResults.filter((item) => {
                        return (item.name !== result.name && item.url !== result.url);
                    });

                    storedResults.unshift(result);
                    storedResults = storedResults.slice(0, this.options.maxSuggestionCount);
                    FefStorage.setItemJsonStringified(LOCAL_STORAGE_KEY, storedResults);
                }

            });

            $('<span>', { role: 'presentation', 'aria-hidden': true }).append(highlightedResult).appendTo($link);
            $('<span>', { class: 'h-offscreen' }).text(result.name).appendTo($link);

            $li.append($link);
            $li.appendTo(this.$searchResults);
        });

        this.$searchResults.show();

        // additional screenreader info (same behavior like meteo search field)
        let $result_alert = $('.search-result__alert');
        let result_html = `${results.length} ${this.$inputField.data('result-alert-text')} <span>${query}</span>`;

        if ($result_alert.length === 0) {
            this.$searchResults.before(`<span class="h-offscreen search-result__alert" role="alert">${result_html}</span>`);
        } else {
            $result_alert.html(result_html);
        }

        this.$inputField.attr('aria-expanded', true);
    }

    /**
     * Replaces the query string in a search result with the same string in bold  e.g.
     * "Tag" in "Tagesschau" after searching for "Tag"
     *
     * @param {String} query
     * @param {String} name
     * @returns {String}
     */
    highlightQuery(query, name) {
        query = query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
        return name.replace(new RegExp('(' + query + ')', 'ig'), ($1, match) => `<strong>${match}</strong>`);
    }
}
<|endoftext|>


<commit_before>function cloneAndCompare(item) {
	var copy = item.clone();
	equals(function() {
		return item.parent == copy.parent;
	}, true);
	equals(function() {
		return item.nextSibling == copy;
	}, true);
	if (item.name) {
		equals(function() {
			return copy.parent.children[copy.name] == copy;
		}, true);
	}
	compareItems(copy, item, true);
	// Remove the cloned item to restore the document:
	copy.remove();
}

test('Path#clone()', function() {
	var path = new Path([10, 20], [30, 40]);
	path.closed = true;
	path.name = 'test';
	path.style = {
		strokeCap: 'round',
		strokeJoin: 'round',
		dashOffset: 10,
		dashArray: [10, 2, 10],
		fillColor: new RGBColor(0, 0, 1),
		strokeColor: new RGBColor(0, 0, 1),
		miterLimit: 5
	};
	path.clockwise = false;
	path.opacity = 0.5;
	path.locked = true;
	path.visible = false;
	path.blendMode = 'multiply';
	path.clipMask = true;
	path.selected = true;
	cloneAndCompare(path);
});

test('Path#clone() with GradientColor', function() {
	var colors = ['red', 'green', 'black'];
	var gradient = new Gradient(colors, 'radial');
	var color = new GradientColor(gradient, [0, 0], [20, 20], [10, 10]);

	var path = new Path([10, 20], [30, 40]);
	path.fillColor = color;
	cloneAndCompare(path);
});

test('CompoundPath#clone()', function() {
	var path1 = new Path.Rectangle([200, 200], [100, 100]);
	var path2 = new Path.Rectangle([50, 50], [200, 200]);
	var compound = new CompoundPath(path1, path2);
	cloneAndCompare(compound);
});

test('Layer#clone()', function() {
	var path = new Path.Rectangle([200, 200], [100, 100]);
	cloneAndCompare(paper.project.activeLayer);
});

test('Layer#clone() - check activeLayer', function() {
	var project = paper.project,
		activeLayer = project.activeLayer,
		layer = activeLayer.clone();
	equals(function() {
		return layer == project.activeLayer;
	}, true);
	equals(function() {
		return activeLayer != project.activeLayer;
	}, true);
});

test('Group#clone()', function() {
	var path = new Path.Circle([150, 150], 60);
	path.style = {
		strokeCap: 'round',
		strokeJoin: 'round',
		dashOffset: 10,
		dashArray: [10, 2, 10],
		fillColor: new RGBColor(0, 0, 1),
		strokeColor: new RGBColor(0, 0, 1),
		miterLimit: 5
	};
	var secondPath = new Path.Circle([175, 175], 85);
	var group = new Group([path, secondPath]);
	cloneAndCompare(group);
});

test('PointText#clone()', function() {
	var pointText = new PointText(new Point(50, 50));
	pointText.content = 'test';
	pointText.position += 100;
	pointText.characterStyle = {
		font: 'serif',
		fontSize: 20
	};
	pointText.paragraphStyle.justification = 'center';
	cloneAndCompare(pointText);
});

test('PlacedSymbol#clone()', function() {
	var path = new Path.Circle([150, 150], 60);
	var symbol = new Symbol(path);
	var placedSymbol = new PlacedSymbol(symbol);
	placedSymbol.position = [100, 100];
	placedSymbol.rotate(90);
	cloneAndCompare(placedSymbol);
});

test('Symbol#clone()', function() {
	var path = new Path.Circle([150, 150], 60);
	path.style = {
		strokeCap: 'round',
		strokeJoin: 'round',
		dashOffset: 10,
		dashArray: [10, 2, 10],
		fillColor: new RGBColor(0, 0, 1),
		strokeColor: new RGBColor(0, 0, 1),
		miterLimit: 5
	};
	path.selected = true;
	var symbol = new Symbol(path);
	var copy = symbol.clone();
	compareItems(copy.definition, symbol.definition);
	equals(function() {
		return symbol.project == copy.project;
	}, true);
	equals(function() {
		return paper.project.symbols.length == 2;
	}, true);
});

test('Raster#clone()', function() {
	var path = new Path.Circle([150, 150], 60);
	path.style = {
		fillColor: new RGBColor(0, 0, 1),
		strokeColor: new RGBColor(0, 0, 1)
	};
	var raster = path.rasterize();
	raster.opacity = 0.5;
	raster.locked = true;
	raster.visible = false;
	raster.blendMode = 'multiply';
	raster.rotate(20).translate(100);
	cloneAndCompare(raster);
});

test('Group with clipmask', function() {
	var path = new Path.Circle([100, 100], 30),
		path2 = new Path.Circle([100, 100], 20),
		group = new Group([path, path2]);
	group.clipped = true;
	cloneAndCompare(group);
});<commit_msg>Remove use of characterStyle / paragraphStyle where not necessary.<commit_after>function cloneAndCompare(item) {
	var copy = item.clone();
	equals(function() {
		return item.parent == copy.parent;
	}, true);
	equals(function() {
		return item.nextSibling == copy;
	}, true);
	if (item.name) {
		equals(function() {
			return copy.parent.children[copy.name] == copy;
		}, true);
	}
	compareItems(copy, item, true);
	// Remove the cloned item to restore the document:
	copy.remove();
}

test('Path#clone()', function() {
	var path = new Path([10, 20], [30, 40]);
	path.closed = true;
	path.name = 'test';
	path.style = {
		strokeCap: 'round',
		strokeJoin: 'round',
		dashOffset: 10,
		dashArray: [10, 2, 10],
		fillColor: new RGBColor(0, 0, 1),
		strokeColor: new RGBColor(0, 0, 1),
		miterLimit: 5
	};
	path.clockwise = false;
	path.opacity = 0.5;
	path.locked = true;
	path.visible = false;
	path.blendMode = 'multiply';
	path.clipMask = true;
	path.selected = true;
	cloneAndCompare(path);
});

test('Path#clone() with GradientColor', function() {
	var colors = ['red', 'green', 'black'];
	var gradient = new Gradient(colors, 'radial');
	var color = new GradientColor(gradient, [0, 0], [20, 20], [10, 10]);

	var path = new Path([10, 20], [30, 40]);
	path.fillColor = color;
	cloneAndCompare(path);
});

test('CompoundPath#clone()', function() {
	var path1 = new Path.Rectangle([200, 200], [100, 100]);
	var path2 = new Path.Rectangle([50, 50], [200, 200]);
	var compound = new CompoundPath(path1, path2);
	cloneAndCompare(compound);
});

test('Layer#clone()', function() {
	var path = new Path.Rectangle([200, 200], [100, 100]);
	cloneAndCompare(paper.project.activeLayer);
});

test('Layer#clone() - check activeLayer', function() {
	var project = paper.project,
		activeLayer = project.activeLayer,
		layer = activeLayer.clone();
	equals(function() {
		return layer == project.activeLayer;
	}, true);
	equals(function() {
		return activeLayer != project.activeLayer;
	}, true);
});

test('Group#clone()', function() {
	var path = new Path.Circle([150, 150], 60);
	path.style = {
		strokeCap: 'round',
		strokeJoin: 'round',
		dashOffset: 10,
		dashArray: [10, 2, 10],
		fillColor: new RGBColor(0, 0, 1),
		strokeColor: new RGBColor(0, 0, 1),
		miterLimit: 5
	};
	var secondPath = new Path.Circle([175, 175], 85);
	var group = new Group([path, secondPath]);
	cloneAndCompare(group);
});

test('PointText#clone()', function() {
	var pointText = new PointText(new Point(50, 50));
	pointText.content = 'test';
	pointText.position += 100;
	pointText.characterStyle = {
		font: 'serif',
		fontSize: 20
	};
	pointText.justification = 'center';
	cloneAndCompare(pointText);
});

test('PlacedSymbol#clone()', function() {
	var path = new Path.Circle([150, 150], 60);
	var symbol = new Symbol(path);
	var placedSymbol = new PlacedSymbol(symbol);
	placedSymbol.position = [100, 100];
	placedSymbol.rotate(90);
	cloneAndCompare(placedSymbol);
});

test('Symbol#clone()', function() {
	var path = new Path.Circle([150, 150], 60);
	path.style = {
		strokeCap: 'round',
		strokeJoin: 'round',
		dashOffset: 10,
		dashArray: [10, 2, 10],
		fillColor: new RGBColor(0, 0, 1),
		strokeColor: new RGBColor(0, 0, 1),
		miterLimit: 5
	};
	path.selected = true;
	var symbol = new Symbol(path);
	var copy = symbol.clone();
	compareItems(copy.definition, symbol.definition);
	equals(function() {
		return symbol.project == copy.project;
	}, true);
	equals(function() {
		return paper.project.symbols.length == 2;
	}, true);
});

test('Raster#clone()', function() {
	var path = new Path.Circle([150, 150], 60);
	path.style = {
		fillColor: new RGBColor(0, 0, 1),
		strokeColor: new RGBColor(0, 0, 1)
	};
	var raster = path.rasterize();
	raster.opacity = 0.5;
	raster.locked = true;
	raster.visible = false;
	raster.blendMode = 'multiply';
	raster.rotate(20).translate(100);
	cloneAndCompare(raster);
});

test('Group with clipmask', function() {
	var path = new Path.Circle([100, 100], 30),
		path2 = new Path.Circle([100, 100], 20),
		group = new Group([path, path2]);
	group.clipped = true;
	cloneAndCompare(group);
});<|endoftext|>


<commit_before>'use strict';

var FetchHll = require('../../../lib/commands/crdt/fetchhll');
var DtFetchResp = require('../../../lib/protobuf/riakprotobuf').getProtoFor('DtFetchResp');
var DtValue = require('../../../lib/protobuf/riakprotobuf').getProtoFor('DtValue');
var RpbErrorResp = require('../../../lib/protobuf/riakprotobuf').getProtoFor('RpbErrorResp');

var assert = require('assert');

describe('FetchHll', function() {
    describe('Build', function() {
        it('should build a DtFetchReq correctly', function(done) {
            var fetch = new FetchHll.Builder()
                .withBucketType('hlls')
                .withBucket('myBucket')
                .withKey('hll_1')
                .withCallback(function(){})
                .withR(1)
                .withPr(2)
                .withNotFoundOk(true)
                .withBasicQuorum(true)
                .withTimeout(20000)
                .build();

            var protobuf = fetch.constructPbRequest();

            assert.equal(protobuf.getType().toString('utf8'), 'hlls');
            assert.equal(protobuf.getBucket().toString('utf8'), 'myBucket');
            assert.equal(protobuf.getKey().toString('utf8'), 'hll_1');
            assert.equal(protobuf.getR(), 1);
            assert.equal(protobuf.getPr(), 2);
            assert.equal(protobuf.getNotfoundOk(), true);
            assert.equal(protobuf.getBasicQuorum(), true);
            assert.equal(protobuf.getTimeout(), 20000);
            done();
        });

        it('should take a DtFetchResp and call the users callback with the response', function(done) {
            var dtFetchResp = new DtFetchResp();
            var dtValue = new DtValue();
            dtValue.setHllValue(42);
            dtFetchResp.value = dtValue;

            var callback = function(err, response) {
                if (response) {
                    assert.equal(response.cardinality, 42);
                    done();
                }
            };

            var fetch = new FetchHll.Builder()
                .withBucketType('hlls')
                .withBucket('myBucket')
                .withKey('hll_1')
                .withCallback(callback)
                .build();

            fetch.onSuccess(dtFetchResp);
        });

        it ('should take a RpbErrorResp and call the users callback with the error message', function(done) {
            var rpbErrorResp = new RpbErrorResp();
            rpbErrorResp.setErrmsg(new Buffer('this is an error'));

            var callback = function(err, response) {
                if (err) {
                    assert.equal(err,'this is an error');
                    done();
                }
            };

            var fetch = new FetchHll.Builder()
                .withBucketType('hlls')
                .withBucket('myBucket')
                .withKey('hll_1')
                .withCallback(callback)
                .build();

            fetch.onRiakError(rpbErrorResp);
        });

    });
});
<commit_msg>whitespace<commit_after>'use strict';

var FetchHll = require('../../../lib/commands/crdt/fetchhll');
var DtFetchResp = require('../../../lib/protobuf/riakprotobuf').getProtoFor('DtFetchResp');
var DtValue = require('../../../lib/protobuf/riakprotobuf').getProtoFor('DtValue');
var RpbErrorResp = require('../../../lib/protobuf/riakprotobuf').getProtoFor('RpbErrorResp');

var assert = require('assert');

describe('FetchHll', function() {
    describe('Build', function() {
        it('should build a DtFetchReq correctly', function(done) {
            var fetch = new FetchHll.Builder()
                .withBucketType('hlls')
                .withBucket('myBucket')
                .withKey('hll_1')
                .withCallback(function(){})
                .withR(1)
                .withPr(2)
                .withNotFoundOk(true)
                .withBasicQuorum(true)
                .withTimeout(20000)
                .build();

            var protobuf = fetch.constructPbRequest();

            assert.equal(protobuf.getType().toString('utf8'), 'hlls');
            assert.equal(protobuf.getBucket().toString('utf8'), 'myBucket');
            assert.equal(protobuf.getKey().toString('utf8'), 'hll_1');
            assert.equal(protobuf.getR(), 1);
            assert.equal(protobuf.getPr(), 2);
            assert.equal(protobuf.getNotfoundOk(), true);
            assert.equal(protobuf.getBasicQuorum(), true);
            assert.equal(protobuf.getTimeout(), 20000);
            done();
        });

        it('should take a DtFetchResp and call the users callback with the response', function(done) {
            var dtFetchResp = new DtFetchResp();
            var dtValue = new DtValue();
            dtValue.setHllValue(42);
            dtFetchResp.value = dtValue;

            var callback = function(err, response) {
                if (response) {
                    assert.equal(response.cardinality, 42);
                    done();
                }
            };

            var fetch = new FetchHll.Builder()
                .withBucketType('hlls')
                .withBucket('myBucket')
                .withKey('hll_1')
                .withCallback(callback)
                .build();

            fetch.onSuccess(dtFetchResp);
        });

        it ('should take a RpbErrorResp and call the users callback with the error message', function(done) {
            var rpbErrorResp = new RpbErrorResp();
            rpbErrorResp.setErrmsg(new Buffer('this is an error'));

            var callback = function(err, response) {
                if (err) {
                    assert.equal(err,'this is an error');
                    done();
                }
            };

            var fetch = new FetchHll.Builder()
                .withBucketType('hlls')
                .withBucket('myBucket')
                .withKey('hll_1')
                .withCallback(callback)
                .build();

            fetch.onRiakError(rpbErrorResp);
        });
    });
});
<|endoftext|>


<commit_before>import Keysim from './keysim';
const Keyboard = Keysim.Keyboard;
const Keystroke = Keysim.Keystroke;
const KeyEvents = Keysim.KeyEvents;

const DEFAULT_KEYBOARD = Keyboard.US_ENGLISH;

const keyboardWrapper = (type, keyboardFilter=false) => (title, fn) => {
  [
    {
      keyboard: Keyboard.US_ENGLISH,
      name: 'English',
      tag: ['desktop']
    },
    {
      keyboard: ANDROID_CHROME,
      name: 'Android',
      tag: ['mobile']
    }
  ]
  .filter((keyboard) => {
    return !keyboardFilter ||
      keyboard.tag.indexOf(keyboardFilter) >= 0;
  })
  .forEach(testKeyboard => {
    type(`KEYBOARD-${testKeyboard.name} :: ${title}`, () => {
      beforeEach(() => window.keyboard = testKeyboard.keyboard );

      fn.call(this);

      afterEach(() => window.keyboard = DEFAULT_KEYBOARD);
    });
  });
};

window.testsWithAllKeyboards = keyboardWrapper(describe);
window.testsWithDesktopKeyboards = keyboardWrapper(describe, 'desktop');

const DEFAULT_UA = navigator.userAgent;

beforeEach(() => {
  navigator.__defineGetter__('userAgent', function(){
    return DEFAULT_UA;
  });
});

const CTRL  = 1 << 0;
const META  = 1 << 1;
const ALT   = 1 << 2;
const SHIFT = 1 << 3;

const ANDROID_CHROME_CHARCODE_KEYCODE_MAP = {
  32:  new Keystroke(0,     32),  // <space>
  33:  new Keystroke(SHIFT, 33),  // !
  34:  new Keystroke(SHIFT, 34), // "
  35:  new Keystroke(SHIFT, 35),  // #
  36:  new Keystroke(SHIFT, 36),  // $
  37:  new Keystroke(SHIFT, 37),  // %
  38:  new Keystroke(SHIFT, 38),  // &
  39:  new Keystroke(0,     39),  // '
  40:  new Keystroke(SHIFT, 40),  // (
  41:  new Keystroke(SHIFT, 41),  // )
  42:  new Keystroke(SHIFT, 42),  // *
  43:  new Keystroke(SHIFT, 43),  // +
  44:  new Keystroke(0,     44),  // ,
  45:  new Keystroke(0,     45),  // -
  46:  new Keystroke(0,     46),  // .
  47:  new Keystroke(0,     47),  // /
  48:  new Keystroke(0,     48),  // 0
  49:  new Keystroke(0,     49),  // 1
  50:  new Keystroke(0,     50),  // 2
  51:  new Keystroke(0,     51),  // 3
  52:  new Keystroke(0,     52),  // 4
  53:  new Keystroke(0,     53),  // 5
  54:  new Keystroke(0,     54),  // 6
  55:  new Keystroke(0,     55),  // 7
  56:  new Keystroke(0,     56),  // 8
  57:  new Keystroke(0,     57),  // 9
  58:  new Keystroke(SHIFT, 58),  // :
  59:  new Keystroke(0,     59),  // ;
  60:  new Keystroke(SHIFT, 60),  // <
  61:  new Keystroke(0,     61),  // =
  62:  new Keystroke(SHIFT, 62),  // >
  63:  new Keystroke(SHIFT, 63),  // ?
  64:  new Keystroke(SHIFT, 64),  // @
  65:  new Keystroke(SHIFT, 65),  // A
  66:  new Keystroke(SHIFT, 66),  // B
  67:  new Keystroke(SHIFT, 67),  // C
  68:  new Keystroke(SHIFT, 68),  // D
  69:  new Keystroke(SHIFT, 69),  // E
  70:  new Keystroke(SHIFT, 70),  // F
  71:  new Keystroke(SHIFT, 71),  // G
  72:  new Keystroke(SHIFT, 72),  // H
  73:  new Keystroke(SHIFT, 73),  // I
  74:  new Keystroke(SHIFT, 74),  // J
  75:  new Keystroke(SHIFT, 75),  // K
  76:  new Keystroke(SHIFT, 76),  // L
  77:  new Keystroke(SHIFT, 77),  // M
  78:  new Keystroke(SHIFT, 78),  // N
  79:  new Keystroke(SHIFT, 79),  // O
  80:  new Keystroke(SHIFT, 80),  // P
  81:  new Keystroke(SHIFT, 81),  // Q
  82:  new Keystroke(SHIFT, 82),  // R
  83:  new Keystroke(SHIFT, 83),  // S
  84:  new Keystroke(SHIFT, 84),  // T
  85:  new Keystroke(SHIFT, 85),  // U
  86:  new Keystroke(SHIFT, 86),  // V
  87:  new Keystroke(SHIFT, 87),  // W
  88:  new Keystroke(SHIFT, 88),  // X
  89:  new Keystroke(SHIFT, 89),  // Y
  90:  new Keystroke(SHIFT, 90),  // Z
  91:  new Keystroke(0,     219), // [
  92:  new Keystroke(0,     220), // \
  93:  new Keystroke(0,     221), // ]
  96:  new Keystroke(0,     192), // `
  97:  new Keystroke(0,     97),  // a
  98:  new Keystroke(0,     98),  // b
  99:  new Keystroke(0,     99),  // c
  100: new Keystroke(0,     100), // d
  101: new Keystroke(0,     101), // e
  102: new Keystroke(0,     102), // f
  103: new Keystroke(0,     103), // g
  104: new Keystroke(0,     104), // h
  105: new Keystroke(0,     105), // i
  106: new Keystroke(0,     106), // j
  107: new Keystroke(0,     107), // k
  108: new Keystroke(0,     108), // l
  109: new Keystroke(0,     109), // m
  110: new Keystroke(0,     110), // n
  111: new Keystroke(0,     111), // o
  112: new Keystroke(0,     112), // p
  113: new Keystroke(0,     113), // q
  114: new Keystroke(0,     114), // r
  115: new Keystroke(0,     115), // s
  116: new Keystroke(0,     116), // t
  117: new Keystroke(0,     117), // u
  118: new Keystroke(0,     118), // v
  119: new Keystroke(0,     119), // w
  120: new Keystroke(0,     120), // x
  121: new Keystroke(0,     121), // y
  122: new Keystroke(0,     122), // z
  123: new Keystroke(SHIFT, 123), // {
  124: new Keystroke(SHIFT, 124), // |
  125: new Keystroke(SHIFT, 125), // }
  126: new Keystroke(SHIFT, 126)  // ~
};

const ANDROID_CHROME_ACTION_KEYCODE_MAP = {
  BACKSPACE: 8,
  TAB:       9,
  ENTER:    13,
  SHIFT:    16,
  CTRL:     17,
  ALT:      18,
  PAUSE:    19,
  CAPSLOCK: 20,
  ESCAPE:   27,
  PAGEUP:   33,
  PAGEDOWN: 34,
  END:      35,
  HOME:     36,
  LEFT:     37,
  UP:       38,
  RIGHT:    39,
  DOWN:     40,
  INSERT:   45,
  DELETE:   46,
  META:     91,
  F1:      112,
  F2:      113,
  F3:      114,
  F4:      115,
  F5:      116,
  F6:      117,
  F7:      118,
  F8:      119,
  F9:      120,
  F10:     121,
  F11:     122,
  F12:     123
};

function replaceStringSelection(replacement, text, range) {
  var end = range.start + range.length;
  return text.substring(0, range.start) + replacement + text.substring(end);
}

/**
 * Gets a keyboard instance configured as a U.S. English keyboard would be.
 *
 * @return {Keyboard}
 */
const ANDROID_CHROME = new Keyboard(
  ANDROID_CHROME_CHARCODE_KEYCODE_MAP,
  ANDROID_CHROME_ACTION_KEYCODE_MAP
);

const originalDispatch = ANDROID_CHROME.dispatchEventsForKeystroke;

ANDROID_CHROME.dispatchEventsForKeystroke = (keystroke, target, mods=true) => {
  if (!keystroke.modifiers && !mods) {
    const transitionModifiers = false;
    // Dispatch keyDown Events
    originalDispatch.call(ANDROID_CHROME, new Keystroke(0, 229), target, transitionModifiers, KeyEvents.DOWN);

    const field = target['field-kit-text-field'];
    const currentSelectedRange = field.selectedRange();

    // Mock the browser inputting the character from keyDown
    field.element.value = replaceStringSelection(
      String.fromCharCode(keystroke.keyCode),
      field.text(),
      currentSelectedRange
    );
    field.setSelectedRange({ start: currentSelectedRange.start + 1, length: 0 });

    // Dispatch keyUp
    // This is where we do most of the processing for this type of Android Keyboard
    return originalDispatch.call(ANDROID_CHROME, new Keystroke(0, 229), target, transitionModifiers, KeyEvents.UP);
  } else {
    if (keystroke.keyCode === 97) {
      keystroke.keyCode = 65;
    } else if (keystroke.keyCode === 122) {
      keystroke.keyCode = 90;
    }
    originalDispatch.call(ANDROID_CHROME, keystroke, target, mods);
  }
};
<commit_msg>address Donovans concerns<commit_after>import Keysim from './keysim';
const Keyboard = Keysim.Keyboard;
const Keystroke = Keysim.Keystroke;
const KeyEvents = Keysim.KeyEvents;

const DEFAULT_KEYBOARD = Keyboard.US_ENGLISH;

const keyboardWrapper = (type, keyboardFilter=false) => (title, fn) => {
  [
    {
      keyboard: Keyboard.US_ENGLISH,
      name: 'English',
      tag: ['desktop']
    },
    {
      keyboard: ANDROID_CHROME,
      name: 'Android',
      tag: ['mobile']
    }
  ]
  .filter(keyboard => {
    return !keyboardFilter ||
      keyboard.tag.indexOf(keyboardFilter) >= 0;
  })
  .forEach(testKeyboard => {
    type(`KEYBOARD-${testKeyboard.name} :: ${title}`, () => {
      beforeEach(() => window.keyboard = testKeyboard.keyboard );

      fn.call(this);

      afterEach(() => window.keyboard = DEFAULT_KEYBOARD);
    });
  });
};

window.testsWithAllKeyboards = keyboardWrapper(describe);
window.testsWithDesktopKeyboards = keyboardWrapper(describe, 'desktop');

const DEFAULT_UA = navigator.userAgent;

beforeEach(() => {
  navigator.__defineGetter__('userAgent', function(){
    return DEFAULT_UA;
  });
});

const SHIFT = 1 << 3;

const ANDROID_CHROME_CHARCODE_KEYCODE_MAP = {
  32:  new Keystroke(0,     32),  // <space>
  33:  new Keystroke(SHIFT, 33),  // !
  34:  new Keystroke(SHIFT, 34),  // "
  35:  new Keystroke(SHIFT, 35),  // #
  36:  new Keystroke(SHIFT, 36),  // $
  37:  new Keystroke(SHIFT, 37),  // %
  38:  new Keystroke(SHIFT, 38),  // &
  39:  new Keystroke(0,     39),  // '
  40:  new Keystroke(SHIFT, 40),  // (
  41:  new Keystroke(SHIFT, 41),  // )
  42:  new Keystroke(SHIFT, 42),  // *
  43:  new Keystroke(SHIFT, 43),  // +
  44:  new Keystroke(0,     44),  // ,
  45:  new Keystroke(0,     45),  // -
  46:  new Keystroke(0,     46),  // .
  47:  new Keystroke(0,     47),  // /
  48:  new Keystroke(0,     48),  // 0
  49:  new Keystroke(0,     49),  // 1
  50:  new Keystroke(0,     50),  // 2
  51:  new Keystroke(0,     51),  // 3
  52:  new Keystroke(0,     52),  // 4
  53:  new Keystroke(0,     53),  // 5
  54:  new Keystroke(0,     54),  // 6
  55:  new Keystroke(0,     55),  // 7
  56:  new Keystroke(0,     56),  // 8
  57:  new Keystroke(0,     57),  // 9
  58:  new Keystroke(SHIFT, 58),  // :
  59:  new Keystroke(0,     59),  // ;
  60:  new Keystroke(SHIFT, 60),  // <
  61:  new Keystroke(0,     61),  // =
  62:  new Keystroke(SHIFT, 62),  // >
  63:  new Keystroke(SHIFT, 63),  // ?
  64:  new Keystroke(SHIFT, 64),  // @
  65:  new Keystroke(SHIFT, 65),  // A
  66:  new Keystroke(SHIFT, 66),  // B
  67:  new Keystroke(SHIFT, 67),  // C
  68:  new Keystroke(SHIFT, 68),  // D
  69:  new Keystroke(SHIFT, 69),  // E
  70:  new Keystroke(SHIFT, 70),  // F
  71:  new Keystroke(SHIFT, 71),  // G
  72:  new Keystroke(SHIFT, 72),  // H
  73:  new Keystroke(SHIFT, 73),  // I
  74:  new Keystroke(SHIFT, 74),  // J
  75:  new Keystroke(SHIFT, 75),  // K
  76:  new Keystroke(SHIFT, 76),  // L
  77:  new Keystroke(SHIFT, 77),  // M
  78:  new Keystroke(SHIFT, 78),  // N
  79:  new Keystroke(SHIFT, 79),  // O
  80:  new Keystroke(SHIFT, 80),  // P
  81:  new Keystroke(SHIFT, 81),  // Q
  82:  new Keystroke(SHIFT, 82),  // R
  83:  new Keystroke(SHIFT, 83),  // S
  84:  new Keystroke(SHIFT, 84),  // T
  85:  new Keystroke(SHIFT, 85),  // U
  86:  new Keystroke(SHIFT, 86),  // V
  87:  new Keystroke(SHIFT, 87),  // W
  88:  new Keystroke(SHIFT, 88),  // X
  89:  new Keystroke(SHIFT, 89),  // Y
  90:  new Keystroke(SHIFT, 90),  // Z
  91:  new Keystroke(0,     219), // [
  92:  new Keystroke(0,     220), // \
  93:  new Keystroke(0,     221), // ]
  96:  new Keystroke(0,     192), // `
  97:  new Keystroke(0,     97),  // a
  98:  new Keystroke(0,     98),  // b
  99:  new Keystroke(0,     99),  // c
  100: new Keystroke(0,     100), // d
  101: new Keystroke(0,     101), // e
  102: new Keystroke(0,     102), // f
  103: new Keystroke(0,     103), // g
  104: new Keystroke(0,     104), // h
  105: new Keystroke(0,     105), // i
  106: new Keystroke(0,     106), // j
  107: new Keystroke(0,     107), // k
  108: new Keystroke(0,     108), // l
  109: new Keystroke(0,     109), // m
  110: new Keystroke(0,     110), // n
  111: new Keystroke(0,     111), // o
  112: new Keystroke(0,     112), // p
  113: new Keystroke(0,     113), // q
  114: new Keystroke(0,     114), // r
  115: new Keystroke(0,     115), // s
  116: new Keystroke(0,     116), // t
  117: new Keystroke(0,     117), // u
  118: new Keystroke(0,     118), // v
  119: new Keystroke(0,     119), // w
  120: new Keystroke(0,     120), // x
  121: new Keystroke(0,     121), // y
  122: new Keystroke(0,     122), // z
  123: new Keystroke(SHIFT, 123), // {
  124: new Keystroke(SHIFT, 124), // |
  125: new Keystroke(SHIFT, 125), // }
  126: new Keystroke(SHIFT, 126)  // ~
};

const ANDROID_CHROME_ACTION_KEYCODE_MAP = {
  BACKSPACE: 8,
  TAB:       9,
  ENTER:    13,
  SHIFT:    16,
  CTRL:     17,
  ALT:      18,
  PAUSE:    19,
  CAPSLOCK: 20,
  ESCAPE:   27,
  PAGEUP:   33,
  PAGEDOWN: 34,
  END:      35,
  HOME:     36,
  LEFT:     37,
  UP:       38,
  RIGHT:    39,
  DOWN:     40,
  INSERT:   45,
  DELETE:   46,
  META:     91,
  F1:      112,
  F2:      113,
  F3:      114,
  F4:      115,
  F5:      116,
  F6:      117,
  F7:      118,
  F8:      119,
  F9:      120,
  F10:     121,
  F11:     122,
  F12:     123
};

function replaceStringSelection(replacement, text, range) {
  var end = range.start + range.length;
  return text.substring(0, range.start) + replacement + text.substring(end);
}

/**
 * NOTE: Some Androids keyboards will always return 229
 * This keyboard also only produces the keyDown and
 * keyUp event. FieldKit uses what the browser enters in keyDown
 * to process with the formatter. So this keyboard needs to also
 * insert the character into the field.
 *
 * https://code.google.com/p/chromium/issues/detail?id=118639
 */
const ANDROID_CHROME = new Keyboard(
  ANDROID_CHROME_CHARCODE_KEYCODE_MAP,
  ANDROID_CHROME_ACTION_KEYCODE_MAP
);

const originalDispatch = ANDROID_CHROME.dispatchEventsForKeystroke;

ANDROID_CHROME.dispatchEventsForKeystroke = function(keystroke, target, mods=true) {
  if (!keystroke.modifiers && !mods) {
    const transitionModifiers = false;
    // Dispatch keyDown Events
    originalDispatch.call(this, new Keystroke(0, 229), target, transitionModifiers, KeyEvents.DOWN);

    const field = target['field-kit-text-field'];
    const currentSelectedRange = field.selectedRange();

    // Mock the browser inputting the character from keyDown
    field.element.value = replaceStringSelection(
      String.fromCharCode(keystroke.keyCode),
      field.text(),
      currentSelectedRange
    );
    field.setSelectedRange({ start: currentSelectedRange.start + 1, length: 0 });

    // Dispatch keyUp
    // This is where we do most of the processing for this type of Android Keyboard
    return originalDispatch.call(this, new Keystroke(0, 229), target, transitionModifiers, KeyEvents.UP);
  } else {
    if (keystroke.keyCode === 97) {
      keystroke.keyCode = 65;
    } else if (keystroke.keyCode === 122) {
      keystroke.keyCode = 90;
    }
    originalDispatch.call(this, keystroke, target, mods);
  }
};
<|endoftext|>


<commit_before>/* eslint max-len: ["off"] */

// TODO: reflect props and attrs:
// - set a property, read the property and the attribute
// - set an attribute, read the property and the attribute
// - Try to set unvalid values

(function() {
  const expect = chai.expect;

  describe('Init', function() {
    before(wcutils.before());
    after(wcutils.after());

    it('declarative', function() {
      this.container.innerHTML = `<x-slider></x-slider>`;

      return wcutils.waitForElement('x-slider').then(() => {
        expect(document.querySelector('x-slider').constructor.name)
            .to.equal('XSlider');
      });
    });

    it('programmatic', function() {
      const slider = document.createElement('x-slider');

      return wcutils.waitForElement('x-slider').then(() => {
        expect(slider.constructor.name).to.equal('XSlider');
      });
    });
  });

  describe('Property', function() {
    const numberOfSlides = 4;

    before(wcutils.before());
    after(wcutils.after());
    beforeEach(async function() {
      this.container.innerHTML = `
      <x-slider>
        ${[...Array(numberOfSlides).keys()]
            .map(i => `<article>Slide ${i}</article>`)
            .join('\n')}
      </x-slider>`;
      return wcutils.waitForElement('x-slider')
        .then(() => {
          this.slider = this.container.querySelector('x-slider');
        });
    });

    const allPropertyTests = [
      {
        // selected has to be a Number in range [0, slides.length - 1].
        // Otherwise it rolls back to the previous value.
        name: 'selected',
        tests: [
          {value: 2, expected: 2},
          {value: -1, expected: 0},
          {value: numberOfSlides, expected: 0},
          {value: 'string', expected: 0},
          {value: null, expected: 0},
          {value: undefined, expected: 0},
          {value: {}, expected: 0},
          {value: true, expected: 0},
        ],
      },
      {
        // truthy values set it to true, falsy values set it to false
        name: 'loop',
        tests: [
          {value: true, expected: true},
          {value: 2, expected: true},
          {value: 'string', expected: true},
          {value: {}, expected: true},
          {value: 0, expected: false},
          {value: null, expected: false},
          {value: undefined, expected: false},
        ],
      },
    ];

    // Since all tests are async function, the loop variable is saved to a local
    // variable. An alternative approach would be to wrap the test in a closure.
    for (property of allPropertyTests) {
      let propertyName = property.name;
      describe(`${propertyName}`, () => {
        for (test of property.tests) {
          let t = test;
          it(`should be ${t.expected}`, async function() {
            await wcutils.flush();
            this.slider[propertyName] = t.value;
            await wcutils.flush();
            expect(this.slider[propertyName]).to.equal(t.expected);
          });
        }
      });
    }
  });
})();
<commit_msg>GH-12 feat(test): setting all types of values for properties<commit_after>/* eslint max-len: ["off"] */

// TODO: reflect props and attrs:
// - set a property, read the property and the attribute
// - set an attribute, read the property and the attribute

(function() {
  const expect = chai.expect;
  const numberOfSlides = 4;
  const allPropertyTests = [
    {
      // selected has to be a Number in range [0, slides.length - 1].
      // Otherwise it rolls back to the previous value.
      propertyName: 'selected',
      tests: [
        {value: 2, expected: 2},
        {value: -1, expected: 0},
        {value: numberOfSlides, expected: 0},
        {value: 'string', expected: 0},
        {value: null, expected: 0},
        {value: undefined, expected: 0},
        {value: {}, expected: 0},
        {value: true, expected: 0},
      ],
    },
    {
      // truthy values set it to true, falsy values set it to false
      propertyName: 'loop',
      tests: [
        {value: true, expected: true},
        {value: 2, expected: true},
        {value: 'string', expected: true},
        {value: {}, expected: true},
        {value: 0, expected: false},
        {value: null, expected: false},
        {value: undefined, expected: false},
      ],
    },
    {
      // truthy values set it to true, falsy values set it to false
      propertyName: 'navigation',
      tests: [
        {value: true, expected: true},
        {value: 2, expected: true},
        {value: 'string', expected: true},
        {value: {}, expected: true},
        {value: 0, expected: false},
        {value: null, expected: false},
        {value: undefined, expected: false},
      ],
    },
    {
      // truthy values set it to true, falsy values set it to false
      propertyName: 'pagination',
      tests: [
        {value: true, expected: true},
        {value: 2, expected: true},
        {value: 'string', expected: true},
        {value: {}, expected: true},
        {value: 0, expected: false},
        {value: null, expected: false},
        {value: undefined, expected: false},
      ],
    },
    {
      // truthy values set it to true, falsy values set it to false
      propertyName: 'disableDrag',
      tests: [
        {value: true, expected: true},
        {value: 2, expected: true},
        {value: 'string', expected: true},
        {value: {}, expected: true},
        {value: 0, expected: false},
        {value: null, expected: false},
        {value: undefined, expected: false},
      ],
    },
    {
      // slidesPerView has to be a Number in range [1, slides.length].
      // Otherwise it rolls back to the previous value.
      propertyName: 'slidesPerView',
      tests: [
        {value: 2, expected: 2},
        {value: 0, expected: 1},
        {value: numberOfSlides + 1, expected: 1},
        {value: 'string', expected: 1},
        {value: null, expected: 1},
        {value: undefined, expected: 1},
        {value: {}, expected: 1},
        {value: true, expected: 1},
      ],
    },
    {
      // truthy values set it to true, falsy values set it to false
      propertyName: 'reducedMotion',
      tests: [
        {value: true, expected: true},
        {value: 2, expected: true},
        {value: 'string', expected: true},
        {value: {}, expected: true},
        {value: 0, expected: false},
        {value: null, expected: false},
        {value: undefined, expected: false},
      ],
    },
    {
      // truthy values set it to true, falsy values set it to false
      propertyName: 'autoFocus',
      tests: [
        {value: true, expected: true},
        {value: 2, expected: true},
        {value: 'string', expected: true},
        {value: {}, expected: true},
        {value: 0, expected: false},
        {value: null, expected: false},
        {value: undefined, expected: false},
      ],
    },
  ];

  describe('Init', function() {
    before(wcutils.before());
    after(wcutils.after());

    it('declarative', function() {
      this.container.innerHTML = `<x-slider></x-slider>`;

      return wcutils.waitForElement('x-slider').then(() => {
        expect(document.querySelector('x-slider').constructor.name)
            .to.equal('XSlider');
      });
    });

    it('programmatic', function() {
      const slider = document.createElement('x-slider');

      return wcutils.waitForElement('x-slider').then(() => {
        expect(slider.constructor.name).to.equal('XSlider');
      });
    });
  });

  describe('The value of', function() {
    before(wcutils.before());
    after(wcutils.after());
    beforeEach(async function() {
      this.container.innerHTML = `
      <x-slider>
        ${[...Array(numberOfSlides).keys()]
            .map(i => `<article>Slide ${i}</article>`)
            .join('\n')}
      </x-slider>`;
      return wcutils.waitForElement('x-slider')
        .then(() => {
          this.slider = this.container.querySelector('x-slider');
        });
    });

    // Since all tests are async function, the loop variable is saved to a local
    // variable. An alternative approach would be to wrap the test in a closure.
    for (let p of allPropertyTests) {
      describe(`the ${p.propertyName} property`, () => {
        for (let t of p.tests) {
          it(`should be ${t.expected} when set to ${t.value}`, async function() {
            await wcutils.flush();
            this.slider[p.propertyName] = t.value;
            await wcutils.flush();
            expect(this.slider[p.propertyName]).to.equal(t.expected);
          });
        }
      });
    }
  });
})();
<|endoftext|>


<commit_before>var junitReportBuilder = require('junit-report-builder');
var junitReporter = require('../../lib/junitReporter.js');
var sinon = require('sinon');
var parseString = require('xml2js').parseString;

describe('junitReporter', function () {
	beforeEach(function () {
		this.sinon = sinon.sandbox.create();
	});
	afterEach(function () {
		this.sinon.restore();
	});
	it('Should generate a Junit XML file from a JSON report', function () {
		var results = [
			{
				url: 'http://test.com',
				timestamp: new Date().getTime(),
				time: 42,
				violations: [
					{
						id: 'ruleId1',
						help: 'Help message',
						helpUrl: 'helpURL',
						nodes: [
							{
								target: 'div > div',
								html: 'My HTML element',
								any: [
									{
										message: 'First message'
									},
									{
										message: 'Second message',
										relatedNodes: [
											{
												target: [
													'body > .related > .node'
												]
											}
										]
									}
								],
								all: [],
								none: []
							}
						]
					}
				],
				passes: [
					{
						id: 'ruleId2',
						nodes: [
							{
								target: 'div > span > a'
							}
						]
					}
				]
			}
		];
		var junitDest = 'junitDest.xml';

		var junitXml = null;
		var writeToSpy = sinon.stub(junitReportBuilder, 'writeTo', function () {
			junitXml = junitReportBuilder.build();
		});

		junitReporter(results, junitDest);

		sinon.assert.calledOnce(writeToSpy);
		sinon.assert.calledWith(writeToSpy, junitDest);

		parseString(junitXml, function (err, result) {
			result.should.have.keys('testsuites');
			result.testsuites.should.have.keys('testsuite');

			var testsuite = result.testsuites.testsuite[0];
			testsuite.$.name.should.equal(results[0].url);
			testsuite.$.timestamp.should.equal('' + results[0].timestamp);
			testsuite.$.time.should.equal('' + results[0].time);
			testsuite.$.tests.should.equal('' + (results[0].violations.length + results[0].passes.length));
			testsuite.$.failures.should.equal('' + results[0].violations.length);

			var testcase1 = testsuite.testcase[0];
			testcase1.$.classname.should.equal(results[0].url + '.' + results[0].violations[0].id);
			testcase1.$.name.should.equal(results[0].violations[0].nodes[0].target);

			var failure = testcase1.failure[0];
			failure.$.message.should.match(new RegExp(results[0].violations[0].help));
			failure.$.message.should.match(new RegExp(results[0].violations[0].helpUrl));
			failure._.should.match(new RegExp(results[0].violations[0].nodes[0].html));
			failure._.should.match(new RegExp(results[0].violations[0].nodes[0].any[0].message));
			failure._.should.match(new RegExp(results[0].violations[0].nodes[0].any[1].message));
			failure._.should.match(new RegExp(results[0].violations[0].nodes[0].any[1].relatedNodes[0].target));

			var testcase2 = testsuite.testcase[1];
			testcase2.$.classname.should.equal(results[0].url + '.' + results[0].passes[0].id);
			testcase2.$.name.should.equal(results[0].passes[0].nodes[0].target);
		});
	});
});
<commit_msg>Remove unused<commit_after>var junitReportBuilder = require('junit-report-builder');
var junitReporter = require('../../lib/junitReporter.js');
var sinon = require('sinon');
var parseString = require('xml2js').parseString;

describe('junitReporter', function () {
	it('Should generate a Junit XML file from a JSON report', function () {
		var results = [
			{
				url: 'http://test.com',
				timestamp: new Date().getTime(),
				time: 42,
				violations: [
					{
						id: 'ruleId1',
						help: 'Help message',
						helpUrl: 'helpURL',
						nodes: [
							{
								target: 'div > div',
								html: 'My HTML element',
								any: [
									{
										message: 'First message'
									},
									{
										message: 'Second message',
										relatedNodes: [
											{
												target: [
													'body > .related > .node'
												]
											}
										]
									}
								],
								all: [],
								none: []
							}
						]
					}
				],
				passes: [
					{
						id: 'ruleId2',
						nodes: [
							{
								target: 'div > span > a'
							}
						]
					}
				]
			}
		];
		var junitDest = 'junitDest.xml';

		var junitXml = null;
		var writeToSpy = sinon.stub(junitReportBuilder, 'writeTo', function () {
			junitXml = junitReportBuilder.build();
		});

		junitReporter(results, junitDest);

		sinon.assert.calledOnce(writeToSpy);
		sinon.assert.calledWith(writeToSpy, junitDest);

		parseString(junitXml, function (err, result) {
			result.should.have.keys('testsuites');
			result.testsuites.should.have.keys('testsuite');

			var testsuite = result.testsuites.testsuite[0];
			testsuite.$.name.should.equal(results[0].url);
			testsuite.$.timestamp.should.equal('' + results[0].timestamp);
			testsuite.$.time.should.equal('' + results[0].time);
			testsuite.$.tests.should.equal('' + (results[0].violations.length + results[0].passes.length));
			testsuite.$.failures.should.equal('' + results[0].violations.length);

			var testcase1 = testsuite.testcase[0];
			testcase1.$.classname.should.equal(results[0].url + '.' + results[0].violations[0].id);
			testcase1.$.name.should.equal(results[0].violations[0].nodes[0].target);

			var failure = testcase1.failure[0];
			failure.$.message.should.match(new RegExp(results[0].violations[0].help));
			failure.$.message.should.match(new RegExp(results[0].violations[0].helpUrl));
			failure._.should.match(new RegExp(results[0].violations[0].nodes[0].html));
			failure._.should.match(new RegExp(results[0].violations[0].nodes[0].any[0].message));
			failure._.should.match(new RegExp(results[0].violations[0].nodes[0].any[1].message));
			failure._.should.match(new RegExp(results[0].violations[0].nodes[0].any[1].relatedNodes[0].target));

			var testcase2 = testsuite.testcase[1];
			testcase2.$.classname.should.equal(results[0].url + '.' + results[0].passes[0].id);
			testcase2.$.name.should.equal(results[0].passes[0].nodes[0].target);
		});
	});
});
<|endoftext|>


<commit_before>// Karma configuration
// Generated on Thu Dec 29 2016 18:10:21 GMT-0200 (Horário brasileiro de verão)

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine'],

    // list of files / patterns to load in the browser
    files: [
      //initialization
      'init.js',

      //third-party dependencies
      '../../node_modules/angular/angular.js',
      '../../node_modules/angular-mocks/angular-mocks.js',
      '../../node_modules/angular-mocks/angular-resource.min.js',

      //file which will be tested
      '../../dist/ui-deni-grid.js',

      //specs
      'specs/*.spec.js'
    ],

    // list of files to exclude
    exclude: [
    ],

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
    },

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress', 'jasmine-diff'],
    //reporters: ['kjhtml'],

    // web server port
    port: 9876,

    // enable / disable colors in the output (reporters and logs)
    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'],

    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false,

    // Concurrency level
    // how many browser should be started simultaneous
    concurrency: Infinity
  })
}
<commit_msg>implementing tests...<commit_after>// Karma configuration
// Generated on Thu Dec 29 2016 18:10:21 GMT-0200 (Horário brasileiro de verão)

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine'],

    // list of files / patterns to load in the browser
    files: [
      //initialization
      'init.js',

      //third-party dependencies
      '../../node_modules/angular/angular.js',
      '../../node_modules/angular-mocks/angular-mocks.js',
      '../../node_modules/angular-mocks/angular-resource.min.js',

      //file which will be tested
      '../../dist/ui-deni-grid.js',

      //specs
      'specs/*.spec.js'
    ],

    // list of files to exclude
    exclude: [
    ],

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
    },

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],
    //reporters: ['kjhtml'],

    // web server port
    port: 9876,

    // enable / disable colors in the output (reporters and logs)
    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'],

    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false,

    // Concurrency level
    // how many browser should be started simultaneous
    concurrency: Infinity
  })
}
<|endoftext|>