Skip to content

Commit b738da2

Browse files
committed
refactor(useDate): fix stale V0DateAdapter references and add type tests
- Update all references to renamed Vuetify0DateAdapter class - Replace == null with isNullOrUndefined() utility - Add type-level regression tests for overload protection - Fix createDateContext type signature in docs
1 parent f25ea5e commit b738da2

File tree

5 files changed

+50
-19
lines changed

5 files changed

+50
-19
lines changed

apps/docs/src/pages/composables/plugins/use-date.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ const isSameMonth = adapter.isSameMonth(today, inFiveDays)
105105

106106
- **Details**
107107

108-
- `adapter`: Custom date adapter (default: `V0DateAdapter` using Temporal API)
108+
- `adapter`: Custom date adapter (default: `Vuetify0DateAdapter` using Temporal API)
109109
- `locale`: Default locale for formatting (default: 'en-US' or from `useLocale`)
110110
- `localeMap`: Mapping from short locale codes to Intl locale strings
111111
- `namespace`: Context namespace for dependency injection (default: 'v0:date')
@@ -277,10 +277,8 @@ The `formatByString()` method supports these tokens:
277277

278278
- **Type**
279279
```ts
280-
function createDateContext<
281-
T extends Temporal.PlainDateTime = Temporal.PlainDateTime,
282-
E extends DateContext<T> = DateContext<T>
283-
> (options: DateContextOptions<T>): ContextTrinity<E>
280+
function createDateContext (options?: DateContextOptionsDefault): ContextTrinity<DateContext<Temporal.PlainDateTime>>
281+
function createDateContext<T> (options: DateContextOptionsWithAdapter<T>): ContextTrinity<DateContext<T>>
284282
```
285283

286284
- **Details**

