Skip to content
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

fix: 404 error when creating subform #13992

Merged
merged 14 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,39 @@ describe('CreateSubformWrapper', () => {
});
await user.click(confirmButton);
expect(addLayoutSet).toHaveBeenCalledWith(org, app, subformName, {
layoutSetConfig: { id: subformName, type: 'subform' },
layoutSetConfig: { id: subformName, type: 'subform', dataType: '' },
taskType: undefined,
});
});

it('should show spinner when adding subform', async () => {
const user = userEvent.setup();

const addLayoutSetMock = jest.fn().mockImplementation(
() =>
new Promise((resolve) => {
setTimeout(resolve, 100);
}),
);

renderCreateSubformWrapper(undefined, { addLayoutSet: addLayoutSetMock });

const createSubformButton = screen.getByRole('button', {
name: textMock('ux_editor.create.subform'),
});
await user.click(createSubformButton);

const input = screen.getByRole('textbox');
await user.type(input, subformName);

const confirmButton = screen.getByRole('button', {
name: textMock('ux_editor.create.subform.confirm_button'),
});
await user.click(confirmButton);

const spinner = await screen.findByText(textMock('general.loading'));
expect(spinner).toBeInTheDocument();
});
});

const renderCreateSubformWrapper = (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { StudioButton, StudioPopover, StudioTextfield } from '@studio/components';
import { StudioButton, StudioPopover, StudioSpinner, StudioTextfield } from '@studio/components';
import { PlusIcon } from '@studio/icons';
import { useTranslation } from 'react-i18next';
import { useValidateLayoutSetName } from 'app-shared/hooks/useValidateLayoutSetName';
Expand All @@ -21,21 +21,38 @@ export const CreateSubformWrapper = ({
const [nameError, setNameError] = useState('');
const { t } = useTranslation();
const { validateLayoutSetName } = useValidateLayoutSetName();
const { createSubform } = useCreateSubform();
const { createSubform, isPendingLayoutSetMutation } = useCreateSubform();

const onCreateConfirmClick = () => {
setCreateNewOpen(false);
createSubform({ layoutSetName: newSubformName, onSubformCreated });
onSubformCreated(newSubformName);
};

const onNameChange = (subformName: string) => {
const handleNameChange = (subformName: string) => {
const subformNameValidation = validateLayoutSetName(subformName, layoutSets);
setNameError(subformNameValidation);
setNewSubformName(subformName);
};
const handleCreateSubform = () => {
createSubform({
layoutSetName: newSubformName,
onSubformCreated: onCreateConfirmClick,
//setting datatype to empty string as this createSubform area is only temporary and will be removed in a later PR
dataType: '',
});
};

const createSubformButtonContent = isPendingLayoutSetMutation ? (
<StudioSpinner spinnerTitle={t('general.loading')} />
) : (
t('ux_editor.create.subform.confirm_button')
);

return (
<StudioPopover open={createNewOpen} onOpenChange={setCreateNewOpen}>
<StudioPopover
open={createNewOpen}
onOpenChange={!isPendingLayoutSetMutation && setCreateNewOpen}
>
<StudioPopover.Trigger asChild>
<StudioButton
icon={<PlusIcon />}
Expand All @@ -50,16 +67,17 @@ export const CreateSubformWrapper = ({
label={t('ux_editor.create.subform.label')}
size='small'
value={newSubformName}
onChange={(e) => onNameChange(e.target.value)}
onChange={(e) => handleNameChange(e.target.value)}
error={nameError}
disabled={isPendingLayoutSetMutation}
/>
<StudioButton
className={classes.confirmCreateButton}
variant='secondary'
onClick={onCreateConfirmClick}
onClick={handleCreateSubform}
disabled={!newSubformName || !!nameError}
>
{t('ux_editor.create.subform.confirm_button')}
{createSubformButtonContent}
</StudioButton>
</StudioPopover.Content>
</StudioPopover>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import React from 'react';
import { renderWithProviders } from '../../../../../../testing/mocks';
import { CreateNewSubformLayoutSet } from './CreateNewSubformLayoutSet';
import type { ComponentType } from 'app-shared/types/ComponentType';
import { textMock } from '@studio/testing/mocks/i18nMock';
import { screen, waitFor } from '@testing-library/react';
import { screen } from '@testing-library/react';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { app, org } from '@studio/testing/testids';
import { QueryKey } from 'app-shared/types/QueryKey';
import { layoutSets } from 'app-shared/mocks/mocks';
import type { LayoutSets } from 'app-shared/types/api/LayoutSetsResponse';
import userEvent from '@testing-library/user-event';
import type { FormComponent } from '../../../../../../types/FormComponent';
import { AppContext } from '../../../../../../AppContext';
import { appContextMock } from '../../../../../../testing/appContextMock';
import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext';
import { queriesMock } from 'app-shared/mocks/queriesMock';

const onSubformCreatedMock = jest.fn();
const selectedOptionDataType = 'moped';
Expand Down Expand Up @@ -70,10 +67,36 @@ describe('CreateNewSubformLayoutSet ', () => {
await user.selectOptions(dataModelSelect, ['moped']);
const saveButton = screen.getByRole('button', { name: textMock('general.close') });
await user.click(saveButton);
await waitFor(() => expect(onSubformCreatedMock).toHaveBeenCalledTimes(1));
expect(onSubformCreatedMock).toHaveBeenCalledTimes(1);
expect(onSubformCreatedMock).toHaveBeenCalledWith('NewSubform');
});

it('displays loading spinner when save button is clicked', async () => {
const user = userEvent.setup();

const addLayoutSetMock = jest.fn().mockImplementation(
() =>
new Promise((resolve) => {
setTimeout(resolve, 100);
}),
);
renderCreateNewSubformLayoutSet({
addLayoutSet: addLayoutSetMock,
});

const input = screen.getByRole('textbox');
await user.type(input, 'NewSubform');

const dataModelSelect = screen.getByRole('combobox');
await user.selectOptions(dataModelSelect, ['moped']);

const saveButton = screen.getByRole('button', { name: textMock('general.close') });
await user.click(saveButton);

const spinner = await screen.findByText(textMock('general.loading'));
expect(spinner).toBeInTheDocument();
});

it('disables the save button when input is invalid', async () => {
const user = userEvent.setup();
renderCreateNewSubformLayoutSet();
Expand Down Expand Up @@ -124,20 +147,14 @@ describe('CreateNewSubformLayoutSet ', () => {
});
});

const renderCreateNewSubformLayoutSet = (
layoutSetsMock: LayoutSets = layoutSets,
componentProps: Partial<FormComponent<ComponentType.Subform>> = {},
) => {
const queryClient = createQueryClientMock();
queryClient.setQueryData([QueryKey.LayoutSets, org, app], layoutSetsMock);
const renderCreateNewSubformLayoutSet = (queries?: Partial<ServicesContextProps>) => {
return renderWithProviders(
<AppContext.Provider value={{ ...appContextMock }}>
<CreateNewSubformLayoutSet
onSubformCreated={onSubformCreatedMock}
layoutSets={layoutSets}
{...componentProps}
/>
<CreateNewSubformLayoutSet onSubformCreated={onSubformCreatedMock} layoutSets={layoutSets} />
</AppContext.Provider>,
{ queryClient },
{
queries: { ...queriesMock, ...queries },
queryClient: createQueryClientMock(),
},
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { StudioButton, StudioCard, StudioTextfield } from '@studio/components';
import { StudioButton, StudioCard, StudioSpinner, StudioTextfield } from '@studio/components';
import { ClipboardIcon, CheckmarkIcon } from '@studio/icons';
import classes from './CreateNewSubformLayoutSet.module.css';
import { SubformDataModelSelect } from './SubformDataModelSelect';
Expand All @@ -21,19 +21,25 @@ export const CreateNewSubformLayoutSet = ({
const [newSubform, setNewSubform] = useState('');
const [selectedDataType, setSelectedDataType] = useState<string>();
const { validateLayoutSetName } = useValidateLayoutSetName();
const { createSubform } = useCreateSubform();
const { createSubform, isPendingLayoutSetMutation } = useCreateSubform();
const [nameError, setNameError] = useState('');

function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const subformNameValidation = validateLayoutSetName(e.target.value, layoutSets);
function handleChange(subformName: string) {
const subformNameValidation = validateLayoutSetName(subformName, layoutSets);
setNameError(subformNameValidation);
setNewSubform(e.target.value);
setNewSubform(subformName);
}

function handleCreateSubform() {
createSubform({ layoutSetName: newSubform, onSubformCreated, dataType: selectedDataType });
}

const saveIcon = isPendingLayoutSetMutation ? (
<StudioSpinner size='sm' spinnerTitle={t('general.loading')} />
) : (
<CheckmarkIcon />
);

return (
<StudioCard>
<StudioCard.Content>
Expand All @@ -43,8 +49,8 @@ export const CreateNewSubformLayoutSet = ({
<StudioTextfield
label={t('ux_editor.component_properties.subform.created_layout_set_name')}
value={newSubform}
size='sm'
onChange={handleChange}
disabled={isPendingLayoutSetMutation}
onChange={(e) => handleChange(e.target.value)}
error={nameError}
/>
<SubformDataModelSelect
Expand All @@ -54,7 +60,7 @@ export const CreateNewSubformLayoutSet = ({
/>
<StudioButton
className={classes.savelayoutSetButton}
icon={<CheckmarkIcon />}
icon={saveIcon}
onClick={handleCreateSubform}
title={t('general.close')}
disabled={!newSubform || !!nameError || !selectedDataType}
Expand Down
20 changes: 13 additions & 7 deletions frontend/packages/ux-editor/src/hooks/useCreateSubform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,20 @@ describe('useCreateSubform', () => {
const subformName = 'underskjema';
const onSubformCreated = jest.fn();

createSubform({ layoutSetName: subformName, onSubformCreated });
createSubform({ layoutSetName: subformName, onSubformCreated, dataType: 'dataModel1' });

expect(addLayoutSetMock).toHaveBeenCalledWith({
layoutSetIdToUpdate: subformName,
layoutSetConfig: {
id: subformName,
type: 'subform',
expect(addLayoutSetMock).toHaveBeenCalledWith(
{
layoutSetIdToUpdate: subformName,
layoutSetConfig: {
id: subformName,
type: 'subform',
dataType: 'dataModel1',
},
},
});
{
onSuccess: expect.any(Function),
},
);
});
});
38 changes: 26 additions & 12 deletions frontend/packages/ux-editor/src/hooks/useCreateSubform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,38 @@ import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmen
type CreateSubformProps = {
layoutSetName: string;
onSubformCreated: (layoutSetName: string) => void;
dataType?: string;
dataType: string;
};

export const useCreateSubform = () => {
type UseCreateSubformReturn = {
createSubform: (props: CreateSubformProps) => void;
isPendingLayoutSetMutation: boolean;
};

export const useCreateSubform = (): UseCreateSubformReturn => {
const { org, app } = useStudioEnvironmentParams();
const { mutate: addLayoutSet } = useAddLayoutSetMutation(org, app);
const { mutate: addLayoutSet, isPending: isPendingLayoutSetMutation } = useAddLayoutSetMutation(
org,
app,
);

const createSubform = ({ layoutSetName, onSubformCreated, dataType }: CreateSubformProps) => {
addLayoutSet({
layoutSetIdToUpdate: layoutSetName,
layoutSetConfig: {
id: layoutSetName,
type: 'subform',
dataType,
addLayoutSet(
{
layoutSetIdToUpdate: layoutSetName,
layoutSetConfig: {
id: layoutSetName,
type: 'subform',
dataType,
},
},
{
onSuccess: () => {
onSubformCreated(layoutSetName);
},
},
});
onSubformCreated(layoutSetName);
);
};

return { createSubform };
return { createSubform, isPendingLayoutSetMutation };
};