Skip to content

Libraries

Jewoo Ham edited this page Dec 7, 2022 · 2 revisions

configuration

__mocks__/fileMock.js

module.exports = 'test-file-stub'

__mocks__/styleMock.js

module.exports = {}

jest.config.cjs
You can modify collectCoverageFrom and coverageThreshold for coverage configuration.
You can modify moduleNameMapper for absolute path.

module.exports = {
    testEnvironment: 'jsdom',
    collectCoverageFrom: [
      'src/**/*.{ts,tsx}',
      '!src/App.tsx',
      '!src/env.ts',
      '!src/{stories,store,mocks,utils,hooks}/**',
      '!src/**/*.{types,type,stories,constants,test,spec}.{ts,tsx}',
      '!**/*.d.ts',
      '!**/*.config.*',
      '!**/node_modules/**',
      '!**/.yarn/**'
    ],
    testMatch: [
      '<rootDir>/__tests__/**/*.(spec|test).ts?(x)',
      '<rootDir>/src/**/*.(spec|test).ts?(x)',
    ],
    moduleNameMapper: {
      '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
      '^.+\\.(css|sass|scss)$': '<rootDir>/__mocks__/styleMock.js',
      '^.+\\.(jpg|jpeg|png|gif|webp|svg)$': '<rootDir>/__mocks__/fileMock.js',
      '^src/(.*)$': '<rootDir>/src/$1',
      '^@/(.*)$': '<rootDir>/src/$1',
    },
    setupFilesAfterEnv: [
      '<rootDir>/jest.setup.ts',
      'jest-plugin-context/setup',
    ],
    testPathIgnorePatterns: [
      '<rootDir>/node_modules/',
      '<rootDir>/.yarn/'
    ],
    transform: {
      '^.+\\.(js|jsx|ts|tsx)$': [
        '@swc/jest',
        {
          sourceMaps: true,
          jsc: {
            parser: {
              syntax: 'typescript',
              tsx: true,
              decorators: false,
              dynamicImport: false,
            },
            transform: {
              react: {
                runtime: 'automatic',
              },
            },
          },
        },
      ],
    },
    coveragePathIgnorePatterns: [
      '/node_modules/',
      '^.+\\.module\\.(css|sass|scss)$',
      'coverage',
      'src/main.ts',
      '.yarn',
      'src/config.ts',
    ],
    coverageThreshold: {
      global: {
        branches: 100,
        functions: 100,
        lines: 100,
        statements: 100,
      },
    },
};

jest.setup.ts
Configuration for jest & MSW.

import '@testing-library/jest-dom';
import {server} from "./src/mocks/server";
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

configuration

.eslintrc.json

{
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "plugin:react/recommended",
        "standard-with-typescript",
        "plugin:prettier/recommended",
        "plugin:jest-dom/recommended",
        "plugin:storybook/recommended"
    ],
    "overrides": [
        {
            "files": ["*.jsx", "*.tsx"],
            "rules": {
                "@typescript-eslint/explicit-function-return-type": ["off"]
            }
        },
        {
            "files": ["**/stories/**.jsx", "**/stories/**.tsx"],
            "rules": {
                "@typescript-eslint/consistent-type-assertions": ["off"]
            }
        }
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": "latest",
        "sourceType": "module",
        "project": "./tsconfig.json"
    },
    "plugins": [
        "react"
    ],
    "rules": {
        "react/react-in-jsx-scope": "off",
        "prettier/prettier": ["error", { "endOfLine": "auto" }]
    },
    "ignorePatterns": [
        "vite-env.d.ts",
        "jest.config.cjs",
        "jest.setup.ts",
        "vite.config.ts",
        "__mocks__/**"
    ]
}

.vscode/settings.json
Forcing the ESLint auto fix in case of not configuring themselves.

{
	"editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "eslint.format.enable": true,
	"editor.formatOnSave": false,
}

configuration

.prettierrc
You can modify by your preferences.

{
    "trailingComma": "all",
    "singleQuote": true,
    "semi": true,
    "useTabs": false,
    "tabWidth": 2,
    "printWidth": 80,
    "arrowParens": "always"
}

configuration

.husky/pre-commit
Checking lint error before commit.

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged

.husky/pre-push
Checking all test files before push.

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn run jest --watchAll=false

pacakge.json

"scripts": {
    "lint": "eslint --fix",
},
"lint-staged": {
    "src/**/*.{ts,js,tsx}": [
      "yarn lint"
    ]
}

configuration

There is no configuration.

configuration

.storybook
src/stories

There is no configuration.
Only default configuration is added.

configuration
This project is using react-query with axios.
src/main.tsx

import { QueryClientProvider, QueryClient } from "react-query";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <QueryClientProvider client={new QueryClient()}>
      <RecoilRoot>
        <Router>
          <App />
        </Router>
      </RecoilRoot>
    </QueryClientProvider>
  </React.StrictMode>
);

