- Run
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
- Run
nvm install 16
- Run
nvm use 16
- Create
.npmrc
and add this to it:
node-version=16.14.2
engine-strict=true
package-lock=false
registry=https://registry.npmjs.org/
- Create
.gitattributes
and add this to it:
* text=auto eol=lf
- Initialize git repository or clone
- Run
npm init
- Delete the
homepage
attribute inside thepackage.json
- Run
npm i -g lerna lerna-wizard yarn
- Run
lerna-wizard
and create your package - Create
.gitignore
with the following code:
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
browser
# profiling files
chrome-profiler-events*.json
reports
# IDE - VSCode
.history/*
# compiled output
dist
lib
tmp
out-tsc
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.eslintcache
connect.lock
libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
typings
fixtures
npm-debug.log*
yarn-debug.log*
yarn-error.log*
*.tsbuildinfo
- Add these scripts to
package.json
scripts:
"bootstrap": "lerna bootstrap",
"clean:node_modules": "lerna clean && rm -Rf node_modules",
- Replace the content of
lerna.json
to:
{
"version": "0.0.0",
"private": true,
"npmClient": "yarn",
"command": {
"run": {
"npmClient": "yarn"
}
},
"useWorkspaces": true,
"packages": ["@challenge-solving/angular", "@challenge-solving/expressjs-challenges"]
}
- Add this code to
package.json
:
"workspaces": {
"packages": [
"packages/*"
],
"nohoist": []
}
- Create
.editorconfig
with these options:
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false
- Create
.vscode/settings.json
and add settings to it - Create
.vscode/extensions.json
and add extensions to it
- Run
yarn add typescript -D
- Run
yarn tsc --init
and uncomment what you need or put this code below:
{
/* Visit https://aka.ms/tsconfig.json to read more about this file */
"compileOnSave": true,
"include": ["packages/*/src/**/*"],
"exclude": [
"**/node_modules",
"**/.*",
"**/build",
"**/docs",
"**/lib",
"**/dist",
"**/reports",
"**/__mocks__"
],
"compilerOptions": {
/* Projects */
"incremental": true,
// "composite": true,
// "tsBuildInfoFile": "./",
// "disableSourceOfProjectReferenceRedirect": true,
// "disableSolutionSearching": true,
// "disableReferencedProjectLoad": true,
"disableSizeLimit": true,
/* Language and Environment */
"target": "esnext",
"lib": ["dom", "dom.iterable", "es5", "esnext"],
"jsx": "react-jsx",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
// "jsxFactory": "",
// "jsxFragmentFactory": "",
// "jsxImportSource": "react-jsx",
// "reactNamespace": "",
// "noLib": true,
"useDefineForClassFields": true,
/* Modules */
"module": "esnext",
// "rootDir": "./",
"moduleResolution": "node",
"baseUrl": "./",
// "rootDirs": [],
"typeRoots": ["node_modules/@types", "node_modules/@testing-library","packages/angular/src/@types",
"packages/expressjs-challenges/src/@types"],
"types": ["node", "jasmine", "jest", "react", "react-dom"],
// "allowUmdGlobalAccess": true,
"resolveJsonModule": true,
// "noResolve": true,
// "plugins": [
// {
// "transform": "typescript-plugin-styled-components",
// "type": "config",
// "minify": true
// }
// ],
// LERNA ONLY
"paths": {
"@code-challenges/angular": ["./packages/angular/src"],
"@code-challenges/expressjs-challenges": [
"./packages/expressjs-challenges/src"
]
},
/* JavaScript Support */
"allowJs": true,
// "checkJs": true,
// "maxNodeModuleJsDepth": 1,
/* Emit */
// "declaration": true,
// "declarationMap": true,
// "emitDeclarationOnly": true,
"sourceMap": true,
// "outFile": "./",
"outDir": "./dist",
"removeComments": true,
"noEmit": true,
"importHelpers": true,
// "importsNotUsedAsValues": "remove",
"downlevelIteration": true,
// "sourceRoot": "",
// "mapRoot": "",
"inlineSources": true,
// "emitBOM": true,
"newLine": "lf",
// "stripInternal": true,
"noEmitHelpers": true,
"noEmitOnError": true,
// "preserveConstEnums": true,
// "declarationDir": "./",
// "preserveValueImports": true,
/* Interop Constraints */
"isolatedModules": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"preserveSymlinks": true,
"forceConsistentCasingInFileNames": true,
/* Type Checking */
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
// "strictPropertyInitialization": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
// "exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
// "allowUnusedLabels": true,
// "allowUnreachableCode": true,
/* Completeness */
"skipLibCheck": true
}
}
- Run
yarn add -D prettier
- Copy and paste
.gitignore
, rename to.prettierignore
and add this code to the end:
# ESLINT ONLY
__snapshots__
- Create
.prettierrc
and add these configs to it:
{
"arrowParens": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "css",
"jsxSingleQuote": false,
"printWidth": 80,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false
}
- Run
yarn eslint --init
- Adding eslint devDependencies
yarn add -D @types/eslint-config-prettier \
@types/eslint-plugin-markdown \
@types/eslint-plugin-prettier \
@typescript-eslint/eslint-plugin \
@typescript-eslint/parser \
eslint \
eslint-config-airbnb \
eslint-config-airbnb-typescript \
eslint-config-prettier \
eslint-plugin-import \
eslint-plugin-import-helpers \
eslint-plugin-jest \
eslint-plugin-jest-react \
eslint-plugin-jsx-a11y \
eslint-plugin-markdown \
eslint-plugin-prettier \
eslint-plugin-promise \
eslint-plugin-react \
eslint-plugin-react-hooks \
lint-staged \
stylelint \
stylelint-config-prettier \
stylelint-config-recommended \
stylelint-config-standard \
stylelint-config-styled-components \
stylelint-prettier \
stylelint-processor-styled-components
- Create
.stylelintrc
and add this to it:
{
"extends": [
"stylelint-config-recommended",
"stylelint-config-prettier",
],
"ignoreFiles": ["**/*.d.ts"]
}
- Replace
.eslintrc.js
content to this:
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
jasmine: true,
jest: true,
'jest/globals': true,
},
globals: {
context: true,
document: 'readonly',
EventSource: 'readonly',
expect: true,
FormData: 'readonly',
google: true,
jsdom: true,
JSX: true,
mount: true,
mountWithRouter: true,
React: true,
shallow: true,
shallowWithRouter: true,
window: 'readonly',
},
parser: '@typescript-eslint/parser',
parserOptions: {
files: ['*.ts', '*.tsx'],
project: ['./tsconfig.json'],
tsconfigRootDir: __dirname,
ecmaFeatures: {
jsx: true,
impliedStrict: true,
},
ecmaVersion: 2021,
sourceType: 'module',
},
settings: {
react: {
createClass: 'createReactClass',
pragma: 'React',
fragment: 'Fragment',
version: 'detect',
},
propWrapperFunctions: [
'forbidExtraProps',
{ property: 'freeze', object: 'Object' },
{ property: 'myFavoriteWrapper' },
{ property: 'forbidExtraProps', exact: true },
],
componentWrapperFunctions: [
'observer',
{ property: 'styled' },
{ property: 'observer', object: 'Mobx' },
{ property: 'observer', object: '<pragma>' },
],
formComponents: ['CustomForm', { name: 'Form', formAttribute: 'endpoint' }],
linkComponents: ['Hyperlink', { name: 'Link', linkAttribute: 'to' }],
jest: {
version: require('jest/package.json').version,
},
},
extends: [
'eslint:recommended',
'airbnb',
'airbnb-typescript',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:react/all',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
'airbnb/hooks',
'plugin:jsx-a11y/recommended',
'plugin:jest/all',
'plugin:jest-react/recommended',
'prettier',
'plugin:prettier/recommended',
'plugin:promise/recommended',
'plugin:markdown/recommended',
],
plugins: [
'@typescript-eslint',
'react',
'react-hooks',
'jsx-a11y',
'jest',
'jest-react',
'import',
'import-helpers',
'prettier',
'promise',
'markdown',
],
rules: {
'@typescript-eslint/comma-dangle': 0,
'@typescript-eslint/explicit-member-accessibility': 0,
'@typescript-eslint/explicit-function-return-type': 0,
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/indent': 0,
'@typescript-eslint/no-empty-interface': 0,
'@typescript-eslint/no-extraneous-class': 0,
'@typescript-eslint/no-floating-promises': 0,
'@typescript-eslint/no-misused-promises': 0,
'@typescript-eslint/no-unused-vars': [2, { varsIgnorePattern: 'React' }],
'@typescript-eslint/no-use-before-define': 2,
curly: 2,
'implicit-arrow-linebreak': 0,
'import/order': 0,
'import/prefer-default-export': 0,
'import-helpers/order-imports': [
2,
{
newlinesBetween: 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true,
ignoreCase: true,
},
groups: ['/^react/', 'module', ['parent', 'sibling', 'index']],
},
],
'jest/no-conditional-expect': 0,
'jest/no-hooks': 0,
'jest/require-hook': 0,
'jsx-a11y/control-has-associated-label': 0,
'no-alert': 2,
'no-console': [
2,
{
allow: ['warn', 'error'],
},
],
'no-debugger': 2,
'no-inline-comments': 2,
'no-param-reassign': 2,
'no-plusplus': 0,
'no-restricted-syntax': [
2,
'ForInStatement',
'ForOfStatement',
'DoWhileStatement',
'WithStatement',
'TSEnumDeclaration',
],
'no-undef': 2,
'operator-linebreak': 0,
'prefer-template': 2,
'prettier/prettier': [
'error',
{},
{
usePrettierrc: true,
},
],
radix: 2,
'react/destructuring-assignment': 2,
'react/forbid-component-props': 0,
'react/function-component-definition': [
2,
{
namedComponents: 'arrow-function',
unnamedComponents: 'arrow-function',
},
],
'react/jsx-child-element-spacing': 0,
'react/jsx-filename-extension': [
2,
{
extensions: ['.jsx', '.tsx'],
},
],
'react/jsx-indent': 0,
'react/jsx-indent-props': 0,
'react/jsx-max-props-per-line': [2, { maximum: 1, when: 'multiline' }],
'react/jsx-props-no-spreading': 0,
'react/jsx-max-depth': [2, { max: 3 }],
'react/jsx-newline': 0,
'react/jsx-no-bind': [
2,
{
ignoreDOMComponents: false,
ignoreRefs: false,
allowArrowFunctions: true,
allowFunctions: false,
allowBind: false,
},
],
'react/jsx-no-literals': 0,
'react/jsx-one-expression-per-line': 0,
'react/jsx-props-no-spreading': 0,
'react/prop-types': 0,
'react/self-closing-comp': [
2,
{
component: true,
html: true,
},
],
'react-hooks/exhaustive-deps': 2,
'react-hooks/rules-of-hooks': 2,
'testing-library/await-async-query': 0,
'testing-library/no-await-sync-query': 0,
},
};
- Copy and paste
.gitignore
, rename to.eslintignore
and add this code to the end:
# ESLINT ONLY
__snapshots__
*.js
- Run
yarn create react-app twitter-alike-social-media --template typescript
- Replace the
dependencies
,devDependencies
and theeslintConfig
intopackage.json
to this:
"devDependencies": {
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@testing-library/user-event": "^13.2.1",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@types/react": "^17.0.20",
"@types/react-dom": "^17.0.9",
"typescript": "^4.4.2",
},
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "5.0.0",
"web-vitals": "^2.1.0"
},
- Run
yarn add styled-components postcss-syntax @stylelint/postcss-css-in-js
and then the scripts below:
yarn add -D @babel/core \
@babel/plugin-transform-react-jsx \
@babel/preset-env \
@babel/preset-react \
@babel/preset-typescript \
@types/styled-components \
@types/styled-theming \
babel-jest \
babel-plugin-styled-components \
jest-styled-components \
styled-normalize \
styled-theming \
stylelint-config-styled-components \
stylelint-processor-styled-components \
typescript-plugin-styled-components
- Update
.stylelintrc
to this:
{
"processors": ["stylelint-processor-styled-components"],
"extends": [
"stylelint-config-standard",
"stylelint-config-prettier",
"stylelint-config-styled-components"
],
"customSyntax": "@stylelint/postcss-css-in-js",
"ignoreFiles": ["**/*.d.ts", "**/*.tsx"],
"rules": {
"property-no-vendor-prefix": null
}
}
- Update
babel.config.js
to this:
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
['@babel/preset-typescript', { targets: { node: 'current' } }],
[
'@babel/preset-react',
{ targets: { node: 'automatic' }, runtime: 'automatic' },
],
],
plugins: [
[
'@babel/plugin-transform-react-jsx',
{
throwIfNamespace: true,
runtime: 'automatic',
importSource: 'react',
},
],
[
'@babel/plugin-transform-runtime',
{
regenerator: true,
},
],
[
'babel-plugin-styled-components',
{
namespace: require('./package.json').name,
displayName: true,
fileName: false,
pure: true,
},
],
],
};
- Update the
lint
script intopackage.json
to this:
"lint:ts": "eslint --quiet --fix . -c ./.eslintrc.js",
"lint:css": "stylelint ./src/**/*.styles.ts --config .stylelintrc",
"lint": "yarn lint:ts && yarn lint:css",
"lint:report": "yarn lint -f json -o reports/eslint-report.json",
- Uncomment the code below into tsconfig.json:
"plugins": [
{
"transform": "typescript-plugin-styled-components",
"type": "config",
"minify": true
}
],
-
Delete
src/App.css
andsrc/index.css
and ther respective imports insrc/App.tsx
andsrc/index.tsx
. -
Update the
src/App.tsx
file to this code:
import { FC } from 'react';
export const App: FC = (): JSX.Element => {
return (
<div>
Home page
</div>
);
}
- Add the normalize to
src/index.tsx
by updating the code to this:
import { StrictMode } from 'react';
import ReactDOM from 'react-dom';
import { Normalize } from 'styled-normalize';
import { App } from './App';
ReactDOM.render(
<StrictMode>
<Normalize />
<App />
</StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
// reportWebVitals();
- Create the file
src/Theme/Types/CustomTheme.types.ts
and add this code:
import { Borders } from './Borders.types';
import { Colors } from './Colors.types';
import { Typographies } from './Typographies.types';
export const availableThemeModes = ['dark', 'deepSpace'] as const;
export type ThemeMode = typeof availableThemeModes[number];
export interface CustomThemeType {
mode: ThemeMode;
colors: Colors;
borders: Borders;
typographies: Typographies;
//! To be implemented
opacities: undefined;
shadows: undefined;
spacings: undefined;
acessibilities: undefined;
animations: undefined;
}
-
In the same folder, create the files
Borders.types.ts
,Colors.types.ts
,Typographies.types.ts
,Sizes.types.ts
with their respectively types. -
In the folder
src/Theme/Modes
, create the filesDark.theme.ts
,DeepSpace.theme.ts
,Futuristic.theme.ts
,Light.theme.ts
with their respectively theme values. -
Create the file
src/globals.d.ts
and add this code:
import 'styled-components';
import { CustomThemeType } from './Theme/Types';
declare module 'styled-components' {
export interface DefaultTheme extends CustomThemeType {}
}
- Create the file
src/Theme/CustomThemeProvider/CustomThemeProvider.types.ts
and add this code:
import { CustomThemeType, ThemeMode } from '../Types';
export type ThemeContextType = {
theme: CustomThemeType;
switchTheme: (themeMode: ThemeMode) => void;
};
export interface RequiredCustomThemeProviderProps {
children?: React.ReactNode;
}
export interface DefaultCustomThemeProviderProps {
testID?: string;
themeName?: ThemeMode;
}
export type CustomThemeProviderProps = RequiredCustomThemeProviderProps &
DefaultCustomThemeProviderProps;
- Create the file
src/Theme/CustomThemeProvider/CustomThemeProvider.tsx
and add this code:
import { useMemo, useState, createContext } from 'react';
import { BrowserRouter } from 'react-router-dom';
import { ThemeProvider } from 'styled-components';
import { darkTheme } from '../Modes';
import { ThemeMode, CustomThemeType } from '../Types';
import {
ThemeContextType,
CustomThemeProviderProps,
DefaultCustomThemeProviderProps,
} from './CustomThemeProvider.types';
export const themes: Record<ThemeMode | 'default', CustomThemeType> = {
default: darkTheme,
dark: darkTheme,
};
export const ThemeContext = createContext<
ThemeContextType | Record<string, never>
>({});
export const customThemeProviderDefaults: Required<DefaultCustomThemeProviderProps> =
{
testID: 'ThemeContextProvider',
themeName: 'dark',
};
export const CustomThemeProvider: React.FC<CustomThemeProviderProps> = (
props
): JSX.Element => {
const {
testID = customThemeProviderDefaults.testID,
children,
themeName = customThemeProviderDefaults.themeName,
} = props;
const [theme, setTheme] = useState<CustomThemeType>(themes[themeName]);
const switchTheme = (themeMode: ThemeMode): void => {
setTheme(themes[themeMode]);
};
const providerValue = useMemo(() => ({ theme, switchTheme }), [theme]);
return (
<ThemeContext.Provider data-testid={testID} value={providerValue}>
<ThemeProvider theme={theme}>
<BrowserRouter>{children}</BrowserRouter>
</ThemeProvider>
</ThemeContext.Provider>
);
};
- Create the file
src/Theme/CustomThemeProvider/index.ts
and add this code:
export * from './CustomThemeProvider';
- Create the file
src/App.styles.ts
and add this code:
import styled, { createGlobalStyle } from 'styled-components';
export const GlobalStyle = createGlobalStyle`
body {
overflow: hidden;
}
`;
export const ContainerScroll = styled.div`
overflow-x: hidden;
overflow-y: scroll;
&::-webkit-scrollbar {
width: 10px;
}
&::-webkit-scrollbar-thumb {
background: ${({ theme }) => theme.colors.background.default.normal};
}
&::-webkit-scrollbar-track {
background: ${({ theme }) => theme.colors.background.default.light};
}
&::-webkit-scrollbar-thumb:hover {
background: ${({ theme }) => theme.colors.background.default.darker}cc;
}
&::-webkit-scrollbar-track:hover {
background: ${({ theme }) => theme.colors.background.default.light}cc;
}
`;
export const Container = styled.div`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
background-color: ${({ theme }) => theme.colors.background.default.darkest};
overflow-x: hidden;
overflow-y: hidden;
`;
export const PageContainer = styled(ContainerScroll)`
padding: 0 2rem;
height: 100vh;
`;
- Replace the code of the file
src/App.tsx
with this code:
import { FC } from 'react';
import { Container, GlobalStyle } from './App.styles';
import { CustomThemeProvider } from './Theme/CustomThemeProvider';
export const App: FC = (): JSX.Element => {
return (
<CustomThemeProvider>
<GlobalStyle />
<Container data-testid="container">
hello world!
</Container>
</CustomThemeProvider>
);
};
- Delete the files
src/logo.svg
,src/App.css
,index.css
andsetupTests.ts
.
- Run the following command:
yarn add -D @testing-library/jest-dom \
@testing-library/react \
@testing-library/user-event \
@types/jest \
@types/node \
@types/node-notifier \
@types/prettier \
@types/react-test-renderer \
babel-jest \
identity-obj-proxy \
jest \
jest-config \
jest-styled-components \
jest-svg-transformer \
jest-transform-stub \
node-notifier \
react-test-renderer
- Replace the
test
script in package.json to:
"test": "jest --config=jest.config.js",
"test:dev": "jest --config=jest.config.js --watchAll",
- Create the
jest.config.js
file and copy and paste the code below, or runjest --init
to create and configure it manually:
const { defaults } = require('jest-config');
module.exports = {
...defaults,
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/tmp/jest_rt",
// Automatically clear mock calls, instances and results before every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: 'coverage',
// An array of regexp pattern strings used to skip coverage collection
coveragePathIgnorePatterns: ['/node_modules/'],
// Indicates which provider should be used to instrument code for coverage
// coverageProvider: 'babel',
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: ['json', 'text', 'lcov', 'clover'],
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: ['<rootDir>/node_modules', '<rootDir>/src'],
// An array of file extensions your modules use
// moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'node'],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
moduleNameMapper: {
'.+\\.(css|styl|less|sass|scss)$': `identity-obj-proxy`,
'.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'jest-transform-stub',
},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
notify: true,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: 'babel-jest',
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
roots: ['<rootDir>/src'],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: 'jest-runner',
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: ['dotenv/config'],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: 'jsdom',
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)'],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
transform: {
'\\.((j|t)?s(x)?)?$': 'babel-jest',
},
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
verbose: true,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};
- Create the file
src/Config/Tests/GlobalSetup.config.tsx
and copy and paste the following code:
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as React from 'react';
import { createElement, FC } from 'react';
import { create } from 'react-test-renderer';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { CustomThemeProvider } from '../../Theme/CustomThemeProvider';
const renderJestDomCreator = <ComponentProps extends Record<string, any>>(
componentReference: FC<ComponentProps>,
props: ComponentProps
) =>
render(createElement(componentReference, { ...props }), {
wrapper: CustomThemeProvider,
});
const renderRTRCreator = <ComponentProps extends Record<string, any>>(
componentReference: FC<ComponentProps>,
props: ComponentProps
) =>
create(
<CustomThemeProvider>
{createElement(componentReference, { ...props })}
</CustomThemeProvider>
);
global.React = React;
export * from '@testing-library/react';
export { renderJestDomCreator, renderRTRCreator };
- Create the file
src/Config/Tests/test.types.ts
and add the following code:
export interface TestProps {
testID?: string;
}
- Create the file
src/App.test.tsx
and add the following code:
import { App } from './App';
import {
renderRTRCreator,
renderJestDomCreator,
screen,
} from './Config/Tests/GlobalSetup.config';
import { themes } from './Theme/CustomThemeProvider';
import { hexToRgb } from './Utils/Transform/hexToRgb.util';
describe('App component tests', () => {
const setup = () => {
const renderRTR = () => renderRTRCreator(App, {});
const renderJestDom = () => renderJestDomCreator(App, {});
return { renderRTR, renderJestDom };
};
describe(`behavior tests`, () => {
it(`should render the Typography component`, () => {
setup().renderJestDom();
const testInstance = screen.getByTestId('container');
expect(testInstance).toBeTruthy();
});
});
describe(`style tests`, () => {
it(`should have style the Container component`, () => {
setup().renderJestDom();
const container = screen.getByTestId('container');
expect(container).toHaveStyle({
backgroundColor: hexToRgb(
themes.default.colors.background.default.darkest
),
width: '100%',
height: '100%',
overflowX: 'hidden',
});
});
});
});
- Run
yarn add -D scaffdog
- Run
yarn scaffdog init
and then entercomponent
- Replace the content of
.scaffdog/component.template.md
with this:
---
name: 'component'
root: '.'
output: 'src/Components/'
ignore: ['.', 'src/Components/**/*']
questions:
value: 'Please, enter the component name:'
---
# `{{ inputs.value | pascal }}/index.ts`
```ts:
export * from './{{ inputs.value | pascal }}.types';
export * from './{{ inputs.value | pascal }}';
```
# `{{ inputs.value | pascal }}/{{ inputs.value | pascal }}.types.ts`
```ts:
import { TestProps } from '../../Config/Tests/Test.types';
import { TypographyVariant } from '../../Theme/Types/Typographies.types';
export interface Required{{ inputs.value | pascal }}Props {
/**
* Component's children.
*/
children: React.ReactNode;
}
export interface Default{{ inputs.value | pascal }}Props {
/**
* Sets the component variant. It changes the HTML tag and the styles.
* @default 'body1'.
*/
variant?: TypographyVariant;
}
export interface Optional{{ inputs.value | pascal }}Props {
/**
* Sets the component styles.
*/
style?: React.CSSProperties;
}
export type {{ inputs.value | pascal }}Props = Required{{ inputs.value | pascal }}Props &
Default{{ inputs.value | pascal }}Props &
Optional{{ inputs.value | pascal }}Props &
TestProps &
Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'>;
export type {{ inputs.value | pascal }}StyleProps = Required<Default{{ inputs.value | pascal }}Props>;
```
# `{{ inputs.value | pascal }}/{{ inputs.value | pascal }}.tsx`
```tsx:
import { TestProps } from '../../Config/Tests/Test.types';
import { typographyVariantToTag } from '../../Theme/Types/Typographies.types';
import { {{ inputs.value | pascal }}Container } from './{{ inputs.value | pascal }}.styles';
import { {{ inputs.value | pascal }}Props, Default{{ inputs.value | pascal }}Props } from './{{ inputs.value | pascal }}.types';
export const {{ inputs.value | camel }}Defaults: Required<Default{{ inputs.value | pascal }}Props> &
Required<TestProps> = {
testID: '{{ inputs.value | pascal }}',
variant: 'body1',
};
export const {{ inputs.value | pascal }}: React.FC<{{ inputs.value | pascal }}Props> = (props): JSX.Element => {
const {
testID = {{ inputs.value | camel }}Defaults.testID,
children,
variant = {{ inputs.value | camel }}Defaults.variant,
...others
} = props;
return (
<{{ inputs.value | pascal }}Container
as={typographyVariantToTag[variant]}
data-testid={testID}
style={style}
variant={variant}
{...others}
>
{children}
</{{ inputs.value | pascal }}Container>
);
};
```
# `{{ inputs.value | pascal }}/{{ inputs.value | pascal }}.styles.ts`
```ts:
import styled from 'styled-components';
import { {{ inputs.value | pascal }}StyleProps } from './{{ inputs.value | pascal }}.types';
export const {{ inputs.value | pascal }}Container = styled.span<{{ inputs.value | pascal }}StyleProps>`
color: ${({ theme }) => theme.colors.font.default};
${({ variant, theme }) => theme.typographies[variant]};
`;
```
# `{{ inputs.value | pascal }}/{{ inputs.value | pascal }}.test.tsx`
```tsx:
import {
renderJestDomCreator,
renderRTRCreator,
screen,
} from '../../Config/Tests/GlobalSetup.config';
import { themes } from '../../Theme/CustomThemeProvider';
import { hexToRgb } from '../../Utils/Transform';
import { {{ inputs.value | pascal }}, {{ inputs.value | camel }}Defaults } from './{{ inputs.value | pascal }}';
import { Required{{ inputs.value | pascal }}Props, {{ inputs.value | pascal }}Props } from './{{ inputs.value | pascal }}.types';
describe('{{ inputs.value | pascal }} component tests', () => {
const text = 'text';
const newVariant = 'h1';
const requiredProps: Required{{ inputs.value | pascal }}Props = {
children: text,
};
const setup = (props?: {{ inputs.value | pascal }}Props) => {
const renderRTR = () =>
renderRTRCreator<{{ inputs.value | pascal }}Props>({{ inputs.value | pascal }}, {
...requiredProps,
...props,
});
const renderJestDom = () =>
renderJestDomCreator<{{ inputs.value | pascal }}Props>({{ inputs.value | pascal }}, {
...requiredProps,
...props,
});
return { renderRTR, renderJestDom };
};
describe('behavior tests', () => {
it(`should render the component`, () => {
expect.assertions(1);
setup().renderJestDom();
const testInstance = screen.getByTestId({{ inputs.value | camel }}Defaults.testID);
expect(testInstance).toBeTruthy();
});
it(`should render the text`, () => {
expect.assertions(1);
setup().renderJestDom();
const element = screen.getByText(text);
expect(element).toBeInTheDocument();
});
it(`should render '${ {{ inputs.value | camel }}Defaults.variant}' as the default variant`, () => {
expect.assertions(1);
const instance =
setup().renderRTR().root;
const element = instance.findByProps({
variant: {{ inputs.value | camel }}Defaults.variant,
});
expect(element).toBeTruthy();
});
it(`should override the default variant when it is passed as prop`, () => {
expect.assertions(1);
const instance =
setup({
...requiredProps,
variant: newVariant,
}).renderRTR().root;
const element = instance.findByProps({ variant: newVariant });
expect(element).toBeTruthy();
});
});
describe('style tests', () => {
it(`should have style the Container component`, () => {
expect.assertions(1);
setup().renderJestDom();
const container = screen.getByTestId({{ inputs.value | camel }}Defaults.testID);
expect(container).toHaveStyle({
color: hexToRgb(themes.default.colors.font.default),
...themes.default.typographies[{{ inputs.value | camel }}Defaults.variant],
});
});
});
describe('snapshot tests', () => {
it(`should render correctly`, () => {
expect.assertions(1);
const generatedJson = setup().renderRTR().toJSON();
expect(generatedJson).toMatchSnapshot();
});
});
});
```
- Create the file
.scaffdog/apiConsumerService.template.md
with this:
---
name: 'API consumer service'
root: '.'
output: 'src/Services/'
ignore: ['.', 'src/Services/**/*']
questions:
value: 'Please, enter the service name:'
---
# `{{ inputs.value | pascal }}/{{ inputs.value | pascal }}.types.ts`
```ts:
export interface {{ inputs.value | pascal }} {
id: number;
albumId: number;
title: string;
url: string;
thumbnailUrl: string;
}
```
# `{{ inputs.value | pascal }}/{{ inputs.value | pascal }}.service.ts`
```ts:
import axios from 'axios';
import { API_URL } from '../../Config/constants';
import { requestErrorHandler } from '../ErrorHandler.service';
import { {{ inputs.value | pascal }} } from './{{ inputs.value | pascal }}.types';
const getAll = async () => {
const {{ inputs.value | camel }}s = await axios
.get<{{ inputs.value | pascal }}[]>(`${API_URL}/{{ inputs.value | camel }}s`)
.then((res) => res.data)
.catch(requestErrorHandler);
return {{ inputs.value | camel }}s;
};
const getByAlbumId = async (albumId: number) => {
const {{ inputs.value | camel }}s = await axios
.get<{{ inputs.value | pascal }}[]>(`${API_URL}/albums/${albumId}/{{ inputs.value | camel }}s`)
.then((res) => res.data)
.catch(requestErrorHandler);
return {{ inputs.value | camel }}s;
};
export const {{ inputs.value | camel }}Service = {
getAll,
getByAlbumId,
};
```
# `{{ inputs.value | pascal }}/{{ inputs.value | pascal }}.service.test.ts`
```ts:
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { API_URL, APPLICATION_ERROR } from '../../Config/constants';
import { {{ inputs.value | camel }}Service } from './{{ inputs.value | pascal }}.service';
import { {{ inputs.value | pascal }} } from './{{ inputs.value | pascal }}.types';
describe('{{ inputs.value | pascal }} service tests', () => {
global.console.error = jest.fn();
const {{ inputs.value | camel }}sUrl = `${API_URL}/{{ inputs.value | camel }}s`;
const get{{ inputs.value | pascal }}sByAlbumIdUrl = (id: number | string) =>
`${API_URL}/albums/${id}/{{ inputs.value | camel }}s`;
const unexistentAlbumId = 9999;
const {{ inputs.value | camel }}sData: {{ inputs.value | pascal }}[] = [
{
albumId: 1,
id: 1,
title: 'accusamus beatae ad facilis cum similique qui sunt',
url: 'https://via.placeholder.com/600/92c952',
thumbnailUrl: 'https://via.placeholder.com/150/92c952',
},
{
albumId: 2,
id: 51,
title: 'non sunt voluptatem placeat consequuntur rem incidunt',
url: 'https://via.placeholder.com/600/8e973b',
thumbnailUrl: 'https://via.placeholder.com/150/8e973b',
},
{
albumId: 3,
id: 101,
title: 'incidunt alias vel enim',
url: 'https://via.placeholder.com/600/e743b',
thumbnailUrl: 'https://via.placeholder.com/150/e743b',
},
];
const filterDataByAlbumId = (id: number) =>
{{ inputs.value | camel }}sData.filter(({{ inputs.value | camel }}) => {{ inputs.value | camel }}.albumId === id);
const server = setupServer(
rest.get({{ inputs.value | camel }}sUrl, (_req, res, ctx) => res(ctx.json({{ inputs.value | camel }}sData))),
rest.get(get{{ inputs.value | pascal }}sByAlbumIdUrl(1), (_req, res, ctx) =>
res(ctx.json(filterDataByAlbumId(1)))
),
rest.get(get{{ inputs.value | pascal }}sByAlbumIdUrl(2), (_req, res, ctx) =>
res(ctx.json(filterDataByAlbumId(2)))
),
rest.get(get{{ inputs.value | pascal }}sByAlbumIdUrl(3), (_req, res, ctx) =>
res(ctx.json(filterDataByAlbumId(3)))
),
rest.get(get{{ inputs.value | pascal }}sByAlbumIdUrl(unexistentAlbumId), (_req, res, ctx) =>
res(ctx.json([]))
)
);
const serverError = setupServer(
rest.get({{ inputs.value | camel }}sUrl, (_req, res, ctx) => res(ctx.status(500))),
rest.get(get{{ inputs.value | pascal }}sByAlbumIdUrl(1), (_req, res, ctx) =>
res(ctx.status(500))
),
rest.get(get{{ inputs.value | pascal }}sByAlbumIdUrl(2), (_req, res, ctx) =>
res(ctx.status(500))
),
rest.get(get{{ inputs.value | pascal }}sByAlbumIdUrl(3), (_req, res, ctx) =>
res(ctx.status(500))
),
rest.get(get{{ inputs.value | pascal }}sByAlbumIdUrl(unexistentAlbumId), (_req, res, ctx) =>
res(ctx.status(500))
)
);
describe('Successful tests', () => {
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
it(`should fetch data successfully from '${ {{ inputs.value | camel }}sUrl}'`, async () => {
const {{ inputs.value | camel }}s = await {{ inputs.value | camel }}Service.getAll();
expect({{ inputs.value | camel }}s).toEqual({{ inputs.value | camel }}sData);
});
it(`should fetch filtered data successfully from '${get{{ inputs.value | pascal }}sByAlbumIdUrl(
'{albumId}'
)}`, async () => {
const {{ inputs.value | camel }}s1 = await {{ inputs.value | camel }}Service.getByAlbumId(1);
expect({{ inputs.value | camel }}s1).toEqual(filterDataByAlbumId(1));
const {{ inputs.value | camel }}s2 = await {{ inputs.value | camel }}Service.getByAlbumId(2);
expect({{ inputs.value | camel }}s2).toEqual(filterDataByAlbumId(2));
const {{ inputs.value | camel }}s3 = await {{ inputs.value | camel }}Service.getByAlbumId(3);
expect({{ inputs.value | camel }}s3).toEqual(filterDataByAlbumId(3));
});
it(`should fetch no data successfully from '${get{{ inputs.value | pascal }}sByAlbumIdUrl(
'{albumId}'
)} when '{albumId}' doesn't exists`, async () => {
const {{ inputs.value | camel }}s = await {{ inputs.value | camel }}Service.getByAlbumId(unexistentAlbumId);
expect({{ inputs.value | camel }}s).toEqual(filterDataByAlbumId(unexistentAlbumId));
});
});
describe('Unsuccessful tests', () => {
beforeAll(() => serverError.listen());
afterEach(() => serverError.resetHandlers());
afterAll(() => serverError.close());
it(`should throw error when server returns status 500 when trying to fetch '${ {{ inputs.value | camel }}sUrl}'`, async () => {
try {
await {{ inputs.value | camel }}Service.getAll();
} catch (e) {
expect(() => {
throw new Error(APPLICATION_ERROR);
}).toThrow(Error);
}
});
it(`should throw error when server returns status 500 when trying to fetch '${get{{ inputs.value | pascal }}sByAlbumIdUrl(
'{albumId}'
)}`, async () => {
try {
await {{ inputs.value | camel }}Service.getByAlbumId(1);
} catch (e) {
expect(() => {
throw new Error(APPLICATION_ERROR);
}).toThrow(Error);
}
try {
await {{ inputs.value | camel }}Service.getByAlbumId(2);
} catch (e) {
expect(() => {
throw new Error(APPLICATION_ERROR);
}).toThrow(Error);
}
try {
await {{ inputs.value | camel }}Service.getByAlbumId(3);
} catch (e) {
expect(() => {
throw new Error(APPLICATION_ERROR);
}).toThrow(Error);
}
});
});
});
```
- Create the file
.scaffdog/page.template.md
with this:
---
name: 'page'
root: '.'
output: 'src/Pages/'
ignore: ['.', 'src/Pages/**/*']
questions:
value: 'Please, enter the page name:'
---
# `{{ inputs.value | pascal }}/index.ts`
```ts:
export * from './{{ inputs.value | pascal }}';
```
# `{{ inputs.value | pascal }}/{{ inputs.value | pascal }}.tsx`
```tsx:
import { PageContainer } from '../../App.styles';
import { TestProps } from '../../Config/Tests/Test.types';
export const {{ inputs.value | camel }}Defaults: Required<TestProps> = {
testID: '{{ inputs.value | pascal }}',
};
export const {{ inputs.value | pascal }}: React.FC = (): JSX.Element => {
return (
<PageContainer data-testid={ {{ inputs.value | camel }}Defaults.testID}>
teste
</PageContainer>
);
};
```
# `{{ inputs.value | pascal }}/{{ inputs.value | pascal }}.styles.ts`
```ts:
import styled from 'styled-components';
export const {{ inputs.value | pascal }}Container = styled.div`
color: ${({ theme }) => theme.colors.font.default};
`;
```
- Run
yarn add -D commitizen cz-conventional-changelog husky
- Run
yarn commitizen init cz-conventional-changelog --yarn --dev --exact
- Add these scripts to package.json scripts:
"prepare": "husky install",
"lint:ts": "eslint --quiet --fix . -c ./.eslintrc.js",
"lint:css": "stylelint ./src/**/*.styles.ts --config .stylelintrc",
"lint": "yarn lint:ts && yarn lint:css",
"lint:report": "yarn lint -f json -o reports/eslint-report.json",
"type-check": "tsc --noEmit",
"format": "prettier --config .prettierrc --write .",
- Create
.czrc
file and add{ "path": "cz-conventional-changelog" }
to it - Run
yarn husky add .husky/prepare-commit-msg "exec < /dev/tty && node_modules/.bin/cz --hook || true"
- Create
.lintstagedrc.json
and add:
{
"*.{ts,tsx}": ["yarn lint", "bash -c 'yarn type-check'"],
"*.{js,jsx,ts,tsx,css,scss,sass,md,html,json}": "yarn format"
}
- Run
yarn husky add .husky/pre-commit "yarn test"
- Run
yarn husky add .husky/pre-commit "node_modules/.bin/lint-staged"
- Run
npm i -g @angular/cli
- Run
ng new challenge-solving-platform --create-application false --force
- Run
lerna-wizard
to create theangular
package - Update "name" in package.json from "angular" to "@challenge-solving/angular"
- Run
cd packages/angular && ng new angular
with the desired parameters - Copy these files/folders to root:
.browserlistrc
,.vscode/launch.json
,.vscode/tasks.json
- Run
lerna-wizard
to create theexpressjs-challenges
package - Update "name" in package.json from "expressjs-challenges" to "@challenge-solving/expressjs-challenges"
- Run
cd ../../packages/expressjs-challenges && npx express-generator --no-view --git --force