PR-421183: Adding Cypress and updating packages.

This commit is contained in:
Alejandro Lembke Barrientos 2023-03-02 07:57:42 -06:00
parent f3f0095674
commit a4dcc2c391
18 changed files with 2832 additions and 437 deletions

View File

@ -14,7 +14,18 @@ jobs:
registry-url: https://registry.npmjs.org/ registry-url: https://registry.npmjs.org/
- run: npm ci - run: npm ci
- run: npm test - run: npm test
cypress-run:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v5.x.x # use the explicit version number
with:
build: npm run build
start: npm start
publish-npm: publish-npm:
needs: build needs: build
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -20,6 +20,18 @@ jobs:
registry-url: https://registry.npmjs.org/ registry-url: https://registry.npmjs.org/
- run: npm ci - run: npm ci
- run: npm test - run: npm test
cypress-run:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v5.x.x # use the explicit version number
with:
build: npm run build
start: npm start
test-build-package: test-build-package:
needs: test needs: test
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -8,8 +8,9 @@ Tech(Library or Framework) | Version |
--- | --- | --- | --- |
React (Render Library) | 18.2.0 React (Render Library) | 18.2.0
Redux (Global State Management) | 4.2.1 Redux (Global State Management) | 4.2.1
React Router DOM (Routing) | 6.8.1 React Router DOM (Routing) | 6.8.2
Jest (Testing) | 29.4.3 Jest (Testing) | 29.4.3
Cypress (E2E Testing) | 12.7.0
Typescript | 4.9.5 Typescript | 4.9.5
## Setup ## Setup

12
cypress.config.ts Normal file
View File

@ -0,0 +1,12 @@
import { defineConfig } from 'cypress';
export default defineConfig({
env: {},
e2e: {
/*setupNodeEvents(on, config) {
// implement node event listeners here
},*/
baseUrl: 'http://localhost',
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
experimentalRunAllSpecs: true,
},
});

20
cypress/e2e/App.test.ts Normal file
View File

@ -0,0 +1,20 @@
describe('Initial Component Tests', () => {
it('Will show the Initial Component page.', () => {
cy.visit('/');
cy.get('a').contains('Other Component');
});
it('Will Redirect to Other Component page.', () => {
cy.visit('/');
cy.get('a').contains('Other Component').click();
cy.get('a').contains('Initial Component');
});
it('Will show the Other Component page.', () => {
cy.visit('/other-component');
cy.get('a').contains('Initial Component');
});
it('Will Redirect to Initial Component page.', () => {
cy.visit('/other-component');
cy.get('a').contains('Initial Component').click();
cy.get('a').contains('Other Component');
});
});

View File

@ -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"
}

View File

@ -0,0 +1,37 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }

20
cypress/support/e2e.ts Normal file
View File

@ -0,0 +1,20 @@
// ***********************************************************
// 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 './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@ -1,17 +1,18 @@
const { pathsToModuleNameMapper } = require("ts-jest"); const { pathsToModuleNameMapper } = require('ts-jest');
const { compilerOptions } = require("./tsconfig"); const { compilerOptions } = require('./tsconfig');
const aliases = pathsToModuleNameMapper(compilerOptions.paths, { const aliases = pathsToModuleNameMapper(compilerOptions.paths, {
prefix: "<rootDir>" prefix: '<rootDir>'
}) });
module.exports = { module.exports = {
setupFilesAfterEnv: ['<rootDir>/setupTest.ts'], setupFilesAfterEnv: ['<rootDir>/setupTest.ts'],
testEnvironment: "jsdom", testEnvironment: 'jsdom',
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
modulePathIgnorePatterns: ['<rootDir>/cypress/'],
moduleNameMapper: { moduleNameMapper: {
...aliases, ...aliases,
"\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/src/__mocks__/fileMock.ts", '\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/src/__mocks__/fileMock.ts',
"\\.(css|sass|scss|less)$": "identity-obj-proxy", '\\.(css|sass|scss|less)$': 'identity-obj-proxy',
} }
}; };

