From 4afc7e261f5d2eecdde45bd871e9ea107852a3a4 Mon Sep 17 00:00:00 2001 From: arbulu89 Date: Mon, 29 Apr 2024 14:35:41 +0200 Subject: [PATCH 1/4] Update UserForm to allow edition style --- assets/js/lib/test-utils/factories/users.js | 3 +- assets/js/pages/Users/UserForm.jsx | 41 +++++++++++++++------ assets/js/pages/Users/UserForm.stories.jsx | 31 +++++++++++----- assets/js/pages/Users/UserForm.test.jsx | 40 ++++++++++++++++---- 4 files changed, 87 insertions(+), 28 deletions(-) diff --git a/assets/js/lib/test-utils/factories/users.js b/assets/js/lib/test-utils/factories/users.js index bc9bf258a7..4e8411cd6d 100644 --- a/assets/js/lib/test-utils/factories/users.js +++ b/assets/js/lib/test-utils/factories/users.js @@ -10,7 +10,8 @@ export const userFactory = Factory.define(() => ({ enabled: faker.datatype.boolean(), fullname: faker.internet.displayName(), email: faker.internet.email(), - password: faker.internet.password(), + created_at: formatISO(faker.date.past()), + updated_at: formatISO(faker.date.past()), })); export const adminUser = userFactory.params({ diff --git a/assets/js/pages/Users/UserForm.jsx b/assets/js/pages/Users/UserForm.jsx index 0ca52b488f..7f104b232e 100644 --- a/assets/js/pages/Users/UserForm.jsx +++ b/assets/js/pages/Users/UserForm.jsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; import { capitalize, noop } from 'lodash'; +import { format, parseISO } from 'date-fns'; import Button from '@common/Button'; import Input, { Password } from '@common/Input'; @@ -10,6 +11,7 @@ import { getError } from '@lib/api/validationErrors'; const USER_ENABLED = 'Enabled'; const REQUIRED_FIELD_TEXT = 'Required field'; +const PASSWORD_PLACEHOLDER = '********'; const PASSWORD_POLICY_TEXT = (
The password must be compliant with: @@ -33,12 +35,13 @@ function UserForm({ fullName = '', emailAddress = '', username = '', - password = '', - confirmPassword = '', status = 'Enabled', + createdAt = '', + updatedAt = '', errors = defaultErrors, saving = false, saveText = 'Create', + editing = false, onSave = noop, onCancel = noop, }) { @@ -48,9 +51,9 @@ function UserForm({ const [emailAddressErrorState, setEmailAddressError] = useState(null); const [usernameState, setUsername] = useState(username); const [usernameErrorState, setUsernameError] = useState(null); - const [passwordState, setPassword] = useState(password); + const [passwordState, setPassword] = useState(''); const [passwordErrorState, setPasswordError] = useState(null); - const [confirmPasswordState, setConfirmPassword] = useState(confirmPassword); + const [confirmPasswordState, setConfirmPassword] = useState(''); const [confirmPasswordErrorState, setConfirmPasswordError] = useState(null); const [statusState, setStatus] = useState(status); @@ -79,12 +82,12 @@ function UserForm({ error = true; } - if (!passwordState) { + if (!editing && !passwordState) { setPasswordError(REQUIRED_FIELD_TEXT); error = true; } - if (!confirmPasswordState) { + if (!editing && !confirmPasswordState) { setConfirmPasswordError(REQUIRED_FIELD_TEXT); error = true; } @@ -100,11 +103,14 @@ function UserForm({ const user = { fullname: fullNameState, email: emailAddressState, - username: usernameState, - password: passwordState, - password_confirmation: confirmPasswordState, enabled: statusState === USER_ENABLED, + ...(!editing && { username: usernameState }), + ...(passwordState && { password: passwordState }), + ...(confirmPasswordState && { + password_confirmation: confirmPasswordState, + }), }; + onSave(user); }; @@ -157,6 +163,7 @@ function UserForm({ setUsername(value); setUsernameError(null); }} + disabled={editing} /> {usernameErrorState && errorMessage(usernameErrorState)}
@@ -171,7 +178,7 @@ function UserForm({ { setPassword(value); @@ -187,7 +194,7 @@ function UserForm({ { setConfirmPassword(value); @@ -213,6 +220,18 @@ function UserForm({ }} /> + {editing && ( + <> + + + {format(parseISO(createdAt), 'PPpp')} + + + + {format(parseISO(updatedAt), 'PPpp')} + + + )}

* Required Fields

diff --git a/assets/js/pages/Users/UserForm.stories.jsx b/assets/js/pages/Users/UserForm.stories.jsx index b7b58172eb..50019747db 100644 --- a/assets/js/pages/Users/UserForm.stories.jsx +++ b/assets/js/pages/Users/UserForm.stories.jsx @@ -5,7 +5,13 @@ import { userFactory } from '@lib/test-utils/factories/users'; import UserForm from './UserForm'; -const { fullname, email, username, password } = userFactory.build(); +const { + fullname, + email, + username, + created_at: createdAt, + updated_at: updatedAt, +} = userFactory.build(); function ContainerWrapper({ children }) { return ( @@ -35,18 +41,24 @@ export default { type: 'text', }, }, - password: { - description: 'Password', + createdAt: { + description: 'User creation time', control: { type: 'text', }, }, - confirmPassword: { - description: 'Password confirmation', + udpatedAt: { + description: 'User last edition time', control: { type: 'text', }, }, + editing: { + description: 'User is being edited', + control: { + type: 'boolean', + }, + }, errors: { description: 'OpenAPI errors coming from backend validation', }, @@ -75,19 +87,20 @@ export default { export const Empty = {}; -export const PrePopulated = { +export const Editing = { args: { fullName: fullname, emailAddress: email, username, - password, - confirmPassword: password, + createdAt, + updatedAt, + editing: true, }, }; export const WithErrors = { args: { - ...PrePopulated.args, + ...Editing.args, errors: [ { detail: 'Error validating fullname', diff --git a/assets/js/pages/Users/UserForm.test.jsx b/assets/js/pages/Users/UserForm.test.jsx index fe93ad6dbf..12e82795f9 100644 --- a/assets/js/pages/Users/UserForm.test.jsx +++ b/assets/js/pages/Users/UserForm.test.jsx @@ -4,6 +4,7 @@ import { act, render, screen } from '@testing-library/react'; import 'intersection-observer'; import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; +import { faker } from '@faker-js/faker'; import { userFactory } from '@lib/test-utils/factories/users'; @@ -25,6 +26,8 @@ describe('UserForm', () => { expect(screen.getByLabelText('password-confirmation').value).toBe(''); expect(screen.getByText('Permissions')).toBeVisible(); expect(screen.getByText('Status')).toBeVisible(); + expect(screen.queryByText('Created')).not.toBeInTheDocument(); + expect(screen.queryByText('Updated')).not.toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Save' })).toBeVisible(); expect(screen.getByRole('button', { name: 'Cancel' })).toBeVisible(); @@ -39,8 +42,9 @@ describe('UserForm', () => { fullName={fullname} emailAddress={email} username={username} - password={password} - confirmPassword={password} + createdAt={createdAt} + updatedAt={updatedAt} + editing /> ); }); @@ -48,11 +52,13 @@ describe('UserForm', () => { expect(screen.getByLabelText('fullname').value).toBe(fullname); expect(screen.getByLabelText('email').value).toBe(email); expect(screen.getByLabelText('username').value).toBe(username); - expect(screen.getByLabelText('password').value).toBe(password); - expect(screen.getByLabelText('password-confirmation').value).toBe(password); + expect(screen.getByLabelText('username')).toBeDisabled(); + expect(screen.getAllByPlaceholderText('********').length).toBe(2); + expect(screen.getByText('Created')).toBeVisible(); + expect(screen.getByText('Updated')).toBeVisible(); }); - it('should display a prepopulated User form with errors', () => { + it('should display a form with errors', () => { const errors = [ { detail: 'Error validating fullname', @@ -102,9 +108,29 @@ describe('UserForm', () => { expect(screen.getAllByText('Required field').length).toBe(5); }); - it('should save the user', async () => { + it('should fail if required fields on editing mode are missing', async () => { const user = userEvent.setup(); - const { fullname, email, username, password } = userFactory.build(); + const { created_at: createdAt, updated_at: updatedAt } = + userFactory.build(); + + render( + + ); + + await user.click(screen.getByRole('button', { name: 'Edit' })); + + expect(screen.getAllByText('Required field').length).toBe(3); + }); + + it('saves the user', async () => { + const user = userEvent.setup(); + const { fullname, email, username } = userFactory.build(); + const password = faker.internet.password(); const mockOnSave = jest.fn(); render(); From 1f1dedce3c428a7808f6cf0e00322125fd0cde58 Mon Sep 17 00:00:00 2001 From: arbulu89 Date: Mon, 29 Apr 2024 14:36:06 +0200 Subject: [PATCH 2/4] Add EditUserPage --- assets/js/lib/api/users.js | 4 +- assets/js/pages/Users/CreateUserPage.test.jsx | 7 +- assets/js/pages/Users/EditUserPage.jsx | 109 ++++++++++++ assets/js/pages/Users/EditUserPage.test.jsx | 156 ++++++++++++++++++ assets/js/pages/Users/index.js | 3 +- assets/js/trento.jsx | 3 +- 6 files changed, 277 insertions(+), 5 deletions(-) create mode 100644 assets/js/pages/Users/EditUserPage.jsx create mode 100644 assets/js/pages/Users/EditUserPage.test.jsx diff --git a/assets/js/lib/api/users.js b/assets/js/lib/api/users.js index 728089a474..bbb7c63555 100644 --- a/assets/js/lib/api/users.js +++ b/assets/js/lib/api/users.js @@ -1,7 +1,9 @@ -import { del, get, post } from '@lib/network'; +import { del, get, patch, post } from '@lib/network'; export const listUsers = () => get('/users'); export const createUser = (payload) => post('/users', payload); +export const editUser = (userID, payload) => patch(`/users/${userID}`, payload); + export const deleteUser = (userID) => del(`/users/${userID}`); diff --git a/assets/js/pages/Users/CreateUserPage.test.jsx b/assets/js/pages/Users/CreateUserPage.test.jsx index 77150a99fe..d6a512300f 100644 --- a/assets/js/pages/Users/CreateUserPage.test.jsx +++ b/assets/js/pages/Users/CreateUserPage.test.jsx @@ -6,6 +6,7 @@ import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import MockAdapter from 'axios-mock-adapter'; +import { faker } from '@faker-js/faker'; // eslint-disable-next-line import/no-extraneous-dependencies import * as router from 'react-router'; @@ -53,7 +54,8 @@ describe('CreateUserPage', () => { const user = userEvent.setup(); const navigate = jest.fn(); jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate); - const { fullname, email, username, password } = userFactory.build(); + const { fullname, email, username } = userFactory.build(); + const password = faker.internet.password(); axiosMock.onPost(USERS_URL).reply(202, {}); @@ -74,7 +76,8 @@ describe('CreateUserPage', () => { const user = userEvent.setup(); const navigate = jest.fn(); jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate); - const { fullname, email, username, password } = userFactory.build(); + const { fullname, email, username } = userFactory.build(); + const password = faker.internet.password(); const errors = [ { diff --git a/assets/js/pages/Users/EditUserPage.jsx b/assets/js/pages/Users/EditUserPage.jsx new file mode 100644 index 0000000000..8431d27844 --- /dev/null +++ b/assets/js/pages/Users/EditUserPage.jsx @@ -0,0 +1,109 @@ +import React, { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; + +import BackButton from '@common/BackButton'; +import Banner from '@common/Banners/Banner'; +import PageHeader from '@common/PageHeader'; + +import { editUser, getUser } from '@lib/api/users'; + +import UserForm from './UserForm'; + +function EditUserPage() { + const { userID } = useParams(); + const navigate = useNavigate(); + const [savingState, setSaving] = useState(false); + const [errorsState, setErrors] = useState([]); + const [loading, setLoading] = useState(true); + const [userState, setUser] = useState(null); + const [updatedByOther, setUpdatedByOther] = useState(false); + + useEffect(() => { + getUser(userID) + .then(({ data: user }) => { + setUser(user); + }) + .catch(() => {}) + .finally(() => { + setLoading(false); + }); + }, [userID]); + + const onEditUser = (payload) => { + setSaving(true); + editUser(userID, payload) + .then(() => { + navigate('/users'); + }) + .catch( + ({ + response: { + status, + data: { errors }, + }, + }) => { + if (status === 412) { + setUpdatedByOther(true); + return; + } + setErrors(errors); + } + ) + .finally(() => { + setSaving(false); + }); + }; + + const onCancel = () => { + navigate('/users'); + }; + + if (loading) { + return
Loading...
; + } + + if (!userState) { + return
Not found
; + } + + const { + fullname, + email, + username, + enabled, + created_at: createdAt, + updated_at: updatedAt, + } = userState; + + return ( +
+ Back to Users + {updatedByOther && ( + + + Information has been updated by another user and your changes have + not been saved. Please refresh the page to load the latest of + information. + + + )} + Edit User + +
+ ); +} + +export default EditUserPage; diff --git a/assets/js/pages/Users/EditUserPage.test.jsx b/assets/js/pages/Users/EditUserPage.test.jsx new file mode 100644 index 0000000000..2a334bd942 --- /dev/null +++ b/assets/js/pages/Users/EditUserPage.test.jsx @@ -0,0 +1,156 @@ +import React from 'react'; + +import { screen } from '@testing-library/react'; +import 'intersection-observer'; +import '@testing-library/jest-dom'; + +import userEvent from '@testing-library/user-event'; +import MockAdapter from 'axios-mock-adapter'; + +// eslint-disable-next-line import/no-extraneous-dependencies +import * as router from 'react-router'; + +import { networkClient } from '@lib/network'; + +import { renderWithRouterMatch } from '@lib/test-utils'; +import { userFactory } from '@lib/test-utils/factories/users'; + +import EditUserPage from './EditUserPage'; + +const usersUrl = '/api/v1/users/'; +const axiosMock = new MockAdapter(networkClient); + +describe('EditUserPage', () => { + beforeEach(() => { + axiosMock.reset(); + jest.spyOn(console, 'error').mockImplementation(() => null); + }); + + it('Back To Users redirects to the users view', async () => { + const user = userEvent.setup(); + const navigate = jest.fn(); + jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate); + const userData = userFactory.build(); + axiosMock.onGet(usersUrl.concat(userData.id)).reply(200, userData); + + renderWithRouterMatch(, { + path: '/users/:userID/edit', + route: `/users/${userData.id}/edit`, + }); + + await screen.findByText('Edit User'); + + await user.click(screen.getByRole('button', { name: 'Back to Users' })); + + expect(navigate).toHaveBeenCalledWith('/users'); + }); + + it('Cancel button redirects to the users view', async () => { + const user = userEvent.setup(); + const navigate = jest.fn(); + jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate); + const userData = userFactory.build(); + axiosMock.onGet(usersUrl.concat(userData.id)).reply(200, userData); + + renderWithRouterMatch(, { + path: '/users/:userID/edit', + route: `/users/${userData.id}/edit`, + }); + + await screen.findByText('Edit User'); + + await user.click(screen.getByRole('button', { name: 'Cancel' })); + + expect(navigate).toHaveBeenCalledWith('/users'); + }); + + it('shows user not found if the given user ID does not exist', async () => { + const userID = '1'; + axiosMock.onGet(usersUrl.concat(userID)).reply(404, {}); + + renderWithRouterMatch(, { + path: '/users/:userID/edit', + route: `/users/${userID}/edit`, + }); + + await screen.findByText('Not found'); + }); + + it('edits a user and redirects to users view', async () => { + const user = userEvent.setup(); + const navigate = jest.fn(); + jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate); + const userData = userFactory.build(); + + axiosMock + .onGet(usersUrl.concat(userData.id)) + .reply(200, userData) + .onPatch(usersUrl.concat(userData.id)) + .reply(204, {}); + + renderWithRouterMatch(, { + path: '/users/:userID/edit', + route: `/users/${userData.id}/edit`, + }); + + await screen.findByText('Edit User'); + + await user.click(screen.getByRole('button', { name: 'Edit' })); + + expect(navigate).toHaveBeenCalledWith('/users'); + }); + + it('displays validation errors', async () => { + const user = userEvent.setup(); + const userData = userFactory.build(); + + const errors = [ + { + detail: 'Error validating fullname', + source: { pointer: '/fullname' }, + title: 'Invalid value', + }, + ]; + + axiosMock + .onGet(usersUrl.concat(userData.id)) + .reply(200, userData) + .onPatch(usersUrl.concat(userData.id)) + .reply(422, { errors }); + + renderWithRouterMatch(, { + path: '/users/:userID/edit', + route: `/users/${userData.id}/edit`, + }); + + await screen.findByText('Edit User'); + + await user.click(screen.getByRole('button', { name: 'Edit' })); + + await screen.findByText('Error validating fullname'); + }); + + it('displays user already updated warning banner', async () => { + const user = userEvent.setup(); + const userData = userFactory.build(); + + axiosMock + .onGet(usersUrl.concat(userData.id)) + .reply(200, userData) + .onPatch(usersUrl.concat(userData.id)) + .reply(412, {}); + + renderWithRouterMatch(, { + path: '/users/:userID/edit', + route: `/users/${userData.id}/edit`, + }); + + await screen.findByText('Edit User'); + + await user.click(screen.getByRole('button', { name: 'Edit' })); + + await screen.findByText('Information has been updated by another user', { + exact: false, + }); + }); +}); diff --git a/assets/js/pages/Users/index.js b/assets/js/pages/Users/index.js index 04a9989ff6..d1246ab0fa 100644 --- a/assets/js/pages/Users/index.js +++ b/assets/js/pages/Users/index.js @@ -1,5 +1,6 @@ import UsersPage from './UsersPage'; import CreateUserPage from './CreateUserPage'; +import EditUserPage from './EditUserPage'; -export { CreateUserPage }; +export { CreateUserPage, EditUserPage }; export default UsersPage; diff --git a/assets/js/trento.jsx b/assets/js/trento.jsx index 26ee278725..37462befb9 100644 --- a/assets/js/trento.jsx +++ b/assets/js/trento.jsx @@ -30,7 +30,7 @@ import SapSystemsOverviewPage from '@pages/SapSystemsOverviewPage'; import SaptuneDetailsPage from '@pages/SaptuneDetails'; import SettingsPage from '@pages/SettingsPage'; import SomethingWentWrong from '@pages/SomethingWentWrong'; -import UsersPage, { CreateUserPage } from '@pages/Users'; +import UsersPage, { CreateUserPage, EditUserPage } from '@pages/Users'; import { me } from '@lib/auth'; import { networkClient } from '@lib/network'; @@ -81,6 +81,7 @@ function App() { } /> } /> } /> + } /> } /> Date: Tue, 30 Apr 2024 13:55:03 +0200 Subject: [PATCH 3/4] Fix conflicts after rebase resolution --- assets/js/lib/api/users.js | 2 ++ assets/js/lib/test-utils/factories/users.js | 1 - assets/js/pages/Users/EditUserPage.test.jsx | 32 ++++++++++----------- assets/js/pages/Users/UserForm.test.jsx | 12 ++++++-- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/assets/js/lib/api/users.js b/assets/js/lib/api/users.js index bbb7c63555..5f4d73b401 100644 --- a/assets/js/lib/api/users.js +++ b/assets/js/lib/api/users.js @@ -2,6 +2,8 @@ import { del, get, patch, post } from '@lib/network'; export const listUsers = () => get('/users'); +export const getUser = (userID) => get(`/users/${userID}`); + export const createUser = (payload) => post('/users', payload); export const editUser = (userID, payload) => patch(`/users/${userID}`, payload); diff --git a/assets/js/lib/test-utils/factories/users.js b/assets/js/lib/test-utils/factories/users.js index 4e8411cd6d..fd566f443d 100644 --- a/assets/js/lib/test-utils/factories/users.js +++ b/assets/js/lib/test-utils/factories/users.js @@ -5,7 +5,6 @@ import { formatISO } from 'date-fns'; export const userFactory = Factory.define(() => ({ id: faker.number.int(), username: faker.internet.userName(), - created_at: formatISO(faker.date.past()), actions: 'Delete', enabled: faker.datatype.boolean(), fullname: faker.internet.displayName(), diff --git a/assets/js/pages/Users/EditUserPage.test.jsx b/assets/js/pages/Users/EditUserPage.test.jsx index 2a334bd942..f9b2e19666 100644 --- a/assets/js/pages/Users/EditUserPage.test.jsx +++ b/assets/js/pages/Users/EditUserPage.test.jsx @@ -17,7 +17,7 @@ import { userFactory } from '@lib/test-utils/factories/users'; import EditUserPage from './EditUserPage'; -const usersUrl = '/api/v1/users/'; +const USERS_URL = '/api/v1/users/'; const axiosMock = new MockAdapter(networkClient); describe('EditUserPage', () => { @@ -26,12 +26,12 @@ describe('EditUserPage', () => { jest.spyOn(console, 'error').mockImplementation(() => null); }); - it('Back To Users redirects to the users view', async () => { + it('should redirect back to users when the Back To Users button is clicked', async () => { const user = userEvent.setup(); const navigate = jest.fn(); jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate); const userData = userFactory.build(); - axiosMock.onGet(usersUrl.concat(userData.id)).reply(200, userData); + axiosMock.onGet(USERS_URL.concat(userData.id)).reply(200, userData); renderWithRouterMatch(, { path: '/users/:userID/edit', @@ -45,12 +45,12 @@ describe('EditUserPage', () => { expect(navigate).toHaveBeenCalledWith('/users'); }); - it('Cancel button redirects to the users view', async () => { + it('should redirect back to users when the Cancel button is clicked', async () => { const user = userEvent.setup(); const navigate = jest.fn(); jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate); const userData = userFactory.build(); - axiosMock.onGet(usersUrl.concat(userData.id)).reply(200, userData); + axiosMock.onGet(USERS_URL.concat(userData.id)).reply(200, userData); renderWithRouterMatch(, { path: '/users/:userID/edit', @@ -64,9 +64,9 @@ describe('EditUserPage', () => { expect(navigate).toHaveBeenCalledWith('/users'); }); - it('shows user not found if the given user ID does not exist', async () => { + it('should show user not found if the given user ID does not exist', async () => { const userID = '1'; - axiosMock.onGet(usersUrl.concat(userID)).reply(404, {}); + axiosMock.onGet(USERS_URL.concat(userID)).reply(404, {}); renderWithRouterMatch(, { path: '/users/:userID/edit', @@ -76,16 +76,16 @@ describe('EditUserPage', () => { await screen.findByText('Not found'); }); - it('edits a user and redirects to users view', async () => { + it('should edit a user and redirect to users view', async () => { const user = userEvent.setup(); const navigate = jest.fn(); jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate); const userData = userFactory.build(); axiosMock - .onGet(usersUrl.concat(userData.id)) + .onGet(USERS_URL.concat(userData.id)) .reply(200, userData) - .onPatch(usersUrl.concat(userData.id)) + .onPatch(USERS_URL.concat(userData.id)) .reply(204, {}); renderWithRouterMatch(, { @@ -100,7 +100,7 @@ describe('EditUserPage', () => { expect(navigate).toHaveBeenCalledWith('/users'); }); - it('displays validation errors', async () => { + it('should display validation errors', async () => { const user = userEvent.setup(); const userData = userFactory.build(); @@ -113,9 +113,9 @@ describe('EditUserPage', () => { ]; axiosMock - .onGet(usersUrl.concat(userData.id)) + .onGet(USERS_URL.concat(userData.id)) .reply(200, userData) - .onPatch(usersUrl.concat(userData.id)) + .onPatch(USERS_URL.concat(userData.id)) .reply(422, { errors }); renderWithRouterMatch(, { @@ -130,14 +130,14 @@ describe('EditUserPage', () => { await screen.findByText('Error validating fullname'); }); - it('displays user already updated warning banner', async () => { + it('should display user already updated warning banner', async () => { const user = userEvent.setup(); const userData = userFactory.build(); axiosMock - .onGet(usersUrl.concat(userData.id)) + .onGet(USERS_URL.concat(userData.id)) .reply(200, userData) - .onPatch(usersUrl.concat(userData.id)) + .onPatch(USERS_URL.concat(userData.id)) .reply(412, {}); renderWithRouterMatch(, { diff --git a/assets/js/pages/Users/UserForm.test.jsx b/assets/js/pages/Users/UserForm.test.jsx index 12e82795f9..d0c75d74cd 100644 --- a/assets/js/pages/Users/UserForm.test.jsx +++ b/assets/js/pages/Users/UserForm.test.jsx @@ -11,7 +11,7 @@ import { userFactory } from '@lib/test-utils/factories/users'; import UserForm from './UserForm'; describe('UserForm', () => { - it('should display an empty User form', () => { + it('should display an empty user form', () => { render(); expect(screen.getByText('Full Name')).toBeVisible(); @@ -33,8 +33,14 @@ describe('UserForm', () => { expect(screen.getByRole('button', { name: 'Cancel' })).toBeVisible(); }); - it('should display a prepopulated User form', async () => { - const { fullname, email, username, password } = userFactory.build(); + it('should display an editing user form', async () => { + const { + fullname, + email, + username, + created_at: createdAt, + updated_at: updatedAt, + } = userFactory.build(); await act(async () => { render( From 1ecc205e5981bc4c6b66e287bd087a18b90de74c Mon Sep 17 00:00:00 2001 From: arbulu89 Date: Tue, 30 Apr 2024 16:16:57 +0200 Subject: [PATCH 4/4] Correct some typos and descriptions --- assets/js/pages/Users/EditUserPage.test.jsx | 2 +- assets/js/pages/Users/UserForm.stories.jsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/js/pages/Users/EditUserPage.test.jsx b/assets/js/pages/Users/EditUserPage.test.jsx index f9b2e19666..7ed635f6b8 100644 --- a/assets/js/pages/Users/EditUserPage.test.jsx +++ b/assets/js/pages/Users/EditUserPage.test.jsx @@ -76,7 +76,7 @@ describe('EditUserPage', () => { await screen.findByText('Not found'); }); - it('should edit a user and redirect to users view', async () => { + it('should edit a user and redirect to users view', async () => { const user = userEvent.setup(); const navigate = jest.fn(); jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate); diff --git a/assets/js/pages/Users/UserForm.stories.jsx b/assets/js/pages/Users/UserForm.stories.jsx index 50019747db..6f6fe57322 100644 --- a/assets/js/pages/Users/UserForm.stories.jsx +++ b/assets/js/pages/Users/UserForm.stories.jsx @@ -42,13 +42,13 @@ export default { }, }, createdAt: { - description: 'User creation time', + description: 'User creation timestamp', control: { type: 'text', }, }, udpatedAt: { - description: 'User last edition time', + description: 'User last update timestamp', control: { type: 'text', },