Skip to content

Commit

Permalink
feat: Add rich text input validation for "(opens in a new tab)" (#2979)
Browse files Browse the repository at this point in the history
  • Loading branch information
DafyddLlyr authored Apr 4, 2024
1 parent 6225ced commit ee7bafd
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,8 @@ const AlteredNodesSummaryContent = (props: {
<Typography variant="body2">
{`Preview these content changes in-service before publishing `}
<Link href={url.replace("/published", "/amber")} target="_blank">
{`here`}
{`here (opens in a new tab).`}
</Link>
{` (opens in a new tab).`}
</Typography>
</Box>
</Box>
Expand Down
22 changes: 0 additions & 22 deletions editor.planx.uk/src/ui/editor/RichTextInput.test.ts

This file was deleted.

55 changes: 55 additions & 0 deletions editor.planx.uk/src/ui/editor/RichTextInput.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { screen } from "@testing-library/react";
import React from "react";
import { setup } from "testUtils";
import RichTextInput from "ui/editor/RichTextInput";

import { modifyDeep } from "./RichTextInput";

test("modifyDeep helper", () => {
/**
* This example traverses a nested structure and increments every even number. The modifier function
* returns `null` for every level where it didn't find a number, instructing the function to leave
* the value unchanged at that level but keep traversing downwards to see if there is something to change.
*/
expect(
modifyDeep((val) =>
typeof val === "number" ? (val % 2 === 0 ? val + 1 : val) : null,
)({
a: { c: { val: 2 } },
b: { val: 3 },
d: [1, 2, 3, 4],
}),
).toEqual({
a: { c: { val: 3 } },
b: { val: 3 },
d: [1, 3, 3, 5],
});
});
describe("input validation", () => {
it("does not display an error if the text '(opens in a new tab)' is wrapped in an anchor element", async () => {
setup(
<RichTextInput
value={'<a href="#">Some link (opens in a new tab)</a>'}
/>,
);

const errorIcon = screen.queryByTestId("ErrorIcon");
expect(errorIcon).not.toBeInTheDocument();
});

it("displays an error if the text '(opens in a new tab)' is not wrapped in an anchor element", async () => {
const { user } = setup(
<RichTextInput
value={'<p><a href="#">Some link</a> (opens in a new tab)</p>'}
/>,
);

const errorIcon = screen.getByTestId("ErrorIcon");
expect(errorIcon).toBeVisible();

await user.click(errorIcon);
expect(
screen.getByText('Links must wrap the text "(opens in a new tab)"'),
).toBeVisible();
});
});
37 changes: 36 additions & 1 deletion editor.planx.uk/src/ui/editor/RichTextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,28 @@ const getContentHierarchyError = (doc: JSONContent): string | null => {
return error;
};

const getLinkNewTabError = (
content: JSONContent | undefined = [],
): string | undefined => {
let error: string | undefined;
if (!content) return;

content.forEach((child: JSONContent) => {
if (!child.content) return;

child.content.forEach(({ marks, text }) => {
const isLink = marks?.map(({ type }) => type).includes("link");
const hasOpenTabText = text?.includes("(opens in a new tab)");

if (hasOpenTabText && !isLink) {
error = 'Links must wrap the text "(opens in a new tab)"';
}
});
});

return error;
};

const PopupError: FC<{ id: string; error: string }> = (props) => {
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);

Expand Down Expand Up @@ -400,6 +422,10 @@ const RichTextInput: FC<Props> = (props) => {
string | null
>(getContentHierarchyError(fromHtml(stringValue)));

const [linkNewTabError, setLinkNewTabError] = useState<string | undefined>(
getLinkNewTabError(fromHtml(stringValue).content),
);

// Handle update events
const handleUpdate = useCallback(
(transaction: { editor: Editor }) => {
Expand All @@ -409,6 +435,7 @@ const RichTextInput: FC<Props> = (props) => {
const doc = transaction.editor.getJSON();

setContentHierarchyError(getContentHierarchyError(doc));
setLinkNewTabError(getLinkNewTabError(doc.content));

const html = toHtml(doc);
internalValue.current = html;
Expand Down Expand Up @@ -672,7 +699,15 @@ const RichTextInput: FC<Props> = (props) => {
<EditorContent editor={editor} />
{contentHierarchyError && (
<Box sx={{ position: "absolute", top: 0, right: 0 }}>
<PopupError id="content-error" error={contentHierarchyError} />
<PopupError
id="content-error-hierarchy"
error={contentHierarchyError}
/>
</Box>
)}
{linkNewTabError && (
<Box sx={{ position: "absolute", top: 0, right: 0 }}>
<PopupError id="content-error-link-tab" error={linkNewTabError} />
</Box>
)}
</RichContentContainer>
Expand Down

0 comments on commit ee7bafd

Please sign in to comment.