diff --git a/src/datepicker/datepicker-service.spec.ts b/src/datepicker/datepicker-service.spec.ts index 49c55f72a6..62f11c56d0 100644 --- a/src/datepicker/datepicker-service.spec.ts +++ b/src/datepicker/datepicker-service.spec.ts @@ -60,25 +60,34 @@ describe('ngb-datepicker-service', () => { expect(mock.onNext).not.toHaveBeenCalled(); }); + it(`should allow setting multiple properties at the same time`, () => { + // 1st call + service.focus(new NgbDate(2017, 1, 1)); + + // 2nd call + service.set({firstDayOfWeek: 3, minDate: new NgbDate(2020, 1, 1), maxDate: new NgbDate(2020, 2, 1)}); + expect(mock.onNext).toHaveBeenCalledTimes(2); + }); + describe(`min/max dates`, () => { it(`should emit null and valid 'minDate' values`, () => { // valid const minDate = new NgbDate(2017, 5, 1); - service.minDate = minDate; + service.set({minDate}); service.focus(new NgbDate(2017, 5, 1)); expect(model.minDate).toEqual(minDate); // null - service.minDate = null; + service.set({minDate: null}); expect(model.minDate).toBeNull(); // undefined -> ignore - service.minDate = undefined; + service.set({minDate: undefined}); expect(model.minDate).toBeNull(); // invalid -> ignore - service.minDate = new NgbDate(-2, 0, null); + service.set({minDate: new NgbDate(-2, 0, null)}); expect(model.minDate).toBeNull(); expect(mock.onNext).toHaveBeenCalledTimes(2); @@ -87,39 +96,39 @@ describe('ngb-datepicker-service', () => { it(`should emit null and valid 'maxDate' values`, () => { // valid const maxDate = new NgbDate(2017, 5, 1); - service.maxDate = maxDate; + service.set({maxDate: maxDate}); service.focus(new NgbDate(2017, 5, 1)); expect(model.maxDate).toEqual(maxDate); // null - service.maxDate = null; + service.set({maxDate: null}); expect(model.maxDate).toBeNull(); // undefined -> ignore - service.maxDate = undefined; + service.set({maxDate: undefined}); expect(model.maxDate).toBeNull(); // invalid -> ignore - service.maxDate = new NgbDate(-2, 0, null); + service.set({maxDate: new NgbDate(-2, 0, null)}); expect(model.maxDate).toBeNull(); expect(mock.onNext).toHaveBeenCalledTimes(2); }); it(`should not emit the same 'minDate' value twice`, () => { - service.minDate = new NgbDate(2017, 5, 1); + service.set({minDate: new NgbDate(2017, 5, 1)}); service.focus(new NgbDate(2015, 5, 1)); - service.minDate = new NgbDate(2017, 5, 1); + service.set({minDate: new NgbDate(2017, 5, 1)}); expect(mock.onNext).toHaveBeenCalledTimes(1); }); it(`should not emit the same 'maxDate' value twice`, () => { - service.maxDate = new NgbDate(2017, 5, 1); + service.set({maxDate: new NgbDate(2017, 5, 1)}); service.focus(new NgbDate(2015, 5, 1)); - service.maxDate = new NgbDate(2017, 5, 1); + service.set({maxDate: new NgbDate(2017, 5, 1)}); expect(mock.onNext).toHaveBeenCalledTimes(1); }); @@ -128,20 +137,30 @@ describe('ngb-datepicker-service', () => { const minDate = new NgbDate(2017, 5, 1); service.focus(new NgbDate(2015, 5, 1)); - expect(() => { - service.minDate = minDate; - service.maxDate = new NgbDate(2017, 4, 1); - }).toThrowError(); + expect(() => { service.set({minDate: minDate, maxDate: new NgbDate(2017, 4, 1)}); }).toThrowError(); + }); + + it(`should allow adjusting 'max' and 'min' dates at the same time`, () => { + service.set({minDate: new NgbDate(2017, 5, 1), maxDate: new NgbDate(2018, 5, 1)}); + service.focus(new NgbDate(2015, 5, 1)); + + service.set({minDate: new NgbDate(2020, 5, 1), maxDate: new NgbDate(2021, 5, 1)}); + service.focus(new NgbDate(2020, 5, 1)); + expect(model.focusDate).toEqual(new NgbDate(2020, 5, 1)); + + service.set({minDate: new NgbDate(2000, 5, 1), maxDate: new NgbDate(2001, 5, 1)}); + service.focus(new NgbDate(2000, 5, 1)); + expect(model.focusDate).toEqual(new NgbDate(2000, 5, 1)); }); it(`should align 'date' with 'maxDate'`, () => { - service.maxDate = new NgbDate(2017, 5, 1); + service.set({maxDate: new NgbDate(2017, 5, 1)}); service.focus(new NgbDate(2017, 5, 5)); expect(model.focusDate).toEqual(new NgbDate(2017, 5, 1)); }); it(`should align 'date' with 'minDate'`, () => { - service.minDate = new NgbDate(2017, 5, 10); + service.set({minDate: new NgbDate(2017, 5, 10)}); service.focus(new NgbDate(2017, 5, 5)); expect(model.focusDate).toEqual(new NgbDate(2017, 5, 10)); }); @@ -154,8 +173,8 @@ describe('ngb-datepicker-service', () => { expect(getDayCtx(0).disabled).toBe(false); // 1 MAY expect(getDayCtx(5).disabled).toBe(false); // 6 MAY - service.minDate = new NgbDate(2017, 5, 2); - service.maxDate = new NgbDate(2017, 5, 5); + service.set({minDate: new NgbDate(2017, 5, 2)}); + service.set({maxDate: new NgbDate(2017, 5, 5)}); expect(getDayCtx(0).disabled).toBe(true); // 1 MAY expect(getDayCtx(5).disabled).toBe(true); // 6 MAY }); @@ -168,12 +187,12 @@ describe('ngb-datepicker-service', () => { expect(model.maxDate).toBeUndefined(); // MIN -> 5 MAY, 2017 - service.minDate = new NgbDate(2017, 5, 5); + service.set({minDate: new NgbDate(2017, 5, 5)}); expect(model.months.length).toBe(1); expect(getDayCtx(0).disabled).toBe(true); // MAX -> 10 MAY, 2017 - service.maxDate = new NgbDate(2017, 5, 10); + service.set({maxDate: new NgbDate(2017, 5, 10)}); expect(model.months.length).toBe(1); expect(model.months[0].weeks[4].days[0].context.disabled).toBe(true); }); @@ -183,30 +202,30 @@ describe('ngb-datepicker-service', () => { it(`should emit only positive numeric 'firstDayOfWeek' values`, () => { // valid - service.firstDayOfWeek = 2; + service.set({firstDayOfWeek: 2}); service.focus(new NgbDate(2015, 5, 1)); expect(model.firstDayOfWeek).toEqual(2); // -1 -> ignore - service.firstDayOfWeek = -1; + service.set({firstDayOfWeek: -1}); expect(model.firstDayOfWeek).toEqual(2); // null -> ignore - service.firstDayOfWeek = null; + service.set({firstDayOfWeek: null}); expect(model.firstDayOfWeek).toEqual(2); // undefined -> ignore - service.firstDayOfWeek = null; + service.set({firstDayOfWeek: null}); expect(model.firstDayOfWeek).toEqual(2); expect(mock.onNext).toHaveBeenCalledTimes(1); }); it(`should not emit the same 'firstDayOfWeek' value twice`, () => { - service.firstDayOfWeek = 2; + service.set({firstDayOfWeek: 2}); service.focus(new NgbDate(2015, 5, 1)); - service.firstDayOfWeek = 2; + service.set({firstDayOfWeek: 2}); expect(mock.onNext).toHaveBeenCalledTimes(1); }); @@ -218,12 +237,12 @@ describe('ngb-datepicker-service', () => { }); it(`should generate weeks starting with 'firstDayOfWeek'`, () => { - service.firstDayOfWeek = 2; + service.set({firstDayOfWeek: 2}); service.focus(new NgbDate(2017, 5, 5)); expect(model.months.length).toBe(1); expect(model.months[0].weekdays[0]).toBe(2); - service.firstDayOfWeek = 4; + service.set({firstDayOfWeek: 4}); expect(model.months.length).toBe(1); expect(model.months[0].weekdays[0]).toBe(4); }); @@ -236,7 +255,7 @@ describe('ngb-datepicker-service', () => { const oldFirstDate = getDay(0).date; expect(oldFirstDate).toEqual(new NgbDate(2017, 5, 1)); - service.firstDayOfWeek = 3; + service.set({firstDayOfWeek: 3}); expect(model.months.length).toBe(1); expect(model.firstDayOfWeek).toBe(3); const newFirstDate = getDay(0).date; @@ -248,40 +267,40 @@ describe('ngb-datepicker-service', () => { it(`should emit only positive numeric 'displayMonths' values`, () => { // valid - service.displayMonths = 2; + service.set({displayMonths: 2}); service.focus(new NgbDate(2017, 5, 1)); expect(model.displayMonths).toEqual(2); // -1 -> ignore - service.displayMonths = -1; + service.set({displayMonths: -1}); expect(model.displayMonths).toEqual(2); // null -> ignore - service.displayMonths = null; + service.set({displayMonths: null}); expect(model.displayMonths).toEqual(2); // undefined -> ignore - service.displayMonths = null; + service.set({displayMonths: null}); expect(model.displayMonths).toEqual(2); expect(mock.onNext).toHaveBeenCalledTimes(1); }); it(`should not emit the same 'displayMonths' value twice`, () => { - service.displayMonths = 2; + service.set({displayMonths: 2}); service.focus(new NgbDate(2017, 5, 1)); - service.displayMonths = 2; + service.set({displayMonths: 2}); expect(mock.onNext).toHaveBeenCalledTimes(1); }); it(`should generate 'displayMonths' number of months`, () => { - service.displayMonths = 2; + service.set({displayMonths: 2}); service.focus(new NgbDate(2017, 5, 5)); expect(model.months.length).toBe(2); - service.displayMonths = 4; + service.set({displayMonths: 4}); expect(model.months.length).toBe(4); }); @@ -295,20 +314,20 @@ describe('ngb-datepicker-service', () => { expect(date).toEqual(new NgbDate(2017, 5, 1)); // 2 months - service.displayMonths = 2; + service.set({displayMonths: 2}); expect(model.months.length).toBe(2); expect(model.months[0]).toBe(month); expect(getDay(0).date).toBe(date); // back to 1 month - service.displayMonths = 1; + service.set({displayMonths: 1}); expect(model.months.length).toBe(1); expect(model.months[0]).toBe(month); expect(getDay(0).date).toBe(date); }); it(`should change the tabindex when changing the current month`, () => { - service.displayMonths = 2; + service.set({displayMonths: 2}); const date = new NgbDate(2018, 3, 31); service.focus(date); @@ -324,7 +343,7 @@ describe('ngb-datepicker-service', () => { }); it(`should set the aria-label when changing the current month`, () => { - service.displayMonths = 2; + service.set({displayMonths: 2}); const date = new NgbDate(2018, 3, 31); service.focus(date); @@ -346,23 +365,23 @@ describe('ngb-datepicker-service', () => { service.focus(new NgbDate(2017, 5, 1)); expect(model.disabled).toEqual(false); - service.disabled = true; + service.set({disabled: true}); expect(model.disabled).toEqual(true); }); it(`should not emit the same 'disabled' value twice`, () => { service.focus(new NgbDate(2017, 5, 1)); // 1 - service.disabled = true; // 2 + service.set({disabled: true}); // 2 - service.disabled = true; // ignored + service.set({disabled: true}); // ignored expect(mock.onNext).toHaveBeenCalledTimes(2); }); it(`should not allow focusing when disabled`, () => { const today = new NgbDate(2017, 5, 2); - service.focus(today); // 1 - service.disabled = true; // 2 + service.focus(today); // 1 + service.set({disabled: true}); // 2 // focus service.focus(new NgbDate(2017, 5, 1)); // nope @@ -377,8 +396,8 @@ describe('ngb-datepicker-service', () => { it(`should not allow selecting when disabled`, () => { const today = new NgbDate(2017, 5, 2); - service.focus(today); // 1 - service.disabled = true; // 2 + service.focus(today); // 1 + service.set({disabled: true}); // 2 // select service.select(new NgbDate(2017, 5, 2)); // nope @@ -393,7 +412,7 @@ describe('ngb-datepicker-service', () => { it(`should not allow opening when disabled`, () => { service.focus(new NgbDate(2017, 5, 2)); // 1 - service.disabled = true; // 2 + service.set({disabled: true}); // 2 // open service.open(new NgbDate(2016, 5, 1)); // nope @@ -404,19 +423,19 @@ describe('ngb-datepicker-service', () => { it(`should turn focus off when disabled`, () => { service.focus(new NgbDate(2017, 5, 2)); - service.focusVisible = true; + service.set({focusVisible: true}); expect(model.focusVisible).toBeTruthy(); - service.disabled = true; + service.set({disabled: true}); expect(model.focusVisible).toBeFalsy(); }); it(`should not turn focus on when disabled`, () => { service.focus(new NgbDate(2017, 5, 2)); - service.disabled = true; + service.set({disabled: true}); expect(model.focusVisible).toBeFalsy(); - service.focusVisible = true; + service.set({focusVisible: true}); expect(model.focusVisible).toBeFalsy(); }); @@ -425,11 +444,11 @@ describe('ngb-datepicker-service', () => { expect(model.prevDisabled).toBeFalsy(); expect(model.nextDisabled).toBeFalsy(); - service.disabled = true; + service.set({disabled: true}); expect(model.prevDisabled).toBeTruthy(); expect(model.nextDisabled).toBeTruthy(); - service.disabled = false; + service.set({disabled: false}); expect(model.prevDisabled).toBeFalsy(); expect(model.nextDisabled).toBeFalsy(); }); @@ -442,15 +461,15 @@ describe('ngb-datepicker-service', () => { service.focus(new NgbDate(2017, 5, 1)); expect(model.focusVisible).toEqual(false); - service.focusVisible = true; + service.set({focusVisible: true}); expect(model.focusVisible).toEqual(true); }); it(`should not emit the same 'focusVisible' value twice`, () => { - service.focusVisible = true; + service.set({focusVisible: true}); service.focus(new NgbDate(2017, 5, 1)); - service.focusVisible = true; // ignored + service.set({focusVisible: true}); // ignored expect(mock.onNext).toHaveBeenCalledTimes(1); }); @@ -462,7 +481,7 @@ describe('ngb-datepicker-service', () => { const month = model.months[0]; const date = month.weeks[0].days[0].date; - service.focusVisible = true; + service.set({focusVisible: true}); expect(model.focusVisible).toEqual(true); expect(model.months[0]).toBe(month); expect(getDay(0).date).toBe(date); @@ -477,17 +496,17 @@ describe('ngb-datepicker-service', () => { service.focus(new NgbDate(2015, 5, 1)); expect(model.navigation).toEqual('select'); - service.navigation = 'none'; + service.set({navigation: 'none'}); expect(model.navigation).toEqual('none'); - service.navigation = 'arrows'; + service.set({navigation: 'arrows'}); expect(model.navigation).toEqual('arrows'); }); it(`should not emit the same 'navigation' value twice`, () => { service.focus(new NgbDate(2017, 5, 1)); - service.navigation = 'select'; // ignored + service.set({navigation: 'select'}); // ignored expect(mock.onNext).toHaveBeenCalledTimes(1); }); @@ -496,24 +515,22 @@ describe('ngb-datepicker-service', () => { const range = (start, end) => Array.from({length: end - start + 1}, (e, i) => start + i); it(`should not generate 'months' and 'years' for non-select navigations`, () => { - service.minDate = new NgbDate(2010, 5, 1); - service.maxDate = new NgbDate(2012, 5, 1); + service.set({minDate: new NgbDate(2010, 5, 1), maxDate: new NgbDate(2012, 5, 1)}); service.focus(new NgbDate(2011, 5, 1)); expect(model.selectBoxes.years).not.toEqual([]); expect(model.selectBoxes.months).not.toEqual([]); - service.navigation = 'none'; + service.set({navigation: 'none'}); expect(model.selectBoxes.years).toEqual([]); expect(model.selectBoxes.months).toEqual([]); - service.navigation = 'arrows'; + service.set({navigation: 'arrows'}); expect(model.selectBoxes.years).toEqual([]); expect(model.selectBoxes.months).toEqual([]); }); it(`should generate 'months' and 'years' for given min/max dates`, () => { - service.minDate = new NgbDate(2010, 5, 1); - service.maxDate = new NgbDate(2012, 5, 1); + service.set({minDate: new NgbDate(2010, 5, 1), maxDate: new NgbDate(2012, 5, 1)}); service.focus(new NgbDate(2011, 5, 1)); expect(model.selectBoxes.years).toEqual([2010, 2011, 2012]); @@ -529,18 +546,17 @@ describe('ngb-datepicker-service', () => { }); it(`should update 'months' and 'years' when min/max dates change`, () => { - service.minDate = new NgbDate(2010, 5, 1); - service.maxDate = new NgbDate(2012, 5, 1); + service.set({minDate: new NgbDate(2010, 5, 1), maxDate: new NgbDate(2012, 5, 1)}); service.focus(new NgbDate(2011, 5, 1)); expect(model.selectBoxes.years).toEqual([2010, 2011, 2012]); expect(model.selectBoxes.months).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); - service.minDate = new NgbDate(2011, 2, 1); + service.set({minDate: new NgbDate(2011, 2, 1)}); expect(model.selectBoxes.years).toEqual([2011, 2012]); expect(model.selectBoxes.months).toEqual([2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); - service.maxDate = new NgbDate(2011, 8, 1); + service.set({maxDate: new NgbDate(2011, 8, 1)}); expect(model.selectBoxes.years).toEqual([2011]); expect(model.selectBoxes.months).toEqual([2, 3, 4, 5, 6, 7, 8]); }); @@ -558,28 +574,28 @@ describe('ngb-datepicker-service', () => { }); it(`should generate [min/-500, +10] 'years' when max date is missing`, () => { - service.minDate = new NgbDate(2010, 1, 1); + service.set({minDate: new NgbDate(2010, 1, 1)}); service.open(new NgbDate(2011, 1, 1)); expect(model.selectBoxes.years).toEqual(range(2010, 2021)); - service.minDate = new NgbDate(2015, 1, 1); + service.set({minDate: new NgbDate(2015, 1, 1)}); expect(model.selectBoxes.years).toEqual(range(2015, 2025)); // -500 - service.minDate = new NgbDate(1000, 1, 1); + service.set({minDate: new NgbDate(1000, 1, 1)}); expect(model.selectBoxes.years).toEqual(range(1515, 2025)); }); it(`should generate [-10, +500/max] 'years' when min date is missing`, () => { - service.maxDate = new NgbDate(2010, 1, 1); + service.set({maxDate: new NgbDate(2010, 1, 1)}); service.open(new NgbDate(2009, 1, 1)); expect(model.selectBoxes.years).toEqual(range(1999, 2010)); - service.maxDate = new NgbDate(2005, 1, 1); + service.set({maxDate: new NgbDate(2005, 1, 1)}); expect(model.selectBoxes.years).toEqual(range(1995, 2005)); // +500 - service.maxDate = new NgbDate(3000, 1, 1); + service.set({maxDate: new NgbDate(3000, 1, 1)}); expect(model.selectBoxes.years).toEqual(range(1995, 2505)); }); @@ -592,42 +608,40 @@ describe('ngb-datepicker-service', () => { }); it(`should generate 'months' and 'years' when resetting min/max dates`, () => { - service.minDate = new NgbDate(2010, 3, 1); - service.maxDate = new NgbDate(2010, 8, 1); + service.set({minDate: new NgbDate(2010, 3, 1), maxDate: new NgbDate(2010, 8, 1)}); service.open(new NgbDate(2010, 5, 10)); expect(model.selectBoxes.months).toEqual([3, 4, 5, 6, 7, 8]); expect(model.selectBoxes.years).toEqual([2010]); - service.minDate = null; + service.set({minDate: null}); expect(model.selectBoxes.months).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); expect(model.selectBoxes.years).toEqual(range(2000, 2010)); - service.maxDate = null; + service.set({maxDate: null}); expect(model.selectBoxes.months).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); expect(model.selectBoxes.years).toEqual(range(2000, 2020)); }); it(`should generate 'months' when max date is missing`, () => { - service.minDate = new NgbDate(2010, 1, 1); + service.set({minDate: new NgbDate(2010, 1, 1)}); service.open(new NgbDate(2010, 5, 1)); expect(model.selectBoxes.months).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); - service.minDate = new NgbDate(2010, 4, 1); + service.set({minDate: new NgbDate(2010, 4, 1)}); expect(model.selectBoxes.months).toEqual([4, 5, 6, 7, 8, 9, 10, 11, 12]); }); it(`should generate 'months' when min date is missing`, () => { - service.maxDate = new NgbDate(2010, 12, 1); + service.set({maxDate: new NgbDate(2010, 12, 1)}); service.open(new NgbDate(2010, 5, 1)); expect(model.selectBoxes.months).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); - service.maxDate = new NgbDate(2010, 7, 1); + service.set({maxDate: new NgbDate(2010, 7, 1)}); expect(model.selectBoxes.months).toEqual([1, 2, 3, 4, 5, 6, 7]); }); it(`should generate 'months' based on the first date, not the focus date`, () => { - service.displayMonths = 2; - service.maxDate = new NgbDate(2017, 1, 11); + service.set({displayMonths: 2, maxDate: new NgbDate(2017, 1, 11)}); service.open(new NgbDate(2017, 1, 1)); expect(model.selectBoxes.months).toEqual([1]); @@ -666,8 +680,7 @@ describe('ngb-datepicker-service', () => { }); it(`should use initial 'minDate' and 'maxDate' values`, () => { - service.minDate = new NgbDate(2018, 3, 10); - service.maxDate = new NgbDate(2018, 3, 10); + service.set({minDate: new NgbDate(2018, 3, 10), maxDate: new NgbDate(2018, 3, 10)}); service.focus(new NgbDate(2018, 3, 10)); expect(model.prevDisabled).toBeTruthy(); expect(model.nextDisabled).toBeTruthy(); @@ -675,31 +688,30 @@ describe('ngb-datepicker-service', () => { it(`should react to 'minDate' changes`, () => { service.focus(new NgbDate(2018, 3, 10)); - service.minDate = new NgbDate(2018, 3, 1); + service.set({minDate: new NgbDate(2018, 3, 1)}); expect(model.prevDisabled).toBeTruthy(); - service.minDate = new NgbDate(2018, 2, 1); + service.set({minDate: new NgbDate(2018, 2, 1)}); expect(model.prevDisabled).toBeFalsy(); - service.minDate = new NgbDate(2018, 2, 28); + service.set({minDate: new NgbDate(2018, 2, 28)}); expect(model.prevDisabled).toBeFalsy(); }); it(`should react to 'maxDate' changes`, () => { service.focus(new NgbDate(2018, 3, 10)); - service.maxDate = new NgbDate(2018, 3, 31); + service.set({maxDate: new NgbDate(2018, 3, 31)}); expect(model.nextDisabled).toBeTruthy(); - service.maxDate = new NgbDate(2018, 4, 1); + service.set({maxDate: new NgbDate(2018, 4, 1)}); expect(model.nextDisabled).toBeFalsy(); - service.maxDate = new NgbDate(2018, 4, 30); + service.set({maxDate: new NgbDate(2018, 4, 30)}); expect(model.nextDisabled).toBeFalsy(); }); it(`should react to 'minDate' changes with multiple months`, () => { - service.displayMonths = 2; - service.minDate = new NgbDate(2018, 3, 1); + service.set({displayMonths: 2, minDate: new NgbDate(2018, 3, 1)}); service.open(new NgbDate(2018, 3, 10)); // open: [MAR, APR], focus: MAR expect(model.prevDisabled).toBeTruthy(); @@ -714,8 +726,7 @@ describe('ngb-datepicker-service', () => { }); it(`should react to 'maxDate' changes with multiple months`, () => { - service.displayMonths = 2; - service.maxDate = new NgbDate(2018, 3, 10); + service.set({displayMonths: 2, maxDate: new NgbDate(2018, 3, 10)}); service.open(new NgbDate(2018, 3, 1)); // open: [MAR, APR], focus: MAR expect(model.nextDisabled).toBeTruthy(); @@ -740,23 +751,23 @@ describe('ngb-datepicker-service', () => { service.focus(new NgbDate(2015, 5, 1)); expect(model.outsideDays).toEqual('visible'); - service.outsideDays = 'hidden'; + service.set({outsideDays: 'hidden'}); expect(model.outsideDays).toEqual('hidden'); - service.outsideDays = 'collapsed'; + service.set({outsideDays: 'collapsed'}); expect(model.outsideDays).toEqual('collapsed'); }); it(`should not emit the same 'outsideDays' value twice`, () => { service.focus(new NgbDate(2017, 5, 1)); - service.outsideDays = 'visible'; // ignored + service.set({outsideDays: 'visible'}); // ignored expect(mock.onNext).toHaveBeenCalledTimes(1); }); it(`should not hide days when 'outsideDays' is 'visible'`, () => { // single month - service.outsideDays = 'visible'; + service.set({outsideDays: 'visible'}); service.focus(new NgbDate(2018, 5, 1)); expect(getDay(0, 0).hidden).toBeFalsy(); // 30 APR @@ -770,7 +781,7 @@ describe('ngb-datepicker-service', () => { // multiple months // days is between two month must stay hidden regardless of outside days value - service.displayMonths = 2; + service.set({displayMonths: 2}); // MAY 2018 expect(getDay(0, 0, 0).hidden).toBeFalsy(); // 30 APR @@ -818,7 +829,7 @@ describe('ngb-datepicker-service', () => { it(`should hide days when 'outsideDays' is 'hidden'`, () => { // single month - service.outsideDays = 'hidden'; + service.set({outsideDays: 'hidden'}); service.focus(new NgbDate(2018, 5, 1)); expect(getDay(0, 0).hidden).toBeTruthy(); // 30, APR @@ -831,7 +842,7 @@ describe('ngb-datepicker-service', () => { expect(getWeek(5).collapsed).toBeFalsy(); // multiple months - service.displayMonths = 2; + service.set({displayMonths: 2}); // MAY 2018 expect(getDay(0, 0, 0).hidden).toBeTruthy(); // 30 APR @@ -856,7 +867,7 @@ describe('ngb-datepicker-service', () => { it(`should hide days when 'outsideDays' is 'collapsed'`, () => { // single month - service.outsideDays = 'collapsed'; + service.set({outsideDays: 'collapsed'}); service.focus(new NgbDate(2018, 5, 1)); expect(getDay(0, 0).hidden).toBeTruthy(); // 30, APR @@ -869,7 +880,7 @@ describe('ngb-datepicker-service', () => { expect(getWeek(5).collapsed).toBeTruthy(); // multiple months - service.displayMonths = 2; + service.set({displayMonths: 2}); // MAY 2018 expect(getDay(0, 0, 0).hidden).toBeTruthy(); // 30 APR @@ -893,12 +904,12 @@ describe('ngb-datepicker-service', () => { }); it(`should toggle days when 'outsideDays' changes`, () => { - service.outsideDays = 'visible'; + service.set({outsideDays: 'visible'}); service.focus(new NgbDate(2018, 5, 1)); expect(getDay(0).hidden).toBeFalsy(); // 30, APR expect(getWeek(5).collapsed).toBeFalsy(); - service.outsideDays = 'collapsed'; + service.set({outsideDays: 'collapsed'}); expect(getDay(0).hidden).toBeTruthy(); // 30, APR expect(getWeek(5).collapsed).toBeTruthy(); }); @@ -913,7 +924,7 @@ describe('ngb-datepicker-service', () => { }); it(`should pass arbitrary data to the template`, () => { - service.dayTemplateData = () => 'data'; + service.set({dayTemplateData: () => 'data'}); // MAY 2017 service.focus(new NgbDate(2017, 5, 1)); @@ -922,12 +933,12 @@ describe('ngb-datepicker-service', () => { it(`should update months when 'dayTemplateData' changes`, () => { // MAY 2017 - service.dayTemplateData = () => 'one'; + service.set({dayTemplateData: () => 'one'}); service.focus(new NgbDate(2017, 5, 1)); expect(getDay(0).context.data).toBe('one'); - service.dayTemplateData = (_) => 'two'; + service.set({dayTemplateData: (_) => 'two'}); expect(getDay(0).context.data).toBe('two'); }); @@ -937,7 +948,7 @@ describe('ngb-datepicker-service', () => { it(`should mark dates as disabled by passing 'markDisabled'`, () => { // marking 5th day of each month as disabled - service.markDisabled = (date) => date && date.day === 5; + service.set({markDisabled: (date) => date && date.day === 5}); // MAY 2017 service.focus(new NgbDate(2017, 5, 1)); @@ -949,12 +960,12 @@ describe('ngb-datepicker-service', () => { it(`should update months when 'markDisabled changes'`, () => { // MAY 2017 - service.markDisabled = (_) => true; + service.set({markDisabled: (_) => true}); service.focus(new NgbDate(2017, 5, 1)); expect(getDay(0).context.disabled).toBe(true); - service.markDisabled = (_) => false; + service.set({markDisabled: (_) => false}); expect(getDay(0).context.disabled).toBe(false); }); @@ -1016,13 +1027,13 @@ describe('ngb-datepicker-service', () => { it(`should move focus when 'minDate' changes`, () => { service.focus(new NgbDate(2017, 5, 5)); - service.maxDate = new NgbDate(2017, 5, 1); + service.set({maxDate: new NgbDate(2017, 5, 1)}); expect(model.focusDate).toEqual(new NgbDate(2017, 5, 1)); }); it(`should move focus when 'maxDate' changes`, () => { service.focus(new NgbDate(2017, 5, 5)); - service.minDate = new NgbDate(2017, 5, 10); + service.set({minDate: new NgbDate(2017, 5, 10)}); expect(model.focusDate).toEqual(new NgbDate(2017, 5, 10)); }); @@ -1040,7 +1051,7 @@ describe('ngb-datepicker-service', () => { }); it(`should not rebuild multiple months if newly focused date is visible`, () => { - service.displayMonths = 2; + service.set({displayMonths: 2}); service.focus(new NgbDate(2017, 5, 5)); expect(model.months.length).toBe(2); @@ -1088,7 +1099,7 @@ describe('ngb-datepicker-service', () => { it(`should open multiple months and move focus with them`, () => { // MAY-JUN - service.displayMonths = 2; + service.set({displayMonths: 2}); service.focus(new NgbDate(2017, 5, 5)); expect(model.months.length).toBe(2); expect(model.firstDate).toEqual(new NgbDate(2017, 5, 1)); @@ -1107,7 +1118,7 @@ describe('ngb-datepicker-service', () => { it(`should open multiple months and do not touch focus if it is visible`, () => { // MAY-JUN - service.displayMonths = 2; + service.set({displayMonths: 2}); service.focus(new NgbDate(2017, 5, 5)); expect(model.months.length).toBe(2); expect(model.firstDate).toEqual(new NgbDate(2017, 5, 1)); @@ -1177,7 +1188,7 @@ describe('ngb-datepicker-service', () => { it(`should not select disabled dates with 'focusSelect()'`, () => { // marking 5th day of each month as disabled - service.markDisabled = (d) => d && d.day === 5; + service.set({markDisabled: (d) => d && d.day === 5}); // focusing MAY, 5 const date = new NgbDate(2017, 5, 5); @@ -1234,7 +1245,7 @@ describe('ngb-datepicker-service', () => { it(`should not emit date selection event for disabled dates'`, () => { // marking 5th day of each month as disabled - service.markDisabled = (d) => d && d.day === 5; + service.set({markDisabled: (d) => d && d.day === 5}); // focusing MAY, 5 const date = new NgbDate(2017, 5, 5); @@ -1313,7 +1324,7 @@ describe('ngb-datepicker-service', () => { expect(getDay(1).tabindex).toEqual(-1); // on - service.focusVisible = true; + service.set({focusVisible: true}); expect(getDayCtx(0).focused).toBeTruthy(); expect(getDayCtx(1).focused).toBeFalsy(); expect(getDay(0).tabindex).toEqual(0); @@ -1327,7 +1338,7 @@ describe('ngb-datepicker-service', () => { expect(getDay(1).tabindex).toEqual(0); // off - service.focusVisible = false; + service.set({focusVisible: false}); expect(getDayCtx(0).focused).toBeFalsy(); expect(getDayCtx(1).focused).toBeFalsy(); expect(getDay(0).tabindex).toEqual(-1); @@ -1363,17 +1374,17 @@ describe('ngb-datepicker-service', () => { expect(getDayCtx(1).disabled).toBeFalsy(); // marking 2nd day of each month as disabled - service.markDisabled = (date) => date && date.day === 2; + service.set({markDisabled: (date) => date && date.day === 2}); expect(getDayCtx(0).disabled).toBeFalsy(); expect(getDayCtx(1).disabled).toBeTruthy(); // global disabled on - service.disabled = true; + service.set({disabled: true}); expect(getDayCtx(0).disabled).toBeTruthy(); expect(getDayCtx(1).disabled).toBeTruthy(); // global disabled on - service.disabled = false; + service.set({disabled: false}); expect(getDayCtx(0).disabled).toBeFalsy(); expect(getDayCtx(1).disabled).toBeTruthy(); }); diff --git a/src/datepicker/datepicker-service.ts b/src/datepicker/datepicker-service.ts index 8262d82776..d942a18d7e 100644 --- a/src/datepicker/datepicker-service.ts +++ b/src/datepicker/datepicker-service.ts @@ -21,8 +21,70 @@ import { import {filter} from 'rxjs/operators'; import {NgbDatepickerI18n} from './datepicker-i18n'; +export interface DatepickerServiceInputs extends + Partial> {} + @Injectable() export class NgbDatepickerService { + private _VALIDATORS: + {[K in keyof DatepickerServiceInputs]: (v: DatepickerServiceInputs[K]) => Partial} = { + dayTemplateData: (dayTemplateData: NgbDayTemplateData) => { + if (this._state.dayTemplateData !== dayTemplateData) { + return {dayTemplateData}; + } + }, + displayMonths: (displayMonths: number) => { + displayMonths = toInteger(displayMonths); + if (isInteger(displayMonths) && displayMonths > 0 && this._state.displayMonths !== displayMonths) { + return {displayMonths}; + } + }, + disabled: (disabled: boolean) => { + if (this._state.disabled !== disabled) { + return {disabled}; + } + }, + firstDayOfWeek: (firstDayOfWeek: number) => { + firstDayOfWeek = toInteger(firstDayOfWeek); + if (isInteger(firstDayOfWeek) && firstDayOfWeek >= 0 && this._state.firstDayOfWeek !== firstDayOfWeek) { + return {firstDayOfWeek}; + } + }, + focusVisible: (focusVisible: boolean) => { + if (this._state.focusVisible !== focusVisible && !this._state.disabled) { + return {focusVisible}; + } + }, + markDisabled: (markDisabled: NgbMarkDisabled) => { + if (this._state.markDisabled !== markDisabled) { + return {markDisabled}; + } + }, + maxDate: (date: NgbDate) => { + const maxDate = this.toValidDate(date, null); + if (isChangedDate(this._state.maxDate, maxDate)) { + return {maxDate}; + } + }, + minDate: (date: NgbDate) => { + const minDate = this.toValidDate(date, null); + if (isChangedDate(this._state.minDate, minDate)) { + return {minDate}; + } + }, + navigation: (navigation: 'select' | 'arrows' | 'none') => { + if (this._state.navigation !== navigation) { + return {navigation}; + } + }, + outsideDays: (outsideDays: 'visible' | 'collapsed' | 'hidden') => { + if (this._state.outsideDays !== outsideDays) { + return {outsideDays}; + } + } + }; + private _model$ = new Subject(); private _dateSelect$ = new Subject(); @@ -45,67 +107,13 @@ export class NgbDatepickerService { get dateSelect$(): Observable { return this._dateSelect$.pipe(filter(date => date !== null)); } - set dayTemplateData(dayTemplateData: NgbDayTemplateData) { - if (this._state.dayTemplateData !== dayTemplateData) { - this._nextState({dayTemplateData}); - } - } - - set disabled(disabled: boolean) { - if (this._state.disabled !== disabled) { - this._nextState({disabled}); - } - } - - set displayMonths(displayMonths: number) { - displayMonths = toInteger(displayMonths); - if (isInteger(displayMonths) && displayMonths > 0 && this._state.displayMonths !== displayMonths) { - this._nextState({displayMonths}); - } - } - - set firstDayOfWeek(firstDayOfWeek: number) { - firstDayOfWeek = toInteger(firstDayOfWeek); - if (isInteger(firstDayOfWeek) && firstDayOfWeek >= 0 && this._state.firstDayOfWeek !== firstDayOfWeek) { - this._nextState({firstDayOfWeek}); - } - } - - set focusVisible(focusVisible: boolean) { - if (this._state.focusVisible !== focusVisible && !this._state.disabled) { - this._nextState({focusVisible}); - } - } - - set maxDate(date: NgbDate) { - const maxDate = this.toValidDate(date, null); - if (isChangedDate(this._state.maxDate, maxDate)) { - this._nextState({maxDate}); - } - } - - set markDisabled(markDisabled: NgbMarkDisabled) { - if (this._state.markDisabled !== markDisabled) { - this._nextState({markDisabled}); - } - } - - set minDate(date: NgbDate) { - const minDate = this.toValidDate(date, null); - if (isChangedDate(this._state.minDate, minDate)) { - this._nextState({minDate}); - } - } - - set navigation(navigation: 'select' | 'arrows' | 'none') { - if (this._state.navigation !== navigation) { - this._nextState({navigation}); - } - } + set(options: DatepickerServiceInputs) { + let patch = Object.keys(options) + .map(key => this._VALIDATORS[key](options[key])) + .reduce((obj, part) => ({...obj, ...part}), {}); - set outsideDays(outsideDays: 'visible' | 'collapsed' | 'hidden') { - if (this._state.outsideDays !== outsideDays) { - this._nextState({outsideDays}); + if (Object.keys(patch).length > 0) { + this._nextState(patch); } } diff --git a/src/datepicker/datepicker.spec.ts b/src/datepicker/datepicker.spec.ts index e4437d618e..900e64fe36 100644 --- a/src/datepicker/datepicker.spec.ts +++ b/src/datepicker/datepicker.spec.ts @@ -169,6 +169,20 @@ describe('ngb-datepicker', () => { }).toThrowError(); }); + it('should allow changing min/max dates at the same time', () => { + const fixture = createTestComponent(''); + + expect(() => { + fixture.componentInstance.minDate = {year: 2110, month: 1, day: 1}; + fixture.componentInstance.maxDate = {year: 2120, month: 12, day: 31}; + fixture.detectChanges(); + + fixture.componentInstance.minDate = {year: 2010, month: 1, day: 1}; + fixture.componentInstance.maxDate = {year: 2020, month: 12, day: 31}; + fixture.detectChanges(); + }).not.toThrowError(); + }); + it('should handle incorrect startDate values', () => { const fixture = createTestComponent(``); const today = new Date(); diff --git a/src/datepicker/datepicker.ts b/src/datepicker/datepicker.ts index 116cf9ccfd..8869549e3f 100644 --- a/src/datepicker/datepicker.ts +++ b/src/datepicker/datepicker.ts @@ -22,7 +22,7 @@ import { import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import {NgbCalendar} from './ngb-calendar'; import {NgbDate} from './ngb-date'; -import {NgbDatepickerService} from './datepicker-service'; +import {DatepickerServiceInputs, NgbDatepickerService} from './datepicker-service'; import {NgbDatepickerKeyboardService} from './datepicker-keyboard-service'; import {DatepickerViewModel, NavigationEvent} from './datepicker-view-model'; import {DayTemplateContext} from './datepicker-day-template-context'; @@ -408,7 +408,7 @@ export class NgbDatepicker implements OnDestroy, !(hasClassName(target, 'ngb-dp-day') && hasClassName(relatedTarget, 'ngb-dp-day') && nativeElement.contains(target as Node) && nativeElement.contains(relatedTarget as Node))), takeUntil(this._destroyed$)) - .subscribe(({type}) => this._ngZone.run(() => this._service.focusVisible = type === 'focusin')); + .subscribe(({type}) => this._ngZone.run(() => this._service.set({focusVisible: type === 'focusin'}))); }); } @@ -416,18 +416,23 @@ export class NgbDatepicker implements OnDestroy, ngOnInit() { if (this.model === undefined) { + const inputs: DatepickerServiceInputs = {}; ['dayTemplateData', 'displayMonths', 'markDisabled', 'firstDayOfWeek', 'navigation', 'minDate', 'maxDate', 'outsideDays'] - .forEach(input => this._service[input] = this[input]); + .forEach(name => inputs[name] = this[name]); + this._service.set(inputs); + this.navigateTo(this.startDate); } } ngOnChanges(changes: SimpleChanges) { + const inputs: DatepickerServiceInputs = {}; ['dayTemplateData', 'displayMonths', 'markDisabled', 'firstDayOfWeek', 'navigation', 'minDate', 'maxDate', 'outsideDays'] - .filter(input => input in changes) - .forEach(input => this._service[input] = this[input]); + .filter(name => name in changes) + .forEach(name => inputs[name] = this[name]); + this._service.set(inputs); if ('startDate' in changes) { const {currentValue, previousValue} = changes.startDate; @@ -461,7 +466,7 @@ export class NgbDatepicker implements OnDestroy, registerOnTouched(fn: () => any): void { this.onTouched = fn; } - setDisabledState(isDisabled: boolean) { this._service.disabled = isDisabled; } + setDisabledState(disabled: boolean) { this._service.set({disabled}); } writeValue(value) { this._controlValue = NgbDate.from(this._ngbDateAdapter.fromModel(value));