From 79e2b4cba1289452a5c45d3ec1e3f21daf62987b Mon Sep 17 00:00:00 2001 From: George Karan Date: Sun, 17 Dec 2023 18:17:28 -0300 Subject: [PATCH] Add tests and optimize unescape (#15) --- src/parsing.ts | 5 +++ src/rendering.ts | 11 ++++- test/parsing.test.ts | 4 ++ test/rendering.test.ts | 94 +++++++++++++++++++++++++++--------------- 4 files changed, 80 insertions(+), 34 deletions(-) diff --git a/src/parsing.ts b/src/parsing.ts index 60b87c4..d28a545 100644 --- a/src/parsing.ts +++ b/src/parsing.ts @@ -5,6 +5,11 @@ export function parse(markdown: string): MarkdownNode { } export function unescape(input: string): string { + if (!input.includes('\\')) { + // Optimization for cases where there are no escape sequences + return input; + } + let text = ''; for (let index = 0; index < input.length; index++) { diff --git a/src/rendering.ts b/src/rendering.ts index 7ec5a20..7622461 100644 --- a/src/rendering.ts +++ b/src/rendering.ts @@ -34,7 +34,10 @@ type VisitedMarkdownNodeMap = { }; export type VisitedMarkdownNode = { - [K in MarkdownNodeType]: {type: K} & VisitedMarkdownNodeMap[K] + [K in MarkdownNodeType]: { + type: K, + source: string, + } & VisitedMarkdownNodeMap[K] }[T]; export interface MarkdownRenderer { @@ -62,18 +65,21 @@ function visit(node: MarkdownNode, visitor: MarkdownRenderer): T { return visitor.bold({ type: node.type, children: visit(node.children, visitor), + source: node.source, }); case 'italic': return visitor.italic({ type: node.type, children: visit(node.children, visitor), + source: node.source, }); case 'strike': return visitor.strike({ type: node.type, children: visit(node.children, visitor), + source: node.source, }); case 'code': @@ -87,18 +93,21 @@ function visit(node: MarkdownNode, visitor: MarkdownRenderer): T { type: node.type, href: node.href, children: visit(node.children, visitor), + source: node.source, }); case 'paragraph': return visitor.paragraph({ type: node.type, children: node.children.map(child => visit(child, visitor)), + source: node.source, }); case 'fragment': return visitor.fragment({ type: node.type, children: node.children.map(child => visit(child, visitor)), + source: node.source, }); } } diff --git a/test/parsing.test.ts b/test/parsing.test.ts index 50ee525..9a94497 100644 --- a/test/parsing.test.ts +++ b/test/parsing.test.ts @@ -1389,6 +1389,10 @@ describe('A Markdown parser function', () => { 'ABC\\D', 'ABCD', ], + [ + 'Not escaped', + 'Not escaped', + ], ])('should unescape %s to %s', (input, output) => { expect(unescape(input)).toEqual(output); }); diff --git a/test/rendering.test.ts b/test/rendering.test.ts index b643867..adbecb4 100644 --- a/test/rendering.test.ts +++ b/test/rendering.test.ts @@ -2,41 +2,79 @@ import {MarkdownNode} from '../src/ast'; import {MarkdownRenderer, render, VisitedMarkdownNode} from '../src'; describe('A Markdown render function', () => { - class HtmlRenderer implements MarkdownRenderer { - public fragment(node: VisitedMarkdownNode): string { - return node.children.join(''); + class TestRenderer implements MarkdownRenderer { + public fragment(node: VisitedMarkdownNode): MarkdownNode { + return { + type: 'fragment', + source: node.source, + children: node.children, + }; } - public text(node: VisitedMarkdownNode): string { - return node.content; + public text(node: VisitedMarkdownNode): MarkdownNode { + return { + type: 'text', + source: node.source, + content: node.content, + }; } - public bold(node: VisitedMarkdownNode): string { - return `${node.children}`; + public bold(node: VisitedMarkdownNode): MarkdownNode { + return { + type: 'bold', + source: node.source, + children: node.children, + }; } - public italic(node: VisitedMarkdownNode): string { - return `${node.children}`; + public italic(node: VisitedMarkdownNode): MarkdownNode { + return { + type: 'italic', + source: node.source, + children: node.children, + }; } - public strike(node: VisitedMarkdownNode): string { - return `${node.children}`; + public strike(node: VisitedMarkdownNode): MarkdownNode { + return { + type: 'strike', + source: node.source, + children: node.children, + }; } - public code(node: VisitedMarkdownNode): string { - return `${node.content}`; + public code(node: VisitedMarkdownNode): MarkdownNode { + return { + type: 'code', + source: node.source, + content: node.content, + }; } - public image(node: VisitedMarkdownNode): string { - return `${node.alt}`; + public image(node: VisitedMarkdownNode): MarkdownNode { + return { + type: 'image', + source: node.source, + alt: node.alt, + src: node.src, + }; } - public link(node: VisitedMarkdownNode): string { - return `${node.children}`; + public link(node: VisitedMarkdownNode): MarkdownNode { + return { + type: 'link', + source: node.source, + href: node.href, + children: node.children, + }; } - public paragraph(node: VisitedMarkdownNode): string { - return `

${node.children.join('')}

`; + public paragraph(node: VisitedMarkdownNode): MarkdownNode { + return { + type: 'paragraph', + source: node.source, + children: node.children, + }; } } @@ -147,11 +185,11 @@ describe('A Markdown render function', () => { children: [ { type: 'link', - source: '![Link](https://example.com)', + source: '[Link](https://example.com)', href: 'https://example.com', children: { type: 'text', - source: 'https://example.com', + source: 'Link', content: 'Link', }, }, @@ -160,21 +198,11 @@ describe('A Markdown render function', () => { ], }; - const html = [ - '

Bold

', - '

Italic

', - '

Bold and italic

', - '

Strike

', - '

Code

', - '

Image

', - '

Link

', - ].join(''); - it('should render a Markdown tree', () => { - expect(render(tree, new HtmlRenderer())).toBe(html); + expect(render(tree, new TestRenderer())).toEqual(tree); }); it('should parse and render a Markdown string', () => { - expect(render(markdown, new HtmlRenderer())).toBe(html); + expect(render(markdown, new TestRenderer())).toEqual(tree); }); });