Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
Expand Up @@ -552,6 +552,186 @@ describe('IgxMonthPicker', () => {
currentValue: new Date(2019, 1, 1)
});
});

it('should return the correct next and previous years via getNextYear/getPreviousYear', () => {
const fixture = TestBed.createComponent(IgxMonthPickerSampleComponent);
fixture.detectChanges();
const monthPicker = fixture.componentInstance.monthPicker;
// viewDate is 2019
expect(monthPicker.getNextYear()).toBe(2020);
expect(monthPicker.getPreviousYear()).toBe(2018);
});

it('should navigate forward one year via PageDown in default (year) view', () => {
const fixture = TestBed.createComponent(IgxMonthPickerSampleComponent);
fixture.detectChanges();
const monthPicker = fixture.componentInstance.monthPicker;
const wrapper = fixture.debugElement.query(By.css('.igx-calendar__wrapper'));
wrapper.nativeElement.focus();
fixture.detectChanges();

const initialYear = monthPicker.viewDate.getFullYear();
UIInteractions.triggerKeyDownEvtUponElem('PageDown', document.activeElement);
fixture.detectChanges();

expect(monthPicker.viewDate.getFullYear()).toBe(initialYear + 1);
});

it('should navigate backward one year via PageUp in default (year) view', () => {
const fixture = TestBed.createComponent(IgxMonthPickerSampleComponent);
fixture.detectChanges();
const monthPicker = fixture.componentInstance.monthPicker;
const wrapper = fixture.debugElement.query(By.css('.igx-calendar__wrapper'));
wrapper.nativeElement.focus();
fixture.detectChanges();

const initialYear = monthPicker.viewDate.getFullYear();
UIInteractions.triggerKeyDownEvtUponElem('PageUp', document.activeElement);
fixture.detectChanges();

expect(monthPicker.viewDate.getFullYear()).toBe(initialYear - 1);
});

it('should navigate forward by 15 years via Shift+PageDown in default (year) view', () => {
const fixture = TestBed.createComponent(IgxMonthPickerSampleComponent);
fixture.detectChanges();
const monthPicker = fixture.componentInstance.monthPicker;
const wrapper = fixture.debugElement.query(By.css('.igx-calendar__wrapper'));
wrapper.nativeElement.focus();
fixture.detectChanges();

const initialYear = monthPicker.viewDate.getFullYear();
UIInteractions.triggerKeyDownEvtUponElem('PageDown', document.activeElement, true, false, true);
fixture.detectChanges();

// Shift+PageDown in year view increments by 1 year (delta=1) using viewDate shift
expect(monthPicker.viewDate.getFullYear()).toBe(initialYear + 1);
});
Comment on lines +595 to +609
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

Test name says “forward by 15 years via Shift+PageDown”, but the assertion (and implementation) advances by 1 year in the default/year view when Shift is held. Rename the spec to match the actual behavior being tested to avoid confusion.

Copilot uses AI. Check for mistakes.

it('should navigate backward one page via PageUp in decade (years) view', () => {
const fixture = TestBed.createComponent(IgxMonthPickerSampleComponent);
fixture.detectChanges();
const monthPicker = fixture.componentInstance.monthPicker;
// Switch to decade view
monthPicker.activeView = IgxCalendarView.Decade;
fixture.detectChanges();

const wrapper = fixture.debugElement.query(By.css('.igx-calendar__wrapper'));
wrapper.nativeElement.focus();
fixture.detectChanges();

const initialYear = monthPicker.viewDate.getFullYear();
UIInteractions.triggerKeyDownEvtUponElem('PageUp', document.activeElement);
fixture.detectChanges();

// In decade view, PageUp calls previousPage which moves back 15 years
expect(monthPicker.viewDate.getFullYear()).toBe(initialYear - 15);
});

