Commit a944c418f340772fa8ac92f7b8496fce1e04f0ca

Guillaume Grossetie 2019-03-11T17:55:56

Add unit tests to the Keep Markup plugin (#1646) Uses JSDOM to simulate the DOM in node.

diff --git a/package.json b/package.json
index b4e616b..dce27e1 100755
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
   "main": "prism.js",
   "style": "themes/prism.css",
   "scripts": {
-    "test": "mocha tests/testrunner-tests.js && mocha tests/run.js"
+    "test": "mocha tests/testrunner-tests.js && mocha tests/run.js && mocha tests/plugins/**/*.js"
   },
   "repository": {
     "type": "git",
@@ -29,6 +29,7 @@
     "gulp-rename": "^1.2.0",
     "gulp-replace": "^1.0.0",
     "gulp-uglify": "^3.0.1",
+    "jsdom": "^13.0.0",
     "mocha": "^6.0.0",
     "pump": "^3.0.0",
     "yargs": "^13.2.2"
diff --git a/plugins/keep-markup/prism-keep-markup.js b/plugins/keep-markup/prism-keep-markup.js
index bc136c6..3f87089 100644
--- a/plugins/keep-markup/prism-keep-markup.js
+++ b/plugins/keep-markup/prism-keep-markup.js
@@ -1,4 +1,4 @@
-(function () {
+(function (self, document) {
 
 	if (typeof self === 'undefined' || !self.Prism || !self.document || !document.createRange) {
 		return;
@@ -96,4 +96,4 @@
 			env.highlightedCode = env.element.innerHTML;
 		}
 	});
-}());
+}(self, document));
diff --git a/plugins/keep-markup/prism-keep-markup.min.js b/plugins/keep-markup/prism-keep-markup.min.js
index 5d072e9..a0fa98a 100644
--- a/plugins/keep-markup/prism-keep-markup.min.js
+++ b/plugins/keep-markup/prism-keep-markup.min.js
@@ -1 +1 @@
-"undefined"!=typeof self&&self.Prism&&self.document&&document.createRange&&(Prism.plugins.KeepMarkup=!0,Prism.hooks.add("before-highlight",function(e){if(e.element.children.length){var a=0,s=[],l=function(e,n){var o={};n||(o.clone=e.cloneNode(!1),o.posOpen=a,s.push(o));for(var t=0,d=e.childNodes.length;t<d;t++){var r=e.childNodes[t];1===r.nodeType?l(r):3===r.nodeType&&(a+=r.data.length)}n||(o.posClose=a)};l(e.element,!0),s&&s.length&&(e.keepMarkup=s)}}),Prism.hooks.add("after-highlight",function(n){if(n.keepMarkup&&n.keepMarkup.length){var a=function(e,n){for(var o=0,t=e.childNodes.length;o<t;o++){var d=e.childNodes[o];if(1===d.nodeType){if(!a(d,n))return!1}else 3===d.nodeType&&(!n.nodeStart&&n.pos+d.data.length>n.node.posOpen&&(n.nodeStart=d,n.nodeStartPos=n.node.posOpen-n.pos),n.nodeStart&&n.pos+d.data.length>=n.node.posClose&&(n.nodeEnd=d,n.nodeEndPos=n.node.posClose-n.pos),n.pos+=d.data.length);if(n.nodeStart&&n.nodeEnd){var r=document.createRange();return r.setStart(n.nodeStart,n.nodeStartPos),r.setEnd(n.nodeEnd,n.nodeEndPos),n.node.clone.appendChild(r.extractContents()),r.insertNode(n.node.clone),r.detach(),!1}}return!0};n.keepMarkup.forEach(function(e){a(n.element,{node:e,pos:0})}),n.highlightedCode=n.element.innerHTML}}));
\ No newline at end of file
+"undefined"!=typeof self&&self.Prism&&self.document&&document.createRange&&(Prism.plugins.KeepMarkup=!0,Prism.hooks.add("before-highlight",function(e){if(e.element.children.length){var a=0,s=[],l=function(e,n){var o={};n||(o.clone=e.cloneNode(!1),o.posOpen=a,s.push(o));for(var t=0,d=e.childNodes.length;t<d;t++){var r=e.childNodes[t];1===r.nodeType?l(r):3===r.nodeType&&(a+=r.data.length)}n||(o.posClose=a)};l(e.element,!0),s&&s.length&&(e.keepMarkup=s)}}),Prism.hooks.add("after-highlight",function(n){if(n.keepMarkup&&n.keepMarkup.length){var a=function(e,n){for(var o=0,t=e.childNodes.length;o<t;o++){var d=e.childNodes[o];if(1===d.nodeType){if(!a(d,n))return!1}else 3===d.nodeType&&(!n.nodeStart&&n.pos+d.data.length>n.node.posOpen&&(n.nodeStart=d,n.nodeStartPos=n.node.posOpen-n.pos),n.nodeStart&&n.pos+d.data.length>=n.node.posClose&&(n.nodeEnd=d,n.nodeEndPos=n.node.posClose-n.pos),n.pos+=d.data.length);if(n.nodeStart&&n.nodeEnd){var r=document.createRange();return r.setStart(n.nodeStart,n.nodeStartPos),r.setEnd(n.nodeEnd,n.nodeEndPos),n.node.clone.appendChild(r.extractContents()),r.insertNode(n.node.clone),r.detach(),!1}}return!0};n.keepMarkup.forEach(function(e){a(n.element,{node:e,pos:0})}),n.highlightedCode=n.element.innerHTML}}));
diff --git a/tests/plugins/keep-markup/test.js b/tests/plugins/keep-markup/test.js
new file mode 100644
index 0000000..bb0e78b
--- /dev/null
+++ b/tests/plugins/keep-markup/test.js
@@ -0,0 +1,92 @@
+const expect = require('chai').expect;
+const jsdom = require('jsdom')
+const { JSDOM } = jsdom
+
+require('../../../prism')
+// fake DOM
+global.self = {}
+global.self.Prism = Prism
+global.document = {}
+document.createRange = function () {
+}
+global.self.document = document
+
+require('../../../plugins/keep-markup/prism-keep-markup')
+
+describe('Prism Keep Markup Plugin', function () {
+
+	function execute (code) {
+		const start = [];
+		const end = [];
+		const nodes = [];
+		document.createRange = function () {
+			return {
+				setStart: function (node, offset) {
+					start.push({ node, offset })
+				},
+				setEnd: function (node, offset) {
+					end.push({ node, offset })
+				},
+				extractContents: function () {
+					return new JSDOM('').window.document.createTextNode('')
+				},
+				insertNode: function (node) {
+					nodes.push(node)
+				},
+				detach: function () {
+				}
+			}
+		}
+		const beforeHighlight = Prism.hooks.all['before-highlight'][0]
+		const afterHighlight = Prism.hooks.all['after-highlight'][0]
+		const env = {
+			element: new JSDOM(code).window.document.getElementsByTagName('code')[0],
+			language: "javascript"
+		}
+		beforeHighlight(env)
+		afterHighlight(env)
+		return { start, end, nodes }
+	}
+
+	it('should keep <span> markup', function () {
+		const result = execute(`<code class="language-javascript">x<span>a</span>y</code>`)
+		expect(result.start.length).to.equal(1)
+		expect(result.end.length).to.equal(1)
+		expect(result.nodes.length).to.equal(1)
+		expect(result.nodes[0].nodeName).to.equal('SPAN')
+	})
+	it('should preserve markup order', function () {
+		const result = execute(`<code class="language-javascript">x<a></a><b></b>y</code>`)
+		expect(result.start.length).to.equal(2)
+		expect(result.start[0].offset).to.equal(0)
+		expect(result.start[0].node.textContent).to.equal('y')
+		expect(result.start[1].offset).to.equal(0)
+		expect(result.start[1].node.textContent).to.equal('y')
+		expect(result.end.length).to.equal(2)
+		expect(result.end[0].offset).to.equal(0)
+		expect(result.end[0].node.textContent).to.equal('y')
+		expect(result.end[1].offset).to.equal(0)
+		expect(result.end[1].node.textContent).to.equal('y')
+		expect(result.nodes.length).to.equal(2)
+		expect(result.nodes[0].nodeName).to.equal('A')
+		expect(result.nodes[1].nodeName).to.equal('B')
+	})
+	it('should keep last <span> markup', function () {
+		const result = execute(`<code class="language-javascript">xy<span>a</span></code>`)
+		expect(result.start.length).to.equal(1)
+		expect(result.end.length).to.equal(1)
+		expect(result.nodes.length).to.equal(1)
+		expect(result.nodes[0].nodeName).to.equal('SPAN')
+	})
+	// The markup is removed if it's the last element and the element's name is a single letter: a(nchor), b(old), i(talic)...
+	// https://github.com/PrismJS/prism/issues/1618
+	/*
+	it('should keep last single letter empty markup', function () {
+		const result = execute(`<code class="language-javascript">xy<a></a></code>`)
+		expect(result.start.length).to.equal(1)
+		expect(result.end.length).to.equal(1)
+		expect(result.nodes.length).to.equal(1)
+		expect(result.nodes[0].nodeName).to.equal('A')
+	})
+	*/
+})