A guide on simulating mouse events in Angular for testing Directives

I am currently exploring the functionality of a column resizable directive that relies on mouse events such as mouseup, mousemove, and mousedown.

resize-column.directive.ts

import { Directive, OnInit, Renderer2, Input, ElementRef, HostListener } from "@angular/core";

@Directive({
  selector: "[resizeColumn]"
})
export class ResizeColumnDirective implements OnInit {

  @Input() index: number;    
  private startX: number;    
  private startWidth: number;    
  private column: HTMLElement;    
  private table: HTMLElement;    
  private pressed: boolean;

  constructor(private renderer: Renderer2, private el: ElementRef) {
    this.column = this.el.nativeElement;
  }

  ngOnInit() {
    if (this.resizable) {
      const row = this.renderer.parentNode(this.column);
      this.table = this.renderer.parentNode(row);

      const resizer = this.renderer.createElement("span");
      this.renderer.addClass(resizer, "resize-holder");
      this.renderer.appendChild(this.column, resizer);
      this.renderer.listen(resizer, "mousedown", this.onMouseDown);
    }
  }

  onMouseDown = (event: MouseEvent) => {
    this.pressed = true;
    this.startX = event.pageX;
    this.startWidth = this.column.offsetWidth;
  };

  @HostListener('document: mousemove')
  onMouseMove(event: MouseEvent) {
    const offset = 35;
    if (this.pressed && event.buttons) {
      this.renderer.addClass(this.table, "resizing");

      // Calculate width of column
      let width =
        this.startWidth + (event.pageX - this.startX - offset);

      const tableCells = Array.from(this.table.querySelectorAll(".custom-row")).map(
        (row: any) => row.querySelectorAll(".custom-cell").item(this.index)
      );

      // Set table header width
      this.renderer.setStyle(this.column, "max-width", `${width}px`);
      this.renderer.setStyle(this.column, "flex-basis", `${width}px`);

      // Set table cells width
      for (const cell of tableCells) {
        this.renderer.setStyle(cell, "max-width", `${width}px`);
        this.renderer.setStyle(cell, "flex-basis", `${width}px`);
      }
    }
  };

  @HostListener('document: mouseup')
  onMouseUp(){
    if (this.pressed) {
      this.pressed = false;
      this.renderer.removeClass(this.table, "resizing");
    }
  };
}

I am looking to create unit tests for this directive but I am encountering difficulties in simulating mouse events. I have tried using triggerEventHandler to handle events but have not been successful in updating the values of max-width and flex-basis after simulating mousedown and mousemove events.

resize-column.directive.spec.ts

import { Component, DebugElement } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { ResizeColumnDirective } from './resize-column.directive';

@Component({
    template: `
    <custom-table>
        <custom-row>
            <custom-header *ngFor="let column of displayedColumns; let i = index"
                 resizeColumn [index]="i">
                {{column.label}}
            </custom-header>
        </custom-row>
        <custom-row *ngFor="let row of rowDatas">
            <custom-cell *ngFor="let column of displayedColumns">{{row[column.field]}}
            </custom-cell>
        </custom-row>
    </custom-table>`
})
class TestComponent {
    displayedColumns = [
        { field: 'name', label: 'Name' },
        { field: 'sex', label: 'Sex' },
        { field: 'age', label: 'Age' }
    ];

    rowDatas = [{
        name: 'Albert',
        sex: 'M',
        age: '20'
    }.....];
}

describe('ResizableDirective', () => {
    let component: TestComponent;
    let fixture: ComponentFixture<TestComponent>;

    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [ResizeColumnDirective, TestComponent, table component components....]
        });

        fixture = TestBed.createComponent(TestComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
    });

    it('should create the component successfully', () => {
        expect(component).toBeDefined();
    });

    it('should correctly update the flex and max-width values on mouse move', () => {
        const headerEl = fixture.debugElement.query(By.css('custom-header'));
        console.log(headerEl.nativeElement.style.flex);
        headerEl.triggerEventHandler('mousedown', { pageX: 50 });
        headerEl.triggerEventHandler('mousemove', { pageX: 150 });
        fixture.detectChanges();
        console.log(headerEl.nativeElement.style.flex);
    });
});

I would appreciate guidance on how to effectively test this directive and welcome suggestions on alternative methods to write tests for this, as I am relatively new to Jasmine. Thank you in advance.

Answer №1

Consider using:

headerEl.dispatchEvent(new MouseEvent('mousemove', {clientX: 50, clientY: 150}));

or

headerEl.dispatchEvent(new Event('mousemove', {pageX: 50, pageY: 150}));

Update:

After reviewing your code, there are two key points to note:

  1. To simulate a mouse move event, utilize
    document.dispatchEvent(new MouseEvent('mousemove', {clientX: 50, clientY: 150, buttons: 1}))
  2. Include '$event' in your @HostListener to avoid undefined values in the handler
@HostListener('document: mousemove', ['$event'])
onMouseMove(event: MouseEvent) {
  const offset = 35;
  if (this.pressed && event.buttons) {
    //your code
  }
};

Remember that the variable pressed must be set to true for your code to function properly

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

