Skip to content
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
3 changes: 3 additions & 0 deletions __mocks__/react-markdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function ReactMarkdown({ children }) {
return <>{children}</>;
}
57 changes: 57 additions & 0 deletions __tests__/PortfolioEntry.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { render } from '@testing-library/react'
import PortfolioEntry, {
GithubEntry,
TwitterEntry,
LinkedInEntry,
EmailEntry,
BitbucketEntry,
StackOverflowEntry,
StackExchangeEntry,
} from '../components/PortfolioEntry'
import { faGithub } from '@fortawesome/free-brands-svg-icons'

const wrappers = {
github: [GithubEntry, 'github'],
twitter: [TwitterEntry, 'twitter'],
linkedin: [LinkedInEntry, 'linkedin-in'],
email: [EmailEntry, 'at'],
bitbucket: [BitbucketEntry, 'bitbucket'],
'stack-overflow': [StackOverflowEntry, 'stack-overflow'],
'stack-exchange': [StackExchangeEntry, 'stack-exchange'],
}

describe('PortfolioEntry components', () => {
it('renders an anchor when url is provided', () => {
const { container } = render(
<PortfolioEntry url="https://x" title="X" icon={faGithub} type="github" />
)
const a = container.querySelector('a.portfolio__element')
expect(a).toBeInTheDocument()
expect(a).toHaveAttribute('href', 'https://x')
expect(a).toHaveClass('portfolio__element--github')
})

it('renders a div when no url is provided', () => {
const { container } = render(
<PortfolioEntry title="No Link" icon={faGithub} type="github" />
)
const div = container.querySelector('div.portfolio__element')
expect(div).toBeInTheDocument()
})

it('wrapper components set icon and type props', () => {
Object.entries(wrappers).forEach(([type, [Comp, icon]]) => {
const { container } = render(<Comp url="#" title={type} />)
const el = container.querySelector('.portfolio__element')
expect(el).toHaveClass(`portfolio__element--${type}`)
const svg = container.querySelector(`svg[data-icon="${icon}"]`)
expect(svg).toBeInTheDocument()
})
})

it('EmailEntry uses _self target', () => {
const { container } = render(<EmailEntry url="mailto:test" title="Email" />)
const a = container.querySelector('a')
expect(a).toHaveAttribute('target', '_self')
})
})
31 changes: 31 additions & 0 deletions components/PortfolioEntries.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react'
import PortfolioEntry, {
GithubEntry,
TwitterEntry,
LinkedInEntry,
EmailEntry,
BitbucketEntry,
StackOverflowEntry,
StackExchangeEntry,
} from './PortfolioEntry'

const map = {
github: GithubEntry,
twitter: TwitterEntry,
linkedin: LinkedInEntry,
email: EmailEntry,
bitbucket: BitbucketEntry,
'stack-overflow': StackOverflowEntry,
'stack-exchange': StackExchangeEntry,
}

export default function PortfolioEntries({ entries }) {
return (
<>
{entries.map((entry, idx) => {
const EntryComponent = map[entry.type] || PortfolioEntry
return <EntryComponent key={idx} {...entry} />
})}
</>
)
}
34 changes: 34 additions & 0 deletions components/PortfolioEntry.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
faGithub,
faTwitter,
faLinkedinIn,
faBitbucket,
faStackOverflow,
faStackExchange,
} from '@fortawesome/free-brands-svg-icons'
import { faAt } from '@fortawesome/free-solid-svg-icons'

function PortfolioEntry({ url, title, icon, type, target }) {
const Element = url ? 'a' : 'div'
const props = url ? { href: url, target: target || '_blank', title } : { title }

return (
<Element {...props} className={`portfolio__element portfolio__element--${type}`}>
<div className="portfolio__element__icon">
<FontAwesomeIcon icon={icon} />
</div>
</Element>
)
}

export default PortfolioEntry

export const GithubEntry = (props) => <PortfolioEntry {...props} icon={faGithub} type="github" />
export const TwitterEntry = (props) => <PortfolioEntry {...props} icon={faTwitter} type="twitter" />
export const LinkedInEntry = (props) => <PortfolioEntry {...props} icon={faLinkedinIn} type="linkedin" />
export const EmailEntry = (props) => <PortfolioEntry {...props} icon={faAt} type="email" target="_self" />
export const BitbucketEntry = (props) => <PortfolioEntry {...props} icon={faBitbucket} type="bitbucket" />
export const StackOverflowEntry = (props) => <PortfolioEntry {...props} icon={faStackOverflow} type="stack-overflow" />
export const StackExchangeEntry = (props) => <PortfolioEntry {...props} icon={faStackExchange} type="stack-exchange" />
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ const createJestConfig = nextJest({
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jsdom',
transformIgnorePatterns: ['/node_modules/(?!(nextjs-google-analytics)/)'],
moduleNameMapper: {
'^react-markdown$': '<rootDir>/__mocks__/react-markdown.js',
},
};

module.exports = createJestConfig(customJestConfig);
Loading