@wger-project/react-components 25.12.5 → 26.2.26
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/build/assets/ajax-loader.gif +0 -0
- package/build/assets/index.css +1 -1
- package/build/assets/slick.svg +14 -0
- package/build/locales/ar/translation.json +347 -236
- package/build/locales/cs/translation.json +257 -253
- package/build/locales/de/translation.json +13 -3
- package/build/locales/en/translation.json +4 -1
- package/build/locales/es/translation.json +26 -4
- package/build/locales/fr/translation.json +10 -1
- package/build/locales/hi/translation.json +4 -1
- package/build/locales/hr/translation.json +15 -3
- package/build/locales/mk/translation.json +138 -0
- package/build/locales/nl/translation.json +118 -11
- package/build/locales/pt/translation.json +338 -338
- package/build/locales/pt_BR/translation.json +15 -3
- package/build/locales/ru/translation.json +43 -3
- package/build/locales/ta/translation.json +1 -1
- package/build/locales/uk/translation.json +13 -1
- package/build/locales/zh_Hans/translation.json +25 -1
- package/build/locales/zh_Hant/translation.json +255 -243
- package/build/main.js +170 -170
- package/build/main.js.map +1 -1
- package/package.json +15 -13
- package/src/components/BodyWeight/TableDashboard/TableDashboard.tsx +4 -6
- package/src/components/Calendar/Components/CalendarComponent.test.tsx +18 -22
- package/src/components/Calendar/Components/CalendarComponent.tsx +11 -8
- package/src/components/Calendar/Components/CalendarHeader.tsx +3 -3
- package/src/components/Calendar/Components/Entries.tsx +8 -3
- package/src/components/Dashboard/CalendarCard.tsx +16 -0
- package/src/components/Dashboard/ConfigurableDashboard.test.ts +129 -0
- package/src/components/Dashboard/ConfigurableDashboard.tsx +352 -89
- package/src/components/Dashboard/DashboardCard.tsx +3 -2
- package/src/components/Dashboard/MeasurementCard.test.tsx +75 -0
- package/src/components/Dashboard/MeasurementCard.tsx +101 -0
- package/src/components/Dashboard/RoutineCard.tsx +1 -1
- package/src/components/Dashboard/TrophiesCard.test.tsx +63 -0
- package/src/components/Dashboard/TrophiesCard.tsx +84 -0
- package/src/components/Dashboard/WeightCard.test.tsx +0 -10
- package/src/components/Measurements/Screens/MeasurementCategoryOverview.tsx +1 -1
- package/src/components/Measurements/models/Category.ts +13 -2
- package/src/components/Measurements/models/Entry.ts +13 -2
- package/src/components/Trophies/components/TrophiesDetail.test.tsx +34 -0
- package/src/components/Trophies/components/TrophiesDetail.tsx +88 -0
- package/src/components/Trophies/models/trophy.test.ts +33 -0
- package/src/components/Trophies/models/trophy.ts +75 -0
- package/src/components/Trophies/models/userTrophy.test.ts +38 -0
- package/src/components/Trophies/models/userTrophy.ts +67 -0
- package/src/components/Trophies/models/userTrophyProgression.test.ts +43 -0
- package/src/components/Trophies/models/userTrophyProgression.ts +68 -0
- package/src/components/Trophies/queries/trophies.ts +31 -0
- package/src/components/Trophies/services/trophies.ts +22 -0
- package/src/components/Trophies/services/userTrophies.ts +33 -0
- package/src/components/Trophies/services/userTrophyProgression.ts +16 -0
- package/src/components/WorkoutRoutines/Detail/RoutineDetail.tsx +1 -1
- package/src/components/WorkoutRoutines/Detail/TemplateDetail.tsx +1 -1
- package/src/components/WorkoutRoutines/models/Routine.test.ts +17 -0
- package/src/components/WorkoutRoutines/models/Routine.ts +20 -3
- package/src/components/WorkoutRoutines/widgets/RoutineDetailsCard.tsx +1 -1
- package/src/components/index.ts +0 -2
- package/src/pages/Calendar/index.tsx +2 -2
- package/src/pages/WeightOverview/index.tsx +1 -1
- package/src/routes.tsx +6 -1
- package/src/services/measurements.ts +10 -17
- package/src/tests/trophies/trophiesTestData.ts +80 -0
- package/src/utils/consts.ts +18 -3
- package/src/utils/url.test.ts +32 -1
- package/src/utils/url.ts +24 -3
- package/src/components/Carousel/carousel.module.css +0 -43
- package/src/components/Carousel/carousel.module.css.map +0 -1
- package/src/components/Carousel/carousel.module.scss +0 -46
- package/src/components/Carousel/index.tsx +0 -66
- package/src/components/Dashboard/GoalCard.tsx +0 -71
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"exports": {
|
|
12
12
|
".": "./build/index.js"
|
|
13
13
|
},
|
|
14
|
-
"version": "
|
|
14
|
+
"version": "26.2.26",
|
|
15
15
|
"repository": "https://github.com/wger-project/react",
|
|
16
16
|
"type": "module",
|
|
17
17
|
"publishConfig": {
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
"@emotion/react": "^11.14.0",
|
|
23
23
|
"@emotion/styled": "^11.14.1",
|
|
24
24
|
"@hello-pangea/dnd": "^18.0.1",
|
|
25
|
-
"@mui/icons-material": "^7.3.
|
|
25
|
+
"@mui/icons-material": "^7.3.7",
|
|
26
26
|
"@mui/lab": "^7.0.1-beta.18",
|
|
27
27
|
"@mui/material": "^7.3.4",
|
|
28
28
|
"@mui/system": "^7.3.3",
|
|
29
|
-
"@mui/x-data-grid": "^8.
|
|
30
|
-
"@mui/x-date-pickers": "^8.
|
|
29
|
+
"@mui/x-data-grid": "^8.26.0",
|
|
30
|
+
"@mui/x-date-pickers": "^8.26.0",
|
|
31
31
|
"@tanstack/react-query": "^5.90.2",
|
|
32
32
|
"@vitejs/plugin-react": "^5.1.1",
|
|
33
33
|
"axios": "^1.12.2",
|
|
@@ -38,14 +38,16 @@
|
|
|
38
38
|
"i18next-http-backend": "^3.0.2",
|
|
39
39
|
"luxon": "^3.7.2",
|
|
40
40
|
"react": "^19.2.0",
|
|
41
|
-
"react-dom": "^19.2.
|
|
42
|
-
"react-grid-layout": "^
|
|
41
|
+
"react-dom": "^19.2.4",
|
|
42
|
+
"react-grid-layout": "^2.2.2",
|
|
43
43
|
"react-i18next": "^16.3.3",
|
|
44
|
-
"react-is": "^19.2.
|
|
44
|
+
"react-is": "^19.2.4",
|
|
45
45
|
"react-responsive": "^10.0.1",
|
|
46
|
-
"react-router-dom": "^7.
|
|
46
|
+
"react-router-dom": "^7.12.0",
|
|
47
47
|
"react-simple-wysiwyg": "^3.4.1",
|
|
48
|
+
"react-slick": "^0.31.0",
|
|
48
49
|
"recharts": "^3.4.1",
|
|
50
|
+
"slick-carousel": "^1.8.1",
|
|
49
51
|
"slug": "^9.1.0",
|
|
50
52
|
"typescript": "^5.9.3",
|
|
51
53
|
"vite-tsconfig-paths": "^5.1.4",
|
|
@@ -65,25 +67,25 @@
|
|
|
65
67
|
"@types/node": "^22.18.9",
|
|
66
68
|
"@types/react": "^19.2.5",
|
|
67
69
|
"@types/react-dom": "^19.2.3",
|
|
68
|
-
"@types/react-grid-layout": "^1.3.5",
|
|
69
70
|
"@types/react-is": "^19.2.0",
|
|
71
|
+
"@types/react-slick": "^0.23.13",
|
|
70
72
|
"@types/slug": "^5.0.9",
|
|
71
73
|
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
|
72
74
|
"@typescript-eslint/parser": "^8.47.0",
|
|
73
|
-
"eslint": "^9.
|
|
75
|
+
"eslint": "^9.39.2",
|
|
74
76
|
"eslint-plugin-import": "^2.32.0",
|
|
75
77
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
76
78
|
"eslint-plugin-react": "^7.37.5",
|
|
77
79
|
"eslint-plugin-react-hooks": "^7.0.0",
|
|
78
|
-
"i18next-
|
|
80
|
+
"i18next-cli": "^1.46.0",
|
|
79
81
|
"jest": "^30.2.0",
|
|
80
82
|
"jest-environment-jsdom": "^30.2.0",
|
|
81
83
|
"jsdom": "^27.2.0",
|
|
82
84
|
"ts-jest": "^29.4.5",
|
|
83
85
|
"typescript-eslint": "^8.46.0",
|
|
84
|
-
"vite": "^7.
|
|
86
|
+
"vite": "^7.3.1",
|
|
85
87
|
"vite-plugin-eslint": "^1.8.1",
|
|
86
|
-
"vitest": "^
|
|
88
|
+
"vitest": "^4.0.18",
|
|
87
89
|
"webpack-bundle-analyzer": "^4.10.2"
|
|
88
90
|
},
|
|
89
91
|
"scripts": {
|
|
@@ -41,17 +41,15 @@ export const WeightTableDashboard = ({ weights }: WeightTableProps) => {
|
|
|
41
41
|
<Table size={"small"}>
|
|
42
42
|
<TableHead>
|
|
43
43
|
<TableRow>
|
|
44
|
-
<TableCell
|
|
45
|
-
<TableCell
|
|
44
|
+
<TableCell>{t('date')}</TableCell>
|
|
45
|
+
<TableCell>{t('weight')}</TableCell>
|
|
46
46
|
</TableRow>
|
|
47
47
|
</TableHead>
|
|
48
48
|
<TableBody>
|
|
49
49
|
{filteredWeight.map((row) => (
|
|
50
50
|
<TableRow key={row.date.toISOString()}>
|
|
51
|
-
<TableCell
|
|
52
|
-
|
|
53
|
-
</TableCell>
|
|
54
|
-
<TableCell align="center">{row.weight}</TableCell>
|
|
51
|
+
<TableCell>{dateTimeToLocale(row.date)}</TableCell>
|
|
52
|
+
<TableCell>{row.weight}</TableCell>
|
|
55
53
|
</TableRow>
|
|
56
54
|
))}
|
|
57
55
|
</TableBody>
|
|
@@ -33,33 +33,29 @@ describe('CalendarComponent', () => {
|
|
|
33
33
|
beforeEach(() => {
|
|
34
34
|
|
|
35
35
|
(getWeights as jest.Mock).mockImplementation(() => Promise.resolve([
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
testWorkoutSession
|
|
45
|
-
]
|
|
36
|
+
new WeightEntry(
|
|
37
|
+
new Date(currentYear, currentMonth, 2, 12, 0),
|
|
38
|
+
70
|
|
39
|
+
),
|
|
40
|
+
]));
|
|
41
|
+
|
|
42
|
+
(getSessions as jest.Mock).mockImplementation(() => Promise.resolve(
|
|
43
|
+
[testWorkoutSession]
|
|
46
44
|
));
|
|
47
45
|
|
|
48
46
|
(getMeasurementCategories as jest.Mock).mockImplementation(() => Promise.resolve([
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
));
|
|
47
|
+
new MeasurementCategory(
|
|
48
|
+
1,
|
|
49
|
+
"Body Fat",
|
|
50
|
+
"%",
|
|
51
|
+
[new MeasurementEntry(1, 1, new Date(currentYear, currentMonth, 1, 12, 0), 20, "Normal")]
|
|
52
|
+
),
|
|
53
|
+
]));
|
|
57
54
|
|
|
58
55
|
(getNutritionalDiaryEntries as jest.Mock).mockImplementation(() => Promise.resolve([
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
));
|
|
56
|
+
TEST_DIARY_ENTRY_1,
|
|
57
|
+
TEST_DIARY_ENTRY_2,
|
|
58
|
+
]));
|
|
63
59
|
|
|
64
60
|
testQueryClient.clear();
|
|
65
61
|
});
|
|
@@ -25,7 +25,7 @@ export interface DayProps {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
const CalendarComponent = () => {
|
|
28
|
+
const CalendarComponent = (props: { showBorder?: boolean }) => {
|
|
29
29
|
const [t] = useTranslation();
|
|
30
30
|
|
|
31
31
|
const currentDate = new Date();
|
|
@@ -35,6 +35,8 @@ const CalendarComponent = () => {
|
|
|
35
35
|
const startOfMonth = new Date(currentYear, currentMonth, 1);
|
|
36
36
|
const endOfMonth = new Date(currentYear, currentMonth + 1, 0);
|
|
37
37
|
|
|
38
|
+
const showBorder = props.showBorder ?? true;
|
|
39
|
+
|
|
38
40
|
|
|
39
41
|
const weightsQuery = useBodyWeightQuery();
|
|
40
42
|
const sessionQuery = useSessionsQuery({
|
|
@@ -175,14 +177,15 @@ const CalendarComponent = () => {
|
|
|
175
177
|
display: 'flex',
|
|
176
178
|
gap: 2,
|
|
177
179
|
flexDirection: { xs: 'column', md: 'row' },
|
|
178
|
-
height: { xs: 'auto', md: 'calc(100vh - 130px)' },
|
|
180
|
+
// height: { xs: 'auto', md: 'calc(100vh - 130px)' },
|
|
179
181
|
width: '100%',
|
|
180
182
|
}}>
|
|
181
183
|
<Card sx={{
|
|
184
|
+
boxShadow: showBorder ? undefined : 'none',
|
|
182
185
|
width: { xs: 'auto', md: '65%' },
|
|
183
186
|
height: { xs: 'auto', md: '100%' },
|
|
184
|
-
m: { xs: 0, sm: 1, md: 2 },
|
|
185
|
-
p: { xs: 1, sm: 1.5, md: 2 },
|
|
187
|
+
// m: { xs: 0, sm: 1, md: 2 },
|
|
188
|
+
// p: { xs: 1, sm: 1.5, md: 2 },
|
|
186
189
|
display: 'flex',
|
|
187
190
|
flexDirection: 'column'
|
|
188
191
|
}}>
|
|
@@ -193,7 +196,7 @@ const CalendarComponent = () => {
|
|
|
193
196
|
<div style={{
|
|
194
197
|
display: 'flex',
|
|
195
198
|
alignItems: 'center',
|
|
196
|
-
marginBottom: '16px'
|
|
199
|
+
// marginBottom: '16px'
|
|
197
200
|
}}>
|
|
198
201
|
<CalendarMonthIcon style={{
|
|
199
202
|
width: isMobile ? '28px' : '32px',
|
|
@@ -235,14 +238,14 @@ const CalendarComponent = () => {
|
|
|
235
238
|
sx={{
|
|
236
239
|
width: { xs: 'auto', md: '65%' },
|
|
237
240
|
height: { xs: '60%', md: '100%' },
|
|
238
|
-
m: { xs: 0, sm: 1, md: 2 },
|
|
239
|
-
p: { xs: 1, sm: 1.5, md: 2 }
|
|
241
|
+
// m: { xs: 0, sm: 1, md: 2 },
|
|
242
|
+
// p: { xs: 1, sm: 1.5, md: 2 }
|
|
240
243
|
}}
|
|
241
244
|
>
|
|
242
245
|
<LoadingPlaceholder />
|
|
243
246
|
</Card>}
|
|
244
247
|
|
|
245
|
-
{isSuccess && <Entries selectedDay={selectedDay} />}
|
|
248
|
+
{isSuccess && <Entries selectedDay={selectedDay} showBorder={showBorder} />}
|
|
246
249
|
</Box>
|
|
247
250
|
);
|
|
248
251
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Button, Typography } from '@mui/material';
|
|
3
|
-
import {useTranslation} from "react-i18next";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
4
|
|
|
5
5
|
interface CalendarHeaderProps {
|
|
6
6
|
currentMonth: number;
|
|
@@ -9,7 +9,7 @@ interface CalendarHeaderProps {
|
|
|
9
9
|
onNextMonth: () => void;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const CalendarHeader: React.FC<CalendarHeaderProps> = ({currentMonth, currentYear, onPrevMonth, onNextMonth,}) => {
|
|
12
|
+
const CalendarHeader: React.FC<CalendarHeaderProps> = ({ currentMonth, currentYear, onPrevMonth, onNextMonth, }) => {
|
|
13
13
|
const { i18n } = useTranslation();
|
|
14
14
|
const months = Array.from({ length: 12 }, (_, index) =>
|
|
15
15
|
new Date(2024, index, 1).toLocaleString(i18n.language, { month: "long" })
|
|
@@ -21,7 +21,7 @@ const CalendarHeader: React.FC<CalendarHeaderProps> = ({currentMonth, currentYea
|
|
|
21
21
|
alignItems: 'center',
|
|
22
22
|
justifyContent: 'space-between',
|
|
23
23
|
width: '100%',
|
|
24
|
-
paddingBottom: '
|
|
24
|
+
paddingBottom: '1em',
|
|
25
25
|
}}>
|
|
26
26
|
<Button variant="outlined" onClick={onPrevMonth}>
|
|
27
27
|
<
|
|
@@ -17,22 +17,27 @@ import { DayProps } from "./CalendarComponent";
|
|
|
17
17
|
|
|
18
18
|
interface LogProps {
|
|
19
19
|
selectedDay: DayProps;
|
|
20
|
+
showBorder?: boolean;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const Entries: React.FC<LogProps> = ({ selectedDay }) => {
|
|
23
|
+
const Entries: React.FC<LogProps> = ({ selectedDay, showBorder }) => {
|
|
23
24
|
const [t] = useTranslation();
|
|
24
25
|
|
|
25
26
|
const [openMeasurements, setOpenMeasurements] = React.useState(false);
|
|
26
27
|
const [openSession, setOpenSession] = React.useState(false);
|
|
27
28
|
const [openNutritionDiary, setOpenNutritionDiary] = React.useState(false);
|
|
28
29
|
|
|
30
|
+
showBorder = showBorder ?? true;
|
|
31
|
+
|
|
29
32
|
return (
|
|
30
33
|
<Card
|
|
31
34
|
sx={{
|
|
35
|
+
boxShadow: showBorder ? undefined : 'none',
|
|
32
36
|
width: { xs: 'auto', md: '45%' },
|
|
33
37
|
height: { xs: '60%', md: '100%' },
|
|
34
|
-
m: { xs: 0, sm: 1, md: 2 },
|
|
35
|
-
p: { xs: 1, sm: 1.5, md: 2 }
|
|
38
|
+
// m: { xs: 0, sm: 1, md: 2 },
|
|
39
|
+
// p: { xs: 1, sm: 1.5, md: 2 }
|
|
40
|
+
|
|
36
41
|
}}
|
|
37
42
|
>
|
|
38
43
|
<CardHeader
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import CalendarComponent from "components/Calendar/Components/CalendarComponent";
|
|
2
|
+
import { DashboardCard } from "components/Dashboard/DashboardCard";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export const CalendarCard = () => {
|
|
8
|
+
const { t } = useTranslation();
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<DashboardCard title={t("calendar")}>
|
|
13
|
+
<CalendarComponent showBorder={false} />
|
|
14
|
+
</DashboardCard>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { AVAILABLE_WIDGETS, loadDashboardState, } from './ConfigurableDashboard';
|
|
2
|
+
|
|
3
|
+
describe('loadDashboardState migration', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
localStorage.clear();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test('filters unknown ids and falls back to all widgets when none valid', () => {
|
|
9
|
+
// Arrange
|
|
10
|
+
const data = {
|
|
11
|
+
"version": 1,
|
|
12
|
+
"selectedWidgetIds": ["foo"],
|
|
13
|
+
"layouts": {
|
|
14
|
+
"lg": [
|
|
15
|
+
{ "i": "foo", "w": 4, "h": 5, "x": 0, "y": 0, "minW": 3, "minH": 2 },
|
|
16
|
+
],
|
|
17
|
+
"md": [],
|
|
18
|
+
"sm": [],
|
|
19
|
+
"xs": []
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
localStorage.setItem('dashboard-state', JSON.stringify(data));
|
|
23
|
+
|
|
24
|
+
// Act
|
|
25
|
+
const res = loadDashboardState();
|
|
26
|
+
|
|
27
|
+
// Assert
|
|
28
|
+
expect(res).not.toBeNull();
|
|
29
|
+
const allowed = AVAILABLE_WIDGETS.map((w) => w.id).slice().sort();
|
|
30
|
+
expect(res!.selectedWidgetIds.slice().sort()).toEqual(allowed);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('removes unknown IDs', () => {
|
|
34
|
+
// Arrange
|
|
35
|
+
const data = {
|
|
36
|
+
"version": 1,
|
|
37
|
+
"selectedWidgetIds": ["routine", "foo"],
|
|
38
|
+
"layouts": {
|
|
39
|
+
"lg": [
|
|
40
|
+
{ "i": "routine", "w": 4, "h": 5, "x": 0, "y": 0, "minW": 3, "minH": 2 },
|
|
41
|
+
{ "i": "foo", "w": 4, "h": 5, "x": 0, "y": 0, "minW": 3, "minH": 2 },
|
|
42
|
+
],
|
|
43
|
+
"md": [],
|
|
44
|
+
"sm": [],
|
|
45
|
+
"xs": []
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
localStorage.setItem('dashboard-state', JSON.stringify(data));
|
|
49
|
+
|
|
50
|
+
// Act
|
|
51
|
+
const res = loadDashboardState();
|
|
52
|
+
|
|
53
|
+
// Assert
|
|
54
|
+
expect(res).not.toBeNull();
|
|
55
|
+
expect(res!.selectedWidgetIds).not.toContain('foo');
|
|
56
|
+
expect(res!.selectedWidgetIds).toContain('routine');
|
|
57
|
+
const lg = res?.layouts?.lg ?? [];
|
|
58
|
+
expect(lg.length).toEqual(1);
|
|
59
|
+
expect(lg[0]?.i).toEqual('routine');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('migrates old top-level structure', () => {
|
|
63
|
+
// Arrange
|
|
64
|
+
const oldData = {
|
|
65
|
+
lg: [{ i: 'routine', x: 0, y: 0, w: 4, h: 5 }, { i: 'weight', x: 4, y: 0, w: 4, h: 5 }],
|
|
66
|
+
md: [],
|
|
67
|
+
sm: []
|
|
68
|
+
};
|
|
69
|
+
localStorage.setItem('dashboard-state', JSON.stringify(oldData));
|
|
70
|
+
|
|
71
|
+
// Act
|
|
72
|
+
const res = loadDashboardState();
|
|
73
|
+
|
|
74
|
+
// Assert
|
|
75
|
+
expect(res).not.toBeNull();
|
|
76
|
+
expect(res!.version).toBe(1);
|
|
77
|
+
// selectedWidgetIds should contain the extracted ids
|
|
78
|
+
expect(res!.selectedWidgetIds).toEqual(expect.arrayContaining(['routine', 'weight']));
|
|
79
|
+
|
|
80
|
+
const savedRaw = localStorage.getItem('dashboard-state');
|
|
81
|
+
expect(savedRaw).not.toBeNull();
|
|
82
|
+
const saved = JSON.parse(savedRaw!);
|
|
83
|
+
expect(saved.version).toBe(1);
|
|
84
|
+
expect(Array.isArray(saved.selectedWidgetIds)).toBe(true);
|
|
85
|
+
expect(saved.selectedWidgetIds).toEqual(expect.arrayContaining(['routine', 'weight']));
|
|
86
|
+
expect(saved.layouts).toBeDefined();
|
|
87
|
+
expect(Array.isArray(saved.layouts.lg)).toBe(true);
|
|
88
|
+
expect(saved.layouts.lg.length).toBe(2);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('adds newly available widgets to selected when they are neither selected nor hidden', () => {
|
|
92
|
+
// Arrange: saved state has 'routine' selected and 'nutrition' hidden, other widgets are missing
|
|
93
|
+
const data = {
|
|
94
|
+
version: 1,
|
|
95
|
+
selectedWidgetIds: ['routine'],
|
|
96
|
+
hiddenWidgetIds: ['nutrition'],
|
|
97
|
+
layouts: {
|
|
98
|
+
lg: [
|
|
99
|
+
{ i: 'routine', w: 4, h: 5, x: 0, y: 0, minW: 3, minH: 2 }
|
|
100
|
+
],
|
|
101
|
+
md: [],
|
|
102
|
+
sm: [],
|
|
103
|
+
xs: []
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
localStorage.setItem('dashboard-state', JSON.stringify(data));
|
|
107
|
+
|
|
108
|
+
// Act
|
|
109
|
+
const res = loadDashboardState();
|
|
110
|
+
|
|
111
|
+
// Assert
|
|
112
|
+
expect(res).not.toBeNull();
|
|
113
|
+
expect(res!.hiddenWidgetIds).toEqual(['nutrition']);
|
|
114
|
+
expect(res!.selectedWidgetIds).toEqual(expect.arrayContaining(['routine']));
|
|
115
|
+
const expectedSelected = AVAILABLE_WIDGETS.map((w) => w.id).filter((id) => id !== 'nutrition');
|
|
116
|
+
expectedSelected.forEach((id) => {
|
|
117
|
+
expect(res!.selectedWidgetIds).toEqual(expect.arrayContaining([id]));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// And the persisted state should also include the newly added widgets
|
|
121
|
+
const savedRaw = localStorage.getItem('dashboard-state');
|
|
122
|
+
expect(savedRaw).not.toBeNull();
|
|
123
|
+
const saved = JSON.parse(savedRaw!);
|
|
124
|
+
expectedSelected.forEach((id) => {
|
|
125
|
+
expect(saved.selectedWidgetIds).toEqual(expect.arrayContaining([id]));
|
|
126
|
+
});
|
|
127
|
+
expect(saved.hiddenWidgetIds).toEqual(expect.arrayContaining(['nutrition']));
|
|
128
|
+
});
|
|
129
|
+
});
|