diff --git a/cypress/fixtures/items.js b/cypress/fixtures/items.js index a21c34358..1fd2cbc3a 100644 --- a/cypress/fixtures/items.js +++ b/cypress/fixtures/items.js @@ -22,12 +22,18 @@ export const SIMPLE_ITEMS = [ id: 'ecafbd2a-5688-11eb-ae93-0242ac130002', name: 'own_item_name1', path: 'ecafbd2a_5688_11eb_ae93_0242ac130002', + extra: { + image: 'someimageurl', + }, }, { ...DEFAULT_ITEM, id: 'fdf09f5a-5688-11eb-ae93-0242ac130002', name: 'own_item_name2', path: 'fdf09f5a_5688_11eb_ae93_0242ac130002', + extra: { + image: 'someimageurl', + }, }, { ...DEFAULT_ITEM, @@ -35,6 +41,9 @@ export const SIMPLE_ITEMS = [ name: 'own_item_name3', path: 'ecafbd2a_5688_11eb_ae93_0242ac130002.fdf09f5a_5688_11eb_ae93_0242ac130003', + extra: { + image: 'someimageurl', + }, }, { ...DEFAULT_ITEM, @@ -42,5 +51,8 @@ export const SIMPLE_ITEMS = [ name: 'own_item_name4', path: 'ecafbd2a_5688_11eb_ae93_0242ac130002.fdf09f5a_5688_11eb_ae93_0242ac130004', + extra: { + image: 'someimageurl', + }, }, ]; diff --git a/cypress/integration/createItem.spec.js b/cypress/integration/createItem.spec.js index 134874f2e..0ce55d136 100644 --- a/cypress/integration/createItem.spec.js +++ b/cypress/integration/createItem.spec.js @@ -2,11 +2,11 @@ import { buildItemPath } from '../../src/config/paths'; import { buildItemCard, CREATE_ITEM_BUTTON_ID, - NEW_ITEM_CONFIRM_BUTTON_ID, - NEW_ITEM_DESCRIPTION_INPUT_ID, - NEW_ITEM_IMAGE_INPUT_ID, - NEW_ITEM_NAME_INPUT_ID, - NEW_ITEM_TYPE_SELECT_ID, + ITEM_FORM_CONFIRM_BUTTON_ID, + ITEM_FORM_DESCRIPTION_INPUT_ID, + ITEM_FORM_IMAGE_INPUT_ID, + ITEM_FORM_NAME_INPUT_ID, + ITEM_FORM_TYPE_SELECT_ID, } from '../../src/config/selectors'; import { CREATED_ITEM, SIMPLE_ITEMS } from '../fixtures/items'; @@ -18,15 +18,15 @@ const createItem = ({ }) => { cy.get(`#${CREATE_ITEM_BUTTON_ID}`).click(); - cy.get(`#${NEW_ITEM_NAME_INPUT_ID}`).type(name); + cy.get(`#${ITEM_FORM_NAME_INPUT_ID}`).type(name); - cy.get(`#${NEW_ITEM_DESCRIPTION_INPUT_ID}`).type(description); + cy.get(`#${ITEM_FORM_DESCRIPTION_INPUT_ID}`).type(description); - cy.get(`#${NEW_ITEM_TYPE_SELECT_ID}`).click(); + cy.get(`#${ITEM_FORM_TYPE_SELECT_ID}`).click(); cy.get(`li[data-value="${type}"]`).click(); - cy.get(`#${NEW_ITEM_IMAGE_INPUT_ID}`).type(extra.image); + cy.get(`#${ITEM_FORM_IMAGE_INPUT_ID}`).type(extra.image); - cy.get(`#${NEW_ITEM_CONFIRM_BUTTON_ID}`).click(); + cy.get(`#${ITEM_FORM_CONFIRM_BUTTON_ID}`).click(); }; describe('Create Item', () => { diff --git a/cypress/integration/editItem.spec.js b/cypress/integration/editItem.spec.js new file mode 100644 index 000000000..b9e00eb9f --- /dev/null +++ b/cypress/integration/editItem.spec.js @@ -0,0 +1,97 @@ +import { buildItemPath } from '../../src/config/paths'; +import { + buildItemCard, + buildItemLink, + buildItemMenu, + ITEM_MENU_BUTTON_CLASS, + ITEM_MENU_EDIT_BUTTON_CLASS, + ITEM_FORM_CONFIRM_BUTTON_ID, + ITEM_FORM_DESCRIPTION_INPUT_ID, + ITEM_FORM_IMAGE_INPUT_ID, + ITEM_FORM_NAME_INPUT_ID, + ITEM_FORM_TYPE_SELECT_ID, +} from '../../src/config/selectors'; +import { SIMPLE_ITEMS } from '../fixtures/items'; + +const editItem = ({ + id, + name = '', + type = 'Space', + extra = {}, + description = '', +}) => { + const menuSelector = `#${buildItemCard(id)} .${ITEM_MENU_BUTTON_CLASS}`; + cy.get(menuSelector).click(); + cy.get(`#${buildItemMenu(id)} .${ITEM_MENU_EDIT_BUTTON_CLASS}`).click(); + + cy.get(`#${ITEM_FORM_NAME_INPUT_ID}`).clear().type(name); + + cy.get(`#${ITEM_FORM_DESCRIPTION_INPUT_ID}`).clear().type(description); + + cy.get(`#${ITEM_FORM_TYPE_SELECT_ID}`).click(); + cy.get(`li[data-value="${type}"]`).click(); + cy.get(`#${ITEM_FORM_IMAGE_INPUT_ID}`).clear().type(extra.image); + + cy.get(`#${ITEM_FORM_CONFIRM_BUTTON_ID}`).click(); +}; + +describe('Edit Item', () => { + it('edit item on Home', () => { + cy.setUpApi({ items: SIMPLE_ITEMS }); + cy.visit('/'); + + const itemToEdit = SIMPLE_ITEMS[0]; + const newName = 'new name'; + const newDescription = 'new description'; + + // create + editItem({ + ...itemToEdit, + name: newName, + description: newDescription, + }); + + cy.wait('@editItem').then( + ({ + response: { + body: { id, name }, + }, + }) => { + // check item is created and displayed + cy.wait(1000); + cy.get(`#${buildItemCard(id)}`).should('exist'); + cy.get(`#${buildItemLink(id)}`).contains(name); + }, + ); + }); + + it('create item in item', () => { + cy.setUpApi({ items: SIMPLE_ITEMS }); + // go to children item + cy.visit(buildItemPath(SIMPLE_ITEMS[0].id)); + + const itemToEdit = SIMPLE_ITEMS[2]; + const newName = 'new name'; + const newDescription = 'new description'; + + // create + editItem({ + ...itemToEdit, + name: newName, + description: newDescription, + }); + + cy.wait('@editItem').then( + ({ + response: { + body: { id, name }, + }, + }) => { + // check item is created and displayed + cy.wait(1000); + cy.get(`#${buildItemCard(id)}`).should('exist'); + cy.get(`#${buildItemLink(id)}`).contains(name); + }, + ); + }); +}); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 479c370a4..d84a4545d 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -11,6 +11,7 @@ import { mockGetOwnItems, mockMoveItem, mockPostItem, + mockEditItem, } from './server'; Cypress.Commands.add( @@ -22,6 +23,7 @@ Cypress.Commands.add( moveItemError = false, copyItemError = false, getItemError = false, + editItemError = false, } = {}) => { const cachedItems = JSON.parse(JSON.stringify(items)); @@ -38,6 +40,8 @@ Cypress.Commands.add( mockMoveItem(cachedItems, moveItemError); mockCopyItem(cachedItems, copyItemError); + + mockEditItem(cachedItems, editItemError); }, ); diff --git a/cypress/support/server.js b/cypress/support/server.js index df20408aa..ddeb57f46 100644 --- a/cypress/support/server.js +++ b/cypress/support/server.js @@ -2,6 +2,7 @@ import { v4 as uuidv4 } from 'uuid'; import { buildCopyItemRoute, buildDeleteItemRoute, + buildEditItemRoute, buildGetChildrenRoute, buildGetItemRoute, buildMoveItemRoute, @@ -180,3 +181,19 @@ export const mockCopyItem = (items, shouldThrowError) => { }, ).as('copyItem'); }; + +export const mockEditItem = (items, shouldThrowError) => { + cy.intercept( + { + method: 'PATCH', + url: new RegExp(`${API_HOST}/${buildEditItemRoute(ID_FORMAT)}`), + }, + ({ reply, body }) => { + if (shouldThrowError) { + return reply({ statusCode: ERROR_CODE }); + } + + return reply(body); + }, + ).as('editItem'); +}; diff --git a/src/actions/item.js b/src/actions/item.js index e3b6c6bd2..bf86c744d 100644 --- a/src/actions/item.js +++ b/src/actions/item.js @@ -18,7 +18,9 @@ import { FLAG_GETTING_ITEMS, FLAG_MOVING_ITEM, FLAG_COPYING_ITEM, + EDIT_ITEM_SUCCESS, FLAG_SETTING_ITEM, + FLAG_EDITING_ITEM, } from '../types/item'; import { getParentsIdsFromPath } from '../utils/item'; import { createFlag } from './utils'; @@ -209,3 +211,18 @@ export const getChildren = (id) => async (dispatch) => { dispatch(createFlag(FLAG_GETTING_CHILDREN, false)); } }; + +export const editItem = (item) => async (dispatch) => { + try { + dispatch(createFlag(FLAG_EDITING_ITEM, true)); + const editedItem = await Api.editItem(item); + dispatch({ + type: EDIT_ITEM_SUCCESS, + payload: editedItem, + }); + } catch (e) { + console.error(e); + } finally { + dispatch(createFlag(FLAG_EDITING_ITEM, false)); + } +}; diff --git a/src/actions/layout.js b/src/actions/layout.js index f8c0c5fcb..bd810deb3 100644 --- a/src/actions/layout.js +++ b/src/actions/layout.js @@ -1,5 +1,6 @@ import { SET_COPY_MODAL_SETTINGS, + SET_EDIT_MODAL_SETTINGS, SET_MOVE_MODAL_SETTINGS, } from '../types/layout'; @@ -16,3 +17,10 @@ export const setCopyModalSettings = (payload) => (dispatch) => { payload, }); }; + +export const setEditModalSettings = (payload) => (dispatch) => { + dispatch({ + type: SET_EDIT_MODAL_SETTINGS, + payload, + }); +}; diff --git a/src/api/item.js b/src/api/item.js index 05c467939..c34586165 100644 --- a/src/api/item.js +++ b/src/api/item.js @@ -2,13 +2,19 @@ import { API_HOST, ROOT_ID } from '../config/constants'; import { buildCopyItemRoute, buildDeleteItemRoute, + buildEditItemRoute, buildGetChildrenRoute, buildGetItemRoute, buildMoveItemRoute, buildPostItemRoute, GET_OWN_ITEMS_ROUTE, } from './routes'; -import { DEFAULT_DELETE, DEFAULT_GET, DEFAULT_POST } from './utils'; +import { + DEFAULT_DELETE, + DEFAULT_GET, + DEFAULT_POST, + DEFAULT_PATCH, +} from './utils'; import * as CacheOperations from '../config/cache'; export const getItem = async (id) => { @@ -80,6 +86,26 @@ export const deleteItem = async (id) => { return res.json(); }; +// payload = {name, type, description, extra} +// querystring = {parentId} +export const editItem = async (item) => { + const req = await fetch(`${API_HOST}/${buildEditItemRoute(item.id)}`, { + ...DEFAULT_PATCH, + body: JSON.stringify(item), + }); + + if (!req.ok) { + throw new Error((await req.json()).message); + } + + const newItem = await req.json(); + + await CacheOperations.saveItem(newItem); + + return newItem; +}; + +// we need this function for navigation purposes: when you click on an item, you want to see its 'immediate' children export const getChildren = async (id) => { const res = await fetch( `${API_HOST}/${buildGetChildrenRoute(id)}`, diff --git a/src/api/routes.js b/src/api/routes.js index e9ed645f4..a282319d1 100644 --- a/src/api/routes.js +++ b/src/api/routes.js @@ -11,3 +11,4 @@ export const buildGetChildrenRoute = (id) => `items/${id}/children`; export const buildGetItemRoute = (id) => `items/${id}`; export const buildMoveItemRoute = (id) => `items/${id}/move`; export const buildCopyItemRoute = (id) => `items/${id}/copy`; +export const buildEditItemRoute = (id) => `items/${id}`; diff --git a/src/api/utils.js b/src/api/utils.js index 3c1144be3..048724db9 100644 --- a/src/api/utils.js +++ b/src/api/utils.js @@ -14,3 +14,9 @@ export const DEFAULT_DELETE = { method: 'DELETE', credentials: 'include', }; + +export const DEFAULT_PATCH = { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', +}; diff --git a/src/components/main/CustomCardHeader.js b/src/components/main/CustomCardHeader.js index 2e151aaca..c2210aa2e 100644 --- a/src/components/main/CustomCardHeader.js +++ b/src/components/main/CustomCardHeader.js @@ -37,7 +37,8 @@ const useStyles = makeStyles((theme) => ({ }, })); -const CustomCardHeader = ({ id, creator, title, type }) => { +const CustomCardHeader = ({ item }) => { + const { id, creator, name, type } = item; const classes = useStyles(); const { t } = useTranslation(); return ( @@ -47,7 +48,7 @@ const CustomCardHeader = ({ id, creator, title, type }) => {