codeforlife 2.6.1
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/.eslintrc.json +47 -0
- package/.github/workflows/contributing.yaml +8 -0
- package/.github/workflows/main.yml +36 -0
- package/.prettierignore +5 -0
- package/.prettierrc.json +4 -0
- package/.vscode/launch.json +22 -0
- package/.vscode/settings.json +30 -0
- package/CHANGELOG.md +1864 -0
- package/CONTRIBUTING.md +3 -0
- package/LICENSE.md +3 -0
- package/README.md +94 -0
- package/codecov.yml +11 -0
- package/package.json +139 -0
- package/src/api/createApi.ts +84 -0
- package/src/api/endpoints/authFactor.ts +31 -0
- package/src/api/endpoints/index.ts +9 -0
- package/src/api/endpoints/klass.ts +87 -0
- package/src/api/endpoints/school.ts +34 -0
- package/src/api/endpoints/session.ts +40 -0
- package/src/api/endpoints/user.ts +70 -0
- package/src/api/index.ts +4 -0
- package/src/api/models.ts +144 -0
- package/src/api/tagTypes.ts +12 -0
- package/src/api/urls.ts +13 -0
- package/src/components/App.css +38 -0
- package/src/components/App.tsx +150 -0
- package/src/components/ClickableTooltip.tsx +43 -0
- package/src/components/CopyIconButton.test.tsx +16 -0
- package/src/components/CopyIconButton.tsx +27 -0
- package/src/components/Countdown.tsx +42 -0
- package/src/components/ElevatedAppBar.tsx +41 -0
- package/src/components/Image.tsx +41 -0
- package/src/components/InputFileButton.tsx +27 -0
- package/src/components/ItemizedList.tsx +61 -0
- package/src/components/OrderedGrid.tsx +92 -0
- package/src/components/ScrollIntoViewLink.tsx +23 -0
- package/src/components/SyncError.tsx +14 -0
- package/src/components/TablePagination.tsx +132 -0
- package/src/components/YouTubeVideo.tsx +26 -0
- package/src/components/form/ApiAutocompleteField.tsx +180 -0
- package/src/components/form/AutocompleteField.tsx +124 -0
- package/src/components/form/CheckboxField.tsx +81 -0
- package/src/components/form/CountryField.tsx +68 -0
- package/src/components/form/DatePickerField.tsx +119 -0
- package/src/components/form/EmailField.tsx +38 -0
- package/src/components/form/FirstNameField.tsx +40 -0
- package/src/components/form/Form.tsx +82 -0
- package/src/components/form/OtpField.tsx +28 -0
- package/src/components/form/PasswordField.tsx +71 -0
- package/src/components/form/RepeatField.tsx +115 -0
- package/src/components/form/SubmitButton.tsx +47 -0
- package/src/components/form/TextField.tsx +103 -0
- package/src/components/form/UkCountyField.tsx +67 -0
- package/src/components/form/index.tsx +28 -0
- package/src/components/index.ts +26 -0
- package/src/components/page/Banner.tsx +84 -0
- package/src/components/page/Notification.tsx +71 -0
- package/src/components/page/Page.tsx +73 -0
- package/src/components/page/Section.tsx +21 -0
- package/src/components/page/TabBar.tsx +131 -0
- package/src/components/page/index.ts +10 -0
- package/src/components/router/Link.tsx +22 -0
- package/src/components/router/LinkButton.tsx +21 -0
- package/src/components/router/LinkIconButton.tsx +21 -0
- package/src/components/router/LinkListItem.tsx +21 -0
- package/src/components/router/LinkTab.tsx +21 -0
- package/src/components/router/Navigate.tsx +33 -0
- package/src/components/router/index.tsx +12 -0
- package/src/components/table/CellStack.tsx +19 -0
- package/src/components/table/Table.tsx +55 -0
- package/src/components/table/index.tsx +10 -0
- package/src/features/InactiveDialog.tsx +40 -0
- package/src/features/ScreenTimeDialog.tsx +33 -0
- package/src/features/index.ts +4 -0
- package/src/fonts/Inter-VariableFont_slnt,wght.ttf +0 -0
- package/src/fonts/SpaceGrotesk-VariableFont_wght.ttf +0 -0
- package/src/hooks/api.tsx +37 -0
- package/src/hooks/auth.tsx +87 -0
- package/src/hooks/general.ts +110 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/router.tsx +168 -0
- package/src/index.ts +2 -0
- package/src/middlewares/index.ts +1 -0
- package/src/middlewares/session.ts +16 -0
- package/src/public/images/brain.svg +1 -0
- package/src/schemas/user.ts +4 -0
- package/src/scripts/freshDesk.js +473 -0
- package/src/scripts/index.ts +1 -0
- package/src/server.js +181 -0
- package/src/settings/custom.ts +22 -0
- package/src/settings/index.ts +5 -0
- package/src/settings/vite.ts +26 -0
- package/src/setupTests.ts +1 -0
- package/src/slices/createSlice.ts +8 -0
- package/src/slices/index.ts +2 -0
- package/src/slices/session.ts +32 -0
- package/src/theme/ThemedBox.tsx +265 -0
- package/src/theme/colors.ts +57 -0
- package/src/theme/components/MuiAccordion.tsx +13 -0
- package/src/theme/components/MuiAutocomplete.tsx +11 -0
- package/src/theme/components/MuiButton.ts +70 -0
- package/src/theme/components/MuiCardActions.tsx +12 -0
- package/src/theme/components/MuiCheckbox.ts +12 -0
- package/src/theme/components/MuiContainer.ts +19 -0
- package/src/theme/components/MuiDialog.tsx +16 -0
- package/src/theme/components/MuiFormControlLabel.ts +18 -0
- package/src/theme/components/MuiFormHelperText.ts +12 -0
- package/src/theme/components/MuiGrid2.ts +16 -0
- package/src/theme/components/MuiInputBase.ts +14 -0
- package/src/theme/components/MuiLink.ts +41 -0
- package/src/theme/components/MuiList.ts +12 -0
- package/src/theme/components/MuiListItemText.ts +18 -0
- package/src/theme/components/MuiMenu.ts +14 -0
- package/src/theme/components/MuiMenuItem.ts +15 -0
- package/src/theme/components/MuiSelect.ts +16 -0
- package/src/theme/components/MuiTab.ts +29 -0
- package/src/theme/components/MuiTable.ts +29 -0
- package/src/theme/components/MuiTableBody.ts +15 -0
- package/src/theme/components/MuiTableHead.ts +26 -0
- package/src/theme/components/MuiTabs.ts +26 -0
- package/src/theme/components/MuiTextField.ts +86 -0
- package/src/theme/components/MuiToolbar.ts +11 -0
- package/src/theme/components/MuiTypography.ts +12 -0
- package/src/theme/components/_components.ts +93 -0
- package/src/theme/components/index.ts +57 -0
- package/src/theme/index.ts +25 -0
- package/src/theme/palette.ts +98 -0
- package/src/theme/spacing.ts +8 -0
- package/src/theme/typography.ts +101 -0
- package/src/utils/api.test.ts +19 -0
- package/src/utils/api.tsx +327 -0
- package/src/utils/auth.ts +17 -0
- package/src/utils/form.ts +153 -0
- package/src/utils/general.test.ts +42 -0
- package/src/utils/general.ts +498 -0
- package/src/utils/router.test.ts +156 -0
- package/src/utils/router.ts +67 -0
- package/src/utils/schema.ts +80 -0
- package/src/utils/store.ts +31 -0
- package/src/utils/test.tsx +82 -0
- package/src/utils/theme.tsx +82 -0
- package/src/utils/window.ts +9 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +31 -0
- package/tsconfig.node.json +11 -0
- package/types/fixes.d.ts +18 -0
- package/vite.config.ts +21 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { path as p, type Parameters, type Path } from "./router"
|
|
2
|
+
|
|
3
|
+
const m = <SubMatches extends Record<string, Path>>(
|
|
4
|
+
_: string,
|
|
5
|
+
__: string | Parameters,
|
|
6
|
+
subMatches: SubMatches = {} as SubMatches,
|
|
7
|
+
) => ({ _, __, ...subMatches })
|
|
8
|
+
|
|
9
|
+
function testPaths({
|
|
10
|
+
name,
|
|
11
|
+
paths,
|
|
12
|
+
match,
|
|
13
|
+
}: {
|
|
14
|
+
name: string
|
|
15
|
+
paths: Path
|
|
16
|
+
match: Path
|
|
17
|
+
}) {
|
|
18
|
+
test(name, () => {
|
|
19
|
+
expect(paths).toMatchObject(match)
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
testPaths({
|
|
24
|
+
name: "no nested paths",
|
|
25
|
+
paths: p(""),
|
|
26
|
+
match: m("/", ""),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
testPaths({
|
|
30
|
+
name: "nested paths",
|
|
31
|
+
paths: p("", {
|
|
32
|
+
a: p("/a", {
|
|
33
|
+
b: p("/b"),
|
|
34
|
+
}),
|
|
35
|
+
}),
|
|
36
|
+
match: m("/", "", {
|
|
37
|
+
a: m("/a", "/a"),
|
|
38
|
+
}),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
testPaths({
|
|
42
|
+
name: "one param",
|
|
43
|
+
paths: p("", {
|
|
44
|
+
person: p("/person", {
|
|
45
|
+
name: p("/:name", {
|
|
46
|
+
sam: p({ name: "samantha" }),
|
|
47
|
+
}),
|
|
48
|
+
}),
|
|
49
|
+
}),
|
|
50
|
+
match: m("/", "", {
|
|
51
|
+
person: m("/person", "/person", {
|
|
52
|
+
name: m("/person/:name", "/:name", {
|
|
53
|
+
sam: m("/person/samantha", { name: "samantha" }),
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
56
|
+
}),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
testPaths({
|
|
60
|
+
name: "multiple params",
|
|
61
|
+
paths: p("", {
|
|
62
|
+
hero: p("/hero", {
|
|
63
|
+
firstAndLastName: p("/:firstName/:lastName", {
|
|
64
|
+
spiderMan: p(
|
|
65
|
+
{ firstName: "peter", lastName: "parker" },
|
|
66
|
+
{
|
|
67
|
+
mainVillain: p("/green-goblin"),
|
|
68
|
+
},
|
|
69
|
+
),
|
|
70
|
+
}),
|
|
71
|
+
}),
|
|
72
|
+
}),
|
|
73
|
+
match: m("/", "", {
|
|
74
|
+
hero: m("/hero", "/hero", {
|
|
75
|
+
firstAndLastName: m(
|
|
76
|
+
"/hero/:firstName/:lastName",
|
|
77
|
+
"/:firstName/:lastName",
|
|
78
|
+
{
|
|
79
|
+
spiderMan: m(
|
|
80
|
+
"/hero/peter/parker",
|
|
81
|
+
{ firstName: "peter", lastName: "parker" },
|
|
82
|
+
{
|
|
83
|
+
mainVillain: m(
|
|
84
|
+
"/hero/peter/parker/green-goblin",
|
|
85
|
+
"/green-goblin",
|
|
86
|
+
),
|
|
87
|
+
},
|
|
88
|
+
),
|
|
89
|
+
},
|
|
90
|
+
),
|
|
91
|
+
}),
|
|
92
|
+
}),
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
testPaths({
|
|
96
|
+
name: "nested params",
|
|
97
|
+
paths: p("", {
|
|
98
|
+
hero: p("/hero", {
|
|
99
|
+
firstName: p("/:firstName", {
|
|
100
|
+
spiderMan: p({ firstName: "peter" }),
|
|
101
|
+
lastName: p("/:lastName?", {
|
|
102
|
+
superMan: p(
|
|
103
|
+
{ firstName: "clark" },
|
|
104
|
+
{
|
|
105
|
+
superMan: p({ lastName: "kent" }),
|
|
106
|
+
},
|
|
107
|
+
),
|
|
108
|
+
}),
|
|
109
|
+
batMan: p(
|
|
110
|
+
{ firstName: "bruce" },
|
|
111
|
+
{
|
|
112
|
+
lastName: p("/:lastName", {
|
|
113
|
+
batMan: p(
|
|
114
|
+
{ lastName: "wayne" },
|
|
115
|
+
{
|
|
116
|
+
mainVillain: p("/joker"),
|
|
117
|
+
},
|
|
118
|
+
),
|
|
119
|
+
}),
|
|
120
|
+
},
|
|
121
|
+
),
|
|
122
|
+
}),
|
|
123
|
+
}),
|
|
124
|
+
}),
|
|
125
|
+
match: m("/", "", {
|
|
126
|
+
hero: m("/hero", "/hero", {
|
|
127
|
+
firstName: m("/hero/:firstName", "/:firstName", {
|
|
128
|
+
spiderMan: m("/hero/peter", { firstName: "peter" }),
|
|
129
|
+
lastName: m("/hero/:firstName/:lastName?", "/:lastName?", {
|
|
130
|
+
superMan: m(
|
|
131
|
+
"/hero/clark",
|
|
132
|
+
{ firstName: "clark" },
|
|
133
|
+
{
|
|
134
|
+
superMan: m("/hero/clark/kent", { lastName: "kent" }),
|
|
135
|
+
},
|
|
136
|
+
),
|
|
137
|
+
}),
|
|
138
|
+
batMan: m(
|
|
139
|
+
"/hero/bruce",
|
|
140
|
+
{ firstName: "bruce" },
|
|
141
|
+
{
|
|
142
|
+
lastName: m("/hero/bruce/:lastName", "/:lastName", {
|
|
143
|
+
batMan: m(
|
|
144
|
+
"/hero/bruce/wayne",
|
|
145
|
+
{ lastName: "wayne" },
|
|
146
|
+
{
|
|
147
|
+
mainVillain: m("/hero/bruce/wayne/joker", "/joker"),
|
|
148
|
+
},
|
|
149
|
+
),
|
|
150
|
+
}),
|
|
151
|
+
},
|
|
152
|
+
),
|
|
153
|
+
}),
|
|
154
|
+
}),
|
|
155
|
+
}),
|
|
156
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generatePath,
|
|
3
|
+
type LinkProps as _LinkProps,
|
|
4
|
+
type To,
|
|
5
|
+
} from "react-router-dom"
|
|
6
|
+
|
|
7
|
+
import { type PageState } from "../components/page/Page"
|
|
8
|
+
|
|
9
|
+
export type LinkProps<
|
|
10
|
+
Override extends "delta" | "to",
|
|
11
|
+
State extends Record<string, any> = Record<string, any>,
|
|
12
|
+
> = Omit<_LinkProps, "to" | "state"> &
|
|
13
|
+
(Override extends "delta"
|
|
14
|
+
? { to: number }
|
|
15
|
+
: { to: To; state?: State & Partial<PageState> })
|
|
16
|
+
|
|
17
|
+
export type ReadOnly<T> = {
|
|
18
|
+
readonly [P in keyof T]: T[P]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type Parameters = Record<string, string>
|
|
22
|
+
|
|
23
|
+
export interface Path {
|
|
24
|
+
_: string
|
|
25
|
+
__: string | Parameters
|
|
26
|
+
[subpath: string]: string | Path | Parameters
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function path<Subpaths extends Record<string, Path>>(
|
|
30
|
+
_: string | Parameters,
|
|
31
|
+
subpaths: Subpaths = {} as Subpaths,
|
|
32
|
+
): Path & Subpaths {
|
|
33
|
+
function updatePath(path: Path, root: boolean, params?: Parameters) {
|
|
34
|
+
if (typeof path.__ === "object") {
|
|
35
|
+
params = params ? { ...params, ...path.__ } : path.__
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const _path = typeof _ === "string" && params ? generatePath(_, params) : _
|
|
39
|
+
|
|
40
|
+
Object.entries(path).forEach(([key, subpath]) => {
|
|
41
|
+
if (key !== "__") {
|
|
42
|
+
subpath = subpath as string | Path
|
|
43
|
+
if (typeof subpath === "string") {
|
|
44
|
+
if (typeof _path === "string" && (!root || key !== "_")) {
|
|
45
|
+
let __path = _path + subpath
|
|
46
|
+
if (__path.endsWith("/")) __path = __path.slice(0, -1)
|
|
47
|
+
path[key] = __path
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
updatePath(subpath, false, params)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const path = { ...subpaths, _: typeof _ === "string" ? _ : "", __: _ }
|
|
57
|
+
if (_ === "") {
|
|
58
|
+
path._ = "/"
|
|
59
|
+
} else {
|
|
60
|
+
updatePath(path, true)
|
|
61
|
+
}
|
|
62
|
+
return path
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getParam(path: Path, key: string) {
|
|
66
|
+
return (path.__ as Parameters)[key]
|
|
67
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ValidationError,
|
|
3
|
+
type AnyObject,
|
|
4
|
+
type DefaultFromShape,
|
|
5
|
+
type InferType,
|
|
6
|
+
type MakePartial,
|
|
7
|
+
type ObjectSchema,
|
|
8
|
+
type ObjectShape,
|
|
9
|
+
type Schema,
|
|
10
|
+
type TypeFromShape,
|
|
11
|
+
type ValidateOptions,
|
|
12
|
+
} from "yup"
|
|
13
|
+
|
|
14
|
+
export type _<T> = T extends {}
|
|
15
|
+
? {
|
|
16
|
+
[k in keyof T]: T[k]
|
|
17
|
+
}
|
|
18
|
+
: T
|
|
19
|
+
|
|
20
|
+
export type MakeKeysOptional<T> = T extends AnyObject ? _<MakePartial<T>> : T
|
|
21
|
+
|
|
22
|
+
export type ObjectFromShape<Shape extends ObjectShape> = MakeKeysOptional<
|
|
23
|
+
_<TypeFromShape<Shape, AnyObject>>
|
|
24
|
+
>
|
|
25
|
+
|
|
26
|
+
export type ObjectSchemaFromShape<Shape extends ObjectShape> = ObjectSchema<
|
|
27
|
+
_<TypeFromShape<Shape, AnyObject>>,
|
|
28
|
+
AnyObject,
|
|
29
|
+
_<DefaultFromShape<Shape>>,
|
|
30
|
+
""
|
|
31
|
+
>
|
|
32
|
+
|
|
33
|
+
// -----------------------------------------------------------------------------
|
|
34
|
+
// Try Validate Sync
|
|
35
|
+
// -----------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
export type TryValidateSyncOnErrorRT<S extends Schema> = InferType<S> | void
|
|
38
|
+
|
|
39
|
+
export type TryValidateSyncRT<
|
|
40
|
+
S extends Schema,
|
|
41
|
+
OnErrorRT extends TryValidateSyncOnErrorRT<S>,
|
|
42
|
+
> = OnErrorRT extends InferType<S> ? InferType<S> : InferType<S> | undefined
|
|
43
|
+
|
|
44
|
+
export type TryValidateSyncOptions<
|
|
45
|
+
S extends Schema,
|
|
46
|
+
OnErrorRT extends TryValidateSyncOnErrorRT<S>,
|
|
47
|
+
> = ValidateOptions & {
|
|
48
|
+
onError?: (error: ValidationError) => OnErrorRT
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function tryValidateSync<S extends Schema>(
|
|
52
|
+
value: any,
|
|
53
|
+
schema: S,
|
|
54
|
+
options?: ValidateOptions,
|
|
55
|
+
): InferType<S> | undefined
|
|
56
|
+
|
|
57
|
+
export function tryValidateSync<
|
|
58
|
+
S extends Schema,
|
|
59
|
+
OnErrorRT extends TryValidateSyncOnErrorRT<S>,
|
|
60
|
+
>(
|
|
61
|
+
value: any,
|
|
62
|
+
schema: S,
|
|
63
|
+
options?: ValidateOptions & {
|
|
64
|
+
onError: (error: ValidationError) => OnErrorRT
|
|
65
|
+
},
|
|
66
|
+
): TryValidateSyncRT<S, OnErrorRT>
|
|
67
|
+
|
|
68
|
+
export function tryValidateSync<
|
|
69
|
+
S extends Schema,
|
|
70
|
+
OnErrorRT extends TryValidateSyncOnErrorRT<S>,
|
|
71
|
+
>(value: any, schema: S, options?: TryValidateSyncOptions<S, OnErrorRT>) {
|
|
72
|
+
const { onError, ...validateOptions } = options || {}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
return schema.validateSync(value, validateOptions)
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (!(error instanceof ValidationError)) throw error
|
|
78
|
+
else if (onError) return onError(error)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Middleware, Reducer } from "@reduxjs/toolkit"
|
|
2
|
+
import { configureStore } from "@reduxjs/toolkit"
|
|
3
|
+
import { setupListeners } from "@reduxjs/toolkit/query"
|
|
4
|
+
|
|
5
|
+
// The store setup is wrapped in `makeStore` to allow reuse
|
|
6
|
+
// when setting up tests that need the same store config
|
|
7
|
+
export function makeStore<R extends Reducer>({
|
|
8
|
+
reducer,
|
|
9
|
+
middlewares = [],
|
|
10
|
+
preloadedState = {},
|
|
11
|
+
}: {
|
|
12
|
+
reducer: R
|
|
13
|
+
middlewares?: Middleware[]
|
|
14
|
+
preloadedState?: Partial<ReturnType<R>>
|
|
15
|
+
}) {
|
|
16
|
+
const store = configureStore({
|
|
17
|
+
reducer,
|
|
18
|
+
// Adding the api middleware enables caching, invalidation, polling,
|
|
19
|
+
// and other useful features of `rtk-query`.
|
|
20
|
+
middleware: getDefaultMiddleware => {
|
|
21
|
+
return getDefaultMiddleware().concat(middlewares)
|
|
22
|
+
},
|
|
23
|
+
preloadedState,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// configure listeners using the provided defaults
|
|
27
|
+
// optional, but required for `refetchOnFocus`/`refetchOnReconnect` behaviors
|
|
28
|
+
setupListeners(store.dispatch)
|
|
29
|
+
|
|
30
|
+
return store
|
|
31
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Middleware, Reducer, Store } from "@reduxjs/toolkit"
|
|
2
|
+
import type { RenderOptions } from "@testing-library/react"
|
|
3
|
+
import { render } from "@testing-library/react"
|
|
4
|
+
import userEvent from "@testing-library/user-event"
|
|
5
|
+
import type { PropsWithChildren, ReactElement } from "react"
|
|
6
|
+
import { Provider } from "react-redux"
|
|
7
|
+
|
|
8
|
+
import { makeStore } from "./store"
|
|
9
|
+
|
|
10
|
+
export function renderWithUser(
|
|
11
|
+
ui: ReactElement,
|
|
12
|
+
renderOptions: RenderOptions = {},
|
|
13
|
+
) {
|
|
14
|
+
return {
|
|
15
|
+
user: userEvent.setup(),
|
|
16
|
+
...render(ui, renderOptions),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Renders the given React element with Redux Provider and custom store.
|
|
22
|
+
* This function is useful for testing components that are connected to the
|
|
23
|
+
* Redux store.
|
|
24
|
+
*
|
|
25
|
+
* @param ui - The React component or element to render.
|
|
26
|
+
* @param reducer - The root reducer to use for the store.
|
|
27
|
+
* @param extendedRenderOptions - Optional configuration options for rendering.
|
|
28
|
+
* This includes `preloadedState` for initial Redux state and `store` for a
|
|
29
|
+
* specific Redux store instance. Any additional properties are passed to React
|
|
30
|
+
* Testing Library's render function.
|
|
31
|
+
* @returns An object containing the Redux store used in the render, User event
|
|
32
|
+
* API for simulating user interactions in tests, and all of React Testing
|
|
33
|
+
* Library's query functions for testing the component.
|
|
34
|
+
*/
|
|
35
|
+
export function renderWithStore<R extends Reducer>(
|
|
36
|
+
ui: ReactElement,
|
|
37
|
+
reducer: R,
|
|
38
|
+
extendedRenderOptions: RenderOptions & {
|
|
39
|
+
/**
|
|
40
|
+
* The middlewares used to create the Redux store.
|
|
41
|
+
*/
|
|
42
|
+
middlewares?: Middleware[]
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Defines a specific portion or the entire initial state for the Redux store.
|
|
46
|
+
* This is particularly useful for initializing the state in a
|
|
47
|
+
* controlled manner during testing, allowing components to be rendered
|
|
48
|
+
* with predetermined state conditions.
|
|
49
|
+
*/
|
|
50
|
+
preloadedState?: Partial<ReturnType<R>>
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Allows the use of a specific Redux store instance instead of a
|
|
54
|
+
* default or global store. This flexibility is beneficial when
|
|
55
|
+
* testing components with unique store requirements or when isolating
|
|
56
|
+
* tests from a global store state. The custom store should be configured
|
|
57
|
+
* to match the structure and middleware of the store used by the application.
|
|
58
|
+
*
|
|
59
|
+
* @default makeStore({reducer,middlewares,preloadedState})
|
|
60
|
+
*/
|
|
61
|
+
store?: Store
|
|
62
|
+
} = {},
|
|
63
|
+
) {
|
|
64
|
+
const {
|
|
65
|
+
middlewares,
|
|
66
|
+
preloadedState,
|
|
67
|
+
// Automatically create a store instance if no store was passed in
|
|
68
|
+
store = makeStore({ reducer, middlewares, preloadedState }),
|
|
69
|
+
...renderOptions
|
|
70
|
+
} = extendedRenderOptions
|
|
71
|
+
|
|
72
|
+
const Wrapper = ({ children }: PropsWithChildren) => (
|
|
73
|
+
<Provider store={store}>{children}</Provider>
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
// Return an object with the store and all of RTL's query functions
|
|
77
|
+
return {
|
|
78
|
+
store,
|
|
79
|
+
user: userEvent.setup(),
|
|
80
|
+
...render(ui, { wrapper: Wrapper, ...renderOptions }),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type React from "react"
|
|
2
|
+
import { Divider, type DividerProps, type ThemeOptions } from "@mui/material"
|
|
3
|
+
import { type CommonProps } from "@mui/material/OverridableComponent"
|
|
4
|
+
|
|
5
|
+
import _components from "../theme/components"
|
|
6
|
+
|
|
7
|
+
export function insertDividerBetweenElements({
|
|
8
|
+
elements,
|
|
9
|
+
dividerProps,
|
|
10
|
+
}: {
|
|
11
|
+
elements: React.ReactElement[]
|
|
12
|
+
dividerProps?: DividerProps
|
|
13
|
+
}): React.ReactElement[] {
|
|
14
|
+
return elements.map((element, index) => (
|
|
15
|
+
<>
|
|
16
|
+
{element}
|
|
17
|
+
{index !== elements.length - 1 ? (
|
|
18
|
+
<Divider {...dividerProps} />
|
|
19
|
+
) : undefined}
|
|
20
|
+
</>
|
|
21
|
+
))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getStyleOverrides(
|
|
25
|
+
ownerState: CommonProps,
|
|
26
|
+
componentKey: keyof NonNullable<ThemeOptions["components"]>,
|
|
27
|
+
muiClassName: string = "root",
|
|
28
|
+
components: ThemeOptions["components"] = _components,
|
|
29
|
+
): object {
|
|
30
|
+
if (components !== undefined) {
|
|
31
|
+
const component = components[componentKey]
|
|
32
|
+
|
|
33
|
+
if (
|
|
34
|
+
component !== undefined &&
|
|
35
|
+
"styleOverrides" in component &&
|
|
36
|
+
typeof component.styleOverrides === "object" &&
|
|
37
|
+
muiClassName in component.styleOverrides
|
|
38
|
+
) {
|
|
39
|
+
const muiClass = (component.styleOverrides as Record<string, any>)[
|
|
40
|
+
muiClassName
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
switch (typeof muiClass) {
|
|
44
|
+
case "function":
|
|
45
|
+
return muiClass({ ownerState })
|
|
46
|
+
case "object":
|
|
47
|
+
return muiClass
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getClassNames(props: CommonProps): string[] {
|
|
56
|
+
return props.className?.split(" ") ?? []
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function includesClassNames(
|
|
60
|
+
propsOrClassNames: CommonProps | string[],
|
|
61
|
+
includes: string[],
|
|
62
|
+
): boolean {
|
|
63
|
+
const classNames = Array.isArray(propsOrClassNames)
|
|
64
|
+
? propsOrClassNames
|
|
65
|
+
: getClassNames(propsOrClassNames)
|
|
66
|
+
|
|
67
|
+
return includes.every(className => classNames.includes(className))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function matchClassNames(
|
|
71
|
+
propsOrClassNames: CommonProps | string[],
|
|
72
|
+
pattern: string | RegExp,
|
|
73
|
+
): RegExpMatchArray[] {
|
|
74
|
+
const classNames = Array.isArray(propsOrClassNames)
|
|
75
|
+
? propsOrClassNames
|
|
76
|
+
: getClassNames(propsOrClassNames)
|
|
77
|
+
|
|
78
|
+
return classNames
|
|
79
|
+
.map(className => className.match(pattern))
|
|
80
|
+
.filter(match => match !== null)
|
|
81
|
+
.map(match => match as RegExpMatchArray)
|
|
82
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function configureFreshworksWidget(display: "open" | "hide"): void {
|
|
2
|
+
// @ts-expect-error defined in external script
|
|
3
|
+
window.FreshworksWidget(display)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function toggleOneTrustInfoDisplay(): void {
|
|
7
|
+
// @ts-expect-error defined in external script
|
|
8
|
+
window.Optanon.ToggleInfoDisplay()
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": [
|
|
6
|
+
"DOM",
|
|
7
|
+
"DOM.Iterable",
|
|
8
|
+
"ESNext"
|
|
9
|
+
],
|
|
10
|
+
"allowJs": false,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"esModuleInterop": false,
|
|
13
|
+
"allowSyntheticDefaultImports": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"module": "ESNext",
|
|
16
|
+
"moduleResolution": "bundler",
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
"isolatedModules": true,
|
|
19
|
+
"noEmit": true,
|
|
20
|
+
"jsx": "react-jsx",
|
|
21
|
+
"types": [
|
|
22
|
+
"vitest/globals",
|
|
23
|
+
"node"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"references": [
|
|
27
|
+
{
|
|
28
|
+
"path": "./tsconfig.node.json"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
package/types/fixes.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Namespace 'React' has no exported member 'StatelessComponent'
|
|
3
|
+
* in formik, react-mapbox-gl
|
|
4
|
+
*/
|
|
5
|
+
declare namespace React {
|
|
6
|
+
type StatelessComponent<P> = React.FunctionComponent<P>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare module "*.svg" {
|
|
10
|
+
const content: string
|
|
11
|
+
export default content
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* Don't care about the typings of external libraries.
|
|
16
|
+
* All libraries without typings will be imported as `any`.
|
|
17
|
+
*/
|
|
18
|
+
declare module "*" {}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import react from "@vitejs/plugin-react"
|
|
2
|
+
import { defineConfig } from "vitest/config"
|
|
3
|
+
|
|
4
|
+
// https://vitejs.dev/config/
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react()],
|
|
7
|
+
server: {
|
|
8
|
+
open: true,
|
|
9
|
+
},
|
|
10
|
+
test: {
|
|
11
|
+
globals: true,
|
|
12
|
+
environment: "jsdom",
|
|
13
|
+
setupFiles: "src/setupTests",
|
|
14
|
+
mockReset: true,
|
|
15
|
+
coverage: {
|
|
16
|
+
enabled: true,
|
|
17
|
+
provider: "istanbul",
|
|
18
|
+
reporter: ["html", "cobertura"],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
})
|