zotero/test/tests/utilities_internalTest.js

944 lines
35 KiB
JavaScript
Raw Normal View History

"use strict";
describe("Zotero.Utilities.Internal", function () {
var ZUI;
before(function () {
ZUI = Zotero.Utilities.Internal;
});
describe("#md5()", function () {
it("should generate hex string given file path", function* () {
var file = OS.Path.join(getTestDataDirectory().path, 'test.png');
assert.equal(
Zotero.Utilities.Internal.md5(Zotero.File.pathToFile(file)),
'93da8f1e5774c599f0942dcecf64b11c'
);
})
})
describe("#md5Async()", function () {
it("should generate hex string given file path", function* () {
var file = OS.Path.join(getTestDataDirectory().path, 'test.png');
yield assert.eventually.equal(
Zotero.Utilities.Internal.md5Async(file),
'93da8f1e5774c599f0942dcecf64b11c'
);
});
it("should generate hex string given file path for file bigger than chunk size", function* () {
var tmpDir = Zotero.getTempDirectory().path;
var file = OS.Path.join(tmpDir, 'md5Async');
let encoder = new TextEncoder();
let arr = encoder.encode("".padStart(100000, "a"));
yield OS.File.writeAtomic(file, arr);
yield assert.eventually.equal(
Zotero.Utilities.Internal.md5Async(file),
'1af6d6f2f682f76f80e606aeaaee1680'
);
yield OS.File.remove(file);
});
it("should return false for a nonexistent file", async function () {
var tmpDir = Zotero.getTempDirectory().path;
var file = OS.Path.join(tmpDir, 'nonexistent-asawefaweoihafa');
await assert.eventually.isFalse(ZUI.md5Async(file));
});
it("should return hash for an empty file", async function () {
const emptyHash = 'd41d8cd98f00b204e9800998ecf8427e';
var tmpDir = Zotero.getTempDirectory().path;
var file = OS.Path.join(tmpDir, 'empty-file');
await IOUtils.write(file, new Uint8Array());
await assert.eventually.equal(ZUI.md5Async(file), emptyHash);
await IOUtils.remove(file);
});
})
describe("#gzip()/gunzip()", function () {
it("should compress and decompress a Unicode text string", function* () {
var text = "Voilà! \u1F429";
var compstr = yield Zotero.Utilities.Internal.gzip(text);
assert.isAbove(compstr.length, 0);
assert.notEqual(compstr.length, text.length);
var str = yield Zotero.Utilities.Internal.gunzip(compstr);
assert.equal(str, text);
});
});
describe("#decodeUTF8()", function () {
it("should properly decode binary string", async function () {
let text = String.fromCharCode.apply(null, new Uint8Array([226, 130, 172]));
let utf8 = Zotero.Utilities.Internal.decodeUTF8(text);
assert.equal(utf8, "€");
});
});
describe("#containsEmoji()", function () {
it("should return true for text with an emoji", function () {
assert.isTrue(Zotero.Utilities.Internal.containsEmoji("🐩 Hello 🐩"));
});
it("should return true for text with an emoji with text representation that use Variation Selector-16", function () {
assert.isTrue(Zotero.Utilities.Internal.containsEmoji("This is a ⭐️"));
});
it("should return true for text with an emoji made up of multiple characters with ZWJ", function () {
assert.isTrue(Zotero.Utilities.Internal.containsEmoji("I am a 👨‍🌾"));
});
it("should return false for integer", function () {
assert.isFalse(Zotero.Utilities.Internal.containsEmoji("0"));
});
});
describe("#delayGenerator", function () {
var spy;
before(function () {
spy = sinon.spy(Zotero.Promise, "delay");
});
afterEach(function () {
spy.resetHistory();
});
after(function () {
spy.restore();
});
it("should delay for given amounts of time without limit", function* () {
var intervals = [1, 2];
var gen = Zotero.Utilities.Internal.delayGenerator(intervals);
// When intervals are exhausted, keep using last interval
var testIntervals = intervals.slice();
testIntervals.push(intervals[intervals.length - 1]);
for (let i of testIntervals) {
let val = yield gen.next().value;
assert.isTrue(val);
assert.isTrue(spy.calledWith(i));
spy.resetHistory();
}
});
it("should return false when maxTime is reached", function* () {
var intervals = [5, 10];
var gen = Zotero.Utilities.Internal.delayGenerator(intervals, 30);
// When intervals are exhausted, keep using last interval
var testIntervals = intervals.slice();
testIntervals.push(intervals[intervals.length - 1]);
for (let i of testIntervals) {
let val = yield gen.next().value;
assert.isTrue(val);
assert.isTrue(spy.calledWith(i));
spy.resetHistory();
}
// Another interval would put us over maxTime, so return false immediately
let val = yield gen.next().value;
assert.isFalse(val);
assert.isFalse(spy.called);
});
});
describe("#extractExtraFields()", function () {
it("should ignore 'Type: note', 'Type: attachment', and 'Type: annotation'", function () {
for (let type of ['note', 'attachment', 'annotation']) {
let str = `Type: ${type}`;
let { itemType, extra } = Zotero.Utilities.Internal.extractExtraFields(str);
assert.isNull(itemType, type);
assert.equal(extra, `Type: ${type}`, type);
}
});
it("should ignore numeric values for Type", function () {
for (let type of ['3']) {
let str = `Type: ${type}`;
let { itemType, extra } = Zotero.Utilities.Internal.extractExtraFields(str);
assert.isNull(itemType, type);
assert.equal(extra, `Type: ${type}`, type);
}
});
it("should use the first mapped Zotero type for a CSL type", function () {
var str = 'type: personal_communication';
var { itemType, fields, extra } = Zotero.Utilities.Internal.extractExtraFields(str);
assert.equal(itemType, 'letter');
});
it("should extract a field", function () {
var val = '10.1234/abcdef';
var str = `DOI: ${val}`;
var { fields, extra } = Zotero.Utilities.Internal.extractExtraFields(str);
assert.equal(fields.size, 1);
assert.equal(fields.get('DOI'), val);
assert.strictEqual(extra, '');
});
it("should extract a field for a given item", function () {
var item = createUnsavedDataObject('item', { itemType: 'journalArticle' });
var val = '10.1234/abcdef';
var str = `DOI: ${val}`;
var { fields, extra } = Zotero.Utilities.Internal.extractExtraFields(str, item);
assert.equal(fields.size, 1);
assert.equal(fields.get('DOI'), val);
assert.strictEqual(extra, '');
});
it("should extract a CSL field", function () {
var val = '10.1234/abcdef';
var str = `container-title: ${val}`;
var { fields, extra } = Zotero.Utilities.Internal.extractExtraFields(str);
assert.equal(fields.size, Zotero.Schema.CSL_TEXT_MAPPINGS['container-title'].length);
assert.equal(fields.get('publicationTitle'), val);
assert.strictEqual(extra, '');
});
it("should extract a CSL field for a given item type", function () {
var item = createUnsavedDataObject('item', { itemType: 'journalArticle' });
var val = '10.1234/abcdef';
var str = `container-title: ${val}`;
var { fields, extra } = Zotero.Utilities.Internal.extractExtraFields(str, item);
assert.equal(fields.size, 1);
assert.equal(fields.get('publicationTitle'), val);
assert.strictEqual(extra, '');
});
it("should extract a field with different case", function () {
var val = '10.1234/abcdef';
var str = `doi: ${val}`;
var { fields, extra } = Zotero.Utilities.Internal.extractExtraFields(str);
assert.equal(fields.size, 1);
assert.equal(fields.get('DOI'), val);
assert.strictEqual(extra, '');
});
it("should extract a field with other fields, text, and whitespace", function () {
var date = '2020-04-01';
var doi = '10.1234/abcdef';
var str = `Line 1\nDate: ${date}\nFoo: Bar\nDOI: ${doi}\n\nLine 2`;
var { fields, extra } = Zotero.Utilities.Internal.extractExtraFields(str);
assert.equal(fields.size, 2);
assert.equal(fields.get('date'), date);
assert.equal(fields.get('DOI'), doi);
assert.equal(extra, 'Line 1\nFoo: Bar\n\nLine 2');
});
it("should extract the first instance of a field", function () {
var date1 = '2020-04-01';
var date2 = '2020-04-02';
var str = `Date: ${date1}\nDate: ${date2}`;
var { fields, extra } = Zotero.Utilities.Internal.extractExtraFields(str);
assert.equal(fields.size, 1);
assert.equal(fields.get('date'), date1);
assert.equal(extra, "Date: " + date2);
});
it("shouldn't extract a field from a line that begins with a whitespace", function () {
var str = '\n number-of-pages: 11';
var { fields, extra } = Zotero.Utilities.Internal.extractExtraFields(str);
assert.equal(fields.size, 0);
});
it("shouldn't extract a field that already exists on the item", function () {
var item = createUnsavedDataObject('item', { itemType: 'book' });
item.setField('numPages', 10);
var str = 'number-of-pages: 11';
var { fields, extra } = Zotero.Utilities.Internal.extractExtraFields(str, item);
assert.equal(fields.size, 0);
});
it("should extract an author and add it to existing creators", function () {
var item = createUnsavedDataObject('item', { itemType: 'book' });
item.setCreator(0, { creatorType: 'author', name: 'Foo' });
var str = 'author: Bar';
var { fields, creators, extra } = Zotero.Utilities.Internal.extractExtraFields(str, item);
assert.equal(fields.size, 0);
assert.lengthOf(creators, 1);
assert.equal(creators[0].creatorType, 'author');
assert.equal(creators[0].name, 'Bar');
});
it("should extract a CSL date field", function () {
var str = 'issued: 2000';
var { fields, extra } = Zotero.Utilities.Internal.extractExtraFields(str);
assert.equal(fields.size, 1);
assert.equal(fields.get('date'), 2000);
assert.strictEqual(extra, '');
});
it("should extract a CSL name", function () {
2020-03-15 13:51:04 -04:00
var str = 'container-author: Last || First';
var { creators, extra } = Zotero.Utilities.Internal.extractExtraFields(str);
assert.lengthOf(creators, 1);
assert.propertyVal(creators[0], 'creatorType', 'bookAuthor');
assert.propertyVal(creators[0], 'firstName', 'First');
assert.propertyVal(creators[0], 'lastName', 'Last');
assert.strictEqual(extra, '');
});
it("should extract a CSL name that's valid for a given item type", function () {
var item = createUnsavedDataObject('item', { itemType: 'bookSection' });
2020-03-15 13:51:04 -04:00
var str = 'container-author: Last || First';
var { creators, extra } = Zotero.Utilities.Internal.extractExtraFields(str, item);
assert.lengthOf(creators, 1);
assert.propertyVal(creators[0], 'creatorType', 'bookAuthor');
assert.propertyVal(creators[0], 'firstName', 'First');
assert.propertyVal(creators[0], 'lastName', 'Last');
assert.strictEqual(extra, '');
});
it("shouldn't extract a CSL name that's not valid for a given item type", function () {
var item = createUnsavedDataObject('item', { itemType: 'journalArticle' });
2020-03-15 13:51:04 -04:00
var str = 'container-author: Last || First';
var { creators, extra } = Zotero.Utilities.Internal.extractExtraFields(str, item);
assert.lengthOf(creators, 0);
assert.strictEqual(extra, str);
});
it("should extract the citeproc-js cheater syntax", function () {
var issued = '{:number-of-pages:11}\n{:issued:2014}';
var { fields, extra } = Zotero.Utilities.Internal.extractExtraFields(issued);
assert.equal(fields.size, 2);
assert.equal(fields.get('numPages'), 11);
assert.equal(fields.get('date'), 2014);
assert.strictEqual(extra, '');
});
it("should ignore empty creator in citeproc-js cheater syntax", function () {
var str = '{:author: }\n';
var { fields, extra } = Zotero.Utilities.Internal.extractExtraFields(str);
assert.equal(fields.size, 0);
assert.strictEqual(extra, str);
});
});
describe("#combineExtraFields", function () {
var originalDate = "1887";
var publicationPlace = "New York";
var doi = '10.1234/123456789';
var fieldMap = new Map();
fieldMap.set('originalDate', originalDate);
fieldMap.set('publicationPlace', publicationPlace);
fieldMap.set('DOI', doi);
var fieldStr = `DOI: ${doi}\nOriginal Date: ${originalDate}\nPublication Place: ${publicationPlace}`;
it("should create 'field: value' pairs from field map", function () {
var extra = "";
var newExtra = ZUI.combineExtraFields(extra, fieldMap);
assert.equal(newExtra, fieldStr);
});
it("should add fields above existing Extra content", function () {
var extra = "This is a note.";
var newExtra = ZUI.combineExtraFields(extra, fieldMap);
assert.equal(newExtra, fieldStr + '\n' + extra);
});
it("should replace existing fields", function () {
var extra = "This is a note.\nOriginal Date: 1886\nFoo: Bar";
var newExtra = ZUI.combineExtraFields(extra, fieldMap);
assert.equal(
newExtra,
fieldStr.split(/\n/).filter(x => !x.startsWith('Original Date')).join("\n")
+ "\nThis is a note.\nOriginal Date: 1887\nFoo: Bar"
);
});
});
describe("#extractIdentifiers()", function () {
it("should extract ISBN-10", async function () {
var id = "0838985890";
var identifiers = ZUI.extractIdentifiers(id);
assert.lengthOf(identifiers, 1);
assert.lengthOf(Object.keys(identifiers[0]), 1);
assert.propertyVal(identifiers[0], "ISBN", id);
});
it("should extract ISBN-13", async function () {
var identifiers = ZUI.extractIdentifiers("978-0838985892");
assert.lengthOf(identifiers, 1);
assert.lengthOf(Object.keys(identifiers[0]), 1);
assert.propertyVal(identifiers[0], "ISBN", "9780838985892");
});
it("should extract multiple ISBN-13s", async function () {
var identifiers = ZUI.extractIdentifiers("978-0838985892 9781479347711 ");
assert.lengthOf(identifiers, 2);
assert.lengthOf(Object.keys(identifiers[0]), 1);
assert.lengthOf(Object.keys(identifiers[1]), 1);
assert.propertyVal(identifiers[0], "ISBN", "9780838985892");
assert.propertyVal(identifiers[1], "ISBN", "9781479347711");
});
it("should extract DOI", async function () {
var id = "10.4103/0976-500X.85940";
var identifiers = ZUI.extractIdentifiers(id);
assert.lengthOf(identifiers, 1);
assert.lengthOf(Object.keys(identifiers[0]), 1);
assert.propertyVal(identifiers[0], "DOI", id);
});
it("should extract PMID", async function () {
var identifiers = ZUI.extractIdentifiers("1 PMID:24297125,222 3-4 1234567890, 123456789");
assert.lengthOf(identifiers, 4);
assert.lengthOf(Object.keys(identifiers[0]), 1);
assert.lengthOf(Object.keys(identifiers[1]), 1);
assert.lengthOf(Object.keys(identifiers[2]), 1);
assert.lengthOf(Object.keys(identifiers[3]), 1);
assert.propertyVal(identifiers[0], "PMID", "1");
assert.propertyVal(identifiers[1], "PMID", "24297125");
assert.propertyVal(identifiers[2], "PMID", "222");
assert.propertyVal(identifiers[3], "PMID", "123456789");
});
2018-04-18 20:03:10 +03:00
it("should extract multiple old and new style arXivs", async function () {
var identifiers = ZUI.extractIdentifiers("0706.0044 arXiv:0706.00441v1,12345678,hep-ex/9809001v1, math.GT/0309135.");
2018-04-18 20:03:10 +03:00
assert.lengthOf(identifiers, 4);
assert.lengthOf(Object.keys(identifiers[0]), 1);
assert.lengthOf(Object.keys(identifiers[1]), 1);
assert.lengthOf(Object.keys(identifiers[2]), 1);
assert.lengthOf(Object.keys(identifiers[3]), 1);
assert.propertyVal(identifiers[0], "arXiv", "0706.0044");
assert.propertyVal(identifiers[1], "arXiv", "0706.00441");
assert.propertyVal(identifiers[2], "arXiv", "hep-ex/9809001");
assert.propertyVal(identifiers[3], "arXiv", "math.GT/0309135");
});
it("should extract ADS bibcodes", async function () {
var identifiers = ZUI.extractIdentifiers("9 2021wfc..rept....8D, 2022MSSP..16208010Y.");
assert.lengthOf(identifiers, 2);
assert.lengthOf(Object.keys(identifiers[0]), 1);
assert.lengthOf(Object.keys(identifiers[1]), 1);
assert.propertyVal(identifiers[0], "adsBibcode", "2021wfc..rept....8D");
assert.propertyVal(identifiers[1], "adsBibcode", "2022MSSP..16208010Y");
});
});
describe("#resolveLocale()", function () {
var availableLocales;
before(function () {
availableLocales = Services.locale.availableLocales;
});
function resolve(locale) {
return Zotero.Utilities.Internal.resolveLocale(locale, availableLocales);
}
it("should return en-US for en-US", function () {
assert.equal(resolve('en-US'), 'en-US');
});
it("should return en-US for en", function () {
assert.equal(resolve('en'), 'en-US');
});
it("should return fr-FR for fr-FR", function () {
assert.equal(resolve('fr-FR'), 'fr-FR');
});
it("should return fr-FR for fr", function () {
assert.equal(resolve('fr'), 'fr-FR');
});
it("should return ar for ar", function () {
assert.equal(resolve('ar'), 'ar');
});
it("should return pt-PT for pt", function () {
assert.equal(resolve('pt'), 'pt-PT');
});
it("should return zh-CN for zh-CN", function () {
assert.equal(resolve('zh-CN'), 'zh-CN');
});
it("should return zh-TW for zh-TW", function () {
assert.equal(resolve('zh-TW'), 'zh-TW');
});
it("should return zh-CN for zh", function () {
assert.equal(resolve('zh'), 'zh-CN');
});
});
describe("#camelToTitleCase()", function () {
it("should convert 'fooBar' to 'Foo Bar'", function () {
assert.equal(Zotero.Utilities.Internal.camelToTitleCase('fooBar'), 'Foo Bar');
});
it("should keep all-caps strings intact", function () {
assert.equal(Zotero.Utilities.Internal.camelToTitleCase('DOI'), 'DOI');
});
it("should convert 'fooBAR' to 'Foo BAR'", function () {
assert.equal(Zotero.Utilities.Internal.camelToTitleCase('fooBAR'), 'Foo BAR');
});
});
describe("#getNextName()", function () {
it("should get the next available numbered name", function () {
var existing = ['Name', 'Name 1', 'Name 3'];
assert.equal(Zotero.Utilities.Internal.getNextName('Name', existing), 'Name 2');
});
it("should return 'Name 1' if no numbered names", function () {
var existing = ['Name'];
assert.equal(Zotero.Utilities.Internal.getNextName('Name', existing), 'Name 1');
});
it("should return 'Name' if only numbered names", function () {
var existing = ['Name 1', 'Name 3'];
assert.equal(Zotero.Utilities.Internal.getNextName('Name', existing), 'Name');
});
it("should trim given name if trim=true", function () {
var existing = ['Name', 'Name 1', 'Name 2', 'Name 3'];
assert.equal(Zotero.Utilities.Internal.getNextName('Name 2', existing, true), 'Name 4');
});
});
describe("#parseURL()", function () {
var f;
before(() => {
f = Zotero.Utilities.Internal.parseURL;
});
describe("#fileName", function () {
it("should contain filename", function () {
assert.propertyVal(f('http://example.com/abc/def.html?foo=bar'), 'fileName', 'def.html');
});
it("should be empty if no filename", function () {
assert.propertyVal(f('http://example.com/abc/'), 'fileName', '');
});
});
describe("#fileExtension", function () {
it("should contain extension", function () {
assert.propertyVal(f('http://example.com/abc/def.html?foo=bar'), 'fileExtension', 'html');
});
it("should be empty if no extension", function () {
assert.propertyVal(f('http://example.com/abc/def'), 'fileExtension', '');
});
it("should be empty if no filename", function () {
assert.propertyVal(f('http://example.com/abc/'), 'fileExtension', '');
});
});
describe("#fileBaseName", function () {
it("should contain base name", function () {
assert.propertyVal(f('http://example.com/abc/def.html?foo=bar'), 'fileBaseName', 'def');
});
it("should equal filename if no extension", function () {
assert.propertyVal(f('http://example.com/abc/def'), 'fileBaseName', 'def');
});
it("should be empty if no filename", function () {
assert.propertyVal(f('http://example.com/abc/'), 'fileBaseName', '');
});
});
});
describe("#generateHTMLFromTemplate()", function () {
it("should support variables with attributes", function () {
var vars = {
v1: '1',
v2: pars => `${pars.a1 ?? ''}${pars.a2 ?? ''}${pars.a3 ?? ''}`,
v3: () => '',
v5: () => 'something',
ar1: [],
ar2: [1, 2]
};
var template = `{{ v1}}{{v2 a1= "1" a2 =' 2' a3 = "3 "}}{{v3}}{{v4}}{{if ar1}}ar1{{endif}}{{if ar2}}{{ar2}}{{endif}}{{if v5}}yes{{endif}}{{if v3}}no1{{endif}}{{if v2}}{{v2}}{{endif}}`;
var html = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
assert.equal(html, '11 23 1,2yes');
});
it("should support empty string as attribute value and correctly render returned false-ish values", function () {
const vars = {
length: ({ string }) => string.length.toString(),
};
const template = `"" has a length of {{ length string="" }} and "hello" has a length of {{ length string="hello" }}`;
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
assert.equal(out, '"" has a length of 0 and "hello" has a length of 5');
});
it("should support functions in comparison statements", function () {
const vars = {
sum: ({ a, b }) => (parseInt(a) + parseInt(b)).toString(),
fooBar: ({ isFoo }) => (isFoo === 'true' ? 'foo' : 'bar'),
false: 'false',
twoWords: 'two words',
onlyOne: 'actually == 1'
};
const template = `{{if {{ sum a="1" b="2" }} == "3"}}1 + 2 = {{sum a="1" b="2"}}{{else}}no speak math{{endif}}`;
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
assert.equal(out, '1 + 2 = 3');
const template2 = '{{if false != "false"}}no{{elseif false == "false"}}yes{{else}}no{{endif}}';
const out2 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template2, vars);
assert.equal(out2, 'yes');
const template3 = '{{ if twoWords == "two words" }}yes{{else}}no{{endif}}';
const out3 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template3, vars);
assert.equal(out3, 'yes');
const template4 = '{{ if onlyOne == \'actually == 1\' }}yes{{else}}no{{endif}}';
const out4 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template4, vars);
assert.equal(out4, 'yes');
const template5 = '{{ if "3" == {{ sum a="1" b="2" }} }}yes{{else}}no{{endif}}';
const out5 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template5, vars);
assert.equal(out5, 'yes');
const template6 = '{{ if {{ sum a="1" b="2" }} }}yes{{else}}no{{endif}}';
const out6 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template6, vars);
assert.equal(out6, 'yes');
const template7 = '{{ if {{ twoWords }} }}yes{{else}}no{{endif}}';
const out7 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template7, vars);
assert.equal(out7, 'yes');
const template8 = '{{ if twoWords }}yes{{else}}no{{endif}}';
const out8 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template8, vars);
assert.equal(out8, 'yes');
const template9 = '{{ if missing }}no{{else}}yes{{endif}}';
const out9 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template9, vars);
assert.equal(out9, 'yes');
const template10 = '{{ if {{ missing foo="bar" }} }}no{{else}}yes{{endif}}';
const out10 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template10, vars);
assert.equal(out10, 'yes');
const template11 = '{{ if {{ missing foo="bar" }} == "" }}yes{{else}}no{{endif}}';
const out11 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template11, vars);
assert.equal(out11, 'yes');
const template12 = '{{ if fooBar == "bar" }}yes{{else}}no{{endif}}';
const out12 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template12, vars);
assert.equal(out12, 'yes');
const template13 = '{{ if {{ fooBar }} == "bar" }}yes{{else}}no{{endif}}';
const out13 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template13, vars);
assert.equal(out13, 'yes');
const template14 = `{{if {{ sum a="1" b="2" }}=="3"}}1 + 2 = {{sum a="1" b="2"}}{{else}}no{{endif}}`;
const out14 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template14, vars);
assert.equal(out14, '1 + 2 = 3');
const template15 = `{{if "two words"==twoWords}}yes{{else}}no{{endif}}`;
const out15 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template15, vars);
assert.equal(out15, 'yes');
});
it("should support relational operators", function () {
const vars = {
sum: ({ a, b }) => (parseInt(a) + parseInt(b)).toString(),
v1: '1',
v2: 'foo',
v3: '100',
v4: '99',
π: '3.14',
};
const template1 = `{{if v1 > π}}more than π{{elseif v1 <= π}}less or equal to π{{endif}}`;
const out1 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template1, vars);
assert.equal(out1, 'less or equal to π');
const template2 = `{{if {{ sum a="2" b="3" }} > π}}more than π{{else}}less or equal to π{{endif}}`;
const out2 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template2, vars);
assert.equal(out2, 'more than π');
const template3 = `{{if 3.14 >= π}}more than or equal to π{{else}}less than π{{endif}}`;
const out3 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template3, vars);
assert.equal(out3, 'more than or equal to π');
const template4 = `{{if v3 > v4}}100 is more than 99{{else}}string "100" would be sorted before "99"{{endif}}`;
const out4 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template4, vars);
assert.equal(out4, '100 is more than 99');
// This is undocumented and unsupported behavior, but comparing strings should work
const template5 = `{{if "test" > v2}}"t" is after "f" in the alphabet{{else}}no{{endif}}`;
const out5 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template5, vars);
assert.equal(out5, '"t" is after "f" in the alphabet');
const template6 = `{{if "bar" < v2 }}"f" is before "b" in the alphabet{{else}}no{{endif}}`;
const out6 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template6, vars);
assert.equal(out6, '"f" is before "b" in the alphabet');
});
it("should accept hyphen-case variables and attributes", function () {
const vars = {
fooBar: ({ isFoo }) => (isFoo === 'true' ? 'foo' : 'bar'),
};
const template = '{{ foo-bar is-foo="true" }}{{ if {{ foo-bar is-foo="false" }} == "bar" }}{{ foo-bar is-foo="false" }}{{ endif }}';
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
assert.equal(out, 'foobar');
});
it("should work with a condition in the middle", function () {
const vars = {
v1: '1',
};
const template = 'test {{ if v1 == "1" }}yes{{ else }}no{{ endif }} foobar';
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
assert.equal(out, 'test yes foobar');
});
it("missing identifiers are evaluted as empty string", function () {
const vars = {
foo: 'foo',
};
const template = '{{bar}}{{ if foo == "" }}no{{elseif foo}}{{foo}}{{else}}no{{endif}}';
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
assert.equal(out, 'foo');
const template2 = 'test: {{ if bar == "" }}yes{{else}}no{{endif}}';
const out2 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template2, vars);
assert.equal(out2, 'test: yes');
});
it("should preserve whitespace outside of brackets", function () {
const template = ' starts }} with {{ whitespace {"test"} == \'foobar\' ';
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, {});
assert.equal(out, template);
const vars = {
space: ' ',
spaceFn: () => ' ',
};
const whitespace = ' {{if spaceFn}}{{else}} {{endif}}{{space}} {{space-fn}}';
const out2 = Zotero.Utilities.Internal.generateHTMLFromTemplate(whitespace, vars);
assert.equal(out2, ' ');
});
it("should accept array values in logic statements", function () {
let someTags = ['foo', 'bar'];
const vars = {
tags: ({ join }) => (join ? someTags.join(join) : someTags),
};
const template = '{{ if tags }}#{{ tags join=" #" }}{{else}}no tags{{endif}}';
const out = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
assert.equal(out, '#foo #bar');
someTags = [];
const out2 = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
assert.equal(out2, 'no tags');
});
it("should throw if function returns anything else than a string (or an array which is always joined into string)", function () {
const vars = {
number: () => 1,
logic: () => true,
array: () => [],
fn: () => 1,
};
assert.throws(() => Zotero.Utilities.Internal.generateHTMLFromTemplate('{{ number }}', vars), /Identifier "number" does not evaluate to a string/);
assert.throws(() => Zotero.Utilities.Internal.generateHTMLFromTemplate('{{ logic }}', vars), /Identifier "logic" does not evaluate to a string/);
assert.throws(() => Zotero.Utilities.Internal.generateHTMLFromTemplate('{{ if fn }}no{{endif}}', vars), /Identifier "fn" does not evaluate to a string/);
assert.throws(() => Zotero.Utilities.Internal.generateHTMLFromTemplate('{{ if {{ fn foo="bar" }} }}no{{endif}}', vars), /Identifier "fn" does not evaluate to a string/);
});
it("should support nested 'if' statements", function () {
var vars = {
v1: '1',
v2: 'H',
};
var template = `{{if v1 == '1'}}yes1{{if x}}no{{elseif v2 == "h" }}yes2{{endif}}{{elseif v2 == "2"}}no{{else}}no{{endif}} {{if v2 == "1"}}not{{elseif x}}not{{else}}yes3{{ endif}}`;
var html = Zotero.Utilities.Internal.generateHTMLFromTemplate(template, vars);
assert.equal(html, 'yes1yes2 yes3');
});
});
describe("OpenURL", function () {
var item;
before(async function () {
item = await createDataObject('item', { title: 'Foo Bar', date: '2024-12-19' });
})
after(function () {
Zotero.Prefs.clear('openURL.resolver');
});
describe("#resolve()", function () {
it("should add trailing '?' if no query string", async function () {
Zotero.Prefs.set("openURL.resolver", "https://resolver.ebsco.com/c/abcdef/result");
var url = Zotero.Utilities.Internal.OpenURL.resolve(item);
assert.include(url, 'result?url_ver=Z39.88-2004');
});
it("should add trailing '&' if already a query string", async function () {
Zotero.Prefs.set("openURL.resolver", "https://resolver.ebscohost.com/openurl?custid=abcdef&groupid=main&profile=ftf&authtype=ip,uid");
var url = Zotero.Utilities.Internal.OpenURL.resolve(item);
assert.include(url, 'authtype=ip,uid&url_ver=Z39.88-2004');
});
it("should add trailing '?' after /login?url=", async function () {
Zotero.Prefs.set("openURL.resolver", "https://proxy.school.edu/login?url=https://resolver.ebscohost.com/openurl");
var url = Zotero.Utilities.Internal.OpenURL.resolve(item);
assert.include(url, 'openurl?url_ver=Z39.88-2004');
});
it("shouldn't add trailing '?' after /login?url= if URL already ends in '?'", async function () {
Zotero.Prefs.set("openURL.resolver", "https://proxy.school.edu/login?url=https://resolver.ebscohost.com/openurl?");
var url = Zotero.Utilities.Internal.OpenURL.resolve(item);
assert.include(url, 'openurl?url_ver=Z39.88-2004');
});
});
});
2025-03-06 10:58:21 -05:00
describe("#renderItemTitle()", function () {
function renderToElement(title) {
let elem = new DOMParser().parseFromString('<div></div>', 'text/html')
.querySelector('div');
Zotero.Utilities.Internal.renderItemTitle(title, elem);
return elem;
}
function renderToHTML(title) {
return renderToElement(title).innerHTML;
}
function renderToTextViaHTML(title) {
return renderToElement(title).textContent;
}
function renderToText(title) {
return Zotero.Utilities.Internal.renderItemTitle(title);
}
it("should render a title without tags unchanged", function () {
assert.equal(renderToHTML('My Title'), 'My Title');
assert.equal(renderToHTML('1 < 2'), '1 &lt; 2');
assert.equal(renderToHTML('<<ATTENTION>> READ THIS'), '&lt;&lt;ATTENTION&gt;&gt; READ THIS');
});
it("should render single-level tags", function () {
let examples = [
'My <i>Title</i> XYZ',
'My <b>Title</b> XYZ',
'My <sub>Title</sub> XYZ',
'My <sup>Title</sup> XYZ',
];
for (let example of examples) {
assert.equal(renderToHTML(example), example);
}
assert.equal(renderToHTML('My <span class="nocase">Title</span> XYZ'), 'My <span>Title</span> XYZ');
// Citeproc wants no space, DOM output includes space. Confusing!
assert.equal(
renderToHTML('My <span style="font-variant:small-caps;">Title</span> XYZ'),
'My <span style="font-variant: small-caps;">Title</span> XYZ'
);
});
it("should render a tag nested inside a different tag", function () {
let examples = [
'My <i><b>Title</b></i> XYZ',
'My <b><i>Title</i></b> XYZ',
'My <sub><i>Title</i></sub> XYZ',
];
for (let example of examples) {
assert.equal(renderToHTML(example), example);
}
});
it("should preserve mismatched opening and closing tags", function () {
assert.equal(
renderToHTML('My <i><b>Title</i> XYZ'),
'My <i>&lt;b&gt;Title</i> XYZ'
);
assert.equal(
renderToHTML('My <b>Title</i></b> XYZ'),
'My <b>Title&lt;/i&gt;</b> XYZ'
);
assert.equal(
renderToHTML('My <sub><span class="font-variant:small-caps;">Title</sub> XYZ'),
'My <sub>&lt;span class="font-variant:small-caps;"&gt;Title</sub> XYZ'
);
assert.equal(
renderToHTML('My <sub><span class="font-variant:small-caps;">Title</sub></span> XYZ'),
'My <sub>&lt;span class="font-variant:small-caps;"&gt;Title</sub>&lt;/span&gt; XYZ'
);
});
it("should preserve matched but unsupported tags", function () {
assert.equal(
renderToHTML('My <em>Title</em> XYZ'),
'My &lt;em&gt;Title&lt;/em&gt; XYZ'
);
assert.equal(
renderToHTML('My <strong>Title</strong> XYZ'),
'My &lt;strong&gt;Title&lt;/strong&gt; XYZ'
);
assert.equal(
renderToHTML('My <span class="font-variant: small-caps;">Title</span> XYZ'), // Extra space
'My &lt;span class="font-variant: small-caps;"&gt;Title&lt;/span&gt; XYZ'
);
assert.equal(
renderToHTML('My <span>Title</span> XYZ'), // Extra space
'My &lt;span&gt;Title&lt;/span&gt; XYZ'
);
});
it("should invert a tag nested inside itself", function () {
assert.equal(
renderToHTML('<i>A <i>Title</i> in Italics</i>'),
'<i>A <span style="font-style: normal;">Title</span> in Italics</i>'
);
assert.equal(
renderToHTML('<b>A <b>Title</b> in Bold</b>'),
'<b>A <span style="font-weight: normal;">Title</span> in Bold</b>'
);
});
it("should invert a tag nested inside itself, with a different tag in between", function () {
assert.equal(
renderToHTML('<i>A <span class="nocase"><i>Title</i></span> in Italics</i>'),
'<i>A <span><span style="font-style: normal;">Title</span></span> in Italics</i>'
);
});
it("should render the same text directly and via DOM", function () {
let examples = [
'Basic Title',
'Complicated <i>Title</i>',
'Complicated <i>Title</i> With More Text',
'Nested <i>T<b>i</b>tle</i> With More Text',
'Self-Nested <i>T<i>i</i>tle</i> With More Text',
'Fe<sub>1<i>x</i></sub>O',
'Title with <marquee>INVALID TAGS</marquee> <strong>like this</strong>',
];
for (let example of examples) {
assert.equal(renderToTextViaHTML(example), renderToText(example));
}
});
});
});