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

electron / fiddle / 4604883586

pending completion
4604883586

push

github

GitHub
refactor: add type awareness to getItemIcon() (#1289)

924 of 1085 branches covered (85.16%)

Branch coverage included in aggregate %.

3448 of 3705 relevant lines covered (93.06%)

383.35 hits per line

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

85.29
/src/renderer/components/version-select.tsx
1
import * as React from 'react';
4✔
2

3
import {
4✔
4
  Button,
5
  ButtonGroupProps,
6
  ContextMenu,
7
  IconName,
8
  Intent,
9
  Menu,
10
  MenuItem,
11
  Tooltip,
12
} from '@blueprintjs/core';
13
import {
4✔
14
  ItemListPredicate,
15
  ItemListRenderer,
16
  ItemRenderer,
17
  Select,
18
} from '@blueprintjs/select';
19
import { InstallState } from '@electron/fiddle-core';
20
import { observer } from 'mobx-react';
4✔
21
import { FixedSizeList, ListChildComponentProps } from 'react-window';
4✔
22
import semver from 'semver';
4✔
23

24
import { RunnableVersion, VersionSource } from '../../interfaces';
4✔
25
import { disableDownload } from '../../utils/disable-download';
4✔
26
import { highlightText } from '../../utils/highlight-text';
4✔
27
import { AppState } from '../state';
28

29
const ElectronVersionSelect = Select.ofType<RunnableVersion>();
4✔
30

31
const FixedSizeListItem = ({ index, data, style }: ListChildComponentProps) => {
4✔
32
  const { filteredItems, renderItem } = data;
×
33
  const renderedItem = renderItem(filteredItems[index], index);
×
34

35
  return <div style={style}>{renderedItem}</div>;
×
36
};
37

38
const itemListRenderer: ItemListRenderer<RunnableVersion> = ({
4✔
39
  filteredItems,
40
  renderItem,
41
  itemsParentRef,
42
}) => {
43
  const InnerElement = React.forwardRef((props, ref: React.RefObject<Menu>) => {
1✔
44
    return <Menu ref={ref} ulRef={itemsParentRef} {...props} />;
×
45
  });
46
  InnerElement.displayName = 'Menu';
1✔
47

48
  return (
1✔
49
    <FixedSizeList
50
      innerElementType={InnerElement}
51
      height={300}
52
      width={400}
53
      itemCount={filteredItems.length}
54
      itemSize={30}
55
      itemData={{ renderItem, filteredItems }}
56
    >
57
      {FixedSizeListItem}
58
    </FixedSizeList>
59
  );
60
};
61

62
/**
63
 * Helper method: Returns the <Select /> label for an Electron
64
 * version.
65
 *
66
 * @param {RunnableVersion} { source, state, name }
67
 * @returns {string}
68
 */
69
export function getItemLabel({ source, state, name }: RunnableVersion): string {
4✔
70
  if (source === VersionSource.local) {
8✔
71
    return name || 'Local';
2✔
72
  }
73

74
  const installStateLabels: Record<InstallState, string> = {
6✔
75
    missing: 'Not downloaded',
76
    downloading: 'Downloading',
77
    downloaded: 'Downloaded',
78
    installing: 'Downloaded',
79
    installed: 'Downloaded',
80
  } as const;
81
  return installStateLabels[state] || '';
6!
82
}
83

84
/**
85
 * Helper method: Returns the <Select /> icon for an Electron
86
 * version.
87
 *
88
 * @param {RunnableVersion} { state }
89
 * @returns {IconName}
90
 */
91
export function getItemIcon({ state }: RunnableVersion): IconName {
4✔
92
  const installStateIcons: Record<InstallState, IconName> = {
9✔
93
    missing: 'cloud',
94
    downloading: 'cloud-download',
95
    downloaded: 'compressed',
96
    installing: 'compressed',
97
    installed: 'saved',
98
  } as const;
99

100
  return installStateIcons[state] || '';
9!
101
}
102

103
/**
104
 * Helper method: Returns the <Select /> predicate for an Electron
105
 * version.
106
 *
107
 * Sorts by index of the chosen query.
108
 * For example, if we take the following versions:
109
 * [3.0.0, 14.3.0, 13.2.0, 12.0.0-nightly.20210301, 12.0.0-beta.3]
110
 * and a search query of '3', this method would sort them into:
111
 * [3.0.0, 13.2.0, 14.3.0, 12.0.0-beta.3, 12.0.0-nightly.20210301]
112
 *
113
 * @param {string} query
114
 * @param {RunnableVersion[]} versions
115
 * @returns
116
 */
117
export const filterItems: ItemListPredicate<RunnableVersion> = (
4✔
118
  query,
119
  versions,
120
) => {
121
  if (query === '') return versions;
3✔
122

123
  const q = query.toLowerCase();
2✔
124

125
  return versions
2✔
126
    .map((version: RunnableVersion) => {
127
      const lowercase = version.version.toLowerCase();
14✔
128
      return {
14✔
129
        index: lowercase.indexOf(q),
130
        coerced: semver.coerce(lowercase),
131
        version,
132
      };
133
    })
134
    .filter((item) => item.index !== -1)
14✔
135
    .sort((a, b) => {
136
      // If the user is searching for e.g. 'nightly' we
137
      // want to sort nightlies by descending major version.
138
      if (isNaN(+q)) {
15✔
139
        if (a.coerced && b.coerced) {
7✔
140
          return semver.rcompare(a.coerced, b.coerced);
7✔
141
        }
142
      }
143
      return a.index - b.index;
8✔
144
    })
145
    .map((item) => item.version);
10✔
146
};
147

148
/**
149
 * Renders a context menu to copy the current Electron version.
150
 *
151
 * @param {React.MouseEvent<HTMLButtonElement>} e
152
 * @param {string} version the Electron version number to copy.
153
 */
154
export const renderVersionContextMenu = (
4✔
155
  e: React.MouseEvent<HTMLButtonElement>,
156
  version: string,
157
) => {
158
  e.preventDefault();
×
159

160
  ContextMenu.show(
×
161
    <Menu>
162
      <MenuItem
163
        text="Copy Version Number"
164
        onClick={() => {
165
          navigator.clipboard.writeText(version);
×
166
        }}
167
      />
168
    </Menu>,
169
    { left: e.clientX, top: e.clientY },
170
  );
171
};
172

173
/**
174
 * Helper method: Returns the <Select /> <MenuItem /> for Electron
175
 * versions.
176
 *
177
 * @param {RunnableVersion} item
178
 * @param {IItemRendererProps} { handleClick, modifiers, query }
179
 * @returns
180
 */
181
export const renderItem: ItemRenderer<RunnableVersion> = (
4✔
182
  item,
183
  { handleClick, modifiers, query },
184
) => {
185
  if (!modifiers.matchesPredicate) {
4✔
186
    return null;
1✔
187
  }
188

189
  if (disableDownload(item.version)) {
3✔
190
    return (
1✔
191
      <Tooltip
192
        className="disabled-menu-tooltip"
193
        modifiers={{
194
          flip: { enabled: false },
195
          preventOverflow: { enabled: false },
196
          hide: { enabled: false },
197
        }}
198
        intent={Intent.PRIMARY}
199
        content={`Version is not available on current OS`}
200
      >
201
        <MenuItem
202
          active={modifiers.active}
203
          disabled={true}
204
          text={highlightText(item.version, query)}
205
          key={item.version}
206
          label={getItemLabel(item)}
207
          icon={getItemIcon(item)}
208
        />
209
      </Tooltip>
210
    );
211
  }
212

213
  return (
2✔
214
    <MenuItem
215
      active={modifiers.active}
216
      disabled={modifiers.disabled}
217
      text={highlightText(item.version, query)}
218
      key={item.version}
219
      onClick={handleClick}
220
      label={getItemLabel(item)}
221
      icon={getItemIcon(item)}
222
    />
223
  );
224
};
225

226
interface VersionSelectState {
227
  value: string;
228
}
229

230
interface VersionSelectProps {
231
  appState: AppState;
232
  disabled?: boolean;
233
  currentVersion: RunnableVersion;
234
  onVersionSelect: (version: RunnableVersion) => void;
235
  buttonGroupProps?: ButtonGroupProps;
236
  itemDisabled?:
237
    | keyof RunnableVersion
238
    | ((item: RunnableVersion, index: number) => boolean);
239
}
240

241
/**
242
 * A dropdown allowing the selection of Electron versions. The actual
243
 * download is managed in the state.
244
 *
245
 * @class VersionSelect
246
 * @extends {React.Component<VersionSelectProps, VersionSelectState>}
247
 */
248
export const VersionSelect = observer(
4✔
249
  class VersionSelect extends React.Component<
250
    VersionSelectProps,
251
    VersionSelectState
252
  > {
253
    public render() {
254
      const { currentVersion, itemDisabled } = this.props;
2✔
255
      const { version } = currentVersion;
2✔
256

257
      return (
2✔
258
        <ElectronVersionSelect
259
          filterable={true}
260
          items={this.props.appState.versionsToShow}
261
          itemRenderer={renderItem}
262
          itemListPredicate={filterItems}
263
          itemListRenderer={itemListRenderer}
264
          itemDisabled={itemDisabled}
265
          onItemSelect={this.props.onVersionSelect}
266
          noResults={<MenuItem disabled={true} text="No results." />}
267
          disabled={!!this.props.disabled}
268
        >
269
          <Button
270
            className="version-chooser"
271
            text={`Electron v${version}`}
272
            icon={getItemIcon(currentVersion)}
273
            onContextMenu={(e: React.MouseEvent<HTMLButtonElement>) => {
274
              renderVersionContextMenu(e, version);
×
275
            }}
276
            disabled={!!this.props.disabled}
277
          />
278
        </ElectronVersionSelect>
279
      );
280
    }
281
  },
282
);
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