2908
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,21 @@
{ {
"name": "@aleleba/create-react-ssr", "name": "@aleleba/create-react-ssr",
"version": "3.7.0", "version": "3.8.0",
"description": "Starter Kit of server side render of react", "description": "Starter Kit of server side render of react",
"bin": "./bin/cli.js", "bin": "./bin/cli.js",
"main": "src/server/index", "main": "src/server/index",
"scripts": { "scripts": {
"start": "node build/server/app-server.js", "start": "node build/server/app-server.js",
"start:dev": "rm -rf build && webpack --mode=development --config webpack.config.dev.server.ts", "start:dev": "rm -rf build && webpack --mode=development --config webpack.config.dev.server.ts",
"start:dev-win": "(if exist build rmdir /s /Q build) && webpack --mode=development --config webpack.config.dev.server.ts",
"build": "webpack-cli --config webpack.config.ts", "build": "webpack-cli --config webpack.config.ts",
"lint": "eslint ./ --ext .js --ext .ts --ext .jsx --ext .tsx", "lint": "eslint ./ --ext .js --ext .ts --ext .jsx --ext .tsx",
"lint:fix": "eslint ./ --ext .js --ext .ts --ext .jsx --ext .tsx --fix", "lint:fix": "eslint ./ --ext .js --ext .ts --ext .jsx --ext .tsx --fix",
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"check-updates": "npx npm-check-updates -u && npm i" "check-updates": "npx npm-check-updates -u && npm i",
"cy:open": "npx cypress open",
"cy:run": "cypress run"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -41,7 +44,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"react-router-dom": "^6.8.1", "react-router-dom": "^6.8.2",
"react-router-hash-link": "^2.4.3", "react-router-hash-link": "^2.4.3",
"redux": "^4.2.1", "redux": "^4.2.1",
"webpack": "^5.75.0", "webpack": "^5.75.0",
@ -70,14 +73,14 @@
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3", "@testing-library/user-event": "^14.4.3",
"@types/jest": "^29.4.0", "@types/jest": "^29.4.0",
"@types/node": "^18.14.0", "@types/node": "^18.14.4",
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@types/webpack": "^5.28.0", "@types/webpack": "^5.28.0",
"@types/webpack-hot-middleware": "^2.25.6", "@types/webpack-hot-middleware": "^2.25.6",
"@types/webpack-node-externals": "^3.0.0", "@types/webpack-node-externals": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^5.53.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.53.0", "@typescript-eslint/parser": "^5.54.0",
"babel-jest": "^29.4.3", "babel-jest": "^29.4.3",
"babel-loader": "^9.1.2", "babel-loader": "^9.1.2",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
@ -85,7 +88,8 @@
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.3", "css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^4.2.2", "css-minimizer-webpack-plugin": "^4.2.2",
"eslint": "^8.34.0", "cypress": "^12.7.0",
"eslint": "^8.35.0",
"eslint-plugin-react": "^7.32.2", "eslint-plugin-react": "^7.32.2",
"eslint-webpack-plugin": "^4.0.0", "eslint-webpack-plugin": "^4.0.0",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
@ -101,7 +105,6 @@
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.6", "terser-webpack-plugin": "^5.3.6",
"ts-jest": "^29.0.5", "ts-jest": "^29.0.5",
"ts-loader": "^9.4.2",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"webpack-cli": "^5.0.1", "webpack-cli": "^5.0.1",

View File

@ -5,7 +5,7 @@
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
//import fetch Mock //import fetch Mock
import fetchMock from "jest-fetch-mock"; import fetchMock from 'jest-fetch-mock';
fetchMock.enableMocks(); fetchMock.enableMocks();
//Fixing Pollyfill for react-slick //Fixing Pollyfill for react-slick
@ -14,7 +14,7 @@ window.matchMedia ||
function() { function() {
return { return {
matches: false, matches: false,
addListener: function() {}, addListener: () => {/**/},
removeListener: function() {} removeListener: () => {/**/}
}; };
}; };

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
//Dependencies of Server //Dependencies of Server
import express from 'express'; import express from 'express';
import webpack from 'webpack'; import webpack from 'webpack';
@ -28,6 +29,8 @@ const { ENV, PORT, PREFIX_URL, ONLY_EXACT_PATH } = config;
const routesUrls = routes.map( route => route.path ); const routesUrls = routes.map( route => route.path );
const isWin = process.platform === 'win32';
const app = express(); const app = express();
// @ts-ignore:next-line // @ts-ignore:next-line
@ -46,13 +49,15 @@ if(ENV === 'development'){
})); }));
}else{ }else{
const baseUrl = __dirname.replace(/\/server(.*)/,''); const baseUrl = __dirname.replace(/\/server(.*)/,'');
const baseUrlWin = __dirname.replace(/\\server(.*)/,'');
const fullURL = `${baseUrl}` ; const fullURL = `${baseUrl}` ;
const fullURLWin = `${baseUrlWin}` ;
app app
.use((req, res, next) => { .use((req, res, next) => {
if(!req.hashManifest) req.hashManifest = getHashManifest(); if(!req.hashManifest) req.hashManifest = getHashManifest();
next(); next();
}) })
.use(express.static(fullURL)) .use(express.static(isWin ? fullURLWin : fullURL))
.use(helmet()) .use(helmet())
.use(helmet.permittedCrossDomainPolicies()) .use(helmet.permittedCrossDomainPolicies())
.use(helmet({ .use(helmet({
@ -60,6 +65,7 @@ if(ENV === 'development'){
directives: { directives: {
...helmet.contentSecurityPolicy.getDefaultDirectives(), ...helmet.contentSecurityPolicy.getDefaultDirectives(),
'script-src': ['\'self\'', '\'unsafe-inline\''], //"example.com" 'script-src': ['\'self\'', '\'unsafe-inline\''], //"example.com"
'connectSrc': ['\'self\'', '\'unsafe-inline\'', 'localhost:*']
}, },
}, },
})) }))
@ -72,8 +78,8 @@ const setResponse = (html, preloadedState, manifest) => {
const mainBuild = manifest ? manifest['frontend.js'] : 'assets/app.js'; const mainBuild = manifest ? manifest['frontend.js'] : 'assets/app.js';
const vendorBuild = manifest ? manifest['vendors.js'] : 'assets/vendor.js'; const vendorBuild = manifest ? manifest['vendors.js'] : 'assets/vendor.js';
const manifestJson = manifest ? `<link rel="manifest" href="${manifest['manifest.json']}">` : ''; const manifestJson = manifest ? `<link rel="manifest" href="${manifest['manifest.json']}">` : '';
const memoryFs = compiler.outputFileSystem const memoryFs = compiler.outputFileSystem;
const haveVendor = haveVendorsCss(manifest, memoryFs) const haveVendor = haveVendorsCss(manifest, memoryFs);
return(` return(`
<!DOCTYPE html> <!DOCTYPE html>

View File

@ -1,14 +1,18 @@
import fs from 'fs'; import fs from 'fs';
import { config } from '../../config'; import { config } from '../../config';
const { ENV } = config const { ENV } = config;
const isWin = process.platform === 'win32';
export const getHashManifest = () => { export const getHashManifest = () => {
try { try {
const baseUrl = __dirname.replace(/\/server(.*)/,''); const baseUrl = __dirname.replace(/\/server(.*)/,'');
const baseUrlWin = __dirname.replace(/\\server(.*)/,'');
const fullURL = `${baseUrl}/assets/manifest-hash.json`; const fullURL = `${baseUrl}/assets/manifest-hash.json`;
const readFileData = JSON.parse(fs.readFileSync(fullURL).toString()); const fullURLWin = `${baseUrlWin}\\assets\\manifest-hash.json`;
return readFileData const readFileData = isWin ? JSON.parse(fs.readFileSync(fullURLWin).toString()) : JSON.parse(fs.readFileSync(fullURL).toString());
return readFileData;
}catch(err){ }catch(err){
console.error(err); console.error(err);
} }
@ -17,12 +21,13 @@ export const getHashManifest = () => {
export const haveVendorsCss = (manifest, memoryFs) => { export const haveVendorsCss = (manifest, memoryFs) => {
try { try {
const baseUrl = __dirname.replace(/\/server(.*)/,''); const baseUrl = __dirname.replace(/\/server(.*)/,'');
const fullURL = `${baseUrl}${manifest ? `/${manifest['vendors.css']}` : '/build/assets/vendors.css'}`; const baseUrlWin = __dirname.replace(/\\server(.*)/,'');
const fullURL = `${isWin ? baseUrlWin : baseUrl}${manifest ? `/${manifest['vendors.css']}` : '/build/assets/vendors.css'}`;
ENV === 'production' && fs.readFileSync(fullURL).toString(); ENV === 'production' && fs.readFileSync(fullURL).toString();
ENV === 'development' && memoryFs.readFileSync(fullURL).toString(); ENV === 'development' && memoryFs.readFileSync(fullURL).toString();
return true return true;
}catch(err){ }catch(err){
// console.error(err); // console.error(err);
return false return false;
} }
}; };

View File

@ -51,6 +51,7 @@
"exclude": [ "exclude": [
"node_modules", "node_modules",
"build", "build",
"PRNameGenerator.ts" "PRNameGenerator.ts",
"cypress.config.ts"
] ]
} }

View File

@ -1,7 +1,7 @@
import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import webpackNodeExternals from 'webpack-node-externals'; import webpackNodeExternals from 'webpack-node-externals';
import WebpackShellPluginNext from 'webpack-shell-plugin-next'; import WebpackShellPluginNext from 'webpack-shell-plugin-next';
import { resolveTsAliases } from "resolve-ts-aliases"; import { resolveTsAliases } from 'resolve-ts-aliases';
import path from 'path'; import path from 'path';
import { Configuration } from 'webpack'; import { Configuration } from 'webpack';
const ROOT_DIR = path.resolve(__dirname); const ROOT_DIR = path.resolve(__dirname);
@ -11,7 +11,7 @@ const scriptExtensions = /\.(tsx|ts|js|jsx|mjs)$/;
const styleExtensions = /\.(css|less|styl|scss|sass|sss)$/; const styleExtensions = /\.(css|less|styl|scss|sass|sss)$/;
const fontsExtensions = /\.(eot|otf|ttf|woff|woff2)$/; const fontsExtensions = /\.(eot|otf|ttf|woff|woff2)$/;
const fontsAndImagesExtensions = /\.(png|jpg|jpeg|gif|svg|ico|mp4|avi|ttf|otf|eot|woff|woff2|pdf)$/; const fontsAndImagesExtensions = /\.(png|jpg|jpeg|gif|svg|ico|mp4|avi|ttf|otf|eot|woff|woff2|pdf)$/;
const alias = resolveTsAliases(path.resolve("tsconfig.json")); const alias = resolveTsAliases(path.resolve('tsconfig.json'));
const config: Configuration = { const config: Configuration = {
target: 'node', target: 'node',
@ -26,16 +26,12 @@ const config: Configuration = {
}, },
module: { module: {
rules: [ rules: [
{
test: /\.(tsx|ts)$/, loader: "ts-loader",
exclude: /node_modules/
},
{ {
test: scriptExtensions, test: scriptExtensions,
use: { use: {
loader: 'babel-loader', loader: 'babel-loader',
}, },
exclude: [/node_modules/, /src\/frontend/], exclude: /node_modules/,
}, },
{ {
// Preprocess our own style files // Preprocess our own style files
@ -91,4 +87,4 @@ const config: Configuration = {
], ],
}; };
export default config export default config;

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { config as envConfig } from './config'; import { config as envConfig } from './config';
@ -6,12 +7,12 @@ import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import ESLintPlugin from 'eslint-webpack-plugin'; import ESLintPlugin from 'eslint-webpack-plugin';
import CopyPlugin from 'copy-webpack-plugin'; import CopyPlugin from 'copy-webpack-plugin';
import { resolveTsAliases } from "resolve-ts-aliases"; import { resolveTsAliases } from 'resolve-ts-aliases';
const ROOT_DIR = path.resolve(__dirname); const ROOT_DIR = path.resolve(__dirname);
const resolvePath = (...args: string[]) => path.resolve(ROOT_DIR, ...args); const resolvePath = (...args: string[]) => path.resolve(ROOT_DIR, ...args);
const BUILD_DIR = resolvePath('build'); const BUILD_DIR = resolvePath('build');
const alias = resolveTsAliases(path.resolve("tsconfig.json")); const alias = resolveTsAliases(path.resolve('tsconfig.json'));
const copyPatterns = [ const copyPatterns = [
{ {
@ -26,12 +27,12 @@ const copyPatterns = [
{ {
from: `${ROOT_DIR}/../public/logo512.png`, to: '', from: `${ROOT_DIR}/../public/logo512.png`, to: '',
}, },
] ];
if(fs.existsSync(`${ROOT_DIR}/../public/img`)){ if(fs.existsSync(`${ROOT_DIR}/../public/img`)){
copyPatterns.push({ copyPatterns.push({
from: `${ROOT_DIR}/../public/img`, to: 'assets/img', from: `${ROOT_DIR}/../public/img`, to: 'assets/img',
}) });
} }
const config: Configuration = { const config: Configuration = {
@ -118,4 +119,4 @@ const config: Configuration = {
}, },
}; };
export default config export default config;

View File

@ -10,14 +10,14 @@ import { WebpackManifestPlugin } from 'webpack-manifest-plugin';
import { CleanWebpackPlugin } from 'clean-webpack-plugin'; import { CleanWebpackPlugin } from 'clean-webpack-plugin';
import ESLintPlugin from 'eslint-webpack-plugin'; import ESLintPlugin from 'eslint-webpack-plugin';
import CopyPlugin from 'copy-webpack-plugin'; import CopyPlugin from 'copy-webpack-plugin';
import { resolveTsAliases } from "resolve-ts-aliases"; import { resolveTsAliases } from 'resolve-ts-aliases';
const ROOT_DIR = path.resolve(__dirname); const ROOT_DIR = path.resolve(__dirname);
const resolvePath = (...args) => path.resolve(ROOT_DIR, ...args); const resolvePath = (...args) => path.resolve(ROOT_DIR, ...args);
const BUILD_DIR = resolvePath('build'); const BUILD_DIR = resolvePath('build');
const { InjectManifest } = require('workbox-webpack-plugin'); const { InjectManifest } = require('workbox-webpack-plugin');
const nodeExternals = require('webpack-node-externals'); const nodeExternals = require('webpack-node-externals');
const alias = resolveTsAliases(path.resolve("tsconfig.json")); const alias = resolveTsAliases(path.resolve('tsconfig.json'));
const copyPatterns = [ const copyPatterns = [
{ {
@ -33,12 +33,12 @@ const copyPatterns = [
from: `${ROOT_DIR}/public/logo512.png`, to: '', from: `${ROOT_DIR}/public/logo512.png`, to: '',
}, },
] ];
if(fs.existsSync(`${ROOT_DIR}/public/img`)){ if(fs.existsSync(`${ROOT_DIR}/public/img`)){
copyPatterns.push({ copyPatterns.push({
from: `${ROOT_DIR}/public/img`, to: 'assets/img', from: `${ROOT_DIR}/public/img`, to: 'assets/img',
}) });
} }
const frontendConfig = { const frontendConfig = {
@ -149,7 +149,7 @@ const serverConfig = {
entry: { entry: {
server: './src/server/index.ts', server: './src/server/index.ts',
}, },
target: "node", target: 'node',
externals: [nodeExternals()], externals: [nodeExternals()],
output: { output: {
path: path.resolve(__dirname, 'build'), path: path.resolve(__dirname, 'build'),