From 2fb5c9f74cc80b7560f08fc63092e0b771701eb1 Mon Sep 17 00:00:00 2001 From: Kikuo Emoto Date: Mon, 13 Jan 2025 22:47:04 +0900 Subject: [PATCH] TypeScript Rollout Tier 9 - Datepicker (#379) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(lib): rewrite datepicker in TS Rewrites the datepicker components in the `src/components/datepicker` folder in TypeScript. Adds a new file `types.ts` which defines common types shared among the datepicker components. Makes some props explicitly allow `null` to suppress type errors at lines where `null` is assigned to them. Applies the `valueOf` method to `Date` when it is passed to `isNaN` because `isNaN` allows only a number. It should not matter to the decision. In some cases where the `Date` parameter may be `undefined`, we have to be careful not to apply `valueOf` to `undefined` and replace `undefined` with `NaN`. Replaces some `&&` chaining with ternary conditional operators (`?:`), where a fallback value should be `undefined` rather than a boolean (`false`). Overuses non-null assertion operators (`!`) to suppress type errors while keeping the original behavior. I decided not to use optional chaining operators (`?`) unless unavoidable, because they change the pass of the execution. Overuses type casting (`as`) to suppress type errors while keeping the original behavior. I did not take approaches that were more sophisticated but involving logic changes, since we decided to change the code, especially its logics, as little as possible during the migration process. Some trivial changes: - Wraps the entire component definition in a `defineComponent` call - All the Buefy components are directly named when they are registered; the `name` fields are no longer used. Otherwise type-checking won't work. - Replaces JSDoc-style comments with ordinary comments so that `@microsoft/api-extractor` won't pick them up for documentation. * test(lib): rewrite spec for datepicker in TS Rewrites the specs for the datepicker components in the `src/components/datepicker` in TypeScript. In `Datepicker.spec.js→ts`: - Hoists the `defaultProps` fixture to the top level so that its type is implicitly defined. And makes it a function so that it reflects the configuration made by `setOptions`. - `focusedDate` is no longer injected into `config` via `setOptions` but given as an independent constant. Because `focusedDate` is not a member of `BuefyConfig`, and `BuefyConfig` is not a container of arbitrary values. In `DatepickerMonth.spec.js→ts`: - `newDate` checks if the `y`, `m`, and `d` arguments are defined, because `Date` does not like them undefined. Creates `Date` without arguments if they are omitted. - `focusedDate` is no longer injected into `config` via `setOptions` but given as an independent constant. See the comments for `Datepicker.spec` for why. - Gives a `Date` instead of a number to the following methods: - `emitChosenDate` - `updateSelectedDate` Allowing a number to these methods does not make sense. In `DatepickerTable.spec.js→ts`: - Hoists the `defaultProps` fixture to the top level so that its type can be implicitly defined - `focusedDate` is no longer injected into `config` via `setOptions` but given as an independentconstant. See the comments for `Datepicker.spec` for why. In `DatepickerTableRow.spec.js→ts`: - `newDate` checks if the `y`, `m`, and `d` arguments are defined, because `Date` does not like them undefined. Create `Date` without arguments if they are omitted. - Gives a `Date` instead of a number to the following methods: - `emitChosenDate` - `updateSelectedDate` Allowing a number to these methods does not make sense. - `$emit` of the injected fake `$datepicker` explicitly receives parameters to suppress the type errors regarding `arguments`. Common in the spec files: - `newDate` checks if the `y`, `m`, and `d` arguments are defined. In the `__snapshots__` subfolder, replaces the spec snapshots produced by Jest with those by Vitest: - `Datepicker.spec.js.snap` → `Datepicker.spec.ts.snap` - `DatepickerMonth.spec.js.snap` → `DatepickerMonth.spec.ts.snap` - `DatepickerTable.spec.js.snap` → `DatepickerTable.spec.ts.snap` - `DatepickerTableRow.spec.js.snap` → `DatepickerTableRow.spec.ts.snap` Migrates from Jest to Vitest: - Imports the building blocks for the spec from the `vitest` package - Replaces `jest` with `vi` * chore(lib): bundle datepicker in TS `rollup.config.mjs` removes "datepicker" from `JS_COMPONENTS`. * feat(docs): rewrite docs for datepicker in TS Rewrites the documentation for the datepicker components in the `src/pages/components/datepicker` folder in TypeScript. All the changes are straightforward. Here is a TypeScript migration tip: - Explicitly import and register components so that they are type checked. No type-checking is performed for globally registered components. --- packages/buefy-next/rollup.config.mjs | 1 - ...{Datepicker.spec.js => Datepicker.spec.ts} | 64 +++--- .../src/components/datepicker/Datepicker.vue | 209 ++++++++++-------- ...rMonth.spec.js => DatepickerMonth.spec.ts} | 55 +++-- .../components/datepicker/DatepickerMonth.vue | 126 ++++++----- ...rTable.spec.js => DatepickerTable.spec.ts} | 40 ++-- .../components/datepicker/DatepickerTable.vue | 137 ++++++------ ...Row.spec.js => DatepickerTableRow.spec.ts} | 39 ++-- .../datepicker/DatepickerTableRow.vue | 121 ++++++---- .../__snapshots__/Datepicker.spec.js.snap | 54 ----- .../__snapshots__/Datepicker.spec.ts.snap | 54 +++++ .../DatepickerMonth.spec.js.snap | 33 --- .../DatepickerMonth.spec.ts.snap | 33 +++ .../DatepickerTable.spec.js.snap | 16 -- .../DatepickerTable.spec.ts.snap | 16 ++ .../DatepickerTableRow.spec.js.snap | 21 -- .../DatepickerTableRow.spec.ts.snap | 23 ++ .../datepicker/{index.js => index.ts} | 4 +- .../src/components/datepicker/types.ts | 41 ++++ .../components/datepicker/Datepicker.vue | 45 ++-- .../api/{datepicker.js => datepicker.ts} | 0 .../datepicker/examples/ExEditable.vue | 10 +- .../datepicker/examples/ExEvents.vue | 14 +- .../datepicker/examples/ExFooter.vue | 16 +- .../datepicker/examples/ExHeader.vue | 18 +- .../datepicker/examples/ExInline.vue | 10 +- .../datepicker/examples/ExMonth.vue | 4 + .../datepicker/examples/ExMultipleInput.vue | 13 +- .../examples/ExProgrammatically.vue | 7 +- .../datepicker/examples/ExRange.vue | 9 +- .../datepicker/examples/ExRangeInput.vue | 13 +- .../datepicker/examples/ExSimple.vue | 17 +- .../datepicker/examples/ExTrigger.vue | 17 +- .../datepicker/examples/ExUnselectable.vue | 17 +- .../{datepicker.js => datepicker.ts} | 0 35 files changed, 770 insertions(+), 527 deletions(-) rename packages/buefy-next/src/components/datepicker/{Datepicker.spec.js => Datepicker.spec.ts} (90%) rename packages/buefy-next/src/components/datepicker/{DatepickerMonth.spec.js => DatepickerMonth.spec.ts} (88%) rename packages/buefy-next/src/components/datepicker/{DatepickerTable.spec.js => DatepickerTable.spec.ts} (91%) rename packages/buefy-next/src/components/datepicker/{DatepickerTableRow.spec.js => DatepickerTableRow.spec.ts} (89%) delete mode 100644 packages/buefy-next/src/components/datepicker/__snapshots__/Datepicker.spec.js.snap create mode 100644 packages/buefy-next/src/components/datepicker/__snapshots__/Datepicker.spec.ts.snap delete mode 100644 packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerMonth.spec.js.snap create mode 100644 packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerMonth.spec.ts.snap delete mode 100644 packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTable.spec.js.snap create mode 100644 packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTable.spec.ts.snap delete mode 100644 packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTableRow.spec.js.snap create mode 100644 packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTableRow.spec.ts.snap rename packages/buefy-next/src/components/datepicker/{index.js => index.ts} (80%) create mode 100644 packages/buefy-next/src/components/datepicker/types.ts rename packages/docs/src/pages/components/datepicker/api/{datepicker.js => datepicker.ts} (100%) rename packages/docs/src/pages/components/datepicker/variables/{datepicker.js => datepicker.ts} (100%) diff --git a/packages/buefy-next/rollup.config.mjs b/packages/buefy-next/rollup.config.mjs index 66c0df5a6..e9a95badd 100644 --- a/packages/buefy-next/rollup.config.mjs +++ b/packages/buefy-next/rollup.config.mjs @@ -30,7 +30,6 @@ const components = fs ) const JS_COMPONENTS = [ - 'datepicker', 'datetimepicker', ] diff --git a/packages/buefy-next/src/components/datepicker/Datepicker.spec.js b/packages/buefy-next/src/components/datepicker/Datepicker.spec.ts similarity index 90% rename from packages/buefy-next/src/components/datepicker/Datepicker.spec.js rename to packages/buefy-next/src/components/datepicker/Datepicker.spec.ts index 9b327a287..011ecd6ab 100644 --- a/packages/buefy-next/src/components/datepicker/Datepicker.spec.js +++ b/packages/buefy-next/src/components/datepicker/Datepicker.spec.ts @@ -1,14 +1,16 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' import { shallowMount } from '@vue/test-utils' -import BDatepicker from '@components/datepicker/Datepicker' +import type { VueWrapper } from '@vue/test-utils' +import BDatepicker from '@components/datepicker/Datepicker.vue' import config, { setOptions } from '@utils/config' -let wrapper, defaultProps +let wrapper: VueWrapper> -const newDate = (y, m, d) => { +const newDate = (y: number, m: number, d: number) => { const date = new Date(Date.UTC(y, m, d)) - date.getDate = jest.fn(() => date.getUTCDate()) - date.getMonth = jest.fn(() => date.getUTCMonth()) + date.getDate = vi.fn(() => date.getUTCDate()) + date.getMonth = vi.fn(() => date.getUTCMonth()) return date } @@ -19,14 +21,19 @@ const defaultMonthNames = [ const defaultDayNames = ['Su', 'M', 'Tu', 'W', 'Th', 'F', 'S'] const defaultFirstDayOfWeek = 0 +const defaultProps = () => ({ + dayNames: config.defaultDayNames, + monthNames: config.defaultMonthNames, + focusedDate: newDate(2018, 7, 15) +}) + describe('BDatepicker', () => { describe('with invalid value from config config', () => { beforeEach(() => { setOptions(Object.assign(config, { defaultMonthNames: 'A string!', defaultDayNames: 'A string!', - defaultFirstDayOfWeek: 'A string!', - focusedDate: newDate(2018, 7, 1) + defaultFirstDayOfWeek: 'A string!' })) wrapper = shallowMount(BDatepicker, { @@ -57,19 +64,12 @@ describe('BDatepicker', () => { setOptions(Object.assign(config, { defaultMonthNames, defaultDayNames, - defaultFirstDayOfWeek, - focusedDate: newDate(2018, 7, 15) + defaultFirstDayOfWeek })) - defaultProps = { - dayNames: config.defaultDayNames, - monthNames: config.defaultMonthNames, - focusedDate: config.focusedDate - } - wrapper = shallowMount(BDatepicker, { props: { - ...defaultProps + ...defaultProps() }, global: { stubs: { @@ -78,8 +78,8 @@ describe('BDatepicker', () => { } }) - wrapper.vm.updateInternalState = jest.fn(() => wrapper.vm.updateInternalState) - wrapper.vm.togglePicker = jest.fn(() => wrapper.vm.togglePicker) + wrapper.vm.updateInternalState = vi.fn(() => wrapper.vm.updateInternalState) + wrapper.vm.togglePicker = vi.fn(() => wrapper.vm.togglePicker) }) it('is called', () => { @@ -90,7 +90,7 @@ describe('BDatepicker', () => { it('render correctly', () => { wrapper = shallowMount(BDatepicker, { props: { - ...defaultProps + ...defaultProps() }, global: { stubs: { @@ -104,7 +104,7 @@ describe('BDatepicker', () => { } } }) - wrapper.setProps({ dateCreator: () => {} }) + wrapper.setProps({ dateCreator: () => newDate(2024, 8, 21) }) expect(wrapper.html()).toMatchSnapshot() }) @@ -160,8 +160,8 @@ describe('BDatepicker', () => { it('react accordingly when calling onChange', async () => { const date = new Date() - await wrapper.setProps({ dateParser: jest.fn() }) - wrapper.vm.onChange(date) + await wrapper.setProps({ dateParser: vi.fn() }) + wrapper.vm.onChange(date + '') expect(wrapper.vm.dateParser).toHaveBeenCalled() }) @@ -268,8 +268,8 @@ describe('BDatepicker', () => { await wrapper.setProps({ openOnFocus: false }) - wrapper.vm.onFocus = jest.fn() - wrapper.vm.togglePicker = jest.fn() + wrapper.vm.onFocus = vi.fn() + wrapper.vm.togglePicker = vi.fn() wrapper.vm.handleOnFocus() expect(wrapper.vm.onFocus).toHaveBeenCalled() @@ -321,8 +321,8 @@ describe('BDatepicker', () => { inline: true, multiple: true }) - wrapper.vm.updateInternalState = jest.fn(() => wrapper.vm.updateInternalState) - wrapper.vm.togglePicker = jest.fn(() => wrapper.vm.togglePicker) + wrapper.vm.updateInternalState = vi.fn(() => wrapper.vm.updateInternalState) + wrapper.vm.togglePicker = vi.fn(() => wrapper.vm.togglePicker) }) it('should format multiple dates passed via array', () => { @@ -356,7 +356,7 @@ describe('BDatepicker', () => { describe('#formatValue', () => { it('should call dateFormatter, passing the date', async () => { - const mockDateFormatter = jest.fn() + const mockDateFormatter = vi.fn() await wrapper.setProps({ dateFormatter: mockDateFormatter, closeOnClick: false @@ -367,24 +367,24 @@ describe('BDatepicker', () => { }) it('should not call dateFormatter when value is undefined or NaN', async () => { - const mockDateFormatter = jest.fn() + const mockDateFormatter = vi.fn() await wrapper.setProps({ dateFormatter: mockDateFormatter }) wrapper.vm.formatValue(undefined) expect(mockDateFormatter.mock.calls.length).toEqual(0) - wrapper.vm.formatValue('buefy') + wrapper.vm.formatValue('buefy' as unknown as Date) expect(mockDateFormatter.mock.calls.length).toEqual(0) }) it('should not call dateFormatter when value is an array with undefined or NaN elements', async () => { - const mockDateFormatter = jest.fn() + const mockDateFormatter = vi.fn() await wrapper.setProps({ dateFormatter: mockDateFormatter }) - wrapper.vm.formatValue([new Date(), undefined]) + wrapper.vm.formatValue([new Date(), undefined] as unknown as Date[]) expect(mockDateFormatter.mock.calls.length).toEqual(0) - wrapper.vm.formatValue([new Date(), 'buefy']) + wrapper.vm.formatValue([new Date(), 'buefy'] as unknown as Date[]) expect(mockDateFormatter.mock.calls.length).toEqual(0) }) }) diff --git a/packages/buefy-next/src/components/datepicker/Datepicker.vue b/packages/buefy-next/src/components/datepicker/Datepicker.vue index fbe6a9eef..7fc8546f4 100644 --- a/packages/buefy-next/src/components/datepicker/Datepicker.vue +++ b/packages/buefy-next/src/components/datepicker/Datepicker.vue @@ -207,7 +207,7 @@ ref="input" :type="!isTypeMonth ? 'date' : 'month'" autocomplete="off" - :model-value="formatNative(computedValue)" + :model-value="formatNative(computedValue as Date | null | undefined)" :placeholder="placeholder" :size="size" :icon="icon" @@ -227,23 +227,43 @@ - diff --git a/packages/buefy-next/src/components/datepicker/DatepickerMonth.spec.js b/packages/buefy-next/src/components/datepicker/DatepickerMonth.spec.ts similarity index 88% rename from packages/buefy-next/src/components/datepicker/DatepickerMonth.spec.js rename to packages/buefy-next/src/components/datepicker/DatepickerMonth.spec.ts index 83416f9bd..319c153ce 100644 --- a/packages/buefy-next/src/components/datepicker/DatepickerMonth.spec.js +++ b/packages/buefy-next/src/components/datepicker/DatepickerMonth.spec.ts @@ -1,14 +1,22 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' import { shallowMount } from '@vue/test-utils' -import BDatepickerMonth from '@components/datepicker/DatepickerMonth' +import type { VueWrapper } from '@vue/test-utils' +import BDatepickerMonth from '@components/datepicker/DatepickerMonth.vue' import config, { setOptions } from '@utils/config' -let wrapper +type BDatepickerMonthInstance = InstanceType + +let wrapper: VueWrapper // it used to create a date in UTC but should use the local timezone, // because DatepickerMonth entirely works in the local timezone -const newDate = (y, m, d) => { - return new Date(y, m, d) +const newDate = (y?: number, m?: number, d?: number) => { + if (y != null && m != null && d != null) { + return new Date(y, m, d) + } else { + return new Date() + } } const thisMonth = newDate(2020, 4, 15).getMonth() @@ -56,22 +64,23 @@ const events = [ } ] +const focusedDate = newDate(2019, thisMonth, 11) + describe('BDatepickerMonth', () => { beforeEach(() => { setOptions(Object.assign(config, { defaultMonthNames: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' - ], - focusedDate: newDate(2019, thisMonth, 11) + ] })) wrapper = shallowMount(BDatepickerMonth, { props: { monthNames: config.defaultMonthNames, focused: { - month: config.focusedDate.getMonth(), - year: config.focusedDate.getFullYear() + month: focusedDate.getMonth(), + year: focusedDate.getFullYear() }, dateCreator } @@ -98,8 +107,8 @@ describe('BDatepickerMonth', () => { }) it('emit chosen date', () => { - wrapper.vm.selectableDate = jest.fn(() => true) - wrapper.vm.emitChosenDate(5) + wrapper.vm.selectableDate = vi.fn(() => true) + wrapper.vm.emitChosenDate(new Date(5)) expect(wrapper.vm.selectableDate).toHaveBeenCalled() expect(wrapper.emitted()['update:modelValue']).toBeTruthy() }) @@ -108,8 +117,8 @@ describe('BDatepickerMonth', () => { await wrapper.setProps({ range: false }) - wrapper.vm.selectableDate = jest.fn(() => true) - wrapper.vm.updateSelectedDate(5) + wrapper.vm.selectableDate = vi.fn(() => true) + wrapper.vm.updateSelectedDate(new Date(5)) expect(wrapper.emitted()['update:modelValue']).toBeTruthy() }) @@ -135,8 +144,8 @@ describe('BDatepickerMonth', () => { expect(wrapper.vm.selectableDate(day)).toBeFalsy() await wrapper.setProps({ selectableDates: [ - newDate(config.focusedDate.getFullYear(), config.focusedDate.getMonth(), 1), - newDate(config.focusedDate.getFullYear(), config.focusedDate.getMonth(), 2), + newDate(focusedDate.getFullYear(), focusedDate.getMonth(), 1), + newDate(focusedDate.getFullYear(), focusedDate.getMonth(), 2), day ] }) @@ -170,7 +179,7 @@ describe('BDatepickerMonth', () => { await wrapper.setProps({ selectableDates: (d) => d.getMonth() === 7, - unselectableDates: (d) => true + unselectableDates: () => true }) expect(wrapper.vm.selectableDate(day)).toBeTruthy() @@ -299,35 +308,35 @@ describe('BDatepickerMonth', () => { }) describe('focus', () => { - let wrapper + let wrapper: VueWrapper const nextMonth = thisMonth + 1 - let cellToFocus + let cellToFocus: HTMLElement beforeEach(() => { wrapper = shallowMount(BDatepickerMonth, { props: { monthNames: config.defaultMonthNames, focused: { - month: config.focusedDate.getMonth(), - year: config.focusedDate.getFullYear() + month: focusedDate.getMonth(), + year: focusedDate.getFullYear() }, dateCreator } }) const refName = `month-${nextMonth}` if (Array.isArray(wrapper.vm.$refs[refName])) { - cellToFocus = wrapper.vm.$refs[refName][0] + cellToFocus = (wrapper.vm.$refs[refName] as HTMLElement[])[0] } else { - cellToFocus = wrapper.vm.$refs[refName] + cellToFocus = wrapper.vm.$refs[refName] as HTMLElement } - jest.spyOn(cellToFocus, 'focus') + vi.spyOn(cellToFocus, 'focus') }) it('changing month should call focus on the corresponding cell', async () => { await wrapper.setProps({ focused: { month: nextMonth, - year: config.focusedDate.getFullYear() + year: focusedDate.getFullYear() } }) await wrapper.vm.$nextTick() diff --git a/packages/buefy-next/src/components/datepicker/DatepickerMonth.vue b/packages/buefy-next/src/components/datepicker/DatepickerMonth.vue index a88609d08..975606a48 100644 --- a/packages/buefy-next/src/components/datepicker/DatepickerMonth.vue +++ b/packages/buefy-next/src/components/datepicker/DatepickerMonth.vue @@ -18,14 +18,14 @@ @click.prevent="updateSelectedDate(date)" @mouseenter="setRangeHoverEndDate(date)" @keydown.prevent="manageKeydown($event, date)" - :tabindex="focused.month === date.getMonth() ? null : -1" + :tabindex="focused!.month === date.getMonth() ? undefined : -1" > - {{ monthNames[date.getMonth()] }} + {{ monthNames![date.getMonth()] }}
@@ -35,7 +35,7 @@ :class="classObject(date)" class="datepicker-cell" > - {{ monthNames[date.getMonth()] }} + {{ monthNames![date.getMonth()] }}
@@ -43,36 +43,47 @@ - diff --git a/packages/buefy-next/src/components/datepicker/DatepickerTable.spec.js b/packages/buefy-next/src/components/datepicker/DatepickerTable.spec.ts similarity index 91% rename from packages/buefy-next/src/components/datepicker/DatepickerTable.spec.js rename to packages/buefy-next/src/components/datepicker/DatepickerTable.spec.ts index d2d4d2c99..e82af6116 100644 --- a/packages/buefy-next/src/components/datepicker/DatepickerTable.spec.js +++ b/packages/buefy-next/src/components/datepicker/DatepickerTable.spec.ts @@ -1,10 +1,24 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' import { shallowMount } from '@vue/test-utils' -import BDatepickerTable from '@components/datepicker/DatepickerTable' +import BDatepickerTable from '@components/datepicker/DatepickerTable.vue' import config, { setOptions } from '@utils/config' -let defaultProps +const focusedDate = new Date(2018, 7, 10) -const newDate = (y, m, d) => { +const defaultProps = () => ({ + modelValue: newDate(2018, 6, 21), + dayNames: config.defaultDayNames, + monthNames: config.defaultMonthNames, + focused: { + month: focusedDate.getMonth(), + year: focusedDate.getFullYear() + }, + firstDayOfWeek: config.defaultFirstDayOfWeek, + unselectableDaysOfWeek: config.defaultUnselectableDaysOfWeek, + events: [] +}) + +const newDate = (y: number, m: number, d: number) => { // it used to create a date in UTC. but it is unnecessary, // because DatepickerTable entirely works in the local time zone. return new Date(y, m, d) @@ -19,22 +33,8 @@ describe('BDatepickerTable', () => { 'August', 'September', 'October', 'November', 'December' ], defaultDayNames: ['Su', 'M', 'Tu', 'W', 'Th', 'F', 'S'], - focusedDate: newDate(2018, 7, 10), defaultUnselectableDaysOfWeek: [] })) - - defaultProps = () => ({ - modelValue: newDate(2018, 6, 21), - dayNames: config.defaultDayNames, - monthNames: config.defaultMonthNames, - focused: { - month: config.focusedDate.getMonth(), - year: config.focusedDate.getFullYear() - }, - firstDayOfWeek: config.defaultFirstDayOfWeek, - unselectableDaysOfWeek: config.defaultUnselectableDaysOfWeek, - events: [] - }) }) it('is called', () => { @@ -49,14 +49,14 @@ describe('BDatepickerTable', () => { it('render correctly', () => { // replaces weeksInThisMonth to suppress locale dependent outputs - jest.spyOn(BDatepickerTable.computed, 'weeksInThisMonth').mockReturnValue([]) + vi.spyOn(BDatepickerTable.computed!, 'weeksInThisMonth').mockReturnValue([]) const wrapper = shallowMount(BDatepickerTable, { props: { ...defaultProps() } }) expect(wrapper.html()).toMatchSnapshot() - BDatepickerTable.computed.weeksInThisMonth.mockRestore() + vi.mocked(BDatepickerTable.computed!.weeksInThisMonth).mockRestore() }) it('assign events to weeks even if the event has a time', () => { @@ -77,7 +77,7 @@ describe('BDatepickerTable', () => { it('manage events as expected', () => { const monthEvent = { - date: newDate(config.focusedDate.getFullYear(), config.focusedDate.getMonth(), 13), + date: newDate(focusedDate.getFullYear(), focusedDate.getMonth(), 13), type: 'is-primary' } const events = [ diff --git a/packages/buefy-next/src/components/datepicker/DatepickerTable.vue b/packages/buefy-next/src/components/datepicker/DatepickerTable.vue index f0745ed16..2c579f15c 100644 --- a/packages/buefy-next/src/components/datepicker/DatepickerTable.vue +++ b/packages/buefy-next/src/components/datepicker/DatepickerTable.vue @@ -13,10 +13,10 @@ - diff --git a/packages/buefy-next/src/components/datepicker/DatepickerTableRow.spec.js b/packages/buefy-next/src/components/datepicker/DatepickerTableRow.spec.ts similarity index 89% rename from packages/buefy-next/src/components/datepicker/DatepickerTableRow.spec.js rename to packages/buefy-next/src/components/datepicker/DatepickerTableRow.spec.ts index c1ccd950a..5a813f000 100644 --- a/packages/buefy-next/src/components/datepicker/DatepickerTableRow.spec.js +++ b/packages/buefy-next/src/components/datepicker/DatepickerTableRow.spec.ts @@ -1,10 +1,16 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' import { shallowMount } from '@vue/test-utils' -import BDatepickerTableRow from '@components/datepicker/DatepickerTableRow' +import type { VueWrapper } from '@vue/test-utils' +import BDatepickerTableRow from '@components/datepicker/DatepickerTableRow.vue' -const newDate = (y, m, d) => { +const newDate = (y?: number, m?: number, d?: number) => { // it used to create a date in UTC. but it is unnecessary, // because DatepickerTableRow entirely works in the local time zone - return new Date(y, m, d) + if (y != null && m != null && d != null) { + return new Date(y, m, d) + } else { + return new Date() + } } const props = { firstDayOfWeek: 0, @@ -21,7 +27,7 @@ const props = { dateCreator: () => newDate() } -let wrapper +let wrapper: VueWrapper> describe('BDatepickerTableRow', () => { beforeEach(() => { @@ -139,13 +145,13 @@ describe('BDatepickerTableRow', () => { }) it('emit chosen date', () => { - wrapper.vm.selectableDate = jest.fn(() => false) - wrapper.vm.emitChosenDate(5) + wrapper.vm.selectableDate = vi.fn(() => false) + wrapper.vm.emitChosenDate(new Date(5)) expect(wrapper.vm.selectableDate).toHaveBeenCalled() expect(wrapper.emitted().select).toBeFalsy() - wrapper.vm.selectableDate = jest.fn(() => true) - wrapper.vm.emitChosenDate(5) + wrapper.vm.selectableDate = vi.fn(() => true) + wrapper.vm.emitChosenDate(new Date(5)) expect(wrapper.vm.selectableDate).toHaveBeenCalled() expect(wrapper.emitted().select).toBeTruthy() }) @@ -162,10 +168,11 @@ describe('BDatepickerTableRow', () => { global: { provide: { $datepicker: { - $emit(event) { + // TODO: it should be sufficient to test if `$emit` is called + $emit(event: string, week: number, year: number) { // Vue warns because BDatepickerTableRow is not // supposed to emit "week-number-click" - wrapper.vm.$emit(event, arguments[1], arguments[2]) + wrapper.vm.$emit(event, week, year) } } } @@ -176,7 +183,7 @@ describe('BDatepickerTableRow', () => { await $weekButton.trigger('click') expect(wrapper.emitted()['week-number-click']).toBeTruthy() - expect(wrapper.emitted()['week-number-click'][0].sort()).toEqual([weekDate.getDate(), weekDate.getFullYear()].sort()) + expect((wrapper.emitted()['week-number-click'][0] as number[]).sort()).toEqual([weekDate.getDate(), weekDate.getFullYear()].sort()) }) it('emit focused date', async () => { @@ -186,7 +193,7 @@ describe('BDatepickerTableRow', () => { const inc = 1 wrapper.vm.changeFocus(day, inc) await wrapper.vm.$nextTick() - const valueEmitted = wrapper.emitted()['change-focus'][0] + const valueEmitted = wrapper.emitted<[Date]>('change-focus')![0] expect(valueEmitted[0].getDate()).toEqual(d + inc) }) @@ -264,8 +271,8 @@ describe('BDatepickerTableRow', () => { }) describe('focus', () => { - let wrapper - let cellToFocus + let wrapper: VueWrapper> + let cellToFocus: HTMLElement beforeEach(() => { wrapper = shallowMount(BDatepickerTableRow, { @@ -279,9 +286,9 @@ describe('BDatepickerTableRow', () => { if (Array.isArray(wrapper.vm.$refs[refName])) { cellToFocus = wrapper.vm.$refs[refName][0] } else { - cellToFocus = wrapper.vm.$refs[refName] + cellToFocus = wrapper.vm.$refs[refName] as HTMLElement } - jest.spyOn(cellToFocus, 'focus') + vi.spyOn(cellToFocus, 'focus') }) it('changing day should call focus on the corresponding cell', async () => { diff --git a/packages/buefy-next/src/components/datepicker/DatepickerTableRow.vue b/packages/buefy-next/src/components/datepicker/DatepickerTableRow.vue index a10b5145f..1b2429e6a 100644 --- a/packages/buefy-next/src/components/datepicker/DatepickerTableRow.vue +++ b/packages/buefy-next/src/components/datepicker/DatepickerTableRow.vue @@ -1,4 +1,5 @@ + - diff --git a/packages/buefy-next/src/components/datepicker/__snapshots__/Datepicker.spec.js.snap b/packages/buefy-next/src/components/datepicker/__snapshots__/Datepicker.spec.js.snap deleted file mode 100644 index 191dfc00c..000000000 --- a/packages/buefy-next/src/components/datepicker/__snapshots__/Datepicker.spec.js.snap +++ /dev/null @@ -1,54 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BDatepicker render correctly 1`] = ` -
- -
-`; diff --git a/packages/buefy-next/src/components/datepicker/__snapshots__/Datepicker.spec.ts.snap b/packages/buefy-next/src/components/datepicker/__snapshots__/Datepicker.spec.ts.snap new file mode 100644 index 000000000..c6914127b --- /dev/null +++ b/packages/buefy-next/src/components/datepicker/__snapshots__/Datepicker.spec.ts.snap @@ -0,0 +1,54 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`BDatepicker > render correctly 1`] = ` +"
+
+
+ +
+ +
+
+ +
+
+
+
+
+
+ + + + +
+
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+
" +`; diff --git a/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerMonth.spec.js.snap b/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerMonth.spec.js.snap deleted file mode 100644 index 9317fb1ea..000000000 --- a/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerMonth.spec.js.snap +++ /dev/null @@ -1,33 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BDatepickerMonth render correctly 1`] = ` -
- -
-`; diff --git a/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerMonth.spec.ts.snap b/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerMonth.spec.ts.snap new file mode 100644 index 000000000..b3dbcef24 --- /dev/null +++ b/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerMonth.spec.ts.snap @@ -0,0 +1,33 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`BDatepickerMonth > render correctly 1`] = ` +"
+ +
" +`; diff --git a/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTable.spec.js.snap b/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTable.spec.js.snap deleted file mode 100644 index 3951c6d40..000000000 --- a/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTable.spec.js.snap +++ /dev/null @@ -1,16 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BDatepickerTable render correctly 1`] = ` -
-
-
Su
-
M
-
Tu
-
W
-
Th
-
F
-
S
-
-
-
-`; diff --git a/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTable.spec.ts.snap b/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTable.spec.ts.snap new file mode 100644 index 000000000..30cdf446b --- /dev/null +++ b/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTable.spec.ts.snap @@ -0,0 +1,16 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`BDatepickerTable > render correctly 1`] = ` +"
+
+
Su
+
M
+
Tu
+
W
+
Th
+
F
+
S
+
+
+
" +`; diff --git a/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTableRow.spec.js.snap b/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTableRow.spec.js.snap deleted file mode 100644 index 581f29013..000000000 --- a/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTableRow.spec.js.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BDatepickerTableRow render correctly 1`] = ` - -`; diff --git a/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTableRow.spec.ts.snap b/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTableRow.spec.ts.snap new file mode 100644 index 000000000..6db381bb9 --- /dev/null +++ b/packages/buefy-next/src/components/datepicker/__snapshots__/DatepickerTableRow.spec.ts.snap @@ -0,0 +1,23 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`BDatepickerTableRow > render correctly 1`] = ` +" + +" +`; diff --git a/packages/buefy-next/src/components/datepicker/index.js b/packages/buefy-next/src/components/datepicker/index.ts similarity index 80% rename from packages/buefy-next/src/components/datepicker/index.js rename to packages/buefy-next/src/components/datepicker/index.ts index 1e24336a0..047ccf669 100644 --- a/packages/buefy-next/src/components/datepicker/index.js +++ b/packages/buefy-next/src/components/datepicker/index.ts @@ -1,9 +1,11 @@ +import type { App } from 'vue' + import Datepicker from './Datepicker.vue' import { registerComponent } from '../../utils/plugins' const Plugin = { - install(Vue) { + install(Vue: App) { registerComponent(Vue, Datepicker) } } diff --git a/packages/buefy-next/src/components/datepicker/types.ts b/packages/buefy-next/src/components/datepicker/types.ts new file mode 100644 index 000000000..28689b03a --- /dev/null +++ b/packages/buefy-next/src/components/datepicker/types.ts @@ -0,0 +1,41 @@ +// Defines common types used among the datepicker components. + +import type { ComponentPublicInstance } from 'vue' + +export interface DatepickerEvent { + type: string + date: Date +} + +export interface FocusedDate { + year: number + month: number + day?: number +} + +export type DateSelector = (day: Date) => boolean + +/* eslint-disable @typescript-eslint/ban-types */ +// Partial interface that `Datepicker` is supposed to satisfy. +// +// Defined separately to avoid circular dependencies. +export type IDatepicker = ComponentPublicInstance< + { + multiple?: boolean + }, // P(rops) + {}, // B (raw bindings) + {}, // D(ata) + { + isTypeMonth: () => boolean, + dtf: () => Intl.DateTimeFormat + dtfMonth: () => Intl.DateTimeFormat + }, // C(omputed) + {}, // M(ethods) + { + 'week-number-click': (_week: number, _year: number) => boolean + } // E(mits) +> +/* eslint-enable @typescript-eslint/ban-types */ + +export type DateFormatter = (date: Date | Date[], vm: IDatepicker) => string +export type DateParser = (date: string, vm: IDatepicker) => Date | null diff --git a/packages/docs/src/pages/components/datepicker/Datepicker.vue b/packages/docs/src/pages/components/datepicker/Datepicker.vue index 2f2ff46a3..e400a3ca9 100644 --- a/packages/docs/src/pages/components/datepicker/Datepicker.vue +++ b/packages/docs/src/pages/components/datepicker/Datepicker.vue @@ -85,51 +85,62 @@ - diff --git a/packages/docs/src/pages/components/datepicker/api/datepicker.js b/packages/docs/src/pages/components/datepicker/api/datepicker.ts similarity index 100% rename from packages/docs/src/pages/components/datepicker/api/datepicker.js rename to packages/docs/src/pages/components/datepicker/api/datepicker.ts diff --git a/packages/docs/src/pages/components/datepicker/examples/ExEditable.vue b/packages/docs/src/pages/components/datepicker/examples/ExEditable.vue index c3f4bd27a..05d62de2d 100644 --- a/packages/docs/src/pages/components/datepicker/examples/ExEditable.vue +++ b/packages/docs/src/pages/components/datepicker/examples/ExEditable.vue @@ -29,8 +29,16 @@ - diff --git a/packages/docs/src/pages/components/datepicker/examples/ExFooter.vue b/packages/docs/src/pages/components/datepicker/examples/ExFooter.vue index 72e86bb38..3bed79f37 100644 --- a/packages/docs/src/pages/components/datepicker/examples/ExFooter.vue +++ b/packages/docs/src/pages/components/datepicker/examples/ExFooter.vue @@ -20,12 +20,20 @@ - diff --git a/packages/docs/src/pages/components/datepicker/examples/ExHeader.vue b/packages/docs/src/pages/components/datepicker/examples/ExHeader.vue index 24939ef88..e88d54240 100644 --- a/packages/docs/src/pages/components/datepicker/examples/ExHeader.vue +++ b/packages/docs/src/pages/components/datepicker/examples/ExHeader.vue @@ -24,12 +24,20 @@ - diff --git a/packages/docs/src/pages/components/datepicker/examples/ExInline.vue b/packages/docs/src/pages/components/datepicker/examples/ExInline.vue index e1bfef383..72263fe96 100644 --- a/packages/docs/src/pages/components/datepicker/examples/ExInline.vue +++ b/packages/docs/src/pages/components/datepicker/examples/ExInline.vue @@ -5,12 +5,16 @@ - diff --git a/packages/docs/src/pages/components/datepicker/examples/ExMonth.vue b/packages/docs/src/pages/components/datepicker/examples/ExMonth.vue index 76a82ad0f..ff2217eda 100644 --- a/packages/docs/src/pages/components/datepicker/examples/ExMonth.vue +++ b/packages/docs/src/pages/components/datepicker/examples/ExMonth.vue @@ -8,3 +8,7 @@ + + diff --git a/packages/docs/src/pages/components/datepicker/examples/ExMultipleInput.vue b/packages/docs/src/pages/components/datepicker/examples/ExMultipleInput.vue index f02069088..53b36c413 100644 --- a/packages/docs/src/pages/components/datepicker/examples/ExMultipleInput.vue +++ b/packages/docs/src/pages/components/datepicker/examples/ExMultipleInput.vue @@ -8,12 +8,19 @@ - diff --git a/packages/docs/src/pages/components/datepicker/examples/ExProgrammatically.vue b/packages/docs/src/pages/components/datepicker/examples/ExProgrammatically.vue index 2fe46bca7..814274ea8 100644 --- a/packages/docs/src/pages/components/datepicker/examples/ExProgrammatically.vue +++ b/packages/docs/src/pages/components/datepicker/examples/ExProgrammatically.vue @@ -7,14 +7,13 @@ placeholder="Select a date"> - diff --git a/packages/docs/src/pages/components/datepicker/examples/ExRange.vue b/packages/docs/src/pages/components/datepicker/examples/ExRange.vue index 8c46ecf4a..4daa0127a 100644 --- a/packages/docs/src/pages/components/datepicker/examples/ExRange.vue +++ b/packages/docs/src/pages/components/datepicker/examples/ExRange.vue @@ -8,8 +8,15 @@ - diff --git a/packages/docs/src/pages/components/datepicker/examples/ExSimple.vue b/packages/docs/src/pages/components/datepicker/examples/ExSimple.vue index 53f75b98d..5a8ebf65d 100644 --- a/packages/docs/src/pages/components/datepicker/examples/ExSimple.vue +++ b/packages/docs/src/pages/components/datepicker/examples/ExSimple.vue @@ -40,11 +40,20 @@ - diff --git a/packages/docs/src/pages/components/datepicker/examples/ExTrigger.vue b/packages/docs/src/pages/components/datepicker/examples/ExTrigger.vue index c887fddeb..151a53690 100644 --- a/packages/docs/src/pages/components/datepicker/examples/ExTrigger.vue +++ b/packages/docs/src/pages/components/datepicker/examples/ExTrigger.vue @@ -13,11 +13,20 @@ - diff --git a/packages/docs/src/pages/components/datepicker/examples/ExUnselectable.vue b/packages/docs/src/pages/components/datepicker/examples/ExUnselectable.vue index 6974031c3..08e7aa324 100644 --- a/packages/docs/src/pages/components/datepicker/examples/ExUnselectable.vue +++ b/packages/docs/src/pages/components/datepicker/examples/ExUnselectable.vue @@ -20,8 +20,17 @@ - diff --git a/packages/docs/src/pages/components/datepicker/variables/datepicker.js b/packages/docs/src/pages/components/datepicker/variables/datepicker.ts similarity index 100% rename from packages/docs/src/pages/components/datepicker/variables/datepicker.js rename to packages/docs/src/pages/components/datepicker/variables/datepicker.ts