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

electron / fiddle / ca977947-d226-4d89-8edc-6f8573fedf8b

24 Aug 2023 10:18PM UTC coverage: 88.325% (-2.8%) from 91.157%
ca977947-d226-4d89-8edc-6f8573fedf8b

push

circleci

web-flow
ci: re-enable Coveralls (#1301)

967 of 1183 branches covered (0.0%)

Branch coverage included in aggregate %.

3648 of 4042 relevant lines covered (90.25%)

32.05 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 { observer } from 'mobx-react';
4✔
20
import { FixedSizeList, ListChildComponentProps } from 'react-window';
4✔
21
import semver from 'semver';
4✔
22

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

© 2025 Coveralls, Inc