From 232e276df16ad8691facf43591d86f698ae23ae5 Mon Sep 17 00:00:00 2001 From: "Rong Sen Ng (motss)" Date: Sun, 3 Oct 2021 00:22:39 +0800 Subject: [PATCH] test: add tests for AppMonthCalendar --- src/month-calendar/month-calendar.ts | 18 +- src/month-calendar/typings.ts | 2 +- .../month-calendar/month-calendar.test.ts | 366 ++++++++++++++++++ src/tests/year-grid/year-grid.test.ts | 18 +- src/year-grid/year-grid.ts | 16 +- 5 files changed, 390 insertions(+), 30 deletions(-) create mode 100644 src/tests/month-calendar/month-calendar.test.ts diff --git a/src/month-calendar/month-calendar.ts b/src/month-calendar/month-calendar.ts index bc346c2f..2af209fe 100644 --- a/src/month-calendar/month-calendar.ts +++ b/src/month-calendar/month-calendar.ts @@ -18,7 +18,7 @@ import type { MonthCalendarData, MonthCalendarProperties } from './typings.js'; export class MonthCalendar extends LitElement implements MonthCalendarProperties { @property({ attribute: false }) - public data: MonthCalendarData; + public data?: MonthCalendarData; @queryAsync('.calendar-day[aria-selected="true"]') public selectedCalendarDay!: Promise; @@ -63,7 +63,7 @@ export class MonthCalendar extends LitElement implements MonthCalendarProperties } protected override shouldUpdate(): boolean { - return this.data.formatters != null; + return this.data != null && this.data.formatters != null; } protected override async updated(): Promise { @@ -87,7 +87,7 @@ export class MonthCalendar extends LitElement implements MonthCalendarProperties todayDate, weekdays, formatters, - } = this.data; + } = this.data as MonthCalendarData; let calendarContent: TemplateResult | typeof nothing = nothing; @@ -127,12 +127,10 @@ export class MonthCalendar extends LitElement implements MonthCalendarProperties @keyup=${this.#updateSelectedDate} > ${ - showCaption ? html` + showCaption && secondMonthSecondCalendarDayFullDate ? html`
${ - secondMonthSecondCalendarDayFullDate ? - longMonthYearFormat(secondMonthSecondCalendarDayFullDate) : - '' + longMonthYearFormat(secondMonthSecondCalendarDayFullDate) }
` : nothing @@ -164,7 +162,7 @@ export class MonthCalendar extends LitElement implements MonthCalendarProperties /** Week label, if any */ if (!fullDate && value && showWeekNumber && i < 1) { return html` { + const locale = 'en-US'; + const formatters = toFormatters(locale); + const calendarInit: CalendarInit = { + date: new Date('2020-02-02'), + dayFormat: formatters.dayFormat, + fullDateFormat: formatters.fullDateFormat, + locale, + disabledDates: [], + disabledDays: [], + firstDayOfWeek: 0, + max: new Date('2100-12-31'), + min: new Date('1970-01-01'), + showWeekNumber: false, + weekLabel: 'Wk', + weekNumberType: 'first-4-day-week', + }; + const calendarResult = calendar(calendarInit); + const data: MonthCalendarData = { + calendar: calendarResult.calendar, + currentDate: calendarInit.date, + date: calendarInit.date, + disabledDatesSet: calendarResult.disabledDatesSet, + disabledDaysSet: calendarResult.disabledDaysSet, + max: calendarInit.max as Date, + min: calendarInit.min as Date, + todayDate: calendarInit.date, + weekdays: getWeekdays({ + longWeekdayFormat: formatters.longWeekdayFormat, + narrowWeekdayFormat: formatters.narrowWeekdayFormat, + firstDayOfWeek: calendarInit.firstDayOfWeek, + showWeekNumber: calendarInit.showWeekNumber, + weekLabel: calendarInit.weekLabel, + }), + formatters, + }; + const elementSelectors = { + calendarCaption: '.calendar-caption', + calendarTable: '.calendar-table', + monthCalendar: '.month-calendar', + calendarDay: 'td.calendar-day', + tabbableCalendarDay: 'td.calendar-day[tabindex="0"]', + selectedCalendarDay: 'td.calendar-day[aria-selected="true"]', + disabledCalendarDay: 'td.calendar-day[aria-disabled="true"]', + calendarDayWeekNumber: 'th.calendar-day.week-number', + hiddenCalendarDay: 'td.calendar-day[aria-hidden="true"]', + } as const; + + type A = [string, MonthCalendarData | undefined, boolean]; + const cases: A[] = [ + ['', data, true], + ['nothing', undefined, false], + ]; + cases.forEach(a => { + const [, testData, testShouldRender] = a; + + it(messageFormatter('renders %s(data=%j)', a), async () => { + const el = await fixture( + html`` + ); + + const monthCalendar = el.shadowRoot?.querySelector( + elementSelectors.monthCalendar + ); + const calendarTable = el.shadowRoot?.querySelector( + elementSelectors.calendarTable + ); + + if (testShouldRender) { + expect(monthCalendar).exist; + expect(calendarTable).exist; + } else { + expect(monthCalendar).not.exist; + expect(calendarTable).not.exist; + } + }); + }); + + it('renders first day of calendar month of current date when it has a different month than selected date', async () => { + const testCurrentDate = new Date('2020-03-03'); + const testCalendar = calendar({ + ...calendarInit, + date: testCurrentDate, + }); + const el = await fixture( + html`` + ); + + const expected = new Date('2020-03-01'); + const tabbableCalendarDay = el.shadowRoot?.querySelector( + `${elementSelectors.tabbableCalendarDay}[aria-label="${formatters.fullDateFormat(expected)}"]` + ); + + expect(tabbableCalendarDay).exist; + expect(tabbableCalendarDay?.fullDate).deep.equal(expected); + }); + + type A2 = [string, Partial, string]; + const cases2: A2[] = [ + ['calendar caption', { showCaption: true }, elementSelectors.calendarCaption], + ['week numbers', { showWeekNumber: true }, elementSelectors.calendarDayWeekNumber], + [ + 'disabled day', + { + disabledDatesSet: new Set([+new Date('2020-02-15')]), + }, + `${elementSelectors.disabledCalendarDay}[aria-label="${ + formatters.fullDateFormat(new Date('2020-02-15')) + }"]`, + ], + ]; + cases2.forEach(a => { + const [, testPartialData, testElementSelector] = a; + it(messageFormatter('renders %s', a), async () => { + const testCalendar = calendar({ + ...calendarInit, + ...( + testPartialData.disabledDatesSet && { + disabledDates: [...testPartialData.disabledDatesSet].map(n => new Date(n)), + } + ), + showWeekNumber: testPartialData.showWeekNumber, + }); + const el = await fixture( + html`` + ); + + const element = el.shadowRoot?.querySelector( + testElementSelector + ); + + expect(element).exist; + }); + }); + + type A3 = [ + 'click' | 'keydown', + (Partial | InferredFromSet + >>)[], + Date + ]; + const cases3: A3[] = [ + ['click', [], new Date('2020-02-09')], + [ + 'keydown', + [ + { down: 'ArrowDown' }, + ], + data.date, + ], + [ + 'keydown', + [ + { down: 'ArrowDown' }, + { press: ' ' }, + ], + data.date, + ], + [ + 'keydown', + [ + { down: 'ArrowDown' }, + { press: 'Enter' }, + ], + data.date, + ], + ]; + cases3.forEach(a => { + const [testEventType, testKeyPayloads, testSelectedDate] = a; + it( + messageFormatter('selects new date (eventType=%s, sendKeysPayloads=%j)', a), + async () => { + const el = await fixture( + html`` + ); + + const dateUpdatedEventTask = new Promise((resolve) => { + el.addEventListener('date-updated', function fn(ev) { + resolve((ev as CustomEvent).detail); + + el.removeEventListener('date-updated', fn); + }); + }); + + const selectedDate = el.shadowRoot?.querySelector( + `${elementSelectors.calendarDay}[aria-label="${ + formatters.fullDateFormat(testSelectedDate) + }"]` + ); + + expect(selectedDate).exist; + + selectedDate?.focus(); + + if (testEventType === 'click') { + selectedDate?.click(); + } else { + for (const n of testKeyPayloads) { + await sendKeys(n as SendKeysPayload); + } + } + + el.requestUpdate(); + + await el.updateComplete; + const dateUpdatedEvent = await dateUpdatedEventTask; + + const newSelectedDate = el.shadowRoot?.querySelector( + elementSelectors.selectedCalendarDay + ); + + expect(newSelectedDate).exist; + expect(newSelectedDate?.getAttribute('aria-label')).equal( + formatters.fullDateFormat(calendarInit.date) + ); + expect(newSelectedDate?.fullDate).deep.equal(calendarInit.date); + + const expectedDateUpdatedEvent: DateUpdatedEvent = { + isKeypress: testEventType === 'keydown', + value: new Date('2020-02-09'), + }; + + expect(dateUpdatedEvent).deep.equal(expectedDateUpdatedEvent); + expect(el.shadowRoot?.activeElement?.isEqualNode(newSelectedDate ?? null)); + } + ); + }); + + type A4 = [Partial, string]; + const cases4: A4[] = [ + [{}, elementSelectors.calendarTable], + [{}, elementSelectors.hiddenCalendarDay], + [ + {}, + `${elementSelectors.disabledCalendarDay}[aria-label="${ + formatters.fullDateFormat(new Date(calendarInit.date)) + }"]`, + ], + [ + { + disabledDatesSet: new Set([+new Date('2020-02-15')]), + }, + `${elementSelectors.disabledCalendarDay}[aria-label="${ + formatters.fullDateFormat(new Date('2020-02-15')) + }"]`, + ], + ]; + cases4.forEach(a => { + const [testPartialData, testElementSelector] = a; + it( + messageFormatter('does not select new date (partialData=%j, elementSelector=%s)', a), + async () => { + const testCalendar = calendar({ + ...calendarInit, + ...( + testPartialData.disabledDatesSet && { + disabledDates: [...testPartialData.disabledDatesSet].map(n => new Date(n)), + } + ), + }); + const el = await fixture( + html`` + ); + + const newSelectedDate = el.shadowRoot?.querySelector( + testElementSelector + ); + + newSelectedDate?.focus(); + newSelectedDate?.click(); + + await el.updateComplete; + + const selectedDate = el.shadowRoot?.querySelector( + elementSelectors.selectedCalendarDay + ); + + expect(selectedDate).exist; + expect(selectedDate?.getAttribute('aria-label')).equal( + formatters.fullDateFormat(data.date) + ); + } + ); + }); + + // type A5 = [string, 'click' | 'keydown']; + // const cases5 = [ + // ['focuses', 'click'], + // ]; + // cases5.forEach(a => { + // const [testPartialData, testElementSelector] = a; + // it( + // messageFormatter('%s new date (eventType=%s)', a), + // async () => { + // const testCalendar = calendar({ + // ...calendarInit, + // ...( + // testPartialData.disabledDatesSet && { + // disabledDates: [...testPartialData.disabledDatesSet].map(n => new Date(n)), + // } + // ), + // }); + // const el = await fixture( + // html`` + // ); + + // const newSelectedDate = el.shadowRoot?.querySelector( + // testElementSelector + // ); + + // newSelectedDate?.focus(); + // newSelectedDate?.click(); + + // await el.updateComplete; + + // const selectedDate = el.shadowRoot?.querySelector( + // elementSelectors.selectedCalendarDay + // ); + + // expect(selectedDate).exist; + // expect(selectedDate?.getAttribute('aria-label')).equal( + // formatters.fullDateFormat(data.date) + // ); + // } + // ); + // }); + +}); diff --git a/src/tests/year-grid/year-grid.test.ts b/src/tests/year-grid/year-grid.test.ts index 41b52fdd..2ede93f2 100644 --- a/src/tests/year-grid/year-grid.test.ts +++ b/src/tests/year-grid/year-grid.test.ts @@ -43,20 +43,12 @@ describe(APP_YEAR_GRID_NAME, () => { ]); }); - type A = null | undefined; - const cases: A[] = [null, undefined]; - cases.forEach((a) => { - it( - messageFormatter('renders nothing (formatters=%s)', a), - async () => { - const el = await fixture(html``); - - expect(el.shadowRoot?.querySelector(elementSelectors.yearGrid)).not.exist; - } + it('renders nothing', async () => { + const el = await fixture( + html`` ); + + expect(el.shadowRoot?.querySelector(elementSelectors.yearGrid)).not.exist; }); it('focuses new year with keyboard', async () => { diff --git a/src/year-grid/year-grid.ts b/src/year-grid/year-grid.ts index 06f24816..213d9f34 100644 --- a/src/year-grid/year-grid.ts +++ b/src/year-grid/year-grid.ts @@ -17,7 +17,7 @@ import type { YearGridChangedProperties, YearGridData, YearGridProperties } from export class YearGrid extends LitElement implements YearGridProperties { @property({ attribute: false }) - public data: YearGridData; + public data?: YearGridData; @queryAsync('button[data-year][aria-selected="true"]') public selectedYearGridButton!: Promise; @@ -53,12 +53,16 @@ export class YearGrid extends LitElement implements YearGridProperties { } protected override shouldUpdate(): boolean { - return this.data.formatters != null; + return this.data != null && this.data.formatters != null; } public override willUpdate(changedProperties: YearGridChangedProperties): void { - if (changedProperties.has('data')) { - this.$focusingYear = this.#selectedYear = this.data.date.getUTCFullYear(); + if (changedProperties.has('data') && this.data) { + const { date } = this.data; + + if (date) { + this.$focusingYear = this.#selectedYear = date.getUTCFullYear(); + } } } @@ -72,7 +76,7 @@ export class YearGrid extends LitElement implements YearGridProperties { formatters, max, min, - } = this.data; + } = this.data as YearGridData; const focusingYear =this.$focusingYear; const { yearFormat } = formatters as Formatters; @@ -119,7 +123,7 @@ export class YearGrid extends LitElement implements YearGridProperties { const { max, min, - } = this.data; + } = this.data as YearGridData; const focusingYear = toNextSelectedYear({ key, max,