Skip to content

Commit 33ccf5e

Browse files
users: use generic destinations for user CM form (#3663)
* use destinations for user CM form * use @storybook/test * support status updates on triple field * disable checkbox when form is disabled * update tests --------- Co-authored-by: Nathaniel Caza <[email protected]>
1 parent 3af1cb2 commit 33ccf5e

15 files changed

+879
-667
lines changed

package.json

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,17 @@
4848
"@mui/system": "5.15.6",
4949
"@mui/x-data-grid": "6.19.3",
5050
"@playwright/test": "1.41.2",
51-
"@storybook/addon-essentials": "7.6.7",
52-
"@storybook/addon-interactions": "7.6.7",
53-
"@storybook/addon-links": "7.6.7",
54-
"@storybook/addons": "7.6.7",
55-
"@storybook/blocks": "7.6.10",
56-
"@storybook/jest": "0.2.3",
57-
"@storybook/preview-api": "7.6.7",
58-
"@storybook/react": "7.6.7",
59-
"@storybook/react-vite": "7.6.7",
51+
"@storybook/addon-essentials": "7.6.13",
52+
"@storybook/addon-interactions": "7.6.13",
53+
"@storybook/addon-links": "7.6.13",
54+
"@storybook/addons": "7.6.13",
55+
"@storybook/blocks": "7.6.13",
56+
"@storybook/preview-api": "7.6.13",
57+
"@storybook/react": "7.6.13",
58+
"@storybook/react-vite": "7.6.13",
59+
"@storybook/test": "7.6.13",
6060
"@storybook/test-runner": "0.16.0",
61-
"@storybook/testing-library": "0.2.2",
62-
"@storybook/types": "7.6.7",
61+
"@storybook/types": "7.6.13",
6362
"@types/chance": "1.1.4",
6463
"@types/diff": "5.0.8",
6564
"@types/glob": "8.1.0",
@@ -134,7 +133,7 @@
134133
"remark-breaks": "4.0.0",
135134
"remark-gfm": "4.0.0",
136135
"semver": "7.5.4",
137-
"storybook": "7.6.7",
136+
"storybook": "7.6.13",
138137
"storybook-addon-mock": "4.3.0",
139138
"stylelint": "15.11.0",
140139
"stylelint-config-standard": "34.0.0",

web/src/app/forms/FormField.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ FormField.propTypes = {
287287

288288
multiple: p.bool,
289289

290+
destType: p.string,
290291
options: p.arrayOf(
291292
p.shape({
292293
label: p.string,

web/src/app/selection/DestinationField.stories.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import React from 'react'
22
import type { Meta, StoryObj } from '@storybook/react'
33
import DestinationField from './DestinationField'
4-
import { expect } from '@storybook/jest'
5-
import { within } from '@storybook/testing-library'
4+
import { expect, within } from '@storybook/test'
65
import { handleDefaultConfig } from '../storybook/graphql'
76
import { useArgs } from '@storybook/preview-api'
87
import { FieldValueInput } from '../../schema'

web/src/app/selection/DestinationInputDirect.stories.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import React from 'react'
22
import type { Meta, StoryObj } from '@storybook/react'
33
import DestinationInputDirect from './DestinationInputDirect'
4-
import { expect } from '@storybook/jest'
5-
import { within, userEvent } from '@storybook/testing-library'
4+
import { expect, within, userEvent } from '@storybook/test'
65
import { handleDefaultConfig } from '../storybook/graphql'
76
import { HttpResponse, graphql } from 'msw'
87
import { useArgs } from '@storybook/preview-api'

web/src/app/selection/DestinationInputDirect.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export default function DestinationInputDirect(
9494
}
9595

9696
// add live validation icon to the right of the textfield as an endAdornment
97-
if (adorn && props.value === debouncedValue) {
97+
if (adorn && props.value === debouncedValue && !props.disabled) {
9898
iprops = {
9999
endAdornment: <InputAdornment position='end'>{adorn}</InputAdornment>,
100100
...iprops,

web/src/app/selection/DestinationSearchSelect.stories.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import React from 'react'
22
import type { Meta, StoryObj } from '@storybook/react'
33
import DestinationSearchSelect from './DestinationSearchSelect'
4-
import { expect } from '@storybook/jest'
5-
import { userEvent, within } from '@storybook/testing-library'
4+
import { expect, userEvent, within } from '@storybook/test'
65
import { handleDefaultConfig } from '../storybook/graphql'
76
import { HttpResponse, graphql } from 'msw'
87
import { useArgs } from '@storybook/preview-api'

web/src/app/storybook/defaultDestTypes.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const destTypes: DestinationTypeInfo[] = [
4040
isSchedOnCallNotify: false,
4141
iconURL: '',
4242
iconAltText: '',
43-
supportsStatusUpdates: false,
43+
supportsStatusUpdates: true,
4444
statusUpdatesRequired: false,
4545
requiredFields: [
4646
{
@@ -109,4 +109,60 @@ export const destTypes: DestinationTypeInfo[] = [
109109
},
110110
],
111111
},
112+
{
113+
type: 'supports-status',
114+
name: 'Single Field Destination Type',
115+
enabled: true,
116+
disabledMessage: 'Single field destination type must be configured.',
117+
userDisclaimer: '',
118+
isContactMethod: true,
119+
isEPTarget: false,
120+
isSchedOnCallNotify: false,
121+
iconURL: '',
122+
iconAltText: '',
123+
supportsStatusUpdates: true,
124+
statusUpdatesRequired: false,
125+
requiredFields: [
126+
{
127+
fieldID: 'phone-number',
128+
labelSingular: 'Phone Number',
129+
labelPlural: 'Phone Numbers',
130+
hint: 'Include country code e.g. +1 (USA), +91 (India), +44 (UK)',
131+
hintURL: '',
132+
placeholderText: '11235550123',
133+
prefix: '+',
134+
inputType: 'tel',
135+
isSearchSelectable: false,
136+
supportsValidation: true,
137+
},
138+
],
139+
},
140+
{
141+
type: 'required-status',
142+
name: 'Single Field Destination Type',
143+
enabled: true,
144+
disabledMessage: 'Single field destination type must be configured.',
145+
userDisclaimer: '',
146+
isContactMethod: true,
147+
isEPTarget: false,
148+
isSchedOnCallNotify: false,
149+
iconURL: '',
150+
iconAltText: '',
151+
supportsStatusUpdates: false,
152+
statusUpdatesRequired: true,
153+
requiredFields: [
154+
{
155+
fieldID: 'phone-number',
156+
labelSingular: 'Phone Number',
157+
labelPlural: 'Phone Numbers',
158+
hint: 'Include country code e.g. +1 (USA), +91 (India), +44 (UK)',
159+
hintURL: '',
160+
placeholderText: '11235550123',
161+
prefix: '+',
162+
inputType: 'tel',
163+
isSearchSelectable: false,
164+
supportsValidation: true,
165+
},
166+
],
167+
},
112168
]
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import React from 'react'
2+
import type { Meta, StoryObj } from '@storybook/react'
3+
import UserContactMethodFormDest, { Value } from './UserContactMethodFormDest'
4+
import { expect, within, userEvent, waitFor } from '@storybook/test'
5+
import { handleDefaultConfig } from '../storybook/graphql'
6+
import { useArgs } from '@storybook/preview-api'
7+
import { HttpResponse, graphql } from 'msw'
8+
9+
const meta = {
10+
title: 'users/UserContactMethodFormDest',
11+
component: UserContactMethodFormDest,
12+
tags: ['autodocs'],
13+
parameters: {
14+
msw: {
15+
handlers: [
16+
handleDefaultConfig,
17+
graphql.query('ValidateDestination', ({ variables: vars }) => {
18+
return HttpResponse.json({
19+
data: {
20+
destinationFieldValidate: vars.input.value === '+15555555555',
21+
},
22+
})
23+
}),
24+
],
25+
},
26+
},
27+
render: function Component(args) {
28+
const [, setArgs] = useArgs()
29+
const onChange = (newValue: Value): void => {
30+
if (args.onChange) args.onChange(newValue)
31+
setArgs({ value: newValue })
32+
}
33+
return <UserContactMethodFormDest {...args} onChange={onChange} />
34+
},
35+
} satisfies Meta<typeof UserContactMethodFormDest>
36+
37+
export default meta
38+
type Story = StoryObj<typeof meta>
39+
40+
export const SupportStatusUpdates: Story = {
41+
args: {
42+
value: {
43+
name: 'supports status',
44+
dest: {
45+
type: 'supports-status',
46+
values: [
47+
{
48+
fieldID: 'phone-number',
49+
value: '+15555555555',
50+
},
51+
],
52+
},
53+
statusUpdates: false,
54+
},
55+
disabled: false,
56+
},
57+
play: async ({ canvasElement }) => {
58+
const canvas = within(canvasElement)
59+
// ensure status updates checkbox is clickable
60+
61+
await expect(
62+
await canvas.findByLabelText('Send alert status updates'),
63+
).not.toBeDisabled()
64+
},
65+
}
66+
67+
export const RequiredStatusUpdates: Story = {
68+
args: {
69+
value: {
70+
name: 'required status',
71+
dest: {
72+
type: 'required-status',
73+
values: [
74+
{
75+
fieldID: 'phone-number',
76+
value: '+15555555555',
77+
},
78+
],
79+
},
80+
statusUpdates: false,
81+
},
82+
disabled: false,
83+
},
84+
play: async ({ canvasElement }) => {
85+
const canvas = within(canvasElement)
86+
// ensure status updates checkbox is not clickable
87+
88+
await expect(
89+
await canvas.findByLabelText(
90+
'Send alert status updates (cannot be disabled for this type)',
91+
),
92+
).toBeDisabled()
93+
},
94+
}
95+
96+
export const ErrorSingleField: Story = {
97+
args: {
98+
value: {
99+
name: '-notvalid',
100+
dest: {
101+
type: 'single-field',
102+
values: [
103+
{
104+
fieldID: 'phone-number',
105+
value: '+',
106+
},
107+
],
108+
},
109+
statusUpdates: false,
110+
},
111+
disabled: false,
112+
errors: [
113+
{
114+
field: 'name',
115+
message: 'must begin with a letter',
116+
name: 'FieldError',
117+
path: [],
118+
details: {},
119+
},
120+
],
121+
},
122+
play: async ({ canvasElement }) => {
123+
const canvas = within(canvasElement)
124+
await userEvent.type(await canvas.findByLabelText('Phone Number'), '123')
125+
126+
// ensure errors are shown
127+
await expect(
128+
await canvas.findByText('Must begin with a letter'),
129+
).toBeVisible()
130+
await waitFor(async function CloseIcon() {
131+
await expect(await canvas.findByTestId('CloseIcon')).toBeVisible()
132+
})
133+
},
134+
}
135+
136+
export const ErrorMultiField: Story = {
137+
args: {
138+
value: {
139+
name: '-notvalid',
140+
dest: {
141+
type: 'triple-field',
142+
values: [
143+
{
144+
fieldID: 'first-field',
145+
value: '+',
146+
},
147+
{
148+
fieldID: 'second-field',
149+
value: 'notAnEmail',
150+
},
151+
{
152+
fieldID: 'third-field',
153+
value: '-',
154+
},
155+
],
156+
},
157+
statusUpdates: false,
158+
},
159+
disabled: false,
160+
errors: [
161+
{
162+
field: 'name',
163+
message: 'must begin with a letter',
164+
name: 'FieldError',
165+
path: [],
166+
details: {},
167+
},
168+
],
169+
},
170+
play: async ({ canvasElement }) => {
171+
const canvas = within(canvasElement)
172+
await userEvent.type(await canvas.findByLabelText('First Item'), '123')
173+
174+
// ensure errors are shown
175+
await expect(
176+
await canvas.findByText('Must begin with a letter'),
177+
).toBeVisible()
178+
179+
await waitFor(async function ThreeCloseIcons() {
180+
await expect(await canvas.findAllByTestId('CloseIcon')).toHaveLength(3)
181+
})
182+
},
183+
}
184+
185+
export const Disabled: Story = {
186+
args: {
187+
value: {
188+
name: 'disabled dest',
189+
dest: {
190+
type: 'triple-field',
191+
values: [],
192+
},
193+
statusUpdates: false,
194+
},
195+
disabled: true,
196+
},
197+
play: async ({ canvasElement }) => {
198+
const canvas = within(canvasElement)
199+
200+
const combo = await canvas.findByRole('combobox')
201+
202+
// get it's input field sibling (combo is a dom element)
203+
const input = combo.parentElement?.querySelector('input')
204+
await expect(input).toBeDisabled()
205+
206+
await expect(
207+
await canvas.findByPlaceholderText('11235550123'),
208+
).toBeDisabled()
209+
await expect(
210+
await canvas.findByPlaceholderText('[email protected]'),
211+
).toBeDisabled()
212+
await expect(
213+
await canvas.findByPlaceholderText('slack user ID'),
214+
).toBeDisabled()
215+
await expect(
216+
await canvas.findByLabelText('Send alert status updates'),
217+
).toBeDisabled()
218+
},
219+
}

0 commit comments

Comments
 (0)