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 (
-