mirror of
https://github.com/kremalicious/portfolio.git
synced 2024-12-22 09:13:19 +01:00
reorg
This commit is contained in:
parent
0084295323
commit
b7e03a1dbc
68
package-lock.json
generated
68
package-lock.json
generated
@ -36,6 +36,7 @@
|
||||
"eslint-config-next": "^14.1.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"ora": "^8.0.1",
|
||||
"prettier": "^3.2.4",
|
||||
@ -6599,6 +6600,15 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-fetch": {
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz",
|
||||
"integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"node-fetch": "^2.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
@ -9918,6 +9928,16 @@
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-fetch-mock": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz",
|
||||
"integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-fetch": "^3.0.4",
|
||||
"promise-polyfill": "^8.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-get-type": {
|
||||
"version": "29.6.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
|
||||
@ -12037,6 +12057,48 @@
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
@ -12701,6 +12763,12 @@
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/promise-polyfill": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz",
|
||||
"integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/prompts": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||
|
@ -51,6 +51,7 @@
|
||||
"eslint-config-next": "^14.1.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"ora": "^8.0.1",
|
||||
"prettier": "^3.2.4",
|
||||
|
@ -1,13 +0,0 @@
|
||||
'use server'
|
||||
|
||||
import { cache } from 'react'
|
||||
import { getGithubRepos } from '@/lib/github'
|
||||
|
||||
export const getRepos = cache(async () => {
|
||||
try {
|
||||
const repos = await getGithubRepos()
|
||||
return repos
|
||||
} catch (error: unknown) {
|
||||
console.error((error as Error).message)
|
||||
}
|
||||
})
|
@ -1,3 +0,0 @@
|
||||
export * from './getRandomGif'
|
||||
export * from './getRepos'
|
||||
export * from './getLocation'
|
@ -3,10 +3,10 @@ import { notFound } from 'next/navigation'
|
||||
import Header from '@/components/Header/Header'
|
||||
import Project from '@/components/Project'
|
||||
import ProjectNav from '@/components/ProjectNav'
|
||||
import { getAllSlugs } from '@/lib/getAllSlugs'
|
||||
import { getProjectBySlug } from '@/lib/getProjectBySlug'
|
||||
import meta from '@content/meta.json'
|
||||
import projects from '@generated/projects.json'
|
||||
import { getAllSlugs } from './getAllSlugs'
|
||||
import { getProjectBySlug } from './getProjectBySlug'
|
||||
|
||||
type Props = {
|
||||
params: { slug: string }
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { render } from '@testing-library/react'
|
||||
import meta from '../../../_content/meta.json'
|
||||
import meta from '@content/meta.json'
|
||||
import projectMock from '../../../tests/__fixtures__/project.json'
|
||||
import Page, { generateMetadata, generateStaticParams } from '../[slug]/page'
|
||||
|
||||
jest.mock('../[slug]/getProjectBySlug', () => ({
|
||||
jest.mock('../../lib/getProjectBySlug', () => ({
|
||||
getProjectBySlug: jest.fn().mockImplementation(() => projectMock)
|
||||
}))
|
||||
|
||||
jest.mock('../[slug]/getAllSlugs', () => ({
|
||||
jest.mock('../../lib/getAllSlugs', () => ({
|
||||
getAllSlugs: jest.fn().mockImplementationOnce(() => ['slug1', 'slug2'])
|
||||
}))
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Suspense } from 'react'
|
||||
import { getRepos, preloadLocation } from '@/actions'
|
||||
import Hero from '@/components/Hero'
|
||||
import Projects from '@/components/Projects'
|
||||
import Repositories from '@/components/Repositories'
|
||||
import Repositories from '@/components/Repositories/Repositories'
|
||||
import { preloadLocation } from '@/lib/getLocation'
|
||||
import { getRepos } from '@/lib/getRepos'
|
||||
import projects from '@generated/projects.json'
|
||||
|
||||
export default async function IndexPage() {
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { MouseEvent, useEffect, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { getRandomGif } from '@/actions/getRandomGif'
|
||||
import { getRandomGif } from '@/lib/getRandomGif'
|
||||
import Button from '../Button'
|
||||
import styles from './index.module.css'
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { useEffect, useState, useTransition } from 'react'
|
||||
import RelativeTime from '@yaireo/relative-time'
|
||||
import { LazyMotion, domAnimation, m, useReducedMotion } from 'framer-motion'
|
||||
import { getLocation } from '@/actions/getLocation'
|
||||
import { getLocation } from '@/lib/getLocation'
|
||||
import { fadeIn, getAnimationProps } from '../Transitions'
|
||||
import { Flag } from './Flag'
|
||||
import styles from './Location.module.css'
|
||||
|
14
src/components/Repositories/Repositories.tsx
Normal file
14
src/components/Repositories/Repositories.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import Repo from '@/types/repo'
|
||||
import Repository from '../Repository'
|
||||
import styles from './Repositories.module.css'
|
||||
|
||||
export default function Repositories({ repos }: { repos: Repo[] | undefined }) {
|
||||
return (
|
||||
<>
|
||||
<h2 className={styles.sectionTitle}>Open Source Projects</h2>
|
||||
<div className={styles.repos}>
|
||||
{repos?.map((repo) => <Repository key={repo.name} repo={repo} />)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,14 +1 @@
|
||||
import Repo from '@/types/repo'
|
||||
import Repository from '../Repository'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export default function Repositories({ repos }: { repos: Repo[] | undefined }) {
|
||||
return (
|
||||
<>
|
||||
<h2 className={styles.sectionTitle}>Open Source Projects</h2>
|
||||
<div className={styles.repos}>
|
||||
{repos?.map((repo) => <Repository key={repo.name} repo={repo} />)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export { default } from './Repositories'
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { render } from '@testing-library/react'
|
||||
import Repo from '@/types/repo'
|
||||
import Repository from '.'
|
||||
import repos from '../../../tests/__fixtures__/repos.json'
|
||||
import Repository from '../Repository'
|
||||
|
||||
describe('Repository', () => {
|
||||
it('renders correctly', () => {
|
||||
|
51
src/components/Vcard/imageToDataUrl.test.ts
Normal file
51
src/components/Vcard/imageToDataUrl.test.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import fetch, { FetchMock } from 'jest-fetch-mock'
|
||||
import { imageToDataUrl } from './imageToDataUrl'
|
||||
|
||||
const dummyPath = 'http://example.com/image.png'
|
||||
const pixel = [
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49,
|
||||
0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06,
|
||||
0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44,
|
||||
0x41, 0x54, 0x78, 0x9c, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0d,
|
||||
0x0a, 0x2d, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42,
|
||||
0x60, 0x82
|
||||
]
|
||||
|
||||
describe('imageToDataUrl', () => {
|
||||
beforeEach(() => {
|
||||
fetch.resetMocks()
|
||||
const mockBlob = new Blob([new Uint8Array(pixel)], { type: 'image/png' })
|
||||
const mockResponse = new Response(mockBlob)
|
||||
;(fetch as FetchMock).mockResponseOnce(async () => {
|
||||
const text = await mockResponse.text()
|
||||
return text
|
||||
})
|
||||
})
|
||||
|
||||
it('should convert image to data URL', async () => {
|
||||
function MockFileReader() {
|
||||
this.readAsDataURL = function () {
|
||||
this.result = 'data:image/png;base64,...'
|
||||
setTimeout(() => this.onload(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
window.FileReader = MockFileReader as any
|
||||
|
||||
const dataUrl = await imageToDataUrl(dummyPath)
|
||||
expect(dataUrl).toBe('data:image/png;base64,...')
|
||||
})
|
||||
|
||||
it('should handle errors in readAsDataURL', async () => {
|
||||
function MockFileReader() {
|
||||
this.readAsDataURL = function () {
|
||||
throw new Error('Mock error')
|
||||
}
|
||||
}
|
||||
|
||||
window.FileReader = MockFileReader as any
|
||||
|
||||
// Expect imageToDataUrl to reject with the mock error
|
||||
await expect(imageToDataUrl(dummyPath)).rejects.toThrow('Mock error')
|
||||
})
|
||||
})
|
@ -1,6 +1,7 @@
|
||||
export async function imageToDataUrl(path: string): Promise<string> {
|
||||
const response = await fetch(path)
|
||||
const blob = await response.blob()
|
||||
|
||||
return new Promise((onSuccess, onError) => {
|
||||
try {
|
||||
const reader = new FileReader()
|
||||
|
64
src/lib/getRepos.ts
Normal file
64
src/lib/getRepos.ts
Normal file
@ -0,0 +1,64 @@
|
||||
'use server'
|
||||
|
||||
import { cache } from 'react'
|
||||
import type Repo from '@/types/repo'
|
||||
import filter from '@content/repos.json'
|
||||
|
||||
//
|
||||
// Get GitHub repos
|
||||
//
|
||||
if (!process.env.GITHUB_TOKEN) {
|
||||
throw new Error('Missing GitHub environment variable')
|
||||
}
|
||||
|
||||
const gitHubConfig = {
|
||||
headers: {
|
||||
'User-Agent': 'kremalicious/portfolio',
|
||||
Authorization: `token ${process.env.GITHUB_TOKEN}`
|
||||
}
|
||||
}
|
||||
|
||||
export const getRepos = cache(async () => {
|
||||
try {
|
||||
let repos: Repo[] = []
|
||||
|
||||
for (let item of filter) {
|
||||
const user = item.split('/')[0]
|
||||
const repoName = item.split('/')[1]
|
||||
const response = await fetch(
|
||||
`https://api.github.com/repos/${user}/${repoName}`,
|
||||
gitHubConfig
|
||||
)
|
||||
const json: Repo = await response.json()
|
||||
if (!json?.name) return
|
||||
|
||||
const {
|
||||
name,
|
||||
full_name,
|
||||
description,
|
||||
html_url,
|
||||
homepage,
|
||||
stargazers_count,
|
||||
pushed_at
|
||||
} = json
|
||||
|
||||
const repo: Repo = {
|
||||
name,
|
||||
full_name,
|
||||
description,
|
||||
html_url,
|
||||
homepage,
|
||||
stargazers_count,
|
||||
pushed_at
|
||||
}
|
||||
repos.push(repo)
|
||||
}
|
||||
|
||||
// sort by pushed to, newest first
|
||||
repos = repos.sort((a, b) => b.pushed_at.localeCompare(a.pushed_at))
|
||||
|
||||
return repos
|
||||
} catch (error: unknown) {
|
||||
console.error((error as Error).message)
|
||||
}
|
||||
})
|
@ -1,57 +0,0 @@
|
||||
import type Repo from '@/types/repo'
|
||||
import filter from '@content/repos.json'
|
||||
|
||||
//
|
||||
// Get GitHub repos
|
||||
//
|
||||
if (!process.env.GITHUB_TOKEN) {
|
||||
throw new Error('Missing GitHub environment variable')
|
||||
}
|
||||
|
||||
const gitHubConfig = {
|
||||
headers: {
|
||||
'User-Agent': 'kremalicious/portfolio',
|
||||
Authorization: `token ${process.env.GITHUB_TOKEN}`
|
||||
}
|
||||
}
|
||||
|
||||
export async function getGithubRepos() {
|
||||
let repos: Repo[] = []
|
||||
|
||||
for (let item of filter) {
|
||||
const user = item.split('/')[0]
|
||||
const repoName = item.split('/')[1]
|
||||
const data = await fetch(
|
||||
`https://api.github.com/repos/${user}/${repoName}`,
|
||||
gitHubConfig
|
||||
)
|
||||
const json: Repo = await data.json()
|
||||
if (!json?.name) return
|
||||
|
||||
const {
|
||||
name,
|
||||
full_name,
|
||||
description,
|
||||
html_url,
|
||||
homepage,
|
||||
stargazers_count,
|
||||
pushed_at
|
||||
} = json
|
||||
|
||||
const repo: Repo = {
|
||||
name,
|
||||
full_name,
|
||||
description,
|
||||
html_url,
|
||||
homepage,
|
||||
stargazers_count,
|
||||
pushed_at
|
||||
}
|
||||
repos.push(repo)
|
||||
}
|
||||
|
||||
// sort by pushed to, newest first
|
||||
repos = repos.sort((a, b) => b.pushed_at.localeCompare(a.pushed_at))
|
||||
|
||||
return repos
|
||||
}
|
@ -7,7 +7,7 @@ const createJestConfig = nextJest({
|
||||
/** @type {import('jest').Config} */
|
||||
const customJestConfig = {
|
||||
rootDir: '../',
|
||||
setupFilesAfterEnv: ['<rootDir>/tests/jest.setup.tsx'],
|
||||
setupFilesAfterEnv: ['<rootDir>/tests/jest.setup.ts'],
|
||||
moduleDirectories: ['node_modules', '<rootDir>/src'],
|
||||
testEnvironment: 'jsdom',
|
||||
moduleNameMapper: {
|
||||
|
@ -1,26 +1,29 @@
|
||||
import { jest } from '@jest/globals'
|
||||
import '@testing-library/jest-dom'
|
||||
import fetchMock from 'jest-fetch-mock'
|
||||
import giphyMock from './__fixtures__/giphy.json'
|
||||
import { dataLocation } from './__fixtures__/location'
|
||||
import reposMock from './__fixtures__/repos.json'
|
||||
import './__mocks__/matchMedia'
|
||||
|
||||
fetchMock.enableMocks()
|
||||
|
||||
jest.mock('next/navigation', () => ({
|
||||
usePathname: jest.fn().mockImplementationOnce(() => '/')
|
||||
}))
|
||||
|
||||
jest.mock('../src/actions/getLocation', () => ({
|
||||
jest.mock('../src/lib/getLocation', () => ({
|
||||
getLocation: jest.fn().mockImplementation(() => dataLocation),
|
||||
preloadLocation: jest.fn()
|
||||
}))
|
||||
|
||||
jest.mock('../src/actions/getRandomGif', () => ({
|
||||
jest.mock('../src/lib/getRandomGif', () => ({
|
||||
getRandomGif: jest
|
||||
.fn()
|
||||
.mockImplementation(() => giphyMock.data.images.original.mp4)
|
||||
}))
|
||||
|
||||
jest.mock('../src/actions/getRepos', () => ({
|
||||
jest.mock('../src/lib/getRepos', () => ({
|
||||
getRepos: jest.fn().mockImplementation(() => reposMock)
|
||||
}))
|
||||
|
Loading…
Reference in New Issue
Block a user