Skip to content

Instantly share code, notes, and snippets.

@esmevane
Created June 2, 2020 19:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save esmevane/b9cfdd1551c77b285c0c7c74c2adb980 to your computer and use it in GitHub Desktop.
Save esmevane/b9cfdd1551c77b285c0c7c74c2adb980 to your computer and use it in GitHub Desktop.
[ Typescript / React ]: Login kata 06-02-2020
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import App, { AppContext } from './App';
const SpiesOnApp = () => {
const context = React.useContext(AppContext);
return <>{JSON.stringify(context)}</>;
};
test('tracks username', async () => {
const listener = jest.fn();
const username = 'sir_farblegart';
const password = 'nevergonnaguess';
const testingLibraryAssertions = render(
<App onCreateUser={listener}>
<SpiesOnApp />
</App>
);
fireEvent.input(
(await testingLibraryAssertions.findByPlaceholderText(
'username'
)) as HTMLInputElement,
{
target: {
value: username,
},
}
);
fireEvent.input(
(await testingLibraryAssertions.findByPlaceholderText(
'password'
)) as HTMLInputElement,
{
target: {
value: password,
},
}
);
fireEvent.click(await testingLibraryAssertions.findByRole('button'));
expect(listener).toHaveBeenCalledWith(username, password);
testingLibraryAssertions.debug();
});
import React from 'react';
import './App.css';
interface AppProps {
onCreateUser: (username: string, password: string) => void;
}
export const AppContext = React.createContext({} as any);
export const AppFields = React.createContext({} as any);
type Interface<State, Event, View, Effect> = (
state: State,
event: Event
) => [View, Effect];
const UpdatePassword: 'UPDATE_PASSWORD' = 'UPDATE_PASSWORD';
const UpdateUsername: 'UPDATE_USERNAME' = 'UPDATE_USERNAME';
interface AppState {
username: string;
password: string;
}
type AppEvent =
| { type: typeof UpdatePassword; password: string }
| { type: typeof UpdateUsername; username: string };
const initialState: AppState = { username: '', password: '' };
const reducer = (state: AppState, event: AppEvent) => {
switch (event.type) {
case UpdateUsername:
return { ...state, username: event.username };
case UpdatePassword:
return { ...state, password: event.password };
default:
return state;
}
};
const useUserForm = () => {
const [state, dispatch] = React.useReducer(reducer, initialState);
const updatePassword = (password: string) =>
dispatch({ type: 'UPDATE_PASSWORD', password });
const updateUsername = (username: string) =>
dispatch({ type: 'UPDATE_USERNAME', username });
const events = {
updatePassword: (event: any) => updatePassword(event.target.value),
updateUsername: (event: any) => updateUsername(event.target.value),
};
const fields = {
password: {
onChange: events.updatePassword,
name: 'password',
type: 'password',
value: state.password,
placeholder: 'password',
},
username: {
onChange: events.updateUsername,
name: 'username',
type: 'text',
value: state.username,
placeholder: 'username',
},
};
return [state, fields] as [typeof state, typeof fields];
};
const Field: React.FC<{ type: 'username' | 'password' }> = ({ type }) => {
const fields = React.useContext(AppFields);
const fieldProps = fields[type];
return (
<>
<input {...fieldProps} />
</>
);
};
const App: React.FC<AppProps> = ({ children, onCreateUser }) => {
const [state, fields] = useUserForm();
return (
<AppContext.Provider value={state}>
<AppFields.Provider value={fields}>
<form
className="App"
onSubmit={(event) => {
event.preventDefault();
onCreateUser(state.username, state.password);
}}
>
<Field type="username" />
<Field type="password" />
<button>Submit</button>
</form>
{children}
</AppFields.Provider>
</AppContext.Provider>
);
};
export default App;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment