From 819c4a173c2215189aefcf5046bfb07c76719b53 Mon Sep 17 00:00:00 2001 From: Kim Lan Phan Hoang Date: Fri, 22 Dec 2023 14:02:19 +0100 Subject: [PATCH] feat: use accessible items on home (#887) * feat: use accessible items on home * refactor: fix height to line number for home * refactor: add message on shared items page * refactor: add margin --- cypress/e2e/item/create/createApp.cy.ts | 11 +- cypress/e2e/item/create/createDocument.cy.ts | 2 +- cypress/e2e/item/create/createFolder.cy.ts | 2 +- cypress/e2e/item/create/createLink.cy.ts | 4 +- cypress/e2e/item/create/createShortcut.cy.ts | 2 +- cypress/e2e/item/delete/gridRecycleItem.cy.ts | 2 +- cypress/e2e/item/delete/listRecycleItem.cy.ts | 2 +- .../e2e/item/delete/listRecycleItems.cy.ts | 2 +- cypress/e2e/item/edit/editApp.cy.ts | 4 +- cypress/e2e/item/edit/editDocument.cy.ts | 4 +- cypress/e2e/item/edit/editEtherpad.cy.ts | 4 +- cypress/e2e/item/edit/editFile.cy.ts | 4 +- cypress/e2e/item/edit/editFolder.cy.ts | 4 +- cypress/e2e/item/edit/editH5p.cy.ts | 4 +- cypress/e2e/item/edit/editLink.cy.ts | 4 +- cypress/e2e/item/edit/editShortcut.cy.ts | 4 +- cypress/e2e/item/home/home.cy.ts | 343 ++++++++++++++++++ cypress/e2e/item/move/listMoveMultiple.cy.ts | 4 - cypress/e2e/item/search/gridItemSearch.cy.ts | 122 ------- cypress/e2e/item/search/listItemSearch.cy.ts | 47 --- cypress/e2e/item/shared/sharedItems.cy.ts | 55 ++- cypress/e2e/item/view/viewFolder.cy.ts | 306 ++-------------- cypress/e2e/redirection.cy.ts | 4 +- cypress/support/commands.ts | 3 + cypress/support/commands/item.ts | 4 +- cypress/support/createUtils.ts | 2 +- cypress/support/index.ts | 7 +- cypress/support/server.ts | 27 ++ package.json | 6 +- src/components/SharedItems.tsx | 8 +- src/components/item/ItemContent.tsx | 2 +- src/components/item/ItemSearch.tsx | 15 +- src/components/item/form/AppForm.tsx | 3 +- src/components/layout/Navigation.tsx | 1 + src/components/main/Home.tsx | 139 +++++-- src/components/main/ItemCard.tsx | 2 +- src/components/main/Items.tsx | 55 ++- src/components/main/ItemsGrid.tsx | 88 ++--- src/components/main/ItemsTable.tsx | 77 ++-- src/components/main/ItemsToolbar.tsx | 50 ++- src/config/constants.ts | 3 + src/config/selectors.ts | 7 +- src/langs/constants.ts | 3 + src/langs/en.json | 3 +- src/langs/fr.json | 3 +- yarn.lock | 34 +- 46 files changed, 828 insertions(+), 654 deletions(-) create mode 100644 cypress/e2e/item/home/home.cy.ts delete mode 100644 cypress/e2e/item/search/gridItemSearch.cy.ts delete mode 100644 cypress/e2e/item/search/listItemSearch.cy.ts diff --git a/cypress/e2e/item/create/createApp.cy.ts b/cypress/e2e/item/create/createApp.cy.ts index 8a58b50e2..2a4d5c0cf 100644 --- a/cypress/e2e/item/create/createApp.cy.ts +++ b/cypress/e2e/item/create/createApp.cy.ts @@ -4,6 +4,7 @@ import { GRAASP_APP_ITEM, GRAASP_CUSTOM_APP_ITEM, } from '../../../fixtures/apps'; +import { APPS_LIST } from '../../../fixtures/apps/apps'; import { SAMPLE_ITEMS } from '../../../fixtures/items'; import { createApp } from '../../../support/createUtils'; @@ -16,11 +17,11 @@ describe('Create App', () => { cy.switchMode(ITEM_LAYOUT_MODES.LIST); // create - createApp(GRAASP_APP_ITEM); + createApp(GRAASP_APP_ITEM, { id: APPS_LIST[0].id }); cy.wait('@postItem').then(() => { // should update view - cy.wait('@getOwnItems'); + cy.wait('@getAccessibleItems'); }); }); @@ -31,11 +32,11 @@ describe('Create App', () => { cy.switchMode(ITEM_LAYOUT_MODES.LIST); // create - createApp(GRAASP_APP_ITEM); + createApp(GRAASP_APP_ITEM, { custom: true }); cy.wait('@postItem').then(() => { // should update view - cy.wait('@getOwnItems'); + cy.wait('@getAccessibleItems'); }); }); }); @@ -51,7 +52,7 @@ describe('Create App', () => { cy.switchMode(ITEM_LAYOUT_MODES.LIST); // create - createApp(GRAASP_APP_ITEM); + createApp(GRAASP_APP_ITEM, { id: APPS_LIST[0].id }); cy.wait('@postItem').then(() => { // expect update diff --git a/cypress/e2e/item/create/createDocument.cy.ts b/cypress/e2e/item/create/createDocument.cy.ts index f835db31f..699a2ead1 100644 --- a/cypress/e2e/item/create/createDocument.cy.ts +++ b/cypress/e2e/item/create/createDocument.cy.ts @@ -21,7 +21,7 @@ describe('Create Document', () => { cy.wait('@postItem').then(() => { // should update view - cy.wait('@getOwnItems'); + cy.wait('@getAccessibleItems'); }); }); diff --git a/cypress/e2e/item/create/createFolder.cy.ts b/cypress/e2e/item/create/createFolder.cy.ts index 54a6b555e..6087d2e09 100644 --- a/cypress/e2e/item/create/createFolder.cy.ts +++ b/cypress/e2e/item/create/createFolder.cy.ts @@ -24,7 +24,7 @@ describe('Create Folder', () => { // create createFolder(CREATED_ITEM); - cy.wait(['@postItem', '@getOwnItems']); + cy.wait(['@postItem', '@getAccessibleItems']); }); it('create folder in item', () => { diff --git a/cypress/e2e/item/create/createLink.cy.ts b/cypress/e2e/item/create/createLink.cy.ts index 4306c94dd..e2f076969 100644 --- a/cypress/e2e/item/create/createLink.cy.ts +++ b/cypress/e2e/item/create/createLink.cy.ts @@ -25,7 +25,7 @@ describe('Create Link', () => { cy.wait(CREATE_ITEM_PAUSE); // expect update - cy.wait('@getOwnItems'); + cy.wait('@getAccessibleItems'); }); }); @@ -43,7 +43,7 @@ describe('Create Link', () => { cy.wait(CREATE_ITEM_PAUSE); // expect update - cy.wait('@getOwnItems'); + cy.wait('@getAccessibleItems'); }); }); diff --git a/cypress/e2e/item/create/createShortcut.cy.ts b/cypress/e2e/item/create/createShortcut.cy.ts index 4af08adc7..11c4c0c11 100644 --- a/cypress/e2e/item/create/createShortcut.cy.ts +++ b/cypress/e2e/item/create/createShortcut.cy.ts @@ -66,7 +66,7 @@ const checkCreateShortcutRequest = ({ expect(url).to.include(qs.stringify({ parentId: toItemId })); } else { expect(url).to.not.include('parentId'); - cy.wait('@getOwnItems'); + cy.wait('@getAccessibleItems'); } }); }; diff --git a/cypress/e2e/item/delete/gridRecycleItem.cy.ts b/cypress/e2e/item/delete/gridRecycleItem.cy.ts index fd3619dd4..de4c46e46 100644 --- a/cypress/e2e/item/delete/gridRecycleItem.cy.ts +++ b/cypress/e2e/item/delete/gridRecycleItem.cy.ts @@ -24,7 +24,7 @@ describe('Recycle Item in Grid', () => { // recycle recycleItem(id); - cy.wait(['@recycleItems', '@getOwnItems']); + cy.wait(['@recycleItems', '@getAccessibleItems']); }); it('recycle item inside parent', () => { diff --git a/cypress/e2e/item/delete/listRecycleItem.cy.ts b/cypress/e2e/item/delete/listRecycleItem.cy.ts index 5b85db85e..7ee4d71a2 100644 --- a/cypress/e2e/item/delete/listRecycleItem.cy.ts +++ b/cypress/e2e/item/delete/listRecycleItem.cy.ts @@ -29,7 +29,7 @@ describe('Recycle Item in List', () => { cy.wait('@recycleItems').then(({ request: { url } }) => { expect(url).to.contain(id); }); - cy.wait('@getOwnItems'); + cy.wait('@getAccessibleItems'); }); it('recycle item inside parent', () => { diff --git a/cypress/e2e/item/delete/listRecycleItems.cy.ts b/cypress/e2e/item/delete/listRecycleItems.cy.ts index dd0fc594b..d9e220c65 100644 --- a/cypress/e2e/item/delete/listRecycleItems.cy.ts +++ b/cypress/e2e/item/delete/listRecycleItems.cy.ts @@ -26,7 +26,7 @@ describe('Recycle Items in List', () => { // delete recycleItems([SAMPLE_ITEMS.items[0].id, SAMPLE_ITEMS.items[1].id]); - cy.wait(['@recycleItems', '@getOwnItems']); + cy.wait(['@recycleItems', '@getAccessibleItems']); }); it('recycle 2 items in item', () => { diff --git a/cypress/e2e/item/edit/editApp.cy.ts b/cypress/e2e/item/edit/editApp.cy.ts index b73048548..15d1249d4 100644 --- a/cypress/e2e/item/edit/editApp.cy.ts +++ b/cypress/e2e/item/edit/editApp.cy.ts @@ -85,7 +85,7 @@ describe('Edit App', () => { expect(id).to.equal(itemToEdit.id); expect(name).to.equal(newFields.name); cy.wait(EDIT_ITEM_PAUSE); - cy.wait('@getOwnItems'); + cy.wait('@getAccessibleItems'); }, ); }); @@ -149,7 +149,7 @@ describe('Edit App', () => { }) => { // check item is edited and updated cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getOwnItems'); + cy.get('@getAccessibleItems'); expect(id).to.equal(itemToEdit.id); expect(name).to.equal(newFields.name); }, diff --git a/cypress/e2e/item/edit/editDocument.cy.ts b/cypress/e2e/item/edit/editDocument.cy.ts index 5215cc334..c496873b1 100644 --- a/cypress/e2e/item/edit/editDocument.cy.ts +++ b/cypress/e2e/item/edit/editDocument.cy.ts @@ -59,7 +59,7 @@ describe('Edit Document', () => { expect(name).to.equal(newFields.name); expect(getDocumentExtra(extra)?.content).to.contain(content); cy.wait(EDIT_ITEM_PAUSE); - cy.wait('@getOwnItems'); + cy.wait('@getAccessibleItems'); }, ); }); @@ -125,7 +125,7 @@ describe('Edit Document', () => { }) => { // check item is edited and updated cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getOwnItems'); + cy.get('@getAccessibleItems'); expect(id).to.equal(itemToEdit.id); expect(name).to.equal(newFields.name); expect(getDocumentExtra(extra)?.content).to.contain(content); diff --git a/cypress/e2e/item/edit/editEtherpad.cy.ts b/cypress/e2e/item/edit/editEtherpad.cy.ts index f65f4d955..81b3de804 100644 --- a/cypress/e2e/item/edit/editEtherpad.cy.ts +++ b/cypress/e2e/item/edit/editEtherpad.cy.ts @@ -35,7 +35,7 @@ describe('Edit Etherpad', () => { }) => { // check item is edited and updated cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getOwnItems'); + cy.get('@getAccessibleItems'); expect(id).to.equal(itemToEdit.id); expect(name).to.equal(EDITED_FIELDS.name); }, @@ -67,7 +67,7 @@ describe('Edit Etherpad', () => { }) => { // check item is edited and updated cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getOwnItems'); + cy.get('@getAccessibleItems'); expect(id).to.equal(itemToEdit.id); expect(name).to.equal(EDITED_FIELDS.name); }, diff --git a/cypress/e2e/item/edit/editFile.cy.ts b/cypress/e2e/item/edit/editFile.cy.ts index d9913a063..fb3c74764 100644 --- a/cypress/e2e/item/edit/editFile.cy.ts +++ b/cypress/e2e/item/edit/editFile.cy.ts @@ -88,7 +88,7 @@ describe('Edit File', () => { expect(id).to.equal(itemToEdit.id); expect(name).to.equal(EDITED_FIELDS.name); cy.wait(EDIT_ITEM_PAUSE); - cy.wait('@getOwnItems'); + cy.wait('@getAccessibleItems'); }, ); }); @@ -120,7 +120,7 @@ describe('Edit File', () => { expect(id).to.equal(itemToEdit.id); expect(name).to.equal(EDITED_FIELDS.name); cy.wait(EDIT_ITEM_PAUSE); - cy.wait('@getOwnItems'); + cy.wait('@getAccessibleItems'); }, ); }); diff --git a/cypress/e2e/item/edit/editFolder.cy.ts b/cypress/e2e/item/edit/editFolder.cy.ts index 56e71b50c..7db603046 100644 --- a/cypress/e2e/item/edit/editFolder.cy.ts +++ b/cypress/e2e/item/edit/editFolder.cy.ts @@ -83,7 +83,7 @@ describe('Edit Folder', () => { expect(name).to.equal(EDITED_FIELDS.name); expect(description).to.contain(newDescription); cy.wait(EDIT_ITEM_PAUSE); - cy.wait('@getOwnItems'); + cy.wait('@getAccessibleItems'); }, ); }); @@ -167,7 +167,7 @@ describe('Edit Folder', () => { }) => { // check item is edited and updated cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getOwnItems'); + cy.get('@getAccessibleItems'); expect(id).to.equal(itemToEdit.id); expect(name).to.equal(EDITED_FIELDS.name); }, diff --git a/cypress/e2e/item/edit/editH5p.cy.ts b/cypress/e2e/item/edit/editH5p.cy.ts index 34f69823a..854476d73 100644 --- a/cypress/e2e/item/edit/editH5p.cy.ts +++ b/cypress/e2e/item/edit/editH5p.cy.ts @@ -35,7 +35,7 @@ describe('Edit H5P', () => { }) => { // check item is edited and updated cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getOwnItems'); + cy.get('@getAccessibleItems'); expect(id).to.equal(itemToEdit.id); expect(name).to.equal(EDITED_FIELDS.name); }, @@ -67,7 +67,7 @@ describe('Edit H5P', () => { }) => { // check item is edited and updated cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getOwnItems'); + cy.get('@getAccessibleItems'); expect(id).to.equal(itemToEdit.id); expect(name).to.equal(EDITED_FIELDS.name); }, diff --git a/cypress/e2e/item/edit/editLink.cy.ts b/cypress/e2e/item/edit/editLink.cy.ts index 7b5e90edf..f2a61a058 100644 --- a/cypress/e2e/item/edit/editLink.cy.ts +++ b/cypress/e2e/item/edit/editLink.cy.ts @@ -74,7 +74,7 @@ describe('Edit Link', () => { }) => { // check item is edited and updated cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getOwnItems'); + cy.get('@getAccessibleItems'); expect(id).to.equal(itemToEdit.id); expect(name).to.equal(EDITED_FIELDS.name); }, @@ -106,7 +106,7 @@ describe('Edit Link', () => { }) => { // check item is edited and updated cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getOwnItems'); + cy.get('@getAccessibleItems'); expect(id).to.equal(itemToEdit.id); expect(name).to.equal(EDITED_FIELDS.name); }, diff --git a/cypress/e2e/item/edit/editShortcut.cy.ts b/cypress/e2e/item/edit/editShortcut.cy.ts index 92f7b24f2..5f96d06e9 100644 --- a/cypress/e2e/item/edit/editShortcut.cy.ts +++ b/cypress/e2e/item/edit/editShortcut.cy.ts @@ -35,7 +35,7 @@ describe('Edit Shortcut', () => { }) => { // check item is edited and updated cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getOwnItems'); + cy.get('@getAccessibleItems'); expect(id).to.equal(itemToEdit.id); expect(name).to.equal(EDITED_FIELDS.name); }, @@ -67,7 +67,7 @@ describe('Edit Shortcut', () => { }) => { // check item is edited and updated cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getOwnItems'); + cy.get('@getAccessibleItems'); expect(id).to.equal(itemToEdit.id); expect(name).to.equal(EDITED_FIELDS.name); }, diff --git a/cypress/e2e/item/home/home.cy.ts b/cypress/e2e/item/home/home.cy.ts new file mode 100644 index 000000000..95a6fad8b --- /dev/null +++ b/cypress/e2e/item/home/home.cy.ts @@ -0,0 +1,343 @@ +import i18n, { BUILDER_NAMESPACE } from '../../../../src/config/i18n'; +import { HOME_PATH } from '../../../../src/config/paths'; +import { + ACCESSIBLE_ITEMS_NEXT_PAGE_BUTTON_SELECTOR, + ACCESSIBLE_ITEMS_ONLY_ME_ID, + ITEMS_GRID_NO_ITEM_ID, + ITEMS_GRID_PAGINATION_ID, + ITEMS_TABLE_ROW, + ITEM_SEARCH_INPUT_ID, + NAVIGATION_ROOT_ID, + buildItemCard, + buildItemsTableRowIdAttribute, + buildItemsTableRowSelector, +} from '../../../../src/config/selectors'; +import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; +import { BUILDER } from '../../../../src/langs/constants'; +import { SAMPLE_ITEMS, generateOwnItems } from '../../../fixtures/items'; +import { CURRENT_USER } from '../../../fixtures/members'; +import { + NAVIGATION_LOAD_PAUSE, + TABLE_ITEM_RENDER_TIME, +} from '../../../support/constants'; +import { ItemForTest } from '../../../support/types'; + +const translateBuilder = (key: string) => + i18n.t(key, { ns: BUILDER_NAMESPACE }); + +const sampleItems = generateOwnItems(30); + +describe('Home', () => { + describe('Grid', () => { + describe('Features', () => { + beforeEach(() => { + cy.setUpApi({ + items: sampleItems, + }); + i18n.changeLanguage(CURRENT_USER.extra.lang as string); + cy.visit(HOME_PATH); + cy.switchMode(ITEM_LAYOUT_MODES.GRID); + }); + + it('Show only created by me checkbox should trigger refetch', () => { + cy.wait('@getAccessibleItems').then(({ request: { url } }) => { + expect(url).not.to.contain(CURRENT_USER.id); + }); + + cy.get(`#${ACCESSIBLE_ITEMS_ONLY_ME_ID}`).click(); + + cy.wait('@getAccessibleItems').then(({ request: { url } }) => { + expect(url).to.contain(CURRENT_USER.id); + }); + }); + + describe('Search', () => { + it('Search should trigger refetch', () => { + const searchText = 'mysearch'; + cy.get(`#${ITEM_SEARCH_INPUT_ID}`).type(searchText); + + cy.wait(['@getAccessibleItems', '@getAccessibleItems']).then( + ([ + _first, + { + request: { url }, + }, + ]) => { + expect(url).to.contain(searchText); + }, + ); + }); + + it('Search on second page should reset page number', () => { + const searchText = 'mysearch'; + cy.wait('@getAccessibleItems'); + // navigate to seconde page + cy.get(`#${ITEMS_GRID_PAGINATION_ID} > ul > li`).eq(2).click(); + + cy.wait('@getAccessibleItems').then(({ request: { url } }) => { + expect(url).to.contain('page=2'); + }); + cy.get(`#${ITEM_SEARCH_INPUT_ID}`).type(searchText); + + cy.wait(['@getAccessibleItems', '@getAccessibleItems']).then( + ([ + _unused, + { + request: { url }, + }, + ]) => { + expect(url).to.contain(searchText); + expect(url).to.contain('page=1'); + }, + ); + }); + }); + + describe('Pagination', () => { + const checkGridPagination = ( + items: ItemForTest[], + itemsPerPage: number = 10, + ) => { + const numberPages = Math.ceil(items.length / itemsPerPage); + + // for each page + for (let i = 0; i < numberPages; i += 1) { + // navigate to page + cy.get(`#${ITEMS_GRID_PAGINATION_ID} > ul > li`) + .eq(i + 1) // leftmost li is "prev" button + .click(); + // compute items that should be on this page + const shouldDisplay = items.slice( + i * itemsPerPage, + (i + 1) * itemsPerPage, + ); + // compute items that should not be on this page + const shouldNotDisplay = items.filter( + (it) => !shouldDisplay.includes(it), + ); + + shouldDisplay.forEach((item) => { + cy.get(`#${buildItemCard(item.id)}`).should('exist'); + }); + + shouldNotDisplay.forEach((item) => { + cy.get(`#${buildItemCard(item.id)}`).should('not.exist'); + }); + } + }; + + it('shows only items of each page', () => { + // using default items per page count + checkGridPagination(sampleItems); + }); + }); + }); + + describe('Navigation', () => { + beforeEach(() => { + cy.setUpApi(SAMPLE_ITEMS); + i18n.changeLanguage(CURRENT_USER.extra.lang as string); + cy.visit(HOME_PATH); + cy.switchMode(ITEM_LAYOUT_MODES.GRID); + }); + + it('visit Home', () => { + cy.wait('@getAccessibleItems').then(({ response: { body } }) => { + cy.wait('@getItemMemberships'); + // check item is created and displayed + for (const item of body.data) { + cy.get(`#${buildItemCard(item.id)}`).should('exist'); + } + }); + + // visit child + const { id: childId } = SAMPLE_ITEMS.items[0]; + cy.goToItemInGrid(childId); + + // should get children + cy.wait('@getChildren', { timeout: TABLE_ITEM_RENDER_TIME }).then( + ({ response: { body } }) => { + // check item is created and displayed + for (const item of body) { + cy.get(`#${buildItemCard(item.id)}`).should('exist'); + } + }, + ); + + // root title + cy.get(`#${NAVIGATION_ROOT_ID}`).contains( + translateBuilder(BUILDER.NAVIGATION_MY_ITEMS_TITLE), + ); + + // visit child + const { id: childChildId } = SAMPLE_ITEMS.items[2]; + cy.goToItemInGrid(childChildId); + + // expect no children + cy.get(`#${ITEMS_GRID_NO_ITEM_ID}`).should('exist'); + + // root title + cy.get(`#${NAVIGATION_ROOT_ID}`).contains( + translateBuilder(BUILDER.NAVIGATION_MY_ITEMS_TITLE), + ); + + // return parent with navigation and should display children + cy.wait(NAVIGATION_LOAD_PAUSE); + cy.goToItemWithNavigation(childId); + // should get children + cy.wait('@getChildren').then(() => { + // check item is created and displayed + for (const item of [ + SAMPLE_ITEMS.items[2], + SAMPLE_ITEMS.items[3], + SAMPLE_ITEMS.items[4], + ]) { + cy.get(`#${buildItemCard(item.id)}`).should('exist'); + } + }); + // root title + cy.get(`#${NAVIGATION_ROOT_ID}`).contains( + translateBuilder(BUILDER.NAVIGATION_MY_ITEMS_TITLE), + ); + }); + }); + }); + + describe('List', () => { + describe('Navigation', () => { + beforeEach(() => { + cy.setUpApi(SAMPLE_ITEMS); + cy.visit(HOME_PATH); + cy.switchMode(ITEM_LAYOUT_MODES.LIST); + }); + + it('visit Home', () => { + // visit child + const { id: childId } = SAMPLE_ITEMS.items[0]; + cy.goToItemInList(childId); + + // should get children + cy.wait('@getChildren').then(({ response: { body } }) => { + // check item is created and displayed + for (const item of body) { + cy.get(buildItemsTableRowIdAttribute(item.id)).should('exist'); + } + }); + + // visit child + const { id: childChildId } = SAMPLE_ITEMS.items[2]; + cy.goToItemInList(childChildId); + + // expect no children + cy.get(ITEMS_TABLE_ROW).should('not.exist'); + + // return parent with navigation and should display children + cy.goToItemWithNavigation(childId); + // should get children + cy.wait('@getChildren').then(({ response: { body } }) => { + // check item is created and displayed + for (const item of body) { + cy.get(buildItemsTableRowIdAttribute(item.id)).should('exist'); + } + }); + }); + }); + + describe('Features', () => { + beforeEach(() => { + cy.setUpApi({ + items: sampleItems, + }); + cy.visit(HOME_PATH); + cy.switchMode(ITEM_LAYOUT_MODES.LIST); + }); + + it('Show only created by me checkbox should trigger refetch', () => { + cy.wait('@getAccessibleItems').then(({ request: { url } }) => { + expect(url).not.to.contain(CURRENT_USER.id); + }); + + cy.get(`#${ACCESSIBLE_ITEMS_ONLY_ME_ID}`).click(); + + cy.wait('@getAccessibleItems').then(({ request: { url } }) => { + expect(url).to.contain(CURRENT_USER.id); + }); + }); + + describe('Search', () => { + it('Search should trigger refetch', () => { + const searchText = 'mysearch'; + cy.get(`#${ITEM_SEARCH_INPUT_ID}`).type(searchText); + + cy.wait(['@getAccessibleItems', '@getAccessibleItems']).then( + ([ + _first, + { + request: { url }, + }, + ]) => { + expect(url).to.contain(searchText); + }, + ); + }); + + it('Search on second page should reset page number', () => { + const searchText = 'mysearch'; + cy.wait('@getAccessibleItems'); + // navigate to second page + cy.get(ACCESSIBLE_ITEMS_NEXT_PAGE_BUTTON_SELECTOR).click(); + + cy.wait('@getAccessibleItems').then(({ request: { url } }) => { + expect(url).to.contain('page=2'); + }); + cy.get(`#${ITEM_SEARCH_INPUT_ID}`).type(searchText); + + cy.wait(['@getAccessibleItems', '@getAccessibleItems']).then( + ([ + _unused, + { + request: { url }, + }, + ]) => { + expect(url).to.contain(searchText); + expect(url).to.contain('page=1'); + }, + ); + }); + }); + + describe('Pagination', () => { + const itemsPerPage = 10; + const items = generateOwnItems(30); + const numberPages = Math.ceil(items.length / itemsPerPage); + + it('shows only items of each page', () => { + // for each page + for (let i = 0; i < numberPages; i += 1) { + // compute items that should be on this page + const shouldDisplay = items.slice( + i * itemsPerPage, + (i + 1) * itemsPerPage, + ); + // compute items that should not be on this page + const shouldNotDisplay = items.filter( + (it) => !shouldDisplay.includes(it), + ); + + shouldDisplay.forEach((item) => { + cy.get(buildItemsTableRowSelector(item.id)).should('exist'); + }); + + shouldNotDisplay.forEach((item) => { + cy.get(buildItemsTableRowSelector(item.id)).should('not.exist'); + }); + // navigate to next page + if (i !== numberPages - 1) { + cy.get(ACCESSIBLE_ITEMS_NEXT_PAGE_BUTTON_SELECTOR).click(); + } + } + }); + }); + }); + }); +}); diff --git a/cypress/e2e/item/move/listMoveMultiple.cy.ts b/cypress/e2e/item/move/listMoveMultiple.cy.ts index 9e462a8f4..ffd981c55 100644 --- a/cypress/e2e/item/move/listMoveMultiple.cy.ts +++ b/cypress/e2e/item/move/listMoveMultiple.cy.ts @@ -41,10 +41,6 @@ describe('Move Items in List', () => { cy.wait('@moveItems').then(({ request: { url, body } }) => { expect(body.parentId).to.equal(toItem); itemIds.forEach((movedItem) => expect(url).to.contain(movedItem)); - - itemIds.forEach((id) => { - cy.get(`${buildItemsTableRowIdAttribute(id)}`).should('not.exist'); - }); }); }); diff --git a/cypress/e2e/item/search/gridItemSearch.cy.ts b/cypress/e2e/item/search/gridItemSearch.cy.ts deleted file mode 100644 index b95f26084..000000000 --- a/cypress/e2e/item/search/gridItemSearch.cy.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { HOME_PATH, buildItemPath } from '../../../../src/config/paths'; -import { - ITEMS_GRID_NO_ITEM_ID, - ITEMS_GRID_NO_SEARCH_RESULT_ID, - ITEMS_GRID_PAGINATION_ID, - ITEM_SEARCH_INPUT_ID, - buildItemCard, - buildItemsGridPaginationButtonSelected, -} from '../../../../src/config/selectors'; -import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; -import { SAMPLE_ITEMS, generateOwnItems } from '../../../fixtures/items'; - -describe('Search Item in Grid', () => { - const { id } = SAMPLE_ITEMS.items[0]; - const child3 = SAMPLE_ITEMS.items.find((it) => it.name === 'own_item_name3'); - const child4 = SAMPLE_ITEMS.items.find((it) => it.name === 'own_item_name4'); - - it('searches in grid successfully', () => { - cy.setUpApi(SAMPLE_ITEMS); - - // visit child - cy.visit(buildItemPath(id)); - cy.switchMode(ITEM_LAYOUT_MODES.GRID); - - // should get children - cy.wait('@getChildren').then(({ response: { body } }) => { - // check item is created and displayed - for (const item of body) { - cy.get(`#${buildItemCard(item.id)}`).should('exist'); - } - }); - - // perform search - cy.get(`#${ITEM_SEARCH_INPUT_ID}`) - .type(child3.name) - .then(() => { - // should find child3 but not child4 - cy.get(`#${buildItemCard(child3.id)}`).should('exist'); - cy.get(`#${buildItemCard(child4.id)}`).should('not.exist'); - }); - - // erase search - cy.get(`#${ITEM_SEARCH_INPUT_ID}`) - .clear() - .then(() => { - // should find all children again - cy.get(`#${buildItemCard(child3.id)}`).should('exist'); - cy.get(`#${buildItemCard(child4.id)}`).should('exist'); - }); - }); - - it('displays no results found correctly', () => { - cy.setUpApi(SAMPLE_ITEMS); - - // visit child - cy.visit(buildItemPath(id)); - cy.switchMode(ITEM_LAYOUT_MODES.GRID); - - // should get children - cy.wait('@getChildren').then(({ response: { body } }) => { - // check item is created and displayed - for (const item of body) { - cy.get(`#${buildItemCard(item.id)}`).should('exist'); - } - }); - - // perform search - cy.get(`#${ITEM_SEARCH_INPUT_ID}`) - .type('some-garbage-input-that-doesnt-match-anything') - .then(() => { - // should find no results found but not empty - cy.get(`#${ITEMS_GRID_NO_SEARCH_RESULT_ID}`).should('exist'); - cy.get(`#${ITEMS_GRID_NO_ITEM_ID}`).should('not.exist'); - }); - }); - - it('displays item is empty correctly', () => { - cy.setUpApi({ items: [SAMPLE_ITEMS.items[0]] }); - - // visit child - cy.visit(buildItemPath(id)); - cy.switchMode(ITEM_LAYOUT_MODES.GRID); - - // should be empty - cy.get(`#${ITEMS_GRID_NO_ITEM_ID}`).should('exist'); - - // perform search, then clear - cy.get(`#${ITEM_SEARCH_INPUT_ID}`).type( - 'some-garbage-input-that-doesnt-match-anything', - ); - cy.get(`#${ITEM_SEARCH_INPUT_ID}`).clear(); - - // should still display empty message - cy.get(`#${ITEMS_GRID_NO_ITEM_ID}`).should('exist'); - }); - - it('resets grid pagination if num results < current page', () => { - const items = generateOwnItems(30); - cy.setUpApi({ items }); - - // visit home - cy.visit(HOME_PATH); - cy.switchMode(ITEM_LAYOUT_MODES.GRID); - - // go to page 2 - cy.get(`#${ITEMS_GRID_PAGINATION_ID} > ul > li`) - .eq(2) // leftmost li is "prev" button - .click(); - - // perform search - cy.get(`#${ITEM_SEARCH_INPUT_ID}`).type(items[0].name); - - // there should be only a single item - cy.get(`#${buildItemCard(items[0].id)}`).should('exist'); - items.slice(1).forEach((item) => { - cy.get(`#${buildItemCard(item.id)}`).should('not.exist'); - }); - - // and page number should be 1 and selected - cy.get(buildItemsGridPaginationButtonSelected(1)).should('exist'); - }); -}); diff --git a/cypress/e2e/item/search/listItemSearch.cy.ts b/cypress/e2e/item/search/listItemSearch.cy.ts deleted file mode 100644 index 173ce86bb..000000000 --- a/cypress/e2e/item/search/listItemSearch.cy.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { buildItemPath } from '../../../../src/config/paths'; -import { - ITEM_SEARCH_INPUT_ID, - buildItemsTableRowIdAttribute, -} from '../../../../src/config/selectors'; -import { SAMPLE_ITEMS } from '../../../fixtures/items'; - -describe('Search Item in Table', () => { - const { id } = SAMPLE_ITEMS.items[0]; - const child3 = SAMPLE_ITEMS.items.find((it) => it.name === 'own_item_name3'); - const child4 = SAMPLE_ITEMS.items.find((it) => it.name === 'own_item_name4'); - - beforeEach(() => { - cy.setUpApi(SAMPLE_ITEMS); - }); - - it('searches in list successfully', () => { - // visit child - cy.visit(buildItemPath(id)); - - // should get children - cy.wait('@getChildren').then(({ response: { body } }) => { - // check item is created and displayed - for (const item of body) { - cy.get(buildItemsTableRowIdAttribute(item.id)).should('exist'); - } - }); - - // perform search - cy.get(`#${ITEM_SEARCH_INPUT_ID}`) - .type(child3.name) - .then(() => { - // should find child3 but not child4 - cy.get(buildItemsTableRowIdAttribute(child3.id)).should('exist'); - cy.get(buildItemsTableRowIdAttribute(child4.id)).should('not.exist'); - }); - - // erase search - cy.get(`#${ITEM_SEARCH_INPUT_ID}`) - .clear() - .then(() => { - // should find all children again - cy.get(buildItemsTableRowIdAttribute(child3.id)).should('exist'); - cy.get(buildItemsTableRowIdAttribute(child4.id)).should('exist'); - }); - }); -}); diff --git a/cypress/e2e/item/shared/sharedItems.cy.ts b/cypress/e2e/item/shared/sharedItems.cy.ts index 9339433d0..4a824228b 100644 --- a/cypress/e2e/item/shared/sharedItems.cy.ts +++ b/cypress/e2e/item/shared/sharedItems.cy.ts @@ -1,20 +1,66 @@ +import i18n, { BUILDER_NAMESPACE } from '@/config/i18n'; import { ITEM_MENU_MOVE_BUTTON_CLASS, + NAVIGATION_ROOT_ID, + buildItemCard, buildItemMenu, buildItemMenuButtonId, } from '@/config/selectors'; +import { BUILDER } from '@/langs/constants'; import { SHARED_ITEMS_PATH } from '../../../../src/config/paths'; import ITEM_LAYOUT_MODES from '../../../../src/enums/itemLayoutModes'; import { SAMPLE_ITEMS } from '../../../fixtures/items'; import { MEMBERS } from '../../../fixtures/members'; +const translateBuilder = (key: string) => + i18n.t(key, { ns: BUILDER_NAMESPACE }); + describe('Shared Items', () => { beforeEach(() => { cy.setUpApi({ ...SAMPLE_ITEMS, currentMember: MEMBERS.BOB }); cy.visit(SHARED_ITEMS_PATH); cy.wait(5000); }); + describe('Grid', () => { + it('visit Shared Items', () => { + cy.switchMode(ITEM_LAYOUT_MODES.GRID); + + cy.wait('@getSharedItems').then(({ response: { body } }) => { + // check item is created and displayed + for (const item of body) { + cy.get(`#${buildItemCard(item.id)}`).should('exist'); + } + }); + + // visit child + const { id: childId } = SAMPLE_ITEMS.items[1]; + cy.goToItemInGrid(childId); + + // should get children + cy.wait('@getChildren').then(({ response: { body } }) => { + // check item is created and displayed + for (const item of body) { + cy.get(`#${buildItemCard(item.id)}`).should('exist'); + } + }); + + // breadcrumb navigation + cy.get(`#${NAVIGATION_ROOT_ID}`).contains( + translateBuilder(BUILDER.NAVIGATION_SHARED_ITEMS_TITLE), + ); + }); + it('move should be prevented', () => { + cy.switchMode(ITEM_LAYOUT_MODES.GRID); + const itemId = SAMPLE_ITEMS.items[1].id; + const menuSelector = `#${buildItemMenuButtonId(itemId)}`; + cy.get(menuSelector).click(); + cy.get( + `#${buildItemMenu(itemId)} .${ITEM_MENU_MOVE_BUTTON_CLASS}`, + ).should('not.exist'); + }); + }); + it('move should be prevented in list', () => { cy.switchMode(ITEM_LAYOUT_MODES.LIST); const itemId = SAMPLE_ITEMS.items[1].id; @@ -24,13 +70,4 @@ describe('Shared Items', () => { 'not.exist', ); }); - it('move should be prevented in grid', () => { - cy.switchMode(ITEM_LAYOUT_MODES.GRID); - const itemId = SAMPLE_ITEMS.items[1].id; - const menuSelector = `#${buildItemMenuButtonId(itemId)}`; - cy.get(menuSelector).click(); - cy.get(`#${buildItemMenu(itemId)} .${ITEM_MENU_MOVE_BUTTON_CLASS}`).should( - 'not.exist', - ); - }); }); diff --git a/cypress/e2e/item/view/viewFolder.cy.ts b/cypress/e2e/item/view/viewFolder.cy.ts index 68df29d91..4cab78149 100644 --- a/cypress/e2e/item/view/viewFolder.cy.ts +++ b/cypress/e2e/item/view/viewFolder.cy.ts @@ -1,40 +1,18 @@ +import i18n from '../../../../src/config/i18n'; +import { HOME_PATH, buildItemPath } from '../../../../src/config/paths'; import { - DEFAULT_ITEM_LAYOUT_MODE, - GRID_ITEMS_PER_PAGE_CHOICES, -} from '../../../../src/config/constants'; -import i18n, { BUILDER_NAMESPACE } from '../../../../src/config/i18n'; -import { - HOME_PATH, - SHARED_ITEMS_PATH, - buildItemPath, -} from '../../../../src/config/paths'; -import { - ITEMS_GRID_ITEMS_PER_PAGE_SELECT_ID, - ITEMS_GRID_ITEMS_PER_PAGE_SELECT_LABEL_ID, - ITEMS_GRID_NO_ITEM_ID, - ITEMS_GRID_PAGINATION_ID, - ITEMS_TABLE_ROW, NAVIGATION_ROOT_ID, buildItemCard, buildItemsTableRowIdAttribute, } from '../../../../src/config/selectors'; import { ITEM_LAYOUT_MODES } from '../../../../src/enums'; -import { BUILDER } from '../../../../src/langs/constants'; import { IMAGE_ITEM_DEFAULT, VIDEO_ITEM_S3 } from '../../../fixtures/files'; -import { SAMPLE_ITEMS, generateOwnItems } from '../../../fixtures/items'; +import { SAMPLE_ITEMS } from '../../../fixtures/items'; import { GRAASP_LINK_ITEM } from '../../../fixtures/links'; import { CURRENT_USER } from '../../../fixtures/members'; import { SHARED_ITEMS } from '../../../fixtures/sharedItems'; -import { - NAVIGATION_LOAD_PAUSE, - TABLE_ITEM_RENDER_TIME, -} from '../../../support/constants'; -import { ItemForTest } from '../../../support/types'; import { expectFolderViewScreenLayout } from '../../../support/viewUtils'; -const translateBuilder = (key: string) => - i18n.t(key, { ns: BUILDER_NAMESPACE }); - describe('View Folder', () => { describe('Grid', () => { beforeEach(() => { @@ -50,100 +28,6 @@ describe('View Folder', () => { i18n.changeLanguage(CURRENT_USER.extra.lang as string); }); - it('visit Home', () => { - cy.visit(HOME_PATH); - cy.switchMode(ITEM_LAYOUT_MODES.GRID); - - // should get own items - cy.wait('@getOwnItems').then(({ response: { body } }) => { - cy.wait('@getItemMemberships'); - // check item is created and displayed - for (const item of body) { - cy.get(`#${buildItemCard(item.id)}`).should('exist'); - } - }); - - // visit child - const { id: childId } = SAMPLE_ITEMS.items[0]; - cy.goToItemInGrid(childId); - - // should get children - cy.wait('@getChildren', { timeout: TABLE_ITEM_RENDER_TIME }).then( - ({ response: { body } }) => { - // check item is created and displayed - for (const item of body) { - cy.get(`#${buildItemCard(item.id)}`).should('exist'); - } - }, - ); - - // root title - cy.get(`#${NAVIGATION_ROOT_ID}`).contains( - translateBuilder(BUILDER.NAVIGATION_MY_ITEMS_TITLE), - ); - - // visit child - const { id: childChildId } = SAMPLE_ITEMS.items[2]; - cy.goToItemInGrid(childChildId); - - // expect no children - cy.get(`#${ITEMS_GRID_NO_ITEM_ID}`).should('exist'); - - // root title - cy.get(`#${NAVIGATION_ROOT_ID}`).contains( - translateBuilder(BUILDER.NAVIGATION_MY_ITEMS_TITLE), - ); - - // return parent with navigation and should display children - cy.wait(NAVIGATION_LOAD_PAUSE); - cy.goToItemWithNavigation(childId); - // should get children - cy.wait('@getChildren').then(() => { - // check item is created and displayed - for (const item of [ - SAMPLE_ITEMS.items[2], - SAMPLE_ITEMS.items[3], - SAMPLE_ITEMS.items[4], - ]) { - cy.get(`#${buildItemCard(item.id)}`).should('exist'); - } - }); - // root title - cy.get(`#${NAVIGATION_ROOT_ID}`).contains( - translateBuilder(BUILDER.NAVIGATION_MY_ITEMS_TITLE), - ); - }); - - it('visit Shared Items', () => { - cy.visit(SHARED_ITEMS_PATH); - cy.switchMode(ITEM_LAYOUT_MODES.GRID); - - // should get own items - cy.wait('@getSharedItems').then(({ response: { body } }) => { - // check item is created and displayed - for (const item of body) { - cy.get(`#${buildItemCard(item.id)}`).should('exist'); - } - }); - - // visit child - const { id: childId } = SHARED_ITEMS.items[0]; - cy.goToItemInGrid(childId); - - // should get children - cy.wait('@getChildren').then(({ response: { body } }) => { - // check item is created and displayed - for (const item of body) { - cy.get(`#${buildItemCard(item.id)}`).should('exist'); - } - }); - - // breadcrumb navigation - cy.get(`#${NAVIGATION_ROOT_ID}`).contains( - translateBuilder(BUILDER.NAVIGATION_SHARED_ITEMS_TITLE), - ); - }); - it('visit item by id', () => { const { id } = SAMPLE_ITEMS.items[0]; cy.visit(buildItemPath(id)); @@ -156,7 +40,7 @@ describe('View Folder', () => { // should get children cy.wait('@getChildren').then(({ response: { body } }) => { - // check item is created and displayed + // check all children are created and displayed for (const item of body) { cy.get(`#${buildItemCard(item.id)}`).should('exist'); } @@ -165,168 +49,58 @@ describe('View Folder', () => { // visit home cy.get(`#${NAVIGATION_ROOT_ID} [href="${HOME_PATH}"]`).click(); - // should get own items - cy.wait('@getOwnItems').then(({ response: { body } }) => { + // should get accessible items + cy.wait('@getAccessibleItems').then(({ response: { body } }) => { // check item is created and displayed - for (const item of body) { + for (const item of body.data) { cy.get(`#${buildItemCard(item.id)}`).should('exist'); } }); }); }); - describe('Grid pagination', () => { - const sampleItems = generateOwnItems(30); - - const checkGridPagination = ( - items: ItemForTest[], - itemsPerPage: number, - ) => { - const numberPages = Math.ceil(items.length / itemsPerPage); - - // for each page - for (let i = 0; i < numberPages; i += 1) { - // navigate to page - cy.get(`#${ITEMS_GRID_PAGINATION_ID} > ul > li`) - .eq(i + 1) // leftmost li is "prev" button - .click(); - // compute items that should be on this page - const shouldDisplay = items.slice( - i * itemsPerPage, - (i + 1) * itemsPerPage, - ); - // compute items that should not be on this page - const shouldNotDisplay = items.filter( - (it) => !shouldDisplay.includes(it), - ); - - shouldDisplay.forEach((item) => { - cy.get(`#${buildItemCard(item.id)}`).should('exist'); - }); - - shouldNotDisplay.forEach((item) => { - cy.get(`#${buildItemCard(item.id)}`).should('not.exist'); - }); - } - }; - - beforeEach(() => { - // create many items to be shown on Home (more than default itemsPerPage) - cy.setUpApi({ items: sampleItems }); - cy.visit(HOME_PATH); - - if (DEFAULT_ITEM_LAYOUT_MODE !== ITEM_LAYOUT_MODES.GRID) { - cy.switchMode(ITEM_LAYOUT_MODES.GRID); - } - - cy.wait('@getOwnItems'); - }); - - it('shows only items of each page', () => { - // using default items per page count - checkGridPagination(sampleItems, GRID_ITEMS_PER_PAGE_CHOICES[0]); - }); - - it('selects number of items per page', () => { - // test every possible value of itemsPerPage count - GRID_ITEMS_PER_PAGE_CHOICES.forEach((itemsPerPage, i) => { - // click on itemsPerPage select - cy.get(`#${ITEMS_GRID_ITEMS_PER_PAGE_SELECT_ID}`).click(); - - // select ith option in select popover - const popover = `ul[aria-labelledby="${ITEMS_GRID_ITEMS_PER_PAGE_SELECT_LABEL_ID}"] > li`; - cy.get(popover).eq(i).click(); - - // check pagination display with second items per page count choice - checkGridPagination(sampleItems, itemsPerPage); - }); - }); - }); - describe('List', () => { - beforeEach(() => { - cy.setUpApi({ - items: [ - ...SAMPLE_ITEMS.items, - GRAASP_LINK_ITEM, - IMAGE_ITEM_DEFAULT, - VIDEO_ITEM_S3, - ], - }); - }); - - it('visit Home', () => { - cy.visit(HOME_PATH); - - cy.switchMode(ITEM_LAYOUT_MODES.LIST); - - // should get own items - cy.wait('@getOwnItems').then(({ response: { body } }) => { - // check item is created and displayed - for (const item of body) { - cy.get(buildItemsTableRowIdAttribute(item.id)).should('exist'); - } - }); - - // visit child - const { id: childId } = SAMPLE_ITEMS.items[0]; - cy.goToItemInList(childId); - - // should get children - cy.wait('@getChildren').then(({ response: { body } }) => { - // check item is created and displayed - for (const item of body) { - cy.get(buildItemsTableRowIdAttribute(item.id)).should('exist'); - } - }); - - // visit child - const { id: childChildId } = SAMPLE_ITEMS.items[2]; - cy.goToItemInList(childChildId); - - // expect no children - cy.get(ITEMS_TABLE_ROW).should('not.exist'); - - // return parent with navigation and should display children - cy.goToItemWithNavigation(childId); - // should get children - cy.wait('@getChildren').then(({ response: { body } }) => { - // check item is created and displayed - for (const item of body) { - cy.get(buildItemsTableRowIdAttribute(item.id)).should('exist'); - } + describe('Navigation', () => { + const allItems = [ + ...SAMPLE_ITEMS.items, + GRAASP_LINK_ITEM, + IMAGE_ITEM_DEFAULT, + VIDEO_ITEM_S3, + ]; + beforeEach(() => { + cy.setUpApi({ + items: allItems, + }); }); - }); - it('visit folder by id', () => { - cy.setUpApi(SAMPLE_ITEMS); - const { id } = SAMPLE_ITEMS.items[0]; - cy.visit(buildItemPath(id)); + it('visit folder by id', () => { + const { id } = SAMPLE_ITEMS.items[0]; + cy.visit(buildItemPath(id)); - cy.switchMode(ITEM_LAYOUT_MODES.LIST); + cy.switchMode(ITEM_LAYOUT_MODES.LIST); - // should get current item - cy.wait('@getItem'); + // should get current item + cy.wait('@getItem'); - expectFolderViewScreenLayout({ item: SAMPLE_ITEMS.items[0] }); + expectFolderViewScreenLayout({ item: SAMPLE_ITEMS.items[0] }); - // should get children - cy.wait('@getChildren').then(({ response: { body } }) => { - // check item is created and displayed - for (const item of body) { - cy.get(buildItemsTableRowIdAttribute(item.id)).should('exist'); - } - }); + // should get children + cy.wait('@getChildren').then(({ response: { body } }) => { + // check all children are created and displayed + for (const item of body) { + cy.get(buildItemsTableRowIdAttribute(item.id)).should('exist'); + } + }); - // visit home - cy.get(`#${NAVIGATION_ROOT_ID} [href="${HOME_PATH}"]`).click(); + // visit home + cy.get(`#${NAVIGATION_ROOT_ID} [href="${HOME_PATH}"]`).click(); - // should get own items - cy.wait('@getOwnItems').then(({ response: { body } }) => { - // check item is created and displayed - for (const item of body) { - cy.get(buildItemsTableRowIdAttribute(item.id)).should('exist'); - } + cy.wait('@getAccessibleItems').then(({ response: { body } }) => { + // check item is created and displayed + for (const item of body.data) { + cy.get(buildItemsTableRowIdAttribute(item.id)).should('exist'); + } + }); }); }); }); diff --git a/cypress/e2e/redirection.cy.ts b/cypress/e2e/redirection.cy.ts index 79104acd9..faa1b6b1c 100644 --- a/cypress/e2e/redirection.cy.ts +++ b/cypress/e2e/redirection.cy.ts @@ -1,7 +1,7 @@ import { saveUrlForRedirection } from '@graasp/sdk'; import { REDIRECT_PATH } from '../../src/config/paths'; -import { OWNED_ITEMS_ID } from '../../src/config/selectors'; +import { ACCESSIBLE_ITEMS_TABLE_ID } from '../../src/config/selectors'; const DOMAIN = Cypress.env('REACT_APP_GRAASP_DOMAIN'); @@ -21,6 +21,6 @@ describe('Redirection', () => { cy.visit(REDIRECT_PATH); - cy.get(`#${OWNED_ITEMS_ID}`).should('be.visible'); + cy.get(`#${ACCESSIBLE_ITEMS_TABLE_ID}`).should('be.visible'); }); }); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 0da995cee..1a9b1c710 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -39,6 +39,7 @@ import { mockEditItem, mockEditItemMembershipForItem, mockEditMember, + mockGetAccessibleItems, mockGetAppData, mockGetAppLink, mockGetAppListRoute, @@ -179,6 +180,8 @@ Cypress.Commands.add( mockGetAppListRoute(APPS_LIST); + mockGetAccessibleItems(cachedItems); + mockGetOwnItems(cachedItems); mockGetSharedItems({ items: cachedItems, member: currentMember }); diff --git a/cypress/support/commands/item.ts b/cypress/support/commands/item.ts index 63b7294ac..30c591bab 100644 --- a/cypress/support/commands/item.ts +++ b/cypress/support/commands/item.ts @@ -145,7 +145,7 @@ Cypress.Commands.add( 'fillAppModal', ( { name = '', extra }, - { confirm = true, type = false, custom = false } = {}, + { confirm = true, id, type = false, custom = false } = {}, ) => { cy.fillBaseItemModal({ name }, { confirm: false }); @@ -157,7 +157,7 @@ Cypress.Commands.add( cy.fillBaseItemModal({ name }, { confirm: false }); cy.get(`#${CUSTOM_APP_URL_ID}`).type(CUSTOM_APP_URL); } else { - cy.get(`#${buildItemFormAppOptionId(name)}`).click(); + cy.get(`#${buildItemFormAppOptionId(id)}`).click(); // check name get added automatically cy.get(`#${ITEM_FORM_NAME_INPUT_ID}`).should('have.value', APP_NAME); // edit the app name diff --git a/cypress/support/createUtils.ts b/cypress/support/createUtils.ts index 50e5d771a..19f9555da 100644 --- a/cypress/support/createUtils.ts +++ b/cypress/support/createUtils.ts @@ -23,7 +23,7 @@ import { FileItemForTest } from './types'; export const createApp = ( payload: AppItemType, - options?: { confirm?: boolean; custom?: boolean }, + options?: { confirm?: boolean; custom?: boolean; id?: string }, ): void => { cy.get(`#${CREATE_ITEM_BUTTON_ID}`).click(); cy.get(`#${CREATE_ITEM_APP_ID}`).click(); diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 0921a4dc9..b43a0ae70 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -63,7 +63,12 @@ declare global { ): void; fillAppModal( payload: { name: string; extra?: AppItemExtra }, - options?: { type?: boolean; confirm?: boolean; custom?: boolean }, + options?: { + type?: boolean; + id?: string; + confirm?: boolean; + custom?: boolean; + }, ): void; fillFolderModal( arg1: { name?: string; description?: string }, diff --git a/cypress/support/server.ts b/cypress/support/server.ts index 29a41faa1..2a9614779 100644 --- a/cypress/support/server.ts +++ b/cypress/support/server.ts @@ -99,6 +99,7 @@ const { buildPostShortLinkRoute, buildPatchShortLinkRoute, buildDeleteShortLinkRoute, + buildGetAccessibleItems, } = API_ROUTES; const API_HOST = Cypress.env('API_HOST'); @@ -172,6 +173,32 @@ export const mockGetOwnItems = (items: ItemForTest[]): void => { ).as('getOwnItems'); }; +export const mockGetAccessibleItems = (items: ItemForTest[]): void => { + cy.intercept( + { + method: HttpMethod.GET, + url: new RegExp(`${API_HOST}/${buildGetAccessibleItems({}, {})}`), + }, + ({ url, reply }) => { + const params = new URL(url).searchParams; + + const page = parseInt(params.get('page') ?? '1', 10); + const pageSize = parseInt(params.get('pageSize') ?? '10', 10); + + // as { page: number; pageSize: number }; + + // warning: we don't check memberships + const root = items.filter(isRootItem); + + // todo: filter + + const result = root.slice((page - 1) * pageSize, page * pageSize); + + reply({ data: result, totalCount: root.length }); + }, + ).as('getAccessibleItems'); +}; + export const mockGetRecycledItems = ( recycledItemData: RecycledItemData[], ): void => { diff --git a/package.json b/package.json index 782b49c0e..9fae8522b 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,10 @@ "@emotion/react": "11.11.1", "@emotion/styled": "11.11.0", "@graasp/chatbox": "3.0.0", - "@graasp/query-client": "2.1.1", + "@graasp/query-client": "2.2.1", "@graasp/sdk": "3.3.0", "@graasp/translations": "1.21.1", - "@graasp/ui": "4.1.1", + "@graasp/ui": "4.2.0", "@mui/icons-material": "5.14.19", "@mui/lab": "5.0.0-alpha.151", "@mui/material": "5.14.19", @@ -129,7 +129,7 @@ "@typescript-eslint/parser": "6.15.0", "@vitejs/plugin-react": "4.2.0", "concurrently": "8.2.2", - "cypress": "13.6.0", + "cypress": "13.6.1", "cypress-localstorage-commands": "2.2.5", "env-cmd": "10.1.0", "eslint": "^8.54.0", diff --git a/src/components/SharedItems.tsx b/src/components/SharedItems.tsx index 7c352e8d4..00d3bc6d6 100644 --- a/src/components/SharedItems.tsx +++ b/src/components/SharedItems.tsx @@ -1,3 +1,4 @@ +import { Alert } from '@mui/material'; import Box from '@mui/material/Box'; import { Loader } from '@graasp/ui'; @@ -28,13 +29,18 @@ const SharedItemsLoadableContent = (): JSX.Element => { return ( + + {translateBuilder( + "You can also find the items of this page in ''My Graasp''. This page will be unavailable soon.", + )} + ); diff --git a/src/components/item/ItemContent.tsx b/src/components/item/ItemContent.tsx index e88158d91..0680222ce 100644 --- a/src/components/item/ItemContent.tsx +++ b/src/components/item/ItemContent.tsx @@ -199,7 +199,7 @@ const FolderContent = ({ // todo: not exactly correct, since you could have write rights on some child, // but it's more tedious to check permissions over all selected items ToolbarActions={enableEditing ? ItemActions : undefined} - showCreator + totalCount={children?.length} /> ); }; diff --git a/src/components/item/ItemSearch.tsx b/src/components/item/ItemSearch.tsx index f03c3c458..fb1a0fe46 100644 --- a/src/components/item/ItemSearch.tsx +++ b/src/components/item/ItemSearch.tsx @@ -27,9 +27,11 @@ export const NoItemSearchResult = (): JSX.Element => { ); }; -export const useItemSearch = ( - items?: DiscriminatedItem[], -): { +export const useItemSearch = ({ + onSearch, +}: { + onSearch?: () => void; +}): { results?: DiscriminatedItem[]; text: string; input: JSX.Element; @@ -40,12 +42,9 @@ export const useItemSearch = ( const handleSearchInput = (event: ChangeEvent<{ value: string }>) => { const text = event.target.value; setSearchText(text.toLowerCase()); + onSearch?.(); }; - const results = items?.filter( - (it) => it?.name?.toLowerCase().includes(searchText), - ); - const itemSearchInput = ( ); - return { results, text: searchText, input: itemSearchInput }; + return { text: searchText, input: itemSearchInput }; }; diff --git a/src/components/item/form/AppForm.tsx b/src/components/item/form/AppForm.tsx index bc630ab57..070ebb4fa 100644 --- a/src/components/item/form/AppForm.tsx +++ b/src/components/item/form/AppForm.tsx @@ -48,6 +48,7 @@ const AppGrid = ({ <> {dataToShow.map((ele) => ( ); + .map((i) => ); } return ( diff --git a/src/components/layout/Navigation.tsx b/src/components/layout/Navigation.tsx index 121273dc1..e91a95f62 100644 --- a/src/components/layout/Navigation.tsx +++ b/src/components/layout/Navigation.tsx @@ -56,6 +56,7 @@ const Navigator = (): JSX.Element | null => { const buildToItemPath = (id: string) => buildItemPath(id); const menu = [ + // todo: remove distinction -> not a good idea to show the whole root in arrow { name: translateBuilder(BUILDER.NAVIGATION_MY_ITEMS_TITLE), id: 'home', diff --git a/src/components/main/Home.tsx b/src/components/main/Home.tsx index 340718d4c..4e2db8f45 100644 --- a/src/components/main/Home.tsx +++ b/src/components/main/Home.tsx @@ -1,48 +1,143 @@ +import { useState } from 'react'; + +import { CheckboxProps, LinearProgress } from '@mui/material'; import Box from '@mui/material/Box'; import { Loader } from '@graasp/ui'; +import { ITEM_PAGE_SIZE } from '@/config/constants'; + import { useBuilderTranslation } from '../../config/i18n'; import { hooks } from '../../config/queryClient'; -import { HOME_ERROR_ALERT_ID, OWNED_ITEMS_ID } from '../../config/selectors'; +import { + ACCESSIBLE_ITEMS_TABLE_ID, + HOME_ERROR_ALERT_ID, +} from '../../config/selectors'; import { BUILDER } from '../../langs/constants'; import ErrorAlert from '../common/ErrorAlert'; +import { useCurrentUserContext } from '../context/CurrentUserContext'; import FileUploader from '../file/FileUploader'; import { UppyContextProvider } from '../file/UppyContext'; +import { useItemSearch } from '../item/ItemSearch'; import ItemHeader from '../item/header/ItemHeader'; import ItemActions from './ItemActions'; import Items from './Items'; +import { ItemsTableProps } from './ItemsTable'; import Main from './Main'; import NewItemButton from './NewItemButton'; +type HomeItemSortableColumn = + | 'item.name' + | 'item.type' + | 'item.created_at' + | 'item.updated_at'; + const HomeLoadableContent = (): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); - const { data: ownItems, isLoading, isError, isSuccess } = hooks.useOwnItems(); + const { data: currentMember } = useCurrentUserContext(); + const [showOnlyMe, setShowOnlyMe] = useState(false); - if (isLoading) { - return ; + const [page, setPage] = useState(1); + const [sortColumn, setSortColumn] = + useState('item.updated_at'); + const [ordering, setOrdering] = useState<'asc' | 'desc'>('desc'); + const itemSearch = useItemSearch({ onSearch: () => setPage(1) }); + const { + data: accessibleItems, + isLoading, + isFetching, + isSuccess, + } = hooks.useAccessibleItems( + { + // todo: in the future this can be any member from creators + creatorId: showOnlyMe ? currentMember?.id : undefined, + name: itemSearch.text, + sortBy: sortColumn, + ordering, + }, + // todo: adapt page size given the user window height + { page, pageSize: ITEM_PAGE_SIZE }, + ); + + const onShowOnlyMeChange: CheckboxProps['onChange'] = (e) => { + setShowOnlyMe(e.target.checked); + setPage(1); + }; + + // todo: this should be a global function but this is not applicable to other tables + // since they don't use a pagination + // with a custom table we won't need this anymore + const onSortChanged: ItemsTableProps['onSortChanged'] = (e) => { + const sortedColumn = e.columnApi + .getColumnState() + .find(({ sort }) => Boolean(sort)); + + // todo: remove this code when table is custom + if (sortedColumn) { + const { colId, sort } = sortedColumn; + if (sort) { + setOrdering(sort); + } + + // we don't sort by creator because table definition is global + // we should wait till the table is refactored + + let prop = colId; + if (colId === 'createdAt') { + prop = 'created_at'; + } + if (colId === 'updatedAt') { + prop = 'updated_at'; + } + if (['name', 'type', 'created_at', 'updated_at'].includes(prop)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + setSortColumn(`item.${prop}`); + } + } else { + setSortColumn('item.updated_at'); + setOrdering('desc'); + } + }; + + if (accessibleItems) { + return ( + + + + + , + ]} + ToolbarActions={ItemActions} + onShowOnlyMeChange={onShowOnlyMeChange} + showOnlyMe={showOnlyMe} + page={page} + setPage={setPage} + totalCount={accessibleItems.totalCount} + onSortChanged={onSortChanged} + pageSize={ITEM_PAGE_SIZE} + /> + {isFetching && ( + + + + )} + + + ); } - if (isError || !ownItems) { - return ; + if (isLoading) { + return ; } - return ( - - - - - ]} - ToolbarActions={ItemActions} - /> - - - ); + return ; }; const Home = (): JSX.Element => ( diff --git a/src/components/main/ItemCard.tsx b/src/components/main/ItemCard.tsx index ec43a2f68..16a474814 100644 --- a/src/components/main/ItemCard.tsx +++ b/src/components/main/ItemCard.tsx @@ -101,7 +101,7 @@ const ItemComponent = ({ Actions={Actions} Badges={} name={item.name} - creator={member?.name} + creator={item.creator?.name} ItemMenu={ } diff --git a/src/components/main/Items.tsx b/src/components/main/Items.tsx index 4ff51050d..6437fe98a 100644 --- a/src/components/main/Items.tsx +++ b/src/components/main/Items.tsx @@ -1,10 +1,11 @@ +import { CheckboxProps } from '@mui/material'; + import { DiscriminatedItem } from '@graasp/sdk'; import { Loader } from '@graasp/ui'; import { hooks } from '../../config/queryClient'; import { ITEM_LAYOUT_MODES } from '../../enums'; import { useLayoutContext } from '../context/LayoutContext'; -import { useItemSearch } from '../item/ItemSearch'; import { useItemsStatuses } from '../table/BadgesCellRenderer'; import ItemsGrid from './ItemsGrid'; import ItemsTable from './ItemsTable'; @@ -12,7 +13,7 @@ import ItemsTable from './ItemsTable'; const { useManyItemMemberships, useItemsTags } = hooks; type Props = { - id: string; + id?: string; items?: DiscriminatedItem[]; title: string; headerElements?: JSX.Element[]; @@ -27,9 +28,17 @@ type Props = { }; parentId?: string; showThumbnails?: boolean; - showCreator?: boolean; enableMemberships?: boolean; canMove?: boolean; + onShowOnlyMeChange?: CheckboxProps['onChange']; + showOnlyMe?: boolean; + itemSearch?: { text: string }; + page?: number; + setPage?: (p: number) => void; + // how many items exist, which can be more than the displayed items + totalCount?: number; + onSortChanged?: (e: any) => void; + pageSize?: number; }; const Items = ({ @@ -43,19 +52,24 @@ const Items = ({ parentId, defaultSortedColumn, showThumbnails = true, - showCreator = false, enableMemberships = true, canMove = true, + showOnlyMe = false, + itemSearch, + page, + setPage, + onShowOnlyMeChange, + totalCount = 0, + onSortChanged, + pageSize, }: Props): JSX.Element => { const { mode } = useLayoutContext(); - const itemSearch = useItemSearch(items); - const itemsToDisplay = itemSearch.results; - const itemIds = itemsToDisplay?.map(({ id: itemId }) => itemId); + const itemIds = items?.map(({ id: itemId }) => itemId); const { data: manyMemberships, isLoading: isMembershipsLoading } = useManyItemMemberships(enableMemberships ? itemIds : []); - const { data: itemsTags } = useItemsTags(itemsToDisplay?.map((r) => r.id)); + const { data: itemsTags } = useItemsTags(items?.map((r) => r.id)); const itemsStatuses = useItemsStatuses({ - items: itemsToDisplay, + items, itemsTags, }); @@ -70,12 +84,17 @@ const Items = ({ canMove={canMove} parentId={parentId} title={title} - items={itemsToDisplay} + items={items} manyMemberships={manyMemberships} itemsStatuses={itemsStatuses} // This enables the possiblity to display messages (item is empty, no search result) itemSearch={itemSearch} - headerElements={[itemSearch.input, ...headerElements]} + headerElements={headerElements} + onShowOnlyMeChange={onShowOnlyMeChange} + showOnlyMe={showOnlyMe} + page={page} + onPageChange={setPage} + totalCount={totalCount} /> ); case ITEM_LAYOUT_MODES.LIST: @@ -86,16 +105,22 @@ const Items = ({ actions={actions} tableTitle={title} defaultSortedColumn={defaultSortedColumn} - items={itemsToDisplay} + onSortChanged={onSortChanged} + items={items} manyMemberships={manyMemberships} itemsStatuses={itemsStatuses} - headerElements={[itemSearch.input, ...headerElements]} - isSearching={Boolean(itemSearch.text)} + headerElements={headerElements} + isSearching={Boolean(itemSearch?.text)} ToolbarActions={ToolbarActions} clickable={clickable} showThumbnails={showThumbnails} - showCreator={showCreator} canMove={canMove} + onShowOnlyMeChange={onShowOnlyMeChange} + showOnlyMe={showOnlyMe} + page={page} + setPage={setPage} + totalCount={totalCount} + pageSize={pageSize} /> ); } diff --git a/src/components/main/ItemsGrid.tsx b/src/components/main/ItemsGrid.tsx index 47e79399d..42e78880e 100644 --- a/src/components/main/ItemsGrid.tsx +++ b/src/components/main/ItemsGrid.tsx @@ -1,21 +1,11 @@ -import { useState } from 'react'; - -import { Box, Typography, styled } from '@mui/material'; +import { Box, CheckboxProps } from '@mui/material'; import Grid from '@mui/material/Grid'; -import MenuItem from '@mui/material/MenuItem'; import Pagination from '@mui/material/Pagination'; -import Select from '@mui/material/Select'; import { DiscriminatedItem, ItemMembership, ResultOf } from '@graasp/sdk'; -import { GRID_ITEMS_PER_PAGE_CHOICES } from '../../config/constants'; -import { useBuilderTranslation } from '../../config/i18n'; -import { - ITEMS_GRID_ITEMS_PER_PAGE_SELECT_ID, - ITEMS_GRID_ITEMS_PER_PAGE_SELECT_LABEL_ID, - ITEMS_GRID_PAGINATION_ID, -} from '../../config/selectors'; -import { BUILDER } from '../../langs/constants'; +import { ITEM_PAGE_SIZE } from '../../config/constants'; +import { ITEMS_GRID_PAGINATION_ID } from '../../config/selectors'; import { getMembershipsForItem } from '../../utils/membership'; import FolderDescription from '../item/FolderDescription'; import { NoItemSearchResult } from '../item/ItemSearch'; @@ -24,12 +14,6 @@ import EmptyItem from './EmptyItem'; import ItemCard from './ItemCard'; import ItemsToolbar from './ItemsToolbar'; -const StyledBox = styled(Box)(({ theme }) => ({ - position: 'absolute', - right: theme.spacing(2), - alignItems: 'center', -})); - type Props = { id?: string; items?: DiscriminatedItem[]; @@ -38,11 +22,15 @@ type Props = { title: string; itemSearch?: { text: string; - // input: PropTypes.instanceOf(ItemSearchInput), }; headerElements?: JSX.Element[]; parentId?: string; canMove?: boolean; + showOnlyMe?: boolean; + onShowOnlyMeChange?: CheckboxProps['onChange']; + totalCount?: number; + onPageChange: any; + page?: number; }; const ItemsGrid = ({ @@ -54,39 +42,24 @@ const ItemsGrid = ({ manyMemberships, itemsStatuses, parentId, + onShowOnlyMeChange, canMove = true, + showOnlyMe, + totalCount = 0, + onPageChange, + page = 1, }: Props): JSX.Element => { - const { t: translateBuilder } = useBuilderTranslation(); - const [page, setPage] = useState(1); - const [itemsPerPage, setItemsPerPage] = useState( - GRID_ITEMS_PER_PAGE_CHOICES[0], - ); - - const pagesCount = Math.ceil(items.length / itemsPerPage); - - // bugfix: since page state is independent from search, must ensure always within range - if (page !== 1 && page > pagesCount) { - setPage(1); - } - - const start = (page - 1) * itemsPerPage; - const end = start + itemsPerPage; - const itemsInPage = items.slice(start, end); - + const pagesCount = Math.ceil(Math.max(1, totalCount / ITEM_PAGE_SIZE)); const renderItems = () => { - if (!itemsInPage || !itemsInPage.length) { + if (!items?.length) { return ( - {itemSearch && itemSearch.text ? ( - - ) : ( - - )} + {itemSearch?.text ? : } ); } - return itemsInPage.map((item) => ( + return items.map((item) => ( - + {renderItems()} - - - {translateBuilder(BUILDER.ITEMS_GRID_ITEMS_PER_PAGE_TITLE)} - - - setPage(v)} + onChange={(_e, v) => onPageChange(v)} /> diff --git a/src/components/main/ItemsTable.tsx b/src/components/main/ItemsTable.tsx index c5e47689c..47cd1e983 100644 --- a/src/components/main/ItemsTable.tsx +++ b/src/components/main/ItemsTable.tsx @@ -1,6 +1,8 @@ import { useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router'; +import { CheckboxProps } from '@mui/material'; + import { DiscriminatedItem, Item, @@ -15,7 +17,12 @@ import { COMMON } from '@graasp/translations'; import { useShortenURLParams } from '@graasp/ui'; import { Table as GraaspTable } from '@graasp/ui/dist/table'; -import { CellClickedEvent, ColDef, IRowDragItem } from 'ag-grid-community'; +import { + CellClickedEvent, + ColDef, + IRowDragItem, + SortChangedEvent, +} from 'ag-grid-community'; import { ITEMS_TABLE_CONTAINER_HEIGHT } from '../../config/constants'; import i18n, { @@ -37,7 +44,7 @@ import ItemsToolbar from './ItemsToolbar'; const { useItem } = hooks; -type Props = { +export type ItemsTableProps = { id?: string; items?: DiscriminatedItem[]; manyMemberships?: ResultOf; @@ -55,8 +62,14 @@ type Props = { name?: 'desc' | 'asc' | null; }; showThumbnails?: boolean; - showCreator?: boolean; canMove?: boolean; + onShowOnlyMeChange?: CheckboxProps['onChange']; + showOnlyMe?: boolean; + page?: number; + setPage?: (p: number) => void; + totalCount?: number; + onSortChanged?: (e: SortChangedEvent) => void; + pageSize?: number; }; const ItemsTable = ({ @@ -72,9 +85,15 @@ const ItemsTable = ({ clickable = true, defaultSortedColumn, showThumbnails = true, - showCreator = false, canMove = true, -}: Props): JSX.Element => { + showOnlyMe, + onShowOnlyMeChange, + page = 1, + setPage, + totalCount, + onSortChanged, + pageSize, +}: ItemsTableProps): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const { t: translateCommon } = useCommonTranslation(); const { t: translateEnums } = useEnumsTranslation(); @@ -180,6 +199,20 @@ const ItemsTable = ({ sort: defaultSortedColumn?.name, tooltipField: 'name', }, + { + field: 'creator', + headerName: translateBuilder(BUILDER.ITEMS_TABLE_CREATOR_HEADER), + colId: 'creator', + type: 'rightAligned', + cellRenderer: MemberNameCellRenderer({ + defaultValue: translateCommon(COMMON.MEMBER_DEFAULT_NAME), + }), + cellStyle: { + display: 'flex', + justifyContent: 'end', + }, + sortable: false, + }, { field: 'status', headerName: translateBuilder(BUILDER.ITEMS_TABLE_STATUS_HEADER), @@ -236,26 +269,9 @@ const ItemsTable = ({ }, ]; - if (showCreator) { - columns.splice(2, 0, { - field: 'creator', - headerName: translateBuilder(BUILDER.ITEMS_TABLE_CREATOR_HEADER), - colId: 'creator', - type: 'rightAligned', - cellRenderer: MemberNameCellRenderer({ - defaultValue: translateCommon(COMMON.MEMBER_DEFAULT_NAME), - }), - cellStyle: { - display: 'flex', - justifyContent: 'end', - }, - sortable: false, - }); - } return columns; // eslint-disable-next-line react-hooks/exhaustive-deps }, [ - showCreator, translateBuilder, defaultSortedColumn, ActionComponent, @@ -271,9 +287,15 @@ const ItemsTable = ({ return ( <> - + {itemId && } { + setPage?.(newPage + 1); + }} countTextFunction={countTextFunction} + totalCount={totalCount} + // has to be fixed, otherwise the pagination is false on the last page + // rows can contain less for the last page + pageSize={pageSize ?? rows.length} /> ); diff --git a/src/components/main/ItemsToolbar.tsx b/src/components/main/ItemsToolbar.tsx index ce682ac62..7e05871c5 100644 --- a/src/components/main/ItemsToolbar.tsx +++ b/src/components/main/ItemsToolbar.tsx @@ -1,19 +1,47 @@ import { Stack, Typography } from '@mui/material'; +import Checkbox, { CheckboxProps } from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; + +import { useBuilderTranslation } from '@/config/i18n'; +import { ACCESSIBLE_ITEMS_ONLY_ME_ID } from '@/config/selectors'; type Props = { title: string; headerElements?: JSX.Element[]; + onShowOnlyMeChange?: CheckboxProps['onChange']; + showOnlyMe?: boolean; }; -const ItemsToolbar = ({ title, headerElements }: Props): JSX.Element => ( - - - {title} - - - {headerElements} - - -); - +const ItemsToolbar = ({ + title, + headerElements, + onShowOnlyMeChange, + showOnlyMe, +}: Props): JSX.Element => { + const { t } = useBuilderTranslation(); + return ( + <> + + + {title} + + + {headerElements} + + + {onShowOnlyMeChange && ( + + } + label={t('Show only created by me')} + /> + )} + + ); +}; export default ItemsToolbar; diff --git a/src/config/constants.ts b/src/config/constants.ts index 6e80886e0..c7a1f620b 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -168,3 +168,6 @@ export const TUTORIALS_LINK = export const SHORT_LINK_ID_MAX_LENGTH = 10; export const SHORT_LINK_API_CALL_DEBOUNCE_MS = 500; + +// todo: to remove once we dynamically compute how many items we display +export const ITEM_PAGE_SIZE = 10; diff --git a/src/config/selectors.ts b/src/config/selectors.ts index fe91d1cfa..6806ec9f2 100644 --- a/src/config/selectors.ts +++ b/src/config/selectors.ts @@ -107,8 +107,8 @@ export const CREATE_ITEM_H5P_ID = 'createItemH5P'; export const CREATE_ITEM_ETHERPAD_ID = 'createItemEtherpad'; export const ITEM_FORM_ETHERPAD_NAME_INPUT_ID = 'itemFormEtherpadNameInputId'; export const ITEM_FORM_APP_URL_ID = 'itemFormAppUrl'; -export const buildItemFormAppOptionId = (name?: string): string => - `${name?.replaceAll(/\s/g, '-')}`; +export const buildItemFormAppOptionId = (id?: string): string => + `app-option-${id}`; export const TEXT_EDITOR_CLASS = 'ql-editor'; export const buildSaveButtonId = (id: string): string => `saveButton-${id}`; export const buildCancelButtonId = (id: string): string => `cancelButton-${id}`; @@ -337,3 +337,6 @@ export const buildShortLinkPlatformTextId = ( ): string => `shortLinkPlatformText-${platform}`; export const buildShortLinkUrlTextId = (platform: ShortLinkPlatform): string => `shortLinkUrlText-${platform}`; +export const ACCESSIBLE_ITEMS_ONLY_ME_ID = 'accessibleItemsOnlyMe'; +export const ACCESSIBLE_ITEMS_TABLE_ID = 'accessibleItemsTable'; +export const ACCESSIBLE_ITEMS_NEXT_PAGE_BUTTON_SELECTOR = `#${ACCESSIBLE_ITEMS_TABLE_ID} [data-testid="KeyboardArrowRightIcon"]`; diff --git a/src/langs/constants.ts b/src/langs/constants.ts index 9e32305f9..c85bb1f69 100644 --- a/src/langs/constants.ts +++ b/src/langs/constants.ts @@ -353,4 +353,7 @@ export const BUILDER = { SHORT_LINK_MAX_CHARS_ERROR: 'SHORT_LINK_MAX_CHARS_ERROR', SHORT_LINK_INVALID_CHARS_ERROR: 'SHORT_LINK_INVALID_CHARS_ERROR', SHORT_LINK_UNKNOWN_ERROR: 'SHORT_LINK_UNKNOWN_ERROR', + // temporary message + "You can also find the items of this page in ''My Graasp''. This page will be unavailable soon.": + "You can also find the items of this page in ''My Graasp''. This page will be unavailable soon.", }; diff --git a/src/langs/en.json b/src/langs/en.json index 3bb0ce140..86eb2e24b 100644 --- a/src/langs/en.json +++ b/src/langs/en.json @@ -290,5 +290,6 @@ "SHORT_LINK_MIN_CHARS_ERROR": "The short link must at least have {{data}} chars.", "SHORT_LINK_MAX_CHARS_ERROR": "The short link must not exceed {{data}} chars.", "SHORT_LINK_INVALID_CHARS_ERROR": "The short link contains invalid chars ({{data}}).", - "SHORT_LINK_UNKNOWN_ERROR": "An unknown error occurred." + "SHORT_LINK_UNKNOWN_ERROR": "An unknown error occurred.", + "You can also find the items of this page in ''My Graasp''. This page will be unavailable soon.": "You can also find the items of this page in ''My Graasp''. This page ''Shared Items'' will be unavailable soon." } diff --git a/src/langs/fr.json b/src/langs/fr.json index fa55c65d7..ba8d2002b 100644 --- a/src/langs/fr.json +++ b/src/langs/fr.json @@ -290,5 +290,6 @@ "SHORT_LINK_MIN_CHARS_ERROR": "Le lien rapide doit au moins contenir {{data}} caractères.", "SHORT_LINK_MAX_CHARS_ERROR": "Le lien rapide ne doit pas dépasser les {{data}} caractères.", "SHORT_LINK_INVALID_CHARS_ERROR": "Le lien rapide contient des caractères non valides ({{data}}).", - "SHORT_LINK_UNKNOWN_ERROR": "Il y a une erreur inconnue." + "SHORT_LINK_UNKNOWN_ERROR": "Il y a une erreur inconnue.", + "You can also find the items of this page in ''My Graasp''. This page will be unavailable soon.": "Les éléments de cette page sont aussi disponibles dans ''Mon Graasp''. Cette page ''Eléments Partagés'' sera bientôt indisponible." } diff --git a/yarn.lock b/yarn.lock index 0ebd82220..118154efc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1172,9 +1172,9 @@ __metadata: languageName: node linkType: hard -"@graasp/query-client@npm:2.1.1": - version: 2.1.1 - resolution: "@graasp/query-client@npm:2.1.1" +"@graasp/query-client@npm:2.2.1": + version: 2.2.1 + resolution: "@graasp/query-client@npm:2.2.1" dependencies: "@graasp/sdk": "npm:3.3.0" "@graasp/translations": "npm:1.21.1" @@ -1186,7 +1186,7 @@ __metadata: uuid: "npm:9.0.1" peerDependencies: react: ^17.0.0 || ^18.0.0 - checksum: 1ecb75fd21ce3de3799088b1da0ce8ada68601527b4393d883632a4f7b1d8cbd73db673dec302ea53e7ebbf16bb83aa3257e238a5698add4abc9fb4ebd904691 + checksum: bad1c3a4163f38e45f0f64d8beb28eb6ca6fd4cf123d971be9421a6012d0d2860c71cc0c71b14bf7d46ea3afe73355a32823e037d0216caa8154905d0d408f8f languageName: node linkType: hard @@ -1212,9 +1212,9 @@ __metadata: languageName: node linkType: hard -"@graasp/ui@npm:4.1.1": - version: 4.1.1 - resolution: "@graasp/ui@npm:4.1.1" +"@graasp/ui@npm:4.2.0": + version: 4.2.0 + resolution: "@graasp/ui@npm:4.2.0" dependencies: "@graasp/sdk": "npm:3.3.0" http-status-codes: "npm:2.3.0" @@ -1235,8 +1235,8 @@ __metadata: "@mui/icons-material": ~5.11.9 || ~5.13.0 || ~5.14.0 "@mui/lab": ~5.0.0-alpha.120 "@mui/material": ~5.11.9 || ~5.13.0 || ~5.14.0 - ag-grid-community: ^28.1.1 - ag-grid-react: ^28.1.1 + ag-grid-community: 29.3.5 + ag-grid-react: 29.3.5 i18next: ^22.4.15 || ^23.0.0 react: ^17.0.2 react-dom: ^17.0.2 @@ -1249,7 +1249,7 @@ __metadata: optional: true ag-grid-react: optional: true - checksum: d1ff422d65945113393553df5b875a004086c38c8faf2c73b51e01d8372c6ce533541502e7bca68625ef6ce32f6b93da11e19c7bdcb57adbae7b5a754f76fa55 + checksum: 364a5da87b2c13296e2fc16bd464e5ea31552b614da99eabdbd137daa98c152bf3184e72ee9ec3962804dc0e096f111aa72c6f199041f245aefde40b74c79c12 languageName: node linkType: hard @@ -4200,9 +4200,9 @@ __metadata: languageName: node linkType: hard -"cypress@npm:13.6.0": - version: 13.6.0 - resolution: "cypress@npm:13.6.0" +"cypress@npm:13.6.1": + version: 13.6.1 + resolution: "cypress@npm:13.6.1" dependencies: "@cypress/request": "npm:^3.0.0" "@cypress/xvfb": "npm:^1.2.4" @@ -4249,7 +4249,7 @@ __metadata: yauzl: "npm:^2.10.0" bin: cypress: bin/cypress - checksum: 345e295c305a9574f5de433c216c9734eab5c8594bd32b7c1a74e2d71a6c7aa5a8228f7031c3fc6bd488319c0f50c906d323d00dab5c9445c43594a41af0d23d + checksum: 22e3431faf87b31f57a1afbcb92eb8f2bf366468f0a47399545af772a89acc198df2ce80c3d3169e3a82649811ee60685fbd7a512e0ec9ffb9ddbe13f21ecfd0 languageName: node linkType: hard @@ -5933,10 +5933,10 @@ __metadata: "@emotion/react": "npm:11.11.1" "@emotion/styled": "npm:11.11.0" "@graasp/chatbox": "npm:3.0.0" - "@graasp/query-client": "npm:2.1.1" + "@graasp/query-client": "npm:2.2.1" "@graasp/sdk": "npm:3.3.0" "@graasp/translations": "npm:1.21.1" - "@graasp/ui": "npm:4.1.1" + "@graasp/ui": "npm:4.2.0" "@mui/icons-material": "npm:5.14.19" "@mui/lab": "npm:5.0.0-alpha.151" "@mui/material": "npm:5.14.19" @@ -5975,7 +5975,7 @@ __metadata: ag-grid-react: "npm:29.3.5" axios: "npm:1.6.2" concurrently: "npm:8.2.2" - cypress: "npm:13.6.0" + cypress: "npm:13.6.1" cypress-localstorage-commands: "npm:2.2.5" date-fns: "npm:2.30.0" env-cmd: "npm:10.1.0"