feat(time-picker): add showTimezone prop with comprehensive tests

- Add showTimezone prop to TimePickerProps for optional inline timezone display
- Integrate TimezoneLabel component into TimePicker when showTimezone=true
- Add 6 comprehensive test cases covering all showTimezone scenarios:
  * Default behavior (no timezone label)
  * Explicit disable with showTimezone=false
  * Enable with showTimezone=true
  * Inline prop correctly passed
  * No display when timezone is missing
  * Correct styling classes applied
- Update trigger-schedule panel to use showTimezone prop
- All 15 tests passing with good coverage
This commit is contained in:
lyzno1 2025-10-13 16:36:51 +08:00
parent af6dae3498
commit 4dfb8b988c
No known key found for this signature in database
4 changed files with 114 additions and 19 deletions

View File

@ -28,6 +28,15 @@ jest.mock('@/app/components/base/portal-to-follow-elem', () => ({
jest.mock('./options', () => () => <div data-testid="time-options" />)
jest.mock('./header', () => () => <div data-testid="time-header" />)
jest.mock('@/app/components/base/timezone-label', () => {
return function MockTimezoneLabel({ timezone, inline, className }: { timezone: string, inline?: boolean, className?: string }) {
return (
<span data-testid="timezone-label" data-timezone={timezone} data-inline={inline} className={className}>
UTC+8
</span>
)
}
})
describe('TimePicker', () => {
const baseProps = {
@ -92,4 +101,86 @@ describe('TimePicker', () => {
expect(isDayjsObject(emitted)).toBe(true)
expect(emitted?.utcOffset()).toBe(dayjs().tz('America/New_York').utcOffset())
})
describe('Timezone Label Integration', () => {
test('should not display timezone label by default', () => {
render(
<TimePicker
{...baseProps}
value="12:00 AM"
timezone="Asia/Shanghai"
/>,
)
expect(screen.queryByTestId('timezone-label')).not.toBeInTheDocument()
})
test('should not display timezone label when showTimezone is false', () => {
render(
<TimePicker
{...baseProps}
value="12:00 AM"
timezone="Asia/Shanghai"
showTimezone={false}
/>,
)
expect(screen.queryByTestId('timezone-label')).not.toBeInTheDocument()
})
test('should display timezone label when showTimezone is true', () => {
render(
<TimePicker
{...baseProps}
value="12:00 AM"
timezone="Asia/Shanghai"
showTimezone={true}
/>,
)
const timezoneLabel = screen.getByTestId('timezone-label')
expect(timezoneLabel).toBeInTheDocument()
expect(timezoneLabel).toHaveAttribute('data-timezone', 'Asia/Shanghai')
})
test('should pass inline prop to timezone label', () => {
render(
<TimePicker
{...baseProps}
value="12:00 AM"
timezone="America/New_York"
showTimezone={true}
/>,
)
const timezoneLabel = screen.getByTestId('timezone-label')
expect(timezoneLabel).toHaveAttribute('data-inline', 'true')
})
test('should not display timezone label when showTimezone is true but timezone is not provided', () => {
render(
<TimePicker
{...baseProps}
value="12:00 AM"
showTimezone={true}
/>,
)
expect(screen.queryByTestId('timezone-label')).not.toBeInTheDocument()
})
test('should apply shrink-0 and text-xs classes to timezone label', () => {
render(
<TimePicker
{...baseProps}
value="12:00 AM"
timezone="Europe/London"
showTimezone={true}
/>,
)
const timezoneLabel = screen.getByTestId('timezone-label')
expect(timezoneLabel).toHaveClass('shrink-0', 'text-xs')
})
})
})

View File

@ -19,6 +19,7 @@ import Header from './header'
import { useTranslation } from 'react-i18next'
import { RiCloseCircleFill, RiTimeLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import TimezoneLabel from '@/app/components/base/timezone-label'
const to24Hour = (hour12: string, period: Period) => {
const normalized = Number.parseInt(hour12, 10) % 12
@ -36,6 +37,7 @@ const TimePicker = ({
minuteFilter,
popupClassName,
notClearable = false,
showTimezone = false,
}: TimePickerProps) => {
const { t } = useTranslation()
const [isOpen, setIsOpen] = useState(false)
@ -214,6 +216,9 @@ const TimePicker = ({
onClick={handleClickTrigger}
>
{inputElem}
{showTimezone && timezone && (
<TimezoneLabel timezone={timezone} inline className="shrink-0 text-xs" />
)}
<RiTimeLine className={cn(
'h-4 w-4 shrink-0 text-text-quaternary',
isOpen ? 'text-text-secondary' : 'group-hover:text-text-secondary',

View File

@ -65,6 +65,8 @@ export type TimePickerProps = {
minuteFilter?: (minutes: string[]) => string[]
popupClassName?: string
notClearable?: boolean
/** Show timezone label inline with the time picker */
showTimezone?: boolean
}
export type TimePickerFooterProps = {

View File

@ -12,7 +12,6 @@ import NextExecutionTimes from './components/next-execution-times'
import MonthlyDaysSelector from './components/monthly-days-selector'
import OnMinuteSelector from './components/on-minute-selector'
import Input from '@/app/components/base/input'
import TimezoneLabel from '@/app/components/base/timezone-label'
import useConfig from './use-config'
const i18nPrefix = 'workflow.nodes.triggerSchedule'
@ -70,24 +69,22 @@ const Panel: FC<NodePanelProps<ScheduleTriggerNodeType>> = ({
<label className="mb-2 block text-xs font-medium text-gray-500">
{t('workflow.nodes.triggerSchedule.time')}
</label>
<div className="flex items-center gap-2">
<TimePicker
notClearable={true}
timezone={inputs.timezone}
value={inputs.visual_config?.time || '12:00 AM'}
onChange={(time) => {
if (time) {
const timeString = time.format('h:mm A')
handleTimeChange(timeString)
}
}}
onClear={() => {
handleTimeChange('12:00 AM')
}}
placeholder={t('workflow.nodes.triggerSchedule.selectTime')}
/>
<TimezoneLabel timezone={inputs.timezone} />
</div>
<TimePicker
notClearable={true}
timezone={inputs.timezone}
value={inputs.visual_config?.time || '12:00 AM'}
onChange={(time) => {
if (time) {
const timeString = time.format('h:mm A')
handleTimeChange(timeString)
}
}}
onClear={() => {
handleTimeChange('12:00 AM')
}}
placeholder={t('workflow.nodes.triggerSchedule.selectTime')}
showTimezone={true}
/>
</>
)}
</div>