-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[temp] feat: add detail taxonomy page #5
Changes from 1 commit
d5cc567
09f908b
f61f742
02a683f
05e90b5
2cfde7d
4a3d1a1
3dac6aa
78eb512
7c7b3cd
56dbdd2
e41efba
2fbb490
3378c8e
7c7ea1f
1ee80b6
ce9db57
14e3c25
416ac4f
2804f38
9c582ed
9df1699
0fdaa07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { StudioFooter } from '@edx/frontend-component-footer'; | ||
import { Outlet } from 'react-router-dom'; | ||
|
||
import Header from '../header'; | ||
|
||
const TaxonomyLayout = () => ( | ||
<div className="bg-light-400"> | ||
<Header isHiddenMainMenu /> | ||
<Outlet /> | ||
<StudioFooter /> | ||
</div> | ||
); | ||
|
||
export default TaxonomyLayout; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import React from 'react'; | ||
import { IntlProvider } from '@edx/frontend-platform/i18n'; | ||
import { initializeMockApp } from '@edx/frontend-platform'; | ||
import { AppProvider } from '@edx/frontend-platform/react'; | ||
import { render } from '@testing-library/react'; | ||
|
||
import initializeStore from '../store'; | ||
import TaxonomyLayout from './TaxonomyLayout'; | ||
|
||
let store; | ||
|
||
jest.mock('../header', () => jest.fn(() => <div data-testid="mock-header" />)); | ||
jest.mock('@edx/frontend-component-footer', () => ({ | ||
StudioFooter: jest.fn(() => <div data-testid="mock-footer" />), | ||
})); | ||
jest.mock('react-router-dom', () => ({ | ||
...jest.requireActual('react-router-dom'), | ||
Outlet: jest.fn(() => <div data-testid="mock-content" />), | ||
})); | ||
|
||
const RootWrapper = () => ( | ||
<AppProvider store={store}> | ||
<IntlProvider locale="en" messages={{}}> | ||
<TaxonomyLayout /> | ||
</IntlProvider> | ||
</AppProvider> | ||
); | ||
|
||
describe('<TaxonomyLayout />', async () => { | ||
beforeEach(async () => { | ||
initializeMockApp({ | ||
authenticatedUser: { | ||
userId: 3, | ||
username: 'abc123', | ||
administrator: true, | ||
roles: [], | ||
}, | ||
}); | ||
store = initializeStore(); | ||
}); | ||
|
||
it('should render page correctly', async () => { | ||
const { getByTestId } = render(<RootWrapper />); | ||
expect(getByTestId('mock-header')).toBeInTheDocument(); | ||
expect(getByTestId('mock-content')).toBeInTheDocument(); | ||
expect(getByTestId('mock-footer')).toBeInTheDocument(); | ||
}); | ||
}); | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
// eslint-disable-next-line import/prefer-default-export | ||
export { default as TaxonomyListPage } from './TaxonomyListPage'; | ||
export { default as TaxonomyLayout } from './TaxonomyLayout'; | ||
export { TaxonomyDetailPage } from './taxonomy-detail'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// ts-check | ||
import { useIntl } from '@edx/frontend-platform/i18n'; | ||
import { | ||
DataTable, | ||
} from '@edx/paragon'; | ||
import _ from 'lodash'; | ||
import Proptypes from 'prop-types'; | ||
import { useState } from 'react'; | ||
|
||
import messages from './messages'; | ||
import { useTagListDataResponse, useTagListDataStatus } from './data/selectors'; | ||
|
||
const TagListTable = ({ taxonomyId }) => { | ||
const intl = useIntl(); | ||
|
||
const [options, setOptions] = useState({ | ||
pageIndex: 0, | ||
}); | ||
|
||
const useTagListData = () => { | ||
const { isError, isFetched, isLoading } = useTagListDataStatus(taxonomyId, options); | ||
const tagList = useTagListDataResponse(taxonomyId, options); | ||
return { | ||
isError, | ||
isFetched, | ||
isLoading, | ||
tagList, | ||
}; | ||
}; | ||
|
||
const { tagList, isLoading } = useTagListData(); | ||
|
||
const fetchData = (args) => { | ||
if (!_.isEqual(args, options)) { | ||
setOptions({ ...args }); | ||
} | ||
}; | ||
|
||
return ( | ||
<DataTable | ||
isLoading={isLoading} | ||
isPaginated | ||
manualPagination | ||
fetchData={fetchData} | ||
data={tagList?.results || []} | ||
itemCount={tagList?.count || 0} | ||
pageCount={tagList?.numPages || 0} | ||
initialState={options} | ||
columns={[ | ||
{ | ||
Header: intl.formatMessage(messages.tagListColumnValueHeader), | ||
accessor: 'value', | ||
}, | ||
]} | ||
> | ||
<DataTable.TableControlBar /> | ||
<DataTable.Table /> | ||
<DataTable.EmptyTable content={intl.formatMessage(messages.noResultsFoundMessage)} /> | ||
<DataTable.TableFooter /> | ||
</DataTable> | ||
); | ||
}; | ||
|
||
TagListTable.propTypes = { | ||
taxonomyId: Proptypes.string.isRequired, | ||
}; | ||
|
||
export default TagListTable; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import React from 'react'; | ||
import { IntlProvider } from '@edx/frontend-platform/i18n'; | ||
import { initializeMockApp } from '@edx/frontend-platform'; | ||
import { AppProvider } from '@edx/frontend-platform/react'; | ||
import { render } from '@testing-library/react'; | ||
|
||
import { useTagListData } from './data/api'; | ||
import initializeStore from '../../store'; | ||
import TagListTable from './TagListTable'; | ||
|
||
let store; | ||
|
||
jest.mock('./data/api', () => ({ | ||
useTagListData: jest.fn(), | ||
})); | ||
|
||
const RootWrapper = () => ( | ||
<AppProvider store={store}> | ||
<IntlProvider locale="en" messages={{}}> | ||
<TagListTable taxonomyId="1" /> | ||
</IntlProvider> | ||
</AppProvider> | ||
); | ||
|
||
describe('<TagListPage />', async () => { | ||
beforeEach(async () => { | ||
initializeMockApp({ | ||
authenticatedUser: { | ||
userId: 3, | ||
username: 'abc123', | ||
administrator: true, | ||
roles: [], | ||
}, | ||
}); | ||
store = initializeStore(); | ||
}); | ||
|
||
it('shows the spinner before the query is complete', async () => { | ||
useTagListData.mockReturnValue({ | ||
isLoading: true, | ||
isFetched: false, | ||
}); | ||
const { getByRole } = render(<RootWrapper />); | ||
const spinner = getByRole('status'); | ||
expect(spinner.textContent).toEqual('loading'); | ||
}); | ||
|
||
it('should render page correctly', async () => { | ||
useTagListData.mockReturnValue({ | ||
isSuccess: true, | ||
isFetched: true, | ||
isError: false, | ||
data: { | ||
count: 3, | ||
numPages: 1, | ||
results: [ | ||
{ value: 'Tag 1' }, | ||
{ value: 'Tag 2' }, | ||
{ value: 'Tag 3' }, | ||
], | ||
}, | ||
}); | ||
const { getAllByRole } = render(<RootWrapper />); | ||
const rows = getAllByRole('row'); | ||
expect(rows.length).toBe(3 + 1); // 3 items plus header | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// @ts-check | ||
import { useQuery } from '@tanstack/react-query'; | ||
import { camelCaseObject, getConfig } from '@edx/frontend-platform'; | ||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; | ||
|
||
const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; | ||
const getTagListApiUrl = (taxonomyId, page) => new URL( | ||
`api/content_tagging/v1/taxonomies/${taxonomyId}/tags/?page=${page + 1}`, | ||
getApiBaseUrl(), | ||
).href; | ||
|
||
// ToDo: fix types | ||
/** | ||
* @param {number} taxonomyId | ||
* @param {import('./types.mjs').QueryOptions} options | ||
* @returns {import('@tanstack/react-query').UseQueryResult<import('./types.mjs').TagListData>} | ||
*/ // eslint-disable-next-line import/prefer-default-export | ||
export const useTagListData = (taxonomyId, options) => { | ||
const { pageIndex } = options; | ||
return useQuery({ | ||
queryKey: ['tagList', taxonomyId, pageIndex], | ||
queryFn: () => getAuthenticatedHttpClient().get(getTagListApiUrl(taxonomyId, pageIndex)) | ||
.then((response) => response.data) | ||
.then(camelCaseObject), | ||
}); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { useQuery } from '@tanstack/react-query'; | ||
import { | ||
useTagListData, | ||
} from './api'; | ||
|
||
const mockHttpClient = { | ||
get: jest.fn(), | ||
}; | ||
|
||
jest.mock('@tanstack/react-query', () => ({ | ||
useQuery: jest.fn(), | ||
})); | ||
|
||
jest.mock('@edx/frontend-platform/auth', () => ({ | ||
getAuthenticatedHttpClient: jest.fn(() => mockHttpClient), | ||
})); | ||
|
||
describe('useTagListData', () => { | ||
it('should call useQuery with the correct parameters', () => { | ||
useTagListData('1', { pageIndex: 3 }); | ||
|
||
expect(useQuery).toHaveBeenCalledWith({ | ||
queryKey: ['tagList', '1', 3], | ||
queryFn: expect.any(Function), | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// @ts-check | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rpenido From what I understand from here, these would not be considered selectors in the context of the other MFE modules. To avoid colliding with that context is why I have placed functions like this in |
||
import { | ||
useTagListData, | ||
} from './api'; | ||
|
||
/* eslint-disable max-len */ | ||
/** | ||
* @param {number} taxonomyId | ||
* @param {import("./types.mjs").QueryOptions} options | ||
* @returns {Pick<import('@tanstack/react-query').UseQueryResult, "error" | "isError" | "isFetched" | "isLoading" | "isSuccess" >} | ||
*/ /* eslint-enable max-len */ | ||
export const useTagListDataStatus = (taxonomyId, options) => { | ||
const { | ||
error, | ||
isError, | ||
isFetched, | ||
isLoading, | ||
isSuccess, | ||
} = useTagListData(taxonomyId, options); | ||
return { | ||
error, | ||
isError, | ||
isFetched, | ||
isLoading, | ||
isSuccess, | ||
}; | ||
}; | ||
|
||
// ToDo: fix types | ||
/** | ||
* @param {number} taxonomyId | ||
* @param {import("./types.mjs").QueryOptions} options | ||
* @returns {import("./types.mjs").TagListData | undefined} | ||
*/ | ||
export const useTagListDataResponse = (taxonomyId, options) => { | ||
const { isSuccess, data } = useTagListData(taxonomyId, options); | ||
if (isSuccess) { | ||
return data; | ||
} | ||
|
||
return undefined; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this test is adding anything. There is no logic in this component so I think this file can be entirely skipped.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It only helps the code coverage. If this metric is not an issue here, let me know, and I remove it!