Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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,62 @@
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>) => {
const updatedQuestion = {
...formField,
questionOptions: { ...formField.questionOptions, buttonLabel: event.target.value },
};
setFormField(updatedQuestion);
},
[formField, setFormField],
);

const handleWorkspaceNameChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const updatedQuestion = {
...formField,
questionOptions: { ...formField.questionOptions, workspaceName: event.target.value },
};
setFormField(updatedQuestion);
},
[formField, 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,105 @@
import React from 'react';
import { render, screen, fireEvent } 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', () => {
beforeEach(() => {
jest.clearAllMocks();
});

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', () => {
renderWorkspaceLauncher();
const buttonLabelInput = screen.getByLabelText(/Button Label/i);
const newValue = 'Launch Patient Dashboard';

fireEvent.change(buttonLabelInput, { target: { value: newValue } });

expect(mockSetFormField).toHaveBeenCalledWith({
...formField,
questionOptions: {
...formField.questionOptions,
buttonLabel: newValue,
},
});
});

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

fireEvent.change(workspaceNameInput, { target: { value: newValue } });

expect(mockSetFormField).toHaveBeenCalledWith({
...formField,
questionOptions: {
...formField.questionOptions,
workspaceName: newValue,
},
});
});

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

fireEvent.change(workspaceNameInput, { target: { value: textToPaste } });

expect(mockSetFormField).toHaveBeenCalledWith({
...formField,
questionOptions: {
...formField.questionOptions,
workspaceName: textToPaste,
},
});
});
});

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"
}