jenkins-bot has submitted this change and it was merged. Change subject: Use async serializeDOM in jsapi ......................................................................
Use async serializeDOM in jsapi * Replace uses of (sync) `toString` with an (async) `toWikitext` method. * Follow up to Id68d24007229dcfa0bf6fb16c6ed3ecdaad0c2e5 where these tests were skipped. Change-Id: I33717f9261885bd121aef27360602fe8ebbc8ccb --- M guides/jsapi/README.md M lib/jsapi.js M tests/mocha/jsapi.js 3 files changed, 269 insertions(+), 80 deletions(-) Approvals: Cscott: Looks good to me, approved jenkins-bot: Verified diff --git a/guides/jsapi/README.md b/guides/jsapi/README.md index 5ff6dcf..4df3b5a 100644 --- a/guides/jsapi/README.md +++ b/guides/jsapi/README.md @@ -12,7 +12,20 @@ exports vanilla [`Promise`]s if you wish to maintain compatibility with old versions of `node` at the cost of a little bit of readability. -Use as a wikitext parser is straightforward (where `text` is +Since many methods in the API return [`Promise`]s, we've also provided +a [`Promise`]-aware REPL, that will wait for a promise to be resolved +before printing its value. This can be started from the +shell using: + + node -e 'require("parsoid").repl()' + +Use `"./"` instead of `"parsoid"` if you are running this from a +checked-out repository. Code examples below which contain lines +starting with `>` show sessions using this REPL. (You may also +wish to look in `tests/mocha/jsapi.js` for examples using a more +traditional promise-chaining style.) + +Use of Parsoid as a wikitext parser is straightforward (where `text` is wikitext input): #/usr/bin/node --harmony-generators @@ -29,8 +42,8 @@ main().done(); As you can see, there is a little bit of boilerplate needed to get the -asynchronous machinery started. Future code examples will be assumed -to replace the body of the `main()` method above. +asynchronous machinery started. The body of the `main()` method can +be replaced with your code. The `pdoc` variable above holds a [`PDoc`] object, which has helpful methods to filter and manipulate the document. If you want @@ -46,29 +59,29 @@ > var text = "I has a template! {{foo|bar|baz|eggs=spam}} See it?\n"; > var pdoc = yield Parsoid.parse(text, { pdoc: true }); - > console.log(String(pdoc)); + > console.log(yield pdoc.toWikitext()); I has a template! {{foo|bar|baz|eggs=spam}} See it? > var templates = pdoc.filterTemplates(); - > console.log(templates.map(String)); + > console.log(yield Promise.map(templates, Parsoid.toWikitext)); [ '{{foo|bar|baz|eggs=spam}}' ] > var template = templates[0]; > console.log(template.name); foo > template.name = 'notfoo'; - > console.log(String(template)); + > console.log(yield template.toWikitext()); {{notfoo|bar|baz|eggs=spam}} > console.log(template.params.map(function(p) { return p.name; })); [ '1', '2', 'eggs' ] - > console.log(template.get(1).value); + > console.log(yield template.get(1).value.toWikitext()); bar - > console.log(template.get("eggs").value); + > console.log(yield template.get("eggs").value.toWikitext()); spam Getting nested templates is trivial: > var text = "{{foo|bar={{baz|{{spam}}}}}}"; > var pdoc = yield Parsoid.parse(text, { pdoc: true }); - > console.log(pdoc.filterTemplates().map(String)); + > console.log(yield Promise.map(pdoc.filterTemplates(), Parsoid.toWikitext)); [ '{{foo|bar={{baz|{{spam}}}}}}', '{{baz|{{spam}}}}', '{{spam}}' ] @@ -82,15 +95,15 @@ > var text = "{{foo|this {{includes a|template}}}}"; > var pdoc = yield Parsoid.parse(text, { pdoc: true }); > var templates = pdoc.filterTemplates({ recursive: false }); - > console.log(templates.map(String)); + > console.log(yield Promise.map(templates, Parsoid.toWikitext)); [ '{{foo|this {{includes a|template}}}}' ] > var foo = templates[0]; - > console.log(String(foo.get(1).value)); + > console.log(yield foo.get(1).value.toWikitext()); this {{includes a|template}} > var more = foo.get(1).value.filterTemplates(); - > console.log(more.map(String)); + > console.log(yield Promise.map(more, Parsoid.toWikitext)); [ '{{includes a|template}}' ] - > console.log(String(more[0].get(1).value)); + > console.log(yield more[0].get(1).value.toWikitext()); template Templates can be easily modified to add, remove, or alter params. @@ -108,13 +121,14 @@ ... template.name = 'bar-stub'; ... } ... }); - > console.log(String(pdoc)); + > console.log(yield pdoc.toWikitext()); {{cleanup|date = July 2012}} '''Foo''' is a [[bar]]. {{bar-stub}} At any time you can convert the `pdoc` into HTML conforming to the [MediaWiki DOM spec] (by referencing the [`document`](#!/api/PDoc-property-document) property) or into wikitext (by -invoking [`toString()`](#!/api/PNodeList-method-toString)). This allows you +invoking [`toWikitext()`](#!/api/PNodeList-method-toWikitext), which +returns a [`Promise`] for the wikitext string). This allows you to save the page using either standard API methods or the RESTBase API (once [T101501](https://phabricator.wikimedia.org/T101501) is resolved). diff --git a/lib/jsapi.js b/lib/jsapi.js index 252bacf..b804745 100644 --- a/lib/jsapi.js +++ b/lib/jsapi.js @@ -10,7 +10,9 @@ // PExtLink#url PWikiLink#title should handle mw:ExpandedAttrs // make separate package? +var Promise = require('prfun'); var util = require('util'); + var DOMImpl = require('domino').impl; var Node = DOMImpl.Node; var NodeFilter = DOMImpl.NodeFilter; @@ -19,11 +21,6 @@ // WTS helper var wts = function(env, nodes) { - // XXX: Serializing to wikitext is very user-friendly, but it depends on - // WTS.serializeDOMSync which we might not want to keep around forever. - // An alternative would be: - // return DU.normalizeOut(node, 'parsoidOnly'); - // which might be almost as friendly. var body; if (nodes.length === 0) { return ''; @@ -35,8 +32,39 @@ body.appendChild(nodes[i].cloneNode(true)); } } - // FXIME: serializeDOMSync is dead. - return (new WikitextSerializer({ env: env })).serializeDOMSync(body); + return (new WikitextSerializer({ env: env })).serializeDOM(body); +}; + +// toString helper +var toStringHelper = function(nodes, sizeLimit) { + var out; + if (sizeLimit === undefined) { sizeLimit = 80; /* characters */ } + if (nodes.length === 0) { + return ''; + } else if (nodes.length === 1) { + var body = nodes[0].ownerDocument.createElement('body'); + body.appendChild(nodes[0].cloneNode(true)); + out = DU.normalizeOut(body, 'parsoidOnly'); + if (out.length <= sizeLimit || !DU.isElt(nodes[0])) { return out; } + body.firstChild.innerHTML = '...'; + out = DU.normalizeOut(body, 'parsoidOnly'); + if (out.length <= sizeLimit) { return out; } + var name = nodes[0].nodeName.toLowerCase(); + var children = nodes[0].childNodes; + if (children.length === 0) { + return '<' + name + ' .../>'; + } else { + return '<' + name + ' ...>...</' + name + '>'; + } + } else { + for (var i = 0; i < nodes.length; i++) { + out += toStringHelper( + [nodes[i]], + (sizeLimit - out.length) / (nodes.length - i) + ); + } + return out; + } }; // Forward declarations of Wrapper classes. @@ -425,11 +453,21 @@ }, }, /** - * Return a string representing the contents of this object as wikitext. + * Return a promise for a string representing the contents of this + * object as wikitext. + * @return {Promise} + */ + toWikitext: { value: Promise.method(function() { + return wts(this.pdoc.env, this.nodes); + }), }, + + /** + * Return a string representing the contents of this object for + * debugging. Some contents may be elided. * @return {String} */ toString: { value: function() { - return wts(this.pdoc.env, this.nodes); + return toStringHelper(this.nodes); }, }, }); /** @@ -472,7 +510,7 @@ * A function which will be invoked when {@link #update} is called. * @param {Function} [opts.wtsNodes] * A function returning an array of {@link Node}s which can tweak the - * portion of the document serialized by {@link #toString}. + * portion of the document serialized by {@link #toWikitext}. */ PNode = function PNode(pdoc, parent, node, opts) { /** @property {PDoc} pdoc The parent document for this {@link PNode}. */ @@ -531,12 +569,20 @@ return nodes.map(function(n) { return n.outerHTML; }).join(''); }, }, /** + * @inheritdoc PNodeList#toWikitext + * @method + */ + toWikitext: { value: Promise.method(function() { + var nodes = this._wtsNodes ? this._wtsNodes() : [ this.node ]; + return wts(this.pdoc.env, nodes); + }), }, + /** * @inheritdoc PNodeList#toString * @method */ toString: { value: function() { var nodes = this._wtsNodes ? this._wtsNodes() : [ this.node ]; - return wts(this.pdoc.env, nodes); + return toStringHelper(nodes); }, }, }); @@ -1164,6 +1210,15 @@ this._value.update(); }, }, + toWikitext: { value: Promise.method(function() { + var k = this.key; + return Promise.join( + k ? k.toWikitext() : this.name, + this.value.toWikitext() + ).spread(function(keyWikitext, valueWikitext) { + return keyWikitext + '=' + valueWikitext; + }); + }), }, toString: { value: function() { var k = this.key; return (k ? String(k) : this.name) + '=' + String(this.value); @@ -1269,7 +1324,7 @@ * instance of {@link PNodeList}, you can filter it, mutate it, etc. * But it also provides means to serialize the document as either * HTML (via {@link #document} or {@link #toHtml}) or wikitext - * (via {@link #toString}). + * (via {@link #toWikitext}). * @class * @extends PNodeList * @alternateClassName Parsoid.PDoc @@ -1306,6 +1361,44 @@ }, }, }); +// Promise-using REPL, for easier debugging. +// We also handle `yield`, at least in common cases. +var repl = function() { + /* jshint evil:true */ + // The older version of jshint on jenkins is confused. + var Parsoid = require('../'); + console.log('Parsoid REPL', Parsoid.version); + var r = require('repl').start({ ignoreUndefined: true }); + // `var Parsoid = require('parsoid');` by default. + r.context.Parsoid = Parsoid; + // `var Promise = require('prfun');` by default. + r.context.Promise = Promise; + // Patch the `eval` method to wait for Promises to be resolved. + var oldEval = r.eval; + r.eval = function(cmd, context, filename, callback) { + // If `cmd` mentions `yield`, wrap it in a `function*` + if (/\byield\b/.test(cmd)) { + // Hack to support `var xyz = yield pdq...;`, convert it + // to `var xyz; ...{ xyz = yield pdq...; }...` + var m = /^(var\s+)(\w+)\s*=/.exec(cmd); + if (m) { cmd = cmd.slice(m[1].length); } + cmd = 'Promise.async(function*(){' + cmd + '})();'; + if (m) { cmd = m[1] + m[2] + ';' + cmd; } + } + oldEval.call(r, cmd, context, filename, function(e, v) { + if (e || !(typeof v === 'object' && typeof v.then === 'function')) { + return callback(e, v); + } + // OK, this is a promise! Wait for the result. + v.then(function(v) { + callback(null, v); + }, function(e) { + callback(e); + }); + }); + }; +}; + module.exports = { PDoc: PDoc, PNodeList: PNodeList, @@ -1319,4 +1412,8 @@ PTemplate: PTemplate, PText: PText, PWikiLink: PWikiLink, + // Helper function for `Promise.map` + toWikitext: Promise.method(function(n) { return n.toWikitext(); }), + // Useful REPL that handles promises and `yield` well. + repl: repl, }; diff --git a/tests/mocha/jsapi.js b/tests/mocha/jsapi.js index 6f11fe7..a60e8a0 100644 --- a/tests/mocha/jsapi.js +++ b/tests/mocha/jsapi.js @@ -3,6 +3,7 @@ "use strict"; var Parsoid = require('../../'); +var Promise = require('prfun'); describe('Parsoid JS API', function() { it('converts empty wikitext to HTML', function() { @@ -22,7 +23,7 @@ }); }); -describe.skip('Examples from guides/jsapi', function() { +describe('Examples from guides/jsapi', function() { it('converts empty wikitext to HTML', function() { return Parsoid.parse('', { pdoc: true}).then(function(pdoc) { pdoc.should.have.property('document'); @@ -38,21 +39,33 @@ }); it('filters out templates', function() { var text = "I has a template! {{foo|bar|baz|eggs=spam}} See it?\n"; - return Parsoid.parse(text, { pdoc: true }).then(function(pdoc) { - String(pdoc).should.equal(text); - var templates = pdoc.filterTemplates(); + var pdoc, templates, template; + return Parsoid.parse(text, { pdoc: true }).then(function(_pdoc) { + pdoc = _pdoc; + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal(text); + templates = pdoc.filterTemplates(); templates.length.should.equal(1); - String(templates[0]).should.equal('{{foo|bar|baz|eggs=spam}}'); - var template = templates[0]; + return templates[0].toWikitext(); + }).then(function(wt) { + wt.should.equal('{{foo|bar|baz|eggs=spam}}'); + template = templates[0]; template.name.should.equal('foo'); template.name = 'notfoo'; - String(template).should.equal('{{notfoo|bar|baz|eggs=spam}}'); + return template.toWikitext(); + }).then(function(wt) { + wt.should.equal('{{notfoo|bar|baz|eggs=spam}}'); template.params.length.should.equal(3); template.params[0].name.should.equal('1'); template.params[1].name.should.equal('2'); template.params[2].name.should.equal('eggs'); - String(template.get(1).value).should.equal('bar'); - String(template.get('eggs').value).should.equal('spam'); + return template.get(1).value.toWikitext(); + }).then(function(wt) { + wt.should.equal('bar'); + return template.get('eggs').value.toWikitext(); + }).then(function(wt) { + wt.should.equal('spam'); }); }); it('filters templates, recursively', function() { @@ -66,14 +79,19 @@ }); it('filters templates, non-recursively', function() { var text = "{{foo|this {{includes a|template}}}}"; + var foo; return Parsoid.parse(text, { pdoc: true }).then(function(pdoc) { var templates = pdoc.filterTemplates({ recursive: false }); templates.length.should.equal(1); - var foo = templates[0]; - String(foo.get(1).value).should.equal('this {{includes a|template}}'); + foo = templates[0]; + return foo.get(1).value.toWikitext(); + }).then(function(wt) { + wt.should.equal('this {{includes a|template}}'); var more = foo.get(1).value.filterTemplates(); more.length.should.equal(1); - String(more[0].get(1).value).should.equal('template'); + return more[0].get(1).value.toWikitext(); + }).then(function(wt) { + wt.should.equal('template'); }); }); it('is easy to mutate templates', function() { @@ -87,12 +105,14 @@ template.name = 'bar-stub'; } }); - String(pdoc).should.equal("{{cleanup|date = July 2012}} '''Foo''' is a [[bar]]. {{bar-stub}}"); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal("{{cleanup|date = July 2012}} '''Foo''' is a [[bar]]. {{bar-stub}}"); }); }); }); -describe.skip('Further examples of PDoc API', function() { +describe('Further examples of PDoc API', function() { it('is easy to mutate templates (2)', function() { // Works even on nested templates! var text = "{{echo|{{cleanup}} '''Foo''' is a [[bar]].}} {{uncategorized}}"; @@ -105,7 +125,9 @@ template.add('test2', Parsoid.PNodeList.fromHTML(pdoc, "I'm so <b>bold</b>!")); } }); - String(pdoc).should.equal("{{echo|{{cleanup|date = July 2012|test1 = <nowiki>{{foo}}</nowiki>&bar{{!}}bat<nowiki><p></nowiki>|test2 = I'm so '''bold'''!}} '''Foo''' is a [[bar]].}} {{uncategorized}}"); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal("{{echo|{{cleanup|date = July 2012|test1 = <nowiki>{{foo}}</nowiki>&bar{{!}}bat<nowiki><p></nowiki>|test2 = I'm so '''bold'''!}} '''Foo''' is a [[bar]].}} {{uncategorized}}"); }); }); it('is safe to mutate template arguments', function() { @@ -113,7 +135,9 @@ return Parsoid.parse(text, { pdoc: true }).then(function(pdoc) { var t = pdoc.filterTemplates()[0]; t.remove(1); - String(pdoc).should.equal('{{echo||bar}}'); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal('{{echo||bar}}'); }); }); it('is safe to mutate template arguments (2)', function() { @@ -124,7 +148,9 @@ var param2 = t.get(2); param2.value = param1.value; param1.value = '|'; - String(pdoc).should.equal('{{echo|{{!}}|foo}}'); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal('{{echo|{{!}}|foo}}'); }); }); it('filters and mutates headings', function() { @@ -143,47 +169,77 @@ headings[0].title = '=0='; headings[1].title = headings[2].title; headings[3].level = 3; - String(pdoc).should.equal('=<nowiki>=0=</nowiki>=\n== three ==\n=== three ===\n\n=== four ===\nbody\n'); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal('=<nowiki>=0=</nowiki>=\n== three ==\n=== three ===\n\n=== four ===\nbody\n'); }); }); it('filters and mutates headings inside templates', function() { var text = "{{echo|1=\n= one =\n}}"; - return Parsoid.parse(text, { pdoc: true }).then(function(pdoc) { - var headings = pdoc.filterHeadings(); + var pdoc, headings; + return Parsoid.parse(text, { pdoc: true }).then(function(_pdoc) { + pdoc = _pdoc; + headings = pdoc.filterHeadings(); headings.length.should.equal(1); headings[0].level = 2; - String(headings[0]).should.equal('== one ==\n'); - String(pdoc).should.equal('{{echo|1=\n== one ==\n}}'); + return headings[0].toWikitext(); + }).then(function(wt) { + wt.should.equal('== one ==\n'); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal('{{echo|1=\n== one ==\n}}'); headings[0].title = 'two'; - String(headings[0]).should.equal('== two ==\n'); - String(pdoc).should.equal('{{echo|1=\n== two ==\n}}'); + return headings[0].toWikitext(); + }).then(function(wt) { + wt.should.equal('== two ==\n'); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal('{{echo|1=\n== two ==\n}}'); }); }); it('filters and mutates external links', function() { var text = "[http://example.com {{echo|link content}}]"; - return Parsoid.parse(text, { pdoc: true }).then(function(pdoc) { - var extlinks = pdoc.filterExtLinks(); + var pdoc, extlinks; + return Parsoid.parse(text, { pdoc: true }).then(function(_pdoc) { + pdoc = _pdoc; + extlinks = pdoc.filterExtLinks(); extlinks.length.should.equal(1); String(extlinks[0].url).should.equal('http://example.com'); - String(extlinks[0].title).should.equal('{{echo|link content}}'); + return extlinks[0].title.toWikitext(); + }).then(function(wt) { + wt.should.equal('{{echo|link content}}'); extlinks[0].title = ']'; - String(pdoc).should.equal('[http://example.com <nowiki>]</nowiki>]\n'); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal('[http://example.com <nowiki>]</nowiki>]\n'); }); }); it('filters and mutates wiki links', function() { var text = "[[foo|1]] {{echo|[[bar|2]]}} [[{{echo|bat}}|3]]"; - return Parsoid.parse(text, { pdoc: true }).then(function(pdoc) { - var extlinks = pdoc.filterWikiLinks(); + var pdoc, extlinks; + return Parsoid.parse(text, { pdoc: true }).then(function(_pdoc) { + pdoc = _pdoc; + extlinks = pdoc.filterWikiLinks(); extlinks.length.should.equal(3); - String(extlinks[0].title).should.equal('Foo'); - String(extlinks[0].text).should.equal('1'); - String(extlinks[1].title).should.equal('Bar'); - String(extlinks[1].text).should.equal('2'); - String(extlinks[2].text).should.equal('3'); + return Promise.all([ + extlinks[0].title, + extlinks[0].text.toWikitext(), + extlinks[1].title, + extlinks[1].text.toWikitext(), + extlinks[2].text.toWikitext(), + ]); + }).then(function(all) { + all[0].should.equal('Foo'); + all[1].should.equal('1'); + all[2].should.equal('Bar'); + all[3].should.equal('2'); + all[4].should.equal('3'); extlinks[0].title = extlinks[0].text = 'foobar'; extlinks[1].text = 'A'; extlinks[2].text = 'B'; - String(pdoc).should.equal('[[foobar]] {{echo|[[bar|A]]}} [[{{echo|bat}}|B]]\n'); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal('[[foobar]] {{echo|[[bar|A]]}} [[{{echo|bat}}|B]]\n'); }); }); it('filters and mutates html entities', function() { @@ -195,7 +251,9 @@ entities[1].normalized.should.equal('"'); entities[0].normalized = '<'; entities[1].normalized = '>'; - String(pdoc).should.equal('<{{echo|>}}\n'); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal('<{{echo|>}}\n'); }); }); it('filters and mutates comments', function() { @@ -207,37 +265,53 @@ comments[1].contents.should.equal('bar'); comments[0].contents = '<!-- ha! -->'; comments[1].contents = '--'; - String(pdoc).should.equal('<!--<!-- ha! -->--> {{echo|<!------>}}'); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal('<!--<!-- ha! -->--> {{echo|<!------>}}'); }); }); it('filters and mutates images', function() { var text = '[[File:SomeFile1.jpg]] [[File:SomeFile2.jpg|thumb|caption]]'; - return Parsoid.parse(text, { pdoc: true }).then(function(pdoc) { - var media = pdoc.filterMedia(); + var pdoc, media; + return Parsoid.parse(text, { pdoc: true }).then(function(_pdoc) { + pdoc = _pdoc; + media = pdoc.filterMedia(); media.length.should.equal(2); media[0].should.have.property('caption', null); - String(media[1].caption).should.equal('caption'); + return media[1].caption.toWikitext(); + }).then(function(wt) { + wt.should.equal('caption'); media[0].caption = '|'; media[1].caption = null; - String(pdoc).should.equal('[[File:SomeFile1.jpg|<nowiki>|</nowiki>]] [[File:SomeFile2.jpg|thumb]]'); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal('[[File:SomeFile1.jpg|<nowiki>|</nowiki>]] [[File:SomeFile2.jpg|thumb]]'); media[0].caption = null; media[1].caption = '|'; - String(pdoc).should.equal('[[File:SomeFile1.jpg]] [[File:SomeFile2.jpg|thumb|<nowiki>|</nowiki>]]'); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal('[[File:SomeFile1.jpg]] [[File:SomeFile2.jpg|thumb|<nowiki>|</nowiki>]]'); }); }); it('filters and mutates text', function() { var text = 'foo {{echo|bar}}'; - return Parsoid.parse(text, { pdoc: true }).then(function(pdoc) { - var texts = pdoc.filterText({ recursive: false }); + var pdoc, texts; + return Parsoid.parse(text, { pdoc: true }).then(function(_pdoc) { + pdoc = _pdoc; + texts = pdoc.filterText({ recursive: false }); texts.length.should.equal(1); texts = pdoc.filterText({ recursive: true }); texts.length.should.equal(2); texts[0].value.should.equal('foo '); texts[1].value.should.equal('bar'); texts[0].value = 'FOO '; - String(pdoc).should.equal('FOO {{echo|bar}}\n'); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal('FOO {{echo|bar}}\n'); texts[1].value = 'BAR'; - String(pdoc).should.equal('FOO {{echo|BAR}}\n'); + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal('FOO {{echo|BAR}}\n'); }); }); it.skip('filters and mutates text (2)', function() { @@ -253,15 +327,19 @@ }); it('allows mutation using wikitext', function() { var text = '== heading =='; - return Parsoid.parse(text, { pdoc: true }).then(function(pdoc) { - var headings = pdoc.filterHeadings(); + var pdoc, headings; + return Parsoid.parse(text, { pdoc: true }).then(function(_pdoc) { + pdoc = _pdoc; + headings = pdoc.filterHeadings(); headings.length.should.equal(1); // Note that even if the wikitext is unbalanced, the result // will be balanced. The bold face doesn't escape the heading! - return Parsoid.PNodeList.fromWikitext(pdoc, "'''bold").then(function(pnl) { - headings[0].title = pnl; - String(pdoc).should.equal("== '''bold''' ==\n"); - }); + return Parsoid.PNodeList.fromWikitext(pdoc, "'''bold"); + }).then(function(pnl) { + headings[0].title = pnl; + return pdoc.toWikitext(); + }).then(function(wt) { + wt.should.equal("== '''bold''' ==\n"); }); }); it('allows iteration using length and get()', function() { -- To view, visit https://gerrit.wikimedia.org/r/252886 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I33717f9261885bd121aef27360602fe8ebbc8ccb Gerrit-PatchSet: 14 Gerrit-Project: mediawiki/services/parsoid Gerrit-Branch: master Gerrit-Owner: Arlolra <abrea...@wikimedia.org> Gerrit-Reviewer: Arlolra <abrea...@wikimedia.org> Gerrit-Reviewer: Cscott <canan...@wikimedia.org> Gerrit-Reviewer: Subramanya Sastry <ssas...@wikimedia.org> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits