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

wger-project / react / 24400238877

14 Apr 2026 12:58PM UTC coverage: 74.753% (-0.7%) from 75.488%
24400238877

Pull #1234

github

web-flow
Merge b70152dcd into 47a6ff2da
Pull Request #1234: version 2.5

2018 of 3062 branches covered (65.9%)

Branch coverage included in aggregate %.

125 of 241 new or added lines in 29 files covered. (51.87%)

7 existing lines in 2 files now uncovered.

6077 of 7767 relevant lines covered (78.24%)

28.27 hits per line

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

82.22
/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();
17✔
40
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
    list.forEach((item: any) => {
17✔
42
        const key = keyGetter(item);
37✔
43
        const collection = map.get(key);
37✔
44
        if (!collection) {
37✔
45
            map.set(key, [item]);
17✔
46
        } else {
47
            collection.push(item);
20✔
48
        }
49
    });
50
    return map;
17✔
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;
21✔
56
    const MAX_EXERCISE_NAMES = 5;
21✔
57
    const variationId = exercises[0].variationId;
21✔
58
    const exerciseId = exercises[0].id;
21✔
59

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

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

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

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

74

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

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

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

93
    let isChecked;
94
    if (variationId === null) {
21!
UNCOV
95
        isChecked = state.newVariationExerciseId === exerciseId;
×
96
    } else {
97
        isChecked = variationId === state.variationId;
21✔
98
    }
99

100
    return (
21✔
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
49!
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) =>
21!
114
                        <p style={{ margin: 0 }} key={exercise.id}>{exercise.getTranslation().name}</p>
49✔
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
63!
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();
17✔
138
    const exercisesQuery = useExercisesQuery();
17✔
139
    const [state, dispatch] = useExerciseSubmissionStateValue();
17✔
140

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

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

154
    return <>
17✔
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
                sx={{ display: "flex", justifyContent: "end" }}
165
                size={{
166
                    xs: 12,
167
                    sm: 6
168
                }}>
169
                <TextField
170
                    label={t('name')}
171
                    helperText={t('exercises.filterVariations')}
172
                    variant="standard"
173
                    onChange={(event) => setSearchTerms(event.target.value)}
9✔
174
                    slotProps={{
175
                        input: {
176
                            startAdornment: (
177
                                <InputAdornment position="start">
178
                                    <SearchIcon />
179
                                </InputAdornment>
180
                            ),
181
                        }
182
                    }}
183
                />
184
            </Grid>
185
        </Grid>
186

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

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

212
        <Grid container>
213
            <Grid sx={{ display: "flex", justifyContent: "end" }} size={12}>
214
                <Box sx={{ mb: 2 }}>
215
                    <div>
216
                        <Button
217
                            disabled={false}
218
                            onClick={onBack}
219
                            sx={{ mt: 1, mr: 1 }}
220
                        >
221
                            {t('goBack')}
222
                        </Button>
223
                        <Button
224
                            variant="contained"
225
                            onClick={onContinue}
226
                            sx={{ mt: 1, mr: 1 }}
227
                        >
228
                            {t('continue')}
229
                        </Button>
230
                    </div>
231
                </Box>
232
            </Grid>
233
        </Grid>
234
    </>;
235
};
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