From 1c055fc74694004ab9165ca7dd81fb251a8463a6 Mon Sep 17 00:00:00 2001 From: kim Date: Mon, 9 Dec 2024 14:32:10 +0100 Subject: [PATCH] refactor: update tests --- .../common/MultiSelectChipInput.cy.tsx | 135 -------------- cypress/e2e/item/publish/categories.cy.ts | 171 ------------------ .../e2e/item/publish/coEditorSettings.cy.ts | 8 +- cypress/e2e/item/publish/tags.cy.ts | 107 +++++++---- cypress/fixtures/{categories.ts => tags.ts} | 66 +++---- cypress/support/commands.ts | 25 +-- cypress/support/server.ts | 75 ++------ cypress/support/types.ts | 7 +- src/components/hooks/useItemCategories.tsx | 89 --------- ...pInput.tsx => MultiSelectTagChipInput.tsx} | 26 ++- src/components/item/publish/DropdownMenu.tsx | 94 ---------- .../publish/customizedTags/CustomizedTags.tsx | 15 +- src/config/selectors.ts | 5 +- 13 files changed, 141 insertions(+), 682 deletions(-) delete mode 100644 cypress/components/common/MultiSelectChipInput.cy.tsx delete mode 100644 cypress/e2e/item/publish/categories.cy.ts rename cypress/fixtures/{categories.ts => tags.ts} (54%) delete mode 100644 src/components/hooks/useItemCategories.tsx rename src/components/input/{MultiSelectChipInput.tsx => MultiSelectTagChipInput.tsx} (88%) delete mode 100644 src/components/item/publish/DropdownMenu.tsx diff --git a/cypress/components/common/MultiSelectChipInput.cy.tsx b/cypress/components/common/MultiSelectChipInput.cy.tsx deleted file mode 100644 index e871c38f2..000000000 --- a/cypress/components/common/MultiSelectChipInput.cy.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { v4 } from 'uuid'; - -import MultiSelectChipInput from '@/components/input/MultiSelectChipInput'; -import { - MULTI_SELECT_CHIP_ADD_BUTTON_ID, - MULTI_SELECT_CHIP_CONTAINER_ID, - MULTI_SELECT_CHIP_INPUT_ID, - buildDataCyWrapper, - buildMultiSelectChipsSelector, -} from '@/config/selectors'; - -const ON_SAVE_SPY = 'onSave'; -const getSpyOnSave = () => `@${ON_SAVE_SPY}`; -const eventHandler = { onSave: (_values: string[]) => {} }; -const EXISTING_VALUES = ['first', 'second', 'third']; -const NEW_VALUE = 'my new value'; -const LABEL = 'my label'; - -const getInput = () => - cy.get(`${buildDataCyWrapper(MULTI_SELECT_CHIP_INPUT_ID)} input`); - -const addANewValue = (newValue: string) => { - getInput().type(newValue); - cy.get(buildDataCyWrapper(MULTI_SELECT_CHIP_ADD_BUTTON_ID)).click(); -}; - -const removeAValue = (valueToRemove: string) => { - const idxOfRemovedValue = EXISTING_VALUES.findIndex( - (value) => value === valueToRemove, - ); - - if (idxOfRemovedValue === -1) { - throw new Error(`Given value to remove "${valueToRemove}" was not found!`); - } - - cy.get( - `${buildDataCyWrapper(buildMultiSelectChipsSelector(idxOfRemovedValue))} svg`, - ).click(); -}; - -describe('', () => { - beforeEach(() => { - cy.spy(eventHandler, 'onSave').as(ON_SAVE_SPY); - }); - - describe('Data is empty', () => { - beforeEach(() => { - cy.mount(); - }); - - it('Chips container should not exist when no data', () => { - cy.get(buildDataCyWrapper(MULTI_SELECT_CHIP_CONTAINER_ID)).should( - 'not.exist', - ); - }); - - it('Add a new value should add a new chip', () => { - addANewValue(NEW_VALUE); - cy.get(buildDataCyWrapper(MULTI_SELECT_CHIP_CONTAINER_ID)) - .children() - .should('have.length', 1) - .should('contain', NEW_VALUE); - }); - - it('Add a new value should reset current value', () => { - addANewValue(NEW_VALUE); - getInput().should('have.value', ''); - }); - - it('Add a new empty value should not be possible', () => { - getInput().should('have.value', ''); - cy.get(buildDataCyWrapper(MULTI_SELECT_CHIP_ADD_BUTTON_ID)).should( - 'be.disabled', - ); - }); - }); - - describe('Have some data', () => { - const valueToRemove = EXISTING_VALUES[1]; - - beforeEach(() => { - cy.mount(); - }); - - it('Chips container should contains existing chips', () => { - cy.get(buildDataCyWrapper(MULTI_SELECT_CHIP_CONTAINER_ID)) - .children() - .should('have.length', EXISTING_VALUES.length); - }); - - it('Add a new value should add a new chip', () => { - addANewValue(NEW_VALUE); - cy.get(buildDataCyWrapper(MULTI_SELECT_CHIP_CONTAINER_ID)) - .children() - .should('have.length', EXISTING_VALUES.length + 1) - .should('contain', NEW_VALUE); - }); - - it('Add a new value should call onSave', () => { - addANewValue(NEW_VALUE); - cy.get(getSpyOnSave()).should('be.calledWith', [ - ...EXISTING_VALUES, - NEW_VALUE, - ]); - }); - - it('Add an existing value should not be possible', () => { - getInput().type(EXISTING_VALUES[0].toUpperCase()); - cy.get(buildDataCyWrapper(MULTI_SELECT_CHIP_ADD_BUTTON_ID)).should( - 'be.disabled', - ); - }); - - it('Add an existing value should not call onSave', () => { - getInput().type(EXISTING_VALUES[0].toUpperCase()); - cy.get(getSpyOnSave()).should('not.be.called'); - }); - - it('Remove a value should remove the chip', () => { - removeAValue(valueToRemove); - cy.get(buildDataCyWrapper(MULTI_SELECT_CHIP_CONTAINER_ID)) - .children() - .should('have.length', EXISTING_VALUES.length - 1) - .should('not.contain', valueToRemove); - }); - - it('Remove a value should call onSave', () => { - removeAValue(valueToRemove); - cy.get(getSpyOnSave()).should( - 'be.calledWith', - EXISTING_VALUES.filter((e) => e !== valueToRemove), - ); - }); - }); -}); diff --git a/cypress/e2e/item/publish/categories.cy.ts b/cypress/e2e/item/publish/categories.cy.ts deleted file mode 100644 index f0d0f57bf..000000000 --- a/cypress/e2e/item/publish/categories.cy.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { Category, CategoryType } from '@graasp/sdk'; - -import { buildItemPath } from '../../../../src/config/paths'; -import { - CATEGORIES_ADD_BUTTON_HEADER, - LIBRARY_SETTINGS_CATEGORIES_ID, - MUI_CHIP_REMOVE_BTN, - buildCategoryDropdownParentSelector, - buildCategorySelectionId, - buildCategorySelectionOptionId, - buildDataCyWrapper, - buildDataTestIdWrapper, - buildPublishButtonId, - buildPublishChip, - buildPublishChipContainer, -} from '../../../../src/config/selectors'; -import { - ITEM_WITH_CATEGORIES, - ITEM_WITH_CATEGORIES_CONTEXT, - SAMPLE_CATEGORIES, -} from '../../../fixtures/categories'; -import { PUBLISHED_ITEM } from '../../../fixtures/items'; -import { MEMBERS, SIGNED_OUT_MEMBER } from '../../../fixtures/members'; - -const CATEGORIES_DATA_CY = buildDataCyWrapper( - buildPublishChipContainer(LIBRARY_SETTINGS_CATEGORIES_ID), -); - -const openPublishItemTab = (id: string) => - cy.get(`#${buildPublishButtonId(id)}`).click(); - -const toggleOption = ( - id: string, - categoryType: CategoryType | `${CategoryType}`, -) => { - cy.get(`#${buildCategorySelectionId(categoryType)}`).click(); - cy.get(`#${buildCategorySelectionOptionId(categoryType, id)}`).click(); -}; - -const openCategoriesModal = () => { - cy.get(buildDataCyWrapper(CATEGORIES_ADD_BUTTON_HEADER)).click(); -}; - -describe('Categories', () => { - describe('Item without category', () => { - beforeEach(() => { - const item = { ...ITEM_WITH_CATEGORIES, categories: [] as Category[] }; - cy.setUpApi({ items: [item] }); - cy.visit(buildItemPath(item.id)); - openPublishItemTab(item.id); - }); - - it('Display item without category', () => { - // check for not displaying if no categories - cy.get(CATEGORIES_DATA_CY).should('not.exist'); - }); - }); - - describe('Item with category', () => { - const item = ITEM_WITH_CATEGORIES; - - beforeEach(() => { - cy.setUpApi(ITEM_WITH_CATEGORIES_CONTEXT); - cy.visit(buildItemPath(item.id)); - openPublishItemTab(item.id); - }); - - it('Display item category', () => { - // check for displaying value - const { - categories: [{ category }], - } = item; - const { name } = SAMPLE_CATEGORIES.find(({ id }) => id === category.id); - const categoryContent = cy.get(CATEGORIES_DATA_CY); - categoryContent.contains(name); - }); - - describe('Delete a category', () => { - let id: string; - let category: Category; - let categoryType: Category['type']; - - beforeEach(() => { - const { - categories: [itemCategory], - } = item; - ({ category, id } = itemCategory); - categoryType = SAMPLE_CATEGORIES.find( - ({ id: cId }) => cId === category.id, - )?.type; - }); - - afterEach(() => { - cy.wait('@deleteItemCategory').then((data) => { - const { - request: { url }, - } = data; - expect(url.split('/')).contains(id); - }); - }); - - it('Using Dropdown in modal', () => { - openCategoriesModal(); - toggleOption(category.id, categoryType); - }); - - it('Using cross on category tag in modal', () => { - openCategoriesModal(); - - cy.get( - buildDataCyWrapper(buildCategoryDropdownParentSelector(categoryType)), - ) - .find(`[data-tag-index=0] > svg`) - .click(); - }); - - it('Using cross on category container', () => { - cy.get(buildDataCyWrapper(buildPublishChip(category.name))) - .find(buildDataTestIdWrapper(MUI_CHIP_REMOVE_BTN)) - .click(); - }); - }); - - it('Add a category', () => { - openCategoriesModal(); - const { type, id } = SAMPLE_CATEGORIES[1]; - toggleOption(id, type); - - cy.wait('@postItemCategory').then((data) => { - const { - request: { url }, - } = data; - expect(url.split('/')).contains(item.id); - }); - }); - }); - - // users without permission will not see the sections - describe('Categories permissions', () => { - it('User signed out cannot edit category level', () => { - const item = PUBLISHED_ITEM; - - cy.setUpApi({ - items: [item], - currentMember: SIGNED_OUT_MEMBER, - }); - cy.visit(buildItemPath(item.id)); - - // signed out user should not be able to see the publish button - cy.get(`#${buildPublishButtonId(item.id)}`).should('not.exist'); - cy.get(`#${buildCategorySelectionId(CategoryType.Level)}`).should( - 'not.exist', - ); - }); - - it('Read-only user cannot edit category level', () => { - const item = PUBLISHED_ITEM; - cy.setUpApi({ - items: [item], - currentMember: MEMBERS.BOB, - }); - cy.visit(buildItemPath(item.id)); - - // signed out user should not be able to see the publish button - cy.get(`#${buildPublishButtonId(item.id)}`).should('not.exist'); - cy.get(`#${buildCategorySelectionId(CategoryType.Level)}`).should( - 'not.exist', - ); - }); - }); -}); diff --git a/cypress/e2e/item/publish/coEditorSettings.cy.ts b/cypress/e2e/item/publish/coEditorSettings.cy.ts index 23fd1587a..c0baed957 100644 --- a/cypress/e2e/item/publish/coEditorSettings.cy.ts +++ b/cypress/e2e/item/publish/coEditorSettings.cy.ts @@ -9,16 +9,16 @@ import { buildDataCyWrapper, buildPublishButtonId, } from '../../../../src/config/selectors'; -import { ITEM_WITH_CATEGORIES_CONTEXT } from '../../../fixtures/categories'; import { MEMBERS, SIGNED_OUT_MEMBER } from '../../../fixtures/members'; +import { ITEM_WITH_TAGS_CONTEXT } from '../../../fixtures/tags'; import { EDIT_TAG_REQUEST_TIMEOUT } from '../../../support/constants'; const openPublishItemTab = (id: string) => { cy.get(`#${buildPublishButtonId(id)}`).click(); }; const visitItemPage = () => { - cy.setUpApi(ITEM_WITH_CATEGORIES_CONTEXT); - const item = ITEM_WITH_CATEGORIES_CONTEXT.items[0]; + cy.setUpApi(ITEM_WITH_TAGS_CONTEXT); + const item = ITEM_WITH_TAGS_CONTEXT.items[0]; cy.visit(buildItemPath(item.id)); openPublishItemTab(item.id); }; @@ -35,7 +35,7 @@ describe('Co-editor Setting', () => { it('Change choice', () => { visitItemPage(); - const item = ITEM_WITH_CATEGORIES_CONTEXT.items[0]; + const item = ITEM_WITH_TAGS_CONTEXT.items[0]; const newOptionValue = DISPLAY_CO_EDITORS_OPTIONS.NO.value; cy.wait('@getPublicationStatus').then(() => { diff --git a/cypress/e2e/item/publish/tags.cy.ts b/cypress/e2e/item/publish/tags.cy.ts index b91554063..7c439381b 100644 --- a/cypress/e2e/item/publish/tags.cy.ts +++ b/cypress/e2e/item/publish/tags.cy.ts @@ -2,97 +2,134 @@ import { ItemVisibilityType, PackedFolderItemFactory, PermissionLevel, + TagCategory, } from '@graasp/sdk'; import { buildItemPath } from '../../../../src/config/paths'; import { ITEM_HEADER_ID, - ITEM_TAGS_OPEN_MODAL_BUTTON_ID, + ITEM_TAGS_OPEN_MODAL_BUTTON_CY, MUI_CHIP_REMOVE_BTN, - MULTI_SELECT_CHIP_ADD_BUTTON_ID, - MULTI_SELECT_CHIP_INPUT_ID, buildCustomizedTagsSelector, buildDataCyWrapper, buildDataTestIdWrapper, + buildMultiSelectChipInputId, buildPublishButtonId, } from '../../../../src/config/selectors'; -import { - ITEM_WITH_CATEGORIES, - ITEM_WITH_CATEGORIES_CONTEXT, -} from '../../../fixtures/categories'; import { PUBLISHED_ITEM_NO_TAGS } from '../../../fixtures/items'; import { MEMBERS, SIGNED_OUT_MEMBER } from '../../../fixtures/members'; +import { SAMPLE_TAGS } from '../../../fixtures/tags'; import { EDIT_TAG_REQUEST_TIMEOUT } from '../../../support/constants'; import { ItemForTest } from '../../../support/types'; +const ITEM_WITH_TAGS = { + ...PackedFolderItemFactory( + { + settings: { + displayCoEditors: true, + }, + }, + { permission: PermissionLevel.Admin }, + ), + tags: SAMPLE_TAGS.slice(0, 2), +}; + const openPublishItemTab = (id: string) => { cy.get(`#${buildPublishButtonId(id)}`).click(); }; const visitItemPage = (item: ItemForTest) => { - cy.setUpApi(ITEM_WITH_CATEGORIES_CONTEXT); + cy.setUpApi({ items: [item] }); cy.visit(buildItemPath(item.id)); openPublishItemTab(item.id); }; describe('Customized Tags', () => { it('Display item without tags', () => { - // check for not displaying if no tags const item = PUBLISHED_ITEM_NO_TAGS; cy.setUpApi({ items: [item] }); cy.visit(buildItemPath(item.id)); openPublishItemTab(item.id); - cy.get(buildDataCyWrapper(buildCustomizedTagsSelector(0))).should( - 'not.exist', - ); + // check display edit button + cy.get(buildDataCyWrapper(ITEM_TAGS_OPEN_MODAL_BUTTON_CY)) + // scroll because description can be long + .scrollIntoView() + .should('be.visible'); }); it('Display tags', () => { - const item = ITEM_WITH_CATEGORIES; + const item = ITEM_WITH_TAGS; visitItemPage(item); - expect(item.settings.tags).to.have.lengthOf.above(0); - item.settings.tags!.forEach((tag, index) => { + expect(item.tags).to.have.lengthOf.above(0); + item.tags.forEach((tag) => { const displayTags = cy.get( - buildDataCyWrapper(buildCustomizedTagsSelector(index)), + buildDataCyWrapper(buildCustomizedTagsSelector(tag.id)), ); - displayTags.contains(tag); + displayTags.contains(tag.name); }); }); it('Remove tag', () => { - const item = ITEM_WITH_CATEGORIES; - const removeIdx = 0; - const removedTag = item.settings.tags[removeIdx]; + const item = ITEM_WITH_TAGS; + const tagToRemove = ITEM_WITH_TAGS.tags[1]; visitItemPage(item); - cy.get(buildDataCyWrapper(buildCustomizedTagsSelector(removeIdx))) + cy.get(buildDataCyWrapper(buildCustomizedTagsSelector(tagToRemove.id))) .find(buildDataTestIdWrapper(MUI_CHIP_REMOVE_BTN)) .click(); - cy.wait('@editItem', { timeout: EDIT_TAG_REQUEST_TIMEOUT }).then((data) => { - const { - request: { url, body }, - } = data; - expect(url.split('/')).contains(item.id); - expect(body.settings.tags).not.contains(removedTag); - }); + cy.wait('@removeTag', { timeout: EDIT_TAG_REQUEST_TIMEOUT }).then( + (data) => { + const { + request: { url }, + } = data; + expect(url.split('/')).contains(item.id).contains(tagToRemove.id); + }, + ); }); it('Add tag', () => { - const item = ITEM_WITH_CATEGORIES; - const newTag = 'My new tag'; + cy.intercept( + { + method: 'Get', + url: /\/tags\?search=/, + }, + ({ reply }) => + reply([ + { name: 'secondary school', category: TagCategory.Level }, + ...SAMPLE_TAGS, + ]), + ).as('getTags'); + + const item = ITEM_WITH_TAGS; + const newTag = { name: 'My new tag', category: TagCategory.Level }; visitItemPage(item); - cy.get(buildDataCyWrapper(ITEM_TAGS_OPEN_MODAL_BUTTON_ID)).click(); - cy.get(buildDataCyWrapper(MULTI_SELECT_CHIP_INPUT_ID)).type(newTag); - cy.get(buildDataCyWrapper(MULTI_SELECT_CHIP_ADD_BUTTON_ID)).click(); + cy.get(buildDataCyWrapper(ITEM_TAGS_OPEN_MODAL_BUTTON_CY)).click(); + cy.get(buildDataCyWrapper(buildMultiSelectChipInputId(TagCategory.Level))) + // debounce of 500 + .type(`${newTag.name}`); + + // should call get tags when typing + cy.wait('@getTags').then(({ request: { query } }) => { + expect(query.search).to.contain(newTag.name); + expect(query.category).to.contain(newTag.category); + }); + + // display options for opened category + cy.get(`li:contains("secondary school")`).should('be.visible'); + + cy.get( + buildDataCyWrapper(buildMultiSelectChipInputId(TagCategory.Level)), + ).type('{Enter}'); - cy.wait('@editItem', { timeout: EDIT_TAG_REQUEST_TIMEOUT }).then((data) => { + cy.wait('@addTag', { timeout: EDIT_TAG_REQUEST_TIMEOUT }).then((data) => { const { request: { url, body }, } = data; expect(url.split('/')).contains(item.id); - expect(body.settings.tags).contains(newTag); + expect(body.name).contains(newTag.name); + expect(body.category).contains(newTag.category); }); }); }); diff --git a/cypress/fixtures/categories.ts b/cypress/fixtures/tags.ts similarity index 54% rename from cypress/fixtures/categories.ts rename to cypress/fixtures/tags.ts index f641c710d..01481663b 100644 --- a/cypress/fixtures/categories.ts +++ b/cypress/fixtures/tags.ts @@ -1,82 +1,62 @@ import { - Category, - CategoryType, - ItemCategory, ItemValidation, ItemValidationProcess, ItemValidationStatus, + Tag, + TagCategory, } from '@graasp/sdk'; import { ItemForTest } from '../support/types'; import { PUBLISHED_ITEM } from './items'; -import { MEMBERS } from './members'; -export const SAMPLE_CATEGORIES: Category[] = [ +export const SAMPLE_TAGS: Tag[] = [ { id: 'e873d800-5647-442c-930d-2d677532846a', - name: 'test_category', - type: CategoryType.Discipline, + name: 'discipline 1', + category: TagCategory.Discipline, + }, + { + id: '152ef74e-8893-4736-926e-214c17396ed3', + name: 'level 1', + category: TagCategory.Level, }, { id: '352ef74e-8893-4736-926e-214c17396ed3', - name: 'test_category_2', - type: CategoryType.Level, + name: 'level 2', + category: TagCategory.Level, }, { id: 'ba7f7e3d-dc75-4070-b892-381fbf4759d9', - name: 'language-1', - type: CategoryType.Language, + name: 'resource-type-1', + category: TagCategory.ResourceType, }, { id: 'af7f7e3d-dc75-4070-b892-381fbf4759d5', - name: 'language-2', - type: CategoryType.Language, - }, -]; - -export const SAMPLE_ITEM_CATEGORIES: ItemCategory[] = [ - { - id: 'e75e1950-c5b4-4e21-95a2-c7c3bfa4072b', - item: PUBLISHED_ITEM, - category: SAMPLE_CATEGORIES[0], - createdAt: '2021-08-11T12:56:36.834Z', - creator: MEMBERS.ANNA, + name: 'resource-type-2', + category: TagCategory.ResourceType, }, ]; -export const SAMPLE_ITEM_LANGUAGE: ItemCategory[] = [ - { - id: 'e75e1950-c5b4-4e21-95a2-c7c3bfa4072b', - item: PUBLISHED_ITEM, - category: SAMPLE_CATEGORIES[2], - createdAt: '2021-08-11T12:56:36.834Z', - creator: MEMBERS.ANNA, - }, -]; - -export const CUSTOMIZED_TAGS = ['water', 'ice', 'temperature']; - -export const ITEM_WITH_CATEGORIES: ItemForTest = { +export const ITEM_WITH_TAGS: ItemForTest = { ...PUBLISHED_ITEM, settings: { - tags: CUSTOMIZED_TAGS, displayCoEditors: true, }, // for tests - categories: SAMPLE_ITEM_CATEGORIES, + tags: SAMPLE_TAGS, }; -export const ITEM_WITH_CATEGORIES_CONTEXT = { - items: [ITEM_WITH_CATEGORIES], +export const ITEM_WITH_TAGS_CONTEXT = { + items: [ITEM_WITH_TAGS], itemValidationGroups: [ { id: '65c57d69-0e59-4569-a422-f330c31c995c', - item: ITEM_WITH_CATEGORIES, + item: ITEM_WITH_TAGS, createdAt: '2021-08-11T12:56:36.834Z', itemValidations: [ { id: 'id1', - item: ITEM_WITH_CATEGORIES, + item: ITEM_WITH_TAGS, // itemValidationGroup: iVG, process: ItemValidationProcess.BadWordsDetection, status: ItemValidationStatus.Success, @@ -86,7 +66,7 @@ export const ITEM_WITH_CATEGORIES_CONTEXT = { }, { id: 'id2', - item: ITEM_WITH_CATEGORIES, + item: ITEM_WITH_TAGS, // itemValidationGroup: iVG, process: ItemValidationProcess.ImageChecking, status: ItemValidationStatus.Success, diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 079f07a4b..dc353ab48 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -6,13 +6,13 @@ import { ItemLayoutMode } from '@/enums'; import { LAYOUT_MODE_BUTTON_ID } from '../../src/config/selectors'; import { APPS_LIST } from '../fixtures/apps/apps'; -import { SAMPLE_CATEGORIES } from '../fixtures/categories'; import { SAMPLE_MENTIONS } from '../fixtures/chatbox'; import { CURRENT_USER, MEMBERS } from '../fixtures/members'; import './commands/item'; import './commands/navigation'; import { mockAddFavorite, + mockAddTag, mockAppApiAccessToken, mockCheckShortLink, mockClearItemChat, @@ -21,7 +21,6 @@ import { mockDeleteAppData, mockDeleteFavorite, mockDeleteInvitation, - mockDeleteItemCategory, mockDeleteItemMembershipForItem, mockDeleteItemThumbnail, mockDeleteItemVisibility, @@ -37,11 +36,9 @@ import { mockGetAppLink, mockGetAppListRoute, mockGetAvatarUrl, - mockGetCategories, mockGetChildren, mockGetCurrentMember, mockGetItem, - mockGetItemCategories, mockGetItemChat, mockGetItemFavorites, mockGetItemInvitations, @@ -63,6 +60,7 @@ import { mockGetPublishItemInformations, mockGetPublishItemsForMember, mockGetShortLinksItem, + mockGetTagsByItem, mockImportH5p, mockImportZip, mockMoveItems, @@ -73,7 +71,6 @@ import { mockPostAvatar, mockPostInvitations, mockPostItem, - mockPostItemCategory, mockPostItemChatMessage, mockPostItemFlag, mockPostItemLogin, @@ -87,6 +84,7 @@ import { mockPutItemLoginSchema, mockRecycleItems, mockRejectMembershipRequest, + mockRemoveTag, mockRequestMembership, mockRestoreItems, mockSignInRedirection, @@ -108,7 +106,6 @@ Cypress.Commands.add( members = Object.values(MEMBERS), currentMember = CURRENT_USER, mentions = SAMPLE_MENTIONS, - categories = SAMPLE_CATEGORIES, itemValidationGroups = [], itemPublicationStatus = PublicationStatus.Unpublished, membershipRequests = [], @@ -136,10 +133,6 @@ Cypress.Commands.add( postItemThumbnailError = false, postAvatarError = false, importZipError = false, - getCategoriesError = false, - getItemCategoriesError = false, - postItemCategoryError = false, - deleteItemCategoryError = false, postInvitationsError = false, getItemInvitationsError = false, patchInvitationError = false, @@ -216,8 +209,6 @@ Cypress.Commands.add( mockSignOut(); - // mockGetItemLogin(items); - mockGetItemLoginSchema(items); mockGetItemLoginSchemaType(items); @@ -270,7 +261,6 @@ Cypress.Commands.add( mockRestoreItems(items, restoreItemsError); - // mockGetItemThumbnail(items, getItemThumbnailError); mockGetItemThumbnailUrl(items, getItemThumbnailError); mockDeleteItemThumbnail(items, getItemThumbnailError); @@ -283,13 +273,10 @@ Cypress.Commands.add( mockImportZip(importZipError); - mockGetCategories(categories, getCategoriesError); - - mockGetItemCategories(items, getItemCategoriesError); - - mockPostItemCategory(postItemCategoryError); + mockGetTagsByItem(items); - mockDeleteItemCategory(deleteItemCategoryError); + mockRemoveTag(); + mockAddTag(); mockGetItemValidationGroups(itemValidationGroups); diff --git a/cypress/support/server.ts b/cypress/support/server.ts index a43f3a994..289cb052f 100644 --- a/cypress/support/server.ts +++ b/cypress/support/server.ts @@ -2,7 +2,6 @@ import { API_ROUTES } from '@graasp/query-client'; import { AccountType, App, - Category, ChatMention, CompleteMembershipRequest, DiscriminatedItem, @@ -76,13 +75,9 @@ const { buildPostItemChatMessageRoute, buildClearItemChatRoute, buildDeleteItemVisibilityRoute, - buildDeleteItemsRoute, buildUploadItemThumbnailRoute, buildUploadAvatarRoute, buildImportZipRoute, - buildGetCategoriesRoute, - buildPostItemCategoryRoute, - buildDeleteItemCategoryRoute, buildPostInvitationsRoute, buildGetItemInvitationsForItemRoute, buildDeleteInvitationRoute, @@ -253,7 +248,7 @@ export const mockDeleteItems = ( cy.intercept( { method: HttpMethod.Delete, - url: new RegExp(`${API_HOST}/${buildDeleteItemsRoute([])}`), + url: new RegExp(`${API_HOST}/items\\?id\\=`), }, ({ reply }) => { if (shouldThrowError) { @@ -1460,82 +1455,38 @@ export const mockPostAvatar = (shouldThrowError: boolean): void => { ).as('uploadAvatar'); }; -export const mockGetCategories = ( - categories: Category[], - shouldThrowError: boolean, -): void => { - cy.intercept( - { - method: HttpMethod.Get, - url: new RegExp( - `${API_HOST}/${parseStringToRegExp(buildGetCategoriesRoute())}`, - ), - }, - ({ reply }) => { - if (shouldThrowError) { - reply({ statusCode: StatusCodes.BAD_REQUEST }); - return; - } - reply(categories); - }, - ).as('getCategories'); -}; - -export const mockGetItemCategories = ( - items: ItemForTest[], - shouldThrowError: boolean, -): void => { +export const mockGetTagsByItem = (items: ItemForTest[]): void => { cy.intercept( { method: HttpMethod.Get, - url: new RegExp(`${API_HOST}/items/${ID_FORMAT}/categories`), + url: new RegExp(`${API_HOST}/items/${ID_FORMAT}/tags`), }, ({ reply, url }) => { - if (shouldThrowError) { - return reply({ statusCode: StatusCodes.BAD_REQUEST }); - } const itemId = url.slice(API_HOST.length).split('/')[2]; - const result = items.find(({ id }) => id === itemId)?.categories || []; + const result = items.find(({ id }) => id === itemId)?.tags || []; return reply(result); }, - ).as('getItemCategories'); + ).as('getTagsByItem'); }; -export const mockPostItemCategory = (shouldThrowError: boolean): void => { +export const mockAddTag = (): void => { cy.intercept( { method: HttpMethod.Post, - url: new RegExp(`${API_HOST}/${buildPostItemCategoryRoute(ID_FORMAT)}$`), - }, - ({ reply, body }) => { - if (shouldThrowError) { - return reply({ statusCode: StatusCodes.BAD_REQUEST }); - } - - return reply(body); + url: new RegExp(`${API_HOST}/items/${ID_FORMAT}/tags`), }, - ).as('postItemCategory'); + ({ reply }) => reply({ status: StatusCodes.NO_CONTENT }), + ).as('addTag'); }; -export const mockDeleteItemCategory = (shouldThrowError: boolean): void => { +export const mockRemoveTag = (): void => { cy.intercept( { method: HttpMethod.Delete, - url: new RegExp( - `${API_HOST}/${buildDeleteItemCategoryRoute({ - itemId: ID_FORMAT, - itemCategoryId: ID_FORMAT, - })}$`, - ), - }, - ({ reply, body }) => { - if (shouldThrowError) { - return reply({ statusCode: StatusCodes.BAD_REQUEST }); - } - - return reply(body); + url: new RegExp(`${API_HOST}/items/${ID_FORMAT}/tags/${ID_FORMAT}`), }, - ).as('deleteItemCategory'); + ({ reply }) => reply({ status: StatusCodes.NO_CONTENT }), + ).as('removeTag'); }; export const mockGetItemValidationGroups = ( diff --git a/cypress/support/types.ts b/cypress/support/types.ts index 2eb1b2858..725cbeb4f 100644 --- a/cypress/support/types.ts +++ b/cypress/support/types.ts @@ -1,5 +1,4 @@ import { - Category, ChatMention, ChatMessage, CompleteMember, @@ -7,7 +6,6 @@ import { DiscriminatedItem, Invitation, ItemBookmark, - ItemCategory, ItemLoginSchema, ItemMembership, ItemPublished, @@ -19,11 +17,12 @@ import { RecycledItemData, S3FileItemType, ShortLink, + Tag, ThumbnailsBySize, } from '@graasp/sdk'; export type ItemForTest = DiscriminatedItem & { - categories?: ItemCategory[]; + tags?: Tag[]; thumbnails?: ThumbnailsBySize; visibilities?: ItemVisibility[]; itemLoginSchema?: ItemLoginSchema; @@ -54,7 +53,6 @@ export type ApiConfig = { members?: MemberForTest[]; currentMember?: MemberForTest; mentions?: ChatMention[]; - categories?: Category[]; shortLinks?: ShortLink[]; itemId?: DiscriminatedItem['id']; bookmarkedItems?: ItemBookmark[]; @@ -62,7 +60,6 @@ export type ApiConfig = { itemPublicationStatus?: PublicationStatus; publishedItemData?: ItemPublished[]; membershipRequests?: CompleteMembershipRequest[]; - // statuses = SAMPLE_STATUSES, itemValidationGroups?: ItemValidationGroup[]; deleteItemsError?: boolean; postItemError?: boolean; diff --git a/src/components/hooks/useItemCategories.tsx b/src/components/hooks/useItemCategories.tsx deleted file mode 100644 index aa2e7f7f1..000000000 --- a/src/components/hooks/useItemCategories.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Category } from '@graasp/sdk'; - -import { hooks, mutations } from '@/config/queryClient'; -import { Filter } from '@/types/array'; - -const { useItemCategories: useCategories } = hooks; -const { usePostItemCategory, useDeleteItemCategory } = mutations; - -type Props = { - itemId: string; - filterCategories?: Filter; -}; - -type UseItemCategories = { - isLoading: boolean; - isMutationLoading: boolean; - isMutationError: boolean; - isMutationSuccess: boolean; - categories?: string[]; - addCategory: (categoryId: string) => void; - deleteCategory: (categoryId: string) => void; - deleteCategoryByName: (name: string) => void; -}; - -export const useItemCategories = ({ - itemId, - filterCategories = () => true, -}: Props): UseItemCategories => { - const { data: itemCategories, isLoading } = useCategories(itemId); - - const filteredCategories = itemCategories?.filter(({ category }) => - filterCategories(category), - ); - - const categories = filteredCategories?.map(({ category }) => category.name); - - const { - mutate: createItemCategory, - isPending: isPostLoading, - isSuccess: isPostSuccess, - isError: isPostError, - } = usePostItemCategory(); - const { - mutate: deleteItemCategory, - isPending: isDeleteLoading, - isSuccess: isDeleteSuccess, - isError: isDeleteError, - } = useDeleteItemCategory(); - - const isMutationLoading = isPostLoading || isDeleteLoading; - const isMutationSuccess = isPostSuccess || isDeleteSuccess; - const isMutationError = isPostError || isDeleteError; - - const deleteCategory = (itemCategoryId: string) => - deleteItemCategory({ - itemId, - itemCategoryId, - }); - - const deleteCategoryByName = (categoryName: string) => { - const removedItemCategory = filteredCategories?.find( - ({ category }) => - category.name.toLowerCase() === categoryName.toLowerCase(), - ); - - if (!removedItemCategory) { - console.error('The given category was not found !', categoryName); - return; - } - - deleteCategory(removedItemCategory.id); - }; - - const addCategory = (categoryId: string) => - createItemCategory({ itemId, categoryId }); - - return { - isLoading, - isMutationError, - isMutationLoading, - isMutationSuccess, - categories, - addCategory, - deleteCategory, - deleteCategoryByName, - }; -}; - -export default useItemCategories; diff --git a/src/components/input/MultiSelectChipInput.tsx b/src/components/input/MultiSelectTagChipInput.tsx similarity index 88% rename from src/components/input/MultiSelectChipInput.tsx rename to src/components/input/MultiSelectTagChipInput.tsx index 78809d102..6a846fd34 100644 --- a/src/components/input/MultiSelectChipInput.tsx +++ b/src/components/input/MultiSelectTagChipInput.tsx @@ -16,7 +16,7 @@ import { useBuilderTranslation, useEnumsTranslation } from '@/config/i18n'; import { hooks } from '@/config/queryClient'; import { MULTI_SELECT_CHIP_CONTAINER_ID, - MULTI_SELECT_CHIP_INPUT_ID, + buildMultiSelectChipInputId, buildMultiSelectChipsSelector, } from '@/config/selectors'; @@ -24,11 +24,10 @@ import useTagsManager from '../item/publish/customizedTags/useTagsManager'; type Props = { itemId: DiscriminatedItem['id']; - label: string; tagCategory: TagCategory; }; -export const MultiSelectChipInput = ({ +export const MultiSelectTagChipInput = ({ itemId, tagCategory, }: Props): JSX.Element | null => { @@ -51,7 +50,6 @@ export const MultiSelectChipInput = ({ isFetching, isLoading, } = hooks.useTags({ search: debouncedCurrentValue, category: tagCategory }); - const renderTags = ( value: readonly string[], getTagProps: AutocompleteRenderGetTagProps, @@ -98,30 +96,32 @@ export const MultiSelectChipInput = ({ }, }} onChange={(e) => handleCurrentValueChanged(e.target.value, tagCategory)} + onKeyDown={(e) => { + if (e.code === 'Enter' && 'value' in e.target) { + addValue({ name: e.target.value as string, category: tagCategory }); + } + }} /> ); const options = tags - // remove tags already existing for item - ?.filter( - ({ id: thisId }) => - !tagsPerCategory?.[tagCategory]?.find(({ id }) => id === thisId), - ) + ?.filter(({ category }) => category === tagCategory) ?.map(({ name }) => name) ?? []; return ( @@ -162,5 +162,3 @@ export const MultiSelectChipInput = ({ ); }; - -export default MultiSelectChipInput; diff --git a/src/components/item/publish/DropdownMenu.tsx b/src/components/item/publish/DropdownMenu.tsx deleted file mode 100644 index 977fea79b..000000000 --- a/src/components/item/publish/DropdownMenu.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { SyntheticEvent } from 'react'; - -import { - Autocomplete, - AutocompleteChangeReason, - Box, - TextField, - Typography, -} from '@mui/material'; - -import { Category, CategoryType, ItemCategory } from '@graasp/sdk'; - -import { useBuilderTranslation } from '../../../config/i18n'; -import { - buildCategoryDropdownParentSelector, - buildCategorySelectionId, - buildCategorySelectionOptionId, - buildCategorySelectionTitleId, -} from '../../../config/selectors'; -import { BUILDER } from '../../../langs/constants'; - -type Props = { - disabled?: boolean; - type: CategoryType; - title: string; - values: Category[]; - selectedValues?: ItemCategory[]; - handleChange: ( - _event: SyntheticEvent, - value: Category[], - reason: AutocompleteChangeReason, - details?: { option: Category }, - ) => void; -}; - -const DropdownMenu = ({ - disabled = false, - type, - title, - handleChange, - values, - selectedValues, -}: Props): JSX.Element | null => { - const { t: translateBuilder } = useBuilderTranslation(); - - if (!values.length) { - return null; - } - - const selected = values.filter(({ id }) => - selectedValues?.find(({ category }) => category.id === id), - ); - - return ( - - - {title} - - option.name} - onChange={handleChange} - renderInput={(params) => ( - - )} - renderOption={(props, option) => ( -
  • - {option.name} -
  • - )} - /> -
    - ); -}; - -export default DropdownMenu; diff --git a/src/components/item/publish/customizedTags/CustomizedTags.tsx b/src/components/item/publish/customizedTags/CustomizedTags.tsx index 403f8035d..77b43467e 100644 --- a/src/components/item/publish/customizedTags/CustomizedTags.tsx +++ b/src/components/item/publish/customizedTags/CustomizedTags.tsx @@ -3,10 +3,10 @@ import { Chip, Stack } from '@mui/material'; import { DiscriminatedItem, TagCategory } from '@graasp/sdk'; -import MultiSelectChipInput from '@/components/input/MultiSelectChipInput'; +import { MultiSelectTagChipInput } from '@/components/input/MultiSelectTagChipInput'; import { useBuilderTranslation } from '@/config/i18n'; import { - ITEM_TAGS_OPEN_MODAL_BUTTON_ID, + ITEM_TAGS_OPEN_MODAL_BUTTON_CY, buildCustomizedTagsSelector, } from '@/config/selectors'; import { BUILDER } from '@/langs/constants'; @@ -44,19 +44,16 @@ export const CustomizedTags = ({ item }: Props): JSX.Element => { isOpen={isOpen} modalContent={ - - - @@ -68,7 +65,7 @@ export const CustomizedTags = ({ item }: Props): JSX.Element => { label={t(BUILDER.ITEM_TAGS_CHIP_BUTTON_EDIT)} variant="outlined" onClick={openModal} - data-cy={ITEM_TAGS_OPEN_MODAL_BUTTON_ID} + data-cy={ITEM_TAGS_OPEN_MODAL_BUTTON_CY} /> {chipTags}
    diff --git a/src/config/selectors.ts b/src/config/selectors.ts index af4d47695..33fae546c 100644 --- a/src/config/selectors.ts +++ b/src/config/selectors.ts @@ -193,8 +193,9 @@ export const CROP_MODAL_CONFIRM_BUTTON_ID = 'cropModalConfirmButton'; export const ZIP_DASHBOARD_UPLOADER_ID = 'zipDashboardUploader'; export const H5P_DASHBOARD_UPLOADER_ID = 'h5pDashboardUploader'; -export const ITEM_TAGS_OPEN_MODAL_BUTTON_ID = 'itemTagsOpenModalButton'; -export const MULTI_SELECT_CHIP_INPUT_ID = 'multiSelectChipInput'; +export const ITEM_TAGS_OPEN_MODAL_BUTTON_CY = 'itemTagsOpenModalButton'; +export const buildMultiSelectChipInputId = (id: string): string => + `multiSelectChipInput-${id}`; export const MULTI_SELECT_CHIP_ADD_BUTTON_ID = 'multiSelectChipAddButton'; export const MULTI_SELECT_CHIP_CONTAINER_ID = 'multiSelectChipContainer'; export const buildMultiSelectChipsSelector = (index: number): string =>