Skip to content

Commit

Permalink
users: use generic destinations for user CM form (#3663)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
tony-tvu and mastercactapus authored Feb 9, 2024
1 parent 3af1cb2 commit 33ccf5e
Show file tree
Hide file tree
Showing 15 changed files with 879 additions and 667 deletions.
23 changes: 11 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,17 @@
"@mui/system": "5.15.6",
"@mui/x-data-grid": "6.19.3",
"@playwright/test": "1.41.2",
"@storybook/addon-essentials": "7.6.7",
"@storybook/addon-interactions": "7.6.7",
"@storybook/addon-links": "7.6.7",
"@storybook/addons": "7.6.7",
"@storybook/blocks": "7.6.10",
"@storybook/jest": "0.2.3",
"@storybook/preview-api": "7.6.7",
"@storybook/react": "7.6.7",
"@storybook/react-vite": "7.6.7",
"@storybook/addon-essentials": "7.6.13",
"@storybook/addon-interactions": "7.6.13",
"@storybook/addon-links": "7.6.13",
"@storybook/addons": "7.6.13",
"@storybook/blocks": "7.6.13",
"@storybook/preview-api": "7.6.13",
"@storybook/react": "7.6.13",
"@storybook/react-vite": "7.6.13",
"@storybook/test": "7.6.13",
"@storybook/test-runner": "0.16.0",
"@storybook/testing-library": "0.2.2",
"@storybook/types": "7.6.7",
"@storybook/types": "7.6.13",
"@types/chance": "1.1.4",
"@types/diff": "5.0.8",
"@types/glob": "8.1.0",
Expand Down Expand Up @@ -134,7 +133,7 @@
"remark-breaks": "4.0.0",
"remark-gfm": "4.0.0",
"semver": "7.5.4",
"storybook": "7.6.7",
"storybook": "7.6.13",
"storybook-addon-mock": "4.3.0",
"stylelint": "15.11.0",
"stylelint-config-standard": "34.0.0",
Expand Down
1 change: 1 addition & 0 deletions web/src/app/forms/FormField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ FormField.propTypes = {

multiple: p.bool,

destType: p.string,
options: p.arrayOf(
p.shape({
label: p.string,
Expand Down
3 changes: 1 addition & 2 deletions web/src/app/selection/DestinationField.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react'
import type { Meta, StoryObj } from '@storybook/react'
import DestinationField from './DestinationField'
import { expect } from '@storybook/jest'
import { within } from '@storybook/testing-library'
import { expect, within } from '@storybook/test'
import { handleDefaultConfig } from '../storybook/graphql'
import { useArgs } from '@storybook/preview-api'
import { FieldValueInput } from '../../schema'
Expand Down
3 changes: 1 addition & 2 deletions web/src/app/selection/DestinationInputDirect.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react'
import type { Meta, StoryObj } from '@storybook/react'
import DestinationInputDirect from './DestinationInputDirect'
import { expect } from '@storybook/jest'
import { within, userEvent } from '@storybook/testing-library'
import { expect, within, userEvent } from '@storybook/test'
import { handleDefaultConfig } from '../storybook/graphql'
import { HttpResponse, graphql } from 'msw'
import { useArgs } from '@storybook/preview-api'
Expand Down
2 changes: 1 addition & 1 deletion web/src/app/selection/DestinationInputDirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export default function DestinationInputDirect(
}

// add live validation icon to the right of the textfield as an endAdornment
if (adorn && props.value === debouncedValue) {
if (adorn && props.value === debouncedValue && !props.disabled) {
iprops = {
endAdornment: <InputAdornment position='end'>{adorn}</InputAdornment>,
...iprops,
Expand Down
3 changes: 1 addition & 2 deletions web/src/app/selection/DestinationSearchSelect.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react'
import type { Meta, StoryObj } from '@storybook/react'
import DestinationSearchSelect from './DestinationSearchSelect'
import { expect } from '@storybook/jest'
import { userEvent, within } from '@storybook/testing-library'
import { expect, userEvent, within } from '@storybook/test'
import { handleDefaultConfig } from '../storybook/graphql'
import { HttpResponse, graphql } from 'msw'
import { useArgs } from '@storybook/preview-api'
Expand Down
58 changes: 57 additions & 1 deletion web/src/app/storybook/defaultDestTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const destTypes: DestinationTypeInfo[] = [
isSchedOnCallNotify: false,
iconURL: '',
iconAltText: '',
supportsStatusUpdates: false,
supportsStatusUpdates: true,
statusUpdatesRequired: false,
requiredFields: [
{
Expand Down Expand Up @@ -109,4 +109,60 @@ export const destTypes: DestinationTypeInfo[] = [
},
],
},
{
type: 'supports-status',
name: 'Single Field Destination Type',
enabled: true,
disabledMessage: 'Single field destination type must be configured.',
userDisclaimer: '',
isContactMethod: true,
isEPTarget: false,
isSchedOnCallNotify: false,
iconURL: '',
iconAltText: '',
supportsStatusUpdates: true,
statusUpdatesRequired: false,
requiredFields: [
{
fieldID: 'phone-number',
labelSingular: 'Phone Number',
labelPlural: 'Phone Numbers',
hint: 'Include country code e.g. +1 (USA), +91 (India), +44 (UK)',
hintURL: '',
placeholderText: '11235550123',
prefix: '+',
inputType: 'tel',
isSearchSelectable: false,
supportsValidation: true,
},
],
},
{
type: 'required-status',
name: 'Single Field Destination Type',
enabled: true,
disabledMessage: 'Single field destination type must be configured.',
userDisclaimer: '',
isContactMethod: true,
isEPTarget: false,
isSchedOnCallNotify: false,
iconURL: '',
iconAltText: '',
supportsStatusUpdates: false,
statusUpdatesRequired: true,
requiredFields: [
{
fieldID: 'phone-number',
labelSingular: 'Phone Number',
labelPlural: 'Phone Numbers',
hint: 'Include country code e.g. +1 (USA), +91 (India), +44 (UK)',
hintURL: '',
placeholderText: '11235550123',
prefix: '+',
inputType: 'tel',
isSearchSelectable: false,
supportsValidation: true,
},
],
},
]
219 changes: 219 additions & 0 deletions web/src/app/users/UserContactMethodFormDest.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import React from 'react'
import type { Meta, StoryObj } from '@storybook/react'
import UserContactMethodFormDest, { Value } from './UserContactMethodFormDest'
import { expect, within, userEvent, waitFor } from '@storybook/test'
import { handleDefaultConfig } from '../storybook/graphql'
import { useArgs } from '@storybook/preview-api'
import { HttpResponse, graphql } from 'msw'

const meta = {
title: 'users/UserContactMethodFormDest',
component: UserContactMethodFormDest,
tags: ['autodocs'],
parameters: {
msw: {
handlers: [
handleDefaultConfig,
graphql.query('ValidateDestination', ({ variables: vars }) => {
return HttpResponse.json({
data: {
destinationFieldValidate: vars.input.value === '+15555555555',
},
})
}),
],
},
},
render: function Component(args) {
const [, setArgs] = useArgs()
const onChange = (newValue: Value): void => {
if (args.onChange) args.onChange(newValue)
setArgs({ value: newValue })
}
return <UserContactMethodFormDest {...args} onChange={onChange} />
},
} satisfies Meta<typeof UserContactMethodFormDest>

export default meta
type Story = StoryObj<typeof meta>

export const SupportStatusUpdates: Story = {
args: {
value: {
name: 'supports status',
dest: {
type: 'supports-status',
values: [
{
fieldID: 'phone-number',
value: '+15555555555',
},
],
},
statusUpdates: false,
},
disabled: false,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
// ensure status updates checkbox is clickable

await expect(
await canvas.findByLabelText('Send alert status updates'),
).not.toBeDisabled()
},
}

export const RequiredStatusUpdates: Story = {
args: {
value: {
name: 'required status',
dest: {
type: 'required-status',
values: [
{
fieldID: 'phone-number',
value: '+15555555555',
},
],
},
statusUpdates: false,
},
disabled: false,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
// ensure status updates checkbox is not clickable

await expect(
await canvas.findByLabelText(
'Send alert status updates (cannot be disabled for this type)',
),
).toBeDisabled()
},
}

export const ErrorSingleField: Story = {
args: {
value: {
name: '-notvalid',
dest: {
type: 'single-field',
values: [
{
fieldID: 'phone-number',
value: '+',
},
],
},
statusUpdates: false,
},
disabled: false,
errors: [
{
field: 'name',
message: 'must begin with a letter',
name: 'FieldError',
path: [],
details: {},
},
],
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
await userEvent.type(await canvas.findByLabelText('Phone Number'), '123')

// ensure errors are shown
await expect(
await canvas.findByText('Must begin with a letter'),
).toBeVisible()
await waitFor(async function CloseIcon() {
await expect(await canvas.findByTestId('CloseIcon')).toBeVisible()
})
},
}

export const ErrorMultiField: Story = {
args: {
value: {
name: '-notvalid',
dest: {
type: 'triple-field',
values: [
{
fieldID: 'first-field',
value: '+',
},
{
fieldID: 'second-field',
value: 'notAnEmail',
},
{
fieldID: 'third-field',
value: '-',
},
],
},
statusUpdates: false,
},
disabled: false,
errors: [
{
field: 'name',
message: 'must begin with a letter',
name: 'FieldError',
path: [],
details: {},
},
],
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
await userEvent.type(await canvas.findByLabelText('First Item'), '123')

// ensure errors are shown
await expect(
await canvas.findByText('Must begin with a letter'),
).toBeVisible()

await waitFor(async function ThreeCloseIcons() {
await expect(await canvas.findAllByTestId('CloseIcon')).toHaveLength(3)
})
},
}

export const Disabled: Story = {
args: {
value: {
name: 'disabled dest',
dest: {
type: 'triple-field',
values: [],
},
statusUpdates: false,
},
disabled: true,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)

const combo = await canvas.findByRole('combobox')

// get it's input field sibling (combo is a dom element)
const input = combo.parentElement?.querySelector('input')
await expect(input).toBeDisabled()

await expect(
await canvas.findByPlaceholderText('11235550123'),
).toBeDisabled()
await expect(
await canvas.findByPlaceholderText('[email protected]'),
).toBeDisabled()
await expect(
await canvas.findByPlaceholderText('slack user ID'),
).toBeDisabled()
await expect(
await canvas.findByLabelText('Send alert status updates'),
).toBeDisabled()
},
}
Loading

0 comments on commit 33ccf5e

Please sign in to comment.