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

DLR-SC / ESID / 15845945232

24 Jun 2025 08:55AM UTC coverage: 51.517% (+0.5%) from 51.024%
15845945232

Pull #414

github

fifth-island
fix(tests): resolve test failures in CI

This commit fixes a series of cascading test failures that occurred after introducing the semantic search feature.

- Wraps test components in ArticleDataProvider to provide necessary context.

- Removes a duplicate data-testid that caused an ambiguous element error.
Pull Request #414: feat: Implement basic article search feature

418 of 539 branches covered (77.55%)

Branch coverage included in aggregate %.

353 of 582 new or added lines in 15 files covered. (60.65%)

102 existing lines in 6 files now uncovered.

4115 of 8260 relevant lines covered (49.82%)

4.48 hits per line

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

0.0
/src/components/ScenarioComponents/ScenarioLibrary.tsx
1
// SPDX-FileCopyrightText: 2024 German Aerospace Center (DLR)
2
// SPDX-License-Identifier: Apache-2.0
3

4
import React, {useCallback, useContext, useMemo, useRef, useState} from 'react';
×
5
import Paper from '@mui/material/Paper';
×
6
import Popper from '@mui/material/Popper';
×
7
import Box from '@mui/material/Box';
×
8
import Button from '@mui/material/Button';
×
9
import useTheme from '@mui/material/styles/useTheme';
×
10
import {useTranslation} from 'react-i18next';
×
11
import Typography from '@mui/material/Typography';
×
12
import Divider from '@mui/material/Divider';
×
13
import ClickAwayListener from '@mui/material/ClickAwayListener';
×
14
import {useAppDispatch, useAppSelector} from 'store/hooks';
×
15
import IconButton from '@mui/material/IconButton';
×
16
import Close from '@mui/icons-material/Close';
×
17
import CardTitle from './CardsComponents/MainCard/CardTitle';
×
18
import WebAssetOff from '@mui/icons-material/WebAssetOff';
×
19
import LibraryAddOutlined from '@mui/icons-material/LibraryAddOutlined';
×
20
import Dialog from '@mui/material/Dialog';
×
21
import NewScenarioDialog, {NewScenarioData} from './NewScenarioDialog';
×
22
import {InterventionTemplates, Models, NodeLists, Scenario} from 'store/services/APITypes';
23
import {useGetMultiScenariosQuery} from 'store/services/scenarioApi';
×
24
import {ScenarioVisibility, updateScenario} from 'store/DataSelectionSlice';
×
25
import {setScenarioColors} from 'store/UserPreferenceSlice';
×
26
import {DataContext} from 'context/SelectedDataContext';
×
27

