LFFE-14 Merge branch 'feature/LFFE-14_testing_config' into 'develop'

This commit is contained in:
AmirHossein Mahmoodi
2023-11-05 08:31:45 +00:00
20 changed files with 372 additions and 32 deletions

81
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,81 @@
image: node:latest
cache:
paths:
- node_modules
stages:
- lint
- test
- test:build
- deploy
merge:test:lint:
stage: lint
script:
- npm install
- cp example.env.local .env.local
- cp example.env.local .env.test
- npm run lint
only:
- merge_requests
merge:test:jest:
stage: test
script:
- npm install
- cp example.env.local .env.local
- cp example.env.local .env.test
- npm run test
only:
- merge_requests
#merge:test:build:
# stage: test:build
# script:
# - npm install
# - cp example.env.local .env.local
# - cp example.env.local .env.test
# - npm run build
# only:
# - merge_requests
test:lint:
stage: lint
script:
- npm install
- cp example.env.local .env.local
- cp example.env.local .env.test
- npm run lint
only:
- develop
- main
test:jest:
stage: test
script:
- npm install
- cp example.env.local .env.local
- cp example.env.local .env.test
- npm run test
only:
- develop
- main
#test:build:
# stage: test:build
# script:
# - npm install
# - cp example.env.local .env.local
# - cp example.env.local .env.test
# - npm run build
# only:
# - develop
# - main
deploy:
stage: deploy
script:
- echo "Run deploy project"
only:
- main

16
cypress.config.js Normal file
View File

@@ -0,0 +1,16 @@
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
component: {
devServer: {
framework: "next",
bundler: "webpack",
},
},
});

View File

@@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js 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) => { ... })

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
<!-- Used by Next.js to inject CSS. -->
<div id="__next_css__DO_NOT_USE__"></div>
</head>
<body>
<div data-cy-root></div>
</body>
</html>

View File

@@ -0,0 +1,27 @@
// ***********************************************************
// This example support/component.js 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')
import { mount } from 'cypress/react18'
Cypress.Commands.add('mount', mount)
// Example use:
// cy.mount(<MyComponent />)

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

@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.js 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

@@ -7,6 +7,7 @@ NEXT_PUBLIC_PRIMARY_MAIN = "#084070"
NEXT_PUBLIC_SECONDARY_MAIN = "#FF4E00"
NEXT_PUBLIC_BASE_URL = "https://loan.witel.ir"
NEXT_PUBLIC_POWERED_BY_URL = "https://witel.ir"
#["NEXT_PUBLIC_HAS_WIDGET" , "NEXT_PUBLIC_HAS_NOTIFICATION"]
NEXT_PUBLIC_HAS_VALUE=["NEXT_PUBLIC_HAS_NOTIFICATION"]

17
jest.config.mjs Normal file
View File

@@ -0,0 +1,17 @@
import nextJest from 'next/jest.js'
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
})
// Add any custom config to be passed to Jest
/** @type {import('jest').Config} */
const config = {
// Add more setup options before each test is run
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
}
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
export default createJestConfig(config)

22
jest.setup.js Normal file
View File

@@ -0,0 +1,22 @@
import '@testing-library/jest-dom'
import {server} from "./mocks/server";
import mockRouter from 'next-router-mock'
jest.mock('next/router', () => jest.requireActual('next-router-mock'))
beforeAll(() => {
server.listen()
})
beforeEach(() => {
localStorage.clear();
mockRouter.query = {}
})
afterEach(() => {
server.resetHandlers()
})
afterAll(() => {
server.close()
})

13
mocks/AppWithProvider.jsx Normal file
View File

@@ -0,0 +1,13 @@
import App from "@/pages/_app";
import fa from "&/locales/fa/app.json"
const translations = {fa};
const MockAppWithProviders = ({children, locale, title, isBot}) => {
const pageProps = {
title: title || '', isBot: isBot || true, locale: locale || 'fa', messages: translations[locale || 'fa']
}
return (<App Component={() => (<>{children}</>)} pageProps={pageProps}/>)
}
export default MockAppWithProviders

5
mocks/handler.js Normal file
View File

