Skip to content

Commit

Permalink
Merge branch 'main' into 46-jobs-page
Browse files Browse the repository at this point in the history
  • Loading branch information
YellouMeli committed Aug 3, 2024
2 parents 05fbb4d + 0f77231 commit d4a2221
Show file tree
Hide file tree
Showing 29 changed files with 517 additions and 21 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ name: Playwright Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
Expand Down
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ npm run build
```


## Quality Strategy
# Quality Strategy

### Unit tests 📦️
## Unit tests 📦️
We are using [Vitest](https://vitest.dev/guide/)
and [Testing Library](https://testing-library.com/docs/react-testing-library/intro/) for our unit tests.

Expand All @@ -55,6 +55,33 @@ npm run test:ui
When writing new tests, please follow the [Testing Library Guiding Principles](https://testing-library.com/docs/guiding-principles)


### End-to-end (E2E) tests ↔️
## End-to-end (E2E) tests ↔️

We use [Playwright](https://playwright.dev/) for end-to-end testing.

You can run it in terminal mode with:
```sh
npm run test:e2e
```

or in UI mode with:
```sh
npm run test:e2e:ui
```

### Playwright & CI/CD

Playwright runs against the main branch automatically via GitHub Actions. Since we only have a production environment, it does not run on branches.
If you notice a failed run, here's how to troubleshoot it:


1. Go to the "Actions" tab and select the failing run
2. Examine the logs, and download the artifact
<img width="1587" alt="Screenshot 2024-07-22 at 11 34 57 PM" src="https://github.com/user-attachments/assets/7eb09a4f-8581-4968-84fb-e1c3e765b17a">

3. Unzip the artifact to find one or more folders with the name of the test. Each folder should contain a file called `trace.zip`.
4. Open up the [Trace Viewer](http://trace.playwright.dev), which is a Progressive Web App (basically a special site that runs locally on your machine, not on a server).
5. Drag the `trace.zip` file into the Trace Viewer and you can see the screenshots from the test run

<img width="1587" alt="Screenshot 2024-07-22 at 11 35 14 PM" src="https://github.com/user-attachments/assets/0273ff9e-67a1-48ef-984a-5d7d5554d190">

We will use [Playwright](https://playwright.dev/) (to be installed after #4) for testing.
80 changes: 80 additions & 0 deletions e2e/team.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { test, expect, Locator } from '@playwright/test'

/** Verifies visibility of the name, title, and photo
*
* @param card The Locator for the TeamMember Card
* @param name The name to find
* @param title The title to find, if defined
*/
async function verifyTeamMemberCard(card: Locator, name: string, title?: string) {
await expect(card.getByText(name)).toBeVisible()
if (title) {
await expect(card.getByText(title)).toBeVisible()
}
const photo = card.getByRole('img').first()
await expect(photo).toBeVisible()
await expect(photo).toHaveAttribute('alt-text', `${name} photo`)
}

test('shows the team in English', async ({ page }) => {
await page.goto('/#/team')

const teamContainer = page.getByLabel('team-container')

const heading = teamContainer.getByText('✨ Leadership Team ✨')
await expect(heading).toBeVisible()

const cards = await teamContainer.getByLabel('team-member-card').all()
expect(cards).toHaveLength(7)

await verifyTeamMemberCard(cards[0], 'Ann Kilzer', 'Director')
await verifyTeamMemberCard(cards[1], 'Paty Cortez', 'Director')
await verifyTeamMemberCard(cards[2], 'Maria Tenorio', 'Lead')
await verifyTeamMemberCard(cards[3], 'Daria Vazhenina', 'ML & Data Science Lead')
await verifyTeamMemberCard(cards[4], 'Krizza Bullecer', 'Lead')
await verifyTeamMemberCard(cards[5], 'Ania Nakayama', 'Lead')
await verifyTeamMemberCard(cards[6], 'Aidan Fournier', 'Lead')

// verify link
const links = await page.getByLabel('link-wrapper').all()
expect(links).toHaveLength(1)
const annLink = links[0]
await expect(annLink).toBeVisible()
await expect(annLink).toHaveRole('link')
await expect(annLink).toHaveAttribute('href', 'https://annkilzer.net')
await expect(annLink).toHaveAttribute('target', '_blank')
})

test('shows the team in Japanese', async ({ page, viewport }) => {
await page.goto('/#/team')

// switch locale to Japanese
const hamburger = page.getByLabel('drawer-toggle-button')
await hamburger.click()
const japanese = page.getByLabel('drawer').getByText('日本語')
await japanese.click()

const teamContainer = page.getByLabel('team-container')

// close the sidebar
if (viewport && viewport.width < 600) {
const closeButton = page.getByLabel('close-button')
await closeButton.click()
} else {
await teamContainer.click({ force: true })
}

const heading = teamContainer.getByText('✨ リーダーシップ・チーム ✨')
await expect(heading).toBeVisible()

const cards = await teamContainer.getByLabel('team-member-card').all()
expect(cards).toHaveLength(7)

await verifyTeamMemberCard(cards[0], 'キルザー·杏', 'ディレクター')
await verifyTeamMemberCard(cards[1], 'Paty Cortez', 'ディレクター')
await verifyTeamMemberCard(cards[2], 'Maria Tenorio', 'リード')
await verifyTeamMemberCard(cards[3], 'バジェニナ・ダリヤ', 'ML&データサイエンス・リード')
await verifyTeamMemberCard(cards[4], 'ブレサー クリザ', 'リード')
await verifyTeamMemberCard(cards[5], 'Ania Nakayama', 'リード')
await verifyTeamMemberCard(cards[6], 'エイデン・フォニエ', 'リード')
})
4 changes: 3 additions & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default defineConfig({
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
retries: process.env.CI ? 2 : 1,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
Expand All @@ -29,6 +29,8 @@ export default defineConfig({
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
outputDir: './playwright-report',


/* Configure projects for major browsers */
projects: [
Expand Down
Binary file added public/Aidan.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/Ann.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/Daria.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/Krizza.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/Maria.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/Paty.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/Placeholder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/components/Header/DesktopHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ const DesktopHeader: FC = () => {
<Typography variant="h1">WiSE Japan</Typography>
<Typography variant="caption">{t('header.subtitle')}</Typography>
</StyledNavLink>

<StyledNavLink to="/team" style={{ textDecoration: 'none', color: 'white' }}>
<Typography variant="overline">{t('header.team')}</Typography>
</StyledNavLink>
<StyledNavLink to="/codeofconduct" style={{ textDecoration: 'none', color: 'white' }}>
<Typography variant="overline">{t('header.codeOfConduct')}</Typography>
</StyledNavLink>
Expand Down
8 changes: 7 additions & 1 deletion src/components/Header/__test__/DesktopToolbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,11 @@ describe('Header', () => {
expect(title).toBeVisible()
})

it.todo('should show navigation links')
it('should show navigation links', async () => {
render(<DesktopHeader />)
const team = await screen.findByText('Team')
expect(team).toBeVisible()
const codeOfConduct = await screen.findByText('Code of Conduct')
expect(codeOfConduct).toBeVisible()
})
})
20 changes: 20 additions & 0 deletions src/components/OptionalLinkWrapper/OptionalLinkWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { FC, ReactNode } from 'react'

interface OptionalLinkWrapperProps {
url?: string
children: ReactNode
}

/**
* If url is falsy, returns children. If url is truthy, returns the component wrapped in a link
*/
const OptionalLinkWrapper: FC<OptionalLinkWrapperProps> = ({ url, children }) => {
if (url) {
return <a href={url} target='_blank' rel="noreferrer" aria-label='link-wrapper'>
{children}
</a>
}
return children
}

export default OptionalLinkWrapper
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, it } from 'vitest'
import { render } from '@/tests/customRender'
import { screen } from '@testing-library/react'
import OptionalLinkWrapper from '../OptionalLinkWrapper'


describe('OptionalLinkWrapper', () => {
const exampleURL = 'https://example.com'

it('should render the link when URL is set', async () => {
render(<OptionalLinkWrapper url={exampleURL}>
<span>child</span>
</OptionalLinkWrapper>)

const link = await screen.findByRole('link')
expect(link).toHaveAttribute('href', exampleURL)

const child = await screen.findByText('child')
expect(child).toBeVisible()
})

it.each(['', undefined])('should render the child component when URL is %i', async (url: string | undefined) => {
render(<OptionalLinkWrapper url={url}>
<span>child</span>
</OptionalLinkWrapper>)

const child = await screen.findByText('child')
expect(child).toBeVisible()

const link = screen.queryByRole('link')
expect(link).toBeNull()
})
})
27 changes: 23 additions & 4 deletions src/components/SideDrawer/DrawerContents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,46 @@ import Box from '@mui/material/Box'
import Divider from '@mui/material/Divider'
import List from '@mui/material/List'
import ListItem from '@mui/material/ListItem'
import IconButton from '@mui/material/IconButton'
import CloseIcon from '@mui/icons-material/Close'
import Stack from '@mui/material/Stack'
import { useTheme } from '@mui/material/styles'
import useMediaQuery from '@mui/material/useMediaQuery'
import StyledNavLink from '../StyledNavLink/StyledNavLink'
import LocaleToggle from '../LocaleToggle/LocaleToggle'
import { useTranslation } from 'react-i18next'

const DrawerContents: FC = () => {
interface DrawerContentsProps {
closeDrawer: () => void
}

const DrawerContents: FC<DrawerContentsProps> = ({ closeDrawer }) => {
const theme = useTheme()
const { t } = useTranslation()

let navList = <></>
if (useMediaQuery(theme.breakpoints.down('sm'))) {
navList = (<>
<ListItem>
<StyledNavLink to='/'>Home</StyledNavLink>
<Stack direction='row' sx={{ width: '100%', m: 0, p: 0 }}>
<StyledNavLink to='/'>Home</StyledNavLink>
<Box sx={{ display: 'flex-grow', width: '100%' }} />
<IconButton onClick={closeDrawer} aria-label='close-button'>
<CloseIcon />
</IconButton>
</Stack>
</ListItem>
<ListItem>
<StyledNavLink to='/team'>{t('sidebar.team')}</StyledNavLink>
</ListItem>
<ListItem>
<StyledNavLink to='/codeofconduct'>Code of Conduct</StyledNavLink>
<StyledNavLink to='/codeofconduct'>{t('sidebar.codeOfConduct')}</StyledNavLink>
</ListItem>
<Divider />
</>)
}

return <Box sx={{ width: 300 }}>
return <Box sx={{ width: 300 }} aria-label='drawer-contents'>
<List>
{navList}
<ListItem>
Expand Down
2 changes: 1 addition & 1 deletion src/components/SideDrawer/SideDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const SideDrawer: FC = () => {
onClose={toggleDrawer(false)}
aria-label="drawer"
>
<DrawerContents />
<DrawerContents closeDrawer={() => { setOpen(false) }} />
</SwipeableDrawer>
</>
}
Expand Down
24 changes: 22 additions & 2 deletions src/components/SideDrawer/__test__/TestDrawerContents.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
import { describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
import { render } from '@/tests/customRender'
import { screen } from '@testing-library/react'
import DrawerContents from '../DrawerContents'
import userEvent from '@testing-library/user-event'
import useMediaQuery from '@mui/material/useMediaQuery'

vi.mock('@mui/material/useMediaQuery', () => {
return {
default: vi.fn()
}
})

describe('DrawerContents', () => {
it('should display the DrawerContents', async () => {
render(<DrawerContents />)
render(<DrawerContents closeDrawer={() => { }} />)
const japanese = await screen.findByText('日本語')
expect(japanese).toBeVisible()
})

it('should call closeDrawer when clicking x on mobile viewport', async () => {
const mock = vi.fn(() => { })
const user = userEvent.setup()
vi.mocked(useMediaQuery).mockReturnValue(true)

render(<DrawerContents closeDrawer={mock} />)
const closeButton = await screen.findByLabelText('close-button')
await user.click(closeButton)

expect(mock).toHaveBeenCalled()
})

it.todo('should show the NavLinks on mobile screens')
})
33 changes: 32 additions & 1 deletion src/components/SideDrawer/__test__/TestSideDrawer.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { describe, expect, it } from 'vitest'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { render } from '@/tests/customRender'
import { screen, waitFor } from '@testing-library/react'
import Box from '@mui/material/Box'
import Stack from '@mui/material/Stack'
import SideDrawer from '../SideDrawer'
import userEvent from '@testing-library/user-event'
import useMediaQuery from '@mui/material/useMediaQuery'

vi.mock('@mui/material/useMediaQuery', () => {
return {
default: vi.fn()
}
})

beforeEach(() => {
vi.resetAllMocks()
})

describe('SideDrawer', () => {
it('should initially render the Drawer closed', async () => {
Expand Down Expand Up @@ -61,4 +72,24 @@ describe('SideDrawer', () => {
expect(drawer).not.toBeVisible()
})
})

it('should close the Drawer when clicking the close icon on mobile view', async () => {
const user = userEvent.setup()
vi.mocked(useMediaQuery).mockReturnValue(true)

render(<SideDrawer />)

const button = await screen.findByLabelText('drawer-toggle-button')
await user.click(button)

const drawerContents = await screen.findByLabelText('drawer-contents')
expect(drawerContents).toBeVisible()

const closeButton = await screen.findByLabelText('close-button')
await user.click(closeButton)

await waitFor(() => {
expect(drawerContents).not.toBeVisible()
})
})
})
Loading

0 comments on commit d4a2221

Please sign in to comment.