Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

47 team page #48

Merged
merged 24 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly we have to dev environment, so playwright isn't going to be very reliable to automate here.

branches: [ main ]
jobs:
test:
timeout-minutes: 60
Expand Down
75 changes: 75 additions & 0 deletions e2e/team.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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 }) => {
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')

// click off to close sidebar
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], 'エイデン・フォニエ', 'リード')
})
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()
})
})
7 changes: 6 additions & 1 deletion src/components/SideDrawer/DrawerContents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@ 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 = () => {
const theme = useTheme()
const { t } = useTranslation()
let navList = <></>
if (useMediaQuery(theme.breakpoints.down('sm'))) {
navList = (<>
<ListItem>
<StyledNavLink to='/'>Home</StyledNavLink>
</ListItem>
<ListItem>
<StyledNavLink to='/codeofconduct'>Code of Conduct</StyledNavLink>
<StyledNavLink to='/team'>{t('sidebar.team')}</StyledNavLink>
</ListItem>
<ListItem>
<StyledNavLink to='/codeofconduct'>{t('sidebar.codeOfConduct')}</StyledNavLink>
</ListItem>
<Divider />
</>)
Expand Down
51 changes: 51 additions & 0 deletions src/components/TeamMemberCard/TeamMemberCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { FC, useEffect, useState } from 'react'
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import CardMedia from '@mui/material/CardMedia'
import Typography from '@mui/material/Typography'
import TeamMember from '@/types/TeamMember'
import { getI18n } from 'react-i18next'
import OptionalLinkWrapper from '../OptionalLinkWrapper/OptionalLinkWrapper'

interface TeamMemberCardProps {
member: TeamMember
}

const TeamMemberCard: FC<TeamMemberCardProps> = ({ member }) => {
const [name, setName] = useState(member.nameEN)
const [title, setTitle] = useState(member.titleEN)

const i18n = getI18n()

useEffect(() => {
ann-kilzer marked this conversation as resolved.
Show resolved Hide resolved
if (i18n.language === 'en') {
setName(member.nameEN)
setTitle(member.titleEN)
} else if (i18n.language === 'ja') {
setName(member.nameJA || member.nameEN)
setTitle(member.titleJA || member.titleEN)
}
}, [member, i18n.language])

return <OptionalLinkWrapper url={member.url}>
<Card sx={{ height: 420 }} aria-label="team-member-card">
<CardMedia
sx={{ height: 300, width: 300 }}
image={member.image || 'Placeholder.png'}
title={name}
alt-text={`${name} photo`}
/>
<CardContent>
<Typography variant='h6'>
{name}
</Typography>
<Typography variant="subtitle1">
{title}
</Typography>

</CardContent>
</Card>
</OptionalLinkWrapper>
}

export default TeamMemberCard
68 changes: 68 additions & 0 deletions src/components/TeamMemberCard/__team__/TeamMemberCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { describe, expect, it } from 'vitest'
import { render } from '@/tests/customRender'
import { screen } from '@testing-library/react'
import TeamMemberCard from '../TeamMemberCard'
import TeamMember from '@/types/TeamMember'
import '@/i18n/config'
import i18next from 'i18next'


describe('TeamMemberCard', () => {
const member = {
nameEN: 'Alice',
nameJA: 'アリス',
titleEN: 'Lead',
titleJA: 'リード',
image: 'example.png',
url: 'https://example.com'
} as TeamMember

it('should render a TeamMemberCard in English', async () => {
render(<TeamMemberCard member={member} />)
const name = await screen.findByText(member.nameEN)
expect(name).toBeVisible()
const title = await screen.findByText(member.titleEN)
expect(title).toBeVisible()
const link = await screen.findByRole('link')
expect(link).toHaveAttribute('href', member.url)
const image = await screen.findByRole('img')
expect(image).toBeVisible()
})

it('should render a TeamMemberCard in Japanese', async () => {
await i18next.changeLanguage('ja')
render(<TeamMemberCard member={member} />)

const name = await screen.findByText(member.nameJA)
expect(name).toBeVisible()
const title = await screen.findByText(member.titleJA)
expect(title).toBeVisible()
const link = await screen.findByRole('link')
expect(link).toHaveAttribute('href', member.url)
const image = await screen.findByRole('img')
expect(image).toBeVisible()
})

const partialMember = {
nameEN: 'Alice',
nameJA: '',
titleEN: 'Lead',
titleJA: '',
image: 'example.png',
url: 'https://example.com'
} as TeamMember

it('should render a TeamMemberCard in Japanese and fall back to English when fields are unset', async () => {
await i18next.changeLanguage('ja')
render(<TeamMemberCard member={partialMember} />)

const name = await screen.findByText(partialMember.nameEN)
expect(name).toBeVisible()
const title = await screen.findByText(partialMember.titleEN)
expect(title).toBeVisible()
const link = await screen.findByRole('link')
expect(link).toHaveAttribute('href', member.url)
const image = await screen.findByRole('img')
expect(image).toBeVisible()
})
})
2 changes: 2 additions & 0 deletions src/i18n/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ i18next.use(initReactI18next).init({
// set returnNull to false (and also in the i18next.d.ts options)
// returnNull: false,
})

export default i18next
12 changes: 10 additions & 2 deletions src/i18n/en/translation.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
{
"header": {
"codeOfConduct": "Code of Conduct",
"subtitle": "Women in Software Engineering Japan"
"subtitle": "Women in Software Engineering Japan",
"team": "Team"
},
"home": {
"helloWorld": "✨ Hello World ✨",
"joinUs": "✨ Join us on Slack ✨",
"paragraph1": "Many of us were saddened to hear of the sudden closure of Women Who Code. There is a need for an organization to empower diverse women in technology careers in Tokyo and across Japan.",
"paragraph2": "We are not giving up on this mission. Please join us in rebuilding community so that we can empower women in Japan in Software Engineering careers.",
"paragraph3": "Our events target professional women in software careers with 2+ years of experience. Beginners to seasoned professionals are welcome to participate. While this organization focuses on women, all genders are welcome at our events. Software-adjacent roles like Data Science, Product, UI/UX, Machine Learning, etc., are welcome, too."
},
"sidebar": {
"codeOfConduct": "Code of Conduct",
"team": "Team"
},
"team": {
"title": "✨ Leadership Team ✨"
}
}
}
12 changes: 10 additions & 2 deletions src/i18n/ja/translation.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
{
"header": {
"codeOfConduct": "行動規範",
"subtitle": "ウーマン・イン・ソフトウェアエンジニアリング"
"subtitle": "ウーマン・イン・ソフトウェアエンジニアリング",
"team": "チーム"
},
"home": {
"helloWorld": "✨ Hello 世界 ✨",
"joinUs": "✨ スラックに参加する ✨",
"paragraph1": "Women Who Code(WWCode)の活動中止のニュースに対し、多くの人々が悲しみました。東京や日本全体で、IT業界の多様な女性を支援する組織が必要とされています。",
"paragraph2": "この使命を諦めるつもりはありません。WWCodeの志を引き継ぐために、日本の女性がソフトウェアエンジニアとしてキャリアを築けるよう、私たちと共にコミュニティを再構築していきます。",
"paragraph3": "私たちのイベントは、2年以上の経験を持つITプロフェッショナルの女性を中心に、新卒からベテランまでどなたでも参加できるイベントをやっています。この組織は女性に焦点を当てていますが、イベントにはすべてのジェンダーの方が歓迎されています。また、データサイエンティスト、プロダクトマネジャー、UI/UXデザイナー、機械学習エンジニアなどのソフトウェアに関連した役割も歓迎いたします。"
},
"sidebar": {
"codeOfConduct": "Code of Conduct",
"team": "チーム"
},
"team": {
"title": "✨ リーダーシップ・チーム ✨"
}
}
}
5 changes: 5 additions & 0 deletions src/routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Home from './Home/Home'
import BaseLayout from './BaseLayout'
import NotFound from './NotFound/NotFound'
import CodeOfConduct from './CodeOfConduct/CodeOfConduct'
import Team from './Team/Team'

const browserRouter = createHashRouter([{
element: <BaseLayout />,
Expand All @@ -20,6 +21,10 @@ const browserRouter = createHashRouter([{
path: 'codeofconduct',
element: <CodeOfConduct />
},
{
path: 'team',
element: <Team />
},
{
path: 'theme',
element: <ThemePreview />
Expand Down
Loading
Loading