Passes tests

This commit is contained in:
David Bolack
2026-08-25 11:42:56 -05:00
parent 3cce97de72
commit 3d7daf406d
19 changed files with 223 additions and 764 deletions
@@ -6,7 +6,7 @@ import React, { useState, useRef, useMemo, useEffect } from 'react';
import _ from 'lodash'; import _ from 'lodash';
import MarkdownLegacy from '@shared/markdownLegacy.js'; import MarkdownLegacy from '@shared/markdownLegacy.js';
import Markdown from '@shared/markdown.js'; import { default as Markdown } from 'hbfm';
import ErrorBar from './errorBar/errorBar.jsx'; import ErrorBar from './errorBar/errorBar.jsx';
import ToolBar from './toolBar/toolBar.jsx'; import ToolBar from './toolBar/toolBar.jsx';
@@ -1,7 +1,7 @@
import './notificationPopup.less'; import './notificationPopup.less';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import request from '../../utils/request-middleware.js'; import request from '../../utils/request-middleware.js';
import Markdown from '@shared/markdown.js'; import { default as Markdown } from 'hbfm';
import Dialog from '@components/dialog.jsx'; import Dialog from '@components/dialog.jsx';
@@ -44,7 +44,7 @@ const NotificationPopup = ()=>{
return notifications.map((notification)=>( return notifications.map((notification)=>(
<li key={notification.dismissKey} > <li key={notification.dismissKey} >
<em>{notification.title}</em><br /> <em>{notification.title}</em><br />
<p dangerouslySetInnerHTML={{ __html: Markdown.render(notification.text) }}></p> <p dangerouslySetInnerHTML={{ __html: hbfm.render(notification.text) }}></p>
</li> </li>
)); ));
}; };
+1 -1
View File
@@ -4,7 +4,7 @@ import './editPage.less';
// Common imports // Common imports
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import request from '../../utils/request-middleware.js'; import request from '../../utils/request-middleware.js';
import Markdown from '@shared/markdown.js'; import { default as Markdown } from 'hbfm';
import _ from 'lodash'; import _ from 'lodash';
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js'; import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
@@ -1,7 +1,7 @@
import './errorPage.less'; import './errorPage.less';
import React from 'react'; import React from 'react';
import UIPage from '../basePages/uiPage/uiPage.jsx'; import UIPage from '../basePages/uiPage/uiPage.jsx';
import Markdown from '@shared/markdown.js'; import { default as Markdown } from 'hbfm';
import ErrorIndex from './errors/errorIndex.js'; import ErrorIndex from './errors/errorIndex.js';
const ErrorPage = ({ brew })=>{ const ErrorPage = ({ brew })=>{
+1 -1
View File
@@ -4,7 +4,7 @@ import './homePage.less';
// Common imports // Common imports
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import request from '../../utils/request-middleware.js'; import request from '../../utils/request-middleware.js';
import Markdown from '@shared/markdown.js'; import { default as Markdown } from 'hbfm';
import _ from 'lodash'; import _ from 'lodash';
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js'; import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
+1 -1
View File
@@ -4,7 +4,7 @@ import './newPage.less';
// Common imports // Common imports
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import request from '../../utils/request-middleware.js'; import request from '../../utils/request-middleware.js';
import Markdown from '@shared/markdown.js'; import { default as Markdown } from 'hbfm';
import _ from 'lodash'; import _ from 'lodash';
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js'; import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
+48 -16
View File
@@ -46,6 +46,7 @@
"fflate": "^0.8.3", "fflate": "^0.8.3",
"fs-extra": "^11.3.5", "fs-extra": "^11.3.5",
"hash-wasm": "^4.12.0", "hash-wasm": "^4.12.0",
"hbfm": "file:../hbfm",
"idb-keyval": "^6.2.5", "idb-keyval": "^6.2.5",
"js-yaml": "^5.3.0", "js-yaml": "^5.3.0",
"jwt-simple": "^0.5.6", "jwt-simple": "^0.5.6",
@@ -99,6 +100,49 @@
"npm": ">=10.8 <12" "npm": ">=10.8 <12"
} }
}, },
"../hbfm": {
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"lodash": "^4.18.1",
"marked": ">=3 <16",
"marked-alignment-paragraphs": "^1.0.0",
"marked-definition-lists": "^1.0.1",
"marked-diagrams-markdeep": "^1.0.1",
"marked-emoji": "^2.0.3",
"marked-extended-tables": "^2.0.1",
"marked-gfm-heading-id": "^4.1.4",
"marked-nonbreaking-spaces": "^1.0.1",
"marked-smartypants-lite": "^1.0.3",
"marked-subsuper-text": "^1.0.4",
"marked-variables": "^1.0.5"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.0",
"@markedjs/testutils": "^15.0.0-0",
"@rollup/plugin-commonjs": "^28.0.2",
"@rollup/plugin-node-resolve": "^16.0.3",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.0",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^11.0.1",
"@semantic-release/npm": "^12.0.1",
"@semantic-release/release-notes-generator": "^14.0.2",
"babel-jest": "^29.5.0",
"eslint": "^8.57.1",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.6.0",
"jest-cli": "^29.7.0",
"rollup": "^4.29.2",
"semantic-release": "^24.2.0"
},
"peerDependencies": {
"marked": ">=3 <16"
}
},
"node_modules/@asamuzakjp/css-color": { "node_modules/@asamuzakjp/css-color": {
"version": "6.0.7", "version": "6.0.7",
"dev": true, "dev": true,
@@ -8095,6 +8139,10 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/hbfm": {
"resolved": "../hbfm",
"link": true
},
"node_modules/hookified": { "node_modules/hookified": {
"version": "1.15.1", "version": "1.15.1",
"dev": true, "dev": true,
@@ -10862,22 +10910,6 @@
"url": "https://opencollective.com/mongoose" "url": "https://opencollective.com/mongoose"
} }
}, },
"node_modules/mongoose/node_modules/gcp-metadata": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz",
"integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==",
"license": "Apache-2.0",
"optional": true,
"peer": true,
"dependencies": {
"gaxios": "^7.0.0",
"google-logging-utils": "^1.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/mongoose/node_modules/mongodb": { "node_modules/mongoose/node_modules/mongodb": {
"version": "7.5.0", "version": "7.5.0",
"license": "Apache-2.0", "license": "Apache-2.0",
+1
View File
@@ -122,6 +122,7 @@
"fflate": "^0.8.3", "fflate": "^0.8.3",
"fs-extra": "^11.3.5", "fs-extra": "^11.3.5",
"hash-wasm": "^4.12.0", "hash-wasm": "^4.12.0",
"hbfm": "file:../hbfm",
"idb-keyval": "^6.2.5", "idb-keyval": "^6.2.5",
"js-yaml": "^5.3.0", "js-yaml": "^5.3.0",
"jwt-simple": "^0.5.6", "jwt-simple": "^0.5.6",
+2 -2
View File
@@ -4,7 +4,7 @@ import { model as HomebrewModel } from './homebrew.model.js';
import express from 'express'; import express from 'express';
import zlib from 'zlib'; import zlib from 'zlib';
import GoogleActions from './googleActions.js'; import GoogleActions from './googleActions.js';
import Markdown from '../shared/markdown.js'; import { hbfm } from 'hbfm';
import * as yaml from 'js-yaml'; import * as yaml from 'js-yaml';
import asyncHandler from 'express-async-handler'; import asyncHandler from 'express-async-handler';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
@@ -225,7 +225,7 @@ const api = {
return text; return text;
}, },
getGoodBrewTitle : (text)=>{ getGoodBrewTitle : (text)=>{
const tokens = Markdown.marked.lexer(text); const tokens = hbfm.marked.lexer(text);
return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title') return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title')
.slice(0, MAX_TITLE_LENGTH); .slice(0, MAX_TITLE_LENGTH);
}, },
-574
View File
@@ -1,574 +0,0 @@
/* eslint-disable max-lines */
import _ from 'lodash';
import { marked as Marked } from 'marked';
import MarkedExtendedTables from 'marked-extended-tables';
import MarkedDefinitionLists from 'marked-definition-lists';
import MarkedAlignedParagraphs from 'marked-alignment-paragraphs';
import MarkedNonbreakingSpaces from 'marked-nonbreaking-spaces';
import MarkedSubSuperText from 'marked-subsuper-text';
import { markedVariables,
setMarkedVariablePage,
setMarkedVariable,
getMarkedVariable } from 'marked-variables';
import { markedSmartypantsLite as MarkedSmartypantsLite } from 'marked-smartypants-lite';
import { gfmHeadingId as MarkedGFMHeadingId, resetHeadings as MarkedGFMResetHeadingIDs } from 'marked-gfm-heading-id';
import { markedEmoji as MarkedEmojis } from 'marked-emoji';
import MarkedDiagramsMarkdeep from 'marked-diagrams-markdeep';
//Icon fonts included so they can appear in emoji autosuggest dropdown
import diceFont from '../themes/fonts/iconFonts/diceFont.js';
import elderberryInn from '../themes/fonts/iconFonts/elderberryInn.js';
import gameIcons from '../themes/fonts/iconFonts/gameIcons.js';
import fontAwesome from '../themes/fonts/iconFonts/fontAwesome.js';
const renderer = new Marked.Renderer();
const tokenizer = new Marked.Tokenizer();
//Processes the markdown within an HTML block if it's just a class-wrapper
renderer.html = function (token) {
let html = token.text;
if(_.startsWith(_.trim(html), '<div') && _.endsWith(_.trim(html), '</div>')){
const openTag = html.substring(0, html.indexOf('>')+1);
html = html.substring(html.indexOf('>')+1);
html = html.substring(0, html.lastIndexOf('</div>'));
// Repeat the markdown processing for content inside the div, minus the preprocessing and postprocessing hooks which should only run once globally
const opts = Marked.defaults;
const tokens = Marked.lexer(html, opts);
Marked.walkTokens(tokens, opts.walkTokens);
return `${openTag} ${Marked.parser(tokens, opts)} </div>`;
}
return html;
};
// Don't wrap {{ Spans alone on a line, or {{ Divs in <p> tags
renderer.paragraph = function(token){
let match;
const text = this.parser.parseInline(token.tokens);
if(text.startsWith('<div') || text.startsWith('</div'))
return `${text}`;
else if(match = text.match(/(^|^.*?\n)<span class="inline-block(.*?<\/span>)$/))
return `${match[1].trim() ? `<p>${match[1]}</p>` : ''}<span class="inline-block${match[2]}`;
else
return `<p>${text}</p>\n`;
};
//Fix local links in the Preview iFrame to link inside the frame
renderer.link = function (token) {
let { href, title, tokens } = token;
const text = this.parser.parseInline(tokens);
let self = false;
if(href[0] == '#') {
self = true;
}
href = cleanUrl(href);
if(href === null) {
return text;
}
let out = `<a href="${escape(href)}"`;
if(title) {
out += ` title="${escape(title)}"`;
}
// if(self) {
// out += ' target="_self"';
// }
out += `>${text}</a>`;
return out;
};
// Expose `src` attribute as `--HB_src` to make the URL accessible via CSS
renderer.image = function (token) {
const { href, title, text } = token;
if(href === null)
return text;
let out = `<img loading="lazy" src="${href}" alt="${text}" style="--HB_src:url(${href});"`;
if(title)
out += ` title="${title}"`;
out += '>';
return out;
};
// Disable default reflink behavior, as it steps on our variables extension
tokenizer.def = function () {
return undefined;
};
const mustacheSpans = {
name : 'mustacheSpans',
level : 'inline', // Is this a block-level or inline-level tokenizer?
start(src) { return src.match(/{{[^{]/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const completeSpan = /^{{[^\n]*}}/; // Regex for the complete token
const inlineRegex = /{{(?=((?:[:=](?:"['\w,\-+*/()#%=?.&:!@$^;:\[\]_= ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1 *|}}/g;
const match = completeSpan.exec(src);
if(match) {
//Find closing delimiter
let blockCount = 0;
let tags = {};
let endTags = 0;
let endToken = 0;
let delim;
while (delim = inlineRegex.exec(match[0])) {
if(_.isEmpty(tags)) {
tags = processStyleTags(delim[0].substring(2));
endTags = delim[0].length;
}
if(delim[0].startsWith('{{')) {
blockCount++;
} else if(delim[0] == '}}' && blockCount !== 0) {
blockCount--;
if(blockCount == 0) {
endToken = inlineRegex.lastIndex;
break;
}
}
}
if(endToken) {
const raw = src.slice(0, endToken);
const text = raw.slice(endTags || -2, -2);
return { // Token to generate
type : 'mustacheSpans', // Should match "name" above
raw : raw, // Text to consume from the source
text : text, // Additional custom properties
tags : tags,
tokens : this.lexer.inlineTokens(text) // inlineTokens to process **bold**, *italics*, etc.
};
}
}
},
renderer(token) {
const tags = token.tags;
tags.classes = ['inline-block', tags.classes].join(' ').trim();
return `<span` +
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
`${tags.id ? ` id="${tags.id}"` : ''}` +
`${tags.styles ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
`${tags.attributes ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
`>${this.parser.parseInline(token.tokens)}</span>`; // parseInline to turn child tokens into HTML
}
};
const mustacheDivs = {
name : 'mustacheDivs',
level : 'block',
start(src) { return src.match(/\n *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token
const blockRegex = /^ *{{(?=((?:[:=](?:"['\w,\-+*/()#%=?.&:!@$^;:\[\]_= ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1 *$|^ *}}$/gm;
const match = completeBlock.exec(src);
if(match) {
//Find closing delimiter
let blockCount = 0;
let tags = {};
let endTags = 0;
let endToken = 0;
let delim;
while (delim = blockRegex.exec(match[0])?.[0].trim()) {
if(_.isEmpty(tags)) {
tags = processStyleTags(delim.substring(2));
endTags = delim.length + src.indexOf(delim);
}
if(delim.startsWith('{{')) {
blockCount++;
} else if(delim == '}}' && blockCount !== 0) {
blockCount--;
if(blockCount == 0) {
endToken = blockRegex.lastIndex;
break;
}
}
}
if(endToken) {
const raw = src.slice(0, endToken);
const text = raw.slice(endTags || -2, -2);
return { // Token to generate
type : 'mustacheDivs', // Should match "name" above
raw : raw, // Text to consume from the source
text : text, // Additional custom properties
tags : tags,
tokens : this.lexer.blockTokens(text)
};
}
}
},
renderer(token) {
const tags = token.tags;
tags.classes = ['block', tags.classes].join(' ').trim();
return `<div` +
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
`${tags.id ? ` id="${tags.id}"` : ''}` +
`${tags.styles ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
`${tags.attributes ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
`>${this.parser.parse(token.tokens)}</div>`; // parse to turn child tokens into HTML
}
};
const mustacheInjectInline = {
name : 'mustacheInjectInline',
level : 'inline',
start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-+*/()#%=?.&:!@$^;:\[\]_= ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1}/g;
const match = inlineRegex.exec(src);
if(match) {
const lastToken = tokens[tokens.length - 1];
if(!lastToken || lastToken.type == 'mustacheInjectInline')
return false;
const tags = processStyleTags(match[1]);
lastToken.originalType = lastToken.type;
lastToken.type = 'mustacheInjectInline';
lastToken.injectedTags = tags;
return {
type : 'mustacheInjectInline', // Should match "name" above
raw : match[0], // Text to consume from the source
text : ''
};
}
},
renderer(token) {
if(!token.originalType){
return;
}
token.type = token.originalType;
const text = this.parser.parseInline([token]);
const originalTags = extractHTMLStyleTags(text);
const injectedTags = token.injectedTags;
const tags = mergeHTMLTags(originalTags, injectedTags);
const openingTag = /(<[^\s<>]+)[^\n<>]*(>.*)/s.exec(text);
if(openingTag) {
return `${openingTag[1]}` +
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
`${tags.id ? ` id="${tags.id}"` : ''}` +
`${!_.isEmpty(tags.styles) ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
`${!_.isEmpty(tags.attributes) ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
`${openingTag[2]}`; // parse to turn child tokens into HTML
}
return text;
}
};
const mustacheInjectBlock = {
extensions : [{
name : 'mustacheInjectBlock',
level : 'block',
start(src) { return src.match(/\n *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-+*/()#%=?.&:!@$^;:\[\]_= ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1}/ym;
const match = inlineRegex.exec(src);
if(match) {
const lastToken = tokens[tokens.length - 1];
if(!lastToken || lastToken.type == 'mustacheInjectBlock')
return false;
lastToken.originalType = 'mustacheInjectBlock';
lastToken.injectedTags = processStyleTags(match[1]);
return {
type : 'mustacheInjectBlock', // Should match "name" above
raw : match[0], // Text to consume from the source
text : ''
};
}
},
renderer(token) {
if(!token.originalType){
return;
}
token.type = token.originalType;
const text = this.parser.parse([token]);
const originalTags = extractHTMLStyleTags(text);
const injectedTags = token.injectedTags;
const tags = mergeHTMLTags(originalTags, injectedTags);
const openingTag = /(<[^\s<>]+)[^\n<>]*(>.*)/s.exec(text);
if(openingTag) {
return `${openingTag[1]}` +
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
`${tags.id ? ` id="${tags.id}"` : ''}` +
`${!_.isEmpty(tags.styles) ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
`${!_.isEmpty(tags.attributes) ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
`${openingTag[2]}`; // parse to turn child tokens into HTML
}
return text;
}
}],
walkTokens(token) {
// After token tree is finished, tag tokens to apply styles to so Renderer can find them
// Does not work with tables since Marked.js tables generate invalid "tokens", and changing "type" ruins Marked handling that edge-case
if(token.originalType == 'mustacheInjectBlock' && token.type !== 'table') {
token.originalType = token.type;
token.type = 'mustacheInjectBlock';
}
}
};
const forcedParagraphBreaks = {
name : 'hardBreaks',
level : 'block',
start(src) { return src.match(/\n:+$/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const regex = /^(:+)(?:\n|$)/ym;
const match = regex.exec(src);
if(match?.length) {
return {
type : 'hardBreaks', // Should match "name" above
raw : match[0], // Text to consume from the source
length : match[1].length,
text : ''
};
}
},
renderer(token) {
return `<div class='blank'></div>\n`.repeat(token.length);
}
};
// Emoji options
// To add more icon fonts, need to do these things
// 1) Add the font file as .woff2 to themes/fonts/iconFonts folder
// 2) Create a .less file mapping CSS class names to the font character
// 3) Create a .js file mapping Autosuggest names to CSS class names
// 4) Import the .less file into shared/naturalcrit/codeEditor/codeEditor.less
// 5) Import the .less file into themes/V3/blank.style.less
// 6) Import the .js file to shared/naturalcrit/codeEditor/autocompleteEmoji.js and add to `emojis` object
// 7) Import the .js file here to markdown.js, and add to `emojis` object below
const MarkedEmojiOptions = {
emojis : {
...diceFont,
...elderberryInn,
...fontAwesome,
...gameIcons,
},
renderer : (token)=>`<i class="${token.emoji}"></i>`
};
const tableTerminators = [
`:+\\n`, // hardBreak
` *{[^\n]+}`, // blockInjector
` *{{[^{\n]*\n.*?\n}}` // mustacheDiv
];
const markdeepOptions = { langs: ['asciiArt'] };
Marked.use(markedVariables());
Marked.use(MarkedDiagramsMarkdeep(markdeepOptions));
Marked.use(MarkedDefinitionLists());
Marked.use({ extensions: [forcedParagraphBreaks, mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use(mustacheInjectBlock);
Marked.use(MarkedAlignedParagraphs());
Marked.use(MarkedSubSuperText());
Marked.use(MarkedNonbreakingSpaces());
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
Marked.use(MarkedExtendedTables({ interruptPatterns: tableTerminators }), MarkedGFMHeadingId({ globalSlugs: true }),
MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions));
function cleanUrl(href) {
try {
href = encodeURI(href).replace(/%25/g, '%');
} catch {
return null;
}
return href;
}
const escapeTest = /[&<>"']/;
const escapeReplace = /[&<>"']/g;
const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
const escapeReplacements = {
'&' : '&amp;',
'<' : '&lt;',
'>' : '&gt;',
'"' : '&quot;',
'\'' : '&#39;'
};
const getEscapeReplacement = (ch)=>escapeReplacements[ch];
const escape = function (html, encode) {
if(encode) {
if(escapeTest.test(html)) {
return html.replace(escapeReplace, getEscapeReplacement);
}
} else {
if(escapeTestNoEncode.test(html)) {
return html.replace(escapeReplaceNoEncode, getEscapeReplacement);
}
}
return html;
};
const tagTypes = ['div', 'span', 'a'];
const tagRegex = new RegExp(`(${
_.map(tagTypes, (type)=>{
return `\\<${type}\\b|\\</${type}>`;
}).join('|')})`, 'g');
// Special "void" tags that can be self-closed but don't need to be.
const voidTags = new Set([
'area', 'base', 'br', 'col', 'command', 'hr', 'img',
'input', 'keygen', 'link', 'meta', 'param', 'source'
]);
const processStyleTags = (string)=>{
//split tags up. quotes can only occur right after : or =.
//TODO: can we simplify to just split on commas?
const tags = string.match(/(?:[^, ":=]+|[:=](?:"[^"]*"|))+/g);
const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0] || null;
const classes = _.remove(tags, (tag)=>(!tag.includes(':')) && (!tag.includes('='))).join(' ') || null;
const attributes = _.remove(tags, (tag)=>(tag.includes('='))).map((tag)=>tag.replace(/="?([^"]*)"?/g, '="$1"'))
?.filter((attr)=>!attr.startsWith('class="') && !attr.startsWith('style="') && !attr.startsWith('id="'))
.reduce((obj, attr)=>{
const index = attr.indexOf('=');
let [key, value] = [attr.substring(0, index), attr.substring(index + 1)];
value = value.replace(/"/g, '');
obj[key.trim()] = value.trim();
return obj;
}, {}) || null;
const styles = tags?.length ? tags.reduce((styleObj, style)=>{
const index = style.indexOf(':');
const [key, value] = [style.substring(0, index), style.substring(index + 1)];
styleObj[key.trim()] = value.replace(/"?([^"]*)"?/g, '$1').trim();
return styleObj;
}, {}) : null;
return {
id : id,
classes : classes,
styles : _.isEmpty(styles) ? null : styles,
attributes : _.isEmpty(attributes) ? null : attributes
};
};
//Given a string representing an HTML element, extract all of its properties (id, class, style, and other attributes)
const extractHTMLStyleTags = (htmlString)=>{
const firstElementOnly = htmlString.split('>')[0];
const id = firstElementOnly.match(/id="([^"]*)"/)?.[1] || null;
const classes = firstElementOnly.match(/class="([^"]*)"/)?.[1] || null;
const styles = firstElementOnly.match(/style="([^"]*)"/)?.[1]
?.split(';').reduce((styleObj, style)=>{
if(style.trim() === '') return styleObj;
const index = style.indexOf(':');
const [key, value] = [style.substring(0, index), style.substring(index + 1)];
styleObj[key.trim()] = value.trim();
return styleObj;
}, {}) || null;
const attributes = firstElementOnly.match(/[a-zA-Z]+="[^"]*"/g)
?.filter((attr)=>!attr.startsWith('class="') && !attr.startsWith('style="') && !attr.startsWith('id="'))
.reduce((obj, attr)=>{
const index = attr.indexOf('=');
const [key, value] = [attr.substring(0, index), attr.substring(index + 1)];
obj[key.trim()] = value.replace(/"/g, '');
return obj;
}, {}) || null;
return {
id : id,
classes : classes,
styles : _.isEmpty(styles) ? null : styles,
attributes : _.isEmpty(attributes) ? null : attributes
};
};
const mergeHTMLTags = (originalTags, newTags)=>{
return {
id : newTags.id || originalTags.id || null,
classes : [originalTags.classes, newTags.classes].join(' ').trim() || null,
styles : Object.assign(originalTags.styles ?? {}, newTags.styles ?? {}),
attributes : Object.assign(originalTags.attributes ?? {}, newTags.attributes ?? {})
};
};
const Markdown = {
marked : Marked,
render : (rawBrewText, pageNumber=0)=>{
setMarkedVariablePage(pageNumber);
const lastPageNumber = pageNumber > 0 ? getMarkedVariable('HB_pageNumber', pageNumber - 1) : 0;
setMarkedVariable('HB_pageNumber', //Add document variables for this page
!isNaN(Number(lastPageNumber)) ? Number(lastPageNumber) + 1 : lastPageNumber,
pageNumber);
if(pageNumber==0) MarkedGFMResetHeadingIDs();
rawBrewText = rawBrewText.replace(/^\\column(?:break)?$/gm, `\n<div class='columnSplit'></div>\n`);
const opts = Marked.defaults;
rawBrewText = opts.hooks.preprocess(rawBrewText);
const tokens = Marked.lexer(rawBrewText, opts);
Marked.walkTokens(tokens, opts.walkTokens);
const html = Marked.parser(tokens, opts);
return opts.hooks.postprocess(html);
},
validate : (rawBrewText)=>{
const errors = [];
const leftovers = _.reduce(rawBrewText.split('\n'), (acc, line, _lineNumber)=>{
const lineNumber = _lineNumber + 1;
const matches = line.match(tagRegex);
if(!matches || !matches.length) return acc;
_.each(matches, (match)=>{
_.each(tagTypes, (type)=>{
if(match == `<${type}`){
acc.push({
type : type,
line : lineNumber
});
}
if(match === `</${type}>`){
// Closing tag: Check we expect it to be closed.
// The accumulator may contain a sequence of voidable opening tags,
// over which we skip before checking validity of the close.
while (acc.length && voidTags.has(_.last(acc).type) && _.last(acc).type != type) {
acc.pop();
}
// Now check that what remains in the accumulator is valid.
if(!acc.length){
errors.push({
line : lineNumber,
type : type,
text : 'Unmatched closing tag',
id : 'CLOSE'
});
} else if(_.last(acc).type == type){
acc.pop();
} else {
errors.push({
line : `${_.last(acc).line} to ${lineNumber}`,
type : type,
text : 'Type mismatch on closing tag',
id : 'MISMATCH'
});
acc.pop();
}
}
});
});
return acc;
}, []);
_.each(leftovers, (unmatched)=>{
errors.push({
line : unmatched.line,
type : unmatched.type,
text : 'Unmatched opening tag',
id : 'OPEN'
});
});
return errors;
},
};
export default Markdown;
+3 -3
View File
@@ -1,10 +1,10 @@
import Markdown from '../../shared/markdown.js'; import { hbfm } from 'hbfm';
test('Processes the markdown within an HTML block if its just a class wrapper', function() { test('Processes the markdown within an HTML block if its just a class wrapper', function() {
const source = '<div>*Bold text*</div>'; const source = '<div>*Bold text*</div>';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered).toBe('<div> <p><em>Bold text</em></p>\n </div>'); expect(rendered).toBe('<div> <p><em>Bold text</em></p>\n </div>');
}); });
@@ -12,6 +12,6 @@ test('Processes the markdown within an HTML block if its just a class wrapper',
// //
// test('Check markdown is using the custom renderer; specifically that it adds target=_self attribute to internal links in HTML blocks', function() { // test('Check markdown is using the custom renderer; specifically that it adds target=_self attribute to internal links in HTML blocks', function() {
// const source = '<div>[Has _self Attribute?](#p1)</div>'; // const source = '<div>[Has _self Attribute?](#p1)</div>';
// const rendered = Markdown.render(source); // const rendered = hbfm.render(source);
// expect(rendered).toBe('<div> <p><a href="#p1" target="_self">Has _self Attribute?</a></p>\n </div>'); // expect(rendered).toBe('<div> <p><a href="#p1" target="_self">Has _self Attribute?</a></p>\n </div>');
// }); // });
+17 -17
View File
@@ -1,23 +1,23 @@
import Markdown from '../../shared/markdown.js'; import { hbfm } from 'hbfm';
describe('Inline Definition Lists', ()=>{ describe('Inline Definition Lists', ()=>{
test('No Term 1 Definition', function() { test('No Term 1 Definition', function() {
const source = ':: My First Definition\n\n'; const source = ':: My First Definition\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt></dt><dd>My First Definition</dd>\n</dl>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt></dt><dd>My First Definition</dd>\n</dl>');
}); });
test('Single Definition Term', function() { test('Single Definition Term', function() {
const source = 'My term :: My First Definition\n\n'; const source = 'My term :: My First Definition\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>My term</dt><dd>My First Definition</dd>\n</dl>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>My term</dt><dd>My First Definition</dd>\n</dl>');
}); });
test('Multiple Definition Terms', function() { test('Multiple Definition Terms', function() {
const source = 'Term 1::Definition of Term 1\nTerm 2::Definition of Term 2\n\n'; const source = 'Term 1::Definition of Term 1\nTerm 2::Definition of Term 2\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt><dd>Definition of Term 1</dd>\n<dt>Term 2</dt><dd>Definition of Term 2</dd>\n</dl>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt><dd>Definition of Term 1</dd>\n<dt>Term 2</dt><dd>Definition of Term 2</dd>\n</dl>');
}); });
}); });
@@ -25,79 +25,79 @@ describe('Inline Definition Lists', ()=>{
describe('Multiline Definition Lists', ()=>{ describe('Multiline Definition Lists', ()=>{
test('Single Term, Single Definition', function() { test('Single Term, Single Definition', function() {
const source = 'Term 1\n::Definition 1\n\n'; const source = 'Term 1\n::Definition 1\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd></dl>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd></dl>');
}); });
test('Single Term, Plural Definitions', function() { test('Single Term, Plural Definitions', function() {
const source = 'Term 1\n::Definition 1\n::Definition 2\n\n'; const source = 'Term 1\n::Definition 1\n::Definition 2\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>');
}); });
test('Multiple Term, Single Definitions', function() { test('Multiple Term, Single Definitions', function() {
const source = 'Term 1\n::Definition 1\n\nTerm 2\n::Definition 1\n\n'; const source = 'Term 1\n::Definition 1\n\nTerm 2\n::Definition 1\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd></dl>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd></dl>');
}); });
test('Multiple Term, Plural Definitions', function() { test('Multiple Term, Plural Definitions', function() {
const source = 'Term 1\n::Definition 1\n::Definition 2\n\nTerm 2\n::Definition 1\n::Definition 2\n\n'; const source = 'Term 1\n::Definition 1\n::Definition 2\n\nTerm 2\n::Definition 1\n::Definition 2\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>');
}); });
test('Single Term, Single multi-line definition', function() { test('Single Term, Single multi-line definition', function() {
const source = 'Term 1\n::Definition 1\nand more and\nmore and more\n\n'; const source = 'Term 1\n::Definition 1\nand more and\nmore and more\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more and more</dd></dl>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more and more</dd></dl>');
}); });
test('Single Term, Plural multi-line definitions', function() { test('Single Term, Plural multi-line definitions', function() {
const source = 'Term 1\n::Definition 1\nand more and more\n::Definition 2\nand more\nand more\n::Definition 3\n\n'; const source = 'Term 1\n::Definition 1\nand more and more\n::Definition 2\nand more\nand more\n::Definition 3\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dd>Definition 2 and more and more</dd>\n<dd>Definition 3</dd></dl>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dd>Definition 2 and more and more</dd>\n<dd>Definition 3</dd></dl>');
}); });
test('Multiple Term, Single multi-line definition', function() { test('Multiple Term, Single multi-line definition', function() {
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n'; const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>');
}); });
test('Multiple Term, Single multi-line definition, followed by an inline dl', function() { test('Multiple Term, Single multi-line definition, followed by an inline dl', function() {
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n::Inline Definition (no term)'; const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n::Inline Definition (no term)';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><dl><dt></dt><dd>Inline Definition (no term)</dd>\n</dl>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><dl><dt></dt><dd>Inline Definition (no term)</dd>\n</dl>');
}); });
test('Multiple Term, Single multi-line definition, followed by paragraph', function() { test('Multiple Term, Single multi-line definition, followed by paragraph', function() {
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\nParagraph'; const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\nParagraph';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><p>Paragraph</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><p>Paragraph</p>');
}); });
test('Block Token cannot be the Term of a multi-line definition', function() { test('Block Token cannot be the Term of a multi-line definition', function() {
const source = '## Header\n::Definition 1 of a single-line DL\n::Definition 1 of another single-line DL'; const source = '## Header\n::Definition 1 of a single-line DL\n::Definition 1 of another single-line DL';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 id="header">Header</h2>\n<dl><dt></dt><dd>Definition 1 of a single-line DL</dd>\n<dt></dt><dd>Definition 1 of another single-line DL</dd>\n</dl>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 id="header">Header</h2>\n<dl><dt></dt><dd>Definition 1 of a single-line DL</dd>\n<dt></dt><dd>Definition 1 of another single-line DL</dd>\n</dl>');
}); });
test('Inline DL has priority over Multiline', function() { test('Inline DL has priority over Multiline', function() {
const source = 'Term 1 :: Inline definition 1\n:: Inline definition 2 (no DT)'; const source = 'Term 1 :: Inline definition 1\n:: Inline definition 2 (no DT)';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt><dd>Inline definition 1</dd>\n<dt></dt><dd>Inline definition 2 (no DT)</dd>\n</dl>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt><dd>Inline definition 1</dd>\n<dt></dt><dd>Inline definition 2 (no DT)</dd>\n</dl>');
}); });
test('Multiline Definition Term must have at least one non-empty Definition', function() { test('Multiline Definition Term must have at least one non-empty Definition', function() {
const source = 'Term 1\n::'; const source = 'Term 1\n::';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div>\n<div class='blank'></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div>\n<div class='blank'></div>`);
}); });
test('Multiline Definition List must have at least one non-newline character after ::', function() { test('Multiline Definition List must have at least one non-newline character after ::', function() {
const source = 'Term 1\n::\nDefinition 1\n\n'; const source = 'Term 1\n::\nDefinition 1\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div>\n<div class='blank'></div>\n<p>Definition 1</p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Term 1</p>\n<div class='blank'></div>\n<div class='blank'></div>\n<p>Definition 1</p>`);
}); });
}); });
+8 -8
View File
@@ -1,4 +1,4 @@
import Markdown from '../../shared/markdown.js'; import { hbfm } from 'hbfm';
import dedent from 'dedent'; import dedent from 'dedent';
// Marked.js adds line returns after closing tags on some default tokens. // Marked.js adds line returns after closing tags on some default tokens.
@@ -12,37 +12,37 @@ const emoji = 'df_d12_2';
describe(`When emojis/icons are active`, ()=>{ describe(`When emojis/icons are active`, ()=>{
it('when a word is between two colons (:word:), and a matching emoji exists, it is rendered as an emoji', function() { it('when a word is between two colons (:word:), and a matching emoji exists, it is rendered as an emoji', function() {
const source = `:${emoji}:`; const source = `:${emoji}:`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><i class="df d12-2"></i></p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><i class="df d12-2"></i></p>`);
}); });
it('when a word is between two colons (:word:), and no matching emoji exists, it is not parsed', function() { it('when a word is between two colons (:word:), and no matching emoji exists, it is not parsed', function() {
const source = `:invalid:`; const source = `:invalid:`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>:invalid:</p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>:invalid:</p>`);
}); });
it('two valid emojis with no whitespace are prioritized over definition lists', function() { it('two valid emojis with no whitespace are prioritized over definition lists', function() {
const source = `:${emoji}::${emoji}:`; const source = `:${emoji}::${emoji}:`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><i class="df d12-2"></i><i class="df d12-2"></i></p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><i class="df d12-2"></i><i class="df d12-2"></i></p>`);
}); });
it('definition lists that are not also part of an emoji can coexist with normal emojis', function() { it('definition lists that are not also part of an emoji can coexist with normal emojis', function() {
const source = `definition :: term ${emoji}::${emoji}:`; const source = `definition :: term ${emoji}::${emoji}:`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>definition</dt><dd>term df_d12_2:<i class="df d12-2"></i></dd></dl>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>definition</dt><dd>term df_d12_2:<i class="df d12-2"></i></dd></dl>`);
}); });
it('A valid emoji is compatible with curly injectors', function() { it('A valid emoji is compatible with curly injectors', function() {
const source = `:${emoji}:{color:blue,myClass}`; const source = `:${emoji}:{color:blue,myClass}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><i class="df d12-2 myClass" style="color:blue;"></i></p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><i class="df d12-2 myClass" style="color:blue;"></i></p>`);
}); });
it('Emojis are not parsed inside of curly span CSS blocks', function() { it('Emojis are not parsed inside of curly span CSS blocks', function() {
const source = `{{color:${emoji} text}}`; const source = `{{color:${emoji} text}}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="color:df_d12_2;">text</span>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="color:df_d12_2;">text</span>`);
}); });
@@ -50,7 +50,7 @@ describe(`When emojis/icons are active`, ()=>{
const source = dedent`{{color:${emoji} const source = dedent`{{color:${emoji}
text text
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:df_d12_2;"><p>text</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:df_d12_2;"><p>text</p></div>`);
}); });
+8 -8
View File
@@ -1,47 +1,47 @@
import Markdown from '../../shared/markdown.js'; import { hbfm } from 'hbfm';
describe('Hard Breaks', ()=>{ describe('Hard Breaks', ()=>{
test('Single Break', function() { test('Single Break', function() {
const source = ':\n\n'; const source = ':\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>`);
}); });
test('Double Break', function() { test('Double Break', function() {
const source = '::\n\n'; const source = '::\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>`);
}); });
test('Triple Break', function() { test('Triple Break', function() {
const source = ':::\n\n'; const source = ':::\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
}); });
test('Many Break', function() { test('Many Break', function() {
const source = '::::::::::\n\n'; const source = '::::::::::\n\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
}); });
test('Multiple sets of Breaks', function() { test('Multiple sets of Breaks', function() {
const source = ':::\n:::\n:::'; const source = ':::\n:::\n:::';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>\n<div class='blank'></div>`);
}); });
test('Break directly between two paragraphs', function() { test('Break directly between two paragraphs', function() {
const source = 'Line 1\n::\nLine 2'; const source = 'Line 1\n::\nLine 2';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Line 1</p>\n<div class='blank'></div>\n<div class='blank'></div>\n<p>Line 2</p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>Line 1</p>\n<div class='blank'></div>\n<div class='blank'></div>\n<p>Line 2</p>`);
}); });
test('Ignored inside a code block', function() { test('Ignored inside a code block', function() {
const source = '```\n\n:\n\n```\n'; const source = '```\n\n:\n\n```\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<pre><code>\n:\n</code></pre>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<pre><code>\n:\n</code></pre>`);
}); });
}); });
+67 -67
View File
@@ -1,7 +1,7 @@
/* eslint-disable max-lines */ /* eslint-disable max-lines */
import dedent from 'dedent'; import dedent from 'dedent';
import Markdown from '../../shared/markdown.js'; import { hbfm } from 'hbfm';
// Marked.js adds line returns after closing tags on some default tokens. // Marked.js adds line returns after closing tags on some default tokens.
// This removes those line returns for comparison sake. // This removes those line returns for comparison sake.
@@ -15,112 +15,112 @@ String.prototype.trimReturns = function(){
describe('Inline: When using the Inline syntax {{ }}', ()=>{ describe('Inline: When using the Inline syntax {{ }}', ()=>{
it('Renders a mustache span with text only', function() { it('Renders a mustache span with text only', function() {
const source = '{{ text}}'; const source = '{{ text}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">text</span>');
}); });
it('Renders a mustache span with text only, but with spaces', function() { it('Renders a mustache span with text only, but with spaces', function() {
const source = '{{ this is a text}}'; const source = '{{ this is a text}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">this is a text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">this is a text</span>');
}); });
it('Renders an empty mustache span', function() { it('Renders an empty mustache span', function() {
const source = '{{}}'; const source = '{{}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>');
}); });
it('Renders a mustache span with just a space', function() { it('Renders a mustache span with just a space', function() {
const source = '{{ }}'; const source = '{{ }}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>');
}); });
it('Renders a mustache span with a few spaces only', function() { it('Renders a mustache span with a few spaces only', function() {
const source = '{{ }}'; const source = '{{ }}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>');
}); });
it('Renders a mustache span with text and class', function() { it('Renders a mustache span with text and class', function() {
const source = '{{my-class text}}'; const source = '{{my-class text}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class">text</span>');
}); });
it('Renders a mustache span with text and two classes', function() { it('Renders a mustache span with text and two classes', function() {
const source = '{{my-class,my-class2 text}}'; const source = '{{my-class,my-class2 text}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class my-class2">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class my-class2">text</span>');
}); });
it('Renders a mustache span with text with spaces and class', function() { it('Renders a mustache span with text with spaces and class', function() {
const source = '{{my-class this is a text}}'; const source = '{{my-class this is a text}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class">this is a text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class">this is a text</span>');
}); });
it('Renders a mustache span with text and id', function() { it('Renders a mustache span with text and id', function() {
const source = '{{#my-span text}}'; const source = '{{#my-span text}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="my-span">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="my-span">text</span>');
}); });
it('Renders a mustache span with text and two ids', function() { it('Renders a mustache span with text and two ids', function() {
const source = '{{#my-span,#my-favorite-span text}}'; const source = '{{#my-span,#my-favorite-span text}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="my-span">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="my-span">text</span>');
}); });
it('Renders a mustache span with text and css property', function() { it('Renders a mustache span with text and css property', function() {
const source = '{{color:red text}}'; const source = '{{color:red text}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>');
}); });
it('Renders a mustache span with text and two css properties', function() { it('Renders a mustache span with text and two css properties', function() {
const source = '{{color:red,padding:5px text}}'; const source = '{{color:red,padding:5px text}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; padding:5px;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; padding:5px;">text</span>');
}); });
it('Renders a mustache span with text and css property which contains quotes', function() { it('Renders a mustache span with text and css property which contains quotes', function() {
const source = '{{font-family:"trebuchet ms" text}}'; const source = '{{font-family:"trebuchet ms" text}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms;">text</span>');
}); });
it('Renders a mustache span with text and two css properties which contains quotes', function() { it('Renders a mustache span with text and two css properties which contains quotes', function() {
const source = '{{font-family:"trebuchet ms",padding:"5px 10px" text}}'; const source = '{{font-family:"trebuchet ms",padding:"5px 10px" text}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms; padding:5px 10px;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms; padding:5px 10px;">text</span>');
}); });
it('Renders a mustache span with text with quotes and css property which contains double quotes', function() { it('Renders a mustache span with text with quotes and css property which contains double quotes', function() {
const source = '{{font-family:"trebuchet ms" text "with quotes"}}'; const source = '{{font-family:"trebuchet ms" text "with quotes"}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms;">text “with quotes”</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms;">text “with quotes”</span>');
}); });
it('Renders a mustache span with text with quotes and css property which contains double and simple quotes', function() { it('Renders a mustache span with text with quotes and css property which contains double and simple quotes', function() {
const source = `{{--stringVariable:"'string'" text "with quotes"}}`; const source = `{{--stringVariable:"'string'" text "with quotes"}}`;
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text “with quotes”</span>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text “with quotes”</span>`);
}); });
it('Renders a mustache span with text, id, class and a couple of css properties', function() { it('Renders a mustache span with text, id, class and a couple of css properties', function() {
const source = '{{pen,#author,color:orange,font-family:"trebuchet ms" text}}'; const source = '{{pen,#author,color:orange,font-family:"trebuchet ms" text}}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;">text</span>');
}); });
it('Renders a span with added attributes', function() { it('Renders a span with added attributes', function() {
const source = 'Text and {{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e, text}} and more text!'; const source = 'Text and {{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e, text}} and more text!';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Text and <span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;" a="b and c" d="e">text</span> and more text!</p>\n'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Text and <span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;" a="b and c" d="e">text</span> and more text!</p>\n');
}); });
}); });
@@ -132,7 +132,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{ const source = dedent`{{
text text
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block"><p>text</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block"><p>text</p></div>`);
}); });
@@ -140,14 +140,14 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{ const source = dedent`{{
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block"></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block"></div>`);
}); });
it('Renders a single paragraph with opening and closing brackets', function() { it('Renders a single paragraph with opening and closing brackets', function() {
const source = dedent`{{ const source = dedent`{{
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>{{}}</p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>{{}}</p>`);
}); });
@@ -155,7 +155,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{cat const source = dedent`{{cat
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat"></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat"></div>`);
}); });
@@ -163,7 +163,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{cat const source = dedent`{{cat
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat"><p>Sample text.</p></div>`);
}); });
@@ -171,7 +171,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{cat,dog const source = dedent`{{cat,dog
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat dog"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat dog"><p>Sample text.</p></div>`);
}); });
@@ -179,7 +179,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{color:red const source = dedent`{{color:red
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red;"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red;"><p>Sample text.</p></div>`);
}); });
@@ -187,7 +187,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{--stringVariable:"'string'" const source = dedent`{{--stringVariable:"'string'"
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`);
}); });
@@ -195,7 +195,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{--stringVariable:"'string'" const source = dedent`{{--stringVariable:"'string'"
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`);
}); });
@@ -203,7 +203,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{cat,color:red const source = dedent`{{cat,color:red
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat" style="color:red;"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat" style="color:red;"><p>Sample text.</p></div>`);
}); });
@@ -211,7 +211,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{color:red,cat,#dog const source = dedent`{{color:red,cat,#dog
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat" id="dog" style="color:red;"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat" id="dog" style="color:red;"><p>Sample text.</p></div>`);
}); });
@@ -219,7 +219,7 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{#cat,#dog const source = dedent`{{#cat,#dog
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" id="cat"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" id="cat"><p>Sample text.</p></div>`);
}); });
@@ -227,13 +227,13 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{color:red,cat,#dog,a="b and c",d="e" const source = dedent`{{color:red,cat,#dog,a="b and c",d="e"
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class=\"block cat\" id=\"dog\" style=\"color:red;\" a=\"b and c\" d=\"e\"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class=\"block cat\" id=\"dog\" style=\"color:red;\" a=\"b and c\" d=\"e\"><p>Sample text.</p></div>`);
}); });
it('Renders a div with added attributes', function() { it('Renders a div with added attributes', function() {
const source = '{{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e\nText and text and more text!\n}}\n'; const source = '{{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e\nText and text and more text!\n}}\n';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block pen" id="author" style="color:orange; font-family:trebuchet ms;" a="b and c" d="e"><p>Text and text and more text!</p>\n</div>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block pen" id="author" style="color:orange; font-family:trebuchet ms;" a="b and c" d="e"><p>Text and text and more text!</p>\n</div>');
}); });
}); });
@@ -245,116 +245,116 @@ describe('Injection: When an injection tag follows an element', ()=>{
describe('and that element is an inline-block', ()=>{ describe('and that element is an inline-block', ()=>{
it('Renders a span "text" with no injection', function() { it('Renders a span "text" with no injection', function() {
const source = '{{ text}}{}'; const source = '{{ text}}{}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">text</span>');
}); });
it('Renders a span "text" with injected Class name', function() { it('Renders a span "text" with injected Class name', function() {
const source = '{{ text}}{ClassName}'; const source = '{{ text}}{ClassName}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block ClassName">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block ClassName">text</span>');
}); });
it('Renders a span "text" with injected attribute', function() { it('Renders a span "text" with injected attribute', function() {
const source = '{{ text}}{a="b and c"}'; const source = '{{ text}}{a="b and c"}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" a="b and c">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" a="b and c">text</span>');
}); });
it('Renders a span "text" with injected style', function() { it('Renders a span "text" with injected style', function() {
const source = '{{ text}}{color:red}'; const source = '{{ text}}{color:red}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>');
}); });
it('Renders a span "text" with injected style using a string variable', function() { it('Renders a span "text" with injected style using a string variable', function() {
const source = `{{ text}}{--stringVariable:"'string'"}`; const source = `{{ text}}{--stringVariable:"'string'"}`;
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text</span>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text</span>`);
}); });
it('Renders a span "text" with two injected styles', function() { it('Renders a span "text" with two injected styles', function() {
const source = '{{ text}}{color:red,background:blue}'; const source = '{{ text}}{color:red,background:blue}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; background:blue;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; background:blue;">text</span>');
}); });
it('Renders a span "text" with its own ID, overwritten with an injected ID', function() { it('Renders a span "text" with its own ID, overwritten with an injected ID', function() {
const source = '{{#oldId text}}{#newId}'; const source = '{{#oldId text}}{#newId}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="newId">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="newId">text</span>');
}); });
it('Renders a span "text" with its own attributes, overwritten with an injected attribute, plus a new one', function() { it('Renders a span "text" with its own attributes, overwritten with an injected attribute, plus a new one', function() {
const source = '{{attrA="old",attrB="old" text}}{attrA="new",attrC="new"}'; const source = '{{attrA="old",attrB="old" text}}{attrA="new",attrC="new"}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" attrA="new" attrB="old" attrC="new">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" attrA="new" attrB="old" attrC="new">text</span>');
}); });
it('Renders a span "text" with its own attributes, overwritten with an injected attribute, ignoring "class", "style", and "id"', function() { it('Renders a span "text" with its own attributes, overwritten with an injected attribute, ignoring "class", "style", and "id"', function() {
const source = '{{attrA="old",attrB="old" text}}{attrA="new",attrC="new",class="new",style="new",id="new"}'; const source = '{{attrA="old",attrB="old" text}}{attrA="new",attrC="new",class="new",style="new",id="new"}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" attrA="new" attrB="old" attrC="new">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" attrA="new" attrB="old" attrC="new">text</span>');
}); });
it('Renders a span "text" with its own styles, appended with injected styles', function() { it('Renders a span "text" with its own styles, appended with injected styles', function() {
const source = '{{color:blue,height:10px text}}{width:10px,color:red}'; const source = '{{color:blue,height:10px text}}{width:10px,color:red}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; height:10px; width:10px;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; height:10px; width:10px;">text</span>');
}); });
it('Renders a span "text" with its own classes, appended with injected classes', function() { it('Renders a span "text" with its own classes, appended with injected classes', function() {
const source = '{{classA,classB text}}{classA,classC}'; const source = '{{classA,classB text}}{classA,classC}';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block classA classB classA classC">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block classA classB classA classC">text</span>');
}); });
it('Renders an emphasis element with injected Class name', function() { it('Renders an emphasis element with injected Class name', function() {
const source = '*emphasis*{big}'; const source = '*emphasis*{big}';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><em class="big">emphasis</em></p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><em class="big">emphasis</em></p>');
}); });
it('Renders a code element with injected style', function() { it('Renders a code element with injected style', function() {
const source = '`code`{background:gray}'; const source = '`code`{background:gray}';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><code style="background:gray;">code</code></p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><code style="background:gray;">code</code></p>');
}); });
it('Renders an image element with injected style', function() { it('Renders an image element with injected style', function() {
const source = '![alt text](https://i.imgur.com/hMna6G0.png){position:absolute}'; const source = '![alt text](https://i.imgur.com/hMna6G0.png){position:absolute}';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png); position:absolute;" loading="lazy" src="https://i.imgur.com/hMna6G0.png" alt="alt text"></p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png); position:absolute;" loading="lazy" src="https://i.imgur.com/hMna6G0.png" alt="alt text"></p>');
}); });
it('Renders an element modified by only the first of two consecutive injections', function() { it('Renders an element modified by only the first of two consecutive injections', function() {
const source = '{{ text}}{color:red}{background:blue}'; const source = '{{ text}}{color:red}{background:blue}';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><span class="inline-block" style="color:red;">text</span>{background:blue}</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><span class="inline-block" style="color:red;">text</span>{background:blue}</p>');
}); });
it('Renders an parent and child element, each modified by an injector', function() { it('Renders an parent and child element, each modified by an injector', function() {
const source = dedent`**bolded text**{color:red} const source = dedent`**bolded text**{color:red}
{color:blue}`; {color:blue}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p style="color:blue;"><strong style="color:red;">bolded text</strong></p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p style="color:blue;"><strong style="color:red;">bolded text</strong></p>');
}); });
it('Renders an image with added attributes', function() { it('Renders an image with added attributes', function() {
const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`; const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png); position:absolute; bottom:20px; left:130px; width:220px;" loading="lazy" src="https://i.imgur.com/hMna6G0.png" alt="homebrew mug" a="b and c" d="e"></p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png); position:absolute; bottom:20px; left:130px; width:220px;" loading="lazy" src="https://i.imgur.com/hMna6G0.png" alt="homebrew mug" a="b and c" d="e"></p>`);
}); });
it('Renders an image with "=" in the url, and added attributes', function() { it('Renders an image with "=" in the url, and added attributes', function() {
const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png?auth=12345&height=1024) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`; const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png?auth=12345&height=1024) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png?auth=12345&height=1024); position:absolute; bottom:20px; left:130px; width:220px;" loading="lazy" src="https://i.imgur.com/hMna6G0.png?auth=12345&height=1024" alt="homebrew mug" a="b and c" d="e"></p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png?auth=12345&height=1024); position:absolute; bottom:20px; left:130px; width:220px;" loading="lazy" src="https://i.imgur.com/hMna6G0.png?auth=12345&height=1024" alt="homebrew mug" a="b and c" d="e"></p>`);
}); });
it('Renders an image and added attributes with "=" in the value, ', function() { it('Renders an image and added attributes with "=" in the value, ', function() {
const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e,otherUrl="url?auth=12345"}`; const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e,otherUrl="url?auth=12345"}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png); position:absolute; bottom:20px; left:130px; width:220px;" loading="lazy" src="https://i.imgur.com/hMna6G0.png" alt="homebrew mug" a="b and c" d="e" otherUrl="url?auth=12345"></p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img style="--HB_src:url(https://i.imgur.com/hMna6G0.png); position:absolute; bottom:20px; left:130px; width:220px;" loading="lazy" src="https://i.imgur.com/hMna6G0.png" alt="homebrew mug" a="b and c" d="e" otherUrl="url?auth=12345"></p>`);
}); });
}); });
@@ -362,19 +362,19 @@ describe('Injection: When an injection tag follows an element', ()=>{
describe('and that element is a block', ()=>{ describe('and that element is a block', ()=>{
it('renders a div "text" with no injection', function() { it('renders a div "text" with no injection', function() {
const source = '{{\ntext\n}}\n{}'; const source = '{{\ntext\n}}\n{}';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block"><p>text</p></div>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block"><p>text</p></div>');
}); });
it('renders a div "text" with injected Class name', function() { it('renders a div "text" with injected Class name', function() {
const source = '{{\ntext\n}}\n{ClassName}'; const source = '{{\ntext\n}}\n{ClassName}';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block ClassName"><p>text</p></div>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block ClassName"><p>text</p></div>');
}); });
it('renders a div "text" with injected style', function() { it('renders a div "text" with injected style', function() {
const source = '{{\ntext\n}}\n{color:red}'; const source = '{{\ntext\n}}\n{color:red}';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:red;"><p>text</p></div>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:red;"><p>text</p></div>');
}); });
@@ -383,7 +383,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
text text
}} }}
{color:red,background:blue}`; {color:red,background:blue}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red; background:blue;"><p>text</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red; background:blue;"><p>text</p></div>`);
}); });
@@ -392,7 +392,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
text text
}} }}
{--stringVariable:"'string'"}`; {--stringVariable:"'string'"}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>text</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>text</p></div>`);
}); });
@@ -401,7 +401,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
text text
}} }}
{#newId}`; {#newId}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" id="newId"><p>text</p></div>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" id="newId"><p>text</p></div>');
}); });
@@ -410,7 +410,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
text text
}} }}
{attrA="new",attrC="new"}`; {attrA="new",attrC="new"}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" attrA="new" attrB="old" attrC="new"><p>text</p></div>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" attrA="new" attrB="old" attrC="new"><p>text</p></div>');
}); });
@@ -419,7 +419,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
text text
}} }}
{attrA="new",attrC="new",class="new",style="new",id="new"}`; {attrA="new",attrC="new",class="new",style="new",id="new"}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" attrA="new" attrB="old" attrC="new"><p>text</p></div>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" attrA="new" attrB="old" attrC="new"><p>text</p></div>');
}); });
@@ -428,7 +428,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
text text
}} }}
{width:10px,color:red}`; {width:10px,color:red}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:red; height:10px; width:10px;"><p>text</p></div>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:red; height:10px; width:10px;"><p>text</p></div>');
}); });
@@ -437,14 +437,14 @@ describe('Injection: When an injection tag follows an element', ()=>{
text text
}} }}
{classA,classC}`; {classA,classC}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block classA classB classA classC"><p>text</p></div>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block classA classB classA classC"><p>text</p></div>');
}); });
it('renders an h2 header "text" with injected class name', function() { it('renders an h2 header "text" with injected class name', function() {
const source = dedent`## text const source = dedent`## text
{ClassName}`; {ClassName}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 class="ClassName" id="text">text</h2>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 class="ClassName" id="text">text</h2>');
}); });
@@ -455,7 +455,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
| 300 | 2 | | 300 | 2 |
{ClassName}`; {ClassName}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<table class="ClassName"><thead><tr><th align=left>Experience Points</th><th align=center>Level</th></tr></thead><tbody><tr><td align=left>0</td><td align=center>1</td></tr><tr><td align=left>300</td><td align=center>2</td></tr></tbody></table>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<table class="ClassName"><thead><tr><th align=left>Experience Points</th><th align=center>Level</th></tr></thead><tbody><tr><td align=left>0</td><td align=center>1</td></tr><tr><td align=left>300</td><td align=center>2</td></tr></tbody></table>`);
}); });
@@ -466,7 +466,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
// - Dark Chant of the Dentists // - Dark Chant of the Dentists
// - Divine Spell of Crossdressing // - Divine Spell of Crossdressing
// {color:red}`; // {color:red}`;
// const rendered = Markdown.render(source).trimReturns(); // const rendered = hbfm.render(source).trimReturns();
// expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`...`); // FIXME: expect this to be injected into <ul>? Currently injects into last <li> // expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`...`); // FIXME: expect this to be injected into <ul>? Currently injects into last <li>
// }); // });
@@ -474,7 +474,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
const source = dedent`## text const source = dedent`## text
{ClassName} {ClassName}
{secondInjection}`; {secondInjection}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 class="ClassName" id="text">text</h2><p>{secondInjection}</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 class="ClassName" id="text">text</h2><p>{secondInjection}</p>');
}); });
@@ -487,7 +487,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
{innerDiv} {innerDiv}
}} }}
{outerDiv}`; {outerDiv}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block outerDiv"><p>outer text</p><div class="block innerDiv"><p>inner text</p></div></div>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block outerDiv"><p>outer text</p><div class="block innerDiv"><p>inner text</p></div></div>');
}); });
}); });
+4 -4
View File
@@ -1,23 +1,23 @@
import Markdown from '../../shared/markdown.js'; import {hbfm} from 'hbfm';
describe('Non-Breaking Spaces Interactions', ()=>{ describe('Non-Breaking Spaces Interactions', ()=>{
test('I am actually a single-line definition list!', function() { test('I am actually a single-line definition list!', function() {
const source = 'Term ::> Definition 1\n'; const source = 'Term ::> Definition 1\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt><dd>&gt; Definition 1</dd>\n</dl>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt><dd>&gt; Definition 1</dd>\n</dl>`);
}); });
test('I am actually a definition list!', function() { test('I am actually a definition list!', function() {
const source = 'Term\n::> Definition 1\n'; const source = 'Term\n::> Definition 1\n';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt>\n<dd>&gt; Definition 1</dd></dl>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt>\n<dd>&gt; Definition 1</dd></dl>`);
}); });
test('I am actually a two-term definition list!', function() { test('I am actually a two-term definition list!', function() {
const source = 'Term\n::> Definition 1\n::>> Definition 2'; const source = 'Term\n::> Definition 1\n::>> Definition 2';
const rendered = Markdown.render(source).trim(); const rendered = hbfm.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt>\n<dd>&gt; Definition 1</dd>\n<dd>&gt;&gt; Definition 2</dd></dl>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<dl><dt>Term</dt>\n<dd>&gt; Definition 1</dd>\n<dd>&gt;&gt; Definition 2</dd></dl>`);
}); });
}); });
@@ -1,27 +1,27 @@
import Markdown from '../../shared/markdown.js'; import { hbfm } from 'hbfm';
describe('Justification', ()=>{ describe('Justification', ()=>{
test('Left Justify', function() { test('Left Justify', function() {
const source = ':- Hello'; const source = ':- Hello';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Left\">Hello</p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Left\">Hello</p>`);
}); });
test('Right Justify', function() { test('Right Justify', function() {
const source = '-: Hello'; const source = '-: Hello';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Right\">Hello</p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Right\">Hello</p>`);
}); });
test('Center Justify', function() { test('Center Justify', function() {
const source = ':-: Hello'; const source = ':-: Hello';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Center\">Hello</p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p align=\"Center\">Hello</p>`);
}); });
test('Ignored inside a code block', function() { test('Ignored inside a code block', function() {
const source = '```\n\n:- Hello\n\n```\n'; const source = '```\n\n:- Hello\n\n```\n';
const rendered = Markdown.render(source); const rendered = hbfm.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<pre><code>\n:- Hello\n</code></pre>\n`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<pre><code>\n:- Hello\n</code></pre>\n`);
}); });
}); });
+52 -52
View File
@@ -1,7 +1,7 @@
/* eslint-disable max-lines */ /* eslint-disable max-lines */
import dedent from 'dedent'; import dedent from 'dedent';
import Markdown from '../../shared/markdown.js'; import { hbfm } from 'hbfm';
// Marked.js adds line returns after closing tags on some default tokens. // Marked.js adds line returns after closing tags on some default tokens.
// This removes those line returns for comparison sake. // This removes those line returns for comparison sake.
@@ -12,7 +12,7 @@ String.prototype.trimReturns = function(){
const renderAllPages = function(pages){ const renderAllPages = function(pages){
const outputs = []; const outputs = [];
pages.forEach((page, index)=>{ pages.forEach((page, index)=>{
const output = Markdown.render(page, index); const output = hbfm.render(page, index);
outputs.push(output); outputs.push(output);
}); });
@@ -29,7 +29,7 @@ describe('Block-level variables', ()=>{
$[var] $[var]
`; `;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p>');
}); });
@@ -40,7 +40,7 @@ describe('Block-level variables', ()=>{
lines lines
$[var]`; $[var]`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns(); const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string across multiple lines</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string across multiple lines</p>');
}); });
@@ -54,7 +54,7 @@ describe('Block-level variables', ()=>{
| C | D | | C | D |
$[var]`; $[var]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<h5 id="title">Title</h5> <h5 id="title">Title</h5>
<table><thead><tr><th align=left>H1</th> <table><thead><tr><th align=left>H1</th>
@@ -71,7 +71,7 @@ describe('Block-level variables', ()=>{
$[var] $[var]
[var]: string`; [var]: string`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns(); const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p>');
}); });
@@ -82,7 +82,7 @@ describe('Block-level variables', ()=>{
[var]: string [var]: string
[var]: new string`; [var]: new string`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns(); const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>new string</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>new string</p>');
}); });
@@ -102,7 +102,7 @@ describe('Block-level variables', ()=>{
[lastName]: $[lastName]son [lastName]: $[lastName]son
`; `;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns(); const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Welcome, Mr. Bob Jacobson!</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Welcome, Mr. Bob Jacobson!</p>');
}); });
@@ -116,7 +116,7 @@ describe('Block-level variables', ()=>{
$[var] $[var]
`; `;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>one</p><p>two</p>'.trimReturns()); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>one</p><p>two</p>'.trimReturns());
}); });
@@ -132,7 +132,7 @@ describe('Block-level variables', ()=>{
$[var] $[var]
`; `;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>two</p><p>one</p><p>two</p>'.trimReturns()); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>two</p><p>one</p><p>two</p>'.trimReturns());
}); });
@@ -142,7 +142,7 @@ describe('Block-level variables', ()=>{
$[last]: Jones $[last]: Jones
`; `;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns(); const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>My name is $[first] Jones</p>`.trimReturns()); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>My name is $[first] Jones</p>`.trimReturns());
}); });
}); });
@@ -154,7 +154,7 @@ describe('Inline-level variables', ()=>{
$[var] $[var]
`; `;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p><p>string</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p><p>string</p>');
}); });
@@ -163,7 +163,7 @@ describe('Inline-level variables', ()=>{
$[var](My name is $[name] Jones) $[var](My name is $[name] Jones)
[name]: Bob`; [name]: Bob`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns(); const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>My name is Bob Jones</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>My name is Bob Jones</p>');
}); });
@@ -174,7 +174,7 @@ describe('Inline-level variables', ()=>{
$[name](Bob) $[name](Bob)
[name]: Bill`; [name]: Bill`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns(); const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>My name is Bill Jones</p> <p>Bob</p>`.trimReturns()); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>My name is Bill Jones</p> <p>Bob</p>`.trimReturns());
}); });
@@ -187,7 +187,7 @@ describe('Inline-level variables', ()=>{
$[var2](A variable ) with unbalanced parens) $[var2](A variable ) with unbalanced parens)
$[var2]`; $[var2]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p>A variable (with nested parens) inside</p> <p>A variable (with nested parens) inside</p>
<p>A variable (with nested parens) inside</p> <p>A variable (with nested parens) inside</p>
@@ -202,35 +202,35 @@ describe('Math', ()=>{
const source = dedent` const source = dedent`
$[1 + 3 * 5 - (1 / 4)] $[1 + 3 * 5 - (1 / 4)]
`; `;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>15.75</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>15.75</p>');
}); });
it('Handles round function', function() { it('Handles round function', function() {
const source = dedent` const source = dedent`
$[round(1/4)]`; $[round(1/4)]`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns(); const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>0</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>0</p>');
}); });
it('Handles floor function', function() { it('Handles floor function', function() {
const source = dedent` const source = dedent`
$[floor(0.6)]`; $[floor(0.6)]`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns(); const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>0</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>0</p>');
}); });
it('Handles ceil function', function() { it('Handles ceil function', function() {
const source = dedent` const source = dedent`
$[ceil(0.2)]`; $[ceil(0.2)]`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns(); const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>1</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>1</p>');
}); });
it('Handles nested functions', function() { it('Handles nested functions', function() {
const source = dedent` const source = dedent`
$[ceil(floor(round(0.6)))]`; $[ceil(floor(round(0.6)))]`;
const rendered = Markdown.render(source).replace(/\s/g, ' ').trimReturns(); const rendered = hbfm.render(source).replace(/\s/g, ' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>1</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>1</p>');
}); });
@@ -242,7 +242,7 @@ describe('Math', ()=>{
Answer is $[answer]($[1 + 3 * num1 - (1 / num2)]). Answer is $[answer]($[1 + 3 * num1 - (1 / num2)]).
`; `;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Answer is 15.75.</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Answer is 15.75.</p>');
}); });
@@ -252,7 +252,7 @@ describe('Math', ()=>{
Increment num1 to get $[num1]($[num1 + 1]) and again to $[num1]($[num1 + 1]). Increment num1 to get $[num1]($[num1 + 1]) and again to $[num1]($[num1 + 1]).
`; `;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Increment num1 to get 6 and again to 7.</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Increment num1 to get 6 and again to 7.</p>');
}); });
}); });
@@ -268,7 +268,7 @@ describe('Code blocks', ()=>{
$[var](new string) $[var](new string)
\`\`\` \`\`\`
`; `;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<pre><code> <pre><code>
[var]: string [var]: string
@@ -289,7 +289,7 @@ describe('Code blocks', ()=>{
$[var](new string) $[var](new string)
`; `;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p>test</p> <p>test</p>
@@ -304,7 +304,7 @@ describe('Code blocks', ()=>{
it('Ignores all variables in inline code blocks', function() { it('Ignores all variables in inline code blocks', function() {
const source = '[var](Hello) `[link](url)`. This `[var] does not work`'; const source = '[var](Hello) `[link](url)`. This `[var] does not work`';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p><a href="Hello">var</a> <code>[link](url)</code>. This <code>[var] does not work</code></p>`.trimReturns()); <p><a href="Hello">var</a> <code>[link](url)</code>. This <code>[var] does not work</code></p>`.trimReturns());
}); });
@@ -313,35 +313,35 @@ describe('Code blocks', ()=>{
describe('Normal Links and Images', ()=>{ describe('Normal Links and Images', ()=>{
it('Renders normal images', function() { it('Renders normal images', function() {
const source = `![alt text](url)`; const source = `![alt text](url)`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p><img loading="lazy" src="url" alt="alt text" style="--HB_src:url(url);"></p>`.trimReturns()); <p><img loading="lazy" src="url" alt="alt text" style="--HB_src:url(url);"></p>`.trimReturns());
}); });
it('Renders normal images with a title', function() { it('Renders normal images with a title', function() {
const source = 'An image ![alt text](url "and title")!'; const source = 'An image ![alt text](url "and title")!';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p>An image <img loading="lazy" src="url" alt="alt text" style="--HB_src:url(url);" title="and title">!</p>`.trimReturns()); <p>An image <img loading="lazy" src="url" alt="alt text" style="--HB_src:url(url);" title="and title">!</p>`.trimReturns());
}); });
it('Applies curly injectors to images', function() { it('Applies curly injectors to images', function() {
const source = `![alt text](url){width:100px}`; const source = `![alt text](url){width:100px}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p><img style="--HB_src:url(url); width:100px;" loading="lazy" src="url" alt="alt text"></p>`.trimReturns()); <p><img style="--HB_src:url(url); width:100px;" loading="lazy" src="url" alt="alt text"></p>`.trimReturns());
}); });
it('Renders normal links', function() { it('Renders normal links', function() {
const source = 'A Link to my [website](url)!'; const source = 'A Link to my [website](url)!';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p>A Link to my <a href="url">website</a>!</p>`.trimReturns()); <p>A Link to my <a href="url">website</a>!</p>`.trimReturns());
}); });
it('Renders normal links with a title', function() { it('Renders normal links with a title', function() {
const source = 'A Link to my [website](url "and title")!'; const source = 'A Link to my [website](url "and title")!';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
<p>A Link to my <a href="url" title="and title">website</a>!</p>`.trimReturns()); <p>A Link to my <a href="url" title="and title">website</a>!</p>`.trimReturns());
}); });
@@ -399,17 +399,17 @@ describe('Cross-page variables', ()=>{
describe('Math function parameter handling', ()=>{ describe('Math function parameter handling', ()=>{
it('allows variables in single-parameter functions', function() { it('allows variables in single-parameter functions', function() {
const source = '[var]:4.1\n\n$[floor(var)]'; const source = '[var]:4.1\n\n$[floor(var)]';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>4</p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>4</p>`);
}); });
it('allows one variable and a number in two-parameter functions', function() { it('allows one variable and a number in two-parameter functions', function() {
const source = '[var]:4\n\n$[min(1,var)]'; const source = '[var]:4\n\n$[min(1,var)]';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>1</p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>1</p>`);
}); });
it('allows two variables in two-parameter functions', function() { it('allows two variables in two-parameter functions', function() {
const source = '[var1]:4\n\n[var2]:8\n\n$[min(var1,var2)]'; const source = '[var1]:4\n\n[var2]:8\n\n$[min(var1,var2)]';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>4</p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>4</p>`);
}); });
}); });
@@ -417,13 +417,13 @@ describe('Math function parameter handling', ()=>{
describe('Variable names that are subsets of other names', ()=>{ describe('Variable names that are subsets of other names', ()=>{
it('do not conflict with function names', function() { it('do not conflict with function names', function() {
const source = `[a]: -1\n\n$[abs(a)]`; const source = `[a]: -1\n\n$[abs(a)]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>1</p>'); expect(rendered).toBe('<p>1</p>');
}); });
it('do not conflict with other variable names', function() { it('do not conflict with other variable names', function() {
const source = `[ab]: 2\n\n[aba]: 8\n\n[ba]: 4\n\n$[ab + aba + ba]`; const source = `[ab]: 2\n\n[aba]: 8\n\n[ba]: 4\n\n$[ab + aba + ba]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>14</p>'); expect(rendered).toBe('<p>14</p>');
}); });
}); });
@@ -431,31 +431,31 @@ describe('Variable names that are subsets of other names', ()=>{
describe('Regression Tests', ()=>{ describe('Regression Tests', ()=>{
it('Don\'t Eat all the parentheticals!', function() { it('Don\'t Eat all the parentheticals!', function() {
const source='\n| title 1 | title 2 | title 3 | title 4|\n|-----------|---------|---------|--------|\n|[foo](bar) | Ipsum | ) | ) |\n'; const source='\n| title 1 | title 2 | title 3 | title 4|\n|-----------|---------|---------|--------|\n|[foo](bar) | Ipsum | ) | ) |\n';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<table><thead><tr><th>title 1</th><th>title 2</th><th>title 3</th><th>title 4</th></tr></thead><tbody><tr><td><a href=\"bar\">foo</a></td><td>Ipsum</td><td>)</td><td>)</td></tr></tbody></table>'); expect(rendered).toBe('<table><thead><tr><th>title 1</th><th>title 2</th><th>title 3</th><th>title 4</th></tr></thead><tbody><tr><td><a href=\"bar\">foo</a></td><td>Ipsum</td><td>)</td><td>)</td></tr></tbody></table>');
}); });
it('Handle Extra spaces in image alt-text 1', function(){ it('Handle Extra spaces in image alt-text 1', function(){
const source='![ where is my image??](http://i.imgur.com/hMna6G0.png)'; const source='![ where is my image??](http://i.imgur.com/hMna6G0.png)';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p><img loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>'); expect(rendered).toBe('<p><img loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
}); });
it('Handle Extra spaces in image alt-text 2', function(){ it('Handle Extra spaces in image alt-text 2', function(){
const source='![where is my image??](http://i.imgur.com/hMna6G0.png)'; const source='![where is my image??](http://i.imgur.com/hMna6G0.png)';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p><img loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>'); expect(rendered).toBe('<p><img loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
}); });
it('Handle Extra spaces in image alt-text 3', function(){ it('Handle Extra spaces in image alt-text 3', function(){
const source='![where is my image?? ](http://i.imgur.com/hMna6G0.png)'; const source='![where is my image?? ](http://i.imgur.com/hMna6G0.png)';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p><img loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>'); expect(rendered).toBe('<p><img loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\"></p>');
}); });
it('Handle Extra spaces in image alt-text 4', function(){ it('Handle Extra spaces in image alt-text 4', function(){
const source='![where is my image??](http://i.imgur.com/hMna6G0.png){height=20%,width=20%}'; const source='![where is my image??](http://i.imgur.com/hMna6G0.png){height=20%,width=20%}';
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p><img style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\" loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" height=\"20%\" width=\"20%\"></p>'); expect(rendered).toBe('<p><img style=\"--HB_src:url(http://i.imgur.com/hMna6G0.png);\" loading="lazy" src=\"http://i.imgur.com/hMna6G0.png\" alt=\"where is my image??\" height=\"20%\" width=\"20%\"></p>');
}); });
}); });
@@ -463,73 +463,73 @@ describe('Regression Tests', ()=>{
describe('Custom Math Function Tests', ()=>{ describe('Custom Math Function Tests', ()=>{
it('Sign Test', function() { it('Sign Test', function() {
const source = `[a]: 13\n\n[b]: -11\n\nPositive: $[sign(a)]\n\nNegative: $[sign(b)]`; const source = `[a]: 13\n\n[b]: -11\n\nPositive: $[sign(a)]\n\nNegative: $[sign(b)]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>Positive: +</p><p>Negative: -</p>'); expect(rendered).toBe('<p>Positive: +</p><p>Negative: -</p>');
}); });
it('Signed Test', function() { it('Signed Test', function() {
const source = `[a]: 13\n\n[b]: -11\n\nPositive: $[signed(a)]\n\nNegative: $[signed(b)]`; const source = `[a]: 13\n\n[b]: -11\n\nPositive: $[signed(a)]\n\nNegative: $[signed(b)]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>Positive: +13</p><p>Negative: -11</p>'); expect(rendered).toBe('<p>Positive: +13</p><p>Negative: -11</p>');
}); });
it('Roman Numerals Test', function() { it('Roman Numerals Test', function() {
const source = `[a]: 18\n\nRoman Numeral: $[toRomans(a)]`; const source = `[a]: 18\n\nRoman Numeral: $[toRomans(a)]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>Roman Numeral: XVIII</p>'); expect(rendered).toBe('<p>Roman Numeral: XVIII</p>');
}); });
it('Roman Numerals Test - Uppercase', function() { it('Roman Numerals Test - Uppercase', function() {
const source = `[a]: 18\n\nRoman Numeral: $[toRomansUpper(a)]`; const source = `[a]: 18\n\nRoman Numeral: $[toRomansUpper(a)]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>Roman Numeral: XVIII</p>'); expect(rendered).toBe('<p>Roman Numeral: XVIII</p>');
}); });
it('Roman Numerals Test - Lowercase', function() { it('Roman Numerals Test - Lowercase', function() {
const source = `[a]: 18\n\nRoman Numeral: $[toRomansLower(a)]`; const source = `[a]: 18\n\nRoman Numeral: $[toRomansLower(a)]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>Roman Numeral: xviii</p>'); expect(rendered).toBe('<p>Roman Numeral: xviii</p>');
}); });
it('Number to Characters Test', function() { it('Number to Characters Test', function() {
const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toChar(a)] $[toChar(b)]`; const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toChar(a)] $[toChar(b)]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>Characters: R AM</p>'); expect(rendered).toBe('<p>Characters: R AM</p>');
}); });
it('Number to Characters Test - Uppercase', function() { it('Number to Characters Test - Uppercase', function() {
const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharUpper(a)] $[toCharUpper(b)]`; const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharUpper(a)] $[toCharUpper(b)]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>Characters: R AM</p>'); expect(rendered).toBe('<p>Characters: R AM</p>');
}); });
it('Number to Characters Test - Lowercase', function() { it('Number to Characters Test - Lowercase', function() {
const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharLower(a)] $[toCharLower(b)]`; const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharLower(a)] $[toCharLower(b)]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>Characters: r am</p>'); expect(rendered).toBe('<p>Characters: r am</p>');
}); });
it('Number to Words Test', function() { it('Number to Words Test', function() {
const source = `[a]: 80085\n\nWords: $[toWords(a)]`; const source = `[a]: 80085\n\nWords: $[toWords(a)]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>Words: eighty thousand and eighty-five</p>'); expect(rendered).toBe('<p>Words: eighty thousand and eighty-five</p>');
}); });
it('Number to Words Test - Uppercase', function() { it('Number to Words Test - Uppercase', function() {
const source = `[a]: 80085\n\nWords: $[toWordsUpper(a)]`; const source = `[a]: 80085\n\nWords: $[toWordsUpper(a)]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>Words: EIGHTY THOUSAND AND EIGHTY-FIVE</p>'); expect(rendered).toBe('<p>Words: EIGHTY THOUSAND AND EIGHTY-FIVE</p>');
}); });
it('Number to Words Test - Lowercase', function() { it('Number to Words Test - Lowercase', function() {
const source = `[a]: 80085\n\nWords: $[toWordsLower(a)]`; const source = `[a]: 80085\n\nWords: $[toWordsLower(a)]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>Words: eighty thousand and eighty-five</p>'); expect(rendered).toBe('<p>Words: eighty thousand and eighty-five</p>');
}); });
it('Number to Words Test - Capitalized', function() { it('Number to Words Test - Capitalized', function() {
const source = `[a]: 80085\n\nWords: $[toWordsCaps(a)]`; const source = `[a]: 80085\n\nWords: $[toWordsCaps(a)]`;
const rendered = Markdown.render(source).trimReturns(); const rendered = hbfm.render(source).trimReturns();
expect(rendered).toBe('<p>Words: Eighty Thousand And Eighty-Five</p>'); expect(rendered).toBe('<p>Words: Eighty Thousand And Eighty-Five</p>');
}); });
}); });
+1 -1
View File
@@ -1,4 +1,4 @@
import Markdown from '@shared/markdown.js'; import { default as Markdown } from 'hbfm';
export default { export default {
createFooterFunc : function(headerSize=1){ createFooterFunc : function(headerSize=1){