From dec8040c095b7aa117536521ac16c2acd22ce345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 29 Jul 2019 19:42:55 -0400 Subject: [PATCH] feat: widgets/index.js enable widget-essentials in tests fix widget test --- src/widgets/index.js | 380 ++++++++++++++++--------------------- test/controllers.js | 14 +- test/mocks/databasemock.js | 1 + 3 files changed, 168 insertions(+), 227 deletions(-) diff --git a/src/widgets/index.js b/src/widgets/index.js index 4fcef7fc2e..470f1d69bb 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -1,180 +1,144 @@ 'use strict'; -var async = require('async'); -var winston = require('winston'); -var _ = require('lodash'); -var Benchpress = require('benchpressjs'); +const async = require('async'); +const winston = require('winston'); +const _ = require('lodash'); +const Benchpress = require('benchpressjs'); +const util = require('util'); + +const plugins = require('../plugins'); +const groups = require('../groups'); +const translator = require('../translator'); +const db = require('../database'); +const apiController = require('../controllers/api'); +const loadConfigAsync = util.promisify(apiController.loadConfig); +const meta = require('../meta'); + +const widgets = module.exports; + +widgets.render = async function (uid, options) { + if (!options.template) { + throw new Error('[[error:invalid-data]]'); + } + const data = await widgets.getWidgetDataForTemplates(['global', options.template]); + delete data.global.drafts; -var plugins = require('../plugins'); -var groups = require('../groups'); -var translator = require('../translator'); -var db = require('../database'); -var apiController = require('../controllers/api'); -var meta = require('../meta'); + const locations = _.uniq(Object.keys(data.global).concat(Object.keys(data[options.template]))); -var widgets = module.exports; + const widgetData = await Promise.all(locations.map(location => renderLocation(location, data, uid, options))); -widgets.render = function (uid, options, callback) { - if (!options.template) { - return callback(new Error('[[error:invalid-data]]')); - } + const returnData = {}; + locations.forEach(function (location, i) { + if (Array.isArray(widgetData[i]) && widgetData[i].length) { + returnData[location] = widgetData[i].filter(Boolean); + } + }); - async.waterfall([ - function (next) { - widgets.getWidgetDataForTemplates(['global', options.template], next); - }, - function (data, next) { - var widgetsByLocation = {}; + return returnData; +}; - delete data.global.drafts; +async function renderLocation(location, data, uid, options) { + const widgetsAtLocation = (data[options.template][location] || []).concat(data.global[location] || []); - var locations = _.uniq(Object.keys(data.global).concat(Object.keys(data[options.template]))); + if (!widgetsAtLocation.length) { + return []; + } - var returnData = {}; + const renderedWidgets = await Promise.all(widgetsAtLocation.map(widget => renderWidget(widget, uid, options))); + return renderedWidgets; +} - async.each(locations, function (location, done) { - widgetsByLocation[location] = (data[options.template][location] || []).concat(data.global[location] || []); +async function renderWidget(widget, uid, options) { + if (!widget || !widget.data || (!!widget.data['hide-mobile'] && options.req.useragent.isMobile)) { + return; + } + let isVisible = true; + if (widget.data.groups.length) { + isVisible = await groups.isMemberOfAny(uid, widget.data.groups); + } + if (!isVisible) { + return; + } + let config = options.res.locals.config || {}; + if (options.res.locals.isAPI) { + config = await loadConfigAsync(options.req); + } - if (!widgetsByLocation[location].length) { - return done(null, { location: location, widgets: [] }); - } + const userLang = config.userLang || meta.config.defaultLang || 'en-GB'; + const templateData = _.assign({ }, options.templateData, { config: config }); + const data = await plugins.fireHook('filter:widget.render:' + widget.widget, { + uid: uid, + area: options, + templateData: templateData, + data: widget.data, + req: options.req, + res: options.res, + }); - async.map(widgetsByLocation[location], function (widget, next) { - renderWidget(widget, uid, options, next); - }, function (err, renderedWidgets) { - if (err) { - return done(err); - } - renderedWidgets = renderedWidgets.filter(Boolean); - returnData[location] = renderedWidgets.length ? renderedWidgets : undefined; - - done(); - }); - }, function (err) { - next(err, returnData); - }); - }, - ], callback); -}; + if (!data) { + return; + } + let html = data; + if (typeof html !== 'string') { + html = data.html; + } else { + winston.warn('[widgets.render] passing a string is deprecated!, filter:widget.render:' + widget.widget + '. Please set hookData.html in your plugin.'); + } -function renderWidget(widget, uid, options, callback) { - var userLang; + if (widget.data.container && widget.data.container.match('{body}')) { + html = await Benchpress.compileRender(widget.data.container, { + title: widget.data.title, + body: html, + template: data.templateData && data.templateData.template, + }); + } - if (!widget || !widget.data || (!!widget.data['hide-mobile'] && options.req.useragent.isMobile)) { - return setImmediate(callback); + if (html !== undefined) { + html = await translator.translate(html, userLang); } - async.waterfall([ - function (next) { - if (!widget.data.groups.length) { - return next(null, true); - } - groups.isMemberOfAny(uid, widget.data.groups, next); - }, - function (isVisible, next) { - if (!isVisible) { - return callback(); - } + return { html: html }; +} - if (options.res.locals.isAPI) { - apiController.loadConfig(options.req, next); - } else { - next(null, options.res.locals.config || {}); - } - }, - function (config, next) { - userLang = config.userLang || meta.config.defaultLang || 'en-GB'; - var templateData = _.assign({ }, options.templateData, { config: config }); - plugins.fireHook('filter:widget.render:' + widget.widget, { - uid: uid, - area: options, - templateData: templateData, - data: widget.data, - req: options.req, - res: options.res, - }, next); - }, - function (data, next) { - if (!data) { - return callback(); - } - var html = data; - if (typeof html !== 'string') { - html = data.html; - } else { - winston.warn('[widgets.render] passing a string is deprecated!, filter:widget.render:' + widget.widget + '. Please set hookData.html in your plugin.'); - } +widgets.getWidgetDataForTemplates = async function (templates) { + const keys = templates.map(tpl => 'widgets:' + tpl); + const data = await db.getObjects(keys); + + const returnData = {}; + + templates.forEach(function (template, index) { + returnData[template] = returnData[template] || {}; - if (widget.data.container && widget.data.container.match('{body}')) { - Benchpress.compileParse(widget.data.container, { - title: widget.data.title, - body: html, - template: data.templateData && data.templateData.template, - }, next); + const templateWidgetData = data[index] || {}; + const locations = Object.keys(templateWidgetData); + + locations.forEach(function (location) { + if (templateWidgetData && templateWidgetData[location]) { + try { + returnData[template][location] = parseWidgetData(templateWidgetData[location]); + } catch (err) { + winston.error('can not parse widget data. template: ' + template + ' location: ' + location); + returnData[template][location] = []; + } } else { - next(null, html); + returnData[template][location] = []; } - }, - function (html, next) { - translator.translate(html, userLang, function (translatedHtml) { - next(null, { html: translatedHtml }); - }); - }, - ], callback); -} + }); + }); -widgets.getWidgetDataForTemplates = function (templates, callback) { - async.waterfall([ - function (next) { - const keys = templates.map(tpl => 'widgets:' + tpl); - db.getObjects(keys, next); - }, - function (data, next) { - var returnData = {}; - - templates.forEach(function (template, index) { - returnData[template] = returnData[template] || {}; - - var templateWidgetData = data[index] || {}; - var locations = Object.keys(templateWidgetData); - - locations.forEach(function (location) { - if (templateWidgetData && templateWidgetData[location]) { - try { - returnData[template][location] = parseWidgetData(templateWidgetData[location]); - } catch (err) { - winston.error('can not parse widget data. template: ' + template + ' location: ' + location); - returnData[template][location] = []; - } - } else { - returnData[template][location] = []; - } - }); - }); - - next(null, returnData); - }, - ], callback); + return returnData; }; -widgets.getArea = function (template, location, callback) { - async.waterfall([ - function (next) { - db.getObjectField('widgets:' + template, location, next); - }, - function (result, next) { - if (!result) { - return callback(null, []); - } - try { - result = parseWidgetData(result); - } catch (err) { - return callback(err); - } - - next(null, result); - }, - ], callback); +widgets.getArea = async function (template, location) { + const result = await db.getObjectField('widgets:' + template, location); + if (!result) { + return []; + } + try { + return parseWidgetData(result); + } catch (err) { + throw err; + } }; function parseWidgetData(data) { @@ -190,84 +154,62 @@ function parseWidgetData(data) { return widgets; } -widgets.setArea = function (area, callback) { +widgets.setArea = async function (area) { if (!area.location || !area.template) { - return callback(new Error('Missing location and template data')); + throw new Error('Missing location and template data'); } - db.setObjectField('widgets:' + area.template, area.location, JSON.stringify(area.widgets), callback); + await db.setObjectField('widgets:' + area.template, area.location, JSON.stringify(area.widgets)); }; -widgets.reset = function (callback) { - var defaultAreas = [ +widgets.reset = async function () { + const defaultAreas = [ { name: 'Draft Zone', template: 'global', location: 'header' }, { name: 'Draft Zone', template: 'global', location: 'footer' }, { name: 'Draft Zone', template: 'global', location: 'sidebar' }, ]; - var drafts; - async.waterfall([ - function (next) { - async.parallel({ - areas: function (next) { - plugins.fireHook('filter:widgets.getAreas', defaultAreas, next); - }, - drafts: function (next) { - widgets.getArea('global', 'drafts', next); - }, - }, next); - }, - function (results, next) { - drafts = results.drafts || []; - - async.eachSeries(results.areas, function (area, next) { - async.waterfall([ - function (next) { - widgets.getArea(area.template, area.location, next); - }, - function (areaData, next) { - drafts = drafts.concat(areaData); - area.widgets = []; - widgets.setArea(area, next); - }, - ], next); - }, next); - }, - function (next) { - widgets.setArea({ - template: 'global', - location: 'drafts', - widgets: drafts, - }, next); - }, - ], callback); + + const [areas, drafts] = await Promise.all([ + plugins.fireHook('filter:widgets.getAreas', defaultAreas), + widgets.getArea('global', 'drafts'), + ]); + + let saveDrafts = drafts || []; + for (const area of areas) { + /* eslint-disable no-await-in-loop */ + const areaData = await widgets.getArea(area.template, area.location); + saveDrafts = saveDrafts.concat(areaData); + area.widgets = []; + await widgets.setArea(area); + } + + await widgets.setArea({ + template: 'global', + location: 'drafts', + widgets: saveDrafts, + }); }; -widgets.resetTemplate = function (template, callback) { - var toBeDrafted = []; - async.waterfall([ - function (next) { - db.getObject('widgets:' + template + '.tpl', next); - }, - function (area, next) { - for (var location in area) { - if (area.hasOwnProperty(location)) { - toBeDrafted = toBeDrafted.concat(JSON.parse(area[location])); - } - } - db.delete('widgets:' + template + '.tpl', next); - }, - function (next) { - db.getObjectField('widgets:global', 'drafts', next); - }, - function (draftWidgets, next) { - draftWidgets = JSON.parse(draftWidgets).concat(toBeDrafted); - db.setObjectField('widgets:global', 'drafts', JSON.stringify(draftWidgets), next); - }, - ], callback); +widgets.resetTemplate = async function (template) { + let toBeDrafted = []; + const area = await db.getObject('widgets:' + template + '.tpl'); + for (var location in area) { + if (area.hasOwnProperty(location)) { + toBeDrafted = toBeDrafted.concat(JSON.parse(area[location])); + } + } + await db.delete('widgets:' + template + '.tpl'); + let draftWidgets = await db.getObjectField('widgets:global', 'drafts'); + draftWidgets = JSON.parse(draftWidgets).concat(toBeDrafted); + await db.setObjectField('widgets:global', 'drafts', JSON.stringify(draftWidgets)); }; -widgets.resetTemplates = function (templates, callback) { - async.eachSeries(templates, widgets.resetTemplate, callback); +widgets.resetTemplates = async function (templates) { + async.eachSeries(templates, widgets.resetTemplate); + for (const template of templates) { + /* eslint-disable no-await-in-loop */ + await widgets.resetTemplate(template); + } }; require('../promisify')(widgets); diff --git a/test/controllers.js b/test/controllers.js index fe16b87faa..81c7b73fc2 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -886,14 +886,11 @@ describe('Controllers', function () { widgets: [ { widget: 'html', - data: [{ - widget: 'html', - data: { - html: 'test', - title: '', - container: '', - }, - }], + data: { + html: 'test', + title: '', + container: '', + }, }, ], }; @@ -920,6 +917,7 @@ describe('Controllers', function () { assert.equal(res.statusCode, 200); assert(body.widgets); assert(body.widgets.sidebar); + assert.equal(body.widgets.sidebar[0].html, 'test'); done(); }); }); diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index 5a7d9a4a66..ed2234467c 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -257,6 +257,7 @@ function enableDefaultPlugins(callback) { var defaultEnabled = [ 'nodebb-plugin-dbsearch', 'nodebb-plugin-soundpack-default', + 'nodebb-widget-essentials', ]; winston.info('[install/enableDefaultPlugins] activating default plugins', defaultEnabled);