src/utils/axios.ts

import axios, { AxiosInstance } from 'axios';

export const useAxios = async (): Promise<AxiosInstance> => {
  const instance = await axios.create({
    baseURL: import.meta.env.VITE_API_URL,
    headers: {
      'Content-Type': 'application/json',
    },
  });

  return instance;
};

src/hooks/useGetUsers.ts

import { useQuery, UseQueryResult } from 'react-query';
import { useAxios } from '@/utils/axios';

interface userGetResType {
  id: number;
  name: string;
  username: string;
  email: string;
}

const getUser = async (id: number): Promise<userGetResType> => {
  try {
    const { data } = await (await useAxios()).get(`/users/${id}`);
    return data;
  } catch (err: any) {
    throw new Error(err.response.status, { cause: err.response.data });
  }
};

export const useGetUser = (id: number): UseQueryResult<userGetResType, any> => {
  return useQuery<userGetResType, any>('user', async () => await getUser(id));
};

src/pages/User.tsx

import { useEffect, useState } from 'react';
import { useGetUser } from '@/hooks/useGetUsers';
import userState from '@/store/user';
import { useSetRecoilState } from 'recoil';

export default function User() {
  const { isLoading, isError, error, data } = useGetUser(1);
  const [warning, setWarning] = useState<string>('');
  const setUser = useSetRecoilState(userState);

  useEffect(() => {
    if (data != null) {
      setUser(data);
    }

    if (isError) {
      error?.message === '404' && setWarning('404: Cannot request');
    }
  }, [isError, data]);

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return (
    <div className="App">
      <p>{warning !== '' && warning}</p>
      <p>{data?.name}</p>
    </div>
  );
}

configuration

src/mocks/handlers.ts

import { rest } from 'msw';
import { VITE_API_URL } from '../env';
const url: string = VITE_API_URL;

export const handlers = [
  rest.get(`${url}/users/1`, (req, res, ctx) => {
    return res(
      ctx.json({
        id: 1,
        name: 'Leanne Graham',
        username: 'Bret',
        email: 'Sincere@april.biz',
        address: {
          street: 'Kulas Light',
          suite: 'Apt. 556',
          city: 'Gwenborough',
          zipcode: '92998-3874',
          geo: { lat: '-37.3159', lng: '81.1496' },
        },
        phone: '1-770-736-8031 x56442',
        website: 'hildegard.org',
        company: {
          name: 'Romaguera-Crona',
          catchPhrase: 'Multi-layered client-server neural-net',
          bs: 'harness real-time e-markets',
        },
      }),
    );
  }),
];

src/mocks/server.ts

import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);

jest.setup.ts

import { server } from './mocks/server.js'
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

configuration

src/main.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
import { QueryClientProvider, QueryClient } from 'react-query';
import { RecoilRoot } from 'recoil';
import { BrowserRouter as Router } from 'react-router-dom';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <QueryClientProvider client={new QueryClient()}>
      <RecoilRoot>
        <Router>
          <App />
        </Router>
      </RecoilRoot>
    </QueryClientProvider>
  </React.StrictMode>,
);

src/store/user.ts

import { atom } from 'recoil';

interface UserType {
  id: number;
  name: string;
  username: string;
  email: string;
}

const userState = atom<UserType>({
  key: 'todos',
  default: {
    id: 0,
    name: '',
    username: '',
    email: '',
  },
});

export default userState;

src/pages/User.tsx

import { useEffect, useState } from 'react';
import { useGetUser } from '@/hooks/useGetUsers';
import userState from '@/store/user';
import { useSetRecoilState } from 'recoil';

export default function User() {
  const { isLoading, isError, error, data } = useGetUser(1);
  const [warning, setWarning] = useState<string>('');
  const setUser = useSetRecoilState(userState);

  useEffect(() => {
    if (data != null) {
      setUser(data);
    }

    if (isError) {
      error?.message === '404' && setWarning('404: Cannot request');
    }
  }, [isError, data]);

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return (
    <div className="App">
      <p>{warning !== '' && warning}</p>
      <p>{data?.name}</p>
    </div>
  );
}

configuration
each page is included in src/pages

src/App.tsx

import { RouteObject, useRoutes } from 'react-router-dom';
import Layout from '@/components/common/Layout';
import User from '@/pages/User';
import Home from '@/pages/Home';
import NotFound from '@/pages/NotFound';

const routes: RouteObject[] = [
  {
    path: '/',
    element: <Layout />,
    children: [
      { index: true, element: <Home></Home> },
      { path: '/user', element: <User></User> },
      { path: '*', element: <NotFound></NotFound> },
    ],
  },
];

function App() {
  const element = useRoutes(routes);

  return element;
}

export default App;

Folder Structure

Folder Structure

Libraries

Libraries

Clone this wiki locally