UNCOV
28
export default function ScenarioLibrary(): JSX.Element {
×
UNCOV
29
  const dispatch = useAppDispatch();
×
UNCOV
30
  const {t} = useTranslation();
×
UNCOV
31
  const theme = useTheme();
×
32

UNCOV
33
  const {scenarios, simulationModels, npis, nodeLists} = useContext(DataContext)!;
×
UNCOV
34
  const scenariosState = useAppSelector((state) => state.dataSelection.scenarios);
×
35

36
  const {data: completeScenarios} = useGetMultiScenariosQuery(scenarios?.map((s) => s.id) ?? [], {skip: !scenarios});
×
37

38
  const scenarioCreated = useCallback(
×
UNCOV
39
    (data: NewScenarioData) => {
×
40
      const result = Object.values(completeScenarios ?? {}).find((scenario: Scenario) => {
×
UNCOV
41
        if (scenario.name === 'casedata') {
×
UNCOV
42
          return false;
×
UNCOV
43
        }
×
44
        if (scenario.linkedInterventions.length === data.npis.length) {
×
45
          return data.npis.every((id) =>
×
46
            scenario.linkedInterventions.find((intervention) => intervention.interventionId === id)
×
47
          );
×
48
        }
×
UNCOV
49
        return false;
×
50
      });
×
51

52
      if (result) {
×
53
        dispatch(
×
UNCOV
54
          updateScenario({
×
55
            id: result.id,
×
UNCOV
56
            state: {
×
57
              name: data.name,
×
58
              description: data.description,
×
59
              visibility: ScenarioVisibility.FaceUp,
×
60
              colors: data.colors,
×
61
            },
×
62
          })
×
63
        );
×
64
        dispatch(
×
65
          setScenarioColors({
×
66
            scenarioId: result.id,
×
67
            colors: data.colors,
×
68
          })
×
69
        );
×
UNCOV
70
      }
×
71
    },
×
72
    [completeScenarios, dispatch]
×
73
  );
×
74

75
  const hiddenScenarios = useMemo(() => {
×
76
    return Object.entries(scenariosState)
×
77
      .filter(([_, value]) => value.visibility === ScenarioVisibility.InLibrary)
×
78
      .map(([key, scenario]) => ({
×
79
        id: key,
×
80
        name: scenario.name,
×
81
      }));
×
82
  }, [scenariosState]);
×
83

84
  const anchorRef = useRef<HTMLButtonElement>(null);
×
85

86
  const [open, setOpen] = useState(false);
×
87
  const handleToggle = () => {
×
88
    setOpen((prevOpen) => !prevOpen);
×
89
  };
×
90

91
  const id = open ? 'simple-popper' : undefined;
×
92
  const handleClose = (event: Event | React.SyntheticEvent) => {
×
UNCOV
93
    if (anchorRef.current?.contains(event.target as HTMLElement)) {
×
94
      return;
×
95
    }
×
96

97
    setOpen(false);
×
98
  };
×
99

100
  return (
×
101
    <Box>
×
UNCOV
102
      <Button
×
103
        ref={anchorRef}
×
UNCOV
104
        aria-describedby={id}
×
105
        type='button'
×
106
        onClick={handleToggle}
×
107
        id='scenario-add-button'
×
108
        variant='outlined'
×
UNCOV
109
        color='success'
×
110
        sx={{
×
111
          height: '244px',
×
112
          width: '200px',
×
113
          margin: theme.spacing(3),
×
114
          marginTop: theme.spacing(2),
×
UNCOV
115
          fontWeight: 'bolder',
×
116
          fontSize: '3rem',
×
117
          border: `2px ${theme.palette.primary.light} dashed`,
×
UNCOV
118
          borderRadius: '3px',
×
119
          color: theme.palette.primary.light,
×
120
          alignSelf: 'top',
×
121

122
          '&:hover': {
×
123
            border: `2px ${theme.palette.primary.light} dashed`,
×
124
            background: '#E7E7E7',
×
125
          },
×
126
        }}
×
127
        aria-label={t('scenario-library.add')}
×
128
      >
×
129
        +
130
      </Button>
×
131
      <Popper id={id} open={open} anchorEl={anchorRef.current} placement='bottom-start' sx={{zIndex: 100}}>
×
132
        <Paper>
×
133
          <ClickAwayListener onClickAway={handleClose}>
×
134
            <Box
×
135
              sx={{
×
136
                width: '930px',
×
137
                margin: theme.spacing(2),
×
138
                display: 'flex',
×
139
                flexDirection: 'column',
×
UNCOV
140
                flexGrow: '1',
×
141
                padding: theme.spacing(4),
×
142
                alignItems: 'center',
×
143
              }}
×
144
            >
145
              <Box
×
146
                id='group-filter-dialog-title-bar'
×
147
                sx={{
×
UNCOV
148
                  display: 'grid',
×
149
                  gridTemplateColumns: '1fr auto 1fr',
×
150
                  gridColumnGap: '5px',
×
151
                  alignItems: 'center',
×
152
                  justifyItems: 'center',
×
153
                  width: '100%',
×
154
                  marginBottom: theme.spacing(2),
×
155
                }}
×
156
              >
157
                <div />
×
158
                <Typography variant='h1'>{t('scenario-library.title')}</Typography>
×
159
                <IconButton color='primary' sx={{marginLeft: 'auto'}} onClick={() => setOpen(false)}>
×
160
                  <Close />
×
161
                </IconButton>
×
162
              </Box>
×
UNCOV
163
              <Divider orientation='horizontal' variant='middle' flexItem />
×
164
              <Box
×
165
                sx={{
×
166
                  display: 'grid',
×
167
                  gridTemplateColumns: 'repeat(auto-fill, 204px)',
×
168
                  gridGap: '1rem',
×
169
                  justifyContent: 'space-between',
×
170
                  width: '100%',
×
171
                  marginTop: theme.spacing(2),
×
172
                  maxHeight: '500px',
×
173
                  overflowY: 'auto',
×
174
                }}
×
175
              >
176
                <NewScenarioCard
×
177
                  models={simulationModels ?? []}
×
178
                  npis={npis ?? []}
×
179
                  nodeLists={nodeLists ?? []}
×
180
                  scenarioCreated={scenarioCreated}
×
181
                />
×
182
                {hiddenScenarios.length > 0 ? (
×
183
                  hiddenScenarios.map((scenario) => <LibraryCard key={scenario.id} {...scenario} />)
×
184
                ) : (
185
                  <Box
×
186
                    sx={{
×
187
                      margin: 'auto',
×
188
                      display: 'flex',
×
189
                      alignItems: 'center',
×
190
                      justifyContent: 'center',
×
191
                      flexDirection: 'column',
×
192
                    }}
×
193
                  >
UNCOV
194
                    <WebAssetOff color='primary' fontSize='large' />
×
195
                    <Typography variant='body1'>{t('scenario-library.no-scenarios')}</Typography>
×
196
                  </Box>
×
197
                )}
198
              </Box>
×
199
            </Box>
×
200
          </ClickAwayListener>
×
201
        </Paper>
×
UNCOV
202
      </Popper>
×
203
    </Box>
×
204
  );
UNCOV
205
}
×
206

