more code coverage

This commit is contained in:
David Lechner
2023-03-08 15:07:16 -06:00
committed by David Lechner
parent 5fa0e90783
commit af160c99a8
28 changed files with 580 additions and 40 deletions
+18
View File
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../test';
import { unexpectedError } from './UnexpectedErrorAlert';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = unexpectedError(callback, { error: new Error('test') });
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { updateServerFailure } from './UpdateServerFailure';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = updateServerFailure(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { bluetoothNotAvailable } from './BluetoothNotAvailable';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = bluetoothNotAvailable(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
+18
View File
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { missingService } from './MissingService';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = missingService(callback, { hubName: 'name', serviceName: 'service' });
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
+18
View File
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { noGatt } from './NoGatt';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = noGatt(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
+29
View File
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { noHub } from './NoHub';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = noHub(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
it('should flash firmware when button is clicked', async () => {
const callback = jest.fn();
const toast = noHub(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /firmware/i }));
expect(callback).toHaveBeenCalledWith('flashFirmware');
});
+18
View File
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { noWebBluetooth } from './NoWebBluetooth';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = noWebBluetooth(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
+29
View File
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { oldFirmware } from './OldFirmware';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = oldFirmware(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
it('should flash firmware when button is clicked', async () => {
const callback = jest.fn();
const toast = oldFirmware(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /firmware/i }));
expect(callback).toHaveBeenCalledWith('flashFirmware');
});
+2
View File
@@ -4,6 +4,8 @@
// HACK: Prevent webpack from picking up all icons.
// https://github.com/palantir/blueprint/issues/2193
// istanbul ignore file
import {
Add,
Archive,
+5 -8
View File
@@ -1,21 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { fileInUse } from './FileInUseAlert';
it('should be valid', () => {
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = fileInUse(callback, { fileName: 'test.file' });
// TODO: refactor this to a common function to be used by all alerts
const [user, message] = testRender(<Toast {...toast} />);
// it should render
const [, message] = testRender(<>{toast.message}</>);
expect(message).toBeDefined();
await user.click(message.getByRole('button', { name: /close/i }));
// it should have a dismiss callback
toast.onDismiss?.(false);
expect(callback).toHaveBeenCalledWith('dismiss');
});
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { noFilesToBackup } from './NoFilesToBackup';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = noFilesToBackup(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
+18
View File
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { noPyFiles } from './NoPyFiles';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = noPyFiles(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
// Copyright (c) 2022-2023 The Pybricks Authors
import { cleanup } from '@testing-library/react';
import React from 'react';
@@ -27,3 +27,54 @@ describe('should handle all possible validation results', () => {
);
});
});
it('should fix file names with spaces', async () => {
const callback = jest.fn();
const [user, group] = testRender(
<FileNameFormGroup
fileName="test name"
fileExtension=".file"
validationResult={FileNameValidationResult.HasSpaces}
onChange={callback}
/>,
);
await user.click(group.getByRole('button', { name: /fix it/i }));
expect(callback).toHaveBeenCalledWith('test_name');
});
it('should fix file names with file extension', async () => {
const callback = jest.fn();
const [user, group] = testRender(
<FileNameFormGroup
fileName="test.file"
fileExtension=".file"
validationResult={FileNameValidationResult.HasFileExtension}
onChange={callback}
/>,
);
await user.click(group.getByRole('button', { name: /fix it/i }));
expect(callback).toHaveBeenCalledWith('test');
});
it('should fix file names with invalid characters', async () => {
const callback = jest.fn();
const [user, group] = testRender(
<FileNameFormGroup
fileName="test-name"
fileExtension=".file"
validationResult={FileNameValidationResult.HasInvalidCharacters}
onChange={callback}
/>,
);
await user.click(group.getByRole('button', { name: /fix it/i }));
expect(callback).toHaveBeenCalledWith('test_name');
});
+29
View File
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { dfuError } from './DfuError';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = dfuError(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
it('should try again when clicked', async () => {
const callback = jest.fn();
const toast = dfuError(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /again/i }));
expect(callback).toHaveBeenCalledWith('tryAgain');
});
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { flashProgress } from './FlashProgress';
type ActionType = 'erase' | 'flash';
it.each(['erase' as ActionType, 'flash' as ActionType])(
'should dismiss when close is clicked',
async (action) => {
const callback = jest.fn();
const toast = flashProgress(callback, { action, progress: 0 });
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
},
);
+39
View File
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { isLinux, isWindows } from '../../utils/os';
import { noDfuHub } from './NoDfuHub';
jest.mock('../../utils/os');
afterEach(() => {
jest.restoreAllMocks();
});
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = noDfuHub(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
it('should install windows driver when clicked', async () => {
const callback = jest.fn();
const toast = noDfuHub(callback, undefined as never);
jest.mocked(isLinux).mockReturnValue(true);
jest.mocked(isWindows).mockReturnValue(true);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /driver/i }));
expect(callback).toHaveBeenCalledWith('installWindowsDriver');
});
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { noDfuInterface } from './NoDfuInterface';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = noDfuInterface(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
+18
View File
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { noWebUsb } from './NoWebUsb';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = noWebUsb(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { releaseButton } from './ReleaseButton';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = releaseButton(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { cleanup } from '@testing-library/react';
import React from 'react';
import { testRender } from '../../../test';
import { InstallPybricksDialog } from './InstallPybricksDialog';
import {
firmwareInstallPybricksDialogAccept,
firmwareInstallPybricksDialogCancel,
} from './actions';
jest.mock('./hooks');
afterEach(() => {
jest.resetAllMocks();
cleanup();
localStorage.clear();
sessionStorage.clear();
});
it('should dispatch when close is clicked', async () => {
const [user, dialog, dispatch] = testRender(<InstallPybricksDialog />, {
firmware: { installPybricksDialog: { isOpen: true } },
});
await user.click(dialog.getByRole('button', { name: /close/i }));
expect(dispatch).toHaveBeenCalledWith(firmwareInstallPybricksDialogCancel());
});
it('should dispatch when done is clicked', async () => {
const [user, dialog, dispatch] = testRender(<InstallPybricksDialog />, {
firmware: { installPybricksDialog: { isOpen: true } },
});
// first page - select hub
await user.click(dialog.getByRole('button', { name: /next/i }));
// second page - accept license
await user.click(dialog.getByRole('checkbox', { name: /agree/i }));
await user.click(dialog.getByRole('button', { name: /next/i }));
// third page - options
await user.click(dialog.getByRole('button', { name: /next/i }));
// last page
await user.click(dialog.getByRole('button', { name: /install/i }));
expect(dispatch).toHaveBeenCalledWith(
firmwareInstallPybricksDialogAccept(
'ble-lwp3-bootloader',
new ArrayBuffer(0),
'',
),
);
});
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 The Pybricks Authors
import type { Hub } from '../../../components/hubPicker';
export function useFirmware(_hubType: Hub) {
return {
firmwareData: {
firmwareZip: new ArrayBuffer(0),
licenseText: 'test',
metadata: {},
},
firmwareError: undefined,
};
}
export function useCustomFirmware(_zipFile: File | undefined) {
return {
isCustomFirmwareRequested: false,
customFirmwareData: undefined,
customFirmwareError: undefined,
};
}
+5 -8
View File
@@ -1,21 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { userProgramSize } from './UserProgramSize';
it('should be valid', () => {
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = userProgramSize(callback, { actual: 10000, max: 8000 });
// TODO: refactor this to a common function to be used by all alerts
const [user, message] = testRender(<Toast {...toast} />);
// it should render
const [, message] = testRender(<>{toast.message}</>);
expect(message).toBeDefined();
await user.click(message.getByRole('button', { name: /close/i }));
// it should have a dismiss callback
toast.onDismiss?.(false);
expect(callback).toHaveBeenCalledWith('dismiss');
});
+2 -1
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020-2022 The Pybricks Authors
// Copyright (c) 2020-2023 The Pybricks Authors
import { I18n, I18nManager, TranslateOptions } from '@shopify/react-i18n';
import type {
@@ -16,6 +16,7 @@ export const i18nManager = new I18nManager({
onError: (err): void => console.error(err),
});
// istanbul ignore next
/** Enables or disables pseudolocalization for development. */
export function pseudolocalize(pseudolocalize: boolean): void {
i18nManager.update({ ...i18nManager.details, pseudolocalize });
+3 -21
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020-2022 The Pybricks Authors
// Copyright (c) 2020-2023 The Pybricks Authors
import './index.scss';
import { HotkeysProvider, Toaster } from '@blueprintjs/core';
@@ -16,6 +16,7 @@ import { appVersion } from './app/constants';
import { db } from './fileStorage/context';
import { i18nManager } from './i18n';
import { rootReducer } from './reducers';
import { serializableCheck } from './redux';
import reportWebVitals from './reportWebVitals';
import rootSaga, { RootSagaContext } from './sagas';
import { defaultTerminalContext } from './terminal/TerminalContext';
@@ -38,26 +39,7 @@ const loggerMiddleware = createLogger({ predicate: () => false });
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActionPaths: [
// copy of defaults
'meta.arg',
'meta.baseQueryMeta',
// monoco view state has class-based object but is technically serializable
'viewState.viewState.firstPosition',
// contain ArrayBuffer, Blob or DataView
'data',
'file',
'firmwareZip',
'payload',
'value',
// Error is not serializable
'error',
'props.error',
],
},
})
getDefaultMiddleware({ serializableCheck })
.concat(sagaMiddleware)
.concat(loggerMiddleware),
});
+39
View File
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender, uuid } from '../../../test';
import { editorGoto } from '../../editor/actions';
import { useFileStorageUuid } from '../../fileStorage/hooks';
import { compilerError } from './CompilerError';
afterEach(() => {
jest.restoreAllMocks();
});
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = compilerError(callback, { error: ['test'] });
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
it('should dispatch go to error when clicked', async () => {
const callback = jest.fn();
const toast = compilerError(callback, {
error: ['test', ' File "test.py", line 1'],
});
jest.mocked(useFileStorageUuid).mockReturnValue(uuid(0));
const [user, message, dispatch] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /go to/i }));
expect(dispatch).toHaveBeenCalledWith(editorGoto(uuid(0), 1));
});
+23
View File
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 The Pybricks Authors
import type { SerializableStateInvariantMiddlewareOptions } from '@reduxjs/toolkit';
export const serializableCheck: SerializableStateInvariantMiddlewareOptions = {
ignoredActionPaths: [
// copy of defaults
'meta.arg',
'meta.baseQueryMeta',
// monoco view state has class-based object but is technically serializable
'viewState.viewState.firstPosition',
// contain ArrayBuffer, Blob or DataView
'data',
'file',
'firmwareZip',
'payload',
'value',
// Error is not serializable
'error',
'props.error',
],
};
+18
View File
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
import { Toast } from '@blueprintjs/core';
import React from 'react';
import { testRender } from '../../../test';
import { addressCopied } from './AddressCopied';
it('should dismiss when close is clicked', async () => {
const callback = jest.fn();
const toast = addressCopied(callback, undefined as never);
const [user, message] = testRender(<Toast {...toast} />);
await user.click(message.getByRole('button', { name: /close/i }));
expect(callback).toHaveBeenCalledWith('dismiss');
});
+4 -1
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020-2022 The Pybricks Authors
// Copyright (c) 2020-2023 The Pybricks Authors
import { HotkeysProvider } from '@blueprintjs/core';
import { ThunkAction, configureStore } from '@reduxjs/toolkit';
@@ -12,6 +12,7 @@ import { AnyAction, PreloadedState, legacy_createStore as createStore } from 're
import { END, MulticastChannel, Saga, Task, runSaga, stdChannel } from 'redux-saga';
import { UUID } from '../src/fileStorage';
import { RootState, rootReducer } from '../src/reducers';
import { serializableCheck } from '../src/redux';
import { RootSagaContext } from '../src/sagas';
// HACK: not a public type so can't be imported directly
@@ -167,6 +168,8 @@ export const testRender = (
const store = configureStore({
reducer: rootReducer,
preloadedState: state,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({ serializableCheck }),
});
const dispatch = jest.spyOn(store, 'dispatch');