Подтвердить что ты не робот

Как я могу имитировать событие click в моем контрольном тесте AngularJS?

Я пробовал следовать формату ng-directive-testing репо для директивы, которую я написал. Директива в основном создает оверлей, когда пользователь нажимает на элемент. Здесь директива (упрощенная):

mod.directive('uiCopyLinkDialog', function(){
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            var $elm = angular.element(element);
            element.bind('click', function(event) {
                $elm.addClass('test');
            });
        }
    };
});

Тест, который я пишу, выглядит следующим образом:

describe('pre-compiled link', function () {

    beforeEach(mocks.inject(function($compile, $rootScope) {
        scope = $rootScope;
        element = angular.element('<span class="foo" ui-copy-link-dialog="url"></span>');
        $compile(element)(scope);
        scope.$digest();
    }));

    it("should change the class when clicked", function () {
        element.click(); // this returns "'undefined' is not a function"
        element[0].click(); // so does this
        $(elm).click(); // this uses jquery and doesn't *seem* to fail
        waits(500); // hack to see if it was a race condition
        expect(elm.className).toContain('test'); // always fails
    });

});

В тесте вы можете увидеть, что я пытаюсь несколько способов запуска события click() в ссылке, причем большинство из них дает ошибку undefined.

Может ли кто-нибудь сказать мне, что я здесь делаю неправильно? Читая примеры, это звучит как правильный синтаксис, но мой тестовый бегун (Karma via Grunt) не хочет играть в мяч.

4b9b3361

Ответ 1

Таким образом, это оказалось проблемой с PhantomJS: некоторые события, которые действуют на элементы, похоже, не срабатывают, когда элементы на самом деле не находятся на документе нигде, а просто в памяти (это моя теория, во всяком случае). Чтобы обойти это, мне пришлось использовать эту функцию для запуска событий click для элементов:

define(function () {
    return {
        click: function (el) {
            var ev = document.createEvent("MouseEvent");
            ev.initMouseEvent(
                "click",
                true /* bubble */, true /* cancelable */,
                window, null,
                0, 0, 0, 0, /* coordinates */
                false, false, false, false, /* modifier keys */
                0 /*left*/, null
            );
            el.dispatchEvent(ev);
        }
    };
});

Это сработало, хотя другие вещи были сложнее: я также хотел написать тест, который гарантирует, что данная форма input имеет фокус, но получение этого значения было почти невозможно с помощью PhantomJS, так как я предполагаю, что браузер не может сделать что-то сфокусированное если у него нет экранного представления. Любой, кто нуждается в этом, мог бы взглянуть на CasperJS, который предлагает простой API для некоторых из этих требований.

Ответ 2

Вы можете использовать triggerHandler, часть JQLite.

Я использовал это, чтобы вызвать событие click в директиве...

element = angular.element("<div myDirective-on='click'></div>");
compiled = $compile(element)($rootScope);
compiled.triggerHandler('click');

Полный пример доступен в этом блоге: http://sravi-kiran.blogspot.co.nz/2013/12/TriggeringEventsInAngularJsDirectiveTests.html

Ответ 3

Итак, мое решение состояло в том, чтобы фактически добавить элемент в тело. Поскольку проблема с корнем заключается в том, что phantomJs не запускает события для элементов в памяти, казалось бы проще добавить каждый элемент, чтобы события работали реальными.

afterEach(function(){
    $('body').empty();
});

it('should do something when clicked', function(){
    element = $compile('<div my-directive></div>')($scope);
    $('body').append(element);

    // fire all the watches, so the scope expressions will be evaluated
    $rootScope.$digest();

    $(element).find('.my-input').click();
});

Ответ 4

У меня тоже были проблемы с этим. Кажется, что click() не работает для меня с PhantomJS для любого элемента, который я компилирую. Он всегда возвращает undefined.

Хотя это действительно не так хорошо, как фактический щелчок, вы можете получить доступ к директивной функции в ng-click, чтобы имитировать щелчок по ней, чтобы выделить область действия:

var element = $compile('<a ng-click="myfunc()">Click me</a>')(scope);
var isolateScope = element.isolateScope();
isolateScope.myfunc();
scope.$digest();

/* check that things changed ... */

Ответ 5

Вы можете использовать angular-test-runner, и тест будет выглядеть так:

const testRunner = require('angular-test-runner');

describe('directive', () => {
    let app;
    const {expectElement, click} = testRunner.actions;

    beforeEach(() => {
        app = testRunner.app(['mod']);
    });

    it("should add class when clicked", function () {
        const html = app.runHtml('<span class="foo" ui-copy-link-dialog="url"></span>');

        html.perform(
            click.in('.foo')
        );

        html.verify(
            expectElement('.foo').toHaveClass('test')
        );
    });

});

Ответ 6

Как насчет включения angular-scenario, а затем с помощью browserTrigger(element, 'click')?