Skip to content

Commit

Permalink
feat: simplify Library Home Page (v2) (#1443)
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielVZ96 authored Nov 7, 2024
1 parent d99e3f0 commit 979c69b
Show file tree
Hide file tree
Showing 28 changed files with 439 additions and 1,093 deletions.
140 changes: 13 additions & 127 deletions src/library-authoring/LibraryAuthoringPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,40 +42,12 @@ const returnEmptyResult = (_url, req) => {
// We have to replace the query (search keywords) in the mock results with the actual query,
// because otherwise we may have an inconsistent state that causes more queries and unexpected results.
mockEmptyResult.results[0].query = query;
mockEmptyResult.results[2].query = query;
// And fake the required '_formatted' fields; it contains the highlighting <mark>...</mark> around matched words
// eslint-disable-next-line no-underscore-dangle, no-param-reassign
mockEmptyResult.results[0]?.hits.forEach((hit) => { hit._formatted = { ...hit }; });
// eslint-disable-next-line no-underscore-dangle, no-param-reassign
mockEmptyResult.results[2]?.hits.forEach((hit) => { hit._formatted = { ...hit }; });
return mockEmptyResult;
};

/**
* Returns 2 components from the search query.
* This lets us test that the StudioHome "View All" button is hidden when a
* low number of search results are shown (<=4 by default).
*/
const returnLowNumberResults = (_url, req) => {
const requestData = JSON.parse(req.body?.toString() ?? '');
const query = requestData?.queries[0]?.q ?? '';
const newMockResult = { ...mockResult };
// We have to replace the query (search keywords) in the mock results with the actual query,
// because otherwise we may have an inconsistent state that causes more queries and unexpected results.
newMockResult.results[0].query = query;
// Limit number of results to just 2
newMockResult.results[0].hits = mockResult.results[0]?.hits.slice(0, 2);
newMockResult.results[2].hits = mockResult.results[2]?.hits.slice(0, 2);
newMockResult.results[0].estimatedTotalHits = 2;
newMockResult.results[2].estimatedTotalHits = 2;
// And fake the required '_formatted' fields; it contains the highlighting <mark>...</mark> around matched words
// eslint-disable-next-line no-underscore-dangle, no-param-reassign
newMockResult.results[0]?.hits.forEach((hit) => { hit._formatted = { ...hit }; });
// eslint-disable-next-line no-underscore-dangle, no-param-reassign
newMockResult.results[2]?.hits.forEach((hit) => { hit._formatted = { ...hit }; });
return newMockResult;
};

const path = '/library/:libraryId/*';
const libraryTitle = mockContentLibrary.libraryData.title;

Expand Down Expand Up @@ -136,35 +108,25 @@ describe('<LibraryAuthoringPage />', () => {

expect(screen.queryByText('You have not added any content to this library yet.')).not.toBeInTheDocument();

// "Recently Modified" header + sort shown
expect(screen.getAllByText('Recently Modified').length).toEqual(2);
expect(screen.getByText('Collections (6)')).toBeInTheDocument();
expect(screen.getByText('Components (10)')).toBeInTheDocument();
expect(screen.getAllByText('Recently Modified').length).toEqual(1);
expect((await screen.findAllByText('Introduction to Testing'))[0]).toBeInTheDocument();

// Navigate to the components tab
fireEvent.click(screen.getByRole('tab', { name: 'Components' }));
// "Recently Modified" default sort shown
expect(screen.getAllByText('Recently Modified').length).toEqual(1);
expect(screen.queryByText('Collections (6)')).not.toBeInTheDocument();
expect(screen.queryByText('Components (10)')).not.toBeInTheDocument();

// Navigate to the collections tab
fireEvent.click(screen.getByRole('tab', { name: 'Collections' }));
// "Recently Modified" default sort shown
expect(screen.getAllByText('Recently Modified').length).toEqual(1);
expect(screen.queryByText('Collections (6)')).not.toBeInTheDocument();
expect(screen.queryByText('Components (10)')).not.toBeInTheDocument();
expect(screen.queryByText('There are 10 components in this library')).not.toBeInTheDocument();
expect((await screen.findAllByText('Collection 1'))[0]).toBeInTheDocument();

// Go back to Home tab
// This step is necessary to avoid the url change leak to other tests
fireEvent.click(screen.getByRole('tab', { name: 'Home' }));
// "Recently Modified" header + sort shown
expect(screen.getAllByText('Recently Modified').length).toEqual(2);
expect(screen.getByText('Collections (6)')).toBeInTheDocument();
expect(screen.getByText('Components (10)')).toBeInTheDocument();
fireEvent.click(screen.getByRole('tab', { name: 'All Content' }));
expect(screen.getAllByText('Recently Modified').length).toEqual(1);
});

it('shows a library without components and collections', async () => {
Expand All @@ -188,7 +150,7 @@ describe('<LibraryAuthoringPage />', () => {
fireEvent.click(cancelButton);
expect(collectionModalHeading).not.toBeInTheDocument();

fireEvent.click(screen.getByRole('tab', { name: 'Home' }));
fireEvent.click(screen.getByRole('tab', { name: 'All Content' }));
expect(screen.getByText('You have not added any content to this library yet.')).toBeInTheDocument();

const addComponentButton = screen.getByRole('button', { name: /add component/i });
Expand Down Expand Up @@ -246,7 +208,7 @@ describe('<LibraryAuthoringPage />', () => {

// Go back to Home tab
// This step is necessary to avoid the url change leak to other tests
fireEvent.click(screen.getByRole('tab', { name: 'Home' }));
fireEvent.click(screen.getByRole('tab', { name: 'All Content' }));
});

it('should open and close new content sidebar', async () => {
Expand Down Expand Up @@ -328,68 +290,6 @@ describe('<LibraryAuthoringPage />', () => {
expect(manageAccess).not.toBeInTheDocument();
});

it('show the "View All" button when viewing library with many components', async () => {
await renderLibraryPage();

expect(screen.getByText('Content library')).toBeInTheDocument();
expect((await screen.findAllByText(libraryTitle))[0]).toBeInTheDocument();

// "Recently Modified" header + sort shown
await waitFor(() => { expect(screen.getAllByText('Recently Modified').length).toEqual(2); });
expect(screen.getByText('Collections (6)')).toBeInTheDocument();
expect(screen.getByText('Components (10)')).toBeInTheDocument();
expect(screen.getAllByText('Introduction to Testing')[0]).toBeInTheDocument();
expect(screen.queryByText('You have not added any content to this library yet.')).not.toBeInTheDocument();

// There should be two "View All" button, since the Components and Collections count
// are above the preview limit (4)
expect(screen.getAllByText('View All').length).toEqual(2);

// Clicking on first "View All" button should navigate to the Collections tab
fireEvent.click(screen.getAllByText('View All')[0]);
// "Recently Modified" default sort shown
expect(screen.getAllByText('Recently Modified').length).toEqual(1);
expect(screen.queryByText('Collections (6)')).not.toBeInTheDocument();
expect(screen.queryByText('Components (10)')).not.toBeInTheDocument();
expect(screen.getByText('Collection 1')).toBeInTheDocument();

fireEvent.click(screen.getByRole('tab', { name: 'Home' }));
// Clicking on second "View All" button should navigate to the Components tab
fireEvent.click(screen.getAllByText('View All')[1]);
// "Recently Modified" default sort shown
expect(screen.getAllByText('Recently Modified').length).toEqual(1);
expect(screen.queryByText('Collections (6)')).not.toBeInTheDocument();
expect(screen.queryByText('Components (10)')).not.toBeInTheDocument();
expect(screen.getAllByText('Introduction to Testing')[0]).toBeInTheDocument();

// Go back to Home tab
// This step is necessary to avoid the url change leak to other tests
fireEvent.click(screen.getByRole('tab', { name: 'Home' }));
// "Recently Modified" header + sort shown
expect(screen.getAllByText('Recently Modified').length).toEqual(2);
expect(screen.getByText('Collections (6)')).toBeInTheDocument();
expect(screen.getByText('Components (10)')).toBeInTheDocument();
});

it('should not show the "View All" button when viewing library with low number of components', async () => {
fetchMock.post(searchEndpoint, returnLowNumberResults, { overwriteRoutes: true });
await renderLibraryPage();

expect(screen.getByText('Content library')).toBeInTheDocument();
expect((await screen.findAllByText(libraryTitle))[0]).toBeInTheDocument();

// "Recently Modified" header + sort shown
await waitFor(() => { expect(screen.getAllByText('Recently Modified').length).toEqual(2); });
expect(screen.getByText('Collections (2)')).toBeInTheDocument();
expect(screen.getByText('Components (2)')).toBeInTheDocument();
expect(screen.getAllByText('Introduction to Testing')[0]).toBeInTheDocument();
expect(screen.queryByText('You have not added any content to this library yet.')).not.toBeInTheDocument();

// There should not be any "View All" button on page since Components count
// is less than the preview limit (4)
expect(screen.queryByText('View All')).not.toBeInTheDocument();
});

it('sorts library components', async () => {
await renderLibraryPage();

Expand Down Expand Up @@ -444,7 +344,7 @@ describe('<LibraryAuthoringPage />', () => {

// Re-selecting the previous sort option resets sort to default "Recently Modified"
await testSortOption('Recently Published', 'modified:desc', true);
expect(screen.getAllByText('Recently Modified').length).toEqual(3);
expect(screen.getAllByText('Recently Modified').length).toEqual(2);

// Enter a keyword into the search box
const searchBox = screen.getByRole('searchbox');
Expand All @@ -467,7 +367,6 @@ describe('<LibraryAuthoringPage />', () => {
expect(mockResult0.display_name).toStrictEqual(displayName);
await renderLibraryPage();

// Click on the first component. It should appear twice, in both "Recently Modified" and "Components"
fireEvent.click((await screen.findAllByText(displayName))[0]);

const sidebar = screen.getByTestId('library-sidebar');
Expand Down Expand Up @@ -579,7 +478,7 @@ describe('<LibraryAuthoringPage />', () => {
}

// Validate click on Problem type
const problemMenu = screen.getByText('Problem');
const problemMenu = screen.getAllByText('Problem')[0];
expect(problemMenu).toBeInTheDocument();
fireEvent.click(problemMenu);
await waitFor(() => {
Expand Down Expand Up @@ -647,7 +546,8 @@ describe('<LibraryAuthoringPage />', () => {
expect(screen.getByText(/add content/i)).toBeInTheDocument();

// Open New collection Modal
const newCollectionButton = screen.getAllByRole('button', { name: /collection/i })[4];
const sidebar = screen.getByTestId('library-sidebar');
const newCollectionButton = within(sidebar).getAllByRole('button', { name: /collection/i })[0];
fireEvent.click(newCollectionButton);
const collectionModalHeading = await screen.findByRole('heading', { name: /new collection/i });
expect(collectionModalHeading).toBeInTheDocument();
Expand Down Expand Up @@ -691,7 +591,8 @@ describe('<LibraryAuthoringPage />', () => {
expect(screen.getByText(/add content/i)).toBeInTheDocument();

// Open New collection Modal
const newCollectionButton = screen.getAllByRole('button', { name: /collection/i })[4];
const sidebar = screen.getByTestId('library-sidebar');
const newCollectionButton = within(sidebar).getAllByRole('button', { name: /collection/i })[0];
fireEvent.click(newCollectionButton);
const collectionModalHeading = await screen.findByRole('heading', { name: /new collection/i });
expect(collectionModalHeading).toBeInTheDocument();
Expand Down Expand Up @@ -724,7 +625,8 @@ describe('<LibraryAuthoringPage />', () => {
expect(screen.getByText(/add content/i)).toBeInTheDocument();

// Open New collection Modal
const newCollectionButton = screen.getAllByRole('button', { name: /collection/i })[4];
const sidebar = screen.getByTestId('library-sidebar');
const newCollectionButton = within(sidebar).getAllByRole('button', { name: /collection/i })[0];
fireEvent.click(newCollectionButton);
const collectionModalHeading = await screen.findByRole('heading', { name: /new collection/i });
expect(collectionModalHeading).toBeInTheDocument();
Expand All @@ -739,22 +641,6 @@ describe('<LibraryAuthoringPage />', () => {
fireEvent.click(createButton);
});

it('shows both components and collections in recently modified section', async () => {
await renderLibraryPage();

expect(await screen.findByText('Content library')).toBeInTheDocument();
expect((await screen.findAllByText(libraryTitle))[0]).toBeInTheDocument();

// "Recently Modified" header + sort shown
expect(screen.getAllByText('Recently Modified').length).toEqual(2);
const recentModifiedContainer = (await screen.findAllByText('Recently Modified'))[1].parentElement?.parentElement?.parentElement;
expect(recentModifiedContainer).toBeTruthy();

const container = within(recentModifiedContainer!);
expect(container.queryAllByText('Text').length).toBeGreaterThan(0);
expect(container.queryAllByText('Collection').length).toBeGreaterThan(0);
});

it('shows a single block when usageKey query param is set', async () => {
render(<LibraryLayout />, {
path,
Expand Down
52 changes: 18 additions & 34 deletions src/library-authoring/LibraryAuthoringPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,35 +35,11 @@ import {
SearchKeywordsField,
SearchSortWidget,
} from '../search-manager';
import LibraryComponents from './components/LibraryComponents';
import LibraryCollections from './collections/LibraryCollections';
import LibraryHome from './LibraryHome';
import LibraryContent, { ContentType } from './LibraryContent';
import { LibrarySidebar } from './library-sidebar';
import { SidebarBodyComponentId, useLibraryContext } from './common/context';
import messages from './messages';

enum TabList {
home = '',
components = 'components',
collections = 'collections',
}

interface TabContentProps {
eventKey: string;
handleTabChange: (key: string) => void;
}

const TabContent = ({ eventKey, handleTabChange }: TabContentProps) => {
switch (eventKey) {
case TabList.components:
return <LibraryComponents variant="full" />;
case TabList.collections:
return <LibraryCollections variant="full" />;
default:
return <LibraryHome tabList={TabList} handleTabChange={handleTabChange} />;
}
};

const HeaderActions = () => {
const intl = useIntl();
const {
Expand Down Expand Up @@ -162,15 +138,15 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage
openInfoSidebar,
} = useLibraryContext();

const [activeKey, setActiveKey] = useState<string | undefined>('');
const [activeKey, setActiveKey] = useState<ContentType | undefined>(ContentType.home);

useEffect(() => {
const currentPath = location.pathname.split('/').pop();

if (componentPickerMode || currentPath === libraryId || currentPath === '') {
setActiveKey(TabList.home);
} else if (currentPath && currentPath in TabList) {
setActiveKey(TabList[currentPath]);
setActiveKey(ContentType.home);
} else if (currentPath && currentPath in ContentType) {
setActiveKey(ContentType[currentPath]);
}
}, [location.pathname]);

Expand Down Expand Up @@ -203,7 +179,7 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage
return <NotFoundAlert />;
}

const handleTabChange = (key: string) => {
const handleTabChange = (key: ContentType) => {
setActiveKey(key);
if (!componentPickerMode) {
navigate({
Expand Down Expand Up @@ -235,6 +211,14 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage
extraFilter.push('last_published IS NOT NULL');
}

const activeTypeFilters = {
components: 'NOT type = "collection"',
collections: 'type = "collection"',
};
if (activeKey !== ContentType.home) {
extraFilter.push(activeTypeFilters[activeKey]);
}

return (
<div className="d-flex">
<div className="flex-grow-1">
Expand Down Expand Up @@ -275,11 +259,11 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage
onSelect={handleTabChange}
className="my-3"
>
<Tab eventKey={TabList.home} title={intl.formatMessage(messages.homeTab)} />
<Tab eventKey={TabList.components} title={intl.formatMessage(messages.componentsTab)} />
<Tab eventKey={TabList.collections} title={intl.formatMessage(messages.collectionsTab)} />
<Tab eventKey={ContentType.home} title={intl.formatMessage(messages.homeTab)} />
<Tab eventKey={ContentType.components} title={intl.formatMessage(messages.componentsTab)} />
<Tab eventKey={ContentType.collections} title={intl.formatMessage(messages.collectionsTab)} />
</Tabs>
<TabContent eventKey={activeKey} handleTabChange={handleTabChange} />
<LibraryContent contentType={activeKey} />
</SearchContextProvider>
</Container>
{!componentPickerMode && <StudioFooter containerProps={{ size: undefined }} />}
Expand Down
Loading

0 comments on commit 979c69b

Please sign in to comment.