Skip to content

Commit

Permalink
Description is required for self-dev tag - desktop view
Browse files Browse the repository at this point in the history
  • Loading branch information
Hubert Legec committed Sep 13, 2021
1 parent 64d9d4e commit c021309
Show file tree
Hide file tree
Showing 12 changed files with 288 additions and 29 deletions.
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"devDependencies": {
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^13.2.1",
"@types/autosuggest-highlight": "^3.1.0",
"@types/jest": "^26.0.15",
"@types/jsonwebtoken": "^8.3.5",
Expand Down
4 changes: 2 additions & 2 deletions src/api/OpenTrappAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export class OpenTrappAPI {
return this.get<ReportingWorkLogDTO[]>(`/calendar/${year}/${month}/work-log/entries`);
}

saveWorkLog(day: string, tags: string[], workload: string, username: string): Observable<string> {
return this.post<{ id: string }>(`/employee/${username}/work-log/entries`, {projectNames: tags, workload, day}).pipe(
saveWorkLog(username: string, day: string, tags: string[], workload: string, note?: string): Observable<string> {
return this.post<{ id: string }>(`/employee/${username}/work-log/entries`, {projectNames: tags, workload, day, note}).pipe(
map(r => r.id)
);
}
Expand Down
43 changes: 32 additions & 11 deletions src/components/registrationPage/RegistrationPage.desktop.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Store } from 'redux';
import { Provider } from 'react-redux';
import { chain } from 'lodash';
import { fireEvent, render, RenderResult, waitFor, within } from '@testing-library/react';
import userEvent from "@testing-library/user-event";
import { ignoreHtmlTags, setupStore } from '../../utils/testUtils';
import { RegistrationPageDesktop } from './RegistrationPage.desktop';
import { OpenTrappRestAPI } from '../../api/OpenTrappAPI';
Expand Down Expand Up @@ -78,14 +79,13 @@ describe('Registration Page - desktop', () => {

describe('Monthly report', () => {
it('displays current month', async () => {
const {getByText} = render(
const {findByText} = render(
<Provider store={store}>
<RegistrationPageDesktop/>
</Provider>
);
await waitFor(() => {});

expect(getByText(ignoreHtmlTags('2019/02 month worklog'))).toBeInTheDocument();
expect(await findByText(ignoreHtmlTags('2019/02 month worklog'))).toBeInTheDocument();
});

it('fetches and renders days with workload for current month', async () => {
Expand All @@ -107,18 +107,18 @@ describe('Registration Page - desktop', () => {
});

it('reloads data on NEXT month click', async () => {
const container = render(
const { getByText, getByTestId } = render(
<Provider store={store}>
<RegistrationPageDesktop/>
</Provider>
);
await waitFor(() => {});

fireEvent.click(container.getByText('Next'));
fireEvent.click(getByText('Next'));

await waitFor(() => expect(httpMock.history.get.filter(r => r.url.startsWith('/calendar'))).toHaveLength(4));
expect(container.getByTestId('monthly-report')).toBeInTheDocument();
expect(container.getByText(ignoreHtmlTags('2019/03 month worklog'))).toBeInTheDocument();
expect(getByTestId('monthly-report')).toBeInTheDocument();
expect(getByText(ignoreHtmlTags('2019/03 month worklog'))).toBeInTheDocument();
});

it('reloads data on PREVIOUS month click', async () => {
Expand Down Expand Up @@ -199,9 +199,8 @@ describe('Registration Page - desktop', () => {

typeExpression(container, '1d #projects #nvm @2019/02/27');
pressEnter(container);
await waitFor(() => {});

expect(httpMock.history.post.length).toEqual(1);
await waitFor(() => expect(httpMock.history.post).toHaveLength(1));
expect(JSON.parse(httpMock.history.post[0].data)).toEqual({
projectNames: ['projects', 'nvm'],
workload: '1d',
Expand All @@ -220,7 +219,7 @@ describe('Registration Page - desktop', () => {
typeExpression(container, '1d #projects #nvm @2019/02/27~@2019/02/28');
pressEnter(container);

await waitFor(() => expect(httpMock.history.post.length).toEqual(2));
await waitFor(() => expect(httpMock.history.post).toHaveLength(2));
expect(JSON.parse(httpMock.history.post[0].data)).toEqual({
projectNames: ['projects', 'nvm'],
workload: '1d',
Expand All @@ -233,6 +232,28 @@ describe('Registration Page - desktop', () => {
});
});

it('saves self-dev work log with description', async () => {
const container = render(
<Provider store={store}>
<RegistrationPageDesktop/>
</Provider>
);
await waitFor(() => {});

typeExpression(container, '1d #internal #self-dev @2019/02/27');
pressEnter(container);
userEvent.type(within(container.getByLabelText('Self-dev description')).getByRole('textbox'), 'Some self-dev description');
userEvent.click(container.getByRole('button', { name: 'Confirm' }));

await waitFor(() => expect(httpMock.history.post).toHaveLength(1));
expect(JSON.parse(httpMock.history.post[0].data)).toEqual({
projectNames: ['internal', 'self-dev'],
workload: '1d',
day: '2019/02/27',
note: 'Some self-dev description'
});
})

it('reloads work logs after save', async () => {
const container = render(
<Provider store={store}>
Expand All @@ -254,7 +275,7 @@ describe('Registration Page - desktop', () => {
});

function workLogInput(container: RenderResult) {
return container.getByRole('combobox').firstChild.firstChild;
return within(container.getByRole('combobox')).getByRole('textbox');
}

function typeExpression(container: RenderResult, expression: string) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ParsedWorkLog } from '../../../workLogExpressionParser/ParsedWorkLog';
import { Dialog } from '@material-ui/core';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import DialogActions from '@material-ui/core/DialogActions';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event'
import { noop, repeat } from 'lodash';
import { SelfDevDescriptionDialog } from './SelfDevDescriptionDialog';

describe('Self-dev description dialog', () => {
it('shows content if open', () => {
const { getByRole, getByLabelText } = render(
<SelfDevDescriptionDialog open={true} onCancel={noop} onConfirm={noop} />
);

expect(getByLabelText('Self-dev description')).toBeVisible();
expect(getByRole('button', { name: 'Cancel' })).toBeVisible();
expect(getByRole('button', { name: 'Confirm' })).toBeVisible();
})

it('does not show content if closed', () => {
const { queryByLabelText, queryByRole } = render(
<SelfDevDescriptionDialog open={true} onCancel={noop} onConfirm={noop} />
);

expect(queryByLabelText('Self-dev description')).toBeVisible();
expect(queryByRole('button', { name: 'Cancel' })).toBeVisible();
expect(queryByRole('button', { name: 'Confirm' })).toBeVisible();
})

it('returns description on confirm', () => {
const onConfirm = jest.fn();
const { getByRole } = render(
<SelfDevDescriptionDialog open={true} onCancel={noop} onConfirm={onConfirm}/>
);

userEvent.type(getByRole('textbox'), 'Some self-dev description');
userEvent.click(getByRole('button', { name: 'Confirm' }));

expect(onConfirm).toHaveBeenCalledWith('Some self-dev description');
})

it('discards description on cancel', () => {
const onCancel = jest.fn();
const { getByRole } = render(
<SelfDevDescriptionDialog open={true} onCancel={onCancel} onConfirm={onCancel}/>
);

userEvent.click(getByRole('button', { name: 'Cancel' }));

expect(onCancel).toHaveBeenCalled();
})

it('description cannot be empty', () => {
const onConfirm = jest.fn();
const { getByRole, getByText } = render(
<SelfDevDescriptionDialog open={true} onCancel={noop} onConfirm={onConfirm}/>
);

userEvent.click(getByRole('button', { name: 'Confirm' }));

expect(getByText('Description cannot be empty')).toBeVisible();
expect(onConfirm).not.toHaveBeenCalled();
})

it('description cannot be blank', () => {
const onConfirm = jest.fn();
const { getByRole, getByText } = render(
<SelfDevDescriptionDialog open={true} onCancel={noop} onConfirm={onConfirm}/>
);

userEvent.type(getByRole('textbox'), ' ');
userEvent.click(getByRole('button', { name: 'Confirm' }));

expect(getByText('Description cannot be empty')).toBeVisible();
expect(onConfirm).not.toHaveBeenCalled();
})

it('description is trimmed', () => {
const onConfirm = jest.fn();
const { getByRole } = render(
<SelfDevDescriptionDialog open={true} onCancel={noop} onConfirm={onConfirm}/>
);

userEvent.type(getByRole('textbox'), ' Text with spaces ');
userEvent.click(getByRole('button', { name: 'Confirm' }));

expect(onConfirm).toHaveBeenCalledWith('Text with spaces');
})

it('description cannot be longer than 1000 characters', () => {
const onConfirm = jest.fn();
const { getByRole, getByText } = render(
<SelfDevDescriptionDialog open={true} onCancel={noop} onConfirm={onConfirm}/>
);

userEvent.type(getByRole('textbox'), repeat('a', 1001));
userEvent.click(getByRole('button', { name: 'Confirm' }));

expect(getByText('Description cannot be longer than 1000 characters')).toBeVisible();
expect(onConfirm).not.toHaveBeenCalled();
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useEffect, useState } from 'react';
import { isEmpty, trim } from 'lodash'
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import TextField from '@material-ui/core/TextField';

interface SelfDevDescriptionDialogProps {
open: boolean;
onCancel: () => void;
onConfirm: (description: string) => void;
}

const validateDescription = (description: string) => {
if (isEmpty(trim(description))) {
return 'Description cannot be empty'
} else if (description.length > 1000) {
return 'Description cannot be longer than 1000 characters'
}
return undefined
}

export const SelfDevDescriptionDialog = ({open, onCancel, onConfirm}: SelfDevDescriptionDialogProps) => {
const [description, setDescription] = useState('')
const [error, setError] = useState(undefined);

useEffect(() => {
setDescription('');
setError(undefined);
}, [open]);

const handleConfirm = () => {
const errorMessage = validateDescription(description)
if (!errorMessage) {
onConfirm(trim(description))
} else {
setError(errorMessage)
}
}

const handleChange = v => {
setError(validateDescription(v));
setDescription(v);
}

return (
<Dialog open={open} onClose={onCancel}>
<DialogTitle>Self-dev description</DialogTitle>
<DialogContent>
<p>Short description is required to report a self-dev</p>
<TextField aria-label='Self-dev description'
multiline
type='text'
value={description}
onChange={e => handleChange(e.target.value)}
error={!!error}
size='medium'
helperText={error}
fullWidth />
</DialogContent>
<DialogActions>
<Button onClick={onCancel}>
Cancel
</Button>
<Button onClick={handleConfirm}>
Confirm
</Button>
</DialogActions>
</Dialog>
);
}
53 changes: 46 additions & 7 deletions src/components/registrationPage/workLogInput/WorkLogInput.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import moment from 'moment';
import { fireEvent, render, RenderResult } from '@testing-library/react';
import { noop } from 'lodash';
import { WorkLogInput, WorkLogInputProps } from './WorkLogInput';
import { ParsedWorkLog } from '../../../workLogExpressionParser/ParsedWorkLog';
import { Preset } from '../registration.model';
import { ignoreHtmlTags } from '../../../utils/testUtils';
import userEvent from '@testing-library/user-event'
import {fireEvent, render, RenderResult, within} from '@testing-library/react';
import {noop} from 'lodash';
import {WorkLogInput, WorkLogInputProps} from './WorkLogInput';
import {ParsedWorkLog} from '../../../workLogExpressionParser/ParsedWorkLog';
import {Preset} from '../registration.model';
import {ignoreHtmlTags} from '../../../utils/testUtils';

const tags = ['projects', 'nvm', 'vacation'];

Expand Down Expand Up @@ -202,6 +203,43 @@ describe('WorkLogInput', () => {
});
});

describe('self dev', () => {
it('asks for description if work log contains self-dev tag', () => {
const onSave = jest.fn();
const initialWorkLog = new ParsedWorkLog('1d #internal #self-dev @2019/03/01', ['2019/03/01'], ['internal', 'self-dev'], '1d');
const container = render(worLogInput({workLog: initialWorkLog, tags}));

pressEnter(container);

expect(onSave).not.toHaveBeenCalled();
expect(container.getByLabelText('Self-dev description')).toBeInTheDocument();
expect(container.getByText('Short description is required to report a self-dev'))
.toBeInTheDocument();
});

it('emits save if description is provided', () => {
let savePayload: ParsedWorkLog = undefined;
const initialWorkLog = new ParsedWorkLog('1d #internal #self-dev @2019/03/01', ['2019/03/01'], ['internal', 'self-dev'], '1d');
const container = render(worLogInput({workLog: initialWorkLog, tags, onSave: payload => savePayload = payload}));

pressEnter(container);
userEvent.type(getDescriptionInput(container), 'Some self-dev description');
userEvent.click(container.getByRole('button', { name: 'Confirm' }));

expect(container.getByText('Self-dev description')).not.toBeVisible();
expect(savePayload.validate().valid).toBeTruthy();
expect(savePayload.workload).toEqual('1d');
expect(savePayload.days).toEqual(['2019/03/01']);
expect(savePayload.tags).toEqual(['internal', 'self-dev']);
expect(savePayload.note).toEqual('Some self-dev description');
});

function getDescriptionInput(container: RenderResult) {
return within(container.getByLabelText('Self-dev description'))
.getByRole('textbox');
}
})

describe('validation', () => {
it('does not show icon for empty input', () => {
const initialWorkLog = ParsedWorkLog.empty();
Expand Down Expand Up @@ -266,6 +304,7 @@ describe('WorkLogInput', () => {
}

function workLogInput(container: RenderResult) {
return container.getByRole('combobox').firstChild.firstChild;
return within(container.getByRole('combobox'))
.getByRole('textbox')
}
});
Loading

0 comments on commit c021309

Please sign in to comment.