Skip to content

Commit

Permalink
TypeScript Rollout Tier 9 - Datepicker (#379)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
kikuomax authored Jan 13, 2025
1 parent 569a9ae commit 2fb5c9f
Show file tree
Hide file tree
Showing 35 changed files with 770 additions and 527 deletions.
1 change: 0 additions & 1 deletion packages/buefy-next/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ const components = fs
)

const JS_COMPONENTS = [
'datepicker',
'datetimepicker',
]

Expand Down
Original file line number Diff line number Diff line change
@@ -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<InstanceType<typeof BDatepicker>>

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
}

Expand All @@ -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, {
Expand Down Expand Up @@ -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: {
Expand All @@ -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', () => {
Expand All @@ -90,7 +90,7 @@ describe('BDatepicker', () => {
it('render correctly', () => {
wrapper = shallowMount(BDatepicker, {
props: {
...defaultProps
...defaultProps()
},
global: {
stubs: {
Expand All @@ -104,7 +104,7 @@ describe('BDatepicker', () => {
}
}
})
wrapper.setProps({ dateCreator: () => {} })
wrapper.setProps({ dateCreator: () => newDate(2024, 8, 21) })
expect(wrapper.html()).toMatchSnapshot()
})

Expand Down Expand Up @@ -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()
})

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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
Expand All @@ -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)
})
})
Expand Down
Loading

0 comments on commit 2fb5c9f

Please sign in to comment.