@pplancq/react-template 2.9.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/mise.toml +2 -2
- package/mocks/browser.ts +1 -1
- package/mocks/fixtures/user.ts +14 -14
- package/mocks/handlers/userHandlers.ts +3 -3
- package/mocks/server.ts +1 -1
- package/package.json +24 -25
- package/playwright-report/html/index.html +90 -0
- package/playwright-report/sonar-results.xml +20 -0
- package/playwright.config.ts +3 -3
- package/src/app/App.tsx +5 -5
- package/src/app/AppHTMLElement.tsx +4 -4
- package/src/app/README.md +2 -2
- package/src/app/assets/css/index.ts +2 -2
- package/src/app/assets/css/reset.css +3 -3
- package/src/app/config/queryClientConfig.ts +1 -1
- package/src/app/config/serviceContainer.ts +2 -2
- package/src/app/providers/Providers/Providers.tsx +5 -5
- package/src/app/providers/QueryClientProvider/QueryClientProvider.tsx +3 -3
- package/src/app/providers/ServiceProvider/ServiceContext.ts +2 -2
- package/src/app/providers/ServiceProvider/ServiceProvider.tsx +4 -4
- package/src/app/routing/Router.tsx +2 -2
- package/src/app/routing/appRoutes.ts +1 -1
- package/src/app/routing/routes.tsx +3 -3
- package/src/demo/README.md +5 -4
- package/src/demo/application/dtos/ArchitecturePrinciple.ts +1 -1
- package/src/demo/application/use-cases/GetArchitecturePrinciplesUseCase.ts +3 -3
- package/src/demo/application/use-cases/GetArchitecturePrinciplesUseCaseInterface.ts +1 -1
- package/src/demo/application/use-cases/GetDemoUsersUseCase.ts +3 -3
- package/src/demo/application/use-cases/GetDemoUsersUseCaseInterface.ts +1 -1
- package/src/demo/domain/repositories/ArchitecturePrincipleRepositoryInterface.d.ts +1 -1
- package/src/demo/domain/repositories/DemoUserRepositoryInterface.d.ts +1 -1
- package/src/demo/infrastructure/repositories/HttpDemoUserRepository.ts +5 -5
- package/src/demo/infrastructure/repositories/StaticArchitecturePrincipleRepository.ts +14 -14
- package/src/demo/serviceDemo.ts +14 -13
- package/src/demo/serviceIdentifiers.ts +4 -4
- package/src/demo/ui/components/DemoFooter/DemoFooter.tsx +5 -5
- package/src/demo/ui/components/DemoFooter/index.ts +1 -1
- package/src/demo/ui/components/DemoHeader/DemoHeader.tsx +10 -10
- package/src/demo/ui/components/DemoHeader/index.ts +1 -1
- package/src/demo/ui/components/DemoNavigation/DemoNavigation.tsx +4 -4
- package/src/demo/ui/components/DemoNavigation/index.ts +1 -1
- package/src/demo/ui/components/Input/Input.tsx +3 -3
- package/src/demo/ui/components/Input/index.ts +1 -1
- package/src/demo/ui/components/Logo/Logo.tsx +2 -2
- package/src/demo/ui/components/Logo/index.ts +1 -1
- package/src/demo/ui/components/NavLink/NavLink.tsx +3 -3
- package/src/demo/ui/components/NavLink/index.ts +1 -1
- package/src/demo/ui/components/TextInput/TextInput.tsx +3 -3
- package/src/demo/ui/components/TextInput/index.ts +1 -1
- package/src/demo/ui/forms/ProfileForm/ProfileForm.tsx +9 -9
- package/src/demo/ui/forms/ProfileForm/index.ts +1 -1
- package/src/demo/ui/hooks/useDemoUsersQuery/useDemoUsersQuery.ts +5 -5
- package/src/demo/ui/layouts/DemoLayout/DemoLayout.tsx +4 -4
- package/src/demo/ui/layouts/MainTemplate/MainTemplate.tsx +2 -2
- package/src/demo/ui/layouts/MainTemplate/index.ts +1 -1
- package/src/demo/ui/pages/DemoHome/DemoHome.module.scss +5 -5
- package/src/demo/ui/pages/DemoHome/DemoHome.tsx +6 -6
- package/src/demo/ui/pages/DependencyInjectionDemo/DependencyInjectionDemo.tsx +5 -5
- package/src/demo/ui/pages/ReactHookFormDemo/ReactHookFormDemo.tsx +4 -4
- package/src/demo/ui/pages/ReactQueryDemo/ReactQueryDemo.tsx +3 -3
- package/src/demo/ui/routing/demoAppRoutes.ts +4 -4
- package/src/demo/ui/routing/demoRoutes.tsx +8 -8
- package/src/main.ts +5 -5
- package/src/react-app-env.d.ts +1 -1
- package/src/shared/infrastructure/http/constant.ts +8 -8
- package/src/shared/infrastructure/http/fetchApi.ts +3 -3
- package/src/shared/ui/hooks/useService/useService.ts +4 -4
- package/test-results/.last-run.json +4 -0
- package/tests/e2e/demo.test.ts +17 -0
- package/tests/e2e/fixture/playwright.fixture.ts +15 -0
- package/tests/{utils → e2e/utils}/createNetworkFixture.ts +2 -2
- package/tests/{utils → e2e/utils}/createTestWithCoverageFixture.ts +8 -8
- package/tests/{utils → e2e/utils}/playwright.globalSetup.ts +4 -4
- package/tests/{utils → e2e/utils}/playwright.globalTeardown.ts +3 -3
- package/{src/app/__tests__ → tests/unit/app}/AppHTMLElement.test.tsx +14 -14
- package/tests/unit/demo/ui/components/DemoFooter/Footer.test.tsx +15 -0
- package/tests/unit/demo/ui/components/DemoHeader/Header.test.tsx +15 -0
- package/tests/unit/demo/ui/components/Logo/Logo.test.tsx +12 -0
- package/tsconfig.json +1 -1
- package/vitest.config.mts +1 -1
- package/postcss.config.mjs +0 -5
- package/src/demo/ui/components/DemoFooter/__tests__/Footer.test.tsx +0 -15
- package/src/demo/ui/components/DemoHeader/__tests__/Header.test.tsx +0 -15
- package/src/demo/ui/components/Logo/__tests__/Logo.test.tsx +0 -12
- package/tests/demo.test.ts +0 -17
- package/tests/fixture/playwright.fixture.ts +0 -15
- /package/{src/demo/ui/components/DemoFooter/__tests__ → tests/unit/demo/ui/components/DemoFooter}/__snapshots__/Footer.test.tsx.snap +0 -0
- /package/{src/demo/ui/components/DemoHeader/__tests__ → tests/unit/demo/ui/components/DemoHeader}/__snapshots__/Header.test.tsx.snap +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { ProfileFormValues } from
|
|
2
|
-
import { TextInput } from
|
|
3
|
-
import { yupResolver } from
|
|
4
|
-
import { FormProvider, type SubmitHandler, useForm } from
|
|
5
|
-
import * as yup from
|
|
6
|
-
import classes from
|
|
1
|
+
import type { ProfileFormValues } from "@Demo/application/dtos/ProfileFormValues";
|
|
2
|
+
import { TextInput } from "@Demo/ui/components/TextInput/TextInput";
|
|
3
|
+
import { yupResolver } from "@hookform/resolvers/yup";
|
|
4
|
+
import { FormProvider, type SubmitHandler, useForm } from "react-hook-form";
|
|
5
|
+
import * as yup from "yup";
|
|
6
|
+
import classes from "./ProfileForm.module.scss";
|
|
7
7
|
|
|
8
8
|
type ProfileFormProps = {
|
|
9
9
|
onSubmit: SubmitHandler<ProfileFormValues>;
|
|
@@ -11,9 +11,9 @@ type ProfileFormProps = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
const schema = yup.object().shape({
|
|
14
|
-
mail: yup.string().email(
|
|
15
|
-
firstName: yup.string().required(
|
|
16
|
-
lastName: yup.string().required(
|
|
14
|
+
mail: yup.string().email("Check the mail pattern").required("This field is required"),
|
|
15
|
+
firstName: yup.string().required("This field is required"),
|
|
16
|
+
lastName: yup.string().required("This field is required"),
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
export const ProfileForm = ({ onSubmit, resetForm }: ProfileFormProps) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { ProfileForm } from
|
|
1
|
+
export { ProfileForm } from "./ProfileForm";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { GetDemoUsersUseCaseInterface } from
|
|
2
|
-
import { DEMO_SERVICES } from
|
|
3
|
-
import { useService } from
|
|
4
|
-
import { useQuery } from
|
|
1
|
+
import type { GetDemoUsersUseCaseInterface } from "@Demo/application/use-cases/GetDemoUsersUseCaseInterface";
|
|
2
|
+
import { DEMO_SERVICES } from "@Demo/serviceIdentifiers";
|
|
3
|
+
import { useService } from "@Shared/ui/hooks/useService/useService";
|
|
4
|
+
import { useQuery } from "@tanstack/react-query";
|
|
5
5
|
|
|
6
6
|
export const useDemoUsersQuery = () => {
|
|
7
7
|
const getDemoUsersUseCase = useService<GetDemoUsersUseCaseInterface>(DEMO_SERVICES.GetDemoUsersUseCase);
|
|
8
8
|
|
|
9
9
|
return useQuery({
|
|
10
|
-
queryKey: [
|
|
10
|
+
queryKey: ["demo-users"],
|
|
11
11
|
queryFn: () => getDemoUsersUseCase.execute(),
|
|
12
12
|
});
|
|
13
13
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { DemoFooter } from
|
|
2
|
-
import { DemoHeader } from
|
|
3
|
-
import { MainTemplate } from
|
|
4
|
-
import { Outlet } from
|
|
1
|
+
import { DemoFooter } from "@Demo/ui/components/DemoFooter/DemoFooter";
|
|
2
|
+
import { DemoHeader } from "@Demo/ui/components/DemoHeader/DemoHeader";
|
|
3
|
+
import { MainTemplate } from "@Demo/ui/layouts/MainTemplate/MainTemplate";
|
|
4
|
+
import { Outlet } from "react-router";
|
|
5
5
|
|
|
6
6
|
export const DemoLayout = () => {
|
|
7
7
|
return (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { PropsWithChildren, ReactNode } from
|
|
1
|
+
import type { PropsWithChildren, ReactNode } from "react";
|
|
2
2
|
|
|
3
|
-
import classes from
|
|
3
|
+
import classes from "./MainTemplate.module.css";
|
|
4
4
|
|
|
5
5
|
type MainTemplateProps = {
|
|
6
6
|
headerProps: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { MainTemplate } from
|
|
1
|
+
export { MainTemplate } from "./MainTemplate";
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
border-top: 1px solid #ccc;
|
|
41
41
|
border-bottom: 1px solid #ccc;
|
|
42
42
|
background-color: #f8f8f8;
|
|
43
|
-
content:
|
|
43
|
+
content: "";
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
&::after {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
border-top: 1px solid #ccc;
|
|
50
50
|
border-bottom: 1px solid #ccc;
|
|
51
51
|
background-color: #f8f8f8;
|
|
52
|
-
content:
|
|
52
|
+
content: " ";
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
width: 55px;
|
|
84
84
|
height: 5px;
|
|
85
85
|
background-color: #111;
|
|
86
|
-
content:
|
|
86
|
+
content: "";
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
&::after {
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
max-width: 255px;
|
|
95
95
|
height: 1px;
|
|
96
96
|
background-color: #333;
|
|
97
|
-
content:
|
|
97
|
+
content: "";
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -177,7 +177,7 @@ li {
|
|
|
177
177
|
code {
|
|
178
178
|
padding: 2px 5px;
|
|
179
179
|
border-radius: 4px;
|
|
180
|
-
font-family:
|
|
180
|
+
font-family: "Courier New", monospace;
|
|
181
181
|
font-size: 14px;
|
|
182
182
|
background-color: #f8f9fa;
|
|
183
183
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import image from
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import classes from
|
|
1
|
+
import image from "@Demo/ui/assets/images/templatePlugins.png";
|
|
2
|
+
import { NavLink } from "@Demo/ui/components/NavLink/NavLink";
|
|
3
|
+
import { demoAppRoutes } from "@Demo/ui/routing/demoAppRoutes";
|
|
4
|
+
import classes from "./DemoHome.module.scss";
|
|
5
5
|
|
|
6
6
|
export const DemoHome = () => {
|
|
7
7
|
return (
|
|
@@ -18,8 +18,8 @@ export const DemoHome = () => {
|
|
|
18
18
|
<span className={classes.centredText}>
|
|
19
19
|
<p className={classes.paragraphStyle}>
|
|
20
20
|
This template now starts from a <span className={classes.highlight}>Clean Architecture</span> inspired
|
|
21
|
-
structure with a dedicated <span className={classes.boldText}>app</span> bootstrap layer, a{
|
|
22
|
-
<span className={classes.boldText}>shared</span> common layer, and a{
|
|
21
|
+
structure with a dedicated <span className={classes.boldText}>app</span> bootstrap layer, a{" "}
|
|
22
|
+
<span className={classes.boldText}>shared</span> common layer, and a{" "}
|
|
23
23
|
<span className={classes.boldText}>demo</span> bounded context.
|
|
24
24
|
</p>
|
|
25
25
|
<p className={classes.paragraphStyle}>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { GetArchitecturePrinciplesUseCaseInterface } from
|
|
2
|
-
import { DEMO_SERVICES } from
|
|
3
|
-
import { useService } from
|
|
4
|
-
import classes from
|
|
1
|
+
import type { GetArchitecturePrinciplesUseCaseInterface } from "@Demo/application/use-cases/GetArchitecturePrinciplesUseCaseInterface";
|
|
2
|
+
import { DEMO_SERVICES } from "@Demo/serviceIdentifiers";
|
|
3
|
+
import { useService } from "@Shared/ui/hooks/useService/useService";
|
|
4
|
+
import classes from "./DependencyInjectionDemo.module.scss";
|
|
5
5
|
|
|
6
6
|
export const DependencyInjectionDemo = () => {
|
|
7
7
|
const getArchitecturePrinciplesUseCase = useService<GetArchitecturePrinciplesUseCaseInterface>(
|
|
@@ -31,7 +31,7 @@ export const DependencyInjectionDemo = () => {
|
|
|
31
31
|
</tr>
|
|
32
32
|
</thead>
|
|
33
33
|
<tbody>
|
|
34
|
-
{architecturePrinciples.map(principle => (
|
|
34
|
+
{architecturePrinciples.map((principle) => (
|
|
35
35
|
<tr key={`${principle.layer}-${principle.title}`}>
|
|
36
36
|
<td>{principle.layer}</td>
|
|
37
37
|
<td>{principle.title}</td>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { ProfileFormValues } from
|
|
2
|
-
import { ProfileForm } from
|
|
3
|
-
import { useState } from
|
|
1
|
+
import type { ProfileFormValues } from "@Demo/application/dtos/ProfileFormValues";
|
|
2
|
+
import { ProfileForm } from "@Demo/ui/forms/ProfileForm/ProfileForm";
|
|
3
|
+
import { useState } from "react";
|
|
4
4
|
|
|
5
|
-
import classes from
|
|
5
|
+
import classes from "./ReactHookFormDemo.module.scss";
|
|
6
6
|
|
|
7
7
|
export const ReactHookFormDemo = () => {
|
|
8
8
|
const [profileData, setProfileData] = useState<ProfileFormValues>();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useDemoUsersQuery } from
|
|
2
|
-
import classes from
|
|
1
|
+
import { useDemoUsersQuery } from "@Demo/ui/hooks/useDemoUsersQuery/useDemoUsersQuery";
|
|
2
|
+
import classes from "./ReactQueryDemo.module.scss";
|
|
3
3
|
|
|
4
4
|
export const ReactQueryDemo = () => {
|
|
5
5
|
const { data = [], isLoading, isError } = useDemoUsersQuery();
|
|
@@ -36,7 +36,7 @@ export const ReactQueryDemo = () => {
|
|
|
36
36
|
</tr>
|
|
37
37
|
</thead>
|
|
38
38
|
<tbody>
|
|
39
|
-
{data.map(eachResult => (
|
|
39
|
+
{data.map((eachResult) => (
|
|
40
40
|
<tr key={eachResult.id}>
|
|
41
41
|
<td>{eachResult.id}</td>
|
|
42
42
|
<td>{eachResult.name}</td>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const demoAppRoutes = Object.freeze({
|
|
2
|
-
home:
|
|
3
|
-
reactQuery:
|
|
4
|
-
reactHookForm:
|
|
5
|
-
dependencyInjection:
|
|
2
|
+
home: "/",
|
|
3
|
+
reactQuery: "/react-query",
|
|
4
|
+
reactHookForm: "/react-hook-form",
|
|
5
|
+
dependencyInjection: "/dependency-injection",
|
|
6
6
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import type { RouteObject } from
|
|
1
|
+
import { DemoLayout } from "@Demo/ui/layouts/DemoLayout/DemoLayout";
|
|
2
|
+
import { DemoHome } from "@Demo/ui/pages/DemoHome/DemoHome";
|
|
3
|
+
import { DependencyInjectionDemo } from "@Demo/ui/pages/DependencyInjectionDemo/DependencyInjectionDemo";
|
|
4
|
+
import { ReactHookFormDemo } from "@Demo/ui/pages/ReactHookFormDemo/ReactHookFormDemo";
|
|
5
|
+
import { ReactQueryDemo } from "@Demo/ui/pages/ReactQueryDemo/ReactQueryDemo";
|
|
6
|
+
import { demoAppRoutes } from "@Demo/ui/routing/demoAppRoutes";
|
|
7
|
+
import type { RouteObject } from "react-router";
|
|
8
8
|
|
|
9
|
-
const toRelativePath = (path: string) => path.replace(/^\//,
|
|
9
|
+
const toRelativePath = (path: string) => path.replace(/^\//, "");
|
|
10
10
|
|
|
11
11
|
export const demoRoutes: RouteObject = {
|
|
12
12
|
element: <DemoLayout />,
|
package/src/main.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import AppHTMLElement from
|
|
1
|
+
import AppHTMLElement from "@App/AppHTMLElement";
|
|
2
2
|
|
|
3
|
-
if (import.meta.env.FRONT_MOCK_ENABLE ===
|
|
4
|
-
const { worker } = await import(
|
|
3
|
+
if (import.meta.env.FRONT_MOCK_ENABLE === "true") {
|
|
4
|
+
const { worker } = await import("@Mocks/browser");
|
|
5
5
|
await worker.start({
|
|
6
|
-
onUnhandledRequest:
|
|
6
|
+
onUnhandledRequest: "warn",
|
|
7
7
|
});
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
customElements.define(
|
|
10
|
+
customElements.define("app-react", AppHTMLElement);
|
package/src/react-app-env.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
export enum METHODS {
|
|
2
|
-
get =
|
|
3
|
-
post =
|
|
4
|
-
put =
|
|
5
|
-
patch =
|
|
6
|
-
delete =
|
|
2
|
+
get = "GET",
|
|
3
|
+
post = "POST",
|
|
4
|
+
put = "PUT",
|
|
5
|
+
patch = "PATCH",
|
|
6
|
+
delete = "DELETE",
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export enum HEADERS {
|
|
10
|
-
contentType =
|
|
10
|
+
contentType = "content-type",
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export enum MIME_TYPES {
|
|
14
|
-
json =
|
|
15
|
-
text =
|
|
14
|
+
json = "application/json",
|
|
15
|
+
text = "plain/text",
|
|
16
16
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { HEADERS, METHODS, MIME_TYPES } from "./constant";
|
|
2
|
+
import type { Json } from "./fetchApi.type";
|
|
3
3
|
|
|
4
4
|
export type FetchApiError = Error & {
|
|
5
5
|
code?: number;
|
|
@@ -41,7 +41,7 @@ export const fetchApi = async <T extends Json | string>({
|
|
|
41
41
|
throw error;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
if ((response.headers.get(HEADERS.contentType) ??
|
|
44
|
+
if ((response.headers.get(HEADERS.contentType) ?? "").includes("json")) {
|
|
45
45
|
return JSON.parse(content) as T;
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { ServiceContext } from
|
|
2
|
-
import type { ServiceIdentifier } from
|
|
3
|
-
import { useContext } from
|
|
1
|
+
import { ServiceContext } from "@App/providers/ServiceProvider/ServiceContext";
|
|
2
|
+
import type { ServiceIdentifier } from "inversify";
|
|
3
|
+
import { useContext } from "react";
|
|
4
4
|
|
|
5
5
|
export const useService = <T = unknown>(serviceIdentifier: ServiceIdentifier<T>): T => {
|
|
6
6
|
const context = useContext(ServiceContext);
|
|
7
7
|
|
|
8
8
|
if (!context) {
|
|
9
|
-
throw new Error(
|
|
9
|
+
throw new Error("useService must be used within a ServiceProvider");
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
return context.container.get<T>(serviceIdentifier);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getUsers200 } from "@Mocks/handlers/userHandlers";
|
|
2
|
+
import { expect } from "@playwright/test";
|
|
3
|
+
import { describe, it } from "./fixture/playwright.fixture";
|
|
4
|
+
|
|
5
|
+
describe("Demo Test", () => {
|
|
6
|
+
it("should have a title", async ({ page }) => {
|
|
7
|
+
await page.goto("/");
|
|
8
|
+
const title = page.getByText("WELCOME TO YOUR REACT STARTER KIT");
|
|
9
|
+
await expect(title).toBeVisible();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should data fetching", async ({ page, network }) => {
|
|
13
|
+
network.use(getUsers200);
|
|
14
|
+
await page.goto("/react-query");
|
|
15
|
+
await expect(page.getByText("Demo user")).toBeVisible();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NetworkFixture } from "@msw/playwright";
|
|
2
|
+
import { test as base } from "@playwright/test";
|
|
3
|
+
import { createNetworkFixture } from "../utils/createNetworkFixture";
|
|
4
|
+
import { createTestWithCoverageFixture } from "../utils/createTestWithCoverageFixture";
|
|
5
|
+
|
|
6
|
+
export const test = base.extend<{
|
|
7
|
+
testWithCoverage: string;
|
|
8
|
+
network: NetworkFixture;
|
|
9
|
+
}>({
|
|
10
|
+
testWithCoverage: createTestWithCoverageFixture(),
|
|
11
|
+
network: createNetworkFixture(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const it = test;
|
|
15
|
+
export const { describe } = test;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { defineNetworkFixture, type NetworkFixture } from
|
|
2
|
-
import type { PlaywrightTestArgs, PlaywrightWorkerArgs, TestFixture } from
|
|
1
|
+
import { defineNetworkFixture, type NetworkFixture } from "@msw/playwright";
|
|
2
|
+
import type { PlaywrightTestArgs, PlaywrightWorkerArgs, TestFixture } from "@playwright/test";
|
|
3
3
|
|
|
4
4
|
export const createNetworkFixture = (): [
|
|
5
5
|
TestFixture<NetworkFixture, PlaywrightTestArgs & PlaywrightWorkerArgs>,
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import type { PlaywrightTestArgs, PlaywrightWorkerArgs, TestFixture } from
|
|
2
|
-
import { test } from
|
|
3
|
-
import { CoverageReport } from
|
|
4
|
-
import { coverageOptions } from
|
|
1
|
+
import type { PlaywrightTestArgs, PlaywrightWorkerArgs, TestFixture } from "@playwright/test";
|
|
2
|
+
import { test } from "@playwright/test";
|
|
3
|
+
import { CoverageReport } from "monocart-coverage-reports";
|
|
4
|
+
import { coverageOptions } from "../../../mrc.playwright.config";
|
|
5
5
|
|
|
6
6
|
export const createTestWithCoverageFixture = (): [
|
|
7
7
|
TestFixture<string, PlaywrightTestArgs & PlaywrightWorkerArgs>,
|
|
8
|
-
{ scope:
|
|
8
|
+
{ scope: "test"; auto: true },
|
|
9
9
|
] => [
|
|
10
10
|
async ({ page }, use) => {
|
|
11
|
-
const isChromium = test.info().project.name ===
|
|
11
|
+
const isChromium = test.info().project.name === "chromium";
|
|
12
12
|
|
|
13
13
|
if (isChromium && coverageOptions.enabled) {
|
|
14
14
|
await page.coverage.startJSCoverage({
|
|
@@ -16,7 +16,7 @@ export const createTestWithCoverageFixture = (): [
|
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
await use(
|
|
19
|
+
await use("testWithCoverage");
|
|
20
20
|
|
|
21
21
|
if (isChromium && coverageOptions.enabled) {
|
|
22
22
|
const coverageList = await page.coverage.stopJSCoverage();
|
|
@@ -25,7 +25,7 @@ export const createTestWithCoverageFixture = (): [
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
|
-
scope:
|
|
28
|
+
scope: "test",
|
|
29
29
|
auto: true,
|
|
30
30
|
},
|
|
31
31
|
];
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { FullConfig } from
|
|
2
|
-
import { CoverageReport } from
|
|
3
|
-
import { coverageOptions } from
|
|
1
|
+
import type { FullConfig } from "@playwright/test";
|
|
2
|
+
import { CoverageReport } from "monocart-coverage-reports";
|
|
3
|
+
import { coverageOptions } from "../../../mrc.playwright.config";
|
|
4
4
|
|
|
5
5
|
const globalSetup = (config: FullConfig) => {
|
|
6
6
|
if (config.webServer && coverageOptions.enabled) {
|
|
7
|
-
console.info(
|
|
7
|
+
console.info("[playwright] Coverage enabled with monocart");
|
|
8
8
|
const mcr = new CoverageReport(coverageOptions);
|
|
9
9
|
mcr.cleanCache();
|
|
10
10
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { FullConfig } from
|
|
2
|
-
import { CoverageReport } from
|
|
3
|
-
import { coverageOptions } from
|
|
1
|
+
import type { FullConfig } from "@playwright/test";
|
|
2
|
+
import { CoverageReport } from "monocart-coverage-reports";
|
|
3
|
+
import { coverageOptions } from "../../../mrc.playwright.config";
|
|
4
4
|
|
|
5
5
|
const globalTeardown = async (config: FullConfig) => {
|
|
6
6
|
if (config.webServer && coverageOptions.enabled) {
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { App } from
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
1
|
+
import { App } from "@App/App";
|
|
2
|
+
import AppHTMLElement from "@App/AppHTMLElement";
|
|
3
|
+
import { waitFor } from "@testing-library/react";
|
|
4
|
+
import { StrictMode } from "react";
|
|
5
|
+
import { createRoot } from "react-dom/client";
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest";
|
|
7
7
|
|
|
8
|
-
vi.mock(
|
|
8
|
+
vi.mock("@App/App", () => ({
|
|
9
9
|
App: () => <div data-testid="app-mock">AppMock</div>,
|
|
10
10
|
}));
|
|
11
11
|
|
|
12
|
-
vi.mock(
|
|
12
|
+
vi.mock("react-dom/client", () => ({
|
|
13
13
|
createRoot: vi.fn(),
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
|
-
describe(
|
|
16
|
+
describe("AppHTMLElement", () => {
|
|
17
17
|
let container: HTMLElement;
|
|
18
18
|
const render = vi.fn();
|
|
19
19
|
const unmount = vi.fn();
|
|
@@ -22,17 +22,17 @@ describe('AppHTMLElement', () => {
|
|
|
22
22
|
unmount,
|
|
23
23
|
}));
|
|
24
24
|
|
|
25
|
-
customElements.define(
|
|
25
|
+
customElements.define("bootstrap-html-element", AppHTMLElement);
|
|
26
26
|
|
|
27
27
|
beforeEach(() => {
|
|
28
|
-
container = document.createElement(
|
|
28
|
+
container = document.createElement("bootstrap-html-element");
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
afterEach(() => {
|
|
32
32
|
container.remove();
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it(
|
|
35
|
+
it("should mount the React component in the custom element", () => {
|
|
36
36
|
document.body.appendChild(container);
|
|
37
37
|
|
|
38
38
|
expect(createRoot as Mock).toHaveBeenCalledWith(expect.anything());
|
|
@@ -44,7 +44,7 @@ describe('AppHTMLElement', () => {
|
|
|
44
44
|
expect(unmount).not.toHaveBeenCalled();
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
it(
|
|
47
|
+
it("should unmount the React component when removed from the DOM", async () => {
|
|
48
48
|
document.body.appendChild(container);
|
|
49
49
|
|
|
50
50
|
expect(unmount).not.toHaveBeenCalled();
|
|
@@ -56,7 +56,7 @@ describe('AppHTMLElement', () => {
|
|
|
56
56
|
});
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
it(
|
|
59
|
+
it("should not unmount when element is removed and re-added to the DOM", async () => {
|
|
60
60
|
document.body.appendChild(container);
|
|
61
61
|
container.remove();
|
|
62
62
|
document.body.appendChild(container);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DemoFooter } from "@Demo/ui/components/DemoFooter";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
import { BrowserRouter as Router } from "react-router";
|
|
4
|
+
|
|
5
|
+
describe("Footer Component", () => {
|
|
6
|
+
it("should render the Footer component correctly", () => {
|
|
7
|
+
const { container } = render(
|
|
8
|
+
<Router>
|
|
9
|
+
<DemoFooter />
|
|
10
|
+
</Router>,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
expect(container).toMatchSnapshot();
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DemoHeader } from "@Demo/ui/components/DemoHeader";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
import { BrowserRouter as Router } from "react-router";
|
|
4
|
+
|
|
5
|
+
describe("Header Component", () => {
|
|
6
|
+
it("should render the Header component correctly", () => {
|
|
7
|
+
const { container } = render(
|
|
8
|
+
<Router>
|
|
9
|
+
<DemoHeader />
|
|
10
|
+
</Router>,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
expect(container).toMatchSnapshot();
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Logo } from "@Demo/ui/components/Logo";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
import { expect } from "vitest";
|
|
4
|
+
import { axe } from "vitest-axe";
|
|
5
|
+
|
|
6
|
+
describe("<Logo />", () => {
|
|
7
|
+
it("should", async () => {
|
|
8
|
+
const { container } = render(<Logo src="foo" alt="bar" />);
|
|
9
|
+
|
|
10
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
11
|
+
});
|
|
12
|
+
});
|
package/tsconfig.json
CHANGED
package/vitest.config.mts
CHANGED
|
@@ -28,7 +28,7 @@ export default defineConfig(({ mode }) => {
|
|
|
28
28
|
'vitest-sonar-reporter': 'sonar-report.xml',
|
|
29
29
|
junit: 'junit-report.xml',
|
|
30
30
|
},
|
|
31
|
-
include: ['
|
|
31
|
+
include: ['tests/unit/**/*.(spec|test|steps).[jt]s?(x)'],
|
|
32
32
|
maxWorkers: env.CI ? 2 : undefined,
|
|
33
33
|
coverage: {
|
|
34
34
|
enabled: env.CI === 'true',
|
package/postcss.config.mjs
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/react';
|
|
2
|
-
import { BrowserRouter as Router } from 'react-router';
|
|
3
|
-
import { DemoFooter } from '../DemoFooter';
|
|
4
|
-
|
|
5
|
-
describe('Footer Component', () => {
|
|
6
|
-
it('should render the Footer component correctly', () => {
|
|
7
|
-
const { container } = render(
|
|
8
|
-
<Router>
|
|
9
|
-
<DemoFooter />
|
|
10
|
-
</Router>,
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
expect(container).toMatchSnapshot();
|
|
14
|
-
});
|
|
15
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/react';
|
|
2
|
-
import { BrowserRouter as Router } from 'react-router';
|
|
3
|
-
import { DemoHeader } from '../DemoHeader';
|
|
4
|
-
|
|
5
|
-
describe('Header Component', () => {
|
|
6
|
-
it('should render the Header component correctly', () => {
|
|
7
|
-
const { container } = render(
|
|
8
|
-
<Router>
|
|
9
|
-
<DemoHeader />
|
|
10
|
-
</Router>,
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
expect(container).toMatchSnapshot();
|
|
14
|
-
});
|
|
15
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/react';
|
|
2
|
-
import { expect } from 'vitest';
|
|
3
|
-
import { axe } from 'vitest-axe';
|
|
4
|
-
import { Logo } from '../Logo';
|
|
5
|
-
|
|
6
|
-
describe('<Logo />', () => {
|
|
7
|
-
it('should', async () => {
|
|
8
|
-
const { container } = render(<Logo src="foo" alt="bar" />);
|
|
9
|
-
|
|
10
|
-
expect(await axe(container)).toHaveNoViolations();
|
|
11
|
-
});
|
|
12
|
-
});
|