diff --git a/src/tests/frontend/specs/importexport.js b/src/tests/frontend/specs/importexport.js index 4eb95eeb..73798eca 100644 --- a/src/tests/frontend/specs/importexport.js +++ b/src/tests/frontend/specs/importexport.js @@ -1,329 +1,604 @@ 'use strict'; -describe('import functionality', function () { - beforeEach(function (cb) { - helper.newPad(cb); // creates a new pad - this.timeout(60000); - }); - - function getinnertext() { - const inner = helper.padInner$; - if (!inner) { - return ''; - } - let newtext = ''; - inner('div').each((line, el) => { - newtext += `${el.innerHTML}\n`; - }); - return newtext; - } - function importrequest(data, importurl, type) { - let error; - const result = $.ajax({ - url: importurl, - type: 'post', - processData: false, - async: false, - contentType: 'multipart/form-data; boundary=boundary', - accepts: { - text: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - }, - data: [ - 'Content-Type: multipart/form-data; boundary=--boundary', - '', - '--boundary', - `Content-Disposition: form-data; name="file"; filename="import.${type}"`, - 'Content-Type: text/plain', - '', - data, - '', - '--boundary', - ].join('\r\n'), - error(res) { - error = res; - }, - }); - expect(error).to.be(undefined); - return result; - } - function exportfunc(link) { - const exportresults = []; - $.ajaxSetup({ - async: false, - }); - $.get(`${link}/export/html`, (data) => { - const start = data.indexOf(''); - const end = data.indexOf(''); - const html = data.substr(start + 6, end - start - 6); - exportresults.push(['html', html]); - }); - $.get(`${link}/export/txt`, (data) => { - exportresults.push(['txt', data]); - }); - return exportresults; - } - - xit('import a pad with newlines from txt', function (done) { - const importurl = `${helper.padChrome$.window.location.href}/import`; - const textWithNewLines = 'imported text\nnewline'; - importrequest(textWithNewLines, importurl, 'txt'); - helper.waitFor(() => expect(getinnertext()) - .to.be('imported text\nnewline\n
\n')); - const results = exportfunc(helper.padChrome$.window.location.href); - expect(results[0][1]).to.be('imported text
newline

'); - expect(results[1][1]).to.be('imported text\nnewline\n\n'); - done(); - }); - xit('import a pad with newlines from html', function (done) { - const importurl = `${helper.padChrome$.window.location.href}/import`; - const htmlWithNewLines = 'htmltext
newline'; - importrequest(htmlWithNewLines, importurl, 'html'); - helper.waitFor(() => expect(getinnertext()) - .to.be('htmltext\nnewline\n
\n')); - const results = exportfunc(helper.padChrome$.window.location.href); - expect(results[0][1]).to.be('htmltext
newline

'); - expect(results[1][1]).to.be('htmltext\nnewline\n\n'); - done(); - }); - xit('import a pad with attributes from html', function (done) { - const importurl = `${helper.padChrome$.window.location.href}/import`; - const htmlWithNewLines = 'htmltext
' + - 'newline'; - importrequest(htmlWithNewLines, importurl, 'html'); - helper.waitFor(() => expect(getinnertext()) - .to.be('htmltext\n' + - 'newline\n
\n')); - const results = exportfunc(helper.padChrome$.window.location.href); - expect(results[0][1]) - .to.be('htmltext
newline

'); - expect(results[1][1]).to.be('htmltext\nnewline\n\n'); - done(); - }); - xit('import a pad with bullets from html', function (done) { - const importurl = `${helper.padChrome$.window.location.href}/import`; - const htmlWithBullets = ''; - importrequest(htmlWithBullets, importurl, 'html'); - helper.waitFor(() => expect(getinnertext()).to.be( - '\n' + - '\n' + - '\n' + - '\n' + - '
\n')); - const results = exportfunc(helper.padChrome$.window.location.href); - expect(results[0][1]).to.be( - '
'); - expect(results[1][1]) - .to.be('\t* bullet line 1\n\t* bullet line 2\n' + - '\t\t* bullet2 line 1\n\t\t* bullet2 line 2\n\n'); - done(); - }); - xit('import a pad with bullets and newlines from html', function (done) { - const importurl = `${helper.padChrome$.window.location.href}/import`; - const htmlWithBullets = '

'; - importrequest(htmlWithBullets, importurl, 'html'); - helper.waitFor(() => expect(getinnertext()).to.be( - '\n' + - '
\n' + - '\n' + - '\n' + - '
\n' + - '\n' + - '
\n')); - const results = exportfunc(helper.padChrome$.window.location.href); - expect(results[0][1]).to.be( - '


'); - expect(results[1][1]).to.be( - '\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t* bullet2 line 2\n\n'); - done(); - }); - xit('import a pad with bullets and newlines and attributes from html', function (done) { - const importurl = `${helper.padChrome$.window.location.href}/import`; - const htmlWithBullets = '
' + - '
'; - importrequest(htmlWithBullets, importurl, 'html'); - helper.waitFor(() => expect(getinnertext()).to.be( - '\n
\n' + - '\n' + - '\n
\n' + - '\n' + - '\n' + - '\n' + - '
\n')); - const results = exportfunc(helper.padChrome$.window.location.href); - expect(results[0][1]).to.be( - '' + - '


'); - expect(results[1][1]).to.be( - '\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t\t\t* bullet4 line 2' + - ' bisu\n\t\t\t\t* bullet4 line 2 bs\n\t\t\t\t* bullet4 line 2 uuis\n\n'); - done(); - }); - xit('import a pad with nested bullets from html', function (done) { - const importurl = `${helper.padChrome$.window.location.href}/import`; - const htmlWithBullets = ''; - importrequest(htmlWithBullets, importurl, 'html'); - const oldtext = getinnertext(); - helper.waitFor(() => oldtext !== getinnertext() - // return expect(getinnertext()).to.be('\ - // \n\ - // \n\ - // \n\ - // \n\ - // \n\ - // \n\ - //
\n') - ); - - const results = exportfunc(helper.padChrome$.window.location.href); - expect(results[0][1]).to.be( - '
'); - expect(results[1][1]).to.be( - '\t* bullet line 1\n\t* bullet line 2\n\t\t* bullet2 line 1\n\t\t\t\t* bullet4 line 2' + - '\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t* bullet3 line 1' + - '\n\t* bullet2 line 1\n\n'); - done(); - }); - xit('import with 8 levels of bullets and newlines and attributes from html', function (done) { - const importurl = `${helper.padChrome$.window.location.href}/import`; - const htmlWithBullets = - '

', + '
', + ], + wantExportHtmlBody: [ + '', + '
', + '', + '
', + '', + '
', + ].map((l) => l.replace(/^\s+/, '')).join(''), + wantExportText: [ + '\t* bullet line 1\n', + '\n', + '\t* bullet line 2\n', + '\t\t* bullet2 line 1\n', + '\n', + '\t\t\t\t* bullet4 line 2 bisu\n', + '\t\t\t\t* bullet4 line 2 bs\n', + '\t\t\t\t* bullet4 line 2 uuis\n', + '\n', + ].join(''), + }, + { + name: 'HTML with nested bullets', + inputHtmlBody: [ + '', + '', + '', + ].join(''), + wantPadLines: [ + '', + '', + '', + '', + '', + '', + '', + '
', + ], + wantExportHtmlBody: [ + '', + '
', + ].map((l) => l.replace(/^\s+/, '')).join(''), + wantExportText: [ + '\t* bullet line 1\n', + '\t* bullet line 2\n', + '\t\t* bullet2 line 1\n', + '\t\t\t\t* bullet4 line 2\n', + '\t\t\t\t* bullet4 line 2\n', + '\t\t\t\t* bullet4 line 2\n', + '\t\t\t* bullet3 line 1\n', + '\n', + ].join(''), + }, + { + name: 'HTML with 8 levels of bullets, newlines, and attributes', + inputHtmlBody: [ + '', + '
', + '', + '
', + '', + ].join(''), + wantPadLines: [ + '', + '
', + '', + '', + '
', + '', + '', + '', + '', + '', + '', + '
', + ], + wantExportHtmlBody: [ + '', + '
', + '', + '
', + '', + '
', + ].map((l) => l.replace(/^\s+/, '')).join(''), + wantExportText: [ + '\t* bullet line 1\n', + '\n', + '\t* bullet line 2\n', + '\t\t* bullet2 line 1\n', + '\n', + '\t\t\t\t* bullet4 line 2 bisu\n', + '\t\t\t\t* bullet4 line 2 bs\n', + '\t\t\t\t* bullet4 line 2 uuis\n', + '\t\t\t\t\t\t\t\t* foo\n', + '\t\t\t\t\t\t\t\t* foobar bs\n', + '\t\t\t\t\t* foobar\n', + '\n', + ].join(''), + }, + { + name: 'HTML with ordered lists', + inputHtmlBody: [ + '
  1. number 1 line 1
', + '
  1. number 2 line 2
', + ].join(''), + wantPadLines: [ + '
  1. number 1 line 1
', + '
  1. number 2 line 2
', + '
', + ], + wantExportHtmlBody: [ + '
    ', + '
  1. number 1 line 1
  2. ', + '
  3. number 2 line 2
  4. ', + '
', + '
', + ].map((l) => l.replace(/^\s+/, '')).join(''), + wantExportText: [ + '\t1. number 1 line 1\n', + '\t2. number 2 line 2\n', + '\n', + ].join(''), + }, + ]; + + let confirm; + before(async function () { + this.timeout(60000); + await new Promise( + (resolve, reject) => helper.newPad((err) => err != null ? reject(err) : resolve())); + confirm = helper.padChrome$.window.confirm; + helper.padChrome$.window.confirm = () => true; + // As of 2021-02-22 a mutable FileList cannot be directly created so DataTransfer is used as a + // hack to access a mutable FileList for testing the '' element. DataTransfer + // itself is quite new so support for it is tested here. See: + // * https://github.com/whatwg/html/issues/3269 + // * https://stackoverflow.com/q/47119426 + try { + const dt = new DataTransfer(); + dt.items.add(new File(['testing'], 'file.txt', {type: 'text/plain'})); + // Supposedly all modern browsers support a settable HTMLInputElement.files property, but + // Firefox 52 complains. + helper.padChrome$('#importform input[type=file]')[0].files = dt.files; + } catch (err) { + return this.skip(); + } }); - xit('import a pad with ordered lists from html', function (done) { - const importurl = `${helper.padChrome$.window.location.href}/import`; - const htmlWithBullets = '
    ' + - '
  1. number 1 line 1
    ' + - '
  1. number 2 line 2
'; - importrequest(htmlWithBullets, importurl, 'html'); - console.error(getinnertext()); - expect(getinnertext()).to.be( - '
  1. number 1 line 1
\n' + - '
  1. number 2 line 2
\n' + - '
\n'); - const results = exportfunc(helper.padChrome$.window.location.href); - expect(results[0][1]).to.be( - '
  1. number 1 line 1
  2. ' + - '
  1. number 2 line 2
'); - expect(results[1][1]).to.be(''); - done(); + after(async function () { + helper.padChrome$.window.confirm = confirm; }); - xit('import a pad with ordered lists and newlines from html', function (done) { - const importurl = `${helper.padChrome$.window.location.href}/import`; - const htmlWithBullets = '
    ' + - '
  1. number 9 line 1

    ' + - '
  1. number 10 line 2
    1. ' + - '
    2. number 2 times line 1

    ' + - '
    1. number 2 times line 2
'; - importrequest(htmlWithBullets, importurl, 'html'); - expect(getinnertext()).to.be( - '
  1. number 9 line 1
\n' + - '
\n' + - '
  1. number 10 line 2
  2. ' + - '
\n' + - '
  1. number 2 times line 1
\n' + - '
\n' + - '
  1. number 2 times line 2
\n' + - '
\n'); - const results = exportfunc(helper.padChrome$.window.location.href); - console.error(results); - done(); - }); - xit('import with nested ordered lists and attributes and newlines from html', function (done) { - const importurl = `${helper.padChrome$.window.location.href}/import`; - const htmlWithBullets = '
  1. ' + - 'bold strikethrough italics underline' + - ' line 1bold
  2. ' + - '

    ' + - '
  1. number 10 line 2
    1. ' + - '
    2. number 2 times line 1

' + - '
      ' + - '
    1. number 2 times line 2
'; - importrequest(htmlWithBullets, importurl, 'html'); - expect(getinnertext()).to.be( - '
  1. ' + - 'bold strikethrough italics underline' + - ' line 1bold
\n' + - '
\n' + - '
  1. number 10 line 2
\n' + - '
  1. ' + - 'number 2 times line 1
\n' + - '
\n' + - '
  1. number 2 times line 2
\n' + - '
\n'); - const results = exportfunc(helper.padChrome$.window.location.href); - console.error(results); - done(); + + beforeEach(async function () { + const popup = helper.padChrome$('#import_export'); + const isVisible = () => popup.hasClass('popup-show'); + if (isVisible()) return; + const button = helper.padChrome$('button[data-l10n-id="pad.toolbar.import_export.title"]'); + button.click(); + await helper.waitForPromise(isVisible); }); + + const docToHtml = (() => { + const s = new XMLSerializer(); + return (doc) => s.serializeToString(doc); + })(); + + const htmlToDoc = (() => { + const p = new DOMParser(); + return (html) => p.parseFromString(html, 'text/html'); + })(); + + const htmlBodyToDoc = (htmlBody) => { + const doc = document.implementation.createHTMLDocument(); + $('body', doc).html(htmlBody); + return doc; + }; + + for (const tc of testCases) { + describe(tc.name, function () { + it('import', async function () { + const ext = tc.inputHtmlBody ? 'html' : 'txt'; + const contents = ext === 'html' ? docToHtml(htmlBodyToDoc(tc.inputHtmlBody)) : tc.inputText; + // DataTransfer is used as a hacky way to get a mutable FileList. For details, see: + // https://stackoverflow.com/q/47119426 + const dt = new DataTransfer(); + dt.items.add(new File([contents], `file.${ext}`, {type: 'text/plain'})); + const form = helper.padChrome$('#importform'); + form.find('input[type=file]')[0].files = dt.files; + form.find('#importsubmitinput').submit(); + try { + await helper.waitForPromise(() => { + const got = helper.linesDiv(); + if (got.length !== tc.wantPadLines.length) return false; + for (let i = 0; i < got.length; i++) { + const gotDiv = $('
').html(got[i].html()); + const wantDiv = $('
').html(tc.wantPadLines[i]); + if (!gotDiv[0].isEqualNode(wantDiv[0])) return false; + } + return true; + }); + } catch (err) { + const formatLine = (l) => ` ${JSON.stringify(l)}`; + const g = helper.linesDiv().map((div) => formatLine(div.html())).join('\n'); + const w = tc.wantPadLines.map(formatLine).join('\n'); + throw new Error(`Import failed. Got pad lines:\n${g}\nWant pad lines:\n${w}`); + } + }); + + it('export to HTML', async function () { + const link = helper.padChrome$('#exporthtmla').attr('href'); + const url = new URL(link, helper.padChrome$.window.location.href).href; + const gotHtml = await $.ajax({url, dataType: 'html'}); + const gotBody = $('body', htmlToDoc(gotHtml)); + gotBody.html(gotBody.html().replace(/^\s+|\s+$/g, '')); + const wantBody = $('body', htmlBodyToDoc(tc.wantExportHtmlBody)); + if (!gotBody[0].isEqualNode(wantBody[0])) { + throw new Error(`Got exported HTML body:\n ${JSON.stringify(gotBody.html())}\n` + + `Want HTML body:\n ${JSON.stringify(wantBody.html())}`); + } + }); + + it('export to text', async function () { + const link = helper.padChrome$('#exportplaina').attr('href'); + const url = new URL(link, helper.padChrome$.window.location.href).href; + const got = await $.ajax({url, dataType: 'text'}); + expect(got).to.be(tc.wantExportText); + }); + }); + } });