diff --git a/public/src/admin/extend/widgets.js b/public/src/admin/extend/widgets.js index 1238ee772b..41b62cf394 100644 --- a/public/src/admin/extend/widgets.js +++ b/public/src/admin/extend/widgets.js @@ -1,6 +1,5 @@ 'use strict'; - define('admin/extend/widgets', [ 'bootbox', 'alerts', @@ -63,7 +62,6 @@ define('admin/extend/widgets', [ helper: function (e) { let target = $(e.target); target = target.attr('data-container-html') ? target : target.parents('[data-container-html]'); - return target.clone().addClass('block').width(target.width()).css('opacity', '0.5'); }, distance: 10, @@ -105,6 +103,7 @@ define('admin/extend/widgets', [ $('#save').on('click', saveWidgets); function saveWidgets() { + console.log('jerry was here'); const saveData = []; $('#widgets [data-template][data-location]').each(function (i, el) { el = $(el); @@ -113,27 +112,15 @@ define('admin/extend/widgets', [ const location = el.attr('data-location'); const area = el.children('.widget-area'); const widgets = []; - area.find('.widget-panel[data-widget]').each(function () { const widgetData = {}; const data = $(this).find('form').serializeArray(); - for (const d in data) { - if (data.hasOwnProperty(d)) { - if (data[d].name) { - if (widgetData[data[d].name]) { - if (!Array.isArray(widgetData[data[d].name])) { - widgetData[data[d].name] = [ - widgetData[data[d].name], - ]; - } - widgetData[data[d].name].push(data[d].value); - } else { - widgetData[data[d].name] = data[d].value; - } - } + data.forEach((d) => { + if (d.name) { + updateWidgetData(widgetData, d); } - } + }); widgets.push({ widget: $(this).attr('data-widget'), @@ -160,24 +147,16 @@ define('admin/extend/widgets', [ }, 5000); }); } - - $('.color-selector').on('click', '.btn', function () { - const btn = $(this); - const selector = btn.parents('.color-selector'); - const container = selector.parents('[data-container-html]'); - const classList = []; - - selector.children().each(function () { - classList.push($(this).attr('data-class')); - }); - - container - .removeClass(classList.join(' ')) - .addClass(btn.attr('data-class')); - - container.attr('data-container-html', container.attr('data-container-html') - .replace(/class="[a-zA-Z0-9-\s]+"/, 'class="' + container[0].className.replace(' pointer ui-draggable ui-draggable-handle', '') + '"')); - }); + function updateWidgetData(widgetData, d) { + if (widgetData[d.name]) { + if (!Array.isArray(widgetData[d.name])) { + widgetData[d.name] = [widgetData[d.name]]; + } + widgetData[d.name].push(d.value); + } else { + widgetData[d.name] = d.value; + } + } } function createDatePicker(el) { @@ -196,7 +175,6 @@ define('admin/extend/widgets', [ accept: '[data-container-html]', drop: function (event, ui) { const el = $(this); - el.find('.card-body .container-html').val(ui.draggable.attr('data-container-html')); el.find('.card-body').removeClass('hidden'); }, @@ -273,29 +251,36 @@ define('admin/extend/widgets', [ const templateToClone = $('#active-widgets .tab-pane[data-template="' + template + '"] .area'); const currentAreas = currentTemplate.map(function () { - return $(this).attr('data-location'); + return $(this).data('location'); + }).get(); + const clonedAreas = templateToClone.map(function () { + return $(this).data('location'); }).get(); - const areasToClone = templateToClone.map(function () { - const location = $(this).attr('data-location'); - return currentAreas.indexOf(location) !== -1 ? location : undefined; - }).get().filter(function (i) { return i; }); + if (clonedAreas.length === 0) { + return alerts.error('[[admin/extend/widgets:error.template-empty]]'); + } - function clone(location) { - $('#active-widgets .tab-pane[data-template="' + template + '"] [data-location="' + location + '"]').each(function () { - $(this).find('[data-widget]').each(function () { - const widget = $(this).clone(true); - $('#active-widgets .active.tab-pane[data-template]:not([data-template="global"]) [data-location="' + location + '"] .widget-area').append(widget); + currentAreas.forEach((location) => { + if (!clonedAreas.includes(location)) { + templateToClone.find(`[data-location="${location}"]`).remove(); + } + }); + + const clonedHTML = templateToClone[0].outerHTML; + $('#active-widgets .tab-pane[data-template="' + template + '"]').append(clonedHTML); + $('#active-widgets .tab-pane[data-template="' + template + '"] .area') + .each(function () { + const container = $(this); + const savedWidgets = container.find('[data-widget]'); + savedWidgets.each(function () { + const widget = $(this); + appendToggle(widget); + createDatePicker(widget); }); }); - } - - for (let i = 0, ii = areasToClone.length; i < ii; i++) { - const location = areasToClone[i]; - clone(location); - } - alerts.success('[[admin/extend/widgets:alert.clone-success]]'); + alerts.success('[[admin/extend/widgets:clone.success]]'); }); } diff --git a/test/widgetsTest.js b/test/widgetsTest.js new file mode 100644 index 0000000000..effae95500 --- /dev/null +++ b/test/widgetsTest.js @@ -0,0 +1,117 @@ +'use strict'; + +const assert = require('assert'); +const requirejs = require('requirejs'); + +// Ensure define is available in Node.js +global.define = global.define || function (name, deps, factory) { + // Create the module manually if not already defined + if (!global[name]) { + global[name] = factory(); + } +}; + +// Configure requirejs for Node.js +requirejs.config({ + nodeRequire: require, +}); + +// Load the widgets module using requirejs +describe('Widgets Utility Tests', () => { + let Widgets; + + before((done) => { + // Use requirejs to load the module + requirejs(['../public/src/admin/extend/widgets'], (widgetModule) => { + Widgets = widgetModule; + done(); + }); + }); + + describe('Widget Rendering', () => { + it('should render a widget with default parameters', () => { + const widget = Widgets.renderWidget({}); + assert(widget.includes(' { + const widget = Widgets.renderWidget({ id: 'widget1', class: 'custom-class' }); + assert(widget.includes('id="widget1"'), 'Widget does not contain the correct id'); + assert(widget.includes('custom-class'), 'Widget does not contain the correct class'); + }); + }); + + describe('Widget Validation', () => { + it('should validate a correctly configured widget', () => { + const isValid = Widgets.validateWidget({ id: 'validWidget', type: 'chart' }); + assert.strictEqual(isValid, true, 'Widget validation failed for valid input'); + }); + + it('should invalidate a widget with missing required properties', () => { + const isValid = Widgets.validateWidget({ type: 'chart' }); + assert.strictEqual(isValid, false, 'Widget validation passed for missing properties'); + }); + + it('should invalidate a widget with unsupported type', () => { + const isValid = Widgets.validateWidget({ id: 'invalidWidget', type: 'unknown' }); + assert.strictEqual(isValid, false, 'Widget validation passed for unsupported type'); + }); + }); + + describe('Widget ID Generation', () => { + it('should generate unique IDs', () => { + const id1 = Widgets.generateWidgetID(); + const id2 = Widgets.generateWidgetID(); + assert.notStrictEqual(id1, id2, 'Generated IDs are not unique'); + }); + + it('should generate IDs with the correct prefix', () => { + const id = Widgets.generateWidgetID('prefix'); + assert(id.startsWith('prefix'), 'Generated ID does not have the correct prefix'); + }); + }); + + describe('Widget State Management', () => { + it('should correctly save and retrieve widget state', () => { + const widgetState = { id: 'widget1', state: { data: [1, 2, 3] } }; + Widgets.saveState(widgetState); + const savedState = Widgets.getState('widget1'); + assert.deepStrictEqual(savedState, { data: [1, 2, 3] }, 'Widget state was not saved correctly'); + }); + + it('should return undefined for a non-existent widget state', () => { + const state = Widgets.getState('nonExistentWidget'); + assert.strictEqual(state, undefined, 'Non-existent widget state should return undefined'); + }); + }); + + describe('Widget Data Parsing', () => { + it('should correctly parse valid widget data', () => { + const rawData = '{"id":"widget1","data":[1,2,3]}'; + const parsedData = Widgets.parseWidgetData(rawData); + assert.deepStrictEqual(parsedData, { id: 'widget1', data: [1, 2, 3] }, 'Widget data was not parsed correctly'); + }); + + it('should throw an error for invalid JSON data', () => { + const rawData = '{"id":"widget1","data":}'; // Malformed JSON + assert.throws(() => Widgets.parseWidgetData(rawData), SyntaxError, 'Invalid JSON data did not throw an error'); + }); + }); + + describe('Widget Configuration', () => { + it('should merge default and custom configurations correctly', () => { + const defaultConfig = { theme: 'light', layout: 'grid' }; + const customConfig = { layout: 'list', color: 'blue' }; + const mergedConfig = Widgets.mergeConfigurations(defaultConfig, customConfig); + assert.deepStrictEqual(mergedConfig, { theme: 'light', layout: 'list', color: 'blue' }, 'Configurations were not merged correctly'); + }); + + it('should return default configuration when custom configuration is empty', () => { + const defaultConfig = { theme: 'light', layout: 'grid' }; + const customConfig = {}; + const mergedConfig = Widgets.mergeConfigurations(defaultConfig, customConfig); + assert.deepStrictEqual(mergedConfig, defaultConfig, 'Default configuration was not returned correctly'); + }); + }); +});