207
function LibraryCard(props: Readonly<{id: string; name: string}>): JSX.Element {
×
208
  const dispatch = useAppDispatch();
×
209
  const theme = useTheme();
×
210
  const {t: tBackend, i18n} = useTranslation('backend');
×
211
  const scenarioColors = useAppSelector((state) => state.userPreference.scenarioColors);
×
212

213
  const handleRestore = () => {
×
UNCOV
214
    const savedColors = scenarioColors[props.id];
×
215
    dispatch(
×
216
      updateScenario({
×
217
        id: props.id,
×
UNCOV
218
        state: {
×
219
          visibility: ScenarioVisibility.FaceUp,
×
220
          colors: savedColors,
×
221
        },
×
222
      })
×
223
    );
×
224
  };
×
225

226
  return (
×
UNCOV
227
    <Box
×
228
      id={`card-root-${props.id}`}
×
229
      sx={{
×
230
        display: 'flex',
×
231
        flexDirection: 'row',
×
232
        color: theme.palette.divider,
×
UNCOV
233
        width: 'min-content',
×
234
      }}
×
235
    >
236
      <Box
×
237
        id='card-container'
×
238
        sx={{
×
239
          position: 'relative',
×
240
          zIndex: 0,
×
241
          flexGrow: 0,
×
242
          flexShrink: 0,
×
243
          width: '200px',
×
244
          boxSizing: 'border-box',
×
245
          marginX: '2px',
×
UNCOV
246
          marginY: theme.spacing(2),
×
247
          marginBottom: 0,
×
248
        }}
×
249
      >
250
        <Box
×
251
          id={`card-main-card-${props.id}`}
×
252
          sx={{
×
253
            position: 'relative',
×
254
            zIndex: 0,
×
255
            boxSizing: 'border-box',
×
UNCOV
256
            height: '244px',
×
257
            paddingTop: theme.spacing(2),
×
258
            paddingBottom: theme.spacing(2),
×
259
            border: `2px solid ${theme.palette.secondary.light}`,
×
260
            borderRadius: '3px',
×
261
            background: theme.palette.background.paper,
×
262
            color: theme.palette.secondary.main,
×
263
            cursor: 'pointer',
×
264

265
            '&:hover': {
×
266
              background: '#EEEEEEEE',
×
267
            },
×
268
          }}
×
269
          onClick={handleRestore}
×
270
        >
271
          <Box
×
272
            id={`card-front-${props.id}`}
×
273
            sx={{
×
274
              marginTop: '6px',
×
275
              marginBottom: '6px',
×
276
              height: '100%',
×
277
              display: 'flex',
×
278
              flexDirection: 'column',
×
279
            }}
×
280
          >
281
            <CardTitle
×
282
              color={theme.palette.secondary.light}
×
283
              label={
×
284
                i18n.exists(`scenario-names.${props.name}`, {ns: 'backend'})
×
UNCOV
285
                  ? tBackend(`scenario-names.${props.name}`)
×
286
                  : props.name
×
287
              }
288
            />
×
289
            <Box
×
290
              sx={{
×
UNCOV
291
                fontWeight: 'bolder',
×
292
                fontSize: '4rem',
×
293
                color: theme.palette.secondary.light,
×
294
                textAlign: 'center',
×
295
                flexGrow: 1,
×
296
                display: 'flex',
×
297
                justifyContent: 'center',
×
298
                alignItems: 'center',
×
299
              }}
×
300
              aria-label={'+'}
×
UNCOV
301
            >
×
302
              +
303
            </Box>
×
304
          </Box>
×
305
        </Box>
×
306
      </Box>
×
307
    </Box>
×
308
  );
309
}
×
310

