create MessageListHolder and MessageListItem

This is the message collection on the left hand side of the Inbox.

Test Plan:
 - Run Storybook
 - Verify that the MessageListHolder story is rendering as expected
 -- Unread converstaions should have an unread badge
 -- The number badge should show the total number of messages in the
      thread
 -- The checkbox should fire the onSelect action
 -- Mousing over a conversation should show its star button
 -- Clicking the star button on a conversation should fill in the icon
      and make the button persist even when moused out of the conversation
 -- Clicking the star button should fire the onStar callback

closes VICE-844

Change-Id: Ia554f8fe9717934dcb72569360a7b180537583c1
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/253560
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Davis Hyer <dhyer@instructure.com>
QA-Review: Davis Hyer <dhyer@instructure.com>
Product-Review: Davis Hyer <dhyer@instructure.com>
This commit is contained in:
Ben Nelson 2020-11-15 22:42:14 -07:00 committed by Jeffrey Johnson
parent 934b46bacd
commit 6d91032edc
16 changed files with 799 additions and 1280 deletions

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2020 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import PropTypes from 'prop-types'
import React from 'react'
import {View} from '@instructure/ui-view'
import {MessageListItem, conversationProp} from './MessageListItem'
export const MessageListHolder = ({...props}) => {
return (
<View
as="div"
maxWidth={400}
height="100%"
overflowX="hidden"
overflowY="auto"
borderWidth="small"
>
{props.conversations.map(conversation => (
<MessageListItem
conversation={conversation.conversation}
isUnread={conversation.workflowState === 'unread'}
onOpen={props.onOpen}
onSelect={props.onSelect}
onStar={props.onStar}
key={conversation.id}
/>
))}
</View>
)
}
const conversationParticipantsProp = PropTypes.shape({
id: PropTypes.number,
workflowState: PropTypes.string,
conversation: conversationProp
})
MessageListHolder.propTypes = {
conversations: PropTypes.arrayOf(conversationParticipantsProp),
onOpen: PropTypes.func,
onSelect: PropTypes.func,
onStar: PropTypes.func
}

View File

