Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,69 +1,153 @@
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { AfterViewInit, Component, TemplateRef, viewChild } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';

import { TableColumnInternal } from '../../types/internal.types';
import {
InnerSortEvent,
SortableTableColumnInternal,
TableColumnInternal
} from '../../types/internal.types';
import { toInternalColumn } from '../../utils/column-helper';
import { DataTableHeaderCellComponent } from './header-cell.component';
import { HeaderCellHarness } from './testing/header-cell.harnes';

describe('DataTableHeaderCellComponent', () => {
let fixture: ComponentFixture<DataTableHeaderCellComponent>;
let component: DataTableHeaderCellComponent;
let harness: HeaderCellHarness;

beforeEach(waitForAsync(() => {
beforeEach(waitForAsync(async () => {
fixture = TestBed.createComponent(DataTableHeaderCellComponent);
component = fixture.componentInstance;
}));

it('should emit new width on resize', () => {
fixture.componentRef.setInput('column', {
name: 'test',
resizeable: true
prop: 'test',
resizeable: true,
sortable: true
});
fixture.componentRef.setInput('sortType', 'single');
fixture.componentRef.setInput('headerHeight', 50);
fixture.componentRef.setInput('ariaHeaderCheckboxMessage', 'Select all rows');
fixture.componentInstance.sort.subscribe(sort => {
fixture.componentRef.setInput('sorts', [
{
prop: sort.column.name,
dir: sort.newValue
}
]);
});
harness = await TestbedHarnessEnvironment.harnessForFixture(fixture, HeaderCellHarness);
}));

it('should emit new width on resize', async () => {
spyOn(component.resizing, 'emit');
fixture.detectChanges();
const initialWidth = fixture.nativeElement.clientWidth;
const event = new MouseEvent('mousedown');
fixture.nativeElement.querySelector('.resize-handle').dispatchEvent(event);
const mouseMoveEvent = new MouseEvent('mousemove', { screenX: 100 });
document.dispatchEvent(mouseMoveEvent);
const mouseUpEvent = new MouseEvent('mouseup');
document.dispatchEvent(mouseUpEvent);
const initialWidth = await harness.cellWidth();
await harness.resizeCell();
const newWidth = 100 + initialWidth;
expect(component.resizing.emit).toHaveBeenCalledWith({
width: newWidth,
column: { name: 'test', resizeable: true } as TableColumnInternal<any>
column: {
name: 'test',
prop: 'test',
resizeable: true,
sortable: true
} as TableColumnInternal<any>
});
});

it('should emit sort event', () => {
fixture.componentRef.setInput('column', {
prop: 'test',
sortable: true
});
it('should emit sort event', async () => {
spyOn(component.sort, 'emit');
fixture.detectChanges();
const event = new MouseEvent('click');
fixture.nativeElement.querySelector('.datatable-header-cell-label').dispatchEvent(event);
await harness.applySort();
expect(component.sort.emit).toHaveBeenCalled();
});

it('should not render resize handle when showResizeHandle is false (last column)', () => {
fixture.componentRef.setInput('column', {
name: 'test',
resizeable: true
});
it('should not render resize handle when showResizeHandle is false (last column)', async () => {
fixture.componentRef.setInput('showResizeHandle', false);
fixture.detectChanges();
const resizeHandle = fixture.nativeElement.querySelector('.resize-handle');
expect(resizeHandle).toBeNull();
expect(await harness.hasResizeHandle()).toBe(false);
});

it('should render resize handle when showResizeHandle is true', () => {
it('should render resize handle when showResizeHandle is true', async () => {
fixture.componentRef.setInput('showResizeHandle', true);
expect(await harness.hasResizeHandle()).toBe(true);
});

it('should emit select when checkbox is clicked', async () => {
fixture.componentRef.setInput('column', {
name: 'test',
resizeable: true
headerCheckboxable: true
});
fixture.componentRef.setInput('showResizeHandle', true);
spyOn(component.select, 'emit');
await harness.selectAllRows();
expect(component.select.emit).toHaveBeenCalled();
});

it('should toggle sort direction on sort button click', async () => {
await harness.applySort();
expect(await harness.getSortDirection()).toBe('asc');
await harness.applySort();
expect(await harness.getSortDirection()).toBe('desc');
});

it('should sort on enter key press', async () => {
spyOn(component.sort, 'emit');
await harness.applySort(true);
expect(component.sort.emit).toHaveBeenCalled();
});
});

@Component({
imports: [DataTableHeaderCellComponent],
template: `<datatable-header-cell
sortType="single"
headerHeight="50"
[column]="column"
(sort)="sort($event)"
/>
<ng-template #headerCellTemplate let-sort="sortFn" let-column="column">
<span class="custom-header">Custom Header for {{ column.name }}</span>
<button class="custom-sort-button" type="button" (click)="sort($event)"
>Custom sort button</button
>
</ng-template> `
})
class TestHeaderCellComponent implements AfterViewInit {
column: TableColumnInternal<any> = toInternalColumn([
{
name: 'test',
sortable: true
}
])[0];

readonly headerCellTemplate = viewChild('headerCellTemplate', { read: TemplateRef<any> });

sort(event: InnerSortEvent) {}

ngAfterViewInit() {
this.column = { ...this.column, headerTemplate: this.headerCellTemplate() };
}
}

describe('DataTableHeaderCellComponent with template', () => {
let fixture: ComponentFixture<TestHeaderCellComponent>;
let harness: HeaderCellHarness;

beforeEach(waitForAsync(async () => {
fixture = TestBed.createComponent(TestHeaderCellComponent);
harness = await TestbedHarnessEnvironment.harnessForFixture(fixture, HeaderCellHarness);
}));

it('should render custom header template', async () => {
fixture.detectChanges();
const resizeHandle = fixture.nativeElement.querySelector('.resize-handle');
expect(resizeHandle).not.toBeNull();
expect(await harness.getHeaderCellText()).toContain('Custom Header for test');
});

it('should call sort function on custom button click', async () => {
spyOn(fixture.componentInstance, 'sort');
await harness.clickCustomSortButton();
expect(fixture.componentInstance.sort).toHaveBeenCalledWith({
column: fixture.componentInstance.column as SortableTableColumnInternal<any>,
prevValue: undefined,
newValue: 'asc'
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ export class DataTableHeaderCellComponent implements OnInit, OnDestroy {
}

protected onMousedown(event: MouseEvent | TouchEvent): void {
const isMouse = event instanceof MouseEvent;
const isMouse = event instanceof MouseEvent || event.type === 'mousedown';
const initialWidth = this.element.clientWidth;
const { screenX } = getPositionFromEvent(event);
event.stopPropagation();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ComponentHarness } from '@angular/cdk/testing';

export class HeaderCellHarness extends ComponentHarness {
static readonly hostSelector = 'datatable-header-cell';

private cellLabel = this.locatorForOptional('.datatable-header-cell-label');
private cellWrapper = this.locatorFor('.datatable-header-cell-template-wrap');
private cellResizeHandle = this.locatorForOptional('.resize-handle');
private cellCheckbox = this.locatorForOptional('.datatable-checkbox');
private sortBtn = this.locatorForOptional('.sort-btn');
private customSortButton = this.locatorForOptional('.custom-sort-button');

async getHeaderCellText(): Promise<string> {
const label = await this.cellLabel();
const wrapperText = await (await this.cellWrapper()).text();
return label?.text() ?? wrapperText;
}

async hasResizeHandle(): Promise<boolean> {
const resizeHandle = await this.cellResizeHandle();
return !!resizeHandle;
}

async resizeCell(): Promise<void> {
const resizeHandle = await this.cellResizeHandle();
if (resizeHandle) {
await resizeHandle.dispatchEvent('mousedown', { clientX: 0, screenX: 0 });
const mouseMove = new MouseEvent('mousemove', { clientX: 100, screenX: 100 });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those should be parameter

document.dispatchEvent(mouseMove);
const mouseUp = new MouseEvent('mouseup');
document.dispatchEvent(mouseUp);
}
}

async selectAllRows(): Promise<void> {
const checkbox = await this.cellCheckbox();
if (checkbox && !(await checkbox.getProperty('checked'))) {
await checkbox.click();
}
}

async applySort(withKeyboard = false): Promise<void> {
const sortButton = await this.sortBtn();
if (sortButton && !withKeyboard) {
await sortButton.click();
} else {
(await this.host()).dispatchEvent('keydown', {
key: 'Enter'
});
}
}

async getSortDirection(): Promise<string | undefined> {
const sortButton = await this.sortBtn();
if (sortButton) {
const isAscending = await sortButton.hasClass('sort-asc');
const isDescending = await sortButton.hasClass('sort-desc');
return isAscending ? 'asc' : isDescending ? 'desc' : undefined;
}
return undefined;
}

async cellWidth(): Promise<number> {
const hostElement = await this.host();
const width = await hostElement.getProperty('offsetWidth');
return width ?? 0;
}

async clickCustomSortButton(): Promise<void> {
const button = await this.customSortButton();
if (button) {
await button.click();
}
Comment on lines +70 to +73
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const button = await this.customSortButton();
if (button) {
await button.click();
}
const button = await this.customSortButton();
await button!.click();

I think failing silently is not ideal here

}
}
7 changes: 6 additions & 1 deletion projects/ngx-datatable/src/lib/utils/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,10 @@ export const getPositionFromEvent = (
screenX: number;
screenY: number;
} => {
return event instanceof MouseEvent ? (event as MouseEvent) : (event.changedTouches[0] as Touch);
return event instanceof MouseEvent ||
event.type === 'mousedown' ||
event.type === 'mouseup' ||
event.type === 'mousemove'
? (event as MouseEvent)
: (event.changedTouches[0] as Touch);
};
Loading