Commit 2dc90311 authored by Nick Stenning's avatar Nick Stenning

Merge pull request #2979 from hypothesis/editor-js-conversion-and-tests

Convert <markdown> component to JS and add basic tests
parents b32ef2c0 2f64665b
......@@ -13,10 +13,7 @@
"chrome": false,
"h": false,
"Promise": false,
"angular": false,
"chai": false,
"moment": false,
"jstz": false,
"sinon": false,
"JSON": false
},
......@@ -30,7 +27,6 @@
"URL",
"after",
"afterEach",
"angular",
"assert",
"before",
"beforeEach",
......
katex = require('katex')
mediaEmbedder = require('../media-embedder')
angular = require('angular')
loadMathJax = ->
if !MathJax?
$.ajax {
url: "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML-full"
dataType: 'script'
cache: true
complete: ->
# MathJax configuration overides.
MathJax.Hub.Config({
showMathMenu: false
displayAlign: "left"
})
}
###*
# @ngdoc directive
# @name markdown
# @restrict A
# @description
# This directive controls both the rendering and display of markdown, as well as
# the markdown editor.
###
module.exports = ['$filter', '$sanitize', '$sce', '$timeout', ($filter, $sanitize, $sce, $timeout) ->
link: (scope, elem, attr, ctrl) ->
return unless ctrl?
input = elem[0].querySelector('.js-markdown-input')
inputEl = angular.element(input)
output = elem[0].querySelector('.js-markdown-preview')
userSelection = ->
if input.selectionStart != undefined
startPos = input.selectionStart
endPos = input.selectionEnd
selectedText = input.value.substring(startPos, endPos)
textBefore = input.value.substring(0, (startPos))
textAfter = input.value.substring(endPos)
selection = {
before: textBefore
after: textAfter
selection: selectedText
start: startPos
end: endPos
}
return selection
insertMarkup = (value, selectionStart, selectionEnd) ->
# New value is set for the input
input.value = value
# A new selection is set, or the cursor is positioned inside the input.
input.selectionStart = selectionStart
input.selectionEnd = selectionEnd
# Focus the input
input.focus()
applyInlineMarkup = (markupL, innertext, markupR) ->
markupR or= markupL
text = userSelection()
if text.selection == ""
newtext = text.before + markupL + innertext + markupR + text.after
start = (text.before + markupL).length
end = (text.before + innertext + markupR).length
insertMarkup(newtext, start, end)
else
# Check to see if markup has already been applied before to the selection.
slice1 = text.before.slice(text.before.length - markupL.length)
slice2 = text.after.slice(0, markupR.length)
if (slice1 == markupL and slice2 == markupR)
# Remove markup
newtext = (
text.before.slice(0, (text.before.length - markupL.length)) +
text.selection + text.after.slice(markupR.length)
)
start = text.before.length - markupL.length
end = (text.before + text.selection).length - markupR.length
insertMarkup(newtext, start, end)
else
# Apply markup
newtext = text.before + markupL + text.selection + markupR + text.after
start = (text.before + markupL).length
end = (text.before + text.selection + markupR).length
insertMarkup(newtext, start, end)
scope.insertBold = ->
applyInlineMarkup("**", "Bold")
scope.insertItalic = ->
applyInlineMarkup("*", "Italic")
scope.insertMath = ->
text = userSelection()
index = text.before.length
if (
index == 0 or
input.value[index - 1] == '\n' or
(input.value[index - 1] == '$' and input.value[index - 2] == '$')
)
applyInlineMarkup('$$', 'Insert LaTeX')
else
applyInlineMarkup('\\(', 'Insert LaTeX', '\\)')
scope.insertLink = ->
text = userSelection()
if text.selection == ""
newtext = text.before + "[Link Text](https://example.com)" + text.after
start = text.before.length + 1
end = text.before.length + 10
insertMarkup(newtext, start, end)
else
# Check to see if markup has already been applied to avoid double presses.
if text.selection == "Link Text" or text.selection == "https://example.com"
return
newtext = text.before + '[' + text.selection + '](https://example.com)' + text.after
start = (text.before + text.selection).length + 3
end = (text.before + text.selection).length + 22
insertMarkup(newtext, start, end)
scope.insertIMG = ->
text = userSelection()
if text.selection == ""
newtext = text.before + "![Image Description](https://yourimage.jpg)" + text.after
start = text.before.length + 21
end = text.before.length + 42
insertMarkup(newtext, start, end)
else
# Check to see if markup has already been applied to avoid double presses.
if text.selection == "https://yourimage.jpg"
return
newtext = text.before + '![' + text.selection + '](https://yourimage.jpg)' + text.after
start = (text.before + text.selection).length + 4
end = (text.before + text.selection).length + 25
insertMarkup(newtext, start, end)
scope.applyBlockMarkup = (markup) ->
text = userSelection()
if text.selection != ""
newstring = ""
index = text.before.length
if index == 0
# The selection takes place at the very start of the input
for char in text.selection
if char == "\n"
newstring = newstring + "\n" + markup
else if index == 0
newstring = newstring + markup + char
else
newstring = newstring + char
index += 1
else
newlinedetected = false
if input.value.substring(index - 1).charAt(0) == "\n"
# Look to see if the selection falls at the beginning of a new line.
newstring = newstring + markup
newlinedetected = true
for char in text.selection
if char == "\n"
newstring = newstring + "\n" + markup
newlinedetected = true
else
newstring = newstring + char
index += 1
if not newlinedetected
# Edge case: The selection does not include any new lines and does not start at 0.
# We need to find the newline before the currently selected text and add markup there.
i = 0
indexoflastnewline = undefined
newstring = ""
for char in (text.before + text.selection)
if char == "\n"
indexoflastnewline = i
newstring = newstring + char
i++
if indexoflastnewline == undefined
# The partial selection happens to fall on the firstline
newstring = markup + newstring
else
newstring = (
newstring.substring(0, (indexoflastnewline + 1)) +
markup + newstring.substring(indexoflastnewline + 1)
)
value = newstring + text.after
start = (text.before + markup).length
end = (text.before + text.selection + markup).length
insertMarkup(value, start, end)
return
# Sets input value and selection for cases where there are new lines in the selection
# or the selection is at the start
value = text.before + newstring + text.after
start = (text.before + newstring).length
end = (text.before + newstring).length
insertMarkup(value, start, end)
else if input.value.substring((text.start - 1 ), text.start) == "\n"
# Edge case, no selection, the cursor is on a new line.
value = text.before + markup + text.selection + text.after
start = (text.before + markup).length
end = (text.before + markup).length
insertMarkup(value, start, end)
else
# No selection, cursor is not on new line.
# Check to see if markup has already been inserted.
if text.before.slice(text.before.length - markup.length) == markup
newtext = (
text.before.substring(0, (index)) + "\n" +
text.before.substring(index + 1 + markup.length) + text.after
)
i = 0
for char in text.before
if char == "\n" and i != 0
index = i
i += 1
if !index # If the line of text happens to fall on the first line and index is not set.
# Check to see if markup has already been inserted and undo it.
if text.before.slice(0, markup.length) == markup
newtext = text.before.substring(markup.length) + text.after
start = text.before.length - markup.length
end = text.before.length - markup.length
insertMarkup(newtext, start, end)
else
newtext = markup + text.before.substring(0) + text.after
start = (text.before + markup).length
end = (text.before + markup).length
insertMarkup(newtext, start, end)
# Check to see if markup has already been inserted and undo it.
else if text.before.slice((index + 1), (index + 1 + markup.length)) == markup
newtext = (
text.before.substring(0, (index)) + "\n" +
text.before.substring(index + 1 + markup.length) + text.after
)
start = text.before.length - markup.length
end = text.before.length - markup.length
insertMarkup(newtext, start, end)
else
newtext = (
text.before.substring(0, (index)) + "\n" +
markup + text.before.substring(index + 1) + text.after
)
start = (text.before + markup).length
end = (text.before + markup).length
insertMarkup(newtext, start, end)
scope.insertList = ->
scope.applyBlockMarkup("* ")
scope.insertNumList = ->
scope.applyBlockMarkup("1. ")
scope.insertQuote = ->
scope.applyBlockMarkup("> ")
# Keyboard shortcuts for bold, italic, and link.
elem.on 'keydown', (e) ->
shortcuts =
66: scope.insertBold
73: scope.insertItalic
75: scope.insertLink
shortcut = shortcuts[e.keyCode]
if shortcut && (e.ctrlKey || e.metaKey)
e.preventDefault()
shortcut()
scope.preview = false
scope.togglePreview = ->
if !scope.readOnly
scope.preview = !scope.preview
if scope.preview
output.style.height = input.style.height
ctrl.$render()
else
input.style.height = output.style.height
$timeout -> input.focus()
mathJaxFallback = false
renderMathAndMarkdown = (textToCheck) ->
convert = $filter('converter')
re = /\$\$/g
startMath = 0
endMath = 0
indexes = (match.index while match = re.exec(textToCheck))
indexes.push(textToCheck.length)
parts = for index in indexes
if startMath > endMath
endMath = index + 2
try
# \\displaystyle tells KaTeX to render the math in display style (full sized fonts).
katex.renderToString($sanitize "\\displaystyle {" + textToCheck.substring(startMath, index) + "}")
catch
loadMathJax()
mathJaxFallback = true
$sanitize textToCheck.substring(startMath, index)
else
startMath = index + 2
$sanitize convert renderInlineMath textToCheck.substring(endMath, index)
htmlString = parts.join('')
# Transform the HTML string into a DOM element.
domElement = document.createElement('div')
domElement.innerHTML = htmlString
mediaEmbedder.replaceLinksWithEmbeds(domElement)
return domElement.innerHTML
renderInlineMath = (textToCheck) ->
re = /\\?\\\(|\\?\\\)/g
startMath = null
endMath = null
match = undefined
indexes = []
while match = re.exec(textToCheck)
indexes.push match.index
for index in indexes
if startMath == null
startMath = index + 2
else
endMath = index
if startMath != null and endMath != null
try
math = katex.renderToString(textToCheck.substring(startMath, endMath))
textToCheck = (
textToCheck.substring(0, (startMath - 2)) + math +
textToCheck.substring(endMath + 2)
)
startMath = null
endMath = null
return renderInlineMath(textToCheck)
catch
loadMathJax()
mathJaxFallback = true
$sanitize textToCheck.substring(startMath, endMath)
return textToCheck
# Re-render the markdown when the view needs updating.
ctrl.$render = ->
if !scope.readOnly and !scope.preview
inputEl.val (ctrl.$viewValue or '')
value = ctrl.$viewValue or ''
output.innerHTML = renderMathAndMarkdown(value)
if mathJaxFallback
$timeout (-> MathJax?.Hub.Queue ['Typeset', MathJax.Hub, output]), 0, false
# React to the changes to the input
inputEl.bind 'blur change keyup', ->
$timeout -> ctrl.$setViewValue inputEl.val()
# Reset height of output div incase it has been changed.
# Re-render when it becomes uneditable.
# Auto-focus the input box when the widget becomes editable.
scope.$watch 'readOnly', (readOnly) ->
scope.preview = false
output.style.height = ""
ctrl.$render()
unless readOnly then $timeout -> input.focus()
require: '?ngModel'
restrict: 'E'
scope:
readOnly: '<'
required: '@'
templateUrl: 'markdown.html'
]
'use strict';
/* globals MathJax */
var angular = require('angular');
var katex = require('katex');
var mediaEmbedder = require('../media-embedder');
var loadMathJax = function() {
if (!(typeof MathJax !== "undefined" && MathJax !== null)) {
return $.ajax({
url: "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML-full",
dataType: 'script',
cache: true,
complete: function () {
// MathJax configuration overides.
return MathJax.Hub.Config({
showMathMenu: false,
displayAlign: "left"
});
}
});
}
};
/**
* @ngdoc directive
* @name markdown
* @restrict A
* @description
* This directive controls both the rendering and display of markdown, as well as
* the markdown editor.
*/
// @ngInject
module.exports = function($filter, $sanitize, $sce, $timeout) {
return {
link: function(scope, elem, attr, ctrl) {
if (!(typeof ctrl !== "undefined" && ctrl !== null)) { return; }
var input = elem[0].querySelector('.js-markdown-input');
var inputEl = angular.element(input);
var output = elem[0].querySelector('.js-markdown-preview');
var userSelection = function() {
var selection;
if (input.selectionStart !== undefined) {
var startPos = input.selectionStart;
var endPos = input.selectionEnd;
var selectedText = input.value.substring(startPos, endPos);
var textBefore = input.value.substring(0, (startPos));
var textAfter = input.value.substring(endPos);
selection = {
before: textBefore,
after: textAfter,
selection: selectedText,
start: startPos,
end: endPos
};
}
return selection;
};
var insertMarkup = function(value, selectionStart, selectionEnd) {
// New value is set for the input
input.value = value;
// A new selection is set, or the cursor is positioned inside the input.
input.selectionStart = selectionStart;
input.selectionEnd = selectionEnd;
// Focus the input
return input.focus();
};
var applyInlineMarkup = function(markupL, innertext, markupR) {
if (!markupR) {
markupR = markupL;
}
var newtext;
var end;
var start;
var text = userSelection();
if (text.selection === "") {
newtext = text.before + markupL + innertext + markupR + text.after;
start = (text.before + markupL).length;
end = (text.before + innertext + markupR).length;
return insertMarkup(newtext, start, end);
} else {
// Check to see if markup has already been applied before to the selection.
var slice1 = text.before.slice(text.before.length - markupL.length);
var slice2 = text.after.slice(0, markupR.length);
if (slice1 === markupL && slice2 === markupR) {
// Remove markup
newtext = (
text.before.slice(0, (text.before.length - markupL.length)) +
text.selection + text.after.slice(markupR.length)
);
start = text.before.length - markupL.length;
end = (text.before + text.selection).length - markupR.length;
return insertMarkup(newtext, start, end);
} else {
// Apply markup
newtext = text.before + markupL + text.selection + markupR + text.after;
start = (text.before + markupL).length;
end = (text.before + text.selection + markupR).length;
return insertMarkup(newtext, start, end);
}
}
};
scope.insertBold = function() {
return applyInlineMarkup("**", "Bold");
};
scope.insertItalic = function() {
return applyInlineMarkup("*", "Italic");
};
scope.insertMath = function() {
var text = userSelection();
var index = text.before.length;
if (
index === 0 ||
input.value[index - 1] === '\n' ||
(input.value[index - 1] === '$' && input.value[index - 2] === '$')
) {
return applyInlineMarkup('$$', 'Insert LaTeX');
} else {
return applyInlineMarkup('\\(', 'Insert LaTeX', '\\)');
}
};
scope.insertLink = function() {
var text = userSelection();
var newtext;
var start;
var end;
if (text.selection === "") {
newtext = text.before + "[Link Text](https://example.com)" + text.after;
start = text.before.length + 1;
end = text.before.length + 10;
return insertMarkup(newtext, start, end);
} else {
// Check to see if markup has already been applied to avoid double presses.
if (text.selection === "Link Text" || text.selection === "https://example.com") {
return;
}
newtext = text.before + '[' + text.selection + '](https://example.com)' + text.after;
start = (text.before + text.selection).length + 3;
end = (text.before + text.selection).length + 22;
return insertMarkup(newtext, start, end);
}
};
scope.insertIMG = function() {
var text = userSelection();
var newtext;
var start;
var end;
if (text.selection === "") {
newtext = text.before + "![Image Description](https://yourimage.jpg)" + text.after;
start = text.before.length + 21;
end = text.before.length + 42;
return insertMarkup(newtext, start, end);
} else {
// Check to see if markup has already been applied to avoid double presses.
if (text.selection === "https://yourimage.jpg") {
return;
}
newtext = text.before + '![' + text.selection + '](https://yourimage.jpg)' + text.after;
start = (text.before + text.selection).length + 4;
end = (text.before + text.selection).length + 25;
return insertMarkup(newtext, start, end);
}
};
/* jshint maxcomplexity:false */
scope.applyBlockMarkup = function(markup) {
var text = userSelection();
var ch;
var value;
var start;
var end;
var index;
var i;
var newtext;
if (text.selection !== "") {
var newstring = "";
index = text.before.length;
if (index === 0) {
// The selection takes place at the very start of the input
for (var j = 0; j < text.selection.length; j++) {
ch = text.selection[j];
if (ch === "\n") {
newstring = newstring + "\n" + markup;
} else if (index === 0) {
newstring = newstring + markup + ch;
} else {
newstring = newstring + ch;
}
index += 1;
}
} else {
var newlinedetected = false;
if (input.value.substring(index - 1).charAt(0) === "\n") {
// Look to see if the selection falls at the beginning of a new line.
newstring = newstring + markup;
newlinedetected = true;
}
for (var k = 0; k < text.selection.length; k++) {
ch = text.selection[k];
if (ch === "\n") {
newstring = newstring + "\n" + markup;
newlinedetected = true;
} else {
newstring = newstring + ch;
}
index += 1;
}
if (!newlinedetected) {
// Edge case: The selection does not include any new lines and does not start at 0.
// We need to find the newline before the currently selected text and add markup there.
i = 0;
var indexoflastnewline;
newstring = "";
var iterable = text.before + text.selection;
for (var i1 = 0; i1 < iterable.length; i1++) {
ch = iterable[i1];
if (ch === "\n") {
indexoflastnewline = i;
}
newstring = newstring + ch;
i++;
}
if (indexoflastnewline === undefined) {
// The partial selection happens to fall on the firstline
newstring = markup + newstring;
} else {
newstring = (
newstring.substring(0, (indexoflastnewline + 1)) +
markup + newstring.substring(indexoflastnewline + 1)
);
}
value = newstring + text.after;
start = (text.before + markup).length;
end = (text.before + text.selection + markup).length;
insertMarkup(value, start, end);
return;
}
}
// Sets input value and selection for cases where there are new lines in the selection
// or the selection is at the start
value = text.before + newstring + text.after;
start = (text.before + newstring).length;
end = (text.before + newstring).length;
return insertMarkup(value, start, end);
} else if (input.value.substring((text.start - 1 ), text.start) === "\n") {
// Edge case, no selection, the cursor is on a new line.
value = text.before + markup + text.selection + text.after;
start = (text.before + markup).length;
end = (text.before + markup).length;
return insertMarkup(value, start, end);
} else {
// No selection, cursor is not on new line.
// Check to see if markup has already been inserted.
if (text.before.slice(text.before.length - markup.length) === markup) {
newtext = (
text.before.substring(0, (index)) + "\n" +
text.before.substring(index + 1 + markup.length) + text.after
);
}
i = 0;
for (var i2 = 0, char; i2 < text.before.length; i2++) {
char = text.before[i2];
if (char === "\n" && i !== 0) {
index = i;
}
i += 1;
}
if (!index) { // If the line of text happens to fall on the first line and index is not set.
// Check to see if markup has already been inserted and undo it.
if (text.before.slice(0, markup.length) === markup) {
newtext = text.before.substring(markup.length) + text.after;
start = text.before.length - markup.length;
end = text.before.length - markup.length;
return insertMarkup(newtext, start, end);
} else {
newtext = markup + text.before.substring(0) + text.after;
start = (text.before + markup).length;
end = (text.before + markup).length;
return insertMarkup(newtext, start, end);
}
// Check to see if markup has already been inserted and undo it.
} else if (text.before.slice((index + 1), (index + 1 + markup.length)) === markup) {
newtext = (
text.before.substring(0, (index)) + "\n" +
text.before.substring(index + 1 + markup.length) + text.after
);
start = text.before.length - markup.length;
end = text.before.length - markup.length;
return insertMarkup(newtext, start, end);
} else {
newtext = (
text.before.substring(0, (index)) + "\n" +
markup + text.before.substring(index + 1) + text.after
);
start = (text.before + markup).length;
end = (text.before + markup).length;
return insertMarkup(newtext, start, end);
}
}
};
scope.insertList = function() {
return scope.applyBlockMarkup("* ");
};
scope.insertNumList = function() {
return scope.applyBlockMarkup("1. ");
};
scope.insertQuote = function() {
return scope.applyBlockMarkup("> ");
};
// Keyboard shortcuts for bold, italic, and link.
elem.on('keydown', function(e) {
var shortcuts =
{66: scope.insertBold,
73: scope.insertItalic,
75: scope.insertLink
};
var shortcut = shortcuts[e.keyCode];
if (shortcut && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
return shortcut();
}
});
scope.preview = false;
scope.togglePreview = function() {
if (!scope.readOnly) {
scope.preview = !scope.preview;
if (scope.preview) {
output.style.height = input.style.height;
return ctrl.$render();
} else {
input.style.height = output.style.height;
return $timeout(function() { return input.focus(); });
}
}
};
var mathJaxFallback = false;
var renderInlineMath = function(textToCheck) {
var re = /\\?\\\(|\\?\\\)/g;
var startMath = null;
var endMath = null;
var match;
var indexes = [];
while ((match = re.exec(textToCheck))) {
indexes.push(match.index);
}
for (var i = 0, index; i < indexes.length; i++) {
index = indexes[i];
if (startMath === null) {
startMath = index + 2;
} else {
endMath = index;
}
if (startMath !== null && endMath !== null) {
try {
var math = katex.renderToString(textToCheck.substring(startMath, endMath));
textToCheck = (
textToCheck.substring(0, (startMath - 2)) + math +
textToCheck.substring(endMath + 2)
);
startMath = null;
endMath = null;
return renderInlineMath(textToCheck);
} catch (error) {
loadMathJax();
mathJaxFallback = true;
$sanitize(textToCheck.substring(startMath, endMath));
}
}
}
return textToCheck;
};
var renderMathAndMarkdown = function(textToCheck) {
var convert = $filter('converter');
var re = /\$\$/g;
var startMath = 0;
var endMath = 0;
var indexes = (function () {
var match;
var result = [];
while ((match = re.exec(textToCheck))) {
result.push(match.index);
}
return result;
})();
indexes.push(textToCheck.length);
var parts = (function () {
var result = [];
/* jshint -W083 */
for (var i = 0, index; i < indexes.length; i++) {
index = indexes[i];
result.push((function () {
if (startMath > endMath) {
endMath = index + 2;
try {
// \\displaystyle tells KaTeX to render the math in display style (full sized fonts).
return katex.renderToString($sanitize("\\displaystyle {" + textToCheck.substring(startMath, index) + "}"));
} catch (error) {
loadMathJax();
mathJaxFallback = true;
return $sanitize(textToCheck.substring(startMath, index));
}
} else {
startMath = index + 2;
return $sanitize(convert(renderInlineMath(textToCheck.substring(endMath, index))));
}
})());
}
/* jshint +W083 */
return result;
})();
var htmlString = parts.join('');
// Transform the HTML string into a DOM element.
var domElement = document.createElement('div');
domElement.innerHTML = htmlString;
mediaEmbedder.replaceLinksWithEmbeds(domElement);
return domElement.innerHTML;
};
// Re-render the markdown when the view needs updating.
ctrl.$render = function() {
if (!scope.readOnly && !scope.preview) {
inputEl.val((ctrl.$viewValue || ''));
}
var value = ctrl.$viewValue || '';
output.innerHTML = renderMathAndMarkdown(value);
if (mathJaxFallback) {
return $timeout((function() {
return ((typeof MathJax !== "undefined" && MathJax !== null) ? MathJax.Hub : undefined).Queue(['Typeset', MathJax.Hub, output]);
}), 0, false);
}
};
// React to the changes to the input
inputEl.bind('blur change keyup', function() {
return $timeout(function() {
return ctrl.$setViewValue(inputEl.val());
});
});
// Reset height of output div incase it has been changed.
// Re-render when it becomes uneditable.
// Auto-focus the input box when the widget becomes editable.
return scope.$watch('readOnly', function(readOnly) {
scope.preview = false;
output.style.height = "";
ctrl.$render();
if (!readOnly) {
return $timeout(function() { return input.focus(); });
}
});
},
require: '?ngModel',
restrict: 'E',
scope: {
readOnly: '=',
required: '@'
},
templateUrl: 'markdown.html'
};
};
'use strict';
var angular = require('angular');
var proxyquire = require('proxyquire');
var util = require('./util');
/**
* Disable calling through to the original module for a stub.
*
* By default proxyquire will call through to the original module
* for any methods not provided by a stub. This function disables
* this behavior for a stub and returns the input stub.
*
* This prevents unintended usage of the original dependency.
*/
function noCallThru(stub) {
return Object.assign(stub, {'@noCallThru':true});
}
describe('markdown', function () {
function isHidden(element) {
return element.classList.contains('ng-hide');
}
function inputElement(editor) {
return editor[0].querySelector('.form-input');
}
function viewElement(editor) {
return editor[0].querySelector('.styled-text');
}
function getRenderedHTML(editor) {
var contentElement = viewElement(editor);
if (isHidden(contentElement)) {
return 'rendered markdown is hidden';
}
return contentElement.innerHTML;
}
before(function () {
angular.module('app', ['ngSanitize'])
.directive('markdown', proxyquire('../markdown', {
angular: noCallThru(angular),
katex: {
renderToString: function (input) {
return 'math:' + input.replace(/$$/g, '');
},
},
'@noCallThru': true,
}))
.filter('converter', function () {
return function (input) {
return 'rendered:' + input;
};
});
});
beforeEach(function () {
angular.mock.module('app');
angular.mock.module('h.templates');
});
describe('read only state', function () {
it('should show the rendered view when readOnly is true', function () {
var editor = util.createDirective(document, 'markdown', {
readOnly: true,
ngModel: 'Hello World',
});
assert.isTrue(isHidden(inputElement(editor)));
assert.isFalse(isHidden(viewElement(editor)));
});
it('should show the editor when readOnly is false', function () {
var editor = util.createDirective(document, 'markdown', {
readOnly: false,
ngModel: 'Hello World',
});
assert.isFalse(isHidden(inputElement(editor)));
assert.isTrue(isHidden(viewElement(editor)));
});
});
describe('rendering', function () {
it('should render input markdown', function () {
var editor = util.createDirective(document, 'markdown', {
readOnly: true,
ngModel: 'Hello World',
});
assert.equal(getRenderedHTML(editor), 'rendered:Hello World');
});
});
describe('math rendering', function () {
it('should render LaTeX', function () {
var editor = util.createDirective(document, 'markdown', {
readOnly: true,
ngModel: '$$x*2$$',
});
assert.equal(getRenderedHTML(editor),
'rendered:math:\\displaystyle {x*2}rendered:');
});
});
});
......@@ -59,6 +59,7 @@ module.exports = function(config) {
browserify: {
debug: true,
extensions: ['.coffee'],
noParse: [require.resolve('./vendor/katex')],
configure: function (bundle) {
bundle
.transform('coffeeify')
......
......@@ -10,5 +10,6 @@ sinon.assert.expose(assert, {prefix: null});
//
window.jQuery = window.$ = require('jquery');
require('angular');
require('angular-resource');
require('angular-mocks');
require('angular-resource');
require('angular-sanitize');
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment