Skip to content

Commit

Permalink
fix(datepicker): keep day/month when adding/removing month/year in ng…
Browse files Browse the repository at this point in the history
…b-calendar (#3355)

Part of #3398
  • Loading branch information
gpolychronis authored and maxokorokov committed Oct 4, 2019
1 parent c169b08 commit 22a1cb6
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 31 deletions.
16 changes: 8 additions & 8 deletions e2e-app/src/app/datepicker/focus/datepicker-focus.e2e-spec.ts
Expand Up @@ -270,36 +270,36 @@ describe('Datepicker', () => {
await expectFocused(page.getDayElement(new Date(2018, 7, 31)), `Last day of month should be focused`);
});

it(`should focus first day of previous month with 'PageUp'`, async() => {
it(`should focus same day of previous month with 'PageUp'`, async() => {
await page.preSelectDate(); // 10 AUG 2018
await page.openDatepicker();

await sendKey(Key.PAGE_UP);
await expectFocused(page.getDayElement(new Date(2018, 6, 1)), `First day of previous month should be focused`);
await expectFocused(page.getDayElement(new Date(2018, 6, 10)), `Same day of previous month should be focused`);
});

it(`should focus first day of next month with 'PageDown'`, async() => {
it(`should focus same day of next month with 'PageDown'`, async() => {
await page.preSelectDate(); // 10 AUG 2018
await page.openDatepicker();

await sendKey(Key.PAGE_DOWN);
await expectFocused(page.getDayElement(new Date(2018, 8, 1)), `First day of next month should be focused`);
await expectFocused(page.getDayElement(new Date(2018, 8, 10)), `Same day of next month should be focused`);
});

it(`should focus first day of previous year with 'Shift+PageUp'`, async() => {
it(`should focus same day of previous year with 'Shift+PageUp'`, async() => {
await page.preSelectDate(); // 10 AUG 2018
await page.openDatepicker();

await sendKey(Key.SHIFT, Key.PAGE_UP);
await expectFocused(page.getDayElement(new Date(2017, 0, 1)), `First day of previous year should be focused`);
await expectFocused(page.getDayElement(new Date(2017, 7, 10)), `Same day of previous year should be focused`);
});

it(`should focus first day of next year with 'Shift+PageDown'`, async() => {
it(`should focus same day of next year with 'Shift+PageDown'`, async() => {
await page.preSelectDate(); // 10 AUG 2018
await page.openDatepicker();

await sendKey(Key.SHIFT, Key.PAGE_DOWN);
await expectFocused(page.getDayElement(new Date(2019, 0, 1)), `First day of next year should be focused`);
await expectFocused(page.getDayElement(new Date(2019, 7, 10)), `Same day of next year should be focused`);
});

it(`should focus min available day with 'Shift+Home'`, async() => {
Expand Down
8 changes: 4 additions & 4 deletions src/datepicker/datepicker-service.spec.ts
Expand Up @@ -1022,20 +1022,20 @@ describe('ngb-datepicker-service', () => {
// months
service.focus(date);
service.focusMove('m', 1);
expect(model.focusDate).toEqual(new NgbDate(2017, 6, 1));
expect(model.focusDate).toEqual(new NgbDate(2017, 6, 5));

service.focus(date);
service.focusMove('m', -1);
expect(model.focusDate).toEqual(new NgbDate(2017, 4, 1));
expect(model.focusDate).toEqual(new NgbDate(2017, 4, 5));

// years
service.focus(date);
service.focusMove('y', 1);
expect(model.focusDate).toEqual(new NgbDate(2018, 1, 1));
expect(model.focusDate).toEqual(new NgbDate(2018, 5, 5));

service.focus(date);
service.focusMove('y', -1);
expect(model.focusDate).toEqual(new NgbDate(2016, 1, 1));
expect(model.focusDate).toEqual(new NgbDate(2016, 5, 5));
});

it(`should move focus when 'minDate' changes`, () => {
Expand Down
7 changes: 4 additions & 3 deletions src/datepicker/datepicker-tools.ts
Expand Up @@ -78,11 +78,12 @@ export function generateSelectBoxYears(date: NgbDate, minDate: NgbDate, maxDate:
}

export function nextMonthDisabled(calendar: NgbCalendar, date: NgbDate, maxDate: NgbDate) {
return maxDate && calendar.getNext(date, 'm').after(maxDate);
const nextDate = Object.assign(calendar.getNext(date, 'm'), {day: 1});
return maxDate && nextDate.after(maxDate);
}

export function prevMonthDisabled(calendar: NgbCalendar, date: NgbDate, minDate: NgbDate) {
const prevDate = calendar.getPrev(date, 'm');
const prevDate = Object.assign(calendar.getPrev(date, 'm'), {day: 1});
return minDate && (prevDate.year === minDate.year && prevDate.month < minDate.month ||
prevDate.year < minDate.year && minDate.month === 1);
}
Expand All @@ -96,7 +97,7 @@ export function buildMonths(

// generate new first dates, nullify or reuse months
const firstDates = Array.from({length: displayMonths}, (_, i) => {
const firstDate = calendar.getNext(date, 'm', i);
const firstDate = Object.assign(calendar.getNext(date, 'm', i), {day: 1});
months[i] = null;

if (!force) {
Expand Down
12 changes: 6 additions & 6 deletions src/datepicker/datepicker.spec.ts
Expand Up @@ -980,24 +980,24 @@ describe('ngb-datepicker', () => {

triggerKeyDown(getMonthContainer(datepicker), 33 /* page up */);
fixture.detectChanges();
expectFocusedDate(datepicker, new NgbDate(2016, 7, 1));
expectFocusedDate(datepicker, new NgbDate(2016, 7, 2));
expectSelectedDate(datepicker, null);

triggerKeyDown(getMonthContainer(datepicker), 34 /* page down */);
fixture.detectChanges();
expectFocusedDate(datepicker, new NgbDate(2016, 8, 1));
expectFocusedDate(datepicker, new NgbDate(2016, 8, 2));
expectSelectedDate(datepicker, null);

triggerKeyDown(getMonthContainer(datepicker), 34 /* page down */);
fixture.detectChanges();

expectFocusedDate(datepicker, new NgbDate(2016, 9, 1));
expectFocusedDate(datepicker, new NgbDate(2016, 9, 2));
expectSelectedDate(datepicker, null);

triggerKeyDown(getMonthContainer(datepicker), 34 /* page down */);
fixture.detectChanges();
datepicker = fixture.debugElement.query(By.directive(NgbDatepicker));
expectFocusedDate(datepicker, new NgbDate(2016, 10, 1));
expectFocusedDate(datepicker, new NgbDate(2016, 10, 2));
expectSelectedDate(datepicker, null);
});

Expand All @@ -1016,13 +1016,13 @@ describe('ngb-datepicker', () => {
triggerKeyDown(getMonthContainer(datepicker), 33 /* page up */, true /* shift */);
fixture.detectChanges();

expectFocusedDate(datepicker, new NgbDate(2015, 1, 1), true);
expectFocusedDate(datepicker, new NgbDate(2015, 8, 1), true);
expectSelectedDate(datepicker, null);

triggerKeyDown(getMonthContainer(datepicker), 34 /* page down */, true /* shift */);
fixture.detectChanges();

expectFocusedDate(datepicker, new NgbDate(2016, 1, 1));
expectFocusedDate(datepicker, new NgbDate(2016, 8, 1));
expectSelectedDate(datepicker, null);
});

Expand Down
32 changes: 24 additions & 8 deletions src/datepicker/ngb-calendar.spec.ts
Expand Up @@ -43,25 +43,41 @@ describe('ngb-calendar-gregorian', () => {
});

it('should add months to date', () => {
expect(calendar.getNext(new NgbDate(2016, 7, 22), 'm')).toEqual(new NgbDate(2016, 8, 1));
expect(calendar.getNext(new NgbDate(2016, 7, 22), 'm')).toEqual(new NgbDate(2016, 8, 22));
expect(calendar.getNext(new NgbDate(2016, 7, 1), 'm')).toEqual(new NgbDate(2016, 8, 1));
expect(calendar.getNext(new NgbDate(2016, 12, 22), 'm')).toEqual(new NgbDate(2017, 1, 1));
expect(calendar.getNext(new NgbDate(2016, 12, 22), 'm')).toEqual(new NgbDate(2017, 1, 22));
expect(calendar.getNext(new NgbDate(2016, 1, 29), 'm')).toEqual(new NgbDate(2016, 2, 29));
expect(calendar.getNext(new NgbDate(2016, 1, 30), 'm')).toEqual(new NgbDate(2016, 2, 29));
expect(calendar.getNext(new NgbDate(2016, 10, 30), 'm', 6)).toEqual(new NgbDate(2017, 4, 30));
expect(calendar.getNext(new NgbDate(2016, 10, 31), 'm', 6)).toEqual(new NgbDate(2017, 4, 30));
});

it('should subtract months from date', () => {
expect(calendar.getPrev(new NgbDate(2016, 7, 22), 'm')).toEqual(new NgbDate(2016, 6, 1));
expect(calendar.getPrev(new NgbDate(2016, 7, 22), 'm')).toEqual(new NgbDate(2016, 6, 22));
expect(calendar.getPrev(new NgbDate(2016, 8, 1), 'm')).toEqual(new NgbDate(2016, 7, 1));
expect(calendar.getPrev(new NgbDate(2017, 1, 22), 'm')).toEqual(new NgbDate(2016, 12, 1));
expect(calendar.getPrev(new NgbDate(2017, 1, 22), 'm')).toEqual(new NgbDate(2016, 12, 22));
expect(calendar.getPrev(new NgbDate(2016, 3, 29), 'm')).toEqual(new NgbDate(2016, 2, 29));
expect(calendar.getPrev(new NgbDate(2016, 3, 30), 'm')).toEqual(new NgbDate(2016, 2, 29));
expect(calendar.getPrev(new NgbDate(2016, 10, 30), 'm', 4)).toEqual(new NgbDate(2016, 6, 30));
expect(calendar.getPrev(new NgbDate(2016, 10, 31), 'm', 4)).toEqual(new NgbDate(2016, 6, 30));
});

it('should add years to date', () => {
expect(calendar.getNext(new NgbDate(2016, 1, 22), 'y')).toEqual(new NgbDate(2017, 1, 1));
expect(calendar.getNext(new NgbDate(2017, 12, 22), 'y')).toEqual(new NgbDate(2018, 1, 1));
expect(calendar.getNext(new NgbDate(2016, 1, 22), 'y')).toEqual(new NgbDate(2017, 1, 22));
expect(calendar.getNext(new NgbDate(2017, 12, 22), 'y')).toEqual(new NgbDate(2018, 12, 22));
expect(calendar.getNext(new NgbDate(2016, 2, 29), 'y')).toEqual(new NgbDate(2017, 2, 28));
expect(calendar.getNext(new NgbDate(2016, 2, 28), 'y')).toEqual(new NgbDate(2017, 2, 28));
expect(calendar.getNext(new NgbDate(2016, 2, 29), 'y', 4)).toEqual(new NgbDate(2020, 2, 29));
expect(calendar.getNext(new NgbDate(2016, 2, 29), 'y', 3)).toEqual(new NgbDate(2019, 2, 28));
});

it('should subtract years from date', () => {
expect(calendar.getPrev(new NgbDate(2016, 12, 22), 'y')).toEqual(new NgbDate(2015, 1, 1));
expect(calendar.getPrev(new NgbDate(2017, 1, 22), 'y')).toEqual(new NgbDate(2016, 1, 1));
expect(calendar.getPrev(new NgbDate(2016, 12, 22), 'y')).toEqual(new NgbDate(2015, 12, 22));
expect(calendar.getPrev(new NgbDate(2017, 1, 22), 'y')).toEqual(new NgbDate(2016, 1, 22));
expect(calendar.getPrev(new NgbDate(2016, 2, 28), 'y')).toEqual(new NgbDate(2015, 2, 28));
expect(calendar.getPrev(new NgbDate(2016, 2, 29), 'y')).toEqual(new NgbDate(2015, 2, 28));
expect(calendar.getPrev(new NgbDate(2016, 2, 29), 'y', 4)).toEqual(new NgbDate(2012, 2, 29));
expect(calendar.getPrev(new NgbDate(2016, 2, 29), 'y', 3)).toEqual(new NgbDate(2013, 2, 28));
});

it('should properly recognize invalid javascript date', () => {
Expand Down
19 changes: 17 additions & 2 deletions src/datepicker/ngb-calendar.ts
Expand Up @@ -98,20 +98,35 @@ export class NgbCalendarGregorian extends NgbCalendar {

getNext(date: NgbDate, period: NgbPeriod = 'd', number = 1) {
let jsDate = toJSDate(date);
let checkMonth = true;
let expectedMonth = jsDate.getMonth();

switch (period) {
case 'y':
return new NgbDate(date.year + number, 1, 1);
jsDate.setFullYear(jsDate.getFullYear() + number);
break;
case 'm':
jsDate = new Date(date.year, date.month + number - 1, 1, 12);
expectedMonth += number;
jsDate.setMonth(expectedMonth);
expectedMonth = expectedMonth % 12;
if (expectedMonth < 0) {
expectedMonth = expectedMonth + 12;
}
break;
case 'd':
jsDate.setDate(jsDate.getDate() + number);
checkMonth = false;
break;
default:
return date;
}

if (checkMonth && jsDate.getMonth() !== expectedMonth) {
// this means the destination month has less days than the initial month
// let's go back to the end of the previous month:
jsDate.setDate(0);
}

return fromJSDate(jsDate);
}

Expand Down

0 comments on commit 22a1cb6

Please sign in to comment.