311
function NewScenarioCard({
×
312
  models,
×
313
  npis,
×
314
  nodeLists,
×
315
  scenarioCreated,
×
316
}: {
×
317
  models: Models;
318
  npis: InterventionTemplates;
319
  nodeLists: NodeLists;
320
  scenarioCreated: (data: NewScenarioData) => void;
321
}): JSX.Element {
×
322
  const theme = useTheme();
×
UNCOV
323
  const {t} = useTranslation();
×
324
  const [newScenarioDialogOpen, setNewScenarioDialogOpen] = useState(false);
×
325

326
  return (
×
327
    <Box
×
328
      id={`new-scenario-card-root`}
×
UNCOV
329
      sx={{
×
330
        display: 'flex',
×
UNCOV
331
        flexDirection: 'row',
×
332
        color: theme.palette.divider,
×
333
        width: 'min-content',
×
334
      }}
×
335
    >
336
      <Box
×
337
        id='new-scenario-card-container'
×
UNCOV
338
        sx={{
×
UNCOV
339
          position: 'relative',
×
UNCOV
340
          zIndex: 0,
×
UNCOV
341
          flexGrow: 0,
×
342
          flexShrink: 0,
×
343
          width: '200px',
×
344
          boxSizing: 'border-box',
×
345
          marginX: '2px',
×
UNCOV
346
          marginY: theme.spacing(2),
×
347
          marginBottom: 0,
×
348
        }}
×
349
      >
350
        <Box
×
351
          id={`new-scenario-card-main-card`}
×
352
          sx={{
×
353
            position: 'relative',
×
354
            zIndex: 0,
×
355
            boxSizing: 'border-box',
×
UNCOV
356
            height: '244px',
×
357
            paddingTop: theme.spacing(2),
×
358
            paddingBottom: theme.spacing(2),
×
359
            border: `2px solid ${theme.palette.primary.main}`,
×
360
            borderRadius: '3px',
×
361
            background: theme.palette.background.paper,
×
362
            color: theme.palette.primary.main,
×
363
            cursor: 'pointer',
×
364

365
            '&:hover': {
×
366
              background: '#EEEEEEEE',
×
367
            },
×
368
          }}
×
369
          onClick={() => setNewScenarioDialogOpen(true)}
×
370
        >
371
          <Box
×
372
            id={`new-scenario-card-front`}
×
373
            sx={{
×
374
              marginTop: '6px',
×
375
              marginBottom: '6px',
×
376
              height: '100%',
×
377
              display: 'flex',
×
378
              flexDirection: 'column',
×
379
            }}
×
380
          >
381
            <CardTitle label={t('scenario-library.new-scenario')} />
×
382
            <Box
×
383
              sx={{
×
384
                fontWeight: 'bolder',
×
UNCOV
385
                fontSize: '3rem',
×
386
                color: theme.palette.primary.main,
×
387
                textAlign: 'center',
×
388
                flexGrow: 1,
×
389
                display: 'flex',
×
390
                justifyContent: 'center',
×
UNCOV
391
                alignItems: 'center',
×
392
              }}
×
393
              aria-label={t('scenario-library.new-scenario') as unknown as string}
×
394
            >
395
              <LibraryAddOutlined color='primary' fontSize='large' />
×
396
            </Box>
×
397
          </Box>
×
398
        </Box>
×
399
      </Box>
×
400
      <Dialog open={newScenarioDialogOpen} maxWidth='sm' fullWidth={true}>
×
UNCOV
401
        <NewScenarioDialog
×
402
          models={[models[0]?.name ?? 'SECIRVV']}
×
403
          npiOptions={npis.map((npi) => {
×
404
            return {
×
405
              id: npi.id,
×
406
              name: npi.name,
×
407
            };
×
408
          })}
×
409
          nodeOptions={[nodeLists[0]?.name ?? 'Districts of Germany']}
×
410
          colorOptions={theme.custom.scenarios.slice(1)}
×
411
          onSubmit={(data) => {
×
412
            if (data) {
×
413
              scenarioCreated(data);
×
414
            }
×
UNCOV
415
            setNewScenarioDialogOpen(false);
×
416
          }}
×
417
        />
×
418
      </Dialog>
×
419
    </Box>
×
420
  );
421
}
×
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