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

mendersoftware / mender-server / 10881

07 Dec 2025 08:53PM UTC coverage: 96.446% (+18.2%) from 78.223%
10881

Pull #1114

gitlab-ci

mineralsfree
test(gui): added and adjusted tests for new zephyr onboarding changes

Signed-off-by: Mikita Pilinka <mikita.pilinka@northern.tech>
Pull Request #1114: feat(gui): zephyr mcu onboarding

3886 of 5413 branches covered (71.79%)

Branch coverage included in aggregate %.

20 of 26 new or added lines in 4 files covered. (76.92%)

8 existing lines in 2 files now uncovered.

38037 of 38055 relevant lines covered (99.95%)

15.82 hits per line

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

96.41
/frontend/src/js/components/devices/dialogs/DeviceConnectionDialog.tsx
1
// Copyright 2019 Northern.tech AS
1✔
2
//
1✔
3
//    Licensed under the Apache License, Version 2.0 (the "License");
1✔
4
//    you may not use this file except in compliance with the License.
1✔
5
//    You may obtain a copy of the License at
1✔
6
//
1✔
7
//        http://www.apache.org/licenses/LICENSE-2.0
1✔
8
//
1✔
9
//    Unless required by applicable law or agreed to in writing, software
1✔
10
//    distributed under the License is distributed on an "AS IS" BASIS,
1✔
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1✔
12
//    See the License for the specific language governing permissions and
1✔
13
//    limitations under the License.
1✔
14
import { useEffect, useState } from 'react';
1✔
15
import { useDispatch, useSelector } from 'react-redux';
1✔
16
import { useNavigate } from 'react-router-dom';
1✔
17

1✔
18
import { ArrowForward as ArrowForwardIcon } from '@mui/icons-material';
1✔
19
import { Button, Chip, DialogActions, DialogContent, List, ListItem, Typography } from '@mui/material';
1✔
20
import { makeStyles } from 'tss-react/mui';
1✔
21

1✔
22
import DocsLink from '@northern.tech/common-ui/DocsLink';
1✔
23
import Loader from '@northern.tech/common-ui/Loader';
1✔
24
import { BaseDialog } from '@northern.tech/common-ui/dialogs/BaseDialog';
1✔
25
import { DEVICE_STATES, TIMEOUTS, onboardingSteps } from '@northern.tech/store/constants';
1✔
26
import { getDeviceCountsByStatus, getFeatures, getOnboardingState, getTenantCapabilities } from '@northern.tech/store/selectors';
1✔
27
import { advanceOnboarding, saveUserSettings, setDeviceListState } from '@northern.tech/store/thunks';
1✔
28

1✔
29
import raspberryPi from '../../../../assets/img/raspberrypi.png';
1✔
30
import zephyr from '../../../../assets/img/zephyr_logo.png';
1✔
31
import { HELPTOOLTIPS } from '../../helptips/HelpTooltips';
1✔
32
import { MenderHelpTooltip } from '../../helptips/MenderTooltip';
1✔
33
import { McuDeviceOnboarding } from './McuDeviceOnboarding';
1✔
34
import PhysicalDeviceOnboarding from './PhysicalDeviceOnboarding';
1✔
35
import VirtualDeviceOnboarding from './VirtualDeviceOnboarding';
1✔
36

1✔
37
const useStyles = makeStyles()(theme => ({
4✔
38
  rpiQuickstart: {
1✔
39
    'img': { height: 30, marginRight: theme.spacing(1.5), marginLeft: theme.spacing(0.75) }
1✔
40
  },
1✔
41
  zephyrLogo: { height: '24px', marginRight: theme.spacing(2) },
1✔
42
  deviceSection: {
1✔
43
    border: `1px solid ${theme.palette.divider}`,
1✔
44
    borderRadius: theme.spacing(0.5),
1✔
45
    gap: theme.spacing(1)
1✔
46
  },
1✔
47
  bottomText: {
1✔
48
    marginTop: theme.spacing(3)
1✔
49
  }
1✔
50
}));
1✔
51

