Skip to content

Commit 7880658

Browse files
committed
test(useDate): add coverage for inspection findings
Add tests for medium/low severity findings from code inspection: - Cache eviction logic at MAX_CACHE_SIZE limit - Component lifecycle cleanup (watchEffect disposal) - useLocale integration with localeMap - Cache clearing on locale change - ZonedDateTime input handling - formatByString midnight/noon edge cases (12:00 AM/PM)
1 parent f8b323d commit 7880658

File tree

1 file changed

+279
-0
lines changed

1 file changed

+279
-0
lines changed

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

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,24 @@ describe('useDate', () => {
3636
expect(date!.day).toBe(15)
3737
})
3838

39+
it('should create date from ZonedDateTime', () => {
40+
const input = Temporal.ZonedDateTime.from('2024-06-15T10:30:00[America/New_York]')
41+
const date = adapter.date(input)
42+
43+
expect(date).not.toBeNull()
44+
expect(date!.year).toBe(2024)
45+
expect(date!.month).toBe(6)
46+
expect(date!.day).toBe(15)
47+
expect(date!.hour).toBe(10)
48+
expect(date!.minute).toBe(30)
49+
})
50+
51+
it('should validate ZonedDateTime as valid', () => {
52+
const zoned = Temporal.ZonedDateTime.from('2024-06-15T10:30:00[UTC]')
53+
54+
expect(adapter.isValid(zoned)).toBe(true)
55+
})
56+
3957
it('should create date from JavaScript Date', () => {
4058
const jsDate = new Date(2024, 5, 15, 10, 30, 0) // June 15, 2024
4159
const date = adapter.date(jsDate)
@@ -614,6 +632,28 @@ describe('useDate', () => {
614632
expect(formatted).toBe('02:30 PM')
615633
})
616634

635+
it('should format midnight as 12:00 AM', () => {
636+
const midnight = adapter.date('2024-06-15T00:00:00')!
637+
expect(adapter.formatByString(midnight, 'h:mm A')).toBe('12:00 AM')
638+
expect(adapter.formatByString(midnight, 'hh:mm a')).toBe('12:00 am')
639+
})
640+
641+
it('should format noon as 12:00 PM', () => {
642+
const noon = adapter.date('2024-06-15T12:00:00')!
643+
expect(adapter.formatByString(noon, 'h:mm A')).toBe('12:00 PM')
644+
expect(adapter.formatByString(noon, 'hh:mm a')).toBe('12:00 pm')
645+
})
646+
647+
it('should format 1 AM correctly', () => {
648+
const oneAm = adapter.date('2024-06-15T01:00:00')!
649+
expect(adapter.formatByString(oneAm, 'h:mm A')).toBe('1:00 AM')
650+
})
651+
652+
it('should format 1 PM correctly', () => {
653+
const onePm = adapter.date('2024-06-15T13:00:00')!
654+
expect(adapter.formatByString(onePm, 'h:mm A')).toBe('1:00 PM')
655+
})
656+
617657
it('should get format helper text', () => {
618658
expect(adapter.getFormatHelperText('keyboardDate')).toBe('mm/dd/yyyy')
619659
})
@@ -853,6 +893,59 @@ describe('useDate', () => {
853893
expect(usResult.toLowerCase()).toContain('june')
854894
expect(deResult.toLowerCase()).toContain('juni')
855895
})
896+
897+
it('should clear format cache when locale changes', () => {
898+
const testAdapter = new Vuetify0DateAdapter('en-US')
899+
const testDate = Temporal.PlainDateTime.from('2024-06-15T10:30:00')
900+
901+
// Format with US locale
902+
const usResult = testAdapter.format(testDate, 'month')
903+
expect(usResult.toLowerCase()).toContain('june')
904+
905+
// Change locale - this should clear the cache
906+
testAdapter.locale = 'de-DE'
907+
908+
// Format again - should use new locale, not cached US formatter
909+
const deResult = testAdapter.format(testDate, 'month')
910+
expect(deResult.toLowerCase()).toContain('juni')
911+
})
912+
913+
it('should not clear cache when setting same locale', () => {
914+
const testAdapter = new Vuetify0DateAdapter('en-US')
915+
const testDate = Temporal.PlainDateTime.from('2024-06-15T10:30:00')
916+
917+
// Format to populate cache
918+
const result1 = testAdapter.format(testDate, 'fullDate')
919+
920+
// Set same locale - should not clear cache
921+
testAdapter.locale = 'en-US'
922+
923+
// Should still produce same result (cache intact)
924+
const result2 = testAdapter.format(testDate, 'fullDate')
925+
expect(result1).toBe(result2)
926+
})
927+
928+
it('should evict oldest entries when cache exceeds limit', () => {
929+
const testAdapter = new Vuetify0DateAdapter('en-US')
930+
const testDate = Temporal.PlainDateTime.from('2024-06-15T10:30:00')
931+
932+
// Generate many unique format calls to exceed MAX_CACHE_SIZE (50)
933+
// Use formatByString with unique patterns to create unique cache keys
934+
for (let i = 0; i < 60; i++) {
935+
// Each unique format string creates a new cache entry
936+
testAdapter.formatByString(testDate, `YYYY-MM-DD-${i}`)
937+
}
938+
939+
// Cache should still work correctly after evictions
940+
// Verify by calling format with a known preset
941+
const result = testAdapter.format(testDate, 'fullDate')
942+
expect(result).toBeDefined()
943+
expect(result.length).toBeGreaterThan(0)
944+
945+
// Verify formatByString still works
946+
const customResult = testAdapter.formatByString(testDate, 'YYYY-MM-DD')
947+
expect(customResult).toBe('2024-06-15')
948+
})
856949
})
857950
})
858951

