diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..2825e2b1c --- /dev/null +++ b/codecov.yml @@ -0,0 +1,18 @@ +coverage: + status: + project: + default: + # Allow project coverage to decrease, but require patch coverage + target: auto + threshold: 100% + if_ci_failed: error + patch: + default: + # Require all new code to be covered + target: 100% + threshold: 0% + +comment: + layout: "reach,diff,flags,tree" + behavior: default + require_changes: false diff --git a/cypress/components/StyledMarkdown.cy.tsx b/cypress/components/StyledMarkdown.cy.tsx new file mode 100644 index 000000000..d30710b07 --- /dev/null +++ b/cypress/components/StyledMarkdown.cy.tsx @@ -0,0 +1,109 @@ +import React from 'react'; +import StyledMarkdown from '~/components/StyledMarkdown'; + +describe('StyledMarkdown - Reference Links', () => { + it('should correctly transform standard reference links', () => { + const markdown = ` +[JSON Schema][json-schema] + +[json-schema]: https://json-schema.org/ +`; + cy.mount(); + cy.get('a').should('have.attr', 'href', 'https://json-schema.org/'); + cy.get('a').should('have.text', 'JSON Schema'); + }); + + it('should correctly transform implicit reference links', () => { + const markdown = ` +[json-schema][] + +[json-schema]: https://json-schema.org/ +`; + cy.mount(); + cy.get('a').should('have.attr', 'href', 'https://json-schema.org/'); + // For implicit links [json-schema][], the text is 'json-schema' + cy.get('a').should('have.text', 'json-schema'); + }); + + it('should correctly transform collapsed reference links', () => { + const markdown = ` +[json-schema] + +[json-schema]: https://json-schema.org/ +`; + cy.mount(); + cy.get('a').should('have.attr', 'href', 'https://json-schema.org/'); + cy.get('a').should('have.text', 'json-schema'); + }); + + it('should correctly handle images as reference links', () => { + const markdown = ` +![JSON Schema Logo][logo] + +[logo]: https://json-schema.org/img/logo-blue.svg +`; + cy.mount(); + cy.get('img').should( + 'have.attr', + 'src', + 'https://json-schema.org/img/logo-blue.svg', + ); + cy.get('img').should('have.attr', 'alt', 'JSON Schema Logo'); + }); + + it('should not break standard inline links', () => { + const markdown = ` +[Inline Link](https://example.com) +`; + cy.mount(); + cy.get('a').should('have.attr', 'href', 'https://example.com'); + cy.get('a').should('have.text', 'Inline Link'); + }); + + it('should handle reference links with an optional space', () => { + const markdown = ` +[JSON Schema] [json-schema] + +[json-schema]: https://json-schema.org/ +`; + cy.mount(); + cy.get('a').should('have.attr', 'href', 'https://json-schema.org/'); + cy.get('a').should('have.text', 'JSON Schema'); + }); + + it('should handle implicit reference links with an optional space', () => { + const markdown = ` +[json-schema] [] + +[json-schema]: https://json-schema.org/ +`; + cy.mount(); + cy.get('a').should('have.attr', 'href', 'https://json-schema.org/'); + cy.get('a').should('have.text', 'json-schema'); + }); + + it('should prioritize the second bracket as the link ID', () => { + const markdown = ` +[Text][actual-id] + +[text]: https://wrong.com/ +[actual-id]: https://right.com/ +`; + cy.mount(); + cy.get('a').should('have.attr', 'href', 'https://right.com/'); + cy.get('a').should('have.text', 'Text'); + }); + + it('should ignore links without definitions', () => { + const markdown = ` +[Missing Definition] +[Missing Implicit][] +[Link][missing-id] +`; + cy.mount(); + // These should NOT be transformed into anchor tags if they are just raw text now + // But since the regex returns the original string if no link is found, + // they will be rendered as plain text by the markdown parser. + cy.get('a').should('not.exist'); + }); +}); diff --git a/lib/markdownUtils.ts b/lib/markdownUtils.ts index b0d89691e..3f386a409 100644 --- a/lib/markdownUtils.ts +++ b/lib/markdownUtils.ts @@ -25,13 +25,14 @@ export function transformMarkdownLinks(markdown: string): string { // Replace reference-style links with inline links return markdown.replace( - /\[([^\]]+)\]\[([^\]]*)\]/g, - (_, text: string, id: string) => { - const link = linkDefinitions[id.toLowerCase()]; + /!?\[([^\]]+)\](?:\s?\[([^\]]*)\])?(?!\()/g, + (match: string, text: string, id: string | undefined) => { + const linkId = (id !== undefined && id !== '' ? id : text).toLowerCase(); + const link = linkDefinitions[linkId]; if (link) { - return `[${text}](${link})`; + return (match.startsWith('!') ? '!' : '') + `[${text}](${link})`; } - return _; // Return the original string if no link is found + return match; // Return the original string if no link is found }, ); }