1✔
52
const docsLinks = [
3✔
53
  { key: 'debian', target: 'operating-system-updates-debian-family', title: 'Debian family' },
1✔
54
  { key: 'yocto', target: 'operating-system-updates-yocto-project', title: 'Yocto OSes' }
1✔
55
];
1✔
56

1✔
57
const MenderHubReference = () => (
3✔
UNCOV
58
  <Typography variant="body1">
1✔
59
    Or visit {/* eslint-disable-next-line react/jsx-no-target-blank */}
1✔
60
    <a href="https://hub.mender.io/c/board-integrations" target="_blank" rel="noopener">
1✔
61
      Mender Hub
1✔
62
    </a>{' '}
1✔
63
    and search integrations for your device and OS.
1✔
64
  </Typography>
1✔
65
);
1✔
66

1✔
67
const OnPremDeviceConnectionExplainer = ({ isEnterprise }) => (
3✔
68
  <>
1✔
69
    <Typography variant="body1">
1✔
70
      You can connect almost any device and Linux OS with Mender, but to make things simple during evaluation we recommend you to get started with a Debian
1✔
71
      based setup. This also works with a Raspberry Pi as a test device.
1✔
72
      <br />
1✔
73
      Follow the <DocsLink path="client-installation/install-with-debian-package" title="installation instructions" /> for Debian packages and select the{' '}
1✔
74
      {isEnterprise ? 'Enterprise' : 'Demo'} server tab to configure the client.
1!
75
      <br />
1✔
76
      For operating system updates, see the documentation to integrate the following with Mender:
1✔
77
    </Typography>
1✔
78
    <List>
1✔
79
      {docsLinks.map(item => (
1✔
80
        <ListItem key={item.key} disablePadding className="padding-top-none padding-bottom-none">
1✔
81
          <DocsLink path={item.target} title={item.title} />
1✔
82
        </ListItem>
1✔
83
      ))}
1✔
84
    </List>
1✔
85
    <MenderHubReference />
1✔
86
  </>
1✔
87
);
1✔
88

