From 57f1d075582a9b1091167ed5ef1b6d49652401df Mon Sep 17 00:00:00 2001 From: Lakatos Andrei Date: Wed, 25 Feb 2026 18:11:36 +0200 Subject: [PATCH] fix: added math-templated node and element [PD-5558] --- .../src/__tests__/EditableHtml.test.jsx | 3 + .../src/components/EditableHtml.jsx | 2 + .../src/components/respArea/MathTemplated.jsx | 124 +++++++++++ .../respArea/__tests__/MathTemplated.test.jsx | 210 ++++++++++++++++++ .../src/extensions/responseArea.js | 3 +- 5 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 packages/editable-html-tip-tap/src/components/respArea/MathTemplated.jsx create mode 100644 packages/editable-html-tip-tap/src/components/respArea/__tests__/MathTemplated.test.jsx diff --git a/packages/editable-html-tip-tap/src/__tests__/EditableHtml.test.jsx b/packages/editable-html-tip-tap/src/__tests__/EditableHtml.test.jsx index bd8dd2e8b..9d72abb09 100644 --- a/packages/editable-html-tip-tap/src/__tests__/EditableHtml.test.jsx +++ b/packages/editable-html-tip-tap/src/__tests__/EditableHtml.test.jsx @@ -91,6 +91,9 @@ jest.mock('../extensions/responseArea', () => ({ InlineDropdownNode: { configure: jest.fn(() => ({})), }, + MathTemplatedNode: { + configure: jest.fn(() => ({})), + }, ResponseAreaExtension: { configure: jest.fn(() => ({})), }, diff --git a/packages/editable-html-tip-tap/src/components/EditableHtml.jsx b/packages/editable-html-tip-tap/src/components/EditableHtml.jsx index 6dabaf6fe..7a95b30bd 100644 --- a/packages/editable-html-tip-tap/src/components/EditableHtml.jsx +++ b/packages/editable-html-tip-tap/src/components/EditableHtml.jsx @@ -19,6 +19,7 @@ import { DragInTheBlankNode, ExplicitConstructedResponseNode, InlineDropdownNode, + MathTemplatedNode, ResponseAreaExtension, } from '../extensions/responseArea'; import { MathNode } from '../extensions/math'; @@ -154,6 +155,7 @@ export const EditableHtml = (props) => { ExplicitConstructedResponseNode.configure(props.responseAreaProps), DragInTheBlankNode.configure(props.responseAreaProps), InlineDropdownNode.configure(props.responseAreaProps), + MathTemplatedNode.configure(props.responseAreaProps), MathNode.configure({ toolbarOpts: toolbarOptsToUse, }), diff --git a/packages/editable-html-tip-tap/src/components/respArea/MathTemplated.jsx b/packages/editable-html-tip-tap/src/components/respArea/MathTemplated.jsx new file mode 100644 index 000000000..b507980b0 --- /dev/null +++ b/packages/editable-html-tip-tap/src/components/respArea/MathTemplated.jsx @@ -0,0 +1,124 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { NodeViewWrapper } from '@tiptap/react'; +import { mq } from '@pie-lib/math-input'; +import { styled } from '@mui/material/styles'; + +const StyledSpanContainer = styled('span')(() => ({ + display: 'inline-flex', + border: '1px solid #C0C3CF', + margin: '1px 5px', + cursor: 'pointer', + alignItems: 'center', + justifyContent: 'center', + minWidth: '50px', + minHeight: '36px', + height: 'fit-content', +})); + +const StyledResponseBox = styled('div')(({ theme }) => ({ + background: theme.palette.grey['A100'], + color: theme.palette.grey['A700'], + display: 'inline-flex', + borderRight: '2px solid #C0C3CF', + boxSizing: 'border-box', + overflow: 'hidden', + fontSize: '12px', + minHeight: '36px', + height: '100%', + alignItems: 'center', + fontFamily: 'Symbola, Times New Roman, serif', + padding: '0 2px', +})); + +const StyledMathBlock = styled('div')(() => ({ + flex: 8, + color: 'var(--pie-text, black)', + padding: '4px !important', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'var(--pie-background, rgba(255, 255, 255, 0))', + '& > .mq-math-mode sup.mq-nthroot': { + fontSize: '70% !important', + verticalAlign: '1em !important', + }, + '& > .mq-math-mode .mq-sqrt-stem': { + borderTop: '0.07em solid', + marginLeft: '-1.5px', + marginTop: '-2px !important', + paddingTop: '5px !important', + }, + '& .mq-overarrow-inner': { + paddingTop: '0 !important', + border: 'none !important', + }, + '& .mq-overarrow.mq-arrow-both': { + marginTop: '0px', + minWidth: '1.23em', + '& *': { + lineHeight: '1 !important', + }, + '&:before': { + top: '-0.4em', + left: '-1px', + }, + '&:after': { + top: '0px !important', + position: 'absolute !important', + right: '-2px', + }, + '&.mq-empty:after': { + top: '-0.45em', + }, + }, + '& .mq-overarrow.mq-arrow-right': { + '&:before': { + top: '-0.4em', + right: '-1px', + }, + }, + '& .mq-overarrow-inner-right': { + display: 'none !important', + }, + '& .mq-overarrow-inner-left': { + display: 'none !important', + }, +})); +const MathTemplated = (props) => { + const { node, options, selected } = props; + const { attrs: attributes } = node; + const { value, index } = attributes; + + // add 1 to index to display R 1 instead of R 0 + const keyToDisplay = `R ${parseInt(index) + 1}`; + + // console.log({nodeProps.children}) + return ( + + + {keyToDisplay} + + + + + + ); +}; + +MathTemplated.propTypes = { + attributes: PropTypes.object, + value: PropTypes.string, + keyToDisplay: PropTypes.string, +}; + +export default MathTemplated; diff --git a/packages/editable-html-tip-tap/src/components/respArea/__tests__/MathTemplated.test.jsx b/packages/editable-html-tip-tap/src/components/respArea/__tests__/MathTemplated.test.jsx new file mode 100644 index 000000000..4a98a5b14 --- /dev/null +++ b/packages/editable-html-tip-tap/src/components/respArea/__tests__/MathTemplated.test.jsx @@ -0,0 +1,210 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import MathTemplated from '../MathTemplated'; + +// Mock the dependencies +jest.mock('@tiptap/react', () => ({ + NodeViewWrapper: ({ children, ...props }) => ( +
+ {children} +
+ ), +})); + +jest.mock('@pie-lib/math-input', () => ({ + mq: { + Static: ({ latex }) => ( +
+ {latex} +
+ ), + }, +})); + +describe('MathTemplated', () => { + const defaultProps = { + node: { + attrs: { + value: 'x^2 + y^2 = r^2', + index: 0, + }, + }, + options: {}, + selected: false, + }; + + it('renders without crashing', () => { + const { container } = render(); + expect(container).toBeInTheDocument(); + }); + + it('renders NodeViewWrapper with correct className', () => { + const { getByTestId } = render(); + const wrapper = getByTestId('node-view-wrapper'); + expect(wrapper).toHaveClass('math-templated'); + }); + + it('displays correct response key for index 0', () => { + const { getByText } = render(); + expect(getByText('R 1')).toBeInTheDocument(); + }); + + it('displays correct response key for index 1', () => { + const props = { + ...defaultProps, + node: { + attrs: { + value: 'a + b', + index: 1, + }, + }, + }; + const { getByText } = render(); + expect(getByText('R 2')).toBeInTheDocument(); + }); + + it('displays correct response key for index 5', () => { + const props = { + ...defaultProps, + node: { + attrs: { + value: 'c = d', + index: 5, + }, + }, + }; + const { getByText } = render(); + expect(getByText('R 6')).toBeInTheDocument(); + }); + + it('renders LaTeX value correctly', () => { + const { getByTestId } = render(); + const mqStatic = getByTestId('mq-static'); + expect(mqStatic).toHaveAttribute('data-latex', 'x^2 + y^2 = r^2'); + }); + + it('renders different LaTeX value', () => { + const props = { + ...defaultProps, + node: { + attrs: { + value: '\\frac{a}{b}', + index: 2, + }, + }, + }; + const { getByTestId } = render(); + const mqStatic = getByTestId('mq-static'); + expect(mqStatic).toHaveAttribute('data-latex', '\\frac{a}{b}'); + }); + + it('passes selected prop to NodeViewWrapper', () => { + const props = { + ...defaultProps, + selected: true, + }; + const { getByTestId } = render(); + const wrapper = getByTestId('node-view-wrapper'); + expect(wrapper).toHaveAttribute('data-selected', 'true'); + }); + + it('passes false selected prop to NodeViewWrapper', () => { + const { getByTestId } = render(); + const wrapper = getByTestId('node-view-wrapper'); + expect(wrapper).toHaveAttribute('data-selected', 'false'); + }); + + it('applies correct inline styles to NodeViewWrapper', () => { + const { getByTestId } = render(); + const wrapper = getByTestId('node-view-wrapper'); + expect(wrapper).toHaveStyle({ + display: 'inline-flex', + minHeight: '36px', + minWidth: '50px', + cursor: 'pointer', + }); + }); + + it('handles string index correctly', () => { + const props = { + ...defaultProps, + node: { + attrs: { + value: 'test', + index: '3', + }, + }, + }; + const { getByText } = render(); + expect(getByText('R 4')).toBeInTheDocument(); + }); + + it('handles empty value', () => { + const props = { + ...defaultProps, + node: { + attrs: { + value: '', + index: 0, + }, + }, + }; + const { getByTestId } = render(); + const mqStatic = getByTestId('mq-static'); + expect(mqStatic).toHaveAttribute('data-latex', ''); + }); + + it('renders all styled components', () => { + const { container, getByText } = render(); + + // Check for response box + expect(getByText('R 1')).toBeInTheDocument(); + + // Check for math block with LaTeX + const mqStatic = container.querySelector('[data-testid="mq-static"]'); + expect(mqStatic).toBeInTheDocument(); + }); + + it('handles zero index', () => { + const props = { + ...defaultProps, + node: { + attrs: { + value: 'x = 0', + index: 0, + }, + }, + }; + const { getByText } = render(); + expect(getByText('R 1')).toBeInTheDocument(); + }); + + it('handles large index values', () => { + const props = { + ...defaultProps, + node: { + attrs: { + value: 'x = 100', + index: 99, + }, + }, + }; + const { getByText } = render(); + expect(getByText('R 100')).toBeInTheDocument(); + }); + + it('renders with complex LaTeX expression', () => { + const props = { + ...defaultProps, + node: { + attrs: { + value: '\\sqrt{x^2 + y^2}', + index: 0, + }, + }, + }; + const { getByTestId } = render(); + const mqStatic = getByTestId('mq-static'); + expect(mqStatic).toHaveAttribute('data-latex', '\\sqrt{x^2 + y^2}'); + }); +}); diff --git a/packages/editable-html-tip-tap/src/extensions/responseArea.js b/packages/editable-html-tip-tap/src/extensions/responseArea.js index b4e56b150..cc4f0ab7d 100644 --- a/packages/editable-html-tip-tap/src/extensions/responseArea.js +++ b/packages/editable-html-tip-tap/src/extensions/responseArea.js @@ -5,6 +5,7 @@ import { Node, ReactNodeViewRenderer } from '@tiptap/react'; import ExplicitConstructedResponse from '../components/respArea/ExplicitConstructedResponse'; import DragInTheBlank from '../components/respArea/DragInTheBlank/DragInTheBlank'; import InlineDropdown from '../components/respArea/InlineDropdown'; +import MathTemplated from '../components/respArea/MathTemplated'; const lastIndexMap = {}; @@ -305,7 +306,7 @@ export const MathTemplatedNode = Node.create({ ]; }, addNodeView() { - return ReactNodeViewRenderer(() =>
); + return ReactNodeViewRenderer((props) => ); }, });