init
commit
0faf7e9d31
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
out
|
|
@ -0,0 +1,84 @@
|
||||||
|
{
|
||||||
|
// Configuration for JavaScript files
|
||||||
|
"extends": [
|
||||||
|
"airbnb-base",
|
||||||
|
"next/core-web-vitals", // Needed to avoid warning in next.js build: 'The Next.js plugin was not detected in your ESLint configuration'
|
||||||
|
"plugin:prettier/recommended"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
// Configuration for TypeScript files
|
||||||
|
{
|
||||||
|
"files": ["**/*.ts", "**/*.tsx"],
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint",
|
||||||
|
"unused-imports",
|
||||||
|
"tailwindcss",
|
||||||
|
"simple-import-sort"
|
||||||
|
],
|
||||||
|
"extends": [
|
||||||
|
"plugin:tailwindcss/recommended",
|
||||||
|
"airbnb-typescript",
|
||||||
|
"next/core-web-vitals",
|
||||||
|
"plugin:prettier/recommended"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"react/destructuring-assignment": "off", // Vscode doesn't support automatically destructuring, it's a pain to add a new variable
|
||||||
|
"jsx-a11y/anchor-is-valid": "off", // Next.js use his own internal link system
|
||||||
|
"react/require-default-props": "off", // Allow non-defined react props as undefined
|
||||||
|
"react/jsx-props-no-spreading": "off", // _app.tsx uses spread operator and also, react-hook-form
|
||||||
|
"react-hooks/exhaustive-deps": "off", // Incorrectly report needed dependency with Next.js router
|
||||||
|
"@next/next/no-img-element": "off", // We currently not using next/image because it isn't supported with SSG mode
|
||||||
|
"@typescript-eslint/comma-dangle": "off", // Avoid conflict rule between Eslint and Prettier
|
||||||
|
"@typescript-eslint/consistent-type-imports": "error", // Ensure `import type` is used when it's necessary
|
||||||
|
"import/prefer-default-export": "off", // Named export is easier to refactor automatically
|
||||||
|
"simple-import-sort/imports": "error", // Import configuration for `eslint-plugin-simple-import-sort`
|
||||||
|
"simple-import-sort/exports": "error", // Export configuration for `eslint-plugin-simple-import-sort`
|
||||||
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
|
"unused-imports/no-unused-imports": "error",
|
||||||
|
"unused-imports/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{ "argsIgnorePattern": "^_" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Configuration for testing
|
||||||
|
{
|
||||||
|
"files": ["**/*.test.ts", "**/*.test.tsx"],
|
||||||
|
"plugins": ["jest", "jest-formatting", "testing-library", "jest-dom"],
|
||||||
|
"extends": [
|
||||||
|
"plugin:jest/recommended",
|
||||||
|
"plugin:jest-formatting/recommended",
|
||||||
|
"plugin:testing-library/react",
|
||||||
|
"plugin:jest-dom/recommended"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// Configuration for e2e testing (Cypress)
|
||||||
|
{
|
||||||
|
"files": ["**/*.cy.ts"],
|
||||||
|
"plugins": ["cypress"],
|
||||||
|
"extends": ["plugin:cypress/recommended"],
|
||||||
|
"parserOptions": {
|
||||||
|
"project": "./cypress/tsconfig.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
/.next
|
||||||
|
/out
|
||||||
|
|
||||||
|
.swc/
|
||||||
|
|
||||||
|
/build
|
||||||
|
|
||||||
|
cypress/screenshots
|
||||||
|
cypress/videos
|
||||||
|
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
.env*.local
|
||||||
|
.env
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx --no -- commitlint --edit $1
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
# Disable concurent to run `check-types` after ESLint in lint-staged
|
||||||
|
npx lint-staged --concurrent false
|
|
@ -0,0 +1,12 @@
|
||||||
|
MUKTI
|
||||||
|
license Mukti/Moksh - 'the liberation' is a basic realization that the result of distribution or giving away does not always come short, specially when things are non physical. MUKTI is not a license but a step towards a license free civilization.
|
||||||
|
Human race is better with 'some sense' rather a bunch of license. [have some sense, not licenses.]
|
||||||
|
|
||||||
|
till all the licenses are deprecated and decentralize [ specially for software, hardware, education, experience,even the network protocol (internet) ], lets use freedom centric and/or having "take if it helps you, just don't sue us - kind of mentality" licenses like GPL, MIT, BSD ...
|
||||||
|
|
||||||
|
this will not be registered / certified / legalise in any manner, as it's not about enforcement - it is about freedom to share.
|
||||||
|
|
||||||
|
example of 'some sense':
|
||||||
|
verbose incremental documentation with versioning, attaching contributor information.
|
||||||
|
try to make some OFFERING to the contributor or the contributing organization with or without a subscription / support.
|
||||||
|
maximize the use of freedom centric tools[hardware, software, os ...] enen if it costs and costs more, try not to be the product.
|
|
@ -0,0 +1,6 @@
|
||||||
|
// The easiest solution to mock `next/router`: https://github.com/vercel/next.js/issues/7479
|
||||||
|
export const useRouter = () => {
|
||||||
|
return {
|
||||||
|
basePath: '.',
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
module.exports = { extends: ['@commitlint/config-conventional'] };
|
|
@ -0,0 +1,8 @@
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
const { defineConfig } = require('cypress');
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
e2e: {
|
||||||
|
baseUrl: 'http://localhost:3000',
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,27 @@
|
||||||
|
describe('Seo metadata', () => {
|
||||||
|
describe('Verify SEO Metadata', () => {
|
||||||
|
it('should render SEO metadata on Index page', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
// The Index page should have a page title
|
||||||
|
cy.title().should('not.be.empty');
|
||||||
|
|
||||||
|
// The Index page should also contain a meta description for SEO
|
||||||
|
cy.get('head meta[name="description"]')
|
||||||
|
.invoke('attr', 'content')
|
||||||
|
.should('not.be.empty');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render SEO metadata on About page', () => {
|
||||||
|
cy.visit('/about');
|
||||||
|
|
||||||
|
// The About page should have a page title
|
||||||
|
cy.title().should('not.be.empty');
|
||||||
|
|
||||||
|
// The About page should also contain a meta description for SEO
|
||||||
|
cy.get('head meta[name="description"]')
|
||||||
|
.invoke('attr', 'content')
|
||||||
|
.should('not.be.empty');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,25 @@
|
||||||
|
describe('Navigation', () => {
|
||||||
|
describe('Static pages', () => {
|
||||||
|
it('should navigate to the about page', () => {
|
||||||
|
// Start from the index page
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
// The index page should contain an h1
|
||||||
|
cy.findByRole('heading', {
|
||||||
|
name: 'Boilerplate code for your Nextjs project with Tailwind CSS',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find a link containing "About" text and click it
|
||||||
|
cy.findByRole('link', { name: 'About' }).click();
|
||||||
|
|
||||||
|
// The new url should include "/about"
|
||||||
|
cy.url().should('include', '/about');
|
||||||
|
|
||||||
|
// The new page should contain two "lorem ipsum" paragraphs
|
||||||
|
cy.findAllByText('Lorem ipsum dolor sit amet', { exact: false }).should(
|
||||||
|
'have.length',
|
||||||
|
2
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "Using fixtures to represent data",
|
||||||
|
"email": "hello@cypress.io",
|
||||||
|
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
// ***********************************************************
|
||||||
|
// This example support/e2e.ts is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import '@testing-library/cypress/add-commands';
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["es5", "dom"],
|
||||||
|
"types": ["node", "cypress", "@testing-library/cypress"],
|
||||||
|
|
||||||
|
"isolatedModules": false
|
||||||
|
},
|
||||||
|
"include": ["**/*.cy.ts"],
|
||||||
|
"exclude": []
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
const nextJest = require('next/jest');
|
||||||
|
|
||||||
|
const createJestConfig = nextJest({
|
||||||
|
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
|
||||||
|
dir: './',
|
||||||
|
});
|
||||||
|
|
||||||
|
const customJestConfig = {
|
||||||
|
moduleNameMapper: {
|
||||||
|
// Handle module aliases (this will be automatically configured for you soon)
|
||||||
|
'^@/(.*)$': '<rootDir>/src/$1',
|
||||||
|
|
||||||
|
'^@/public/(.*)$': '<rootDir>/public/$1',
|
||||||
|
},
|
||||||
|
setupFilesAfterEnv: ['./jest.setup.js'],
|
||||||
|
clearMocks: true,
|
||||||
|
collectCoverage: true,
|
||||||
|
collectCoverageFrom: [
|
||||||
|
'./src/**/*.{js,jsx,ts,tsx}',
|
||||||
|
'!./src/**/_*.{js,jsx,ts,tsx}',
|
||||||
|
'!**/*.d.ts',
|
||||||
|
'!**/node_modules/**',
|
||||||
|
],
|
||||||
|
coverageThreshold: {
|
||||||
|
global: {
|
||||||
|
branches: 80,
|
||||||
|
functions: 80,
|
||||||
|
lines: 80,
|
||||||
|
statements: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
testEnvironment: 'jest-environment-jsdom',
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = createJestConfig(customJestConfig);
|
|
@ -0,0 +1,3 @@
|
||||||
|
// Optional: configure or set up a testing framework before each test.
|
||||||
|
// If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js`
|
||||||
|
import '@testing-library/jest-dom/extend-expect';
|
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'eslint'],
|
||||||
|
'**/*.ts?(x)': () => 'npm run check-types',
|
||||||
|
'*.json': ['prettier --write'],
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
[build]
|
||||||
|
publish = "out"
|
||||||
|
command = "npm run build-prod"
|
||||||
|
|
||||||
|
[build.environment]
|
||||||
|
NETLIFY_NEXT_PLUGIN_SKIP = "true"
|
|
@ -0,0 +1,5 @@
|
||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
|
@ -0,0 +1,17 @@
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
||||||
|
enabled: process.env.ANALYZE === 'true',
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = withBundleAnalyzer({
|
||||||
|
eslint: {
|
||||||
|
dirs: ['.'],
|
||||||
|
},
|
||||||
|
poweredByHeader: false,
|
||||||
|
trailingSlash: true,
|
||||||
|
basePath: '',
|
||||||
|
// The starter code load resources from `public` folder with `router.basePath` in React components.
|
||||||
|
// So, the source code is "basePath-ready".
|
||||||
|
// You can remove `basePath` if you don't need it.
|
||||||
|
reactStrictMode: true,
|
||||||
|
});
|
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"name": "boilerplate-next-tailwind",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"build-stats": "cross-env ANALYZE=true npm run build",
|
||||||
|
"export": "next export",
|
||||||
|
"build-prod": "run-s clean build export",
|
||||||
|
"clean": "rimraf .next out",
|
||||||
|
"lint": "next lint",
|
||||||
|
"check-types": "tsc --noEmit --pretty",
|
||||||
|
"test": "jest",
|
||||||
|
"cypress": "cypress open",
|
||||||
|
"cypress:headless": "cypress run",
|
||||||
|
"e2e": "start-server-and-test dev http://localhost:3000 cypress",
|
||||||
|
"e2e:headless": "start-server-and-test dev http://localhost:3000 cypress:headless",
|
||||||
|
"prepare": "husky install"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"next": "^12.2.5",
|
||||||
|
"next-seo": "^5.5.0",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^17.1.2",
|
||||||
|
"@commitlint/config-conventional": "^17.1.0",
|
||||||
|
"@next/bundle-analyzer": "^12.2.5",
|
||||||
|
"@testing-library/cypress": "^8.0.3",
|
||||||
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
|
"@testing-library/react": "^13.3.0",
|
||||||
|
"@types/jest": "^29.0.0",
|
||||||
|
"@types/node": "^18.7.14",
|
||||||
|
"@types/react": "^18.0.18",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.36.1",
|
||||||
|
"@typescript-eslint/parser": "^5.36.1",
|
||||||
|
"autoprefixer": "^10.4.8",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"cssnano": "^5.1.13",
|
||||||
|
"cypress": "^10.7.0",
|
||||||
|
"eslint": "^8.23.0",
|
||||||
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
|
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||||
|
"eslint-config-next": "^12.2.5",
|
||||||
|
"eslint-config-prettier": "^8.5.0",
|
||||||
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
|
"eslint-plugin-import": "^2.26.0",
|
||||||
|
"eslint-plugin-jest": "^27.0.1",
|
||||||
|
"eslint-plugin-jest-dom": "^4.0.2",
|
||||||
|
"eslint-plugin-jest-formatting": "^3.1.0",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"eslint-plugin-react": "^7.31.1",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||||
|
"eslint-plugin-tailwindcss": "^3.6.1",
|
||||||
|
"eslint-plugin-testing-library": "^5.6.0",
|
||||||
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
|
"husky": "^8.0.1",
|
||||||
|
"jest": "^29.0.1",
|
||||||
|
"jest-environment-jsdom": "^29.0.1",
|
||||||
|
"lint-staged": "^13.0.3",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"postcss": "^8.4.16",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"start-server-and-test": "^1.14.0",
|
||||||
|
"tailwindcss": "^3.1.8",
|
||||||
|
"typescript": "^4.8.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Please do not use the array form (like ['tailwindcss', 'postcss-preset-env'])
|
||||||
|
// it will create an unexpected error: Invalid PostCSS Plugin found: [0]
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}),
|
||||||
|
},
|
||||||
|
};
|
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 254 KiB |
Binary file not shown.
After Width: | Height: | Size: 445 B |
Binary file not shown.
After Width: | Height: | Size: 1001 B |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,28 @@
|
||||||
|
import { render, waitFor } from '@testing-library/react';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { Meta } from './Meta';
|
||||||
|
|
||||||
|
// Mock `next/head`: https://bradgarropy.com/blog/mocking-nextjs
|
||||||
|
jest.mock(
|
||||||
|
'next/head',
|
||||||
|
() =>
|
||||||
|
function Head(props: { children: ReactNode }) {
|
||||||
|
// eslint-disable-next-line testing-library/no-node-access
|
||||||
|
return <>{props.children}</>;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('Meta component', () => {
|
||||||
|
describe('Render method', () => {
|
||||||
|
it('should a page title', async () => {
|
||||||
|
const title = 'Random title';
|
||||||
|
|
||||||
|
render(<Meta title={title} description="Random description" />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(document.title).toEqual(title);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,66 @@
|
||||||
|
import Head from 'next/head';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { NextSeo } from 'next-seo';
|
||||||
|
|
||||||
|
import { AppConfig } from '@/utils/AppConfig';
|
||||||
|
|
||||||
|
type IMetaProps = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
canonical?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Meta = (props: IMetaProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<meta charSet="UTF-8" key="charset" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width,initial-scale=1"
|
||||||
|
key="viewport"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
href={`${router.basePath}/apple-touch-icon.png`}
|
||||||
|
key="apple"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href={`${router.basePath}/favicon-32x32.png`}
|
||||||
|
key="icon32"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
href={`${router.basePath}/favicon-16x16.png`}
|
||||||
|
key="icon16"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
href={`${router.basePath}/favicon.ico`}
|
||||||
|
key="favicon"
|
||||||
|
/>
|
||||||
|
</Head>
|
||||||
|
<NextSeo
|
||||||
|
title={props.title}
|
||||||
|
description={props.description}
|
||||||
|
canonical={props.canonical}
|
||||||
|
openGraph={{
|
||||||
|
title: props.title,
|
||||||
|
description: props.description,
|
||||||
|
url: props.canonical,
|
||||||
|
locale: AppConfig.locale,
|
||||||
|
site_name: AppConfig.site_name,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Meta };
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
|
import About from '@/pages/about';
|
||||||
|
|
||||||
|
// The easiest solution to mock `next/router`: https://github.com/vercel/next.js/issues/7479
|
||||||
|
// The mock has been moved to `__mocks__` folder to avoid duplication
|
||||||
|
|
||||||
|
describe('About page', () => {
|
||||||
|
describe('Render method', () => {
|
||||||
|
it('should have two paragraphs of `Lorem ipsum`', () => {
|
||||||
|
render(<About />);
|
||||||
|
|
||||||
|
const paragraph = screen.getAllByText(/Lorem ipsum/);
|
||||||
|
|
||||||
|
expect(paragraph).toHaveLength(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
|
import Index from '@/pages/index';
|
||||||
|
|
||||||
|
// The easiest solution to mock `next/router`: https://github.com/vercel/next.js/issues/7479
|
||||||
|
// The mock has been moved to `__mocks__` folder to avoid duplication
|
||||||
|
|
||||||
|
describe('Index page', () => {
|
||||||
|
describe('Render method', () => {
|
||||||
|
it('should have h1 tag', () => {
|
||||||
|
render(<Index />);
|
||||||
|
|
||||||
|
const heading = screen.getByRole('heading', {
|
||||||
|
name: /Boilerplate code/,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(heading).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
import '../styles/global.css';
|
||||||
|
|
||||||
|
import type { AppProps } from 'next/app';
|
||||||
|
|
||||||
|
const MyApp = ({ Component, pageProps }: AppProps) => (
|
||||||
|
<Component {...pageProps} />
|
||||||
|
);
|
||||||
|
|
||||||
|
export default MyApp;
|
|
@ -0,0 +1,21 @@
|
||||||
|
import Document, { Head, Html, Main, NextScript } from 'next/document';
|
||||||
|
|
||||||
|
import { AppConfig } from '@/utils/AppConfig';
|
||||||
|
|
||||||
|
// Need to create a custom _document because i18n support is not compatible with `next export`.
|
||||||
|
class MyDocument extends Document {
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Html lang={AppConfig.locale}>
|
||||||
|
<Head />
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MyDocument;
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Meta } from '@/layouts/Meta';
|
||||||
|
import { Main } from '@/templates/Main';
|
||||||
|
|
||||||
|
const About = () => (
|
||||||
|
<Main meta={<Meta title="sample" description="sample description" />}>
|
||||||
|
<p>
|
||||||
|
About descriptiondescriptiondescription
|
||||||
|
</p>
|
||||||
|
</Main>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default About;
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
|
||||||
|
import { Meta } from '@/layouts/Meta';
|
||||||
|
import { Main } from '@/templates/Main';
|
||||||
|
|
||||||
|
const Index = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Main
|
||||||
|
meta={
|
||||||
|
<Meta
|
||||||
|
title="next boilerplate"
|
||||||
|
description="next boilerplate, next boilerplate. "
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
</Main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Index;
|
|
@ -0,0 +1,21 @@
|
||||||
|
@tailwind base;
|
||||||
|
|
||||||
|
a {
|
||||||
|
@apply text-blue-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
@apply border-b-2 border-blue-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
@tailwind components;
|
||||||
|
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
.content p {
|
||||||
|
@apply my-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content ul {
|
||||||
|
@apply my-6;
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { render, screen, within } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { Main } from './Main';
|
||||||
|
|
||||||
|
describe('Main template', () => {
|
||||||
|
describe('Render method', () => {
|
||||||
|
it('should have 3 menu items', () => {
|
||||||
|
render(<Main meta={null}>{null}</Main>);
|
||||||
|
|
||||||
|
const menuItemList = screen.getAllByRole('listitem');
|
||||||
|
|
||||||
|
expect(menuItemList).toHaveLength(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a link to support creativedesignsguru.com', () => {
|
||||||
|
render(<Main meta={null}>{null}</Main>);
|
||||||
|
|
||||||
|
const copyrightSection = screen.getByText(/© Copyright/);
|
||||||
|
const copyrightLink = within(copyrightSection).getByRole('link');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PLEASE READ THIS SECTION
|
||||||
|
* We'll really appreciate if you could have a link to our website
|
||||||
|
* The link doesn't need to appear on every pages, one link on one page is enough.
|
||||||
|
* Thank you for your support it'll mean a lot for us.
|
||||||
|
*/
|
||||||
|
expect(copyrightLink).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
'https://creativedesignsguru.com'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,55 @@
|
||||||
|
import Link from 'next/link';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { AppConfig } from '@/utils/AppConfig';
|
||||||
|
|
||||||
|
type IMainProps = {
|
||||||
|
meta: ReactNode;
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Main = (props: IMainProps) => (
|
||||||
|
<div className="w-full px-1 text-gray-700 antialiased">
|
||||||
|
{props.meta}
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-screen-md">
|
||||||
|
<div className="border-b border-gray-300">
|
||||||
|
<div className="pt-16 pb-8">
|
||||||
|
<div className="text-3xl font-bold text-gray-900">
|
||||||
|
{AppConfig.title}
|
||||||
|
</div>
|
||||||
|
<div className="text-xl">{AppConfig.description}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ul className="flex flex-wrap text-xl">
|
||||||
|
<li className="mr-6">
|
||||||
|
<Link href="/">
|
||||||
|
<a className="border-none text-gray-700 hover:text-gray-900">
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li className="mr-6">
|
||||||
|
<Link href="/about/">
|
||||||
|
<a className="border-none text-gray-700 hover:text-gray-900">
|
||||||
|
About
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li className="mr-6">
|
||||||
|
asd
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="content py-5 text-xl">{props.children}</div>
|
||||||
|
|
||||||
|
<div className="border-t border-gray-300 py-8 text-center text-sm">
|
||||||
|
fgfvgf
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { Main };
|
|
@ -0,0 +1,6 @@
|
||||||
|
export const AppConfig = {
|
||||||
|
site_name: 'Starter',
|
||||||
|
title: 'Nextjs Starter',
|
||||||
|
description: 'Starter code for your Nextjs Boilerplate with Tailwind CSS',
|
||||||
|
locale: 'en',
|
||||||
|
};
|
|
@ -0,0 +1,45 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: ['./src/**/*.{js,ts,jsx,tsx}'],
|
||||||
|
theme: {
|
||||||
|
fontSize: {
|
||||||
|
xs: '0.75rem',
|
||||||
|
sm: '0.875rem',
|
||||||
|
base: '1rem',
|
||||||
|
lg: '1.125rem',
|
||||||
|
xl: '1.25rem',
|
||||||
|
'2xl': '1.5rem',
|
||||||
|
'3xl': '1.875rem',
|
||||||
|
'4xl': '2.25rem',
|
||||||
|
'5xl': '3rem',
|
||||||
|
'6xl': '4rem',
|
||||||
|
},
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
gray: {
|
||||||
|
100: '#f7fafc',
|
||||||
|
200: '#edf2f7',
|
||||||
|
300: '#e2e8f0',
|
||||||
|
400: '#cbd5e0',
|
||||||
|
500: '#a0aec0',
|
||||||
|
600: '#718096',
|
||||||
|
700: '#4a5568',
|
||||||
|
800: '#2d3748',
|
||||||
|
900: '#1a202c',
|
||||||
|
},
|
||||||
|
blue: {
|
||||||
|
100: '#ebf8ff',
|
||||||
|
200: '#bee3f8',
|
||||||
|
300: '#90cdf4',
|
||||||
|
400: '#63b3ed',
|
||||||
|
500: '#4299e1',
|
||||||
|
600: '#3182ce',
|
||||||
|
700: '#2b6cb0',
|
||||||
|
800: '#2c5282',
|
||||||
|
900: '#2a4365',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"preserveConstEnums": true,
|
||||||
|
"strict": true,
|
||||||
|
"alwaysStrict": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"allowUnreachableCode": false,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
"target": "es5",
|
||||||
|
"outDir": "out",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
|
||||||
|
"jsx": "preserve",
|
||||||
|
"noEmit": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"incremental": true,
|
||||||
|
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"],
|
||||||
|
"@/public/*": ["./public/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["./out/**/*", "./node_modules/**/*", "**/*.cy.ts"],
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||||
|
}
|
Loading…
Reference in New Issue