@@ -0,0 +1,5 @@
import {userHandler} from "./handlers/user";
export const handler = [
...userHandler
];

19
mocks/handlers/user.js Normal file
View File

@@ -0,0 +1,19 @@
import {rest} from "msw";
import {GET_USER_ROUTE} from "@/core/data/apiRoutes";
export const userHandler = [
rest.get(GET_USER_ROUTE, (req, res, ctx) => {
return res(ctx.json({
data: {
id: 10,
full_name: "Witel Company",
position: "Software Engineer",
permissions: [
"manage_users",
"manage_roles",
"manage_boss"
],
}
}))
}),
]

4
mocks/server.js Normal file
View File

@@ -0,0 +1,4 @@
import {setupServer} from "msw/node";
import {handler} from "./handler";
export const server = setupServer(...handler)

View File

@@ -10,7 +10,11 @@
"lint": "next lint",
"update": "run-script-os",
"update:win32": "echo 'ok'",
"update:linux": "rm -rf .next/ && npm i && npm run build && sudo systemctl restart loan_facilities_develop_expert.service"
"update:linux": "rm -rf .next/ && npm i && npm run build && sudo systemctl restart loan_facilities_develop_expert.service",
"test": "jest",
"test:watch": "jest --watchAll",
"cypress:open": "cypress open",
"cypress:run": "cypress run"
},
"dependencies": {
"@emotion/react": "^11.10.6",
@@ -50,7 +54,17 @@
},
"devDependencies": {
"@faker-js/faker": "^7.6.0",
"eslint-config-next": "^13.3.0",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
"cypress": "^13.4.0",
"eslint-config-next": "^13.5.6",
"eslint-plugin-jest-dom": "^5.1.0",
"eslint-plugin-testing-library": "^6.1.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"msw": "^1.3.1",
"next-router-mock": "^0.9.10",
"run-script-os": "^1.1.6"
}
}

View File

@@ -2,6 +2,7 @@
"app_name": "سامانه جامع تسهیلات",
"app_short_name": "سامانه تسهیلات",
"dashboard": "داشبورد",
"powered_by_witel": "توسعه یافته توسط وایتل",
"first_page": "خوش آمدید",
"login": "ورود",
"login_expert": "ورود کارشناس",

View File

@@ -0,0 +1,61 @@
import {render, screen, waitFor} from "@testing-library/react";
import FirstComponent from "@/components/first";
import MockAppWithProviders from "../../../../mocks/AppWithProvider";
import {server} from "../../../../mocks/server";
import {rest} from "msw";
import {GET_USER_ROUTE} from "@/core/data/apiRoutes";
describe("First Component From First Page", () => {
describe("Rendering", () => {
it("App Name Text Rendered", () => {
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
const appNameElement = screen.queryByText(/سامانه جامع تسهیلات/i);
expect(appNameElement).toBeInTheDocument()
});
it("App version Text Rendered", () => {
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
const versionControler = screen.queryByText(process.env.NEXT_PUBLIC_API_VERSION, {exact: false});
expect(versionControler).toBeInTheDocument()
});
it("Powered By Rendered With Currect URL", () => {
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
const linkElement = screen.queryByText('توسعه یافته توسط وایتل');
expect(linkElement).toBeInTheDocument()
expect(linkElement).toHaveAttribute('href', process.env.NEXT_PUBLIC_POWERED_BY_URL);
});
});
describe("Behavioral", () => {
it("Show Login Button And Do Not Show Dashboard Button When User Is Not Authenticated", async () => {
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
await waitFor(() => {
const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i)
const authenticationButtonDashboard = screen.queryByText(/داشبورد/i)
expect(authenticationButtonLogin).toBeInTheDocument()
expect(authenticationButtonDashboard).not.toBeInTheDocument()
})
});
it("Show Dashboard Button And Do Not Show Login Button When User Is Authenticated", async () => {
localStorage.setItem("_token", 'token');
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
await waitFor(() => {
const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i)
const authenticationButtonDashboard = screen.queryByText(/داشبورد/i)
expect(authenticationButtonLogin).not.toBeInTheDocument()
expect(authenticationButtonDashboard).toBeInTheDocument()
})
});
it("Show Login Button And Do Not Show Dashboard Button When User Authentication Is Expired", async () => {
localStorage.setItem("_token", 'token');
server.use(rest.get(GET_USER_ROUTE, (req, res, ctx) => {
return res(ctx.status(401))
}))
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
await waitFor(() => {
const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i)
const authenticationButtonDashboard = screen.queryByText(/داشبورد/i)
expect(authenticationButtonLogin).toBeInTheDocument()
expect(authenticationButtonDashboard).not.toBeInTheDocument()
})
});
});
});

