Skip to content
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

Merged
merged 30 commits into from
Jul 30, 2021
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
efe89b7
Add time slot utility to identify whether two times are both AM or PM.
rocketnova Jul 27, 2021
0ef30dc
Fix business hour handling.
rocketnova Jul 28, 2021
30f4d55
Add format for appointment dates.
rocketnova Jul 28, 2021
d30bb8d
Build an appointment object. Refactor test helpers.
rocketnova Jul 28, 2021
e9f72ea
Pass appointment object through to Claim Status component.
rocketnova Jul 28, 2021
de6cf62
Add mockdate to getClaimStatus tests.
rocketnova Jul 28, 2021
eb26ae4
Add missing claim status snapshot tests for scenario 2 and 3.
rocketnova Jul 28, 2021
bd7511e
Display the appointment date in the Claim Status component.
rocketnova Jul 28, 2021
5221a5b
Pass current language to date-fns for i18n.
rocketnova Jul 29, 2021
7edecea
Fix time zone mock bug.
rocketnova Jul 29, 2021
338c3dc
Correct Spanish language translation based on feedback from the trans…
rocketnova Jul 29, 2021
2b810d9
Add Spanish language snapshots for appointment.
rocketnova Jul 29, 2021
0420fea
Refactor into separate Appointment component.
rocketnova Jul 29, 2021
80178e3
Refactor into separate ClaimSummary component.
rocketnova Jul 29, 2021
ed665bb
Refactor appointment translation string using i18n interpolation.
rocketnova Jul 29, 2021
2814d22
Refactor types.
rocketnova Jul 29, 2021
1d55769
Refactor type for Appointment component.
rocketnova Jul 29, 2021
229d808
Validate time slot times.
rocketnova Jul 30, 2021
72ee757
Customize Appointment story controls.
rocketnova Jul 30, 2021
b9789e5
Fix Claim Summary story nesting.
rocketnova Jul 30, 2021
0f100b2
Refactor appointment component tests.
rocketnova Jul 30, 2021
5aaba29
Add document-level comment for strings util.
rocketnova Jul 30, 2021
7388dc4
Add key to all claim summary components.
rocketnova Jul 30, 2021
236a9c1
Add another comment about act().
rocketnova Jul 30, 2021
d22837f
Remove use of unnecessary helper.
rocketnova Jul 30, 2021
838d2af
Eliminate issue with differences in server time resulting in test fai…
rocketnova Jul 30, 2021
ce47f62
Merge branch 'main' into rocket/341-scenario-2-summary
rocketnova Jul 30, 2021
8247a16
Fix comment typo.
rocketnova Jul 30, 2021
67882ed
Use integers in test instead of unnecessary consts.
rocketnova Jul 30, 2021
8b81264
Update snapshots.
rocketnova Jul 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions components/Appointment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useTranslation } from 'next-i18next'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:chef-kiss: component!


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>
)
}
20 changes: 7 additions & 13 deletions components/ClaimStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useTranslation } from 'next-i18next'

import { ClaimSummary } from './ClaimSummary'
import { NextSteps } from './NextSteps'
import { TextLine } from './TextLine'
import { TransLine } from './TransLine'
import { ClaimStatusContent } from '../types/common'

export interface ClaimStatusProps extends ClaimStatusContent {
Expand All @@ -26,18 +26,12 @@ export const ClaimStatus: React.FC<ClaimStatusProps> = ({
<div className="pending-status claim-subsection">
<TextLine loading={loading} header text={t(heading)} />
</div>
<div className="summary">
{summary.map((paragraph, index) => (
<div key={index} className="">
<TransLine
loading={loading}
userArrivedFromUioMobile={userArrivedFromUioMobile}
i18nKey={paragraph.i18nKey}
links={paragraph.links}
/>
</div>
))}
</div>
<ClaimSummary
loading={loading}
userArrivedFromUioMobile={userArrivedFromUioMobile}
paragraphs={summary.paragraphs}
appointment={summary.appointment}
/>
<NextSteps
loading={loading}
userArrivedFromUioMobile={userArrivedFromUioMobile}
Expand Down
41 changes: 41 additions & 0 deletions components/ClaimSummary.tsx
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>
}
6 changes: 6 additions & 0 deletions public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
"warning_plural": "To protect your information, you will be logged out in {{count}} minutes if you do not continue.",
"button": "Return to UI Home"
},
"time": {
"between-range": ", between {{range}} {{ampm}} Pacific time",
"between-start-end": ", between {{start.time}} {{start.ampm}} and {{end.time}} {{end.ampm}} Pacific time",
"am": "a.m.",
"pm": "p.m."
},
"urls": {
"edd": {
"ui-certify": "https://edd.ca.gov/Unemployment/certify.htm",
Expand Down
6 changes: 6 additions & 0 deletions public/locales/es/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
"warning_plural": "Para proteger su información, se cerrará la sesión en {{count}} minutos si no continúa.",
"button": "Volver a UI Casa"
},
"time": {
"between-range": ", entre {{range}} {{ampm}} hora del Pacífico",
"between-start-end": ", entre {{start.time}} {{start.ampm}} y {{end.time}} {{end.ampm}} hora del Pacífico",
"am": "a.m.",
"pm": "p.m."
},
"urls": {
"edd": {
"ui-certify": "https://edd.ca.gov/Unemployment/certify-espanol.htm",
Expand Down
68 changes: 68 additions & 0 deletions stories/Appointment.stories.tsx
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these don't seem to stop me:

Friday, July 30, 2021, between 12–15 p.m. Pacific time

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This hides the timeSlot argument from the Storybook Controls. Let me know if you disagree with this approach.

} 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(),
}
5 changes: 4 additions & 1 deletion stories/ClaimStatus.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ const Template: Story<ClaimStatusProps> = (args) => <ClaimStatusComponent {...ar
export const ClaimStatus = Template.bind({})
ClaimStatus.args = {
heading: 'claim-status:scenarios.scenario4.heading',
summary: [{ i18nKey: 'claim-status:scenarios.scenario4.summary.text' }],
summary: {
paragraphs: [{ i18nKey: 'claim-status:scenarios.scenario4.summary.0.text' }],
appointment: null,
},
yourNextSteps: [{ i18nKey: 'claim-status:scenarios.scenario4.your-next-steps.0.text' }],
eddNextSteps: [{ i18nKey: 'claim-status:scenarios.scenario4.edd-next-steps.0.text' }],
}
25 changes: 25 additions & 0 deletions stories/ClaimSummary.stories.tsx
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(),
},
}
78 changes: 78 additions & 0 deletions tests/components/Appointment.test.tsx
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],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we just wanna allow this? 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ticket is #351

]
Comment on lines +23 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The 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 */
})
})
Loading