-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(timezone-date): add composable for handling timezone-aware dates…
… & add ProjectLinkButton (#5191) * feat: add ProjectLinkButton component for project navigation Signed-off-by: Wanjin Noh <[email protected]> * feat(timezone-date): add composable for handling timezone-aware dates Signed-off-by: Wanjin Noh <[email protected]> * test: add unit tests for useTimezoneDate composable functionality Signed-off-by: Wanjin Noh <[email protected]> --------- Signed-off-by: Wanjin Noh <[email protected]>
- Loading branch information
Showing
3 changed files
with
142 additions
and
0 deletions.
There are no files selected for viewing
65 changes: 65 additions & 0 deletions
65
apps/web/src/common/composables/timezone-date/__tests__/index.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { ref } from 'vue'; | ||
|
||
import dayjs from 'dayjs'; | ||
import timezone from 'dayjs/plugin/timezone'; | ||
import utc from 'dayjs/plugin/utc'; | ||
import { | ||
describe, it, expect, vi, | ||
} from 'vitest'; | ||
|
||
import { useTimezoneDate } from '@/common/composables/timezone-date'; | ||
|
||
// Configure dayjs plugins | ||
dayjs.extend(utc); | ||
dayjs.extend(timezone); | ||
|
||
// Mock userStore | ||
vi.mock('@/store/user/user-store', () => ({ | ||
useUserStore: () => ({ | ||
state: { | ||
timezone: 'Asia/Seoul', | ||
}, | ||
}), | ||
})); | ||
|
||
describe('useTimezoneDate', () => { | ||
it('should return the correct timezone date when date is provided', () => { | ||
const dateRef = ref('2024-12-09T00:00:00Z'); | ||
const { timezoneDate, getTimezoneDate } = useTimezoneDate({ date: dateRef }); | ||
|
||
// Validate: getTimezoneDate function | ||
const expectedDate = dayjs.utc(dateRef.value).tz('Asia/Seoul').format('YYYY/MM/DD HH:mm:ss'); | ||
expect(getTimezoneDate(dateRef.value)).toBe(expectedDate); | ||
|
||
// Validate: computed timezoneDate | ||
expect(timezoneDate?.value).toBe(expectedDate); | ||
}); | ||
|
||
it('should return undefined for timezoneDate when date is not provided', () => { | ||
const { timezoneDate, getTimezoneDate } = useTimezoneDate(); | ||
|
||
// Validate: timezoneDate should be undefined | ||
expect(timezoneDate).toBeUndefined(); | ||
|
||
// Validate: getTimezoneDate should still work | ||
const inputDate = '2024-12-09T00:00:00Z'; | ||
const expectedDate = dayjs.utc(inputDate).tz('Asia/Seoul').format('YYYY/MM/DD HH:mm:ss'); | ||
expect(getTimezoneDate(inputDate)).toBe(expectedDate); | ||
}); | ||
|
||
it('should update timezoneDate when date value changes', async () => { | ||
const dateRef = ref('2024-12-09T00:00:00Z'); | ||
const { timezoneDate } = useTimezoneDate({ date: dateRef }); | ||
|
||
// Validate initial value | ||
const initialExpectedDate = dayjs.utc(dateRef.value).tz('Asia/Seoul').format('YYYY/MM/DD HH:mm:ss'); | ||
expect(timezoneDate?.value).toBe(initialExpectedDate); | ||
|
||
// Change the date value | ||
dateRef.value = '2024-12-10T12:00:00Z'; | ||
|
||
// Validate updated value | ||
const updatedExpectedDate = dayjs.utc(dateRef.value).tz('Asia/Seoul').format('YYYY/MM/DD HH:mm:ss'); | ||
expect(timezoneDate?.value).toBe(updatedExpectedDate); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import type { Ref } from 'vue'; | ||
import { computed } from 'vue'; | ||
|
||
import dayjs from 'dayjs'; | ||
|
||
import { useUserStore } from '@/store/user/user-store'; | ||
|
||
interface UseTimezoneDateOptions { | ||
date?: Ref<string>; | ||
} | ||
export const useTimezoneDate = ({ | ||
date, | ||
}: UseTimezoneDateOptions = {}) => { | ||
const userStore = useUserStore(); | ||
const getTimezoneDate = (_date: string): string => dayjs.utc(_date).tz(userStore.state.timezone ?? 'UTC').format('YYYY/MM/DD HH:mm:ss'); | ||
const timezoneDate = date ? computed(() => getTimezoneDate(date.value)) : undefined; | ||
return { | ||
getTimezoneDate, | ||
timezoneDate, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<script setup lang="ts"> | ||
import { computed } from 'vue'; | ||
import type { Location } from 'vue-router'; | ||
import { PTextButton, PI, PLink } from '@cloudforet/mirinae'; | ||
import { useProjectReferenceStore } from '@/store/reference/project-reference-store'; | ||
import { useProperRouteLocation } from '@/common/composables/proper-route-location'; | ||
import { PROJECT_ROUTE } from '@/services/project/routes/route-constant'; | ||
const props = defineProps<{ | ||
projectId: string; | ||
to?: Location; | ||
useClickEvent?: boolean; | ||
}>(); | ||
const emit = defineEmits<{(event: 'click'): void; | ||
}>(); | ||
const projectReferenceStore = useProjectReferenceStore(); | ||
const { getProperRouteLocation } = useProperRouteLocation(); | ||
const projectPageLocation = computed<Location>(() => (getProperRouteLocation({ | ||
name: PROJECT_ROUTE.DETAIL._NAME, | ||
params: { | ||
id: props.projectId, | ||
}, | ||
}))); | ||
</script> | ||
|
||
<template> | ||
<span> | ||
<p-link v-if="!props.useClickEvent" | ||
action-icon="internal-link" | ||
new-tab | ||
:to="props.to || projectPageLocation" | ||
> | ||
{{ projectReferenceStore.getters.projectItems[props.projectId]?.label || props.projectId }} | ||
</p-link> | ||
<p-text-button v-else | ||
class="text-gray-900" | ||
size="md" | ||
@click="emit('click')" | ||
> | ||
<span> | ||
{{ projectReferenceStore.getters.projectItems[props.projectId]?.label || props.projectId }} | ||
<p-i name="ic_arrow-right-up" | ||
class="link-mark" | ||
height="0.875rem" | ||
width="0.875rem" | ||
color="inherit" | ||
/> | ||
</span> | ||
</p-text-button> | ||
</span> | ||
</template> | ||
|