-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add appointment to Claim Status summary #345
Changes from 21 commits
efe89b7
0ef30dc
30f4d55
d30bb8d
e9f72ea
de6cf62
eb26ae4
bd7511e
5221a5b
7edecea
338c3dc
2b810d9
0420fea
80178e3
ed665bb
2814d22
1d55769
229d808
72ee757
b9789e5
0f100b2
5aaba29
7388dc4
236a9c1
d22837f
838d2af
ce47f62
8247a16
67882ed
8b81264
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { useTranslation } from 'next-i18next' | ||
|
||
import { TextLine } from './TextLine' | ||
import { AppointmentContent } from '../types/common' | ||
import { formatAppointmentDate } from '../utils/formatDate' | ||
import { capitalizeFirstLetter } from '../utils/strings' | ||
import { identifyI18nPeriod, samePeriod } from '../utils/timeSlot' | ||
|
||
export interface AppointmentProps extends AppointmentContent { | ||
loading: boolean | ||
} | ||
|
||
export const Appointment: React.FC<AppointmentProps> = ({ loading = false, date, timeSlot }) => { | ||
const { t, i18n } = useTranslation('common') | ||
|
||
let formattedAppointment = '' | ||
|
||
// Format the date portion. | ||
formattedAppointment = capitalizeFirstLetter(formatAppointmentDate(date, i18n.language)) | ||
|
||
// Format the time portion. | ||
if (timeSlot) { | ||
const start = timeSlot.rangeStart | ||
const end = timeSlot.rangeEnd | ||
|
||
// Appointment time slots are formatted using i18n's interpolation feature. | ||
// See https://www.i18next.com/translation-function/interpolation | ||
|
||
// If the times are both am or both pm, the string should look something like: | ||
// ", between 1–3 p.m. Pacific time" | ||
if (samePeriod(start, end)) { | ||
formattedAppointment += t('time.between-range', { range: `${start}–${end}`, ampm: t(identifyI18nPeriod(end)) }) | ||
} | ||
// If one time is am and one time is pm, the string should look something like: | ||
// ", between 10 a.m. and 12 p.m. Pacific time" | ||
else { | ||
formattedAppointment += t('time.between-start-end', { | ||
start: { time: start, ampm: t(identifyI18nPeriod(start)) }, | ||
end: { time: end, ampm: t(identifyI18nPeriod(end)) }, | ||
}) | ||
} | ||
} | ||
|
||
return ( | ||
<div key="appointment" className="appointment"> | ||
<TextLine loading={loading} text={formattedAppointment} /> | ||
</div> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Appointment } from './Appointment' | ||
import { TransLine } from './TransLine' | ||
import { ClaimSummaryContent } from '../types/common' | ||
|
||
export interface ClaimSummaryProps extends ClaimSummaryContent { | ||
loading: boolean | ||
userArrivedFromUioMobile: boolean | ||
} | ||
|
||
export const ClaimSummary: React.FC<ClaimSummaryProps> = ({ | ||
loading = false, | ||
userArrivedFromUioMobile = false, | ||
paragraphs, | ||
appointment, | ||
}) => { | ||
let elements: JSX.Element[] = [] | ||
|
||
// Build generic paragraphs. | ||
elements = paragraphs.map((paragraph, index) => ( | ||
<div key={index} className=""> | ||
<TransLine | ||
loading={loading} | ||
userArrivedFromUioMobile={userArrivedFromUioMobile} | ||
i18nKey={paragraph.i18nKey} | ||
links={paragraph.links} | ||
/> | ||
</div> | ||
)) | ||
|
||
// Insert appointment as second element. | ||
// Currently only needed for Scenario 2. | ||
if (appointment) { | ||
const formattedAppointment = ( | ||
<Appointment loading={loading} date={appointment.date} timeSlot={appointment.timeSlot} /> | ||
) | ||
// Splice it in as the second element. | ||
elements.splice(1, 0, formattedAppointment) | ||
} | ||
|
||
return <div className="summary">{elements}</div> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { Story, Meta } from '@storybook/react' | ||
|
||
import { Appointment as AppointmentComponent, AppointmentProps } from '../components/Appointment' | ||
|
||
export default { | ||
title: 'Component/Atoms/Appointment', | ||
component: AppointmentComponent, | ||
argTypes: { | ||
date: { | ||
description: 'Please ignore the time picker', | ||
control: { | ||
type: 'date', | ||
}, | ||
}, | ||
start: { | ||
name: 'start time', | ||
table: { | ||
type: { | ||
summary: 'number', | ||
}, | ||
}, | ||
control: { | ||
type: 'number', | ||
min: 1, | ||
max: 12, | ||
}, | ||
}, | ||
end: { | ||
name: 'end time', | ||
table: { | ||
type: { | ||
summary: 'number', | ||
}, | ||
}, | ||
control: { | ||
type: 'number', | ||
min: 1, | ||
max: 12, | ||
Comment on lines
+24
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these don't seem to stop me:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmmm. I'm going to go ahead and merge this without resolving it. I'll open a follow up (low priority) ticket. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ticket is #350 |
||
}, | ||
}, | ||
timeSlot: { | ||
table: { | ||
disable: true, | ||
}, | ||
}, | ||
}, | ||
Comment on lines
+41
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: This hides the |
||
} as Meta | ||
|
||
interface StoryAppointmentProps extends AppointmentProps { | ||
date: Date | ||
start?: number | ||
end?: number | ||
} | ||
|
||
const Template: Story<StoryAppointmentProps> = ({ ...args }) => { | ||
if (args.start && args.end) { | ||
args.timeSlot = { | ||
rangeStart: args.start, | ||
rangeEnd: args.end, | ||
} | ||
} | ||
return <AppointmentComponent {...args} /> | ||
} | ||
|
||
export const Appointment = Template.bind({}) | ||
Appointment.args = { | ||
date: new Date(), | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Story, Meta } from '@storybook/react' | ||
|
||
import { ClaimSummary as ClaimSummaryComponent, ClaimSummaryProps } from '../components/ClaimSummary' | ||
|
||
export default { | ||
title: 'Component/Atoms/Claim Summary', | ||
component: ClaimSummaryComponent, | ||
} as Meta | ||
|
||
const Template: Story<ClaimSummaryProps> = (args) => <ClaimSummaryComponent {...args} /> | ||
|
||
export const ClaimSummary = Template.bind({}) | ||
ClaimSummary.args = { | ||
paragraphs: [ | ||
{ | ||
i18nKey: 'claim-status:scenarios.scenario2.summary.0.text', | ||
}, | ||
{ | ||
i18nKey: 'claim-status:scenarios.scenario2.summary.1.text', | ||
}, | ||
], | ||
appointment: { | ||
date: new Date(), | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import MockDate from 'mockdate' | ||
import renderer, { act } from 'react-test-renderer' | ||
|
||
import i18n from '../jest-i18n' | ||
import { Appointment } from '../../components/Appointment' | ||
import { TimeSlot } from '../../types/common' | ||
import { getDateWithOffset } from '../../utils/formatDate' | ||
|
||
/** | ||
* Helper functions. | ||
*/ | ||
|
||
function renderAppointmentComponent(timeSlot: TimeSlot | undefined): string { | ||
const date = getDateWithOffset(0) | ||
return renderer.create(<Appointment loading={false} date={date} timeSlot={timeSlot} />).toJSON() | ||
} | ||
|
||
/** | ||
* Appointment snapshot tests. | ||
*/ | ||
|
||
beforeAll(() => { | ||
MockDate.set('2021-05-05') | ||
}) | ||
|
||
// Each test case should be: | ||
// [test description, timeSlot.rangeStart, timeSlot.rangeEnd] | ||
const testCases = [ | ||
['with no time slot, then match the snapshot', null, null], | ||
['with a morning time slot, then match the snapshot', 8, 10], | ||
['with an afternoon time slot, then match the snapshot', 1, 3], | ||
['with a time slot that starts in the morning and ends in the afternoon, then match the snapshot', 8, 3], | ||
['with a time slot that has a nonsense time range, then match the snapshot', 3, 9], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we just wanna allow this? 👀 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a good question. I'm not positive? I'll open a new ticket for this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ticket is #351 |
||
] | ||
Comment on lines
+23
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I love this setup! |
||
|
||
// Use describe.each() to DRY up the tests. | ||
// See https://jestjs.io/docs/api#describeeachtablename-fn-timeout | ||
describe.each(testCases)('If given an appointment', (description: string, start: number | null, end: number | null) => { | ||
// Construct the timeslot argument. | ||
let timeSlot: TimeSlot | undefined | ||
if (start && end) { | ||
timeSlot = { | ||
rangeStart: start, | ||
rangeEnd: end, | ||
} | ||
} | ||
|
||
// Run through the test cases first in English. | ||
it(`${description}`, () => { | ||
expect(renderAppointmentComponent(timeSlot)).toMatchSnapshot() | ||
}) | ||
|
||
// Run through the test cases again in Spanish. | ||
it(`${description}, in Spanish`, () => { | ||
// Change the language to Spanish. | ||
|
||
// The call to changeLanguage() must be wrapped in act(), otherwise Jest/react | ||
// complains. | ||
// See https://reactjs.org/link/wrap-tests-with-act | ||
|
||
// Disable floating promises lint check. eslint really wants us to handle the Promise | ||
// returned by changeLanguage(), but it doesn't appear necessary to this test. | ||
// This can be revisited and refactored in the future if necessary. | ||
/* eslint-disable @typescript-eslint/no-floating-promises */ | ||
act(() => { | ||
i18n.changeLanguage('es') | ||
}) | ||
|
||
// Run the actual test. | ||
expect(renderAppointmentComponent(timeSlot)).toMatchSnapshot() | ||
|
||
// Change the language back to Spanish so the first it() renders correctly in English. | ||
act(() => { | ||
i18n.changeLanguage('en') | ||
}) | ||
rocketnova marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/* eslint-enable @typescript-eslint/no-floating-promises */ | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:chef-kiss: component!