mirror of
https://github.com/kremalicious/portfolio.git
synced 2025-01-03 18:35:00 +01:00
add more tests
This commit is contained in:
parent
05aa2ffa84
commit
0e78f34d15
@ -9,6 +9,9 @@
|
|||||||
"childImageSharp": {
|
"childImageSharp": {
|
||||||
"fluid": {
|
"fluid": {
|
||||||
"src": "/static/b45f45aa8d98d4e4019a242d38f2f248/bc3a8/avatar.jpg"
|
"src": "/static/b45f45aa8d98d4e4019a242d38f2f248/bc3a8/avatar.jpg"
|
||||||
|
},
|
||||||
|
"resize": {
|
||||||
|
"src": "/static/b45f45aa8d98d4e4019a242d38f2f248/bc3a8/avatar.jpg"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"format": "prettier --write 'src/**/*.{js,jsx}'",
|
"format": "prettier --write 'src/**/*.{js,jsx}'",
|
||||||
"format:css": "prettier-stylelint --write --quiet 'src/**/*.{css,scss}'",
|
"format:css": "prettier-stylelint --write --quiet 'src/**/*.{css,scss}'",
|
||||||
"test": "npm run lint && jest --coverage",
|
"test": "npm run lint && jest --coverage",
|
||||||
|
"test:watch": "npm run lint && jest --coverage --watch",
|
||||||
"deploy": "./scripts/deploy.sh",
|
"deploy": "./scripts/deploy.sh",
|
||||||
"new": "babel-node ./scripts/new.js"
|
"new": "babel-node ./scripts/new.js"
|
||||||
},
|
},
|
||||||
|
@ -25,59 +25,62 @@ const query = graphql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const LayoutMarkup = ({ children, isHomepage, allowedHosts, location }) => (
|
const LayoutMarkup = ({ children, data, location }) => {
|
||||||
<>
|
const { allowedHosts } = data.contentYaml
|
||||||
<Typekit />
|
const isHomepage = location.pathname === '/'
|
||||||
<HostnameCheck allowedHosts={allowedHosts} />
|
|
||||||
|
|
||||||
<PoseGroup animateOnMount={true}>
|
return (
|
||||||
<RoutesContainer
|
<>
|
||||||
key={location.pathname}
|
<Typekit />
|
||||||
delay={timeout}
|
<HostnameCheck allowedHosts={allowedHosts} />
|
||||||
delayChildren={timeout}
|
|
||||||
>
|
|
||||||
<Header minimal={!isHomepage} />
|
|
||||||
<main className={styles.screen}>{children}</main>
|
|
||||||
</RoutesContainer>
|
|
||||||
</PoseGroup>
|
|
||||||
|
|
||||||
<Footer />
|
<PoseGroup animateOnMount={true}>
|
||||||
</>
|
<RoutesContainer
|
||||||
)
|
key={location.pathname}
|
||||||
|
delay={timeout}
|
||||||
|
delayChildren={timeout}
|
||||||
|
>
|
||||||
|
<Header minimal={!isHomepage} />
|
||||||
|
<main className={styles.screen}>{children}</main>
|
||||||
|
</RoutesContainer>
|
||||||
|
</PoseGroup>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
LayoutMarkup.propTypes = {
|
LayoutMarkup.propTypes = {
|
||||||
children: PropTypes.any.isRequired,
|
children: PropTypes.any.isRequired,
|
||||||
isHomepage: PropTypes.bool.isRequired,
|
data: PropTypes.shape({
|
||||||
allowedHosts: PropTypes.array.isRequired,
|
contentYaml: PropTypes.shape({
|
||||||
location: PropTypes.object.isRequired
|
allowedHosts: PropTypes.array.isRequired
|
||||||
|
}).isRequired
|
||||||
|
}).isRequired,
|
||||||
|
location: PropTypes.shape({
|
||||||
|
pathname: PropTypes.string.isRequired
|
||||||
|
}).isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Layout extends PureComponent {
|
export default class Layout extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.any.isRequired,
|
children: PropTypes.any.isRequired,
|
||||||
location: PropTypes.object.isRequired
|
location: PropTypes.shape({
|
||||||
|
pathname: PropTypes.string.isRequired
|
||||||
|
}).isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { children, location } = this.props
|
const { children, location } = this.props
|
||||||
const isHomepage = location.pathname === '/'
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StaticQuery
|
<StaticQuery
|
||||||
query={query}
|
query={query}
|
||||||
render={data => {
|
render={data => (
|
||||||
const { allowedHosts } = data.contentYaml
|
<LayoutMarkup data={data} location={location}>
|
||||||
|
{children}
|
||||||
return (
|
</LayoutMarkup>
|
||||||
<LayoutMarkup
|
)}
|
||||||
isHomepage={isHomepage}
|
|
||||||
allowedHosts={allowedHosts}
|
|
||||||
location={location}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</LayoutMarkup>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ const query = graphql`
|
|||||||
email
|
email
|
||||||
avatar {
|
avatar {
|
||||||
childImageSharp {
|
childImageSharp {
|
||||||
original: resize {
|
resize {
|
||||||
src
|
src
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,15 +62,16 @@ export default class Vcard extends PureComponent {
|
|||||||
|
|
||||||
// Construct the download from a blob of the just constructed vCard,
|
// Construct the download from a blob of the just constructed vCard,
|
||||||
// and save it to user's file system
|
// and save it to user's file system
|
||||||
const downloadVcard = (vcard, meta) => {
|
export const downloadVcard = (vcard, meta) => {
|
||||||
const name = meta.addressbook.split('/').join('')
|
const { addressbook } = meta
|
||||||
|
const name = addressbook.split('/').join('')
|
||||||
const blob = new Blob([vcard], { type: 'text/x-vcard' })
|
const blob = new Blob([vcard], { type: 'text/x-vcard' })
|
||||||
saveAs(blob, name)
|
saveAs(blob, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
const constructVcard = meta => {
|
export const constructVcard = meta => {
|
||||||
const contact = new vCard()
|
const contact = new vCard()
|
||||||
const photoSrc = meta.avatar.childImageSharp.original.src
|
const photoSrc = meta.avatar.childImageSharp.resize.src
|
||||||
|
|
||||||
// first, convert the avatar to base64, then construct all vCard elements
|
// first, convert the avatar to base64, then construct all vCard elements
|
||||||
toDataURL(
|
toDataURL(
|
||||||
@ -99,7 +100,7 @@ const constructVcard = meta => {
|
|||||||
|
|
||||||
// Helper function to create base64 string from avatar image
|
// Helper function to create base64 string from avatar image
|
||||||
// without the need to read image file from file system
|
// without the need to read image file from file system
|
||||||
const toDataURL = (src, callback, outputFormat) => {
|
export const toDataURL = (src, callback, outputFormat) => {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
img.crossOrigin = 'Anonymous'
|
img.crossOrigin = 'Anonymous'
|
||||||
|
|
||||||
@ -118,6 +119,7 @@ const toDataURL = (src, callback, outputFormat) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
img.src = src
|
img.src = src
|
||||||
|
|
||||||
if (img.complete || img.complete === undefined) {
|
if (img.complete || img.complete === undefined) {
|
||||||
img.src =
|
img.src =
|
||||||
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='
|
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='
|
||||||
|
37
src/components/atoms/Vcard.test.jsx
Normal file
37
src/components/atoms/Vcard.test.jsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import { StaticQuery } from 'gatsby'
|
||||||
|
import vCard from 'vcf'
|
||||||
|
import Vcard, { constructVcard, downloadVcard, toDataURL } from './Vcard'
|
||||||
|
import data from '../../../jest/__fixtures__/meta.json'
|
||||||
|
|
||||||
|
describe('Vcard', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
StaticQuery.mockImplementationOnce(({ render }) => render({ ...data }))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders correctly', () => {
|
||||||
|
const { container } = render(<Vcard />)
|
||||||
|
|
||||||
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('vCard can be constructed', async () => {
|
||||||
|
await constructVcard(data.contentYaml)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('vCard can be downloaded', async () => {
|
||||||
|
const contact = new vCard()
|
||||||
|
const vcard = contact.toString('3.0')
|
||||||
|
|
||||||
|
global.URL.createObjectURL = jest.fn(() => 'details')
|
||||||
|
await downloadVcard(vcard, data.contentYaml)
|
||||||
|
expect(global.URL.createObjectURL).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Base64 from image can be constructed', () => {
|
||||||
|
const photoSrc = data.contentYaml.avatar.childImageSharp.resize.src
|
||||||
|
|
||||||
|
toDataURL(photoSrc, () => null, 'image/jpeg')
|
||||||
|
})
|
||||||
|
})
|
@ -48,7 +48,10 @@ export default class ThemeSwitch extends PureComponent {
|
|||||||
<Helmet>
|
<Helmet>
|
||||||
<body className={dark ? 'dark' : null} />
|
<body className={dark ? 'dark' : null} />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<Animation className={styles.themeSwitch}>
|
<Animation
|
||||||
|
className={styles.themeSwitch}
|
||||||
|
data-testid={'theme-switch'}
|
||||||
|
>
|
||||||
<label className={styles.checkbox}>
|
<label className={styles.checkbox}>
|
||||||
<span className={styles.label}>Toggle Night Mode</span>
|
<span className={styles.label}>Toggle Night Mode</span>
|
||||||
<ThemeToggleInput dark={dark} toggleDark={toggleDark} />
|
<ThemeToggleInput dark={dark} toggleDark={toggleDark} />
|
||||||
|
17
src/components/molecules/ThemeSwitch.test.jsx
Normal file
17
src/components/molecules/ThemeSwitch.test.jsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render } from 'react-testing-library'
|
||||||
|
import AppProvider from '../../store/provider'
|
||||||
|
import ThemeSwitch from './ThemeSwitch'
|
||||||
|
|
||||||
|
describe('ThemeSwitch', () => {
|
||||||
|
it('renders correctly', () => {
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<AppProvider>
|
||||||
|
<ThemeSwitch />
|
||||||
|
</AppProvider>
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(getByTestId('theme-switch')).toBeInTheDocument()
|
||||||
|
expect(getByTestId('theme-switch').nodeName).toBe('ASIDE')
|
||||||
|
})
|
||||||
|
})
|
@ -1,4 +1,5 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
import { Link, StaticQuery, graphql } from 'gatsby'
|
import { Link, StaticQuery, graphql } from 'gatsby'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import Vcard from '../atoms/Vcard'
|
import Vcard from '../atoms/Vcard'
|
||||||
@ -24,12 +25,10 @@ const query = graphql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
let classes = classNames('h-card', [styles.footer])
|
export const FooterMarkup = ({ pkg, meta, year }) => {
|
||||||
|
const classes = classNames('h-card', [styles.footer])
|
||||||
|
|
||||||
export default class Footer extends PureComponent {
|
return (
|
||||||
state = { year: new Date().getFullYear() }
|
|
||||||
|
|
||||||
FooterMarkup = ({ meta, pkg, year }) => (
|
|
||||||
<footer className={classes}>
|
<footer className={classes}>
|
||||||
<Link to={'/'}>
|
<Link to={'/'}>
|
||||||
<LogoUnit minimal />
|
<LogoUnit minimal />
|
||||||
@ -55,6 +54,16 @@ export default class Footer extends PureComponent {
|
|||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
FooterMarkup.propTypes = {
|
||||||
|
pkg: PropTypes.object.isRequired,
|
||||||
|
meta: PropTypes.object.isRequired,
|
||||||
|
year: PropTypes.number.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Footer extends PureComponent {
|
||||||
|
state = { year: new Date().getFullYear() }
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
@ -64,9 +73,7 @@ export default class Footer extends PureComponent {
|
|||||||
const pkg = data.portfolioJson
|
const pkg = data.portfolioJson
|
||||||
const meta = data.contentYaml
|
const meta = data.contentYaml
|
||||||
|
|
||||||
return (
|
return <FooterMarkup year={this.state.year} pkg={pkg} meta={meta} />
|
||||||
<this.FooterMarkup year={this.state.year} pkg={pkg} meta={meta} />
|
|
||||||
)
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -23,28 +23,26 @@ export default class AppProvider extends PureComponent {
|
|||||||
getCountry = async () => {
|
getCountry = async () => {
|
||||||
let trace = []
|
let trace = []
|
||||||
|
|
||||||
await fetch('/cdn-cgi/trace?no-cache=1')
|
try {
|
||||||
.then(data => {
|
const data = await fetch('/cdn-cgi/trace?no-cache=1')
|
||||||
let lines
|
const text = await data.text()
|
||||||
|
const lines = text.split('\n')
|
||||||
|
|
||||||
data.text().then(text => {
|
let keyValue
|
||||||
lines = text.split('\n')
|
|
||||||
|
|
||||||
let keyValue
|
lines.forEach(line => {
|
||||||
|
keyValue = line.split('=')
|
||||||
|
trace[keyValue[0]] = decodeURIComponent(keyValue[1] || '')
|
||||||
|
|
||||||
lines.forEach(line => {
|
if (keyValue[0] === 'loc' && trace['loc'] !== 'XX') {
|
||||||
keyValue = line.split('=')
|
this.setState({ location: trace['loc'] })
|
||||||
trace[keyValue[0]] = decodeURIComponent(keyValue[1] || '')
|
} else {
|
||||||
|
return
|
||||||
if (keyValue[0] === 'loc' && trace['loc'] !== 'XX') {
|
}
|
||||||
this.setState({ location: trace['loc'] })
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.catch(() => null) // fail silently
|
} catch (error) {
|
||||||
|
return null // fail silently
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setDark() {
|
setDark() {
|
||||||
|
Loading…
Reference in New Issue
Block a user