Skip to content

Commit

Permalink
refactor: use boolean input instead of manual conversion (#127)
Browse files Browse the repository at this point in the history
* refactor: use boolean input instead of manual conversion

* refactor: update setup test with less manual mocks

* chore: upgrade to latest actions core version

* fix: clean up the mock input util

* chore: rebuild from macos

* fix: add text to gitattributes

* chore: remove built files

* chore: add build files back
  • Loading branch information
byCedric authored Nov 17, 2021
1 parent ff2ff0f commit f1fe877
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 143 deletions.
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
* text=auto

# These files are text and should be normalized (Convert crlf => lf)
build/** eol=lf
build/** text eol=lf
4 changes: 2 additions & 2 deletions build/setup/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/setup/index.map.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
verbose: true,
clearMocks: true,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
},
"dependencies": {
"@actions/cache": "^1.0.7",
"@actions/core": "^1.4.0",
"@actions/core": "^1.6.0",
"@actions/exec": "^1.1.0",
"@actions/io": "^1.1.1",
"@actions/tool-cache": "^1.7.1",
Expand Down
6 changes: 3 additions & 3 deletions src/actions/setup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addPath, getInput, group, info } from '@actions/core';
import { addPath, getBooleanInput, getInput, group, info } from '@actions/core';

import { install } from '../install';
import * as tools from '../tools';
Expand All @@ -19,7 +19,7 @@ export async function setupAction(): Promise<void> {
})
);

if (tools.getBoolean(getInput('patch-watchers'), true)) {
if (!getInput('patch-watchers') || getBooleanInput('patch-watchers') !== false) {
await group('Patching system watchers for the `ENOSPC` error', () => tools.maybePatchWatchers());
}
}
Expand All @@ -34,7 +34,7 @@ async function installCli(name: tools.PackageName): Promise<string | void> {
}

const version = await tools.resolveVersion(name, inputVersion);
const cache = tools.getBoolean(getInput(`${shortName}-cache`), false);
const cache = getBooleanInput(`${shortName}-cache`);

try {
const path = await group(
Expand Down
7 changes: 0 additions & 7 deletions src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,6 @@ export type AuthenticateOptions = {
password?: string;
};

/**
* Get a boolean value from string, useful for GitHub Actions boolean inputs.
*/
export function getBoolean(value: string, defaultValue = false): boolean {
return (value || String(defaultValue)).toLowerCase() === 'true';
}

/**
* Convert `expo-cli` or `eas-cli` to just their binary name.
* For windows we have to use `<bin>.cmd`, toolkit will handle the Windows binary with that.
Expand Down
162 changes: 59 additions & 103 deletions tests/actions/setup.test.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,71 @@
import { getToolsMock } from '../utils';

const core = {
addPath: jest.fn(),
getInput: jest.fn(),
group: <T>(message: string, action: () => Promise<T>): Promise<T> => action(),
info: jest.fn(),
};
const exec = { exec: jest.fn() };
const install = { install: jest.fn() };
const tools = getToolsMock();

jest.mock('@actions/core', () => core);
jest.mock('@actions/exec', () => exec);
jest.mock('../../src/tools', () => tools);
jest.mock('../../src/install', () => install);
import { getToolsMock, mockInput } from '../utils';

jest.mock('../../src/tools', () => getToolsMock());

import * as core from '@actions/core';

import * as install from '../../src/install';
import * as tools from '../../src/tools';
import { setupAction } from '../../src/actions/setup';

describe('run', () => {
it('patches the system when set to true', async () => {
mockInput({ patchWatchers: 'true' });
await setupAction();
expect(tools.maybePatchWatchers).toHaveBeenCalled();
});
describe(setupAction, () => {
describe('patch watchers', () => {
it('patches the system when set to true', async () => {
mockInput({ 'patch-watchers': 'true' });
await setupAction();
expect(tools.maybePatchWatchers).toHaveBeenCalled();
});

it('patches the system when not set', async () => {
mockInput({ patchWatchers: '' });
await setupAction();
expect(tools.maybePatchWatchers).toHaveBeenCalled();
});
it('patches the system when not set', async () => {
mockInput({ 'patch-watchers': '' });
await setupAction();
expect(tools.maybePatchWatchers).toHaveBeenCalled();
});

it('skips the system patch when set to false', async () => {
mockInput({ patchWatchers: 'false' });
await setupAction();
expect(tools.maybePatchWatchers).not.toHaveBeenCalled();
it('skips the system patch when set to false', async () => {
mockInput({ 'patch-watchers': 'false' });
await setupAction();
expect(tools.maybePatchWatchers).not.toHaveBeenCalled();
});
});

it('authenticates with provided credentials', async () => {
mockInput({ username: 'bycedric', password: 'mypassword', patchWatchers: 'false' });
await setupAction();
expect(tools.maybeAuthenticate).toBeCalledWith({ username: 'bycedric', password: 'mypassword' });
});
describe('authentication', () => {
it('authenticates with provided credentials', async () => {
mockInput({ username: 'bycedric', password: 'mypassword' });
await setupAction();
expect(tools.maybeAuthenticate).toBeCalledWith({ username: 'bycedric', password: 'mypassword' });
});

it('authenticates with provided token', async () => {
mockInput({ token: 'ABC123', patchWatchers: 'false' });
await setupAction();
expect(tools.maybeAuthenticate).toBeCalledWith({ token: 'ABC123' });
it('authenticates with provided token', async () => {
mockInput({ token: 'ABC123' });
await setupAction();
expect(tools.maybeAuthenticate).toBeCalledWith({ token: 'ABC123' });
});
});

['expo', 'eas'].forEach(cliName => {
const packageName = `${cliName}-cli`;
let installMock: jest.SpyInstance;

beforeEach(() => {
installMock = jest.spyOn(install, 'install').mockImplementation();
});

afterEach(() => {
installMock.mockRestore();
});

describe(packageName, () => {
it(`skips installation without \`${cliName}-version\``, async () => {
mockInput();
await setupAction();
expect(install.install).not.toBeCalledWith({ package: packageName });
expect(installMock).not.toBeCalledWith({ package: packageName });
});

it('installs with yarn by default', async () => {
mockInput({ [`${cliName}Version`]: 'latest' });
mockInput({ [`${cliName}-version`]: 'latest' });
await setupAction();
expect(install.install).toBeCalledWith({
expect(installMock).toBeCalledWith({
package: packageName,
version: 'latest',
packager: 'yarn',
Expand All @@ -70,9 +74,9 @@ describe('run', () => {
});

it('installs provided version with npm', async () => {
mockInput({ [`${cliName}Version`]: '3.0.10', packager: 'npm' });
mockInput({ [`${cliName}-version`]: '3.0.10', packager: 'npm' });
await setupAction();
expect(install.install).toBeCalledWith({
expect(installMock).toBeCalledWith({
package: packageName,
version: '3.0.10',
packager: 'npm',
Expand All @@ -83,11 +87,11 @@ describe('run', () => {
it('installs with yarn and cache enabled', async () => {
mockInput({
packager: 'yarn',
[`${cliName}Version`]: '4.2.0',
[`${cliName}Cache`]: 'true',
[`${cliName}-version`]: '4.2.0',
[`${cliName}-cache`]: 'true',
});
await setupAction();
expect(install.install).toBeCalledWith({
expect(installMock).toBeCalledWith({
package: packageName,
version: '4.2.0',
packager: 'yarn',
Expand All @@ -98,12 +102,12 @@ describe('run', () => {
it('installs with yarn and custom cache key', async () => {
mockInput({
packager: 'yarn',
[`${cliName}Version`]: '4.2.0',
[`${cliName}Cache`]: 'true',
[`${cliName}CacheKey`]: 'custom-key',
[`${cliName}-version`]: '4.2.0',
[`${cliName}-cache`]: 'true',
[`${cliName}-cache-key`]: 'custom-key',
});
await setupAction();
expect(install.install).toBeCalledWith({
expect(installMock).toBeCalledWith({
package: packageName,
version: '4.2.0',
packager: 'yarn',
Expand All @@ -113,59 +117,11 @@ describe('run', () => {
});

it('installs path to global path', async () => {
install.install.mockResolvedValue(`/${cliName}/install/path`);
installMock.mockResolvedValue(`/${cliName}/install/path`);
const addPathSpy = jest.spyOn(core, 'addPath').mockImplementation();
await setupAction();
expect(core.addPath).toBeCalledWith(`/${cliName}/install/path`);
expect(addPathSpy).toBeCalledWith(`/${cliName}/install/path`);
});
});
});
});

interface MockInputProps {
expoVersion?: string;
expoCache?: string;
expoCacheKey?: string;
easVersion?: string;
easCache?: string;
easCacheKey?: string;
packager?: string;
token?: string;
username?: string;
password?: string;
patchWatchers?: string;
}

function mockInput(props: MockInputProps = {}) {
// fix: kind of dirty workaround for missing "mock 'value' based on arguments"
const input = (name: string) => {
switch (name) {
case 'expo-version':
return props.expoVersion || '';
case 'expo-cache':
return props.expoCache || '';
case 'expo-cache-key':
return props.expoCacheKey || '';
case 'eas-version':
return props.easVersion || '';
case 'eas-cache':
return props.easCache || '';
case 'eas-cache-key':
return props.easCacheKey || '';
case 'packager':
return props.packager || '';
case 'token':
return props.token || '';
case 'username':
return props.username || '';
case 'password':
return props.password || '';
case 'patch-watchers':
return props.patchWatchers || '';
default:
return '';
}
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
core.getInput = input as any;
}
22 changes: 0 additions & 22 deletions tests/tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,6 @@ import * as cli from '@actions/exec';
import * as tools from '../src/tools';
import * as utils from './utils';

describe(tools.getBoolean, () => {
it('rreturns false for empty strings by default', () => {
expect(tools.getBoolean('')).toBeFalsy();
});

it('returns true for empty strings with default set to true', () => {
expect(tools.getBoolean('', true)).toBeTruthy();
});

it('returns true for `true` strings by default', () => {
expect(tools.getBoolean('true')).toBeTruthy();
});

it('returns false for `false` strings with default set to false', () => {
expect(tools.getBoolean('false', false)).toBeFalsy();
});

it('returns false for `false` strings with default set to true', () => {
expect(tools.getBoolean('false', true)).toBeFalsy();
});
});

describe(tools.getBinaryName, () => {
it('returns expo for `expo-cli`', () => {
expect(tools.getBinaryName('expo-cli')).toBe('expo');
Expand Down
11 changes: 10 additions & 1 deletion tests/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as core from '@actions/core';

// keep track of the original one to revert the platform
const originalPlatform = process.platform;

Expand Down Expand Up @@ -38,7 +40,6 @@ export function restoreEnv(): void {
*/
export function getToolsMock() {
return {
getBoolean: jest.fn((v, d) => (v ? v === 'true' : d)),
getBinaryName: jest.fn(v => v.replace('-cli', '')),
resolveVersion: jest.fn((n, v) => v),
maybeAuthenticate: jest.fn(),
Expand All @@ -48,3 +49,11 @@ export function getToolsMock() {
performAction: jest.fn(),
};
}

/**
* Mock both the input and boolean input methods from `@actions/core`.
*/
export function mockInput(inputs: Record<string, string> = {}) {
jest.spyOn(core, 'getInput').mockImplementation(name => inputs[name]);
jest.spyOn(core, 'getBooleanInput').mockImplementation(name => inputs[name] === 'true');
}
11 changes: 9 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,18 @@
semver "^6.1.0"
uuid "^3.3.3"

"@actions/core@^1.2.6", "@actions/core@^1.4.0":
"@actions/core@^1.2.6":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.4.0.tgz#cf2e6ee317e314b03886adfeb20e448d50d6e524"
integrity sha512-CGx2ilGq5i7zSLgiiGUtBCxhRRxibJYU6Fim0Q1Wg2aQL2LTnF27zbqZOrxfvFQ55eSBW0L8uVStgtKMpa0Qlg==

"@actions/core@^1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.6.0.tgz#0568e47039bfb6a9170393a73f3b7eb3b22462cb"
integrity sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==
dependencies:
"@actions/http-client" "^1.0.11"

"@actions/exec@^1.0.0", "@actions/exec@^1.0.1", "@actions/exec@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@actions/exec/-/exec-1.1.0.tgz#53441d968e56d2fec69ad3f15773d4d94e01162c"
Expand All @@ -37,7 +44,7 @@
"@actions/core" "^1.2.6"
minimatch "^3.0.4"

"@actions/http-client@^1.0.8", "@actions/http-client@^1.0.9":
"@actions/http-client@^1.0.11", "@actions/http-client@^1.0.8", "@actions/http-client@^1.0.9":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-1.0.11.tgz#c58b12e9aa8b159ee39e7dd6cbd0e91d905633c0"
integrity sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==
Expand Down

0 comments on commit f1fe877

Please sign in to comment.