Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 81 additions & 3 deletions rtl-spec/components/version-select.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import {
VersionSelect,
filterItems,
getItemDisplayText,
getItemIcon,
getItemLabel,
renderItem,
Expand Down Expand Up @@ -78,20 +79,62 @@ describe('VersionSelect component', () => {

const { queryAllByTestId } = render(item);

expect(queryAllByTestId('disabled-menu-item')).toHaveLength(0);
});
it('does not disable local versions even when disableDownload returns true', () => {
vi.mocked(disableDownload).mockReturnValueOnce(true);

const localVersion: RunnableVersion = {
...mockVersion1,
source: local,
state: installed,
name: 'My Build',
};

const item = renderItem(localVersion, {
handleClick: () => ({}),
index: 0,
modifiers: { active: true, disabled: false, matchesPredicate: true },
query: '',
})!;

const { queryAllByTestId } = render(item);

expect(queryAllByTestId('disabled-menu-item')).toHaveLength(0);
});
});

describe('getItemLabel()', () => {
describe('getItemDisplayText()', () => {
it('returns name for local versions', () => {
const input: RunnableVersion = {
...mockVersion1,
source: local,
state: installed,
name: 'My Debug Build',
};
expect(getItemDisplayText(input)).toBe('My Debug Build');
});

it('returns "Local Build" for local versions without a name', () => {
const input: RunnableVersion = {
...mockVersion1,
source: local,
state: installed,
};
expect(getItemDisplayText(input)).toBe('Local Build');
});

it('returns version string for remote versions', () => {
expect(getItemDisplayText(mockVersion1)).toBe(mockVersion1.version);
});
it('returns the correct label for an available local version', () => {
const input: RunnableVersion = {
...mockVersion1,
state: installed,
source: local,
};

expect(getItemLabel(input)).toBe('Local');
expect(getItemLabel({ ...input, name: 'Hi' })).toBe('Hi');
expect(getItemLabel(input)).toBe('Local Build');
});

it('returns the correct label for an unavailable local version', () => {
Expand Down Expand Up @@ -167,6 +210,41 @@ describe('VersionSelect component', () => {
expect(filterItems('3', versions)).toEqual(expected);
});

it('sorts local versions before remote versions', () => {
const localVer = {
version: '0.0.0-local.123',
source: local,
name: 'My Build',
} as RunnableVersion;

const versions = [
{ version: '14.3.0' },
localVer,
{ version: '3.0.0' },
] as RunnableVersion[];

const result = filterItems('build', versions);
// Only the local version matches 'build' (via name)
expect(result).toEqual([localVer]);
});

it('matches local versions by name', () => {
const localVer = {
version: '0.0.0-local.123',
source: local,
name: 'My Debug Build',
} as RunnableVersion;

const versions = [
{ version: '14.3.0' },
localVer,
{ version: '3.0.0' },
] as RunnableVersion[];

const result = filterItems('debug', versions);
expect(result).toEqual([localVer]);
});

it('sorts in descending order when the query is non-numeric', () => {
const versions = [
{ version: '3.0.0' },
Expand Down
4 changes: 2 additions & 2 deletions src/less/components/commands.less
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ header {
background-color: @background-3;
-webkit-app-region: drag;

#version-chooser .bp3-button-text::before {
#version-chooser:not([data-local]) .bp3-button-text::before {
content: 'Electron v';
}

@media (max-width: 980px) {
#version-chooser .bp3-button-text::before {
#version-chooser:not([data-local]) .bp3-button-text::before {
content: 'v';
}
}
Expand Down
42 changes: 42 additions & 0 deletions src/less/components/settings-electron.less
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,33 @@
overflow: hidden;
background-color: @background-1;

.electron-versions-section-header {
padding: 8px 15px;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
color: @text-color-2;
background-color: @background-2;
border-bottom: 1px solid @border-color-1;
border-top: 1px solid @border-color-1;
display: flex;
align-items: center;
gap: 6px;

&:first-child {
border-top: none;
}

&.local {
border-left: 3px solid @green4;
}

&.remote {
border-left: 3px solid @blue4;
}
}

.electron-versions-header {
display: flex;
font-weight: 600;
Expand Down Expand Up @@ -62,6 +89,14 @@
background-color: rgba(92, 112, 128, 0.15);
}

&.local {
border-left: 3px solid @green4;
}

&.remote {
border-left: 3px solid @blue4;
}

.version-col {
flex: 1;
padding: 0 15px;
Expand Down Expand Up @@ -103,6 +138,13 @@
color: @foreground-1;
}

.electron-versions-section-header {
background-color: rgba(255, 255, 255, 0.06);
border-bottom-color: @dark-gray4;
border-top-color: @dark-gray4;
color: rgba(255, 255, 255, 0.7);
}

.electron-versions-header {
background-color: @dark-gray5;
border-bottom-color: @dark-gray4;
Expand Down
78 changes: 41 additions & 37 deletions src/renderer/components/dialog-add-version.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
Intent,
} from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as semver from 'semver';

import { Version } from '../../interfaces';
import { AppState } from '../state';
Expand All @@ -22,11 +21,20 @@ interface AddVersionDialogProps {

interface AddVersionDialogState {
isValidElectron: boolean;
isValidVersion: boolean;
isValidName: boolean;
existingLocalVersion?: Version;
folderPath?: string;
localName?: string;
version: string;
name: string;
}

/**
* Generate a unique version key for a local build.
* Uses a format that is valid semver but can never conflict
* with a real Electron release.
*/
function generateLocalVersionKey(): string {
return `0.0.0-local.${Date.now()}`;
}

/**
Expand All @@ -41,14 +49,14 @@ export const AddVersionDialog = observer(
super(props);

this.state = {
isValidVersion: false,
isValidName: false,
isValidElectron: false,
version: '',
name: '',
};

this.onSubmit = this.onSubmit.bind(this);
this.onClose = this.onClose.bind(this);
this.onChangeVersion = this.onChangeVersion.bind(this);
this.onChangeName = this.onChangeName.bind(this);
}

/**
Expand All @@ -65,48 +73,45 @@ export const AddVersionDialog = observer(
folderPath,
isValidElectron,
localName,
// Pre-fill name from detected binary name if available
name: localName || '',
isValidName: !!localName,
});
}
}

/**
* Handles a change of the file input
* Handles a change of the name input
*/
public onChangeVersion(event: React.ChangeEvent<HTMLInputElement>) {
const version = event.target.value || '';
const isValidVersion = !!semver.valid(version);
public onChangeName(event: React.ChangeEvent<HTMLInputElement>) {
const name = event.target.value || '';
const isValidName = name.trim().length > 0;

this.setState({
version,
isValidVersion,
name,
isValidName,
});
}

/**
* Handles the submission of the dialog
*/
public async onSubmit(): Promise<void> {
const {
folderPath,
version,
isValidElectron,
existingLocalVersion,
localName,
} = this.state;
const { folderPath, name, isValidElectron, existingLocalVersion } =
this.state;

if (!folderPath) return;

const toAdd: Version = {
localPath: folderPath,
version,
name: localName,
};

// swap to old local electron version if the user adds a new one with the same path
if (isValidElectron && existingLocalVersion?.localPath) {
// set previous version as active version
this.props.appState.setVersion(existingLocalVersion.version);
} else {
const toAdd: Version = {
localPath: folderPath,
version: generateLocalVersionKey(),
name: name.trim(),
};
this.props.appState.addLocalVersion(toAdd);
}
this.onClose();
Expand All @@ -121,9 +126,8 @@ export const AddVersionDialog = observer(
}

get buttons() {
const { isValidElectron, isValidVersion, existingLocalVersion } =
this.state;
const canAdd = isValidElectron && isValidVersion && !existingLocalVersion;
const { isValidElectron, isValidName, existingLocalVersion } = this.state;
const canAdd = isValidElectron && isValidName && !existingLocalVersion;
const canSwitch = isValidElectron && existingLocalVersion;

return [
Expand Down Expand Up @@ -209,20 +213,20 @@ export const AddVersionDialog = observer(
}

private renderVersionInput(): JSX.Element | null {
const { isValidElectron, isValidVersion, version } = this.state;
const { isValidElectron, isValidName, name } = this.state;
if (!isValidElectron) return null;

return (
<>
<p>
Please specify a version, used for typings and the name. Must be{' '}
<code>semver</code> compliant.
Give this local build a name so you can identify it in the version
list.
</p>
<InputGroup
intent={isValidVersion ? undefined : Intent.DANGER}
value={version}
onChange={this.onChangeVersion}
placeholder="4.0.0"
intent={isValidName ? undefined : Intent.DANGER}
value={name}
onChange={this.onChangeName}
placeholder="e.g. My Debug Build"
/>
</>
);
Expand All @@ -234,8 +238,8 @@ export const AddVersionDialog = observer(
private reset(): void {
this.setState({
isValidElectron: false,
isValidVersion: false,
version: '',
isValidName: false,
name: '',
folderPath: undefined,
localName: undefined,
});
Expand Down
Loading
Loading