packages/0/src/composables/useDate/adapters/v0.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export class Vuetify0DateAdapter implements DateAdapter<PlainDateTime> {
7878
* hydration via `<ClientOnly>` (Nuxt) or `v-if` + `onMounted` pattern.
7979
*/
8080
date (value?: unknown): PlainDateTime | null {
81-
if (value == null) {
81+
if (isNullOrUndefined(value)) {
8282
// SSR safety: Temporal.Now requires browser environment
8383
return IN_BROWSER
8484
? Temporal.Now.plainDateTimeISO()
@@ -190,7 +190,7 @@ export class Vuetify0DateAdapter implements DateAdapter<PlainDateTime> {
190190
}
191191

192192
isValid (date: unknown): boolean {
193-
if (date == null) return false
193+
if (isNullOrUndefined(date)) return false
194194

195195
if (date instanceof Temporal.PlainDateTime) return true
196196
if (date instanceof Temporal.PlainDate) return true

packages/0/src/composables/useDate/index.bench.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Temporal } from '@js-temporal/polyfill'
22
import { bench, describe } from 'vitest'
3-
import { V0DateAdapter } from './adapters/v0'
3+
import { Vuetify0DateAdapter } from './adapters/v0'
44

55
describe('useDate benchmarks', () => {
6-
describe('V0DateAdapter', () => {
7-
const adapter = new V0DateAdapter()
6+
describe('Vuetify0DateAdapter', () => {
7+
const adapter = new Vuetify0DateAdapter()
88
const testDate = Temporal.PlainDateTime.from('2024-06-15T10:30:00')
99

1010
describe('construction', () => {

packages/0/src/composables/useDate/index.test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, expect, beforeEach } from 'vitest'
1+
import { describe, it, expect, beforeEach, expectTypeOf } from 'vitest'
22
import { Temporal } from '@js-temporal/polyfill'
33
import { createDate, createDateContext, createDateFallback, createDatePlugin, useDate } from './index'
44
import { Vuetify0DateAdapter } from './adapters/v0'
@@ -1374,4 +1374,38 @@ describe('useDate', () => {
13741374
wrapper.unmount()
13751375
})
13761376
})
1377+
1378+
describe('type safety (compile-time)', () => {
1379+
it('should infer correct types for overloads', () => {
1380+
// Default adapter: returns DateContext<Temporal.PlainDateTime>
1381+
const defaultCtx = createDate()
1382+
expectTypeOf(defaultCtx.adapter.date()).toEqualTypeOf<Temporal.PlainDateTime | null>()
1383+
1384+
// Custom adapter with explicit type: infers T from adapter
1385+
const customAdapter = new Vuetify0DateAdapter()
1386+
const customCtx = createDate({ adapter: customAdapter })
1387+
expectTypeOf(customCtx.adapter.date()).toEqualTypeOf<Temporal.PlainDateTime | null>()
1388+
1389+
// Context trinity returns correct type
1390+
const [, , ctx] = createDateContext()
1391+
expectTypeOf(ctx.adapter.date()).toEqualTypeOf<Temporal.PlainDateTime | null>()
1392+
1393+
// useDate returns default type
1394+
const usedCtx = useDate()
1395+
expectTypeOf(usedCtx.adapter.date()).toEqualTypeOf<Temporal.PlainDateTime | null>()
1396+
})
1397+
1398+
// Type-level tests: these verify TypeScript rejects invalid calls
1399+
// The @ts-expect-error comments ensure these FAIL to compile if removed
1400+
it('should reject invalid overload usage at compile time', () => {
1401+
// @ts-expect-error - T provided without adapter should fail
1402+
createDate<Date>()
1403+
1404+
// @ts-expect-error - T provided without adapter should fail
1405+
createDateContext<Date>()
1406+
1407+
// @ts-expect-error - T provided without adapter should fail
1408+
createDatePlugin<Date>()
1409+
})
1410+
})
13771411
})

packages/0/src/composables/useDate/index.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ export interface DateOptionsWithAdapter<T> extends DateOptionsBase {
8686
adapter: DateAdapter<T>
8787
}
8888

89-
/** Options when using the default V0DateAdapter */
89+
/** Options when using the default Vuetify0DateAdapter */
9090
export interface DateOptionsDefault extends DateOptionsBase {
91-
/** When omitted, V0DateAdapter is used */
91+
/** When omitted, Vuetify0DateAdapter is used */
9292
adapter?: undefined
9393
}
9494

@@ -214,7 +214,7 @@ function createDateInternal<T> (
214214
}
215215

216216
/**
217-
* Creates a new date context with the default V0DateAdapter.
217+
* Creates a new date context with the default Vuetify0DateAdapter.
218218
*
219219
* @param options Optional locale configuration.
220220
* @returns A date context using Temporal.PlainDateTime.
@@ -282,7 +282,7 @@ export function createDateFallback (): DateContext<DefaultDateType> {
282282
}
283283

284284
/**
285-
* Creates a new date context trinity with the default V0DateAdapter.
285+
* Creates a new date context trinity with the default Vuetify0DateAdapter.
286286
*
287287
* @param options Optional locale and namespace configuration.
288288
* @returns A trinity [useContext, provideContext, defaultContext].
@@ -333,7 +333,7 @@ export function createDateContext<T = DefaultDateType> (
333333
}
334334

335335
/**
336-
* Creates a new date plugin with the default V0DateAdapter.
336+
* Creates a new date plugin with the default Vuetify0DateAdapter.
337337
*
338338
* @param options Optional locale and namespace configuration.
339339
* @returns A Vue plugin.
@@ -378,8 +378,7 @@ export function createDatePlugin<T = DefaultDateType> (
378378
) {
379379
const { namespace = 'v0:date', ...options } = _options
380380

381-
// Preserve custom adapter if provided, otherwise use default
382-
// Safe cast: discriminated union check ensures correct overload selection
381+
// Type narrowing required for overload resolution (cannot be extracted without losing type safety)
383382
const [, provideDateContext, context] = 'adapter' in options && options.adapter !== undefined
384383
? createDateContext({ namespace, ...options } as DateContextOptionsWithAdapter<T>)
385384
: createDateContext({ namespace, ...options } as DateContextOptionsDefault) as unknown as ContextTrinity<DateContext<T>>
@@ -396,7 +395,7 @@ export function createDatePlugin<T = DefaultDateType> (
396395
* Returns the current date context.
397396
*
398397
* When called inside a component with a provided date context (via plugin or provider),
399-
* returns that context. Otherwise, returns a fallback context with V0DateAdapter.
398+
* returns that context. Otherwise, returns a fallback context with Vuetify0DateAdapter.
400399
*
401400
* @param namespace The namespace to look up (defaults to 'v0:date').
402401
* @returns The current date context.

0 commit comments

Comments
 (0)