Add connect diaglog tests convert storage to js to avoid ts issues with testing

This commit is contained in:
Matthew Russell 2022-03-10 15:42:55 -08:00
parent f10cdf491d
commit 69924aa0b3
9 changed files with 191 additions and 61 deletions

View File

@ -0,0 +1,154 @@
import '@testing-library/jest-dom';
import {
act,
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react';
import { VegaWalletContext, VegaWalletContextShape } from './context';
import { VegaConnectDialog } from './connect-dialog';
import { VegaConnectDialogProps } from '.';
import { RestConnector } from './connectors';
let defaultProps: VegaConnectDialogProps;
let defaultContextValue: VegaWalletContextShape;
beforeEach(() => {
defaultProps = {
connectors: {
rest: new RestConnector(),
},
dialogOpen: false,
setDialogOpen: jest.fn(),
};
defaultContextValue = {
keypair: null,
keypairs: null,
connect: jest.fn(),
disconnect: jest.fn(),
selectPublicKey: jest.fn(),
connector: null,
sendTx: jest.fn(),
};
});
function generateJSX(
props?: Partial<VegaConnectDialogProps>,
contextValue?: Partial<VegaWalletContextShape>
) {
return (
<VegaWalletContext.Provider
value={{ ...defaultContextValue, ...contextValue }}
>
<VegaConnectDialog {...defaultProps} {...props} />
</VegaWalletContext.Provider>
);
}
test('Renders list of connectors', () => {
const { container, rerender } = render(generateJSX());
expect(container).toBeEmptyDOMElement();
rerender(generateJSX({ dialogOpen: true }));
const list = screen.getByTestId('connectors-list');
expect(list).toBeInTheDocument();
expect(list.children).toHaveLength(
Object.keys(defaultProps.connectors).length
);
});
const fillInForm = () => {
const walletValue = 'test-wallet';
fireEvent.change(screen.getByLabelText('Wallet'), {
target: { value: walletValue },
});
const passphraseValue = 'test-passphrase';
fireEvent.change(screen.getByLabelText('Passphrase'), {
target: { value: passphraseValue },
});
return { wallet: walletValue, passphrase: passphraseValue };
};
test('Successful connection using rest auth form', async () => {
const spy = jest
.spyOn(defaultProps.connectors['rest'] as RestConnector, 'authenticate')
.mockImplementation(() => Promise.resolve({ success: true, error: null }));
render(generateJSX({ dialogOpen: true }));
// Switches to rest form
fireEvent.click(screen.getByText('rest provider'));
// Client side validation
fireEvent.submit(screen.getByTestId('rest-connector-form'));
expect(spy).not.toHaveBeenCalled();
await waitFor(() => {
expect(screen.getAllByText('Required')).toHaveLength(2);
});
const fields = fillInForm();
// Wait for auth method to be called
await act(async () => {
fireEvent.submit(screen.getByTestId('rest-connector-form'));
});
expect(spy).toHaveBeenCalledWith(fields);
expect(defaultProps.setDialogOpen).toHaveBeenCalledWith(false);
});
test('Unsuccessful connection using rest auth form', async () => {
// Error from service
let spy = jest
.spyOn(defaultProps.connectors['rest'] as RestConnector, 'authenticate')
.mockImplementation(() =>
Promise.resolve({ success: false, error: 'Error message' })
);
render(generateJSX({ dialogOpen: true }));
// Switches to rest form
fireEvent.click(screen.getByText('rest provider'));
const fields = fillInForm();
fireEvent.submit(screen.getByTestId('rest-connector-form'));
// Wait for auth method to be called
await act(async () => {
fireEvent.submit(screen.getByTestId('rest-connector-form'));
});
expect(spy).toHaveBeenCalledWith(fields);
expect(screen.getByTestId('form-error')).toHaveTextContent(
'Something went wrong'
);
expect(defaultProps.setDialogOpen).not.toHaveBeenCalled();
// Fetch failed due to wallet not running
spy = jest
.spyOn(defaultProps.connectors['rest'] as RestConnector, 'authenticate')
// @ts-ignore test fetch failed with typeerror
.mockImplementation(() => Promise.reject(new TypeError('fetch failed')));
await act(async () => {
fireEvent.submit(screen.getByTestId('rest-connector-form'));
});
expect(screen.getByTestId('form-error')).toHaveTextContent(
'Wallet not running at http://localhost:1789'
);
// Reject eg non 200 results
spy = jest
.spyOn(defaultProps.connectors['rest'] as RestConnector, 'authenticate')
// @ts-ignore test fetch failed with typeerror
.mockImplementation(() => Promise.reject(new Error('Error!')));
await act(async () => {
fireEvent.submit(screen.getByTestId('rest-connector-form'));
});
expect(screen.getByTestId('form-error')).toHaveTextContent(
'Authentication failed'
);
});

View File

@ -3,7 +3,8 @@ import { Dialog } from '@vegaprotocol/ui-toolkit';
import { VegaConnector } from './connectors';
import { RestConnectorForm } from './rest-connector-form';
import { useEffect } from 'react';
import { RestConnector, useVegaWallet } from '.';
import { RestConnector } from './connectors/rest-connector';
import { useVegaWallet } from './hooks';
export interface VegaConnectDialogProps {
connectors: { [name: string]: VegaConnector };
@ -57,17 +58,20 @@ export function VegaConnectDialog({
}}
/>
) : (
<ul className="flex flex-col justify-center gap-4 items-start">
<ul
className="flex flex-col justify-center gap-4 items-start"
data-testid="connectors-list"
>
{Object.entries(connectors).map(([key, connector]) => (
<li key={key} className="mb-12 last:mb-0">
<button
key={key}
onClick={() => setSelectedConnector(connector)}
className="capitalize hover:text-vega-pink"
className="capitalize hover:text-vega-pink dark:hover:text-vega-yellow"
>
{key} provider
</button>
<p className="text-neutral-500">{connector.description}</p>
<p>{connector.description}</p>
</li>
))}
</ul>

View File

@ -50,7 +50,7 @@ export function RestConnectorForm({
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={handleSubmit(onSubmit)} data-testid="rest-connector-form">
<FormGroup label="Wallet" labelFor="wallet">
<Input
{...register('wallet', { required: 'Required' })}
@ -59,7 +59,9 @@ export function RestConnectorForm({
autoFocus={true}
/>
{errors.wallet?.message && (
<InputError intent="danger">{errors.wallet.message}</InputError>
<InputError intent="danger" className="mt-4">
{errors.wallet.message}
</InputError>
)}
</FormGroup>
<FormGroup label="Passphrase" labelFor="passphrase">
@ -69,10 +71,16 @@ export function RestConnectorForm({
type="password"
/>
{errors.passphrase?.message && (
<InputError intent="danger">{errors.passphrase.message}</InputError>
<InputError intent="danger" className="mt-4">
{errors.passphrase.message}
</InputError>
)}
</FormGroup>
{error && <p className="text-intent-danger mb-12">{error}</p>}
{error && (
<p className="text-intent-danger mb-12" data-testid="form-error">
{error}
</p>
)}
<Button variant="primary" type="submit">
Connect
</Button>

12
libs/storage/.babelrc Normal file
View File

@ -0,0 +1,12 @@
{
"presets": [
[
"@nrwl/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}

View File

@ -9,9 +9,8 @@
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/storage",
"tsConfig": "libs/storage/tsconfig.lib.json",
"project": "libs/storage/package.json",
"entryFile": "libs/storage/src/index.ts",
"entryFile": "libs/storage/src/index.js",
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
"compiler": "babel",
"assets": [

View File

@ -1,6 +1,6 @@
// TODO: fine for now however will leak state between tests (we don't really have) in future. Ideally should use a provider
export const LocalStorage = {
getItem: (key: string) => {
getItem: (key) => {
if (typeof window === 'undefined') return;
try {
const item = window.localStorage.getItem(key);
@ -10,7 +10,7 @@ export const LocalStorage = {
return null;
}
},
setItem: (key: string, value: string) => {
setItem: (key, value) => {
if (typeof window === 'undefined') return;
try {
window.localStorage.setItem(key, value);
@ -18,7 +18,7 @@ export const LocalStorage = {
console.error(error);
}
},
removeItem: (key: string) => {
removeItem: (key) => {
if (typeof window === 'undefined') return;
try {
window.localStorage.removeItem(key);

View File

@ -1,25 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -1,22 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": [
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

View File

@ -16,7 +16,7 @@
"baseUrl": ".",
"paths": {
"@vegaprotocol/react-helpers": ["libs/react-helpers/src/index.ts"],
"@vegaprotocol/storage": ["libs/storage/src/index.ts"],
"@vegaprotocol/storage": ["libs/storage/src/index.js"],
"@vegaprotocol/tailwindcss-config": [
"libs/tailwindcss-config/src/index.js"
],