@@ -1095,4 +1188,190 @@ describe('useDate', () => {
10951188
expect(ctx.adapter).toBeInstanceOf(Vuetify0DateAdapter)
10961189
})
10971190
})
1191+
1192+
describe('component lifecycle integration', () => {
1193+
it('should provide and consume date context in component tree', async () => {
1194+
const { mount } = await import('@vue/test-utils')
1195+
const { defineComponent } = await import('vue')
1196+
1197+
const [useDateContext, provideDateContext, context] = createDateContext({
1198+
namespace: 'test:date',
1199+
locale: 'en-US',
1200+
})
1201+
1202+
let consumedContext: ReturnType<typeof useDateContext> | null = null
1203+
1204+
const ChildComponent = defineComponent({
1205+
setup () {
1206+
consumedContext = useDateContext()
1207+
return { date: consumedContext.adapter.date('2024-06-15T10:30:00') }
1208+
},
1209+
template: '<div>{{ date?.year }}</div>',
1210+
})
1211+
1212+
const ParentComponent = defineComponent({
1213+
setup () {
1214+
provideDateContext(context)
1215+
},
1216+
components: { ChildComponent },
1217+
template: '<ChildComponent />',
1218+
})
1219+
1220+
const wrapper = mount(ParentComponent)
1221+
1222+
expect(consumedContext).not.toBeNull()
1223+
expect(consumedContext!.adapter).toBe(context.adapter)
1224+
expect(consumedContext!.locale.value).toBe('en-US')
1225+
expect(wrapper.text()).toContain('2024')
1226+
1227+
wrapper.unmount()
1228+
})
1229+
1230+
it('should use plugin-provided context in component', async () => {
1231+
const { mount } = await import('@vue/test-utils')
1232+
const { defineComponent } = await import('vue')
1233+
1234+
const plugin = createDatePlugin({
1235+
namespace: 'test:plugin-date',
1236+
locale: 'de-DE',
1237+
})
1238+
1239+
let consumedLocale: string | undefined
1240+
1241+
const TestComponent = defineComponent({
1242+
setup () {
1243+
const ctx = useDate('test:plugin-date')
1244+
consumedLocale = ctx.locale.value
1245+
return { locale: ctx.locale }
1246+
},
1247+
template: '<div>{{ locale }}</div>',
1248+
})
1249+
1250+
const wrapper = mount(TestComponent, {
1251+
global: {
1252+
plugins: [plugin],
1253+
},
1254+
})
1255+
1256+
expect(consumedLocale).toBe('de-DE')
1257+
expect(wrapper.text()).toContain('de-DE')
1258+
1259+
wrapper.unmount()
1260+
})
1261+
1262+
it('should cleanup watchEffect on component unmount', async () => {
1263+
const { mount } = await import('@vue/test-utils')
1264+
const { defineComponent, ref, nextTick } = await import('vue')
1265+
1266+
const customAdapter = new Vuetify0DateAdapter('en-US')
1267+
const localeChanges: string[] = []
1268+
1269+
// Track locale changes by overriding the setter
1270+
Object.defineProperty(customAdapter, 'locale', {
1271+
get () {
1272+
return this._locale
1273+
},
1274+
set (value: string) {
1275+
localeChanges.push(value)
1276+
this._locale = value
1277+
this.formatCache?.clear()
1278+
this.numberFormatCache?.clear()
1279+
},
1280+
})
1281+
1282+
const [useDateContext, provideDateContext] = createDateContext({
1283+
namespace: 'test:cleanup',
1284+
adapter: customAdapter,
1285+
locale: 'en-US',
1286+
})
1287+
1288+
const ChildComponent = defineComponent({
1289+
setup () {
1290+
const context = useDateContext()
1291+
return { locale: context.locale }
1292+
},
1293+
template: '<div>{{ locale }}</div>',
1294+
})
1295+
1296+
const showChild = ref(true)
1297+
const ParentComponent = defineComponent({
1298+
setup () {
1299+
provideDateContext()
1300+
return { showChild }
1301+
},
1302+
components: { ChildComponent },
1303+
template: '<ChildComponent v-if="showChild" />',
1304+
})
1305+
1306+
const wrapper = mount(ParentComponent)
1307+
1308+
// Initial mount should sync locale
1309+
expect(localeChanges.length).toBeGreaterThanOrEqual(0)
1310+
const initialCount = localeChanges.length
1311+
1312+
// Unmount child
1313+
showChild.value = false
1314+
await nextTick()
1315+
1316+
// No additional locale changes should occur after unmount
1317+
// (cleanup prevents further sync)
1318+
expect(localeChanges.length).toBe(initialCount)
1319+
1320+
wrapper.unmount()
1321+
})
1322+
})
1323+
1324+
describe('useLocale integration', () => {
1325+
it('should sync with useLocale when available', async () => {
1326+
const { mount } = await import('@vue/test-utils')
1327+
const { defineComponent } = await import('vue')
1328+
const { createLocaleContext } = await import('#v0/composables/useLocale')
1329+
1330+
// Create locale context with messages for different locales
1331+
const [useLocaleContext, provideLocaleContext] = createLocaleContext({
1332+
namespace: 'test:locale',
1333+
default: 'en',
1334+
messages: {
1335+
en: { hello: 'Hello' },
1336+
de: { hello: 'Hallo' },
1337+
fr: { hello: 'Bonjour' },
1338+
},
1339+
})
1340+
1341+
const [useDateContext, provideDateContext] = createDateContext({
1342+
namespace: 'test:date-locale',
1343+
localeMap: { en: 'en-US', de: 'de-DE', fr: 'fr-FR' },
1344+
})
1345+
1346+
const ChildComponent = defineComponent({
1347+
setup () {
1348+
// Use both locale and date contexts
1349+
const localeContext = useLocaleContext()
1350+
const dateContext = useDateContext()
1351+
return {
1352+
dateLocale: dateContext.locale,
1353+
selectLocale: localeContext.select,
1354+
}
1355+
},
1356+
template: '<div>{{ dateLocale }}</div>',
1357+
})
1358+
1359+
const ParentComponent = defineComponent({
1360+
setup () {
1361+
provideLocaleContext()
1362+
provideDateContext()
1363+
},
1364+
components: { ChildComponent },
1365+
template: '<ChildComponent />',
1366+
})
1367+
1368+
const wrapper = mount(ParentComponent)
1369+
1370+
// Initial locale should be mapped from useLocale's selection
1371+
// (default is 'en' -> 'en-US')
1372+
expect(wrapper.text()).toContain('en-US')
1373+
1374+
wrapper.unmount()
1375+
})
1376+
})
10981377
})

0 commit comments

Comments
 (0)