Skip to content

Commit

Permalink
Merge pull request #3075 from ONSdigital/EAR-2030-Extra-Spaces
Browse files Browse the repository at this point in the history
Ear 2030 extra spaces
  • Loading branch information
Paul-Joel authored Dec 20, 2023
2 parents 15a7fa1 + bf23c01 commit cca9779
Show file tree
Hide file tree
Showing 13 changed files with 1,858 additions and 1,393 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { withRouter } from "react-router-dom";

import { CollapsiblesEditor } from "./";

jest.mock("components/RichTextEditor");

describe("CollapsiblesEditor", () => {
let props, Component;
beforeEach(() => {
Expand Down
3 changes: 3 additions & 0 deletions eq-author/src/App/page/Design/answers/BasicAnswer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export const StatelessBasicAnswer = ({
answer,
onChange,
onUpdate,
onPaste,
labelText,
descriptionText,
descriptionPlaceholder,
Expand Down Expand Up @@ -163,6 +164,7 @@ export const StatelessBasicAnswer = ({
rows="5"
onChange={onChange}
onBlur={onUpdate}
onPaste={onPaste}
value={answer.description}
placeholder={descriptionPlaceholder}
data-test="txt-answer-description"
Expand Down Expand Up @@ -237,6 +239,7 @@ StatelessBasicAnswer.propTypes = {
answer: CustomPropTypes.answer.isRequired,
onChange: PropTypes.func.isRequired,
onUpdate: PropTypes.func.isRequired,
onPaste: PropTypes.func,
children: PropTypes.element,
labelText: PropTypes.string,
labelPlaceholder: PropTypes.string,
Expand Down
10 changes: 8 additions & 2 deletions eq-author/src/App/settings/SettingsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
} from "constants/validationMessages";
import LegalBasisSelect from "./LegalBasisSelect";

import { reduceMultipleSpaces } from "utils/reduceMultipleSpaces";

const StyledPanel = styled.div`
max-width: 97.5%;
padding: 1.3em;
Expand Down Expand Up @@ -184,7 +186,9 @@ const SettingsPage = ({ questionnaire }) => {
<StyledInput
id="questionnaireTitle"
value={questionnaireTitle}
onChange={({ value }) => setQuestionnaireTitle(value)}
onChange={({ value }) =>
setQuestionnaireTitle(reduceMultipleSpaces(value))
}
onBlur={(e) => handleTitleChange({ ...e.target })}
data-test="change-questionnaire-title"
/>
Expand All @@ -201,7 +205,9 @@ const SettingsPage = ({ questionnaire }) => {
id="shortTitle"
value={questionnaireShortTitle}
onChange={({ value }) =>
setQuestionnaireShortTitle(value)
setQuestionnaireShortTitle(
reduceMultipleSpaces(value)
)
}
onBlur={(e) => handleShortTitleChange({ ...e.target })}
data-test="change-questionnaire-short-title"
Expand Down
2 changes: 1 addition & 1 deletion eq-author/src/components/Forms/Number/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Number.propTypes = {
value: PropTypes.number,
step: PropTypes.number,
min: PropTypes.number,
max: PropTypes.number.isRequired,
max: PropTypes.number,
default: PropTypes.number,
type: PropTypes.oneOf([CURRENCY, PERCENTAGE, NUMBER, UNIT]),
"data-test": PropTypes.string,
Expand Down
88 changes: 74 additions & 14 deletions eq-author/src/components/Forms/WrappingInput/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { invoke } from "lodash";
import { sharedStyles } from "components/Forms/css";
import ValidationError from "components/ValidationError";

import PasteModal from "components/modals/PasteModal";
import { reduceMultipleSpaces } from "utils/reduceMultipleSpaces";

const ENTER_KEY = 13;

const StyleContext = styled.div`
Expand All @@ -27,6 +30,7 @@ class WrappingInput extends React.Component {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func.isRequired,
onPaste: PropTypes.func,
onKeyDown: PropTypes.func,
id: PropTypes.string.isRequired,
bold: PropTypes.bool,
Expand All @@ -38,11 +42,58 @@ class WrappingInput extends React.Component {
bold: false,
};

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

handleChange = (e) => {
e.target.value = e.target.value.replace(/\n/g, " ");
e.target.value = e.target.value.replace(/\s+/g, " ");
this.props.onChange(e);
};

handlePaste = (e) => {
const text = e.clipboardData.getData("text");
e.persist();
if (/\s{2,}/g.test(text)) {
this.setState({
showPasteModal: true,
text: text,
event: e,
});
}
};

handleOnPasteConfirm = () => {
const { text, event } = this.state;
if (event && event.persist) {
const target = event.target;
const currentValue = target.value;
const selectionStart = target.selectionStart;
const selectionEnd = target.selectionEnd;

// Concatenate the text before and after the selection with the pasted text
const newValue =
currentValue.substring(0, selectionStart) +
reduceMultipleSpaces(text) +
currentValue.substring(selectionEnd);

// Create a new event with the updated value
const updatedEvent = {
...event,
target: { ...target, value: newValue.trim().replace(/\s+/g, " ") },
};

// Call the onChange prop with the updated event
this.props.onChange(updatedEvent);
}

// Clear the showPasteModal state
this.setState({ showPasteModal: false, text: "" });
};

handleOnPasteCancel = () => {
this.setState({ showPasteModal: false, text: "" });
};

handleKeyDown = (e) => {
if (e.keyCode === ENTER_KEY) {
e.preventDefault();
Expand All @@ -53,22 +104,31 @@ class WrappingInput extends React.Component {

render() {
const { bold, placeholder, errorValidationMsg, ...otherProps } = this.props;
const { state } = this;
return (
<StyleContext
bold={bold}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
>
<TextArea
{...otherProps}
placeholder={placeholder}
invalid={errorValidationMsg}
aria-invalid={Boolean(errorValidationMsg).toString()}
<>
<PasteModal
isOpen={state.showPasteModal}
onConfirm={this.handleOnPasteConfirm}
onCancel={this.handleOnPasteCancel}
/>
{errorValidationMsg && (
<ValidationError>{errorValidationMsg}</ValidationError>
)}
</StyleContext>
<StyleContext
bold={bold}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
onPaste={this.handlePaste}
>
<TextArea
{...otherProps}
placeholder={placeholder}
invalid={errorValidationMsg}
aria-invalid={Boolean(errorValidationMsg).toString()}
/>
{errorValidationMsg && (
<ValidationError>{errorValidationMsg}</ValidationError>
)}
</StyleContext>
</>
);
}
}
Expand Down
146 changes: 104 additions & 42 deletions eq-author/src/components/PageTitle/PageTitleInput.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React from "react";
import React, { useState } from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import { Field, Label } from "components/Forms";
import { colors, radius } from "constants/theme";
import ValidationError from "components/ValidationError";
// import ExternalLink from "components-themed/ExternalLink";

import PasteModal from "components/modals/PasteModal";
import { reduceMultipleSpaces } from "utils/reduceMultipleSpaces";

const PageTitleContent = styled.div`
color: ${colors.black};
`;
Expand Down Expand Up @@ -54,48 +57,107 @@ const PageTitleInput = ({
inputTitlePrefix,
altFieldName,
errorMessage,
}) => (
<PageTitleContent>
<Heading data-test="page-title-input-heading">{heading}</Heading>
<PageDescriptionContent>
The page title is the first thing read by screen readers and helps users
of assistive technology understand what the page is about. It is shown in
the browser&apos;s title bar or in the page&apos;s tab. Page titles follow
the structure: &apos;page description - questionnaire title&apos;.
</PageDescriptionContent>
{/* From interaction designer - this will be brought back when the page titles link is fixed */}
{/* <p>
For help writing a page description, see our{" "}
<ExternalLink
url="https://ons-design-system.netlify.app/guidance/page-titles-and-urls/#page-titles"
linkText="design system guidance on page titles"
/>
</p> */}
<Field data-test="page-title-missing-error">
<Label
data-test="page-title-input-field-label"
htmlFor={altFieldName ? altFieldName : "pageDescription"}
>
{inputTitlePrefix
? `${inputTitlePrefix} page description`
: "Page description"}
</Label>
<StyledInput
id={altFieldName ? altFieldName : "pageDescription"}
type="text"
data-test="txt-page-description"
autoComplete="off"
name={altFieldName ? altFieldName : "pageDescription"}
placeholder=""
onChange={(e) => onChange(e.target)}
onBlur={(e) => onUpdate(e.target)}
value={pageDescription || ""}
hasError={errorMessage}
}) => {
const [pasteModalInfo, setPasteModalInfo] = useState({
show: false,
text: "",
event: {},
});

const handleChange = (e) => {
e.target.value = e.target.value.replace(/\n/g, " ");
e.target.value = e.target.value.replace(/\s+/g, " ");
onChange(e.target);
};

const handlePaste = (event) => {
const text = event.clipboardData.getData("text");
event.persist();
if (/\s{2,}/g.test(text)) {
setPasteModalInfo({
show: true,
text: text,
event: event,
});
}
};

const updateOnPaste = () => {
const { text, event } = pasteModalInfo;
if (event && event.persist) {
const target = event.target;
const cursorPosition = target.selectionStart;
const currentValue = target.value;

// Insert the pasted text at the cursor position
const newValue =
currentValue.substring(0, cursorPosition) +
reduceMultipleSpaces(text) +
currentValue.substring(target.selectionEnd);

const updatedEvent = { ...event, persist: undefined }; // Create a new event without the persist method
updatedEvent.target.value = newValue.trim().replace(/\s+/g, " ");

onChange(updatedEvent.target);
}

// Clear the pasteModalInfo state
setPasteModalInfo({ show: false, text: "" });
};

const cancelPaste = () => {
setPasteModalInfo({ show: false, text: "" });
};
return (
<PageTitleContent>
<PasteModal
isOpen={pasteModalInfo.show}
onConfirm={updateOnPaste}
onCancel={cancelPaste}
/>
{errorMessage && <ValidationError>{errorMessage}</ValidationError>}
</Field>
</PageTitleContent>
);
<Heading data-test="page-title-input-heading">{heading}</Heading>
<PageDescriptionContent>
The page title is the first thing read by screen readers and helps users
of assistive technology understand what the page is about. It is shown
in the browser&apos;s title bar or in the page&apos;s tab. Page titles
follow the structure: &apos;page description - questionnaire
title&apos;.
</PageDescriptionContent>
{/* From interaction designer - this will be brought back when the page titles link is fixed */}
{/* <p>
For help writing a page description, see our{" "}
<ExternalLink
url="https://ons-design-system.netlify.app/guidance/page-titles-and-urls/#page-titles"
linkText="design system guidance on page titles"
/>
</p> */}
<Field data-test="page-title-missing-error">
<Label
data-test="page-title-input-field-label"
htmlFor={altFieldName ? altFieldName : "pageDescription"}
>
{inputTitlePrefix
? `${inputTitlePrefix} page description`
: "Page description"}
</Label>
<StyledInput
id={altFieldName ? altFieldName : "pageDescription"}
type="text"
data-test="txt-page-description"
autoComplete="off"
name={altFieldName ? altFieldName : "pageDescription"}
placeholder=""
onPaste={(e) => handlePaste(e)}
onChange={(e) => handleChange(e)}
onBlur={(e) => onUpdate(e.target)}
value={pageDescription || ""}
hasError={errorMessage}
/>
{errorMessage && <ValidationError>{errorMessage}</ValidationError>}
</Field>
</PageTitleContent>
);
};

PageTitleInput.propTypes = {
pageDescription: PropTypes.string,
Expand Down
17 changes: 16 additions & 1 deletion eq-author/src/components/RichTextEditor/RichTextEditor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { RichUtils, EditorState, convertToRaw } from "draft-js";
import Editor from "draft-js-plugins-editor";
import Raw from "draft-js-raw-content-state";
import { omit } from "lodash";

import RichTextEditor from "components/RichTextEditor";
import Toolbar, {
STYLE_BLOCK,
Expand All @@ -19,6 +18,12 @@ import { createPipedEntity } from "components/RichTextEditor/entities/PipedValue
// https://github.com/facebook/draft-js/issues/702
jest.mock("draft-js/lib/generateRandomKey", () => () => "123");

jest.mock("components/modals/PasteModal", () => ({
__esModule: true,
namedExport: jest.fn(),
default: jest.fn().mockImplementation(() => null),
}));

let wrapper, props, editorInstance, editorFocus;

const content = `
Expand Down Expand Up @@ -170,6 +175,16 @@ describe("components/RichTextEditor", function () {
expect(html).toContain("hello world");
});

it("should handle paste without the pasteModal for multiple spaced text", () => {
const text = "hello world";

const handled = wrapper.find(Editor).prop("handlePastedText")(text);
const html = wrapper.instance().getHTML();

expect(handled).toBe("handled");
expect(html).toContain("hello world");
});

it("should disable enter key", () => {
var result = wrapper.find(Editor).prop("handleReturn")();
expect(result).toBe("handled");
Expand Down
Loading

0 comments on commit cca9779

Please sign in to comment.