Created
April 19, 2020 10:44
-
-
Save Yengas/2219e9682d30b0becb7eb64338ca76cf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import TitleInput from './TitleInput'; | |
import OptionInput from './OptionInput'; | |
import {withLogic} from "../../utilities/with-logic"; | |
import DataLogic from "./data-logic"; | |
type Option = { | |
id: string; | |
text: string; | |
}; | |
type PollData = { | |
title: string; | |
options: Option[]; | |
} | |
type Props = { | |
title: string; | |
options: Option[]; | |
loading: boolean; | |
changeTitle(title: string): void; | |
changeOption(id: string, text: string): void; | |
onSave(data: PollData): void; | |
reset(): void; | |
}; | |
const CreatePoll = ({ | |
title, | |
options, | |
loading, | |
changeTitle, | |
changeOption, | |
onSave, | |
reset | |
}: Props) => ( | |
<div> | |
<TitleInput | |
value={title} | |
onChange={(text) => changeTitle(text)} | |
/> | |
<div className="create-poll__options"> | |
{options.map(({id, text}) => ( | |
<OptionInput | |
key={id} | |
value={text} | |
onChange={(text) => changeOption(id, text)}/> | |
))} | |
</div> | |
<div> | |
<button disabled={loading} onClick={() => onSave({title, options})}>Save</button> | |
<button disabled={loading} onClick={() => reset()}>Reset</button> | |
</div> | |
</div> | |
); | |
// bunu burada yapmak zorunda değiliz, bağlama işlemini başka bir yerde de yapabiliriz. | |
// örnek için DataLogic burada bağlanırken, SaveLogic CreatePoll component'ının kullanıldığı yerde bağlanılıyor | |
export default withLogic(DataLogic, CreatePoll); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {useState} from "react"; | |
type Option = { | |
id: string; | |
text: string; | |
} | |
const DEFAULT_OPTIONS = [ | |
{id: '1', text: ''}, | |
]; | |
function updateOptionText(options: Option[], id: string, text: string) { | |
return options.reduce<Option[]>((opts, cur) => { | |
if (cur.id === id) { | |
return [...opts, {...cur, text}] | |
} | |
return [...opts, cur]; | |
}, []); | |
} | |
const updateOption = (options: Option[], id: string, text: string) => { | |
const newOptions = updateOptionText(options, id, text); | |
// add new option if the last option is edited | |
if (newOptions[newOptions.length - 1].id === id) { | |
return [...newOptions, {id: (+id + 1).toString(), text: ''}]; | |
} else { | |
return newOptions; | |
} | |
}; | |
const DataLogic = () => { | |
const [title, setTitle] = useState(''); | |
const [options, setOptions] = useState<Option[]>(DEFAULT_OPTIONS); | |
const reset = () => { | |
setTitle(''); | |
setOptions(DEFAULT_OPTIONS); | |
}; | |
const changeTitle = (text: string) => { | |
return setTitle(text); | |
}; | |
const changeOption = (id: string, text: string) => { | |
const newOptions = updateOption(options, id, text); | |
setOptions(newOptions); | |
}; | |
return { | |
title, | |
options, | |
changeTitle, | |
changeOption, | |
reset, | |
}; | |
}; | |
export default DataLogic; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {ComponentProps, useState} from 'react'; | |
import CreatePoll from "./index"; | |
import {useServices} from "../../services/context"; | |
const SaveLogic = () => { | |
const { poll, notification } = useServices(); | |
const [loading, setLoading] = useState(false); | |
const onSave: ComponentProps<typeof CreatePoll>['onSave'] = async (data) => { | |
setLoading(true); | |
try { | |
await poll.create(data); | |
notification.success('created poll!'); | |
} catch(err) { | |
notification.error(err.message); | |
} | |
setLoading(false); | |
}; | |
return { | |
onSave, | |
loading | |
}; | |
}; | |
export default SaveLogic; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import MainLayout from '../MainLayout'; | |
import CreatePoll from "../../pages/CreatePoll"; | |
import {withLogic} from "../../utilities/with-logic"; | |
import SaveLogic from "../../pages/CreatePoll/save-logic"; | |
// SaveLogic burada bağlandı CreatePoll'a. | |
const CP = withLogic(SaveLogic, CreatePoll); | |
function App() { | |
return ( | |
<MainLayout> | |
<CP /> | |
</MainLayout> | |
); | |
} | |
export default App; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { FC } from 'react'; | |
import {renderHook, act} from '@testing-library/react-hooks'; | |
import {anything, instance, mock, reset, verify, when} from 'ts-mockito'; | |
import {NotificationService} from "../../services/notification"; | |
import {PollService} from "../../services/poll"; | |
import SaveLogic from './save-logic'; | |
import {ServiceProvider} from "../../services/context"; | |
import {mockPromise} from "../../utilities/mock-promise"; | |
const mockNotification = mock(NotificationService); | |
const mockPoll = mock(PollService); | |
const mockServices = { | |
notification: instance(mockNotification), | |
poll: instance(mockPoll), | |
}; | |
const wrapper: FC = ({ children }) => (<ServiceProvider services={mockServices}>{children}</ServiceProvider>); | |
describe('<SaveLogic />', () => { | |
afterEach(() => { | |
reset(mockNotification); | |
reset(mockPoll); | |
}); | |
it('should create successfully', async () => { | |
const pollData = {} as any; | |
const { promise, resolve } = mockPromise<void>(); | |
when(mockPoll.create(pollData)).thenReturn(promise); | |
const { result, waitForNextUpdate } = renderHook(() => SaveLogic(), { wrapper }); | |
act(() => { | |
result.current.onSave(pollData); | |
}); | |
verify(mockPoll.create(pollData)).once(); | |
expect(result.current.loading).toBe(true); | |
act(() => resolve()); | |
await waitForNextUpdate(); | |
expect(result.current.loading).toBe(false); | |
verify(mockNotification.success(anything())).once(); | |
verify(mockNotification.error(anything())).never(); | |
}); | |
it('should handle create error', async () => { | |
const pollData = {} as any; | |
const { promise, reject } = mockPromise<void>(); | |
when(mockPoll.create(pollData)).thenReturn(promise); | |
const { result, waitForNextUpdate } = renderHook(() => SaveLogic(), { wrapper }); | |
act(() => { | |
result.current.onSave(pollData); | |
}); | |
verify(mockPoll.create(pollData)).once(); | |
expect(result.current.loading).toBe(true); | |
act(() => reject(new Error('some error'))); | |
await waitForNextUpdate(); | |
expect(result.current.loading).toBe(false); | |
verify(mockNotification.error(anything())).once(); | |
verify(mockNotification.success(anything())).never(); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {ComponentProps} from 'react'; | |
import {fireEvent, render} from '@testing-library/react'; | |
import {wait} from '@testing-library/dom'; | |
import CreatePoll from './index'; | |
const renderComponent = (props: Partial<ComponentProps<typeof CreatePoll>>) => render( | |
<CreatePoll {...(props as any)} /> | |
); | |
describe('<CreatePoll />', () => { | |
it('should have one option at initial state', () => { | |
const {container} = renderComponent({}); | |
const options = container.querySelector('.create-poll__options'); | |
expect(options).not.toBe(null); | |
expect(options!.querySelectorAll('input').length).toBe(1); | |
}); | |
it('should change title', async () => { | |
const {getByTestId} = renderComponent({}); | |
const title = getByTestId('title') as any; | |
fireEvent.change(title!, {target: {value: 'some title'}}); | |
await wait(() => expect(title!.value).toBe('some title')); | |
}); | |
it('should change option', async () => { | |
const {container} = renderComponent({}); | |
const options = container.querySelector('.create-poll__options'); | |
const lastOption: HTMLInputElement | null = options!.querySelector('input:last-child'); | |
fireEvent.change(lastOption!, {target: {value: 'some option'}}); | |
await wait(() => expect(lastOption!.value).toBe('some option')); | |
}); | |
it('should create new option', () => { | |
const {container} = renderComponent({}); | |
const options = container.querySelector('.create-poll__options'); | |
const lastOption = options!.querySelector('input:last-child'); | |
fireEvent.change(lastOption!, {target: {value: 'some option'}}); | |
expect(options!.querySelectorAll('input').length).toBe(2); | |
}); | |
it('should reset', () => { | |
const {container, getByTestId, getByText} = renderComponent({}); | |
const title = getByTestId('title') as any; | |
const option = container.querySelector('.create-poll__options input') as any; | |
const resetButton = getByText(/reset/i); | |
fireEvent.change(title!, {target: {value: 'some title'}}); | |
fireEvent.change(option!, {target: {value: 'some option'}}); | |
fireEvent.click(resetButton!); | |
expect(title!.value).toEqual(''); | |
expect(option!.value).toEqual(''); | |
}); | |
it('should save', () => { | |
const onSaveSpy = jest.fn(); | |
const {container, getByTestId, getByText} = renderComponent({onSave: onSaveSpy}); | |
const title = getByTestId('title') as any; | |
const option = container.querySelector('.create-poll__options input') as any; | |
const saveButton = getByText(/save/i); | |
fireEvent.change(title!, {target: {value: 'some title'}}); | |
fireEvent.change(option!, {target: {value: 'some option'}}); | |
fireEvent.click(saveButton!); | |
expect(onSaveSpy).toHaveBeenCalledWith(expect.objectContaining({ | |
title: 'some title', | |
options: [ | |
{id: '1', text: 'some option'}, | |
{id: '2', text: ''} | |
] | |
})); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment