• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wger-project / react / 18411303152

10 Oct 2025 03:38PM UTC coverage: 75.275% (+0.04%) from 75.236%
18411303152

push

github

rolandgeider
Remove AddSlotEntryParams and use SlotEntry objects

1737 of 2608 branches covered (66.6%)

Branch coverage included in aggregate %.

11 of 13 new or added lines in 4 files covered. (84.62%)

146 existing lines in 36 files now uncovered.

5512 of 7022 relevant lines covered (78.5%)

28.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.89
/src/components/Exercises/Add/Step2Variations.tsx
1
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
2✔
2
import PhotoIcon from '@mui/icons-material/Photo';
2✔
3
import SearchIcon from '@mui/icons-material/Search';
2✔
4
import {
2✔
5
    Alert,
6
    AlertTitle,
7
    Avatar,
8
    AvatarGroup,
9
    Box,
10
    Button,
11
    InputAdornment,
12
    List,
13
    ListItem,
14
    ListItemButton,
15
    ListItemIcon,
16
    ListItemText,
17
    Paper,
18
    Switch,
19
    TextField,
20
    Typography
21
} from "@mui/material";
22
import Grid from '@mui/material/Grid';
2✔
23
import { LoadingPlaceholder } from "components/Core/LoadingWidget/LoadingWidget";
2✔
24
import { StepProps } from "components/Exercises/Add/AddExerciseStepper";
25
import { Exercise } from "components/Exercises/models/exercise";
26

27
import { useExercisesQuery } from "components/Exercises/queries";
2✔
28
import React, { useEffect, useState } from "react";
2✔
29
import { useTranslation } from "react-i18next";
2✔
30
import { useExerciseSubmissionStateValue } from "state";
2✔
31
import { setNewBaseVariationId, setVariationId } from "state/exerciseSubmissionReducer";
2✔
32

33
/*
34
 * Groups a list of objects by a property
35
 */
36

37
// eslint-disable-next-line @typescript-eslint/no-explicit-any
38
function groupBy(list: any[], keyGetter: Function) {
39
    const map = new Map();
18✔
40
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
    list.forEach((item: any) => {
18✔
42
        const key = keyGetter(item);
27✔
43
        const collection = map.get(key);
27✔
44
        if (!collection) {
27✔
45
            map.set(key, [item]);
16✔
46
        } else {
47
            collection.push(item);
11✔
48
        }
49
    });
50
    return map;
18✔
51
}
52

53
// New component that displays the exercise info in a ListItem
54
const ExerciseInfoListItem = ({ exercises }: { exercises: Exercise[] }) => {
2✔
55
    const MAX_EXERCISE_IMAGES = 4;
31✔
56
    const MAX_EXERCISE_NAMES = 5;
31✔
57
    const variationId = exercises[0].variationId;
31✔
58
    const exerciseId = exercises[0].id;
31✔
59

60
    const [state, dispatch] = useExerciseSubmissionStateValue();
31✔
61
    const [showMore, setShowMore] = useState<boolean>(false);
31✔
62

63
    const [stateVariationId, setStateVariationId] = useState<number | null>(state.variationId);
31✔
64
    const [stateNewVariationId, setStateNewVariationId] = useState<number | null>(state.newVariationExerciseId);
31✔
65

66
    useEffect(() => {
31✔
67
        dispatch(setVariationId(stateVariationId));
15✔
68
    }, [dispatch, stateVariationId]);
69

70
    useEffect(() => {
31✔
71
        dispatch(setNewBaseVariationId(stateNewVariationId));
15✔
72
    }, [dispatch, stateNewVariationId]);
73

74

75
    const handleToggle = (variationId: number | null, newVariationId: number | null) => () => {
31✔
76

77
        if (variationId !== null) {
6✔
78
            newVariationId = null;
3✔
79
            if (variationId === state.variationId) {
3!
UNCOV
80
                variationId = null;
×
81
            }
82
        } else {
83
            variationId = null;
3✔
84
            if (newVariationId === state.newVariationExerciseId) {
3!
UNCOV
85
                newVariationId = null;
×
86
            }
87
        }
88

89
        setStateVariationId(variationId);
6✔
90
        setStateNewVariationId(newVariationId);
6✔
91
    };
92

93
    let isChecked;
94
    if (variationId === null) {
31✔
95
        isChecked = state.newVariationExerciseId === exerciseId;
14✔
96
    } else {
97
        isChecked = variationId === state.variationId;
17✔
98
    }
99

100
    return (
31✔
101
        <ListItem disableGutters>
102
            <ListItemButton onClick={handleToggle(variationId, exerciseId)}>
103
                <ListItemIcon>
104
                    <AvatarGroup max={MAX_EXERCISE_IMAGES} spacing={"small"}>
105
                        {exercises.map((base) =>
106
                            base.mainImage
43!
107
                                ? <Avatar key={base.id} src={base.mainImage.url} />
108
                                : <Avatar key={base.id} children={<PhotoIcon />} />
109
                        )}
110
                    </AvatarGroup>
111
                </ListItemIcon>
112
                <ListItemText
113
                    primary={exercises.slice(0, showMore ? exercises.length : MAX_EXERCISE_NAMES).map((exercise) =>
31!
114
                        <p style={{ margin: 0 }} key={exercise.id}>{exercise.getTranslation().name}</p>
43✔
115
                    )} />
116

117
                <Switch
118
                    key={`variation-${variationId}`}
119
                    edge="start"
120
                    checked={isChecked}
121
                    tabIndex={-1}
122
                    disableRipple
123
                />
124

125
                {!showMore && exercises.length > MAX_EXERCISE_NAMES
93!
UNCOV
126
                    ? <ExpandMoreIcon onMouseEnter={() => setShowMore(true)} />
×
127
                    : null
128
                }
129

130
            </ListItemButton>
131
        </ListItem>
132
    );
133
};
134

135

136
export const Step2Variations = ({ onContinue, onBack }: StepProps) => {
2✔
137
    const [t] = useTranslation();
18✔
138
    const exercisesQuery = useExercisesQuery();
18✔
139
    const [state, dispatch] = useExerciseSubmissionStateValue();
18✔
140

141
    const [searchTerm, setSearchTerms] = useState<string>('');
18✔
142

143
    // Group exercises by variationId
144
    let exercises: Exercise[] = [];
18✔
145
    let groupedExercises = new Map<number, Exercise[]>();
18✔
146
    if (exercisesQuery.isSuccess) {
18!
147
        exercises = exercisesQuery.data;
18✔
148
        if (searchTerm !== '') {
18✔
149
            exercises = exercises.filter((base) => base.getTranslation().name.toLowerCase().includes(searchTerm.toLowerCase()));
27✔
150
        }
151
    }
152
    groupedExercises = groupBy(exercises.filter(b => b.variationId !== null), (b: Exercise) => b.variationId);
40✔
153

154
    return <>
18✔
155
        <Grid container>
156
            <Grid
157
                size={{
158
                    xs: 12,
159
                    sm: 6
160
                }}>
161
                <Typography>{t('exercises.whatVariationsExist')}</Typography>
162
            </Grid>
163
            <Grid
164
                display="flex"
165
                justifyContent={"end"}
166
                size={{
167
                    xs: 12,
168
                    sm: 6
169
                }}>
170
                <TextField
171
                    label={t('name')}
172
                    helperText={t('exercises.filterVariations')}
173
                    // defaultValue={state.nameEn}
174
                    variant="standard"
175
                    onChange={(event) => setSearchTerms(event.target.value)}
9✔
176
                    slotProps={{
177
                        input: {
178
                            startAdornment: (
179
                                <InputAdornment position="start">
180
                                    <SearchIcon />
181
                                </InputAdornment>
182
                            ),
183
                        }
184
                    }}
185
                />
186
            </Grid>
187
        </Grid>
188

189
        {exercisesQuery.isLoading
18!
190
            ? <LoadingPlaceholder />
191
            : <Paper elevation={2} sx={{ mt: 2 }}>
192
                <List style={{ height: "400px", overflowY: "scroll" }}>
193
                    {exercises.filter(b => b.variationId === null).map(exercise =>
40✔
194
                        <ExerciseInfoListItem
13✔
195
                            exercises={[exercise]}
196
                            key={'exercise-' + exercise.id}
197
                        />
198
                    )}
199
                    {[...groupedExercises.keys()].map(variationId =>
200
                        <ExerciseInfoListItem
16✔
201
                            exercises={groupedExercises.get(variationId)!}
202
                            key={'variation-' + variationId}
203
                        />
204
                    )}
205
                </List>
206
            </Paper>
207
        }
208

209
        <Alert severity="info" variant="filled" sx={{ mt: 2 }}>
210
            <AlertTitle>{t("exercises.identicalExercise")}</AlertTitle>
211
            {t('exercises.identicalExercisePleaseDiscard')}
212
        </Alert>
213

214
        <Grid container>
215
            <Grid display="flex" justifyContent={"end"} size={12}>
216
                <Box sx={{ mb: 2 }}>
217
                    <div>
218
                        <Button
219
                            disabled={false}
220
                            onClick={onBack}
221
                            sx={{ mt: 1, mr: 1 }}
222
                        >
223
                            {t('goBack')}
224
                        </Button>
225
                        <Button
226
                            variant="contained"
227
                            onClick={onContinue}
228
                            sx={{ mt: 1, mr: 1 }}
229
                        >
230
                            {t('continue')}
231
                        </Button>
232
                    </div>
233
                </Box>
234
            </Grid>
235
        </Grid>
236
    </>;
237
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc