diff --git a/cypress/e2e/navigation.cy.ts b/cypress/e2e/navigation.cy.ts index 77973b90..e3831f68 100644 --- a/cypress/e2e/navigation.cy.ts +++ b/cypress/e2e/navigation.cy.ts @@ -1,8 +1,14 @@ -import { PermissionLevel } from '@graasp/sdk'; +import { + FolderItemFactory, + LinkItemFactory, + PermissionLevel, +} from '@graasp/sdk'; -import { buildMainPath } from '@/config/paths'; +import { buildContentPagePath, buildMainPath } from '@/config/paths'; import { HOME_PAGE_PAGINATION_ID, + TREE_FALLBACK_RELOAD_BUTTON_ID, + TREE_VIEW_ID, buildHomePaginationId, buildTreeItemClass, } from '@/config/selectors'; @@ -12,7 +18,7 @@ import { FOLDER_WITH_SUBFOLDER_ITEM_AND_PARTIAL_ORDER, generateLotsOfFoldersOnHome, } from '../fixtures/items'; -import { MEMBERS } from '../fixtures/members'; +import { CURRENT_USER, MEMBERS } from '../fixtures/members'; const items = generateLotsOfFoldersOnHome({ folderCount: 20 }); const sharedItems = generateLotsOfFoldersOnHome({ @@ -49,7 +55,7 @@ describe('Navigation', () => { ); }); - it('show all folders for partial order', () => { + it('Show all folders for partial order', () => { cy.setUpApi({ items: FOLDER_WITH_SUBFOLDER_ITEM_AND_PARTIAL_ORDER.items }); const parent = FOLDER_WITH_SUBFOLDER_ITEM_AND_PARTIAL_ORDER.items[0]; cy.visit(buildMainPath({ rootId: parent.id })); @@ -60,7 +66,7 @@ describe('Navigation', () => { cy.get(`.${buildTreeItemClass(child1.id)}`).should('be.visible'); }); - it('navigate successfully when opening child as root', () => { + it('Navigate successfully when opening child as root', () => { cy.setUpApi({ items: FOLDER_WITH_SUBFOLDER_ITEM.items }); const child = FOLDER_WITH_SUBFOLDER_ITEM.items[1]; cy.visit(buildMainPath({ rootId: child.id })); @@ -73,3 +79,43 @@ describe('Navigation', () => { ); }); }); + +describe('Internal navigation', () => { + it('Open a /:rootId link works', () => { + const firstCourse = FolderItemFactory({ + name: 'Parent', + creator: CURRENT_USER, + }); + const target = FolderItemFactory({ name: 'Target', creator: CURRENT_USER }); + const url = new URL(target.id, window.location.origin).toString(); + const link = LinkItemFactory({ + name: 'Link to target', + extra: { + embeddedLink: { + url, + }, + }, + settings: { isCollapsible: false }, + parentItem: firstCourse, + creator: CURRENT_USER, + }); + cy.setUpApi({ + items: [target, firstCourse, link], + }); + cy.visit( + buildContentPagePath({ rootId: firstCourse.id, itemId: firstCourse.id }), + ); + cy.get('h2').should('contain', firstCourse.name); + cy.get(`#${link.id}`).click(); + + cy.url().should('contain', url); + // wait for page to stabilize + cy.wait(2000); + cy.get('h2').should('contain', target.name); + + // since the tree view crashes, expect the reload button + cy.get(`#${TREE_FALLBACK_RELOAD_BUTTON_ID}`).click(); + + cy.get(`#${TREE_VIEW_ID}`).should('contain', target.name); + }); +}); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index d10db4aa..95c1aec5 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -20,3 +20,14 @@ import './commands'; // Alternatively you can use CommonJS syntax: // require('./commands') + +/** + * this is here because the accessible-tree-view component crashes + * when requesting a node that is not in its tree, since it keeps a state internally + */ +// eslint-disable-next-line consistent-return +Cypress.on('uncaught:exception', (err): false | void => { + if (err.message.includes('Node with id')) { + return false; + } +}); diff --git a/src/config/selectors.ts b/src/config/selectors.ts index 85e11909..ee2f6d71 100644 --- a/src/config/selectors.ts +++ b/src/config/selectors.ts @@ -2,6 +2,7 @@ import { Platform } from '@graasp/ui'; export const MAIN_MENU_ID = 'mainMenu'; export const TREE_VIEW_ID = 'treeView'; +export const TREE_FALLBACK_RELOAD_BUTTON_ID = 'treeViewReloadButton'; export const SHOW_MORE_ITEMS_ID = 'showMoreItems'; export const HOME_NAVIGATION_STACK_ID = 'homeNavigation'; export const MY_ITEMS_ID = 'myItems'; diff --git a/src/langs/constants.ts b/src/langs/constants.ts index f4208fdf..71fb6ab1 100644 --- a/src/langs/constants.ts +++ b/src/langs/constants.ts @@ -34,6 +34,7 @@ export const PLAYER = { 'NAVIGATION_ISLAND_PINNED_BUTTON_HELPER_TEXT_READERS', NAVIGATION_ISLAND_PINNED_BUTTON_HELPER_TEXT_WRITERS: 'NAVIGATION_ISLAND_PINNED_BUTTON_HELPER_TEXT_WRITERS', + TREE_NAVIGATION_RELOAD_TEXT: 'TREE_NAVIGATION_RELOAD_TEXT', MAP_BUTTON_TEXT: 'MAP_BUTTON_TEXT', MAP_BUTTON_DISABLED_TEXT: 'MAP_BUTTON_DISABLED_TEXT', FROM_SHORTCUT_BUTTON_TEXT: 'FROM_SHORTCUT_BUTTON_TEXT', diff --git a/src/langs/en.json b/src/langs/en.json index 8ca23b8e..44546e04 100644 --- a/src/langs/en.json +++ b/src/langs/en.json @@ -29,6 +29,7 @@ "NAVIGATION_ISLAND_CHAT_BUTTON_HELPER_TEXT_WRITERS": "To enable the chatbox, go in the item settings in the Builder and activate the 'Show Chat in Player' setting.", "NAVIGATION_ISLAND_PINNED_BUTTON_HELPER_TEXT_READERS": "There are no items pinned for this page.", "NAVIGATION_ISLAND_PINNED_BUTTON_HELPER_TEXT_WRITERS": "To add items to the pinned section, set them as pinned in the Builder interface.", + "TREE_NAVIGATION_RELOAD_TEXT": "Reload me!", "MAP_BUTTON_TEXT": "See {{name}} on Map", "MAP_BUTTON_DISABLED_TEXT": "This item does not have a geolocation", "FROM_SHORTCUT_BUTTON_TEXT": "Back to {{name}}" diff --git a/src/modules/navigation/tree/TreeErrorBoundary.tsx b/src/modules/navigation/tree/TreeErrorBoundary.tsx new file mode 100644 index 00000000..075fa0aa --- /dev/null +++ b/src/modules/navigation/tree/TreeErrorBoundary.tsx @@ -0,0 +1,22 @@ +import { Link } from 'react-router-dom'; + +import { Button } from '@mui/material'; + +import { usePlayerTranslation } from '@/config/i18n'; +import { TREE_FALLBACK_RELOAD_BUTTON_ID } from '@/config/selectors'; +import { PLAYER } from '@/langs/constants'; + +const TreeErrorBoundary = (): JSX.Element => { + const { t } = usePlayerTranslation(); + return ( + + ); +}; +export default TreeErrorBoundary; diff --git a/src/modules/navigation/tree/TreeView.tsx b/src/modules/navigation/tree/TreeView.tsx index 5ee550d2..b5d50782 100644 --- a/src/modules/navigation/tree/TreeView.tsx +++ b/src/modules/navigation/tree/TreeView.tsx @@ -21,6 +21,7 @@ import { hooks } from '@/config/queryClient'; import { ItemMetaData, getItemTree } from '@/utils/tree'; import Node from './Node'; +import TreeErrorBoundary from './TreeErrorBoundary'; type Props = { id: string; @@ -95,7 +96,7 @@ const TreeView = ({ : []; return ( - hello

}> + }>