Skip to content

Commit 924f175

Browse files
test(header-cell): use test harness and add more tests
1 parent 3caf38a commit 924f175

File tree

4 files changed

+202
-38
lines changed

4 files changed

+202
-38
lines changed
Lines changed: 120 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,153 @@
1+
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
2+
import { AfterViewInit, Component, TemplateRef, viewChild } from '@angular/core';
13
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
24

3-
import { TableColumnInternal } from '../../types/internal.types';
5+
import {
6+
InnerSortEvent,
7+
SortableTableColumnInternal,
8+
TableColumnInternal
9+
} from '../../types/internal.types';
10+
import { toInternalColumn } from '../../utils/column-helper';
411
import { DataTableHeaderCellComponent } from './header-cell.component';
12+
import { HeaderCellHarness } from './testing/header-cell.harnes';
513

614
describe('DataTableHeaderCellComponent', () => {
715
let fixture: ComponentFixture<DataTableHeaderCellComponent>;
816
let component: DataTableHeaderCellComponent;
17+
let harness: HeaderCellHarness;
918

10-
beforeEach(waitForAsync(() => {
19+
beforeEach(waitForAsync(async () => {
1120
fixture = TestBed.createComponent(DataTableHeaderCellComponent);
1221
component = fixture.componentInstance;
13-
}));
14-
15-
it('should emit new width on resize', () => {
1622
fixture.componentRef.setInput('column', {
1723
name: 'test',
18-
resizeable: true
24+
prop: 'test',
25+
resizeable: true,
26+
sortable: true
27+
});
28+
fixture.componentRef.setInput('sortType', 'single');
29+
fixture.componentRef.setInput('headerHeight', 50);
30+
fixture.componentRef.setInput('ariaHeaderCheckboxMessage', 'Select all rows');
31+
fixture.componentInstance.sort.subscribe(sort => {
32+
fixture.componentRef.setInput('sorts', [
33+
{
34+
prop: sort.column.name,
35+
dir: sort.newValue
36+
}
37+
]);
1938
});
39+
harness = await TestbedHarnessEnvironment.harnessForFixture(fixture, HeaderCellHarness);
40+
}));
41+
42+
it('should emit new width on resize', async () => {
2043
spyOn(component.resizing, 'emit');
21-
fixture.detectChanges();
22-
const initialWidth = fixture.nativeElement.clientWidth;
23-
const event = new MouseEvent('mousedown');
24-
fixture.nativeElement.querySelector('.resize-handle').dispatchEvent(event);
25-
const mouseMoveEvent = new MouseEvent('mousemove', { screenX: 100 });
26-
document.dispatchEvent(mouseMoveEvent);
27-
const mouseUpEvent = new MouseEvent('mouseup');
28-
document.dispatchEvent(mouseUpEvent);
44+
const initialWidth = await harness.cellWidth();
45+
await harness.resizeCell();
2946
const newWidth = 100 + initialWidth;
3047
expect(component.resizing.emit).toHaveBeenCalledWith({
3148
width: newWidth,
32-
column: { name: 'test', resizeable: true } as TableColumnInternal<any>
49+
column: {
50+
name: 'test',
51+
prop: 'test',
52+
resizeable: true,
53+
sortable: true
54+
} as TableColumnInternal<any>
3355
});
3456
});
3557

36-
it('should emit sort event', () => {
37-
fixture.componentRef.setInput('column', {
38-
prop: 'test',
39-
sortable: true
40-
});
58+
it('should emit sort event', async () => {
4159
spyOn(component.sort, 'emit');
42-
fixture.detectChanges();
43-
const event = new MouseEvent('click');
44-
fixture.nativeElement.querySelector('.datatable-header-cell-label').dispatchEvent(event);
60+
await harness.applySort();
4561
expect(component.sort.emit).toHaveBeenCalled();
4662
});
4763

48-
it('should not render resize handle when showResizeHandle is false (last column)', () => {
49-
fixture.componentRef.setInput('column', {
50-
name: 'test',
51-
resizeable: true
52-
});
64+
it('should not render resize handle when showResizeHandle is false (last column)', async () => {
5365
fixture.componentRef.setInput('showResizeHandle', false);
54-
fixture.detectChanges();
55-
const resizeHandle = fixture.nativeElement.querySelector('.resize-handle');
56-
expect(resizeHandle).toBeNull();
66+
expect(await harness.hasResizeHandle()).toBe(false);
5767
});
5868

59-
it('should render resize handle when showResizeHandle is true', () => {
69+
it('should render resize handle when showResizeHandle is true', async () => {
70+
fixture.componentRef.setInput('showResizeHandle', true);
71+
expect(await harness.hasResizeHandle()).toBe(true);
72+
});
73+
74+
it('should emit select when checkbox is clicked', async () => {
6075
fixture.componentRef.setInput('column', {
6176
name: 'test',
62-
resizeable: true
77+
headerCheckboxable: true
6378
});
64-
fixture.componentRef.setInput('showResizeHandle', true);
79+
spyOn(component.select, 'emit');
80+
await harness.selectAllRows();
81+
expect(component.select.emit).toHaveBeenCalled();
82+
});
83+
84+
it('should toggle sort direction on sort button click', async () => {
85+
await harness.applySort();
86+
expect(await harness.getSortDirection()).toBe('asc');
87+
await harness.applySort();
88+
expect(await harness.getSortDirection()).toBe('desc');
89+
});
90+
91+
it('should sort on enter key press', async () => {
92+
spyOn(component.sort, 'emit');
93+
await harness.applySort(true);
94+
expect(component.sort.emit).toHaveBeenCalled();
95+
});
96+
});
97+
98+
@Component({
99+
imports: [DataTableHeaderCellComponent],
100+
template: `<datatable-header-cell
101+
sortType="single"
102+
headerHeight="50"
103+
[column]="column"
104+
(sort)="sort($event)"
105+
/>
106+
<ng-template #headerCellTemplate let-sort="sortFn" let-column="column">
107+
<span class="custom-header">Custom Header for {{ column.name }}</span>
108+
<button class="custom-sort-button" type="button" (click)="sort($event)"
109+
>Custom sort button</button
110+
>
111+
</ng-template> `
112+
})
113+
class TestHeaderCellComponent implements AfterViewInit {
114+
column: TableColumnInternal<any> = toInternalColumn([
115+
{
116+
name: 'test',
117+
sortable: true
118+
}
119+
])[0];
120+
121+
readonly headerCellTemplate = viewChild('headerCellTemplate', { read: TemplateRef<any> });
122+
123+
sort(event: InnerSortEvent) {}
124+
125+
ngAfterViewInit() {
126+
this.column = { ...this.column, headerTemplate: this.headerCellTemplate() };
127+
}
128+
}
129+
130+
describe('DataTableHeaderCellComponent with template', () => {
131+
let fixture: ComponentFixture<TestHeaderCellComponent>;
132+
let harness: HeaderCellHarness;
133+
134+
beforeEach(waitForAsync(async () => {
135+
fixture = TestBed.createComponent(TestHeaderCellComponent);
136+
harness = await TestbedHarnessEnvironment.harnessForFixture(fixture, HeaderCellHarness);
137+
}));
138+
139+
it('should render custom header template', async () => {
65140
fixture.detectChanges();
66-
const resizeHandle = fixture.nativeElement.querySelector('.resize-handle');
67-
expect(resizeHandle).not.toBeNull();
141+
expect(await harness.getHeaderCellText()).toContain('Custom Header for test');
142+
});
143+
144+
it('should call sort function on custom button click', async () => {
145+
spyOn(fixture.componentInstance, 'sort');
146+
await harness.clickCustomSortButton();
147+
expect(fixture.componentInstance.sort).toHaveBeenCalledWith({
148+
column: fixture.componentInstance.column as SortableTableColumnInternal<any>,
149+
prevValue: undefined,
150+
newValue: 'asc'
151+
});
68152
});
69153
});

projects/ngx-datatable/src/lib/components/header/header-cell.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ export class DataTableHeaderCellComponent implements OnInit, OnDestroy {
296296
}
297297

298298
protected onMousedown(event: MouseEvent | TouchEvent): void {
299-
const isMouse = event instanceof MouseEvent;
299+
const isMouse = event instanceof MouseEvent || event.type === 'mousedown';
300300
const initialWidth = this.element.clientWidth;
301301
const { screenX } = getPositionFromEvent(event);
302302
event.stopPropagation();
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { ComponentHarness } from '@angular/cdk/testing';
2+
3+
export class HeaderCellHarness extends ComponentHarness {
4+
static readonly hostSelector = 'datatable-header-cell';
5+
6+
private cellLabel = this.locatorForOptional('.datatable-header-cell-label');
7+
private cellWrapper = this.locatorFor('.datatable-header-cell-template-wrap');
8+
private cellResizeHandle = this.locatorForOptional('.resize-handle');
9+
private cellCheckbox = this.locatorForOptional('.datatable-checkbox');
10+
private sortBtn = this.locatorForOptional('.sort-btn');
11+
private customSortButton = this.locatorForOptional('.custom-sort-button');
12+
13+
async getHeaderCellText(): Promise<string> {
14+
const label = await this.cellLabel();
15+
const wrapperText = await (await this.cellWrapper()).text();
16+
return label?.text() ?? wrapperText;
17+
}
18+
19+
async hasResizeHandle(): Promise<boolean> {
20+
const resizeHandle = await this.cellResizeHandle();
21+
return !!resizeHandle;
22+
}
23+
24+
async resizeCell(): Promise<void> {
25+
const resizeHandle = await this.cellResizeHandle();
26+
if (resizeHandle) {
27+
await resizeHandle.dispatchEvent('mousedown', { clientX: 0, screenX: 0 });
28+
const mouseMove = new MouseEvent('mousemove', { clientX: 100, screenX: 100 });
29+
document.dispatchEvent(mouseMove);
30+
const mouseUp = new MouseEvent('mouseup');
31+
document.dispatchEvent(mouseUp);
32+
}
33+
}
34+
35+
async selectAllRows(): Promise<void> {
36+
const checkbox = await this.cellCheckbox();
37+
if (checkbox && !(await checkbox.getProperty('checked'))) {
38+
await checkbox.click();
39+
}
40+
}
41+
42+
async applySort(withKeyboard = false): Promise<void> {
43+
const sortButton = await this.sortBtn();
44+
if (sortButton && !withKeyboard) {
45+
await sortButton.click();
46+
} else {
47+
(await this.host()).dispatchEvent('keydown', {
48+
key: 'Enter'
49+
});
50+
}
51+
}
52+
53+
async getSortDirection(): Promise<string | undefined> {
54+
const sortButton = await this.sortBtn();
55+
if (sortButton) {
56+
const isAscending = await sortButton.hasClass('sort-asc');
57+
const isDescending = await sortButton.hasClass('sort-desc');
58+
return isAscending ? 'asc' : isDescending ? 'desc' : undefined;
59+
}
60+
return undefined;
61+
}
62+
63+
async cellWidth(): Promise<number> {
64+
const hostElement = await this.host();
65+
const width = await hostElement.getProperty('offsetWidth');
66+
return width ?? 0;
67+
}
68+
69+
async clickCustomSortButton(): Promise<void> {
70+
const button = await this.customSortButton();
71+
if (button) {
72+
await button.click();
73+
}
74+
}
75+
}

projects/ngx-datatable/src/lib/utils/events.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,10 @@ export const getPositionFromEvent = (
1212
screenX: number;
1313
screenY: number;
1414
} => {
15-
return event instanceof MouseEvent ? (event as MouseEvent) : (event.changedTouches[0] as Touch);
15+
return event instanceof MouseEvent ||
16+
event.type === 'mousedown' ||
17+
event.type === 'mouseup' ||
18+
event.type === 'mousemove'
19+
? (event as MouseEvent)
20+
: (event.changedTouches[0] as Touch);
1621
};

0 commit comments

Comments
 (0)