From 8c1cafc94d8f1f79ab08574fe8b3e8e0aa8c2704 Mon Sep 17 00:00:00 2001 From: sandypockets Date: Sat, 16 Dec 2023 21:21:18 -0500 Subject: [PATCH] Chore: Increase test coverage --- __tests__/addTableOfContents.test.js | 46 +++++++++++++++ __tests__/buildExports.test.js | 73 +++++++++++++++++++++++ __tests__/calculateReadingTime.test.js | 34 +++++++++++ __tests__/embed.test.js | 82 ++++++++++++++++++++++++++ __tests__/wrapElements.test.js | 76 ++++++++++++++++++++++++ 5 files changed, 311 insertions(+) create mode 100644 __tests__/addTableOfContents.test.js create mode 100644 __tests__/buildExports.test.js create mode 100644 __tests__/calculateReadingTime.test.js create mode 100644 __tests__/embed.test.js create mode 100644 __tests__/wrapElements.test.js diff --git a/__tests__/addTableOfContents.test.js b/__tests__/addTableOfContents.test.js new file mode 100644 index 0000000..274cf99 --- /dev/null +++ b/__tests__/addTableOfContents.test.js @@ -0,0 +1,46 @@ +import addTableOfContents from '../src/plugins/addTableOfContents.js'; + +describe("addTableOfContents functionality", () => { + it('generates a table of contents from headings', () => { + const mockTree = { + type: 'root', + children: [ + { type: 'element', tagName: 'h1', properties: {}, children: [{ type: 'text', value: 'Heading 1' }] }, + { type: 'element', tagName: 'h2', properties: {}, children: [{ type: 'text', value: 'Heading 2' }] }, + { type: 'element', tagName: 'h3', properties: {}, children: [{ type: 'text', value: 'Heading 3' }] }, + ], + }; + + const tocNode = addTableOfContents(mockTree, false); + expect(tocNode).not.toBeNull(); + expect(tocNode.tagName).toBe('ul'); + expect(tocNode.children.length).toBe(3); + }); + + it('inserts the table of contents into the tree', () => { + const mockTree = { + type: 'root', + children: [ + { type: 'element', tagName: 'h1', properties: {}, children: [{ type: 'text', value: 'Heading 1' }] }, + { type: 'element', tagName: 'h2', properties: {}, children: [{ type: 'text', value: 'Heading 2' }] }, + ], + }; + + addTableOfContents(mockTree, true); + expect(mockTree.children[0].tagName).toBe('ul'); + }); + + it('does not generate a table of contents for documents without headings', () => { + const mockTree = { + type: 'root', + children: [ + { type: 'element', tagName: 'p', properties: {}, children: [{ type: 'text', value: 'Paragraph text' }] }, + ], + }; + + const tocNode = addTableOfContents(mockTree, false); + expect(tocNode).not.toBeNull(); + expect(tocNode.children.length).toBe(0); + }); + +}) \ No newline at end of file diff --git a/__tests__/buildExports.test.js b/__tests__/buildExports.test.js new file mode 100644 index 0000000..12fa611 --- /dev/null +++ b/__tests__/buildExports.test.js @@ -0,0 +1,73 @@ +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +describe('Build Exports', () => { + it('should export processMarkdown for both CommonJS and ESM builds', async () => { + const cjsBuild = await import(join(__dirname, '../dist/index.cjs')); + const esmBuild = await import(join(__dirname, '../dist/index.esm.js')); + + expect(cjsBuild).toBeDefined(); + expect(typeof cjsBuild.processMarkdown).toBe('function'); + + expect(esmBuild).toBeDefined(); + expect(typeof esmBuild.processMarkdown).toBe('function'); + }); + + it('should export addHeadingIds for both CommonJS and ESM builds', async () => { + const cjsBuild = await import(join(__dirname, '../dist/index.cjs')); + const esmBuild = await import(join(__dirname, '../dist/index.esm.js')); + + expect(cjsBuild).toBeDefined(); + expect(typeof cjsBuild.addHeadingIds).toBe('function'); + + expect(esmBuild).toBeDefined(); + expect(typeof esmBuild.addHeadingIds).toBe('function'); + }); + + it('should export addTableOfContents for both CommonJS and ESM builds', async () => { + const cjsBuild = await import(join(__dirname, '../dist/index.cjs')); + const esmBuild = await import(join(__dirname, '../dist/index.esm.js')); + + expect(cjsBuild).toBeDefined(); + expect(typeof cjsBuild.addTableOfContents).toBe('function'); + + expect(esmBuild).toBeDefined(); + expect(typeof esmBuild.addTableOfContents).toBe('function'); + }); + + it('should export calculateReadingTime for both CommonJS and ESM builds', async () => { + const cjsBuild = await import(join(__dirname, '../dist/index.cjs')); + const esmBuild = await import(join(__dirname, '../dist/index.esm.js')); + + expect(cjsBuild).toBeDefined(); + expect(typeof cjsBuild.calculateReadingTime).toBe('function'); + + expect(esmBuild).toBeDefined(); + expect(typeof esmBuild.calculateReadingTime).toBe('function'); + }); + + it('should export embed for both CommonJS and ESM builds', async () => { + const cjsBuild = await import(join(__dirname, '../dist/index.cjs')); + const esmBuild = await import(join(__dirname, '../dist/index.esm.js')); + + expect(cjsBuild).toBeDefined(); + expect(typeof cjsBuild.embed).toBe('function'); + + expect(esmBuild).toBeDefined(); + expect(typeof esmBuild.embed).toBe('function'); + }); + + it('should export wrapElements for both CommonJS and ESM builds', async () => { + const cjsBuild = await import(join(__dirname, '../dist/index.cjs')); + const esmBuild = await import(join(__dirname, '../dist/index.esm.js')); + + expect(cjsBuild).toBeDefined(); + expect(typeof cjsBuild.wrapElements).toBe('function'); + + expect(esmBuild).toBeDefined(); + expect(typeof esmBuild.wrapElements).toBe('function'); + }); +}); diff --git a/__tests__/calculateReadingTime.test.js b/__tests__/calculateReadingTime.test.js new file mode 100644 index 0000000..175baf5 --- /dev/null +++ b/__tests__/calculateReadingTime.test.js @@ -0,0 +1,34 @@ +import calculateReadingTime from '../src/plugins/calculateReadingTime'; + +function generateRandomWord() { + const possibleCharacters = "abcdefghijklmnopqrstuvwxyz"; + const randomIndex = Math.floor(Math.random() * possibleCharacters.length); + return possibleCharacters[randomIndex]; +} + +// Assuming an average word count of 5 characters, this will generate 500 words +const markdownContent = new Array(500).fill(`${generateRandomWord()} `).join(""); + +describe('calculateReadingTime functionality', () => { + it('calculates reading time with default WPM', () => { + const tree = { type: 'root', children: [{ type: 'text', value: markdownContent }] }; + const readingTime = calculateReadingTime()(tree); + // Default WPM is 250, so 500 words should take about 2 minutes, rounded up to 3 minutes + expect(readingTime).toBe(3); + }); + + it('calculates reading time with custom WPM', () => { + const customWPM = 100; + const tree = { type: 'root', children: [{ type: 'text', value: markdownContent }] }; + const readingTime = calculateReadingTime({ wordsPerMinute: customWPM })(tree); + // Custom WPM is 100, so 500 words should take about 5 minutes, rounded up to 6 + expect(readingTime).toBe(6); + }); + + it('handles empty content', () => { + const tree = { type: 'root', children: [{ type: 'text', value: "" }] }; + const readingTime = calculateReadingTime()(tree); + // Why would you have an empty document? I don't know, but it gets rounded up to 1 minute. + expect(readingTime).toBe(1); + }); +}); diff --git a/__tests__/embed.test.js b/__tests__/embed.test.js new file mode 100644 index 0000000..d6ab07b --- /dev/null +++ b/__tests__/embed.test.js @@ -0,0 +1,82 @@ +import embed from "../src/plugins/embed"; + +describe("embed functionality", () => { + it('embeds a GitHub Gist correctly', () => { + const mockTree = { + type: 'root', + children: [ + { + type: 'element', + tagName: 'p', + children: [ + { type: 'text', value: '!embed ' }, + { + type: 'element', + tagName: 'a', + properties: { href: 'https://gist.github.com/user/gistid' }, + children: [{ type: 'text', value: 'Gist link' }], + }, + ], + }, + ], + }; + embed()(mockTree); + + expect(mockTree.children[0].type).toBe('raw'); + expect(mockTree.children[0].value).toContain('https://gist.github.com/user/gistid.pibb'); + }); + + + it('embeds a YouTube video correctly', () => { + const mockTree = { + type: 'root', + children: [ + { + type: 'element', + tagName: 'p', + children: [ + { type: 'text', value: '!embed ' }, + { + type: 'element', + tagName: 'a', + properties: { href: 'https://www.youtube.com/watch?v=videoId' }, + children: [{ type: 'text', value: 'YouTube link' }], + }, + ], + }, + ], + }; + + embed()(mockTree); + + expect(mockTree.children[0].type).toBe('raw'); + expect(mockTree.children[0].value).toContain('https://www.youtube.com/embed/videoId'); + }); + + it('ignores unsupported embed types', () => { + const mockTree = { + type: 'root', + children: [ + { + type: 'element', + tagName: 'p', + children: [ + { type: 'text', value: '!embed ' }, + { + type: 'element', + tagName: 'a', + properties: { href: 'https://unsupported.com' }, + children: [{ type: 'text', value: 'Unsupported link' }], + }, + ], + }, + ], + }; + + embed()(mockTree); + + expect(mockTree.children[0].type).not.toBe('raw'); + expect(mockTree.children[0].tagName).toBe('p'); + }); + +}) \ No newline at end of file diff --git a/__tests__/wrapElements.test.js b/__tests__/wrapElements.test.js new file mode 100644 index 0000000..eca4791 --- /dev/null +++ b/__tests__/wrapElements.test.js @@ -0,0 +1,76 @@ +import wrapElements from '../src/plugins/wrapElements'; + +describe('wrapElements', () => { + it('wraps specified elements with a div', () => { + const mockTree = { + type: 'root', + children: [ + { type: 'element', tagName: 'img', properties: {}, children: [] }, + { type: 'element', tagName: 'table', properties: {}, children: [] }, + ], + }; + + const options = { + img: 'epic-remark-image', + table: 'epic-remark-table', + }; + wrapElements(options)(mockTree); + + expect(mockTree.children[0].type).toBe('element'); + expect(mockTree.children[0].tagName).toBe('div'); + expect(mockTree.children[0].properties.className).toBe('epic-remark-image'); + expect(mockTree.children[1].properties.className).toBe('epic-remark-table'); + }); + + + it('does not wrap elements if options is empty', () => { + const mockTree = { + type: 'root', + children: [ + { type: 'element', tagName: 'img', properties: {}, children: [] }, + { type: 'element', tagName: 'table', properties: {}, children: [] }, + ], + }; + + const emptyOptions = {}; + wrapElements(emptyOptions)(mockTree); + + expect(mockTree.children[0].tagName).toBe('img'); + expect(mockTree.children[1].tagName).toBe('table'); + }); + + + it('handles undefined or null node children safely', () => { + const mockTree = { + type: 'root', + children: null, + }; + + const options = { + img: 'epic-remark-image', + }; + + expect(() => wrapElements(options)(mockTree)).not.toThrow(); + }); + + + it('ignores non-element nodes', () => { + const mockTree = { + type: 'root', + children: [ + { type: 'text', value: 'Just some text' }, + { type: 'element', tagName: 'img', properties: {}, children: [] }, + ], + }; + + const options = { + img: 'epic-remark-image', + }; + + wrapElements(options)(mockTree); + + expect(mockTree.children[0].type).toBe('text'); + expect(mockTree.children[1].tagName).toBe('div'); + expect(mockTree.children[1].properties.className).toBe('epic-remark-image'); + }); +});