ナビゲーションバー
components/Layout.tsx
import Head from 'next/head'
import Link from 'next/link'
import Image from 'next/image'
interface TITLE {
title: string
}
const Layout: React.FC<TITLE> = ({ children, title = 'Nextjs' }) => {
return (
<div className="flex justify-center items-center flex-col min-h-screen font-mono">
<Head>
<title>{title}</title>
</Head>
<header>
<nav className="bg-gray-800 w-screen">
<div className="flex items-center pl-8 h-14">
<div className="flex space-x-4">
<Link href="/">
<a
data-testid="home-nav"
className="text-gray-300 hover:bg-gray-700 px-3 py-2 rounded"
>
Home
</a>
</Link>
<Link href="/blog-page">
<a
data-testid="blog-nav"
className="text-gray-300 hover:bg-gray-700 px-3 py-2 rounded"
>
Blog
</a>
</Link>
<Link href="/comment-page">
<a
data-testid="comment-nav"
className="text-gray-300 hover:bg-gray-700 px-3 py-2 rounded"
>
Comment
</a>
</Link>
<Link href="/context-page">
<a
data-testid="context-nav"
className="text-gray-300 hover:bg-gray-700 px-3 py-2 rounded"
>
Context
</a>
</Link>
<Link href="/task-page">
<a
data-testid="task-nav"
className="text-gray-300 hover:bg-gray-700 px-3 py-2 rounded"
>
Todos
</a>
</Link>
</div>
</div>
</nav>
</header>
<main className="flex flex-1 justify-center items-center flex-col w-screen">
{children}
</main>
<footer className="w-full h-12 flex justify-center items-center border-t">
<a
className="flex items-center"
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by
{/* <img src="/vercel.svg" alt="Vercel Logo" className="h-4 ml-2" /> */}
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</a>
</footer>
</div>
)
}
export default Layout
pages/index.tsx
import Layout from '../components/Layout'
const Home: React.FC = () => {
return (
<Layout title="Home">
<p className="text-4xl">Welcome to Nextjs</p>
</Layout>
)
}
export default Home
pages/blog-page.tsx
import Layout from '../components/Layout'
import { getAllPostsData } from '../lib/fetch'
import Post from '../components/Post'
import { GetStaticProps } from 'next'
import { POST } from '../types/Types'
interface STATICPROPS {
posts: POST[]
}
const BlogPage: React.FC<STATICPROPS> = ({ posts }) => {
return (
<Layout title="Blog">
<p className="text-4xl mb-10">blog page</p>
<ul>{posts && posts.map((post) => <Post key={post.id} {...post} />)}</ul>
</Layout>
)
}
export default BlogPage
export const getStaticProps: GetStaticProps = async () => {
const posts = await getAllPostsData()
return {
props: { posts },
}
}
pages/comment-page.tsx
import Layout from '../components/Layout'
import useSWR from 'swr'
import axios from 'axios'
import Comment from '../components/Comment'
import { COMMENT } from '../types/Types'
const axiosFetcher = async () => {
const result = await axios.get<COMMENT[]>(
'https://jsonplaceholder.typicode.com/comments/?_limit=10'
)
return result.data
}
const CommentPage: React.FC = () => {
const { data: comments, error } = useSWR('commentsFetch', axiosFetcher)
if (error) return <span>Error!</span>
return (
<Layout title="Comment">
<p className="text-4xl m-10">comment page</p>
<ul>
{comments &&
comments.map((comment) => <Comment key={comment.id} {...comment} />)}
</ul>
</Layout>
)
}
export default CommentPage
pages/context-page.tsx
import Layout from '../components/Layout'
import { StateProvider } from '../context/StateProvider'
import ContextA from '../components/ContextA'
import ContextB from '../components/ContextB'
const ContextPage: React.FC = () => {
return (
<Layout title="Context">
<p className="text-4xl mb-10">context page</p>
<StateProvider>
<ContextA />
<ContextB />
</StateProvider>
</Layout>
)
}
export default ContextPage
pages/task-page.tsx
import Layout from '../components/Layout'
import { GetStaticProps } from 'next'
import { getAllTasksData } from '../lib/fetch'
import useSWR from 'swr'
import axios from 'axios'
import { TASK } from '../types/Types'
interface STATICPROPS {
staticTasks: TASK[]
}
const axiosFetcher = async () => {
const result = await axios.get<TASK[]>(
'https://jsonplaceholder.typicode.com/todos/?_limit=10'
)
return result.data
}
const TaskPage: React.FC<STATICPROPS> = ({ staticTasks }) => {
const { data: tasks, error } = useSWR('todosFetch', axiosFetcher, {
fallbackData: staticTasks,
revalidateOnMount: true,
})
if (error) return <span>Error!</span>
return (
<Layout title="Todos">
<p className="mb-10 text-4xl">todos page</p>
<ul>
{tasks &&
tasks.map((task) => (
<li key={task.id}>
{task.id}
{': '}
<span>{task.title}</span>
</li>
))}
</ul>
</Layout>
)
}
export default TaskPage
export const getStaticProps: GetStaticProps = async () => {
const staticTasks = await getAllTasksData()
return {
props: { staticTasks },
}
}
テスト
ターミナル
$ yarn add next-page-tester
_test_/NavBar.test.tsx
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import userEvent from '@testing-library/user-event'
import { getPage } from 'next-page-tester'
import { initTestHelpers } from 'next-page-tester'
import 'setimmediate'
initTestHelpers()
describe('Navigation by Link', () => {
it('Should route to selected page in navbar', async () => {
const { page } = await getPage({
route: '/index',
})
render(page)
userEvent.click(screen.getByTestId('blog-nav'))
expect(await screen.findByText('blog page')).toBeInTheDocument()
//screen.debug()
userEvent.click(screen.getByTestId('comment-nav'))
expect(await screen.findByText('comment page')).toBeInTheDocument()
userEvent.click(screen.getByTestId('context-nav'))
expect(await screen.findByText('context page')).toBeInTheDocument()
userEvent.click(screen.getByTestId('task-nav'))
expect(await screen.findByText('todos page')).toBeInTheDocument()
userEvent.click(screen.getByTestId('home-nav'))
expect(await screen.findByText('Welcome to Nextjs')).toBeInTheDocument()
})
})