diff --git a/package.json b/package.json index ea31cc2..86f564b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@coseeing/see-mark", - "version": "1.3.0", + "version": "1.3.1", "description": "A markdown parser for a11y", "main": "./lib/see-mark.cjs", "files": [ diff --git a/src/markdown-processor/markdown-processor-position.test.js b/src/markdown-processor/markdown-processor-position.test.js index c073df6..7968a98 100644 --- a/src/markdown-processor/markdown-processor-position.test.js +++ b/src/markdown-processor/markdown-processor-position.test.js @@ -929,3 +929,238 @@ describe('markdownProcessor - edge cases', () => { expect(extracted).toBe(markdownContent); }); }); + +describe('markdownProcessor - position tracking for all component types', () => { + const options = { + latexDelimiter: 'bracket', + documentFormat: 'inline', + imageFiles: { 'img-id': 'path/to/image.png' }, + }; + + /** + * Helper to verify position for a component + */ + const verifyPosition = (container, componentType) => { + const element = getElementByType(container, componentType); + expect(element).not.toBeNull(); + + const payload = JSON.parse( + element.getAttribute(SEE_MARK_PAYLOAD_DATA_ATTRIBUTES) + ); + + expect(payload.position).toBeDefined(); + expect(payload.position.start).toBeGreaterThanOrEqual(0); + expect(payload.position.end).toBeGreaterThan(payload.position.start); + + return payload; + }; + + // Link types + it('should include position info in internal link with title payload', () => { + const markdownContent = `[Display][[Title]]`; + const result = markdownProcessor(markdownContent, options); + const container = createDOMFromHTML(result); + + const payload = verifyPosition( + container, + SUPPORTED_COMPONENT_TYPES.INTERNAL_LINK_TITLE, + markdownContent + ); + + const extracted = markdownContent.substring( + payload.position.start, + payload.position.end + ); + expect(extracted).toBe('[Display][[Title]]'); + }); + + it('should include position info in external link tab payload', () => { + const markdownContent = `@[Display](https://example.com)`; + const result = markdownProcessor(markdownContent, options); + const container = createDOMFromHTML(result); + + const payload = verifyPosition( + container, + SUPPORTED_COMPONENT_TYPES.EXTERNAL_LINK_TAB, + markdownContent + ); + + const extracted = markdownContent.substring( + payload.position.start, + payload.position.end + ); + expect(extracted).toBe('@[Display](https://example.com)'); + }); + + it('should include position info in external link tab with title payload', () => { + const markdownContent = `@[Display][[Title]](https://example.com)`; + const result = markdownProcessor(markdownContent, options); + const container = createDOMFromHTML(result); + + const payload = verifyPosition( + container, + SUPPORTED_COMPONENT_TYPES.EXTERNAL_LINK_TAB_TITLE, + markdownContent + ); + + const extracted = markdownContent.substring( + payload.position.start, + payload.position.end + ); + expect(extracted).toBe('@[Display][[Title]](https://example.com)'); + }); + + it('should include position info in external link with title payload', () => { + const markdownContent = `[Display][[Title]](https://example.com)`; + const result = markdownProcessor(markdownContent, options); + const container = createDOMFromHTML(result); + + const payload = verifyPosition( + container, + SUPPORTED_COMPONENT_TYPES.EXTERNAL_LINK_TITLE, + markdownContent + ); + + const extracted = markdownContent.substring( + payload.position.start, + payload.position.end + ); + expect(extracted).toBe('[Display][[Title]](https://example.com)'); + }); + + // Image types + it('should include position info in image link payload', () => { + const markdownContent = `![alt](img-id)((https://example.com))`; + const result = markdownProcessor(markdownContent, options); + const container = createDOMFromHTML(result); + + const payload = verifyPosition( + container, + SUPPORTED_COMPONENT_TYPES.IMAGE_LINK, + markdownContent + ); + + const extracted = markdownContent.substring( + payload.position.start, + payload.position.end + ); + expect(extracted).toBe('![alt](img-id)((https://example.com))'); + }); + + it('should include position info in image display payload', () => { + const markdownContent = `![alt][[caption]](img-id)`; + const result = markdownProcessor(markdownContent, options); + const container = createDOMFromHTML(result); + + const payload = verifyPosition( + container, + SUPPORTED_COMPONENT_TYPES.IMAGE_DISPLAY, + markdownContent + ); + + const extracted = markdownContent.substring( + payload.position.start, + payload.position.end + ); + expect(extracted).toBe('![alt][[caption]](img-id)'); + }); + + it('should include position info in image display link payload', () => { + const markdownContent = `![alt][[caption]](img-id)((https://example.com))`; + const result = markdownProcessor(markdownContent, options); + const container = createDOMFromHTML(result); + + const payload = verifyPosition( + container, + SUPPORTED_COMPONENT_TYPES.IMAGE_DISPLAY_LINK, + markdownContent + ); + + const extracted = markdownContent.substring( + payload.position.start, + payload.position.end + ); + expect(extracted).toBe('![alt][[caption]](img-id)((https://example.com))'); + }); + + // Embed types + it('should include position info in iframe payload', () => { + const markdownContent = `@![title](https://example.com/embed)`; + const result = markdownProcessor(markdownContent, options); + const container = createDOMFromHTML(result); + + const payload = verifyPosition( + container, + SUPPORTED_COMPONENT_TYPES.IFRAME, + markdownContent + ); + + const extracted = markdownContent.substring( + payload.position.start, + payload.position.end + ); + expect(extracted).toBe('@![title](https://example.com/embed)'); + }); + + it('should include position info in youtube payload', () => { + const markdownContent = `@![video title](https://www.youtube.com/embed/abc123)`; + const result = markdownProcessor(markdownContent, options); + const container = createDOMFromHTML(result); + + const payload = verifyPosition( + container, + SUPPORTED_COMPONENT_TYPES.YOUTUBE, + markdownContent + ); + + const extracted = markdownContent.substring( + payload.position.start, + payload.position.end + ); + expect(extracted).toBe( + '@![video title](https://www.youtube.com/embed/abc123)' + ); + }); + + it('should include position info in codepen payload', () => { + const markdownContent = `@![pen title](https://codepen.io/user/embed/abc123)`; + const result = markdownProcessor(markdownContent, options); + const container = createDOMFromHTML(result); + + const payload = verifyPosition( + container, + SUPPORTED_COMPONENT_TYPES.CODEPEN, + markdownContent + ); + + const extracted = markdownContent.substring( + payload.position.start, + payload.position.end + ); + expect(extracted).toBe( + '@![pen title](https://codepen.io/user/embed/abc123)' + ); + }); + + // Heading + it('should include position info in heading payload', () => { + const markdownContent = `# Heading text`; + const result = markdownProcessor(markdownContent, { + ...options, + documentFormat: 'block', + }); + const container = createDOMFromHTML(result); + + const payload = verifyPosition( + container, + SUPPORTED_COMPONENT_TYPES.HEADING, + markdownContent + ); + + const extracted = markdownContent.substring( + payload.position.start, + payload.position.end + ); + expect(extracted).toBe('# Heading text'); + }); +}); diff --git a/src/markup-converters/react/default-components/alert/Alert.jsx b/src/markup-converters/react/default-components/alert/Alert.jsx index 8e9d211..c592099 100644 --- a/src/markup-converters/react/default-components/alert/Alert.jsx +++ b/src/markup-converters/react/default-components/alert/Alert.jsx @@ -20,4 +20,5 @@ Alert.propTypes = { internalLinkId: PropTypes.string, variant: PropTypes.string, title: PropTypes.string, + position: PropTypes.shape({ start: PropTypes.number, end: PropTypes.number }), }; diff --git a/src/markup-converters/react/default-components/codepen/CodePen.jsx b/src/markup-converters/react/default-components/codepen/CodePen.jsx index 5509ff1..208f071 100644 --- a/src/markup-converters/react/default-components/codepen/CodePen.jsx +++ b/src/markup-converters/react/default-components/codepen/CodePen.jsx @@ -21,6 +21,7 @@ const CodePen = ({ title, source }) => { CodePen.propTypes = { title: PropTypes.string, source: PropTypes.string, + position: PropTypes.shape({ start: PropTypes.number, end: PropTypes.number }), }; export default CodePen; diff --git a/src/markup-converters/react/default-components/external-link-tab-title/ExternalLinkTabTitle.jsx b/src/markup-converters/react/default-components/external-link-tab-title/ExternalLinkTabTitle.jsx index fff564a..d6e862e 100644 --- a/src/markup-converters/react/default-components/external-link-tab-title/ExternalLinkTabTitle.jsx +++ b/src/markup-converters/react/default-components/external-link-tab-title/ExternalLinkTabTitle.jsx @@ -13,6 +13,7 @@ ExternalLinkTabTitle.propTypes = { display: PropTypes.string, title: PropTypes.string, target: PropTypes.string.isRequired, + position: PropTypes.shape({ start: PropTypes.number, end: PropTypes.number }), }; export default ExternalLinkTabTitle; diff --git a/src/markup-converters/react/default-components/external-link-tab/ExternalLinkTab.jsx b/src/markup-converters/react/default-components/external-link-tab/ExternalLinkTab.jsx index 724eaa1..cc8b406 100644 --- a/src/markup-converters/react/default-components/external-link-tab/ExternalLinkTab.jsx +++ b/src/markup-converters/react/default-components/external-link-tab/ExternalLinkTab.jsx @@ -12,6 +12,7 @@ const ExternalLinkTab = ({ display = '', target = '' }) => { ExternalLinkTab.propTypes = { display: PropTypes.string, target: PropTypes.string.isRequired, + position: PropTypes.shape({ start: PropTypes.number, end: PropTypes.number }), }; export default ExternalLinkTab; diff --git a/src/markup-converters/react/default-components/external-link-title/ExternalLinkTitle.jsx b/src/markup-converters/react/default-components/external-link-title/ExternalLinkTitle.jsx index 276dd86..617865d 100644 --- a/src/markup-converters/react/default-components/external-link-title/ExternalLinkTitle.jsx +++ b/src/markup-converters/react/default-components/external-link-title/ExternalLinkTitle.jsx @@ -13,6 +13,7 @@ ExternalLinkTitle.propTypes = { display: PropTypes.string, title: PropTypes.string, target: PropTypes.string.isRequired, + position: PropTypes.shape({ start: PropTypes.number, end: PropTypes.number }), }; export default ExternalLinkTitle; diff --git a/src/markup-converters/react/default-components/iframe/Iframe.jsx b/src/markup-converters/react/default-components/iframe/Iframe.jsx index 3814c95..8bd949f 100644 --- a/src/markup-converters/react/default-components/iframe/Iframe.jsx +++ b/src/markup-converters/react/default-components/iframe/Iframe.jsx @@ -8,6 +8,7 @@ const Iframe = ({ title, source }) => { Iframe.propTypes = { title: PropTypes.string, source: PropTypes.string, + position: PropTypes.shape({ start: PropTypes.number, end: PropTypes.number }), }; export default Iframe; diff --git a/src/markup-converters/react/default-components/image-display-link/ImageDisplayLink.jsx b/src/markup-converters/react/default-components/image-display-link/ImageDisplayLink.jsx index f9f47e6..4e107c2 100644 --- a/src/markup-converters/react/default-components/image-display-link/ImageDisplayLink.jsx +++ b/src/markup-converters/react/default-components/image-display-link/ImageDisplayLink.jsx @@ -24,6 +24,7 @@ ImageDisplayLink.propTypes = { imageId: PropTypes.string, src: PropTypes.string, target: PropTypes.string.isRequired, + position: PropTypes.shape({ start: PropTypes.number, end: PropTypes.number }), }; export default ImageDisplayLink; diff --git a/src/markup-converters/react/default-components/image-display/ImageDisplay.jsx b/src/markup-converters/react/default-components/image-display/ImageDisplay.jsx index 47dae9e..c676cbd 100644 --- a/src/markup-converters/react/default-components/image-display/ImageDisplay.jsx +++ b/src/markup-converters/react/default-components/image-display/ImageDisplay.jsx @@ -15,6 +15,7 @@ ImageDisplay.propTypes = { display: PropTypes.string, imageId: PropTypes.string, src: PropTypes.string, + position: PropTypes.shape({ start: PropTypes.number, end: PropTypes.number }), }; export default ImageDisplay; diff --git a/src/markup-converters/react/default-components/image-link/ImageLink.jsx b/src/markup-converters/react/default-components/image-link/ImageLink.jsx index 2705423..7227e09 100644 --- a/src/markup-converters/react/default-components/image-link/ImageLink.jsx +++ b/src/markup-converters/react/default-components/image-link/ImageLink.jsx @@ -14,6 +14,7 @@ ImageLink.propTypes = { imageId: PropTypes.string, src: PropTypes.string, target: PropTypes.string.isRequired, + position: PropTypes.shape({ start: PropTypes.number, end: PropTypes.number }), }; export default ImageLink; diff --git a/src/markup-converters/react/default-components/image/Image.jsx b/src/markup-converters/react/default-components/image/Image.jsx index ff07a52..b31946c 100644 --- a/src/markup-converters/react/default-components/image/Image.jsx +++ b/src/markup-converters/react/default-components/image/Image.jsx @@ -9,6 +9,7 @@ Image.propTypes = { alt: PropTypes.string, imageId: PropTypes.string, src: PropTypes.string, + position: PropTypes.shape({ start: PropTypes.number, end: PropTypes.number }), }; export default Image; diff --git a/src/markup-converters/react/default-components/internal-link-title/InternalLinkTitle.jsx b/src/markup-converters/react/default-components/internal-link-title/InternalLinkTitle.jsx index e9c325e..fccc268 100644 --- a/src/markup-converters/react/default-components/internal-link-title/InternalLinkTitle.jsx +++ b/src/markup-converters/react/default-components/internal-link-title/InternalLinkTitle.jsx @@ -18,6 +18,7 @@ InternalLinkTitle.propTypes = { display: PropTypes.string, title: PropTypes.string, target: PropTypes.string.isRequired, + position: PropTypes.shape({ start: PropTypes.number, end: PropTypes.number }), }; export default InternalLinkTitle; diff --git a/src/markup-converters/react/default-components/internal-link/InternalLink.jsx b/src/markup-converters/react/default-components/internal-link/InternalLink.jsx index 9a52224..a99aa45 100644 --- a/src/markup-converters/react/default-components/internal-link/InternalLink.jsx +++ b/src/markup-converters/react/default-components/internal-link/InternalLink.jsx @@ -12,6 +12,7 @@ const InternalLink = ({ display = '', target = '' }) => { InternalLink.propTypes = { display: PropTypes.string, target: PropTypes.string.isRequired, + position: PropTypes.shape({ start: PropTypes.number, end: PropTypes.number }), }; export default InternalLink; diff --git a/src/markup-converters/react/default-components/youtube/YouTube.jsx b/src/markup-converters/react/default-components/youtube/YouTube.jsx index 1701b2d..5d19f3f 100644 --- a/src/markup-converters/react/default-components/youtube/YouTube.jsx +++ b/src/markup-converters/react/default-components/youtube/YouTube.jsx @@ -20,6 +20,7 @@ const YouTube = ({ title, source }) => { YouTube.propTypes = { title: PropTypes.string, source: PropTypes.string, + position: PropTypes.shape({ start: PropTypes.number, end: PropTypes.number }), }; export default YouTube;