View File

@@ -1,11 +1,10 @@
import {NextLinkComposed} from "@/core/components/LinkRouting";
import LinkRouting, {NextLinkComposed} from "@/core/components/LinkRouting";
import CenterLayout from "@/layouts/CenterLayout";
import FullPageLayout from "@/layouts/FullPageLayout";
import useUser from "@/lib/app/hooks/useUser";
import {Button, Stack, Typography} from "@mui/material";
import {useTranslations} from "next-intl";
import SvgDashboard from "@/core/components/svgs/SvgDashboard";
import process from "next/dist/build/webpack/loaders/resolve-url-loader/lib/postcss";
const FirstComponent = () => {
const t = useTranslations();
@@ -18,28 +17,16 @@ const FirstComponent = () => {
<Typography variant="h5" sx={{textAlign: "center"}}>
{t("app_name")}
</Typography>
{isAuth ? (
<Button
variant="outlined"
component={NextLinkComposed}
to={{
pathname: "/dashboard",
}}
>
{t("dashboard")}
</Button>
) : (
<Button
sx={{mx: 2}}
variant="contained"
component={NextLinkComposed}
to={{
pathname: "/login-expert",
}}
>
{t("login_expert")}
</Button>
)}
<Button
data-testid="button-login-or-dashboard"
variant={isAuth ? "outlined" : "contained"}
component={NextLinkComposed}
to={{
pathname: isAuth ? "/dashboard" : "/login-expert",
}}
>
{isAuth ? t("dashboard") : t("login_expert")}
</Button>
</CenterLayout>
<Stack direction="row" alignItems="center" justifyContent="center">
<Typography variant={"caption"}
@@ -47,10 +34,22 @@ const FirstComponent = () => {
color: 'primary.main',
fontFamily: 'Arial',
fontWeight: 'bold'
}}>v{process.env.NEXT_PUBLIC_API_VERSION}</Typography>
}}
>
v{process.env.NEXT_PUBLIC_API_VERSION}
</Typography>
</Stack>
<Stack direction="row" alignItems="center" justifyContent="center">
<LinkRouting
sx={{margin: 0.5, fontSize: "14px"}}
href={process.env.NEXT_PUBLIC_POWERED_BY_URL}
target="_blank"
>
{t("powered_by_witel")}
</LinkRouting>
</Stack>
</FullPageLayout>
);
};
export default FirstComponent;
export default FirstComponent;

View File

@@ -1,10 +1,10 @@
import {useState} from "react";
import Header from "./header";
import Sidebar from "./sidebar";
import {Toolbar} from "@mui/material";
import BreadCrumbs from "./breadcrumbs";
import FullPageLayout from "@/layouts/FullPageLayout";
import RolePermissionMiddleware from "@/middlewares/RolePermission";
import Header from "@/components/layouts/Dashboard/Header";
import Sidebar from "@/components/layouts/Dashboard/Sidebar";
import BreadCrumbs from "@/components/layouts/Dashboard/Breadcrumbs";
const drawerWidth = 240;

View File

@@ -15,7 +15,7 @@ const App = ({Component, pageProps}) => {
<>
<UserProvider>
<LanguageProvider>
<NextIntlProvider messages={pageProps.messages || {}}>
<NextIntlProvider locale={pageProps.locale} messages={pageProps.messages || {}}>
<MuiLayout isBot={pageProps.isBot}>
{pageProps.messages ? <TitlePage text={pageProps.title}/> : ''}
<LoadingProvider>

View File

@@ -16,6 +16,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "first_page",
isBot,
locale
},
};
}