npm error: JSON input unexpectedly ended while parsing

Encountered an error when running ng new project-name in my Angular project. npm WARN deprecated [email protected]: CircularJSON is in maintenance only, flatted is its successor. npm ERR! Unexpected end of JSON input while parsing near '...: ...

vuejs default properties initialized with vue-i18n

I am trying to establish a default property from a dictionary in this way: props: { title: { type: String, default: this.$t("basic.confirm"), }, description: { type: String, } }, ... The $t function is part of the vu ...

Focusing on a text field after reloading a different div with AJAX

I've been spending a lot of time figuring out the following issue and I'm hoping someone can help me find the solution. My web application has an input field (type="text") that is ready to accept user input when the page loads. When the user nav ...

Retrieve information from an external JSON source

I am receiving data from an API and storing its JSON in a separate file. The unique URL for accessing this file is shown below: http://events.com/rsvp.php?id=1234 The JSON content on the specified page appears like this: { rsvp: true } Each contact ha ...

JavaScript: Trouble with statement execution

My code is designed to classify a point as 1 if it's above the line y=x, and -1 if it's below the line y=x. I visually represent this line in a canvas by plotting y=x (although due to invertion on the y-axis, it appears like y=-x). For each point ...

resetting dropdown selections upon page refresh using jQuery and AJAX

Is there a way to reset or clear the values of two select boxes after refreshing the page in CodeIgniter? Currently, both select boxes retain their values after a refresh. Below is the code I am using: <?php echo form_dropdown('cat_id', $ ...

Synchronize Protractor with an Angular application embedded within an iframe on a non-Angular web platform

I'm having trouble accessing elements using methods like by.binding(). The project structure looks like this: There is a non-angular website | --> Inside an iframe | --> There is an angular app Here's a part of the code I'm ...

Incorporate zoom feature into the jQuery polaroid gallery

Currently, I am utilizing a jQuery and CSS3 photo gallery found on this website. My goal is to allow the images to enlarge when clicked, however, the method provided by the author isn't very clear to me, as I'm not an expert in jQuery. I attempt ...

What methods can I use to dismantle an Angular component?

I have created a modal as a component called <modal-component>. Within the <modal-component>, there is a close button. I am looking for a way to completely remove the <modal-component> when this close button is clicked. I envision somet ...

How can I assign the output of a function to a variable within a class in Angular?

Is there a way for the Army class to automatically update its CP property with the sum of CP values from all Detachments in the Detachment class? In the Army class, the CP property should reflect the total CP value from all Detachments and be accessible t ...

Modify object rotation animation direction using keyboard controls in Three.js

Adjusting the object rotation direction with key controls is within my capability by utilizing the following code: case 37: scene.rotation.x -= 0.01; break case 38: scene.rotation.z -= 0.01 break Nevertheless, the rotation remai ...

An issue with Ajax's syntax

Help needed with my ajax code. I'm encountering an error while trying to send data in Ajax - specifically with the data syntax. Despite several attempts, I have not been able to successfully resolve this issue. Here is the portion of code causing tro ...

Exploring Mikro-ORM with Ben Awad's Lireddit: Navigating the Process of Running Initial Migrations

Having some trouble following the lireddit tutorial, particularly with the initial mikro-orm migration step. Encountering a similar issue as mentioned in this post. Tried modifying the constructor of the example entity (tried both provided format and the ...

Navigate to a different route in AntD Table by clicking on it

I am currently implementing React Router in my navigation component. My goal is to enable users to navigate from screen Y to screen X when they click on a table element. In the past, I achieved this by using "this" command but it seems that it does not ...

How can you obtain values from a nested JSON object and combine them together?

I have a javascript object that is structured in the following format. My goal is to combine the Name and Status values for each block and then store them in an array. { "datatype": "local", "data": [ { "Name": "John", ...

Interacting with touch events in JavaScript on Android devices

I am facing an issue with my HTML page where a div is meant to function as an on-off switch momentarily. The functionality I have implemented looks like this: $('#btn').mousedown(function() { startAction() }) $('#btn ...

Implementing NestJS: Integrating TypeORM Datasource without relying on class dependency injection

I have a unique situation that requires some help. Our team is in the process of integrating nestjs into our current express codebase. Previously, we were using Typeorm 0.2 and recently upgraded to 0.3. Due to the fact that we utilize functions instead of ...

The identifier "resolve" in the catch block has not been defined

Why is it not possible to call resolve in the catch block? I wanted to catch a failed request and attempt it again in the catch block, but I am encountering an issue where resolve is not defined. I am confused since I am inside of the promise, so why is i ...

What is the best way to separate an ellipse into evenly sized portions?

This function is used to determine the coordinates of a vertex on an ellipse: function calculateEllipse(a, b, angle) { var alpha = angle * (Math.PI / 180) ; var sinalpha = Math.sin(alpha); var cosalpha = Math.cos(alpha); var X = a * cosa ...

Strategies for disabling middle mouse button default behavior in Vue

When I use @click.middle.stop.prevent="test", the default scroll wheel still shows up despite it detecting the middle mouse click and calling my function. Is there a way to prevent this from happening? ...