@ -0,0 +1,198 @@
/*
* Copyright (C) 2020 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import {MessageListHolder} from './MessageListHolder'
export default {
title: 'Examples/Canvas Inbox/MessageListHolder',
component: MessageListHolder,
argTypes: {
handleOptionSelect: {action: 'onSelect'},
handleStar: {action: 'onStar'},
handleOpen: {action: 'onOpen'}
}
}
const Template = args => <MessageListHolder {...args} />
export const WithUnreadConversations = Template.bind({})
WithUnreadConversations.args = {
conversations: [
{
id: 1,
workflowState: 'unread',
conversation: {
subject: 'This is the subject line',
participants: [{name: 'Bob Barker'}, {name: 'Sally Ford'}, {name: 'Russel Franks'}],
conversationMessages: [
{
author: {name: 'Bob Barker'},
participants: [{name: 'Bob Barker'}, {name: 'Sally Ford'}, {name: 'Russel Franks'}],
created_at: 'November 5, 2020 at 2:25pm',
body: 'This is the body text for the message.'
},
{
author: {name: 'Sally Ford'},
participants: [{name: 'Sally Ford'}, {name: 'Bob Barker'}, {name: 'Russel Franks'}],
created_at: 'November 4, 2020 at 2:25pm',
body: 'This is the body text for the message.'
}
]
}
},
{
id: 2,
workflowState: 'read',
conversation: {
subject: 'This is a different subject line',
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
conversationMessages: [
{
author: {name: 'Todd Martin'},
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
created_at: 'November 3, 2020 at 8:58am',
body:
'This conversation has a much longer body which should be too long to completely display.'
}
]
}
},
{
id: 3,
workflowState: 'unread',
conversation: {
subject: 'This is a different subject line',
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
starred: true,
conversationMessages: [
{
author: {name: 'Todd Martin'},
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
created_at: 'November 3, 2020 at 8:58am',
body:
'This conversation has a much longer body which should be too long to completely display.'
}
]
}
},
{
id: 4,
workflowState: 'read',
conversation: {
subject: 'This is a different subject line',
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
conversationMessages: [
{
author: {name: 'Todd Martin'},
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
created_at: 'November 3, 2020 at 8:58am',
body:
'This conversation has a much longer body which should be too long to completely display.'
}
]
}
}
]
}
export const WithConversations = Template.bind({})
WithConversations.args = {
conversations: [
{
id: 1,
workflowState: 'read',
conversation: {
subject: 'This is the subject line',
participants: [{name: 'Bob Barker'}, {name: 'Sally Ford'}, {name: 'Russel Franks'}],
conversationMessages: [
{
author: {name: 'Bob Barker'},
participants: [{name: 'Bob Barker'}, {name: 'Sally Ford'}, {name: 'Russel Franks'}],
created_at: 'November 5, 2020 at 2:25pm',
body: 'This is the body text for the message.'
},
{
author: {name: 'Sally Ford'},
participants: [{name: 'Sally Ford'}, {name: 'Bob Barker'}, {name: 'Russel Franks'}],
created_at: 'November 4, 2020 at 2:25pm',
body: 'This is the body text for the message.'
}
]
}
},
{
id: 2,
workflowState: 'read',
conversation: {
subject: 'This is a different subject line',
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
conversationMessages: [
{
author: {name: 'Todd Martin'},
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
created_at: 'November 3, 2020 at 8:58am',
body:
'This conversation has a much longer body which should be too long to completely display.'
}
]
}
},
{
id: 3,
workflowState: 'read',
conversation: {
subject: 'This is a different subject line',
participants: [
{name: 'Jim Clarkson'},
{name: 'Barbara Ellis'},
{name: 'Bob Barker'},
{name: 'Sally Ford'},
{name: 'Russel Franks'}
],
conversationMessages: [
{
author: {name: 'Jim Clarkson'},
participants: [{name: 'Jim Clarkson'}, {name: 'Barbara Ellis'}],
created_at: 'November 3, 2020 at 8:58am',
body:
'This conversation has a much longer body which should be too long to completely display.'
}
]
}
},
{
id: 4,
workflowState: 'read',
conversation: {
subject: 'This is a different subject line',
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
conversationMessages: [
{
author: {name: 'Todd Martin'},
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
created_at: 'November 3, 2020 at 8:58am',
body:
'This conversation has a much longer body which should be too long to completely display.'
}
]
}
}
]
}

View File

@ -0,0 +1,251 @@
/*
* Copyright (C) 2020 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {Badge} from '@instructure/ui-badge'
import {Button, IconButton} from '@instructure/ui-buttons'
import {Checkbox} from '@instructure/ui-checkbox'
import {Focusable} from '@instructure/ui-focusable'
import {Grid} from '@instructure/ui-grid'
import {IconStarLightLine, IconStarSolid} from '@instructure/ui-icons'
import PropTypes from 'prop-types'
import React, {useState} from 'react'
import {ScreenReaderContent} from '@instructure/ui-a11y-content'
import {Text} from '@instructure/ui-text'
import {TruncateText} from '@instructure/ui-truncate-text'
import {View} from '@instructure/ui-view'
import I18n from 'i18n!conversations_2'
export const MessageListItem = ({...props}) => {
const [selected, setSelected] = useState(false)
const [isHovering, setIsHovering] = useState(false)
const [isStarred, setIsStarred] = useState(props.conversation.starred)
const handleSelectionChange = () => {
props.onSelect(!selected)
setSelected(!selected)
}
const handleMessageClick = e => {
e.stopPropagation()
props.onOpen()
}
const handleMessageStarClick = e => {
e.stopPropagation()
props.onStar(!isStarred)
setIsStarred(!isStarred)
}
const formatParticipants = () => {
const participantsStr = props.conversation.participants
.filter(p => p.name !== props.conversation.conversationMessages[0].author.name)
.reduce((prev, curr) => {
return prev + ', ' + curr.name
}, '')
return (
<Text>
<TruncateText>
<b>{props.conversation.conversationMessages[0].author.name}</b>
{participantsStr}
</TruncateText>
</Text>
)
}
return (
<div
style={{
// TODO: Move these styles to a stylesheet once we are moved to the app/ directory
boxShadow: isHovering && 'inset -4px 0px 0px rgb(0, 142, 226)',
backgroundColor: selected && 'rgb(229,242,248)'
}}
>
<View
data-testid="conversation"
as="div"
borderWidth="none none small none"
padding="small x-small"
>
<Grid
vAlign="middle"
colSpacing="none"
rowSpacing="none"
onMouseEnter={() => {
setIsHovering(true)
}}
onMouseLeave={() => {
setIsHovering(false)
}}
onClick={handleMessageClick}
>
<Grid.Row>
<Grid.Col width="auto">
<View
textAlign="center"
as="div"
width={30}
height={30}
padding="xx-small"
margin="0 small 0 0"
>
<Checkbox
label={
<ScreenReaderContent>
{selected ? I18n.t('selected') : I18n.t('not selected')}
</ScreenReaderContent>
}
checked={selected}
onChange={handleSelectionChange}
/>
</View>
</Grid.Col>
<Grid.Col>
<Text color="brand">{props.conversation.conversationMessages[0].created_at}</Text>
</Grid.Col>
<Grid.Col width="auto">
<Badge
count={props.conversation.conversationMessages.length}
countUntil={99}
standalone
/>
</Grid.Col>
</Grid.Row>
<Grid.Row>
<Grid.Col width="auto">
<View textAlign="center" as="div" width={30} height={30} margin="0 small 0 0">
{props.isUnread && (
<Badge
type="notification"
standalone
margin="x-small"
formatOutput={() => {
return <ScreenReaderContent>{I18n.t('Unread')}</ScreenReaderContent>
}}
/>
)}
</View>
</Grid.Col>
<Grid.Col>{formatParticipants()}</Grid.Col>
</Grid.Row>
<Grid.Row>
<Grid.Col width="auto">
<View textAlign="center" as="div" width={30} height={30} margin="0 small 0 0" />
</Grid.Col>
<Grid.Col>
<Text weight="light">
<TruncateText>{props.conversation.subject}</TruncateText>
</Text>
</Grid.Col>
</Grid.Row>
<Grid.Row>
<Grid.Col width="auto">
<View textAlign="center" as="div" width={30} height={30} margin="0 small 0 0" />
</Grid.Col>
<Grid.Col>
<Text color="secondary">
<TruncateText>{props.conversation.conversationMessages[0].body}</TruncateText>
</Text>
</Grid.Col>
<Grid.Col width="auto">
<View textAlign="center" as="div" width={30} height={30} margin="0 small 0 0">
<Focusable>
{({focused}) => {
return (
<div>
{focused || isHovering || isStarred ? (
<IconButton
size="small"
withBackground={false}
withBorder={false}
renderIcon={isStarred ? IconStarSolid : IconStarLightLine}
screenReaderLabel={
isStarred ? I18n.t('starred') : I18n.t('not starred')
}
onClick={handleMessageStarClick}
data-testid="visible-star"
/>
) : (
<ScreenReaderContent>
<IconButton
size="small"
withBackground={false}
withBorder={false}
renderIcon={isStarred ? IconStarSolid : IconStarLightLine}
screenReaderLabel={
isStarred ? I18n.t('starred') : I18n.t('not starred')
}
onClick={handleMessageStarClick}
/>
</ScreenReaderContent>
)}
</div>
)
}}
</Focusable>
</View>
</Grid.Col>
</Grid.Row>
<Grid.Row>
<Grid.Col>
<Focusable>
{({focused}) => {
return focused ? (
<Button
display="block"
textAlign="center"
size="small"
onClick={handleMessageClick}
>
{I18n.t('Open Message')}
</Button>
) : (
<ScreenReaderContent tabIndex="0">{I18n.t('Open Message')}</ScreenReaderContent>
)
}}
</Focusable>
</Grid.Col>
</Grid.Row>
</Grid>
</View>
</div>
)
}
const participantProp = PropTypes.shape({name: PropTypes.string})
const conversaionMessageProp = PropTypes.shape({
author: participantProp,
participants: PropTypes.arrayOf(participantProp),
created_at: PropTypes.string,
body: PropTypes.string
})
export const conversationProp = PropTypes.shape({
subject: PropTypes.string,
participants: PropTypes.arrayOf(participantProp),
conversationMessages: PropTypes.arrayOf(conversaionMessageProp)
})
MessageListItem.propTypes = {
conversation: conversationProp,
isUnread: PropTypes.bool,
onOpen: PropTypes.func,
onSelect: PropTypes.func,
onStar: PropTypes.func
}

View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2020 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {render} from '@testing-library/react'
import React from 'react'
import {MessageListHolder} from '../MessageListHolder'
describe('MessageListHolder', () => {
it('renders the provided conversations', () => {
const props = {
conversations: [
{
id: 1,
workflowState: 'unread',
conversation: {
subject: 'This is the subject line',
participants: [{name: 'Bob Barker'}, {name: 'Sally Ford'}, {name: 'Russel Franks'}],
conversationMessages: [
{
author: {name: 'Bob Barker'},
participants: [{name: 'Bob Barker'}, {name: 'Sally Ford'}, {name: 'Russel Franks'}],
created_at: 'November 5, 2020 at 2:25pm',
body: 'This is the body text for the message.'
},
{
author: {name: 'Sally Ford'},
participants: [{name: 'Sally Ford'}, {name: 'Bob Barker'}, {name: 'Russel Franks'}],
created_at: 'November 4, 2020 at 2:25pm',
body: 'This is the body text for the message.'
}
]
}
},
{
id: 2,
workflowState: 'read',
conversation: {
subject: 'This is a different subject line',
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
conversationMessages: [
{
author: {name: 'Todd Martin'},
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
created_at: 'November 3, 2020 at 8:58am',
body:
'This conversation has a much longer body which should be too long to completely display.'
}
]
}
},
{
id: 3,
workflowState: 'unread',
conversation: {
subject: 'This is a different subject line',
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
conversationMessages: [
{
author: {name: 'Todd Martin'},
participants: [{name: 'Todd Martin'}, {name: 'Jim Thompson'}],
created_at: 'November 3, 2020 at 8:58am',
body:
'This conversation has a much longer body which should be too long to completely display.'
}
]
}
}
]
}
const {getAllByTestId} = render(<MessageListHolder {...props} />)
const conversations = getAllByTestId('conversation')
expect(conversations.length).toBe(3)
})
})

View File

@ -0,0 +1,106 @@
/*
* Copyright (C) 2020 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {render, fireEvent} from '@testing-library/react'
import React from 'react'
import {MessageListItem} from '../MessageListItem'
describe('MessageListItem', () => {
const createProps = overrides => {
return {
conversation: {
subject: 'This is the subject line',
participants: [{name: 'Bob Barker'}, {name: 'Sally Ford'}, {name: 'Russel Franks'}],
conversationMessages: [
{
author: {name: 'Bob Barker'},
participants: [{name: 'Bob Barker'}, {name: 'Sally Ford'}, {name: 'Russel Franks'}],
created_at: 'November 5, 2020 at 2:25pm',
body: 'This is the body text for the message.'
},
{
author: {name: 'Sally Ford'},
participants: [{name: 'Sally Ford'}, {name: 'Bob Barker'}, {name: 'Russel Franks'}],
created_at: 'November 4, 2020 at 2:25pm',
body: 'This is the body text for the message.'
}
]
},
isUnread: false,
onSelect: jest.fn(),
onOpen: jest.fn(),
onStar: jest.fn(),
...overrides
}
}
it('calls the onSelect callback with the new state', () => {
const onSelectMock = jest.fn()
const props = createProps({onSelect: onSelectMock})
const {getByRole} = render(<MessageListItem {...props} />)
const checkbox = getByRole('checkbox')
fireEvent.click(checkbox)
expect(onSelectMock).toHaveBeenLastCalledWith(true)
fireEvent.click(checkbox)
expect(onSelectMock).toHaveBeenLastCalledWith(false)
})
it('calls onOpen when the message is clicked', () => {
const onOpenMock = jest.fn()
const props = createProps({onOpen: onOpenMock})
const {getByText} = render(<MessageListItem {...props} />)
const subjectLine = getByText('This is the subject line')
fireEvent.click(subjectLine)
expect(onOpenMock).toHaveBeenCalled()
})
it('shows and hides the star button correctly', () => {
const onStarMock = jest.fn()
const props = createProps({onStar: onStarMock})
const {queryByTestId, getByText} = render(<MessageListItem {...props} />)
// star not shown by default
expect(queryByTestId('visible-star')).not.toBeInTheDocument()
// star shown when message is moused over
const subjectLine = getByText('This is the subject line')
fireEvent.mouseOver(subjectLine)
expect(queryByTestId('visible-star')).toBeInTheDocument()
fireEvent.click(queryByTestId('visible-star'))
expect(onStarMock).toHaveBeenLastCalledWith(true)
// star always shows if selected
fireEvent.mouseOut(subjectLine)
expect(queryByTestId('visible-star')).toBeInTheDocument()
})
it('renders the unread badge when the conversation is unread', () => {
const props = createProps({isUnread: true})
const {getByText} = render(<MessageListItem {...props} />)
expect(getByText('Unread')).toBeInTheDocument()
})
})

View File

@ -1 +0,0 @@
/node_modules

View File

@ -1,10 +0,0 @@
module.exports = {
"stories": [
"../components/**/*.stories.mdx",
"../components/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials"
]
}