it('should navigate forward one page via PageDown in decade (years) view', () => {
const fixture = TestBed.createComponent(IgxMonthPickerSampleComponent);
fixture.detectChanges();
const monthPicker = fixture.componentInstance.monthPicker;
monthPicker.activeView = IgxCalendarView.Decade;
fixture.detectChanges();

const wrapper = fixture.debugElement.query(By.css('.igx-calendar__wrapper'));
wrapper.nativeElement.focus();
fixture.detectChanges();

const initialYear = monthPicker.viewDate.getFullYear();
UIInteractions.triggerKeyDownEvtUponElem('PageDown', document.activeElement);
fixture.detectChanges();

// In decade view, PageDown calls nextPage which moves forward 15 years
expect(monthPicker.viewDate.getFullYear()).toBe(initialYear + 15);
});

it('should navigate to January via Home key in default (year) view', () => {
const fixture = TestBed.createComponent(IgxMonthPickerSampleComponent);
fixture.detectChanges();
const monthPicker = fixture.componentInstance.monthPicker;
const wrapper = fixture.debugElement.query(By.css('.igx-calendar__wrapper'));
wrapper.nativeElement.focus();
fixture.detectChanges();

UIInteractions.triggerKeyDownEvtUponElem('Home', document.activeElement);
fixture.detectChanges();

const dom = fixture.debugElement;
const selected = dom.query(By.css('.igx-calendar-view__item--selected'));
expect(selected.nativeElement.textContent.trim()).toMatch('Jan');
});

it('should navigate to December via End key in default (year) view', () => {
const fixture = TestBed.createComponent(IgxMonthPickerSampleComponent);
fixture.detectChanges();
const monthPicker = fixture.componentInstance.monthPicker;
const wrapper = fixture.debugElement.query(By.css('.igx-calendar__wrapper'));
wrapper.nativeElement.focus();
fixture.detectChanges();

UIInteractions.triggerKeyDownEvtUponElem('End', document.activeElement);
fixture.detectChanges();

const dom = fixture.debugElement;
const selected = dom.query(By.css('.igx-calendar-view__item--selected'));
expect(selected.nativeElement.textContent.trim()).toMatch('Dec');
});

it('should navigate to first year in view via Home key in decade (years) view', () => {
const fixture = TestBed.createComponent(IgxMonthPickerSampleComponent);
fixture.detectChanges();
const monthPicker = fixture.componentInstance.monthPicker;
monthPicker.activeView = IgxCalendarView.Decade;
fixture.detectChanges();

const wrapper = fixture.debugElement.query(By.css('.igx-calendar__wrapper'));
wrapper.nativeElement.focus();
fixture.detectChanges();

UIInteractions.triggerKeyDownEvtUponElem('Home', document.activeElement);
fixture.detectChanges();

const dom = fixture.debugElement;
const years = dom.queryAll(By.css('.igx-calendar-view__item'));
const selected = dom.query(By.css('.igx-calendar-view__item--selected'));
expect(selected.nativeElement).toBe(years[0].nativeElement);
});

it('should navigate to last year in view via End key in decade (years) view', () => {
const fixture = TestBed.createComponent(IgxMonthPickerSampleComponent);
fixture.detectChanges();
const monthPicker = fixture.componentInstance.monthPicker;
monthPicker.activeView = IgxCalendarView.Decade;
fixture.detectChanges();

const wrapper = fixture.debugElement.query(By.css('.igx-calendar__wrapper'));
wrapper.nativeElement.focus();
fixture.detectChanges();

UIInteractions.triggerKeyDownEvtUponElem('End', document.activeElement);
fixture.detectChanges();

const dom = fixture.debugElement;
const years = dom.queryAll(By.css('.igx-calendar-view__item'));
const selected = dom.query(By.css('.igx-calendar-view__item--selected'));
expect(selected.nativeElement).toBe(years[years.length - 1].nativeElement);
});

