Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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 @@ -6,3 +6,4 @@ export { default as Toggle } from './toggle/toggle.component';
export { default as UiSelectExtended } from './ui-select-extended/ui-select-extended.component';
export { default as Markdown } from './markdown/markdown.component';
export { default as SelectAnswers } from './select/select-answers.component';
export { default as WorkspaceLauncher } from './workspace-launcher/workspace-launcher.component';
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { TextInput } from '@carbon/react';
import { useFormField } from '../../../../form-field-context';

const WorkspaceLauncher: React.FC = () => {
const { t } = useTranslation();
const { formField, setFormField } = useFormField();

const { buttonLabel, workspaceName } = useMemo(
() => ({
buttonLabel: formField.questionOptions?.buttonLabel ?? '',
workspaceName: formField.questionOptions?.workspaceName ?? '',
}),
[formField.questionOptions?.buttonLabel, formField.questionOptions?.workspaceName],
);

const handleButtonLabelChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setFormField((prevFormField) => ({
...prevFormField,
questionOptions: { ...prevFormField.questionOptions, buttonLabel: event.target.value },
}));
},
[setFormField],
);

const handleWorkspaceNameChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setFormField((prevFormField) => ({
...prevFormField,
questionOptions: { ...prevFormField.questionOptions, workspaceName: event.target.value },
}));
},
[setFormField],
);

return (
<>
<TextInput
id="buttonLabel"
labelText={t('buttonLabel', 'Button Label')}
value={buttonLabel}
onChange={handleButtonLabelChange}
placeholder={t('buttonLabelPlaceholder', 'Enter text to display on the button')}
required
/>
<TextInput
id="workspaceName"
labelText={t('workspaceName', 'Workspace Name')}
value={workspaceName}
onChange={handleWorkspaceNameChange}
placeholder={t('workspaceNamePlaceholder', 'Enter the name of the workspace to launch')}
required
/>
</>
);
};

export default React.memo(WorkspaceLauncher);
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import WorkspaceLauncher from './workspace-launcher.component';
import { FormFieldProvider } from '../../../../form-field-context';
import type { FormField } from '@openmrs/esm-form-engine-lib';

const mockSetFormField = jest.fn();
const formField: FormField = {
type: 'obs',
questionOptions: {
rendering: 'workspace-launcher' as const,
buttonLabel: '',
workspaceName: '',
},
id: '1',
};

jest.mock('../../../../form-field-context', () => ({
...jest.requireActual('../../../../form-field-context'),
useFormField: () => ({ formField, setFormField: mockSetFormField }),
}));

describe('WorkspaceLauncher', () => {
it('renders workspace launcher inputs', () => {
renderWorkspaceLauncher();
expect(screen.getByLabelText(/Button Label/i)).toBeInTheDocument();
expect(screen.getByLabelText(/Workspace Name/i)).toBeInTheDocument();
});

it('displays placeholder text for both inputs', () => {
renderWorkspaceLauncher();
expect(screen.getByPlaceholderText(/Enter text to display on the button/i)).toBeInTheDocument();
expect(screen.getByPlaceholderText(/Enter the name of the workspace to launch/i)).toBeInTheDocument();
});

it('marks both inputs as required', () => {
renderWorkspaceLauncher();
const buttonLabelInput = screen.getByLabelText(/Button Label/i);
const workspaceNameInput = screen.getByLabelText(/Workspace Name/i);

expect(buttonLabelInput).toBeRequired();
expect(workspaceNameInput).toBeRequired();
});

it('updates button label when input changes', async () => {
renderWorkspaceLauncher();
const user = userEvent.setup();
const buttonLabelInput = screen.getByLabelText(/Button Label/i);
const newValue = 'Launch Patient Dashboard';

await user.clear(buttonLabelInput);
await user.type(buttonLabelInput, newValue);

expect(mockSetFormField).toHaveBeenCalled();
});

it('updates workspace name when input changes', async () => {
renderWorkspaceLauncher();
const user = userEvent.setup();
const workspaceNameInput = screen.getByLabelText(/Workspace Name/i);
const newValue = 'patient-dashboard';

await user.clear(workspaceNameInput);
await user.type(workspaceNameInput, newValue);

expect(mockSetFormField).toHaveBeenCalled();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good to have, but we can make it a bit better by checking if it got called with the right object. Here's a place where you can use for guidance -

// Gets all calls made to our mock function, the arguments from the first call and the first argument of the first call
const updateFn = mockSetFormField.mock.calls[0][0];
// Execute the update function with the previous state
const resultState = updateFn(formField);
// Check that the result has the expected values
expect(resultState).toEqual({
...formField,
datePickerFormat: 'calendar',
questionOptions: {
...formField.questionOptions,
concept: '456',
},
});

});

it('handles pasting text correctly', async () => {
renderWorkspaceLauncher();
const user = userEvent.setup();
const workspaceNameInput = screen.getByLabelText(/Workspace Name/i);
const textToPaste = 'pasted-workspace-name';

await user.clear(workspaceNameInput);
await user.paste(textToPaste);

expect(mockSetFormField).toHaveBeenCalled();
});
});

function renderWorkspaceLauncher() {
render(
<FormFieldProvider initialFormField={formField}>
<WorkspaceLauncher />
</FormFieldProvider>,
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import React from 'react';
import { Date, Markdown, Number, SelectAnswers, Text, TextArea, Toggle, UiSelectExtended } from './inputs';
import {
Date,
Markdown,
Number,
SelectAnswers,
Text,
TextArea,
Toggle,
UiSelectExtended,
WorkspaceLauncher,
} from './inputs';
import { useFormField } from '../../form-field-context';
import type { RenderType } from '@openmrs/esm-form-engine-lib';
import { renderTypeOptions, renderingTypes } from '@constants';
Expand All @@ -16,6 +26,7 @@ const componentMap: Partial<Record<RenderType, React.FC>> = {
select: SelectAnswers,
radio: SelectAnswers,
checkbox: SelectAnswers,
'workspace-launcher': WorkspaceLauncher,
};

const RenderTypeComponent: React.FC = () => {
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ export interface QuestionOptions {
labelTrue: string;
labelFalse: string;
};
buttonLabel?: string; // Text to display on the button for workspace-launcher rendering type
workspaceName?: string; // Name of the workspace to launch for workspace-launcher rendering type
}

export interface Answer {
Expand Down
4 changes: 4 additions & 0 deletions translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"auditDetails": "Audit Details",
"autogeneratedUuid": "UUID (auto-generated)",
"backToDashboard": "Back to dashboard",
"buttonLabel": "Button Label",
"buttonLabelPlaceholder": "Enter text to display on the button",
"calendarAndTimer": "Calendar and timer",
"calendarOnly": "Calendar only",
"cancel": "Cancel",
Expand Down Expand Up @@ -231,5 +233,7 @@
"viewErrors": "View the errors in the interactive builder",
"welcomeExplainer": "Add pages, sections and questions to your form. The Preview tab automatically updates as you build your form. For a detailed explanation of what constitutes an OpenMRS form schema, please read through the ",
"welcomeHeading": "Interactive schema builder",
"workspaceName": "Workspace Name",
"workspaceNamePlaceholder": "Enter the name of the workspace to launch",
"yes": "Yes"
}