View File

@ -1 +0,0 @@
<div id="canvas_inbox_screenreader_holder" role="alert" aria-live="assertive" aria-relevant="additions text" aria-atomic="false"></div>

View File

@ -1,6 +0,0 @@
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
}
import '@instructure/canvas-theme'

View File

@ -1,33 +0,0 @@
/*
* Copyright (C) 2020 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: '10'
},
useBuiltIns: 'usage',
corejs: 3
}
],
'@babel/preset-react'
],
plugins: [['@babel/plugin-proposal-class-properties', {loose: true}]]
}

View File

@ -1,212 +0,0 @@
/*
* Copyright (C) 2020 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html
module.exports = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/nq/_m3pt0qj1177j4xtkbg6kgxrpx_hfv/T/jest_cpta6z",
// Automatically clear mock calls and instances between every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: 'coverage',
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
// coverageProvider: "babel",
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "json",
// "jsx",
// "ts",
// "tsx",
// "node"
// ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
moduleNameMapper: {
'\\.(css|less)$': 'identity-obj-proxy'
},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: undefined,
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
setupFiles: ['./utils/jestSetup.js', 'jest-canvas-mock'],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
setupFilesAfterEnv: ['./utils/envSetup.js'],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: 'jsdom'
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jasmine2",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
}

View File

@ -1,62 +0,0 @@
{
"name": "canvas_inbox",
"version": "0.0.1",
"scripts": {
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook",
"test": "echo"
},
"description": "An application for Canvas Conversations",
"repository": "https://github.com/instructure/canvas-lms.git",
"licenses": [],
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/preset-env": "^7.11.6",
"@babel/preset-react": "^7.11.6",
"@sheerun/mutationobserver-shim": "0.3.2",
"@testing-library/jest-dom": "^4",
"@testing-library/react": "^9",
"babel-jest": "^24",
"babel-loader": "^8",
"identity-obj-proxy": "^3.0.0",
"jest": "^24",
"jest-canvas-mock": "^2.3.0",
"jest-environment-jsdom-fifteen": "1.0.2",
"prop-types": "^15",
"react-is": "^16.13.1"
},
"dependencies": {
"@instructure/canvas-theme": "6",
"@instructure/ui-a11y-content": "6",
"@instructure/ui-alerts": "6",
"@instructure/ui-avatar": "6",
"@instructure/ui-buttons": "6",
"@instructure/ui-checkbox": "6",
"@instructure/ui-flex": "6",
"@instructure/ui-heading": "6",
"@instructure/ui-icons": "6",
"@instructure/media-capture": "~7.1.0",
"@instructure/ui-menu": "6",
"@instructure/ui-modal": "6",
"@instructure/ui-simple-select": "6",
"@instructure/ui-select": "6",
"@instructure/ui-tabs": "6",
"@instructure/ui-text": "6",
"@instructure/ui-text-area": "6",
"@instructure/ui-text-input": "6",
"@instructure/ui-tooltip": "6",
"@instructure/ui-truncate-text": "6",
"@instructure/ui-view": "6",
"core-js": "^3.6.5",
"prop-types": "^15",
"react": "^16.9.0",
"react-dom": "^16.9.0"
},
"optionalDependencies": {
"@storybook/addon-actions": "^6.0.26",
"@storybook/addon-essentials": "^6.0.26",
"@storybook/addon-links": "^6.0.26",
"@storybook/react": "^6.0.26"
}
}

View File

@ -1,21 +0,0 @@
/*
* Copyright (C) 2020 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import '@testing-library/jest-dom/extend-expect'
window.scroll = () => {}

View File

@ -1,70 +0,0 @@
/*
* Copyright (C) 2020 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import '@instructure/canvas-theme'
// because InstUI themeable components need an explicit "dir" attribute on the <html> element
document.documentElement.setAttribute('dir', 'ltr')
// set up mocks for native APIs
if (!('MutationObserver' in window)) {
Object.defineProperty(window, 'MutationObserver', {
value: require('@sheerun/mutationobserver-shim')
})
}
/**
* We want to ensure errors and warnings get appropriate eyes. If
* you are seeing an exception from here, it probably means you
* have an unintended consequence from your changes. If you expect
* the warning/error, add it to the ignore list below.
*/
/* eslint-disable no-console */
const globalError = console.error
const ignoredErrors = []
const globalWarn = console.warn
const ignoredWarnings = [
/Warning: componentWillReceiveProps has been renamed/,
/Warning: \[SimpleSelect\] is experimental.*/,
/Warning: \[themeable\] component styles require setting a \'dir\'*/,
/Warning: \[Focusable\] Exactly one focusable child is required \(0 found\)/
]
global.console = {
log: console.log,
error: error => {
if (ignoredErrors.some(regex => regex.test(error))) {
return
}
globalError(error)
throw new Error(
'Looks like you have an unhandled error. Keep our test logs clean by handling or filtering it'
)
},
warn: warning => {
if (ignoredWarnings.some(regex => regex.test(warning))) {
return
}
globalWarn(warning)
throw new Error(
'Looks like you have an unhandled warning. Keep our test logs clean by handling or filtering it'
)
},
info: console.info,
debug: console.debug
}
/* eslint-enable no-console */

View File

@ -32,6 +32,7 @@
"@instructure/ui-a11y": "6",
"@instructure/ui-alerts": "6",
"@instructure/ui-avatar": "6",
"@instructure/ui-badge": "6",
"@instructure/ui-billboard": "6",
"@instructure/ui-breadcrumb": "6",
"@instructure/ui-buttons": "6",

957
yarn.lock

File diff suppressed because it is too large Load Diff