it('should change the active view to decade via activeViewDecade and focus the years view', () => {
const fixture = TestBed.createComponent(IgxMonthPickerSampleComponent);
fixture.detectChanges();
const monthPicker = fixture.componentInstance.monthPicker;

expect(monthPicker.activeView).toBe(IgxCalendarView.Year);

const yearBtn = fixture.debugElement.query(By.css('.igx-calendar-picker__date'));
UIInteractions.simulateMouseDownEvent(yearBtn.nativeElement);
fixture.detectChanges();

expect(monthPicker.activeView).toBe(IgxCalendarView.Decade);
});
});

@Component({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1924,6 +1924,140 @@ describe('Nested igxDrag elements', () => {
});
})

describe('igxDrag touch, mouse, pointerLost and shadow root coverage', () => {
let fix: ComponentFixture<TestDragDropComponent>;
let dragDirsRects: { top: number; left: number; right: number; bottom: number }[];

beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [TestDragDropComponent]
}).compileComponents();
}));

beforeEach(() => {
fix = TestBed.createComponent(TestDragDropComponent);
fix.detectChanges();
dragDirsRects = getDragDirsRects(fix.componentInstance.dragElems);
});

it('should handle touchstart event to initiate drag when touch events are used', async () => {
const firstDrag = fix.componentInstance.dragElems.first;
const firstElement = firstDrag.element.nativeElement;
const startingX = (dragDirsRects[0].left + dragDirsRects[0].right) / 2;
const startingY = (dragDirsRects[0].top + dragDirsRects[0].bottom) / 2;

spyOn(firstDrag.dragStart, 'emit');

// In Chrome headless, pointerEventsEnabled is true (PointerEvent is defined).
// Mock the properties so the touch path is taken in ngAfterContentInit
spyOnProperty(firstDrag, 'pointerEventsEnabled').and.returnValue(false);
spyOnProperty(firstDrag, 'touchEventsEnabled').and.returnValue(true);

// Re-bind events with the mocked touch path
firstDrag.ngAfterContentInit();

// Simulate touchstart — triggers onPointerDown via touchstart path
UIInteractions.simulateTouchStartEvent(firstElement, startingX, startingY);
fix.detectChanges();
await wait();

// Simulate touch move via document.defaultView (bound by ngAfterContentInit touch path)
UIInteractions.simulateTouchMoveEvent(document.defaultView, startingX + 20, startingY + 20);
fix.detectChanges();
await wait(100);

// After a 20px move the drag should have started
expect(firstDrag.dragStart.emit).toHaveBeenCalled();

UIInteractions.simulateTouchEndEvent(document.defaultView, startingX + 20, startingY + 20);
fix.detectChanges();
await wait();
});

it('should handle mousedown event to initiate drag when mouse events are used', async () => {
const firstDrag = fix.componentInstance.dragElems.first;
const firstElement = firstDrag.element.nativeElement;
const startingX = (dragDirsRects[0].left + dragDirsRects[0].right) / 2;
const startingY = (dragDirsRects[0].top + dragDirsRects[0].bottom) / 2;

spyOn(firstDrag.dragStart, 'emit');
// Spy on pointerEventsEnabled to return false so the mousedown path is taken
spyOnProperty(firstDrag, 'pointerEventsEnabled').and.returnValue(false);
spyOnProperty(firstDrag, 'touchEventsEnabled').and.returnValue(false);

// Re-init the event subscriptions with the mocked properties
firstDrag.ngAfterContentInit();

UIInteractions.simulateMouseEvent('mousedown', firstElement, startingX, startingY);
fix.detectChanges();
await wait();

UIInteractions.simulateMouseEvent('mousemove', document.body, startingX + 20, startingY + 20);
fix.detectChanges();
await wait(100);

expect(firstDrag.dragStart.emit).toHaveBeenCalled();

UIInteractions.simulateMouseEvent('mouseup', document.body, startingX + 20, startingY + 20);
fix.detectChanges();
await wait();
});

