Skip to content

Commit

Permalink
Merge branch 'master' into EAR-2291-Update-content-for-calculated-sum…
Browse files Browse the repository at this point in the history
…mary-questions
  • Loading branch information
sudeepkunhis authored Jul 1, 2024
2 parents ea1f355 + 7e0ebb7 commit a556e96
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 60 deletions.
2 changes: 1 addition & 1 deletion eq-author-api/utils/createQuestionnaireIntroduction.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module.exports = (metadata) => {
additionalGuidancePanelSwitch: false,
additionalGuidancePanel: "",
description:
"<ul><li>Data should relate to all sites in England, Scotland, Wales and Northern Ireland unless otherwise stated. </li><li>You can provide info estimates if actual figures are not available.</li><li>We will treat your data securely and confidentially.</li></ul>",
"<ul><li>Data should relate to all sites in England, Scotland, Wales and Northern Ireland unless otherwise stated.</li><li>You can provide info estimates if actual figures are not available.</li><li>We will treat your data securely and confidentially.</li></ul>",
legalBasis: NOTICE_1,
// TODO: previewQuestions
previewQuestions: false,
Expand Down
1 change: 1 addition & 0 deletions eq-author/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
"draft-js-block-breakout-plugin": "latest",
"draft-js-plugins-editor": "latest",
"draft-js-raw-content-state": "latest",
"draft-js-import-html": "latest",
"draftjs-filters": "^2.5.0",
"firebase": "latest",
"firebaseui": "latest",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default class AnswerTypeButton extends React.Component {
doNotShowDR: PropTypes.bool,
mutuallyExclusiveEnabled: PropTypes.bool,
radioEnabled: PropTypes.bool,
selectEnabled: PropTypes.bool,
};

handleClick = () => {
Expand All @@ -58,6 +59,7 @@ export default class AnswerTypeButton extends React.Component {
doNotShowDR={this.props.doNotShowDR}
mutuallyExclusiveEnabled={this.props.mutuallyExclusiveEnabled}
radioEnabled={this.props.radioEnabled}
selectEnabled={this.props.selectEnabled}
disabled={this.props.disabled}
iconSrc={icons[this.props.type]}
onClick={this.handleClick}
Expand Down
3 changes: 3 additions & 0 deletions eq-author/src/components/AnswerTypeSelector/AnswerTypeGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class AnswerTypeGrid extends React.Component {
doNotShowDR: PropTypes.bool,
mutuallyExclusiveEnabled: PropTypes.bool,
radioEnabled: PropTypes.bool,
selectEnabled: PropTypes.bool,
};

handleSelect = (type) => {
Expand All @@ -90,6 +91,7 @@ class AnswerTypeGrid extends React.Component {
doNotShowDR,
mutuallyExclusiveEnabled,
radioEnabled,
selectEnabled,
...otherProps
} = this.props;
return (
Expand All @@ -111,6 +113,7 @@ class AnswerTypeGrid extends React.Component {
doNotShowDR={doNotShowDR}
mutuallyExclusiveEnabled={mutuallyExclusiveEnabled}
radioEnabled={radioEnabled}
selectEnabled={selectEnabled}
{...props}
/>
);
Expand Down
13 changes: 10 additions & 3 deletions eq-author/src/components/AnswerTypeSelector/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import IconText from "components/IconText";
import Button from "components/buttons/Button";
import ValidationError from "components/ValidationError";
import { QUESTION_ANSWER_NOT_SELECTED } from "constants/validationMessages";
import { RADIO, MUTUALLY_EXCLUSIVE } from "constants/answer-types";
import { RADIO, MUTUALLY_EXCLUSIVE, SELECT } from "constants/answer-types";

import answersHaveAnswerType from "utils/answersHaveAnswerType";

Expand Down Expand Up @@ -49,14 +49,15 @@ const ErrorContext = styled.div`
`}
`;

const mutuallyExclusiveEnabled = (answers, hasRadioAnswer) => {
const mutuallyExclusiveEnabled = (answers, hasRadioAnswer, hasSelectAnswer) => {
let allowMutuallyExclusive = false;
// Mutually exclusive button will be disabled when page has no answers, page has a radio answer, or page already has mutually exclusive answer
// Does not need to handle date range as "Add an answer" button is disabled when page has a date range answer
if (
answers.length === 0 ||
!answers ||
hasRadioAnswer ||
hasSelectAnswer ||
answersHaveAnswerType(answers, MUTUALLY_EXCLUSIVE) ||
answers.length > 1 // TODO: (Mutually exclusive) When Runner supports multiple answers with mutually exclusive, answers.length > 1 can be removed
) {
Expand Down Expand Up @@ -101,6 +102,7 @@ class AnswerTypeSelector extends React.Component {
let hasDateRange = false;
let hasOtherAnswerType = false;
let hasRadioAnswer = false;
let hasSelectAnswer = false;
let hasMutuallyExclusiveAnswer = false;

const answers = Array.from(this.props.page.answers);
Expand All @@ -118,6 +120,9 @@ class AnswerTypeSelector extends React.Component {
if (answersHaveAnswerType(this.props.page.answers, RADIO)) {
hasRadioAnswer = true;
}
if (answersHaveAnswerType(this.props.page.answers, SELECT)) {
hasSelectAnswer = true;
}
if (answersHaveAnswerType(this.props.page.answers, MUTUALLY_EXCLUSIVE)) {
hasMutuallyExclusiveAnswer = true;
}
Expand Down Expand Up @@ -159,9 +164,11 @@ class AnswerTypeSelector extends React.Component {
doNotShowDR={hasOtherAnswerType}
mutuallyExclusiveEnabled={mutuallyExclusiveEnabled(
this.props.page.answers,
hasRadioAnswer
hasRadioAnswer,
hasSelectAnswer
)}
radioEnabled={!hasMutuallyExclusiveAnswer}
selectEnabled={!hasMutuallyExclusiveAnswer}
/>
</Popout>
</ErrorContext>
Expand Down
13 changes: 12 additions & 1 deletion eq-author/src/components/AnswerTypeSelector/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
NUMBER,
CURRENCY,
RADIO,
SELECT,
// MUTUALLY_EXCLUSIVE,
} from "constants/answer-types";

Expand All @@ -14,7 +15,6 @@ import {
} from "tests/utils/rtl";

import AnswerTypeSelector from ".";

describe("Answer Type Selector", () => {
let props;
beforeEach(() => {
Expand Down Expand Up @@ -161,6 +161,17 @@ describe("Answer Type Selector", () => {
);
});

it("should disable mutually exclusive if there is a select answer", () => {
props.page.answers[0] = { type: SELECT };
const { getByText, getByTestId } = render(
<AnswerTypeSelector {...props} />
);
fireEvent.click(getByText(/Add another answer/));
expect(getByTestId("btn-answer-type-mutuallyexclusive")).toHaveAttribute(
"disabled"
);
});

// TODO: (Mutually exclusive) When Runner supports multiple answers with mutually exclusive, the commented tests and MUTUALLY_EXCLUSIVE import can be uncommented
// it("should disable radio if there is a mutually exclusive answer", () => {
// props.page.answers = [{ type: NUMBER }, { type: MUTUALLY_EXCLUSIVE }];
Expand Down
5 changes: 4 additions & 1 deletion eq-author/src/components/IconGrid/IconGridButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ const IconGridButton = ({
doNotShowDR,
mutuallyExclusiveEnabled,
radioEnabled,
selectEnabled,
...otherProps
}) => {
if (
(doNotShowDR && title === "Date range") ||
(!mutuallyExclusiveEnabled && title === "OR answer") ||
(!radioEnabled && title === "Radio")
(!radioEnabled && title === "Radio") ||
(!selectEnabled && title === "Select")
) {
disabled = true;
}
Expand Down Expand Up @@ -87,6 +89,7 @@ IconGridButton.propTypes = {
doNotShowDR: PropTypes.bool,
mutuallyExclusiveEnabled: PropTypes.bool,
radioEnabled: PropTypes.bool,
selectEnabled: PropTypes.bool,
};

export default IconGridButton;
96 changes: 72 additions & 24 deletions eq-author/src/components/RichTextEditor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from "prop-types";
import styled, { css } from "styled-components";
import Editor from "draft-js-plugins-editor";
import { EditorState, RichUtils, Modifier, CompositeDecorator } from "draft-js";
import { stateFromHTML } from "draft-js-import-html";
import "draft-js/dist/Draft.css";
import createBlockBreakoutPlugin from "draft-js-block-breakout-plugin";

Expand All @@ -27,9 +28,7 @@ import { sharedStyles } from "components/Forms/css";
import { Field, Label } from "components/Forms";
import ValidationError from "components/ValidationError";

import PasteModal, {
preserveRichFormatting,
} from "components/modals/PasteModal";
import PasteModal from "components/modals/PasteModal";

import { colors } from "../../constants/theme";

Expand Down Expand Up @@ -395,12 +394,12 @@ class RichTextEditor extends React.Component {

state = { showPasteModal: false, text: "", multiline: false };

handlePaste = (text) => {
handlePaste = (text, html) => {
if (/\s{2,}/g.test(text)) {
this.setState({
showPasteModal: true,
multiline: false,
text: text,
text: html || text,
});
} else {
this.handleChange(
Expand All @@ -418,12 +417,12 @@ class RichTextEditor extends React.Component {
return "handled";
};

handlePasteMultiline = (text) => {
handlePasteMultiline = (text, html) => {
if (/\s{2,}/g.test(text)) {
this.setState({
showPasteModal: true,
multiline: true,
text: text,
text: html || text,
});
return "handled";
} else {
Expand All @@ -436,27 +435,76 @@ class RichTextEditor extends React.Component {
const currentContent = editorState.getCurrentContent();
const currentSelection = editorState.getSelection();

let modifiedText;
let newEditorState;
let processedText = text;

// Simple HTML sanitization function
const sanitizeHtml = (html) => {
const doc = new DOMParser().parseFromString(html, "text/html");
return doc.body.innerHTML;
};

// Sanitize the input HTML
const sanitizedHtml = sanitizeHtml(processedText);

if (multiline) {
modifiedText = preserveRichFormatting(text);
} else {
modifiedText = text.replace(/\n/g, " ").trim().replace(/\s+/g, " ");
}
// Process the text to remove multiple spaces
const div = document.createElement("div");
div.innerHTML = sanitizedHtml;
const walker = document.createTreeWalker(
div,
NodeFilter.SHOW_TEXT,
null,
false
);
while (walker.nextNode()) {
walker.currentNode.nodeValue = walker.currentNode.nodeValue.replace(
/\s+/g,
" "
);
}
processedText = div.innerHTML;

// Replace the selected text with the pasted content
const newContentState = Modifier.replaceText(
currentContent,
currentSelection,
modifiedText
);
// Convert processed text from HTML to ContentState
const contentState = stateFromHTML(processedText);
const fragment = contentState.getBlockMap();

// Create a new EditorState with the updated content
const newEditorState = EditorState.push(
editorState,
newContentState,
"insert-characters"
);
// Replace the selected text with the pasted content
const newContentState = Modifier.replaceWithFragment(
currentContent,
currentSelection,
fragment
);

// Create a new EditorState with the updated content
newEditorState = EditorState.push(
editorState,
newContentState,
"insert-characters"
);
} else {
// For single line pastes, replace multiple spaces with a single space
processedText = processedText
.replace(/\n/g, " ")
.replace(/\s+/g, " ")
.trim();
const contentState = stateFromHTML(processedText);
const fragment = contentState.getBlockMap();

// Replace the selected text with the pasted content
const newContentState = Modifier.replaceWithFragment(
currentContent,
currentSelection,
fragment
);

// Create a new EditorState with the updated content
newEditorState = EditorState.push(
editorState,
newContentState,
"insert-characters"
);
}

// Set the new editor state and close the paste modal
this.setState({
Expand Down
13 changes: 0 additions & 13 deletions eq-author/src/components/modals/PasteModal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,6 @@ import PropTypes from "prop-types";

const Message = styled.div``;

export const preserveRichFormatting = (text) => {
// Replace multiple spaces and tabs with a single space
let formattedText = text.replace(/[ \t]+/g, " ");

// Split the text into lines
let lines = formattedText.split(/\r?\n/);

// Remove leading and trailing spaces from each line and join them back with newline characters
formattedText = lines.map((line) => line.trim()).join("\n");

return formattedText;
};

const ModalWrapper = styled.div`
.modal-button-container {
margin-top: 1em;
Expand Down
18 changes: 1 addition & 17 deletions eq-author/src/components/modals/PasteModal/index.test.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
import React from "react";
import { render, fireEvent } from "tests/utils/rtl";
import PasteModal, { preserveRichFormatting } from ".";
import PasteModal from ".";

import { keyCodes } from "constants/keyCodes";

const { Escape } = keyCodes;

describe("preserveRichFormatting function", () => {
it("should replace multiple spaces and tabs with a single space", () => {
const inputText = " Hello \t\t World ";
const expectedOutput = "Hello World";
const result = preserveRichFormatting(inputText);
expect(result).toBe(expectedOutput);
});

it("should remove leading and trailing spaces from each line", () => {
const inputText = " Line 1 \n Line 2 \n Line 3 ";
const expectedOutput = "Line 1\nLine 2\nLine 3";
const result = preserveRichFormatting(inputText);
expect(result).toBe(expectedOutput);
});
});

describe("PasteModal", () => {
let onConfirm, onCancel;
onConfirm = jest.fn();
Expand Down
Loading

0 comments on commit a556e96

Please sign in to comment.