mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-01-07 16:12:53 +01:00
Tests for admin search, simplifications
This commit is contained in:
@@ -83,5 +83,7 @@
|
||||
"white" : false, // true: Check against strict whitespace and indentation rules
|
||||
|
||||
// Custom Globals
|
||||
"globals" : {} // additional predefined global variables
|
||||
"globals" : {
|
||||
"Promise": true
|
||||
} // additional predefined global variables
|
||||
}
|
||||
@@ -339,6 +339,68 @@
|
||||
|
||||
Translator.moduleFactories = {};
|
||||
|
||||
/**
|
||||
* Remove the translator patterns from text
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
Translator.removePatterns = function removePatterns(text) {
|
||||
var len = text.length;
|
||||
var cursor = 0;
|
||||
var lastBreak = 0;
|
||||
var level = 0;
|
||||
var out = '';
|
||||
var sub;
|
||||
|
||||
while (cursor < len) {
|
||||
sub = text.slice(cursor, cursor + 2);
|
||||
if (sub === '[[') {
|
||||
if (level === 0) {
|
||||
out += text.slice(lastBreak, cursor);
|
||||
}
|
||||
level += 1;
|
||||
cursor += 2;
|
||||
} else if (sub === ']]') {
|
||||
level -= 1;
|
||||
cursor += 2;
|
||||
if (level === 0) {
|
||||
lastBreak = cursor;
|
||||
}
|
||||
} else {
|
||||
cursor += 1;
|
||||
}
|
||||
}
|
||||
out += text.slice(lastBreak, cursor);
|
||||
return out;
|
||||
};
|
||||
|
||||
/**
|
||||
* Escape translator patterns in text
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
Translator.escape = function escape(text) {
|
||||
return typeof text === 'string' ? text.replace(/\[\[([\S]*?)\]\]/g, '\\[\\[$1\\]\\]') : text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unescape escaped translator patterns in text
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
Translator.unescape = function unescape(text) {
|
||||
return typeof text === 'string' ? text.replace(/\\\[\\\[([\S]*?)\\\]\\\]/g, '[[$1]]') : text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a translator pattern
|
||||
*/
|
||||
Translator.compile = function compile() {
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
|
||||
return '[[' + args.join(', ') + ']]';
|
||||
};
|
||||
|
||||
return Translator;
|
||||
}());
|
||||
|
||||
@@ -348,12 +410,16 @@
|
||||
*/
|
||||
Translator: Translator,
|
||||
|
||||
compile: Translator.compile,
|
||||
escape: Translator.escape,
|
||||
unescape: Translator.unescape,
|
||||
getLanguage: Translator.getLanguage,
|
||||
|
||||
/**
|
||||
* Legacy translator function for backwards compatibility
|
||||
*/
|
||||
translate: function translate(text, language, callback) {
|
||||
// console.warn('[translator] `translator.translate(text, [lang, ]callback)` is deprecated. ' +
|
||||
// 'Use the `translator.Translator` class instead.');
|
||||
// TODO: deprecate?
|
||||
|
||||
var cb = callback;
|
||||
var lang = language;
|
||||
@@ -373,31 +439,6 @@
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Construct a translator pattern
|
||||
* @param {string} name - Translation name
|
||||
* @param {string[]} args - Optional arguments for the pattern
|
||||
*/
|
||||
compile: function compile() {
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
|
||||
return '[[' + args.join(', ') + ']]';
|
||||
},
|
||||
|
||||
/**
|
||||
* Escape translation patterns from text
|
||||
*/
|
||||
escape: function escape(text) {
|
||||
return typeof text === 'string' ? text.replace(/\[\[([\S]*?)\]\]/g, '\\[\\[$1\\]\\]') : text;
|
||||
},
|
||||
|
||||
/**
|
||||
* Unescape translation patterns from text
|
||||
*/
|
||||
unescape: function unescape(text) {
|
||||
return typeof text === 'string' ? text.replace(/\\\[\\\[([\S]*?)\\\]\\\]/g, '[[$1]]') : text;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add translations to the cache
|
||||
*/
|
||||
@@ -422,11 +463,6 @@
|
||||
adaptor.getTranslations(language, namespace, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the language of the current environment, falling back to defaults
|
||||
*/
|
||||
getLanguage: Translator.getLanguage,
|
||||
|
||||
toggleTimeagoShorthand: function toggleTimeagoShorthand() {
|
||||
var tmp = assign({}, jQuery.timeago.settings.strings);
|
||||
jQuery.timeago.settings.strings = assign({}, adaptor.timeagoShort);
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var nconf = require('nconf');
|
||||
var sanitize = require('sanitize-html');
|
||||
var sanitizeHTML = require('sanitize-html');
|
||||
|
||||
var languages = require('../languages');
|
||||
var meta = require('../meta');
|
||||
var utils = require('../../public/src/utils');
|
||||
var Translator = require('../../public/src/modules/translator');
|
||||
|
||||
function walk(directory) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
@@ -45,117 +44,102 @@ function loadLanguage(language, filename) {
|
||||
});
|
||||
}
|
||||
|
||||
function filterDirectories(directories) {
|
||||
return directories.map(function (dir) {
|
||||
// get the relative path
|
||||
return dir.replace(/^.*(admin.*?).tpl$/, '$1');
|
||||
}).filter(function (dir) {
|
||||
// exclude partials
|
||||
// only include subpaths
|
||||
return !dir.includes('/partials/') && /\/.*\//.test(dir);
|
||||
});
|
||||
}
|
||||
|
||||
function getAdminNamespaces() {
|
||||
return walk(path.resolve('./public/templates/admin'))
|
||||
.then(function (directories) {
|
||||
return directories.map(function (dir) {
|
||||
return dir.replace(/^.*(admin.*?).tpl$/, '$1');
|
||||
}).filter(function (dir) {
|
||||
return !dir.includes('/partials/');
|
||||
}).filter(function (dir) {
|
||||
return dir.match(/\/.*\//);
|
||||
});
|
||||
});
|
||||
.then(filterDirectories);
|
||||
}
|
||||
|
||||
function sanitize(html) {
|
||||
// reduce the template to just meaningful text
|
||||
// remove all tags and strip out scripts, etc completely
|
||||
return sanitizeHTML(html, {
|
||||
allowedTags: [],
|
||||
allowedAttributes: [],
|
||||
});
|
||||
}
|
||||
|
||||
function simplify(translations) {
|
||||
return translations
|
||||
// remove all mustaches
|
||||
.replace(/(?:\{{1,2}[^\}]*?\}{1,2})/g, '')
|
||||
// collapse whitespace
|
||||
.replace(/(?:[ \t]*[\n\r]+[ \t]*)+/g, '\n')
|
||||
.replace(/[\t ]+/g, ' ');
|
||||
}
|
||||
|
||||
var fallbackCache = {};
|
||||
|
||||
function removeTranslatorPatterns(str) {
|
||||
var len = str.len;
|
||||
var cursor = 0;
|
||||
var lastBreak = 0;
|
||||
var level = 0;
|
||||
var out = '';
|
||||
var sub;
|
||||
function initFallback(namespace) {
|
||||
return readFile(path.resolve('./public/templates/', namespace + '.tpl'))
|
||||
.then(function (template) {
|
||||
var translations = sanitize(template);
|
||||
translations = simplify(translations);
|
||||
translations = Translator.removePatterns(translations);
|
||||
|
||||
while (cursor < len) {
|
||||
sub = str.slice(cursor, cursor + 2);
|
||||
if (sub === '[[') {
|
||||
if (level === 0) {
|
||||
out += str.slice(lastBreak, cursor);
|
||||
}
|
||||
level += 1;
|
||||
cursor += 2;
|
||||
} else if (sub === ']]') {
|
||||
level -= 1;
|
||||
cursor += 2;
|
||||
if (level === 0) {
|
||||
lastBreak = cursor;
|
||||
}
|
||||
} else {
|
||||
cursor += 1;
|
||||
}
|
||||
}
|
||||
out += str.slice(lastBreak, cursor);
|
||||
return out;
|
||||
return {
|
||||
namespace: namespace,
|
||||
translations: translations,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function fallback(namespace) {
|
||||
fallbackCache[namespace] = fallbackCache[namespace] ||
|
||||
readFile(path.resolve('./public/templates/', namespace + '.tpl'))
|
||||
.then(function (template) {
|
||||
// reduce the template to just meaningful text
|
||||
// remove scripts, etc and replace all tags with divs
|
||||
var translations = sanitize(template, {
|
||||
transformTags: {
|
||||
'*': function () {
|
||||
return {
|
||||
tagName: 'div'
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
// remove all html tags, templating stuff, and translation strings
|
||||
.replace(/(?:<div>)|(?:<\/div>)|(?:\{[^\{\}]*\})/g, '')
|
||||
// collapse whitespace
|
||||
.replace(/([\n\r]+ ?)+/g, '\n')
|
||||
.replace(/[\t ]+/g, ' ');
|
||||
|
||||
translations = removeTranslatorPatterns(translations);
|
||||
|
||||
return {
|
||||
namespace: namespace,
|
||||
translations: translations,
|
||||
};
|
||||
});
|
||||
|
||||
// use cache if exists, else make it
|
||||
fallbackCache[namespace] = fallbackCache[namespace] || initFallback(namespace);
|
||||
return fallbackCache[namespace];
|
||||
}
|
||||
|
||||
function initDict(language) {
|
||||
return getAdminNamespaces().then(function (namespaces) {
|
||||
return Promise.all(namespaces.map(function (namespace) {
|
||||
return loadLanguage(language, namespace).then(function (translations) {
|
||||
return { namespace: namespace, translations: translations };
|
||||
}).then(function (params) {
|
||||
var namespace = params.namespace;
|
||||
var translations = params.translations;
|
||||
return loadLanguage(language, namespace)
|
||||
.then(function (translations) {
|
||||
// join all translations into one string separated by newlines
|
||||
var str = Object.keys(translations).map(function (key) {
|
||||
return translations[key];
|
||||
}).join('\n');
|
||||
|
||||
var str = Object.keys(translations).map(function (key) {
|
||||
return translations[key];
|
||||
}).join('\n');
|
||||
|
||||
return {
|
||||
namespace: namespace,
|
||||
translations: str
|
||||
};
|
||||
})
|
||||
// TODO: Use translator to get title for admin route?
|
||||
.catch(function () {
|
||||
return fallback(namespace);
|
||||
})
|
||||
.catch(function () {
|
||||
return { namespace: namespace, translations: '' };
|
||||
});
|
||||
return {
|
||||
namespace: namespace,
|
||||
translations: str,
|
||||
};
|
||||
})
|
||||
// TODO: Use translator to get title for admin route?
|
||||
.catch(function () {
|
||||
// no translations for this route, fallback to template
|
||||
return fallback(namespace);
|
||||
})
|
||||
.catch(function () {
|
||||
// no fallback, just return blank
|
||||
return {
|
||||
namespace: namespace,
|
||||
translations: '',
|
||||
};
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
var cache = {};
|
||||
|
||||
function getDict(language, term) {
|
||||
function getDict(language) {
|
||||
// use cache if exists, else make it
|
||||
cache[language] = cache[language] || initDict(language);
|
||||
return cache[language];
|
||||
}
|
||||
|
||||
module.exports.getDict = getDict;
|
||||
module.exports.filterDirectories = filterDirectories;
|
||||
module.exports.simplify = simplify;
|
||||
module.exports.sanitize = sanitize;
|
||||
|
||||
82
test/search-admin.js
Normal file
82
test/search-admin.js
Normal file
@@ -0,0 +1,82 @@
|
||||
'use strict';
|
||||
/*global require*/
|
||||
|
||||
var assert = require('assert');
|
||||
var search = require('../src/admin/search.js');
|
||||
|
||||
describe('admin search', function () {
|
||||
describe('filterDirectories', function () {
|
||||
it('should resolve all paths to relative paths', function (done) {
|
||||
assert.deepEqual(search.filterDirectories([
|
||||
'hfjksfd/fdsgagag/admin/gdhgfsdg/sggag.tpl',
|
||||
]), [
|
||||
'admin/gdhgfsdg/sggag',
|
||||
]);
|
||||
done();
|
||||
});
|
||||
it('should exclude partials', function (done) {
|
||||
assert.deepEqual(search.filterDirectories([
|
||||
'hfjksfd/fdsgagag/admin/gdhgfsdg/sggag.tpl',
|
||||
'dfahdfsgf/admin/partials/hgkfds/fdhsdfh.tpl',
|
||||
]), [
|
||||
'admin/gdhgfsdg/sggag',
|
||||
]);
|
||||
done();
|
||||
});
|
||||
it('should exclude files in the admin directory', function (done) {
|
||||
assert.deepEqual(search.filterDirectories([
|
||||
'hfjksfd/fdsgagag/admin/gdhgfsdg/sggag.tpl',
|
||||
'dfdasg/admin/hjkdfsk.tpl',
|
||||
]), [
|
||||
'admin/gdhgfsdg/sggag',
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitize', function () {
|
||||
it('should strip out scripts', function (done) {
|
||||
assert.equal(
|
||||
search.sanitize('Pellentesque tristique senectus' +
|
||||
'<script>alert("nope");</script> habitant morbi'),
|
||||
'Pellentesque tristique senectus' +
|
||||
' habitant morbi'
|
||||
);
|
||||
done();
|
||||
});
|
||||
it('should remove all tags', function (done) {
|
||||
assert.equal(
|
||||
search.sanitize('<p>Pellentesque <b>habitant morbi</b> tristique senectus' +
|
||||
'Aenean <i>vitae</i> est.Mauris <a href="placerat">eleifend</a> leo.</p>'),
|
||||
'Pellentesque habitant morbi tristique senectus' +
|
||||
'Aenean vitae est.Mauris eleifend leo.'
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('simplify', function () {
|
||||
it('should remove all mustaches', function (done) {
|
||||
assert.equal(
|
||||
search.simplify(
|
||||
'Pellentesque tristique {{senectus}}habitant morbi' +
|
||||
'liquam tincidunt {{mauris.eu}}risus'
|
||||
),
|
||||
'Pellentesque tristique habitant morbi' +
|
||||
'liquam tincidunt risus'
|
||||
);
|
||||
done();
|
||||
});
|
||||
it('should collapse all whitespace', function (done) {
|
||||
assert.equal(
|
||||
search.simplify(
|
||||
'Pellentesque tristique habitant morbi' +
|
||||
' \n\n liquam tincidunt mauris eu risus.'
|
||||
),
|
||||
'Pellentesque tristique habitant morbi' +
|
||||
'\nliquam tincidunt mauris eu risus.'
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -233,3 +233,15 @@ describe('Translator modules', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Translator static methods', function () {
|
||||
describe('.removePatterns', function () {
|
||||
it('should remove translator patterns from text', function (done) {
|
||||
assert.strictEqual(
|
||||
Translator.removePatterns('Lorem ipsum dolor [[sit:amet]], consectetur adipiscing elit. [[sed:vitae, [[semper:dolor]]]] lorem'),
|
||||
'Lorem ipsum dolor , consectetur adipiscing elit. lorem'
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user