Skip to content

Commit

Permalink
fix: 404 error when creating subform (#13992)
Browse files Browse the repository at this point in the history
Co-authored-by: Nina Kylstad <[email protected]>
  • Loading branch information
lassopicasso and nkylstad authored Nov 12, 2024
1 parent ceeaae8 commit 002b6e4
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 55 deletions.
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 };
};

0 comments on commit 002b6e4

Please sign in to comment.