it('should call onPointerLost early return when _clicked is false', async () => {
const firstDrag = fix.componentInstance.dragElems.first;

spyOn(firstDrag.dragEnd, 'emit');

// _clicked starts as false — calling onPointerLost should return immediately
firstDrag.onPointerLost({ pageX: 100, pageY: 100 });

expect(firstDrag.dragEnd.emit).not.toHaveBeenCalled();
});

it('should emit dragEnd on onPointerLost when drag was in progress', async () => {
const firstDrag = fix.componentInstance.dragElems.first;
const firstElement = firstDrag.element.nativeElement;
const startingX = (dragDirsRects[0].left + dragDirsRects[0].right) / 2;
const startingY = (dragDirsRects[0].top + dragDirsRects[0].bottom) / 2;

spyOn(firstDrag.dragEnd, 'emit');

UIInteractions.simulatePointerEvent('pointerdown', firstElement, startingX, startingY);
fix.detectChanges();
await wait();

UIInteractions.simulatePointerEvent('pointermove', firstElement, startingX + 10, startingY + 10);
fix.detectChanges();
await wait(100);

// Ghost is now active — onPointerLost should emit dragEnd
UIInteractions.simulatePointerEvent('lostpointercapture', firstDrag.ghostElement, startingX + 10, startingY + 10);
fix.detectChanges();
await wait();

expect(firstDrag.dragEnd.emit).toHaveBeenCalled();
});

it('should return elements from shadow root via getFromShadowRoot', () => {
const firstDrag = fix.componentInstance.dragElems.first;

// Create a mock element with a shadowRoot that returns elements from point
const innerElem = document.createElement('span');
const shadowHost = document.createElement('div');
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
shadowRoot.appendChild(innerElem);

const mockParentElems = [shadowHost];

// Mock elementsFromPoint to return our inner element
spyOn(shadowRoot, 'elementsFromPoint').and.returnValue([innerElem]);

const result = (firstDrag as any).getFromShadowRoot(shadowHost, 100, 100, mockParentElems);

expect(result).toContain(innerElem);
});
})

const getDragDirsRects = (dragDirs: QueryList<IgxDragDirective>) => {
const dragDirsRects = [];
dragDirs.forEach((dragDir) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,40 @@ describe('igxMask', () => {
expect(inputElement.nativeElement.selectionEnd).toEqual(8);
expect(inputHTMLElement.value).toEqual('(123) __67-890');
});

it('should handle deleteContentBackward input event after compositionend (line 209 branch)', fakeAsync(() => {
// This test covers the branch at line 209 of mask.directive.ts:
// After compositionend, Chromium fires an input event with inputType='deleteContentBackward'.
// The branch adjusts start/end indexes to account for mask literals.
const fixture = TestBed.createComponent(MaskComponent);
fixture.detectChanges();
tick();

const inputDebugEl = fixture.debugElement.query(By.css('input'));
const inputEl = inputDebugEl.nativeElement as HTMLInputElement;

// Focus and set a starting selection
inputDebugEl.triggerEventHandler('focus', {});
fixture.detectChanges();

// Use compositionstart → compositionend to set up _compositionStartIndex and _compositionValue
UIInteractions.simulateCompositionEvent('123', inputDebugEl, 0, 3, false);
fixture.detectChanges();
tick();

// Now trigger an InputEvent with inputType='deleteContentBackward' and _key != BACKSPACE
// This is the path Chromium takes after compositionend
const deleteEvent = new InputEvent('input', {
bubbles: true,
inputType: 'deleteContentBackward'
});
inputEl.dispatchEvent(deleteEvent);
fixture.detectChanges();
tick();

// The branch should have run without throwing; mask is still applied
expect(inputEl.value).toBeDefined();
}));
});

describe('igxMaskDirective ControlValueAccessor Unit', () => {
Expand Down
Loading
Loading