1✔
89
const DeviceConnectionExplainer = ({ setOnDevice, setVirtualDevice, setMcu }) => {
3✔
90
  const { classes } = useStyles();
4✔
91
  return (
4✔
92
    <>
1✔
93
      <Typography variant="body1">
1✔
94
        You can connect almost any device from Linux to RTOSes. For simplicity, we recommend you use a Raspberry Pi as a test device.
1✔
95
      </Typography>
1✔
96
      <div className={`margin-top-small padding-small rpi-quickstart ${classes.rpiQuickstart} ${classes.deviceSection}`}>
1✔
97
        <div className="flexbox margin-bottom-small space-between">
1✔
98
          <div className="flexbox centered">
1✔
99
            <img src={raspberryPi} alt="rpi-logo" />
1✔
100
            <Typography variant="subtitle1">Raspberry Pi quick start</Typography>
1✔
101
          </div>
1✔
102
          <Chip size="small" label="Standard" />
1✔
103
        </div>
1✔
104
        <Typography variant="body1">
1✔
105
          A step-by-step guide for new users — connect your Raspberry Pi and get started with your first update using Mender.
1✔
106
        </Typography>
1✔
107
        <div className="flexbox margin-top-small">
1✔
108
          <Button variant="text" size="small" onClick={() => setOnDevice(true)} endIcon={<ArrowForwardIcon />}>
2✔
109
            Get started
1✔
110
          </Button>
1✔
111
        </div>
1✔
112
      </div>
1✔
113
      <div className="two-columns margin-top-small">
1✔
114
        <div className={`padding-small padding-bottom-none flexbox column ${classes.deviceSection}`}>
1✔
115
          <div className="flexbox space-between">
1✔
116
            <div className="flexbox centered">
1✔
117
              <img src={zephyr} className={classes.zephyrLogo} />
1✔
118
              <Typography variant="subtitle1" gutterBottom>
1✔
119
                Zephyr MCU
1✔
120
              </Typography>
1✔
121
            </div>
1✔
122
            <Chip size="small" label="Micro" />
1✔
123
          </div>
1✔
124
          <Typography variant="body1">Connect an Espressif ESP32-S3 DevKitC, or any compatible microcontroller that supports MCUBoot in Zephyr.</Typography>
1✔
125
          <div>
1✔
NEW
126
            <Button variant="text" size="small" endIcon={<ArrowForwardIcon />} onClick={() => setMcu(true)}>
1✔
127
              Get started with Zephyr
1✔
128
            </Button>
1✔
129
          </div>
1✔
130
        </div>
1✔
131
        <div className={`padding-small ${classes.deviceSection}`}>
1✔
132
          <Typography variant="subtitle1" gutterBottom>
1✔
133
            Don&#39;t have a device?
1✔
134
          </Typography>
1✔
135
          <Typography variant="body1">
1✔
136
            You can use our virtual device to explore the UI, deploy an update and get a quick feel for the features of Mender.
1✔
137
          </Typography>
1✔
138
          <div className="flexbox margin-top-small">
1✔
139
            <Button variant="text" size="small" onClick={() => setVirtualDevice(true)} endIcon={<ArrowForwardIcon />}>
2✔
140
              Try the virtual device
1✔
141
            </Button>
1✔
142
          </div>
1✔
143
        </div>
1✔
144
      </div>
1✔
145
      <Typography variant="body1" className={classes.bottomText}>
1✔
146
        <DocsLink path="overview/device-support" title="Visit our documentation" />{' '}
1✔
147
        for full information about device support including Debian family and Yocto OSes.
1✔
148
      </Typography>
1✔
149
    </>
1✔
150
  );
1✔
151
};
1✔
152

1✔
153
export const DeviceConnectionDialog = ({ onCancel }) => {
3✔
154
  const [onDevice, setOnDevice] = useState(false);
12✔
155
  const [progress, setProgress] = useState(1);
12✔
156
  const [virtualDevice, setVirtualDevice] = useState(false);
12✔
157
  const [mcu, setMcu] = useState(false);
12✔
158
  const { pending: pendingCount } = useSelector(getDeviceCountsByStatus);
12✔
159
  const [pendingDevicesCount] = useState(pendingCount);
12✔
160
  const [hasMoreDevices, setHasMoreDevices] = useState(false);
12✔
161
  const { isEnterprise } = useSelector(getTenantCapabilities);
12✔
162
  const { isHosted } = useSelector(getFeatures);
12✔
163
  const { complete: onboardingComplete, deviceType: onboardingDeviceType } = useSelector(getOnboardingState);
12✔
164
  const dispatch = useDispatch();
12✔
165
  const navigate = useNavigate();
12✔
166

1✔
167
  useEffect(() => {
12✔
168
    setHasMoreDevices(pendingCount > pendingDevicesCount);
3✔
169
  }, [pendingDevicesCount, pendingCount]);
1✔
170

1✔
171
  useEffect(() => {
12✔
172
    if ((virtualDevice || progress >= 2) && hasMoreDevices && !window.location.hash.includes('pending')) {
4!
173
      dispatch(advanceOnboarding(onboardingSteps.DASHBOARD_ONBOARDING_START));
1✔
174
      dispatch(setDeviceListState({ state: DEVICE_STATES.pending }));
1✔
175
      navigate('/devices/pending');
1✔
176
    }
1✔
177
    if (virtualDevice || progress >= 2) {
4✔
178
      dispatch(saveUserSettings({ onboarding: { deviceConnection: new Date().toISOString() } }));
2✔
179
    }
1✔
180
  }, [dispatch, hasMoreDevices, navigate, progress, virtualDevice]);
1✔
181

1✔
182
  const onBackClick = () => {
12✔
183
    let updatedProgress = progress - 1;
2✔
184
    if (!updatedProgress) {
2!
185
      updatedProgress = 1;
2✔
186
      setOnDevice(false);
2✔
187
      setVirtualDevice(false);
2✔
188
      setMcu(false);
2✔
189
    }
1✔
190
    setProgress(updatedProgress);
2✔
191
  };
1✔
192

1✔
193
  const onAdvance = () => {
12✔
194
    dispatch(advanceOnboarding(onboardingSteps.DASHBOARD_ONBOARDING_START));
1✔
195
    setProgress(progress + 1);
1✔
196
  };
1✔
197

1✔
198
  let content = <DeviceConnectionExplainer setOnDevice={setOnDevice} setVirtualDevice={setVirtualDevice} setMcu={setMcu} />;
12✔
199
  if (onDevice) {
12✔
200
    content = <PhysicalDeviceOnboarding progress={progress} />;
3✔
201
  } else if (virtualDevice) {
10✔
202
    content = <VirtualDeviceOnboarding />;
7✔
203
  } else if (mcu) {
4!
NEW
204
    content = <McuDeviceOnboarding />;
1✔
205
  } else if (!isHosted) {
4!
206
    content = <OnPremDeviceConnectionExplainer isEnterprise={isEnterprise} />;
1✔
207
  }
1✔
208

1✔
209
  if (hasMoreDevices && !onboardingComplete) {
12!
210
    setTimeout(onCancel, TIMEOUTS.twoSeconds);
1✔
211
  }
1✔
212

1✔
213
  const isPhysicalAndNotFinal = progress < 2 && (!virtualDevice || progress < 1);
12✔
214
  return (
12✔
215
    <BaseDialog open title={mcu ? 'Connecting a Zephyr-based MCU' : 'Connecting a device'} maxWidth="sm" onClose={onCancel}>
1!
216
      <DialogContent>{content}</DialogContent>
1✔
217
      <DialogActions>
1✔
218
        {onDevice || virtualDevice || mcu ? (
1✔
219
          <>
1✔
220
            {isPhysicalAndNotFinal && !mcu && <Button onClick={onCancel}>Cancel</Button>}
1✔
221
            <Button onClick={onBackClick} variant="outlined">
1✔
222
              Back
1✔
223
            </Button>
1✔
224
            {isPhysicalAndNotFinal && !mcu ? (
1✔
225
              <Button variant="contained" disabled={!(virtualDevice || (onDevice && onboardingDeviceType))} onClick={onAdvance}>
1✔
226
                Next
1✔
227
              </Button>
1✔
228
            ) : (
1✔
229
              <Button
1✔
230
                variant="contained"
1✔
231
                disabled={!onboardingComplete && !mcu}
1✔
232
                onClick={onCancel}
1✔
233
                endIcon={!onboardingComplete && !mcu && <Loader show small table style={{ top: -24 }} />}
1✔
234
              >
1✔
235
                {onboardingComplete || mcu ? 'Close' : 'Waiting for device'}
1!
236
              </Button>
1✔
237
            )}
1✔
238
          </>
1✔
239
        ) : (
1✔
240
          <>
1✔
241
            <MenderHelpTooltip id={HELPTOOLTIPS.deviceSupportTip.id} style={{ marginLeft: 20 }} />
1✔
242
            <div style={{ flexGrow: 1 }} />
1✔
243
            <Button onClick={onCancel}>Cancel</Button>
1✔
244
          </>
1✔
245
        )}
1✔
246
      </DialogActions>
1✔
247
    </BaseDialog>
1✔
248
  );
1✔
249
};
1✔
250

1✔
251
export default DeviceConnectionDialog;
1✔
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