fix: allow user provided value if can auto-create orgs [FC-0076] (#1582)
Allows Content Authors to auto-create an organization when creating a library, if auto-creating orgs is allowed by the platform.
This commit is contained in:
@@ -1,18 +1,21 @@
|
||||
import React from 'react';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { fireEvent, render, waitFor } from '@testing-library/react';
|
||||
import type MockAdapter from 'axios-mock-adapter';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import initializeStore from '../../store';
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
initializeMocks,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '../../testUtils';
|
||||
import { studioHomeMock } from '../../studio-home/__mocks__';
|
||||
import { getStudioHomeApiUrl } from '../../studio-home/data/api';
|
||||
import { getApiWaffleFlagsUrl } from '../../data/api';
|
||||
import { CreateLibrary } from '.';
|
||||
import { getContentLibraryV2CreateApiUrl } from './data/api';
|
||||
|
||||
let store;
|
||||
const mockNavigate = jest.fn();
|
||||
let axiosMock: MockAdapter;
|
||||
|
||||
@@ -29,66 +32,41 @@ jest.mock('../../generic/data/apiHooks', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const RootWrapper = () => (
|
||||
<AppProvider store={store}>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<CreateLibrary />
|
||||
</QueryClientProvider>
|
||||
</IntlProvider>
|
||||
</AppProvider>
|
||||
);
|
||||
|
||||
describe('<CreateLibrary />', () => {
|
||||
beforeEach(() => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
store = initializeStore();
|
||||
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
axiosMock = initializeMocks().axiosMock;
|
||||
axiosMock
|
||||
.onGet(getApiWaffleFlagsUrl(undefined))
|
||||
.reply(200, {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
axiosMock.restore();
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
test('call api data with correct data', async () => {
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock);
|
||||
axiosMock.onPost(getContentLibraryV2CreateApiUrl()).reply(200, {
|
||||
id: 'library-id',
|
||||
});
|
||||
|
||||
const { getByRole } = render(<RootWrapper />);
|
||||
render(<CreateLibrary />);
|
||||
|
||||
const titleInput = getByRole('textbox', { name: /library name/i });
|
||||
const titleInput = await screen.findByRole('textbox', { name: /library name/i });
|
||||
userEvent.click(titleInput);
|
||||
userEvent.type(titleInput, 'Test Library Name');
|
||||
|
||||
const orgInput = getByRole('combobox', { name: /organization/i });
|
||||
const orgInput = await screen.findByRole('combobox', { name: /organization/i });
|
||||
userEvent.click(orgInput);
|
||||
userEvent.type(orgInput, 'org1');
|
||||
userEvent.tab();
|
||||
act(() => userEvent.tab());
|
||||
|
||||
const slugInput = getByRole('textbox', { name: /library id/i });
|
||||
const slugInput = await screen.findByRole('textbox', { name: /library id/i });
|
||||
userEvent.click(slugInput);
|
||||
userEvent.type(slugInput, 'test_library_slug');
|
||||
|
||||
fireEvent.click(getByRole('button', { name: /create/i }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: /create/i }));
|
||||
await waitFor(() => {
|
||||
expect(axiosMock.history.post.length).toBe(1);
|
||||
expect(axiosMock.history.post[0].data).toBe(
|
||||
@@ -98,41 +76,115 @@ describe('<CreateLibrary />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('show api error', async () => {
|
||||
axiosMock.onPost(getContentLibraryV2CreateApiUrl()).reply(400, {
|
||||
field: 'Error message',
|
||||
test('cannot create new org unless allowed', async () => {
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock);
|
||||
axiosMock.onPost(getContentLibraryV2CreateApiUrl()).reply(200, {
|
||||
id: 'library-id',
|
||||
});
|
||||
const { getByRole, getByTestId } = render(<RootWrapper />);
|
||||
|
||||
const titleInput = getByRole('textbox', { name: /library name/i });
|
||||
render(<CreateLibrary />);
|
||||
|
||||
const titleInput = await screen.findByRole('textbox', { name: /library name/i });
|
||||
userEvent.click(titleInput);
|
||||
userEvent.type(titleInput, 'Test Library Name');
|
||||
|
||||
const orgInput = getByTestId('autosuggest-textbox-input');
|
||||
userEvent.click(orgInput);
|
||||
userEvent.type(orgInput, 'org1');
|
||||
userEvent.tab();
|
||||
// We cannot create a new org, and so we're restricted to the allowed list
|
||||
const orgOptions = screen.getByTestId('autosuggest-iconbutton');
|
||||
userEvent.click(orgOptions);
|
||||
expect(screen.getByText('org1')).toBeInTheDocument();
|
||||
['org2', 'org3', 'org4', 'org5'].forEach((org) => expect(screen.queryByText(org)).not.toBeInTheDocument());
|
||||
|
||||
const slugInput = getByRole('textbox', { name: /library id/i });
|
||||
const orgInput = await screen.findByRole('combobox', { name: /organization/i });
|
||||
userEvent.click(orgInput);
|
||||
userEvent.type(orgInput, 'NewOrg');
|
||||
act(() => userEvent.tab());
|
||||
|
||||
const slugInput = await screen.findByRole('textbox', { name: /library id/i });
|
||||
userEvent.click(slugInput);
|
||||
userEvent.type(slugInput, 'test_library_slug');
|
||||
|
||||
fireEvent.click(getByRole('button', { name: /create/i }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: /create/i }));
|
||||
await waitFor(() => {
|
||||
expect(axiosMock.history.post.length).toBe(0);
|
||||
});
|
||||
expect(await screen.findByText('Required field.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('can create new org if allowed', async () => {
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, {
|
||||
...studioHomeMock,
|
||||
allow_to_create_new_org: true,
|
||||
});
|
||||
axiosMock.onPost(getContentLibraryV2CreateApiUrl()).reply(200, {
|
||||
id: 'library-id',
|
||||
});
|
||||
|
||||
render(<CreateLibrary />);
|
||||
|
||||
const titleInput = await screen.findByRole('textbox', { name: /library name/i });
|
||||
userEvent.click(titleInput);
|
||||
userEvent.type(titleInput, 'Test Library Name');
|
||||
|
||||
// We can create a new org, so we're also allowed to use any existing org
|
||||
const orgOptions = screen.getByTestId('autosuggest-iconbutton');
|
||||
userEvent.click(orgOptions);
|
||||
['org1', 'org2', 'org3', 'org4', 'org5'].forEach((org) => expect(screen.queryByText(org)).toBeInTheDocument());
|
||||
|
||||
const orgInput = await screen.findByRole('combobox', { name: /organization/i });
|
||||
userEvent.click(orgInput);
|
||||
userEvent.type(orgInput, 'NewOrg');
|
||||
act(() => userEvent.tab());
|
||||
|
||||
const slugInput = await screen.findByRole('textbox', { name: /library id/i });
|
||||
userEvent.click(slugInput);
|
||||
userEvent.type(slugInput, 'test_library_slug');
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: /create/i }));
|
||||
await waitFor(() => {
|
||||
expect(axiosMock.history.post.length).toBe(1);
|
||||
expect(axiosMock.history.post[0].data).toBe(
|
||||
'{"description":"","title":"Test Library Name","org":"NewOrg","slug":"test_library_slug"}',
|
||||
);
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/library/library-id');
|
||||
});
|
||||
});
|
||||
|
||||
test('show api error', async () => {
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock);
|
||||
axiosMock.onPost(getContentLibraryV2CreateApiUrl()).reply(400, {
|
||||
field: 'Error message',
|
||||
});
|
||||
render(<CreateLibrary />);
|
||||
|
||||
const titleInput = await screen.findByRole('textbox', { name: /library name/i });
|
||||
userEvent.click(titleInput);
|
||||
userEvent.type(titleInput, 'Test Library Name');
|
||||
|
||||
const orgInput = await screen.findByTestId('autosuggest-textbox-input');
|
||||
userEvent.click(orgInput);
|
||||
userEvent.type(orgInput, 'org1');
|
||||
act(() => userEvent.tab());
|
||||
|
||||
const slugInput = await screen.findByRole('textbox', { name: /library id/i });
|
||||
userEvent.click(slugInput);
|
||||
userEvent.type(slugInput, 'test_library_slug');
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: /create/i }));
|
||||
await waitFor(async () => {
|
||||
expect(axiosMock.history.post.length).toBe(1);
|
||||
expect(axiosMock.history.post[0].data).toBe(
|
||||
'{"description":"","title":"Test Library Name","org":"org1","slug":"test_library_slug"}',
|
||||
);
|
||||
expect(mockNavigate).not.toHaveBeenCalled();
|
||||
expect(getByRole('alert')).toHaveTextContent('Request failed with status code 400');
|
||||
expect(getByRole('alert')).toHaveTextContent('{"field":"Error message"}');
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('Request failed with status code 400');
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('{"field":"Error message"}');
|
||||
});
|
||||
});
|
||||
|
||||
test('cancel creating library navigates to libraries page', async () => {
|
||||
const { getByRole } = render(<RootWrapper />);
|
||||
render(<CreateLibrary />);
|
||||
|
||||
fireEvent.click(getByRole('button', { name: /cancel/i }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: /cancel/i }));
|
||||
await waitFor(() => {
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/libraries');
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ import FormikErrorFeedback from '../../generic/FormikErrorFeedback';
|
||||
import AlertError from '../../generic/alert-error';
|
||||
import { useOrganizationListData } from '../../generic/data/apiHooks';
|
||||
import SubHeader from '../../generic/sub-header/SubHeader';
|
||||
import { useStudioHome } from '../../studio-home/hooks';
|
||||
import { useCreateLibraryV2 } from './data/apiHooks';
|
||||
import messages from './messages';
|
||||
|
||||
@@ -38,10 +39,23 @@ const CreateLibrary = () => {
|
||||
} = useCreateLibraryV2();
|
||||
|
||||
const {
|
||||
data: organizationListData,
|
||||
data: allOrganizations,
|
||||
isLoading: isOrganizationListLoading,
|
||||
} = useOrganizationListData();
|
||||
|
||||
const {
|
||||
studioHomeData: {
|
||||
allowedOrganizationsForLibraries,
|
||||
allowToCreateNewOrg,
|
||||
},
|
||||
} = useStudioHome();
|
||||
|
||||
const organizations = (
|
||||
allowToCreateNewOrg
|
||||
? allOrganizations
|
||||
: allowedOrganizationsForLibraries
|
||||
) || [];
|
||||
|
||||
const handleOnClickCancel = () => {
|
||||
navigate('/libraries');
|
||||
};
|
||||
@@ -100,12 +114,17 @@ const CreateLibrary = () => {
|
||||
<Form.Autosuggest
|
||||
name="org"
|
||||
isLoading={isOrganizationListLoading}
|
||||
onChange={(event) => formikProps.setFieldValue('org', event.selectionId)}
|
||||
onChange={(event) => formikProps.setFieldValue(
|
||||
'org',
|
||||
allowToCreateNewOrg
|
||||
? (event.selectionId || event.userProvidedText)
|
||||
: event.selectionId,
|
||||
)}
|
||||
placeholder={intl.formatMessage(messages.orgPlaceholder)}
|
||||
>
|
||||
{organizationListData ? organizationListData.map((org) => (
|
||||
{organizations.map((org) => (
|
||||
<Form.AutosuggestOption key={org} id={org}>{org}</Form.AutosuggestOption>
|
||||
)) : []}
|
||||
))}
|
||||
</Form.Autosuggest>
|
||||
<FormikErrorFeedback name="org">
|
||||
<Form.Text>{intl.formatMessage(messages.orgHelp)}</Form.Text>
|
||||
|
||||
@@ -76,4 +76,5 @@ module.exports = {
|
||||
platformName: 'Your Platform Name Here',
|
||||
userIsActive: true,
|
||||
allowToCreateNewOrg: false,
|
||||
allowedOrganizationsForLibraries: ['org1'],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user