- NextJs 13
- Typescript
- Prettier
- Eslint
- Styled Components
- Vitest
- React Testing Library
- Cypress
- Storybook
- Material UI
npx create-next-app --ts
yarn add prettier -D
module.exports = {
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: true,
trailingComma: 'es5',
bracketSpacing: true,
jsxBracketSameLine: false,
arrowParens: 'avoid',
proseWrap: 'preserve',
}
yarn add styled-components
yarn add -D @swc/plugin-styled-components @swc/core @types/styled-components
{
"jsc": {
"experimental": {
"plugins": [
[
"@swc/plugin-styled-components",
{
"displayName": true,
"ssr": true
}
]
]
}
}
}
'use client'
import styled from 'styled-components'
export const StyledTitle = styled.h1`
color: red;
`
import { StyledTitle } from './page.styled'
export default function Home() {
(...)
return (
(...)
<StyledTitle className={styles.title}>
Welcome to <a href="https://nextjs.org">Next.js!</a>
</StyledTitle>
(...)
)
}
It is important to note that since with Next 13 all page are server components, the styled component must be defined in a separate file, with the 'use client' directive at the top of the file. Otherwise, the styled component will be rendered on the server side and will not work.
yarn add -D @testing-library/react @types/node @types/react vitest @vitejs/plugin-react jsdom
Edit the package.json file
{
"scripts": {
(...)
"test": "vitest"
}
}
Create a vitest.config.ts file
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
},
})
import { expect, test } from 'vitest'
import { render, screen, within } from '@testing-library/react'
import { vi } from 'vitest'
import Home from '../src/app/page'
import { NextFont } from 'next/dist/compiled/@next/font'
vi.mock('next/font/google', () => ({
Inter: () =>
({
className: 'inter',
style: {},
} as NextFont),
}))
test('home', () => {
render(<Home />)
const main = within(screen.getByRole('main'))
expect(
main.getByRole('heading', { level: 1, name: /welcome to Next.js!/i })
).toBeDefined()
const vercelLogo = within(screen.getByRole('img', { name: /Vercel Logo/i }))
expect(vercelLogo).toBeDefined()
const nextJsLogo = within(screen.getByRole('img', { name: /Next.js Logo/i }))
expect(nextJsLogo).toBeDefined()
})
Run the test
yarn test
RERUN __tests__/Home.test.tsx x27
✓ __tests__/Home.test.tsx (1)
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 14:43:21
Duration 266ms
PASS Waiting for file changes...
press h to show help, press q to quit
yarn add -D cypress start-server-and-test
Edit package.json file
{
"scripts": {
(...)
"e2e": "start-server-and-test dev http://localhost:3000 \"cypress open --e2e\"",
"e2e:headless": "start-server-and-test dev http://localhost:3000 \"cypress run --e2e\"",
"component": "cypress open --component",
"component:headless": "cypress run --component"
}
}
Run the cypress command to initialize the cypress files for e2e and component testing
yarn cypress:open
import { defineConfig } from 'cypress'
export default defineConfig({
component: {
devServer: {
framework: 'next',
bundler: 'webpack',
},
specPattern: '**/cypress/__tests__/*.spec.tsx',
viewportHeight: 1280,
viewportWidth: 800,
},
e2e: {
baseUrl: 'http://localhost:3000',
specPattern: '**/cypress/e2e/*.spec.ts',
},
video: false,
})
import Link from 'next/link'
export default function About() {
return (
<div>
<h1>About Page</h1>
<Link href="/">Homepage</Link>
</div>
)
}
import Link from 'next/link'
(...)
export default function Home() {
return (
(...)
<Link href="/about">About</Link>
(...)
)
}
describe('Navigation', () => {
it('should navigate to the about page', () => {
// Start from the index page
cy.visit('http://localhost:3000/')
// Find a link with an href attribute containing "about" and click it
cy.get('a[href*="about"]').click()
// The new url should include "/about"
cy.url().should('include', '/about')
// The new page should contain an h1 with "About page"
cy.get('h1').contains('About Page')
})
it('should navigate to the home page', () => {
cy.visit('http://localhost:3000/about')
// Find a link with an href attribute containing "/" and click it
cy.get('a[href*="/"]').click()
// The new url should include "/"
cy.url().should('include', '/')
// The new page should contain an h1 with "Home page"
cy.get('h1').contains('Welcome to Next.js!')
})
})
export {}
yarn e2e:headless
(Run Finished)
Spec Tests Passing Failing Pending Skipped
┌───────────────────────────────────────────────────────────────────────────┐
│ ✔ navigation.spec.ts 00:03 2 2 - - - │
└───────────────────────────────────────────────────────────────────────────┘
✔ All specs passed! 00:03 2 2 - - -
✨ Done in 12.32s.
Create a cypress/__tests__/pageAbout.spec.tsx file
import About from '../../src/app/about/page'
import React from 'react'
describe('<About />', () => {
it('renders', () => {
// see: https://on.cypress.io/mounting-react
cy.mount(<About />)
// see: https://on.cypress.io/interacting-with-elements
// find the link to the homepage
cy.contains('a', 'Homepage').should('have.attr', 'href', '/')
// find the heading
cy.contains('h1', 'About Page').should('be.visible')
})
})
Create a cypress/__tests__/pageHome.spec.tsx file
import React from 'react'
import Home from '../../src/app/page'
describe('<Home />', () => {
it('renders', () => {
// see: https://on.cypress.io/mounting-react
cy.mount(<Home />)
// see: https://on.cypress.io/interacting-with-elements
// find the link to the about page
cy.contains('a', 'About').should('have.attr', 'href', '/about')
// find the heading
cy.contains('h1', 'Welcome to Next.js!').should('be.visible')
})
})
yarn component:headless
(Run Finished)
Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────┐
│ ✔ pageAbout.spec.tsx 114ms 1 1 - - - │
├────────────────────────────────────────────────────────────────────────────────┤
│ ✔ pageHome.spec.tsx 154ms 1 1 - - - │
└────────────────────────────────────────────────────────────────────────────────┘
✔ All specs passed 268ms 2 2 - - -
✨ Done in 8.57s.
npx storybook@next init
yarn add -D supertest
import request from 'supertest'
import { expect, test } from 'vitest'
test('GET /hello', async () => {
const response = await request('http://localhost:3000').get('/api/hello')
expect(response.status).toEqual(200)
const text = await response.text
expect(text).toBe('Hello, Next.js!')
})