Skip to content

Commit

Permalink
feat: Add new section to My Space (#260)
Browse files Browse the repository at this point in the history
* Move custom usa-button styles to new class name

* Move add link form to CustomCollection

* Hook up add bookmark resolver

* Add modal root portal

* Set up AddCustomLink modal

* Update react uswds sha

* Fix modal props

* Update modals branch

* Fix build, lint errors, add E2E test

* Update E2E tests

* Remove collection button and modal working

* Fix tests, split out RemovableBookmark component

* Add tests to modal

* Add tests for ModalPortal

* Fix handling reset state after adding a link

* Reset modal form on close

* Fix outside click hook

* Add renderWithModalRoot test helper fn

* Start working on select mode

* Fix ref for menu trigger

* Update Modal implementation

* Move hooks into directory, update Storybook component

* Fix new modal

* Adding tests and fixing bugs

* Styling SelectableCollection

* Selecting collection UX

* Make SelectableCollection accessible, add tests

* Add unit tests for Dropdown Menu

* Add unit tests for remove custom collection modal

* Adding specific dialog name

* Fixing test id and tests

* Add remove collection to cypress test

* Reorg SelectableCollection component

* Hook up add collections mutation

* Add E2E test

* Edits to address feedback

* Fix cypress tests

* Redirect to MySpace after adding collections, tests

* AddWidget component, updates to DropdownMenu

* Update tests

* Revert list change

* Add E2E tests

* Remove only from Cypress tests

* Add z index to dropdown menu

* Update DropdownMenu props to always require a triggerEl

* Fix tests, rename prop

Co-authored-by: Abigail Young <[email protected]>
  • Loading branch information
Suzanne Rozier and abbyoung authored Oct 13, 2021
1 parent ba698a0 commit feb692f
Show file tree
Hide file tree
Showing 15 changed files with 512 additions and 84 deletions.
12 changes: 8 additions & 4 deletions cypress/integration/sites-and-applications.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ describe('Sites and Applications', () => {
cy.contains('Life & Fitness').should('not.exist')

// Go to Sites & Applications
cy.findByRole('link', { name: 'All sites & applications' }).click()
cy.url().should('eq', Cypress.config().baseUrl + '/sites-and-applications')
cy.findByRole('button', { name: 'Add section' }).click()

cy.findByRole('button', { name: 'Select existing collection(s)' }).click()

cy.url().should(
'eq',
Cypress.config().baseUrl + '/sites-and-applications?selectMode=true'
)

// Enter select mode
cy.findByRole('button', { name: 'Select multiple collections' }).click()
cy.findByRole('button', { name: 'Select collection Career' }).click()
cy.findByRole('button', {
name: 'Select collection Medical & Dental',
Expand Down
240 changes: 211 additions & 29 deletions src/__tests__/pages/beta/sites-and-applications.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { MockedProvider } from '@apollo/client/testing'
import { useRouter } from 'next/router'

import SitesAndApplications, {
getStaticProps,
} from 'pages/beta/sites-and-applications'
import * as addCollections from 'operations/mutations/addCollections'

jest.mock('next/router', () => ({
useRouter: jest.fn().mockReturnValue({
route: '',
pathname: '',
query: '',
asPath: '',
}),
}))

const mockedUseRouter = useRouter as jest.Mock

const mockBookmarks = [
{
id: '1',
Expand Down Expand Up @@ -54,7 +66,188 @@ const mockCollections = [
]

describe('Sites and Applications page', () => {
beforeEach(() => {
describe('default state', () => {
beforeEach(() => {
render(
<MockedProvider>
<SitesAndApplications
collections={mockCollections}
bookmarks={mockBookmarks}
/>
</MockedProvider>
)
})

it('renders Sites & Applications content', () => {
expect(
screen.getByRole('heading', { name: 'Sites & Applications' })
).toBeInTheDocument()
})

it('sorts by type by default', () => {
const collections = screen.getAllByRole('heading', { level: 3 })
expect(collections).toHaveLength(mockCollections.length)
collections.forEach((c, i) => {
// eslint-disable-next-line security/detect-object-injection
expect(collections[i]).toHaveTextContent(mockCollections[i].title)
})
})

it('can toggle sort type', () => {
const sortAlphaBtn = screen.getByRole('button', {
name: 'Sort alphabetically',
})

const sortTypeBtn = screen.getByRole('button', { name: 'Sort by type' })
userEvent.click(sortAlphaBtn)
expect(screen.queryAllByRole('heading', { level: 3 })).toHaveLength(0)
expect(screen.getByRole('table')).toBeInTheDocument()
expect(screen.getAllByRole('link')).toHaveLength(mockBookmarks.length)
userEvent.click(sortTypeBtn)
expect(screen.queryAllByRole('heading', { level: 3 })).toHaveLength(
mockCollections.length
)
expect(screen.queryByRole('table')).not.toBeInTheDocument()
})

describe('selecting collections', () => {
it('can enter select mode', () => {
const selectBtn = screen.getByRole('button', {
name: 'Select multiple collections',
})
expect(selectBtn).toBeInTheDocument()
userEvent.click(selectBtn)

expect(
screen.queryByRole('button', { name: 'Select multiple collections' })
).not.toBeInTheDocument()
expect(
screen.getByRole('button', { name: 'Cancel' })
).toBeInTheDocument()
expect(
screen.getByRole('button', { name: 'Add selected' })
).toBeDisabled()
expect(screen.getByText('0 collections selected')).toBeInTheDocument()

expect(
screen.getByRole('button', {
name: 'Select collection Example Collection 1',
})
).toBeInTheDocument()
expect(
screen.getByRole('button', {
name: 'Select collection Example Collection 2',
})
).toBeInTheDocument()
})

it('can cancel out of select mode', () => {
expect(
screen.queryByText('0 collections selected')
).not.toBeInTheDocument()

userEvent.click(
screen.getByRole('button', {
name: 'Select multiple collections',
})
)

expect(screen.queryByText('0 collections selected')).toBeInTheDocument()

userEvent.click(screen.getByRole('button', { name: 'Cancel' }))

expect(
screen.queryByText('0 collections selected')
).not.toBeInTheDocument()
})

it('can select multiple collections and add them', () => {
const addCollectionsSpy = jest.spyOn(
addCollections,
'useAddCollectionsMutation'
)

userEvent.click(
screen.getByRole('button', {
name: 'Select multiple collections',
})
)

expect(
screen.getByRole('button', { name: 'Add selected' })
).toBeDisabled()
expect(screen.getByText('0 collections selected')).toBeInTheDocument()

userEvent.click(
screen.getByRole('button', {
name: 'Select collection Example Collection 1',
})
)
expect(screen.getByText('1 collection selected')).toBeInTheDocument()
userEvent.click(
screen.getByRole('button', {
name: 'Select collection Example Collection 2',
})
)
expect(screen.getByText('2 collections selected')).toBeInTheDocument()
expect(
screen.getByRole('button', { name: 'Add selected' })
).toBeEnabled()
expect(addCollectionsSpy).toHaveBeenCalled()
})

it('selecting the same collection twice removes it from the selection', () => {
userEvent.click(
screen.getByRole('button', {
name: 'Select multiple collections',
})
)

expect(
screen.getByRole('button', { name: 'Add selected' })
).toBeDisabled()
expect(screen.getByText('0 collections selected')).toBeInTheDocument()

userEvent.click(
screen.getByRole('button', {
name: 'Select collection Example Collection 1',
})
)
expect(screen.getByText('1 collection selected')).toBeInTheDocument()
userEvent.click(
screen.getByRole('button', {
name: 'Select collection Example Collection 2',
})
)
expect(screen.getByText('2 collections selected')).toBeInTheDocument()
expect(
screen.getByRole('button', { name: 'Add selected' })
).toBeEnabled()

userEvent.click(
screen.getByRole('button', {
name: 'Unselect collection Example Collection 1',
})
)
expect(screen.getByText('1 collection selected')).toBeInTheDocument()
userEvent.click(
screen.getByRole('button', {
name: 'Unselect collection Example Collection 2',
})
)
expect(screen.getByText('0 collections selected')).toBeInTheDocument()
})
})
})

it('enters select mode by default if a query param is specified', () => {
mockedUseRouter.mockReturnValueOnce({
route: '',
pathname: '/',
query: { selectMode: 'true' },
asPath: '/',
})

render(
<MockedProvider>
<SitesAndApplications
Expand All @@ -63,41 +256,30 @@ describe('Sites and Applications page', () => {
/>
</MockedProvider>
)
})

it('renders Sites & Applications content', () => {
expect(
screen.getByRole('heading', { name: 'Sites & Applications' })
).toBeInTheDocument()
})
screen.queryByRole('button', {
name: 'Select multiple collections',
})
).not.toBeInTheDocument()

it('sorts by type by default', () => {
const collections = screen.getAllByRole('heading', { level: 3 })
expect(collections).toHaveLength(mockCollections.length)
collections.forEach((c, i) => {
// eslint-disable-next-line security/detect-object-injection
expect(collections[i]).toHaveTextContent(mockCollections[i].title)
})
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Add selected' })).toBeDisabled()
expect(screen.getByText('0 collections selected')).toBeInTheDocument()
})

it('can toggle sort type', () => {
const sortAlphaBtn = screen.getByRole('button', {
name: 'Sort alphabetically',
describe('selecting collections', () => {
beforeEach(() => {
render(
<MockedProvider>
<SitesAndApplications
collections={mockCollections}
bookmarks={mockBookmarks}
/>
</MockedProvider>
)
})

const sortTypeBtn = screen.getByRole('button', { name: 'Sort by type' })
userEvent.click(sortAlphaBtn)
expect(screen.queryAllByRole('heading', { level: 3 })).toHaveLength(0)
expect(screen.getByRole('table')).toBeInTheDocument()
expect(screen.getAllByRole('link')).toHaveLength(mockBookmarks.length)
userEvent.click(sortTypeBtn)
expect(screen.queryAllByRole('heading', { level: 3 })).toHaveLength(
mockCollections.length
)
expect(screen.queryByRole('table')).not.toBeInTheDocument()
})

describe('selecting collections', () => {
it('can enter select mode', () => {
const selectBtn = screen.getByRole('button', {
name: 'Select multiple collections',
Expand Down
37 changes: 37 additions & 0 deletions src/components/AddWidget/AddWidget.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@import 'styles/uswdsDependencies';

.addWidget {
display: block;
cursor: pointer;
text-align: center;
background-color: rgba(220, 222, 224, 0.15);
border: 1px solid color('base-lighter');
box-sizing: border-box;
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.15);
border-radius: 4px;
height: 400px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

.addWidgetButton {
cursor: pointer;
border: none;
background: transparent;
color: #585a5c;
@include u-font-size('body', 'lg');

svg {
pointer-events: none;
}
}

.plus {
color: rgba(0, 0, 0, 0.1);
svg {
width: 70px;
height: 70px;
}
}
20 changes: 20 additions & 0 deletions src/components/AddWidget/AddWidget.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import { Meta } from '@storybook/react'

import AddWidget from './AddWidget'

type StorybookArgTypes = {
handleSelectCollection: () => void
}

export default {
title: 'Components/AddWidget',
component: AddWidget,
argTypes: {
handleSelectCollection: { action: 'Select existing collection(s)' },
},
} as Meta

export const DefaultAddWidget = (argTypes: StorybookArgTypes) => (
<AddWidget handleSelectCollection={argTypes.handleSelectCollection} />
)
Loading

0 comments on commit feb692f

Please sign in to comment.