diff --git a/package.json b/package.json index a92de249..c7133252 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "eslint": "^1.10.3", "eslint-config-airbnb": "^2.0.0", "fs-extra": "^0.26.2", + "mocha": "^2.3.4", + "should": "^8.0.1", "vulcanize": "^0.7.11" } } diff --git a/scripts/vulcanize.js b/scripts/vulcanize.js index bf53b4a3..4fc5a950 100755 --- a/scripts/vulcanize.js +++ b/scripts/vulcanize.js @@ -34,7 +34,6 @@ for (var i = 0; i < release_paths.length; ++i) { child_process.execFileSync(vulcanize, [ '--inline', - '--strip', '--config', path.join(root_path, 'scripts', 'vulcanize.config.json'), '-o', diff --git a/src/gui/html/elements/loot-plugin-card.html b/src/gui/html/elements/loot-plugin-card.html index eadc58d2..c2959880 100644 --- a/src/gui/html/elements/loot-plugin-card.html +++ b/src/gui/html/elements/loot-plugin-card.html @@ -220,7 +220,7 @@ loot-clear-metadata observe: { 'data.tags': 'onTagsChange', - 'data.computed.messages': 'onMessagesChange', + 'data.messages': 'onMessagesChange', 'data.userlist': 'onUserlistChange', 'data.isSearchResult' : 'onSearchResultChange', 'data.isMenuOpen': 'onMenuToggle' @@ -278,8 +278,8 @@ loot-clear-metadata onTagsChange: function(oldValue, newValue) { if (this.data) { - this.getElementsByClassName('tag add')[0].classList.toggle('hidden', this.data.computed.tags.added.length == 0); - this.getElementsByClassName('tag remove')[0].classList.toggle('hidden', this.data.computed.tags.removed.length == 0); + this.getElementsByClassName('tag add')[0].classList.toggle('hidden', this.data.tagStrings.added.length == 0); + this.getElementsByClassName('tag remove')[0].classList.toggle('hidden', this.data.tagStrings.removed.length == 0); } }, @@ -291,10 +291,16 @@ loot-clear-metadata messageUL.removeChild(messageUL.firstElementChild); } /* Now add new messages. */ - this.data.computed.messages.forEach(function(message){ - messageUL.appendChild(message); + var visibleMessages = filters.applyMessageFilters(this.data.messages); + visibleMessages.forEach(function(message) { + var messageLi = document.createElement('li'); + messageLi.className = message.type; + // Use the Marked library for Markdown formatting support. + messageLi.innerHTML = marked(message.content[0].str); + messageUL.appendChild(messageLi); + }); - messageUL.classList.toggle('hidden', this.data.computed.messages.length == 0); + messageUL.classList.toggle('hidden', visibleMessages.length == 0); } }, diff --git a/src/gui/html/elements/loot-plugin-editor.html b/src/gui/html/elements/loot-plugin-editor.html index fba03d6e..cbefbbb0 100644 --- a/src/gui/html/elements/loot-plugin-editor.html +++ b/src/gui/html/elements/loot-plugin-editor.html @@ -126,7 +126,7 @@ loot-editor-close

{{data.name}}

{{data.version}} - {{data.computed.crc}} + {{data.crcString}}
@@ -349,7 +349,7 @@ loot-editor-close plugin.userlist.msg = rowsData; } else if (tables[j].parentElement.id == 'tags') { rowsData.forEach(function(value, index, arr){ - arr[index] = oldData.convTagObj(value); + arr[index] = Plugin.tagFromRowData(value); }); plugin.userlist.tag = rowsData; } else if (tables[j].parentElement.id == 'dirty') { @@ -493,14 +493,14 @@ loot-editor-close if (newData.masterlist && newData.masterlist.tag) { newData.masterlist.tag.forEach(function(tag) { - var tagData = newData.convTagObj(tag); + var tagData = Plugin.tagToRowData(tag); var row = tables[j].addRow(tagData); tables[j].setReadOnly(row); }, newData); } if (tempData.userlist && tempData.userlist.tag) { tempData.userlist.tag.forEach(function(tag) { - var tagData = Plugin.prototype.convTagObj(tag); + var tagData = Plugin.tagToRowData(tag); tables[j].addRow(tagData); }, tempData); } diff --git a/src/gui/html/elements/loot-search.html b/src/gui/html/elements/loot-search.html index 719c66e9..fc4fa2a2 100644 --- a/src/gui/html/elements/loot-search.html +++ b/src/gui/html/elements/loot-search.html @@ -137,7 +137,7 @@ searchTarget is the ID of the core-list element to search the elements of. document.getElementById(host.searchTarget).data.forEach(function(plugin, index){ if (plugin.name.toLowerCase().indexOf(needle) != -1 || (!versionHidden && plugin.version.toLowerCase().indexOf(needle) != -1) - || (!crcHidden && plugin.getCrcString().toLowerCase().indexOf(needle) != -1)) { + || (!crcHidden && plugin.crcString.toLowerCase().indexOf(needle) != -1)) { host.results.push(index); plugin.isSearchResult = true; @@ -145,7 +145,7 @@ searchTarget is the ID of the core-list element to search the elements of. } if (!bashTagHidden) { - var tags = plugin.getTagStrings(); + var tags = plugin.tagStrings; if (tags.added.toLowerCase().indexOf(needle) != -1 || tags.removed.toLowerCase().indexOf(needle) != -1) { @@ -155,8 +155,9 @@ searchTarget is the ID of the core-list element to search the elements of. } } - for (var i = 0; i < plugin.computed.messages.length; ++i) { - if (plugin.computed.messages[i].textContent.toLowerCase().indexOf(needle) != -1) { + var visibleMessages = filters.applyMessageFilters(this.data.messages); + for (var i = 0; i < visibleMessages.length; ++i) { + if (visibleMessages[i].content[0].str.toLowerCase().indexOf(needle) != -1) { host.results.push(index); plugin.isSearchResult = true; return; diff --git a/src/gui/html/index.html b/src/gui/html/index.html index 65928919..ecbd6486 100644 --- a/src/gui/html/index.html +++ b/src/gui/html/index.html @@ -214,9 +214,9 @@

{{model.name}}

{{model.version}} - {{model.computed.crc}} - {{model.computed.tags.added}} - {{model.computed.tags.removed}} + {{model.crcString}} + {{model.tagStrings.added}} + {{model.tagStrings.removed}}
@@ -316,9 +316,9 @@ - + diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js index 2eab2f20..ba492c46 100644 --- a/src/gui/html/js/events.js +++ b/src/gui/html/js/events.js @@ -1,3 +1,17 @@ +'use strict'; +function onPluginMessageChange(evt) { + document.getElementById('filterTotalMessageNo').textContent = parseInt(document.getElementById('filterTotalMessageNo').textContent, 10) + evt.detail.totalDiff; + document.getElementById('totalMessageNo').textContent = parseInt(document.getElementById('totalMessageNo').textContent, 10) + evt.detail.totalDiff; + document.getElementById('totalWarningNo').textContent = parseInt(document.getElementById('totalWarningNo').textContent, 10) + evt.detail.warningDiff; + document.getElementById('totalErrorNo').textContent = parseInt(document.getElementById('totalErrorNo').textContent, 10) + evt.detail.errorDiff; +} +function onPluginIsDirtyChange(evt) { + if (evt.detail.isDirty) { + document.getElementById('dirtyPluginNo').textContent = parseInt(document.getElementById('dirtyPluginNo').textContent, 10) + 1; + } else { + document.getElementById('dirtyPluginNo').textContent = parseInt(document.getElementById('dirtyPluginNo').textContent, 10) - 1; + } +} function saveFilterState(evt) { var request = JSON.stringify({ name: 'saveFilterState', @@ -62,7 +76,7 @@ function onChangeGame(evt) { /* Parse the data sent from C++. */ try { - var gameInfo = JSON.parse(result, jsonToPlugin); + var gameInfo = JSON.parse(result, loot.Plugin.fromJson); loot.game.folder = gameInfo.folder; loot.game.masterlist = gameInfo.masterlist; loot.game.globalMessages = gameInfo.globalMessages; @@ -163,7 +177,7 @@ function onSortPlugins(evt) { } } if (!found) { - loot.game.plugins.push(new Plugin(plugin)); + loot.game.plugins.push(new loot.Plugin(plugin)); loot.game.loadOrder.push(loot.game.plugins[loot.game.plugins.length - 1]); } }); @@ -668,7 +682,7 @@ function onContentRefresh(evt) { } if (!foundPlugin) { /* A new plugin. */ - loot.game.plugins.push(new Plugin(plugin)); + loot.game.plugins.push(new loot.Plugin(plugin)); } pluginNames.push(plugin.name); }); @@ -760,4 +774,8 @@ function setupEventHandlers() { document.getElementById('cardsNav').addEventListener('click', onSidebarClick, false); document.getElementById('cardsNav').addEventListener('dblclick', onSidebarClick, false); + + /* Set up handler for plugin message and dirty info changes. */ + document.addEventListener('loot-plugin-message-change', onPluginMessageChange); + document.addEventListener('loot-plugin-isdirty-change', onPluginIsDirtyChange); } diff --git a/src/gui/html/js/filters.js b/src/gui/html/js/filters.js index 33bde7ac..febd8971 100644 --- a/src/gui/html/js/filters.js +++ b/src/gui/html/js/filters.js @@ -12,13 +12,13 @@ var filters = { } if (plugin.name.toLowerCase().indexOf(needle) != -1 - || plugin.getCrcString().toLowerCase().indexOf(needle) != -1 + || plugin.crcString.toLowerCase().indexOf(needle) != -1 || plugin.version.toLowerCase().indexOf(needle) != -1) { return true; } - var tags = plugin.getTagStrings(); + var tags = plugin.tagStrings; if (tags.added.toLowerCase().indexOf(needle) != -1 || tags.removed.toLowerCase().indexOf(needle) != -1) { @@ -202,9 +202,11 @@ function setFilteredUIData() { document.getElementById('cardsNav').data = filtered; document.getElementById('main').lastElementChild.data = filtered; - /* Also run message filters on the filtered plugins. */ filtered.forEach(function(plugin){ - plugin.computed.messages = plugin.getUIMessages(); + var element = document.getElementById(plugin.id); + if (element) { + element.onMessagesChange(); + } }); /* Now perform search again. If there is no current search, this won't diff --git a/src/gui/html/js/init.js b/src/gui/html/js/init.js index 8c011628..dc650cb0 100644 --- a/src/gui/html/js/init.js +++ b/src/gui/html/js/init.js @@ -155,7 +155,7 @@ function initVars() { }); } else { return loot.query('getGameData').then(function(result){ - var game = JSON.parse(result, jsonToPlugin); + var game = JSON.parse(result, loot.Plugin.fromJson); loot.game.folder = game.folder; loot.game.masterlist = game.masterlist; loot.game.globalMessages = game.globalMessages; diff --git a/src/gui/html/js/plugin.js b/src/gui/html/js/plugin.js index 64202b6a..94733205 100644 --- a/src/gui/html/js/plugin.js +++ b/src/gui/html/js/plugin.js @@ -22,193 +22,197 @@ . */ 'use strict'; -/* Plugin object for managing data and UI interaction. */ -function Plugin(obj) { - this.name = obj.name; - this.crc = obj.crc; - this.version = obj.version; - this.isActive = obj.isActive; - this.isEmpty = obj.isEmpty; - this.isMaster = obj.isMaster; - this.loadsArchive = obj.loadsArchive; +(function exportModule(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], factory); + } else { + // Browser globals + root.loot = root.loot || {}; + root.loot.Plugin = factory(); + } +}(this, () => { + return class Plugin { + constructor(obj) { + /* Plugin data */ + this.name = obj.name; + this.crc = obj.crc; + this.version = obj.version; + this.isActive = obj.isActive; + this.isEmpty = obj.isEmpty; + this.isMaster = obj.isMaster; + this.loadsArchive = obj.loadsArchive; - this.masterlist = obj.masterlist; - this.userlist = obj.userlist; + this.masterlist = obj.masterlist; + this.userlist = obj.userlist; - this.modPriority = obj.modPriority; - this.isGlobalPriority = obj.isGlobalPriority; - this.messages = obj.messages; - this.tags = obj.tags; - this.isDirty = obj.isDirty; + this.modPriority = obj.modPriority; + this.isGlobalPriority = obj.isGlobalPriority; + this._messages = obj.messages; + this.tags = obj.tags; + this._isDirty = obj.isDirty || false; - this.id = this.name.replace(/\s+/g, ''); - this.isMenuOpen = false; - this.isEditorOpen = false; - this.isConflictFilterChecked = false; - this.isSearchResult = false; - - /* Converts between the LOOT metadata object for tags, and their - editor row representation. */ - Plugin.prototype.convTagObj = function(tag) { - var newTag = { - condition: tag.condition - }; - if (tag.type) { - /* Input is row data. */ - if (tag.type == 'remove') { - newTag.name = '-' + tag.name; - } else { - newTag.name = tag.name; - } - } else { - /* Input is metadata object. */ - if (tag.name[0] == '-') { - newTag.type = 'remove'; - newTag.name = tag.name.substr(1); - } else { - newTag.type = 'add'; - newTag.name = tag.name; - } - } - return newTag; + /* UI state variables */ + this.id = this.name.replace(/\s+/g, ''); + this.isMenuOpen = false; + this.isEditorOpen = false; + this.isConflictFilterChecked = false; + this.isSearchResult = false; } - Plugin.prototype.getTagStrings = function() { - var tagsAdded = []; - var tagsRemoved = []; - - if (this.tags) { - for (var i = 0; i < this.tags.length; ++i) { - if (this.tags[i].name[0] == '-') { - tagsRemoved.push(this.tags[i].name.substr(1)); - } else { - tagsAdded.push(this.tags[i].name); - } - } - } - /* Now make sure that the same tag doesn't appear in both arrays. - Prefer the removed list. */ - for (var i = 0; i < tagsAdded.length; ++i) { - for (var j = 0; j < tagsRemoved.length; ++j) { - if (tagsRemoved[j].toLowerCase() == tagsAdded[i].toLowerCase()) { - /* Remove tag from the tagsAdded array. */ - tagsAdded.splice(i, 1); - --i; - } - } - } - - return { - added: tagsAdded.join(', '), - removed: tagsRemoved.join(', ') - }; + static fromJson(key, value) { + if (value !== null && value.__type === 'Plugin') { + return new Plugin(value); + } + return value; } - Plugin.prototype.getPriorityString = function() { - if (this.modPriority != 0) { - return this.modPriority.toString(); - } else { - return ''; - } + static tagFromRowData(rowData) { + if (rowData.condition === undefined || rowData.name === undefined || rowData.type === undefined) { + throw new TypeError('Row data members are undefined'); + } + const tag = { + condition: rowData.condition, + name: '', + }; + + if (rowData.type === 'remove') { + tag.name = '-'; + } + tag.name += rowData.name; + + return tag; } - Plugin.prototype.getCrcString = function() { - if (this.crc == 0) { - return ''; - } else { - /* Pad CRC string to 8 characters. */ - return ('00000000' + this.crc.toString(16).toUpperCase()).slice(-8); - } + static tagToRowData(tag) { + const rowData = { + condition: tag.condition, + }; + + if (tag.name[0] === '-') { + rowData.type = 'remove'; + rowData.name = tag.name.substr(1); + } else { + rowData.type = 'add'; + rowData.name = tag.name; + } + + return rowData; } - Plugin.prototype.getUIMessages = function() { - var uiMessages = []; - /* Now add the new messages. */ - if (this.messages && this.messages.length != 0) { - filters.applyMessageFilters(this.messages).forEach(function(message) { - var messageLi = document.createElement('li'); - messageLi.className = message.type; - // Use the Marked library for Markdown formatting support. - messageLi.innerHTML = marked(message.content[0].str); - uiMessages.push(messageLi); + get tagStrings() { + const tagsAdded = []; + const tagsRemoved = []; - }); + if (this.tags) { + for (let i = 0; i < this.tags.length; ++i) { + if (this.tags[i].name[0] === '-') { + tagsRemoved.push(this.tags[i].name.substr(1)); + } else { + tagsAdded.push(this.tags[i].name); + } } + } + /* Now make sure that the same tag doesn't appear in both arrays. + Prefer the removed list. */ + for (let i = 0; i < tagsAdded.length; ++i) { + for (let j = 0; j < tagsRemoved.length; ++j) { + if (tagsRemoved[j].toLowerCase() === tagsAdded[i].toLowerCase()) { + /* Remove tag from the tagsAdded array. */ + tagsAdded.splice(i, 1); + --i; + } + } + } - return uiMessages; + return { + added: tagsAdded.join(', '), + removed: tagsRemoved.join(', '), + }; } - Plugin.prototype.observer = function(changes) { - changes.forEach(function(change) { - if (change.name == 'tags') { - change.object.computed.tags = change.object.getTagStrings(); - } else if (change.name == 'modPriority') { - change.object.computed.priority = change.object.getPriorityString(); - } else if (change.name == 'crc') { - change.object.computed.crc = change.object.getCrcString(); - } else if (change.name == 'messages') { - /* Update computed list items. */ - change.object.computed.messages = change.object.getUIMessages(); + get priorityString() { + if (this.modPriority === undefined || this.modPriority === 0) { + return ''; + } - /* Update the message counts. */ - var oldTotal = 0; - var newTotal = 0; - var oldWarns = 0; - var newWarns = 0; - var oldErrs = 0; - var newErrs = 0; + return this.modPriority.toString(); + } - if (change.oldValue) { - oldTotal = change.oldValue.length; + get crcString() { + if (this.crc === undefined || this.crc === 0) { + return ''; + } - change.oldValue.forEach(function(message){ - if (message.type == 'warn') { - ++oldWarns; - } else if (message.type == 'error') { - ++oldErrs; - } - }); - } - if (change.object[change.name]) { - newTotal = change.object[change.name].length; + /* Pad CRC string to 8 characters. */ + return ('00000000' + this.crc.toString(16).toUpperCase()).slice(-8); + } - change.object[change.name].forEach(function(message){ - if (message.type == 'warn') { - ++newWarns; - } else if (message.type == 'error') { - ++newErrs; - } - }); - } + get messages() { + return this._messages; + } - document.getElementById('filterTotalMessageNo').textContent = parseInt(document.getElementById('filterTotalMessageNo').textContent, 10) + newTotal - oldTotal; - document.getElementById('totalMessageNo').textContent = parseInt(document.getElementById('totalMessageNo').textContent, 10) + newTotal - oldTotal; - document.getElementById('totalWarningNo').textContent = parseInt(document.getElementById('totalWarningNo').textContent, 10) + newWarns - oldWarns; - document.getElementById('totalErrorNo').textContent = parseInt(document.getElementById('totalErrorNo').textContent, 10) + newErrs - oldErrs; - } else if (change.name == 'isDirty') { - /* Update dirty counts. */ - if (change.object[change.name]) { - document.getElementById('dirtyPluginNo').textContent = parseInt(document.getElementById('dirtyPluginNo').textContent, 10) + 1; - } else { - document.getElementById('dirtyPluginNo').textContent = parseInt(document.getElementById('dirtyPluginNo').textContent, 10) - 1; - } - } + set messages(messages) { + /* Update the message counts. */ + let oldTotal = 0; + let newTotal = 0; + let oldWarns = 0; + let newWarns = 0; + let oldErrs = 0; + let newErrs = 0; + + if (this._messages) { + oldTotal = this._messages.length; + + this._messages.forEach((message) => { + if (message.type === 'warn') { + ++oldWarns; + } else if (message.type === 'error') { + ++oldErrs; + } }); + } + + if (messages) { + newTotal = messages.length; + + messages.forEach((message) => { + if (message.type === 'warn') { + ++newWarns; + } else if (message.type === 'error') { + ++newErrs; + } + }); + } + + if (newTotal !== oldTotal || newWarns !== oldWarns || newErrs !== oldErrs) { + document.dispatchEvent(new CustomEvent('loot-plugin-message-change', { + detail: { + totalDiff: newTotal - oldTotal, + warningDiff: newWarns - oldWarns, + errorDiff: newErrs - oldErrs, + }, + })); + } + + this._messages = messages; } - this.computed = { - tags: this.getTagStrings(), - priority: this.getPriorityString(), - crc: this.getCrcString(), - messages: this.getUIMessages(), - }; - Object.observe(this, this.observer); -} - -function jsonToPlugin(key, value) { - if (value !== null && value.__type === 'Plugin') { - var p = new Plugin(value); - return p; + get isDirty() { + return this._isDirty; } - return value; -} + + set isDirty(dirty) { + /* Update dirty counts. */ + if (dirty !== this._isDirty) { + document.dispatchEvent(new CustomEvent('loot-plugin-isdirty-change', { + detail: { + isDirty: dirty, + }, + })); + } + + this._isDirty = dirty; + } + }; +})); diff --git a/src/tests/gui/html/js/test.html b/src/tests/gui/html/js/test.html new file mode 100644 index 00000000..a5d71d3c --- /dev/null +++ b/src/tests/gui/html/js/test.html @@ -0,0 +1,18 @@ + +Mocha Tests + + +
+ + + + + + + + + + diff --git a/src/tests/gui/html/js/test_plugin.js b/src/tests/gui/html/js/test_plugin.js new file mode 100644 index 00000000..78bb99a7 --- /dev/null +++ b/src/tests/gui/html/js/test_plugin.js @@ -0,0 +1,365 @@ +'use strict'; + +describe('Plugin', () => { + describe('#Plugin()', () => { + it('should throw if nothing is passed', () => { + (() => { new loot.Plugin(); }).should.throw(); + }); + + it('should throw if an object with no name key is passed', () => { + (() => { new loot.Plugin({}); }).should.throw(); + }); + + it('should not throw if some members are undefined', () => { + (() => { new loot.Plugin({ name: 'test' }); }).should.not.throw(); + }); + }); + + describe('#fromJson()', () => { + it('should return the value object if the JSON is not of the Plugin type', () => { + const testInputObj = { + name: 'test', + crc: 0xDEADBEEF, + }; + const testInputJson = JSON.stringify(testInputObj); + + JSON.parse(testInputJson, loot.Plugin.fromJson).should.deepEqual(testInputObj); + }); + + it('should return a Plugin object if the JSON is of the Plugin type', () => { + const testInputObj = { + name: 'test', + crc: 0xDEADBEEF, + __type: 'Plugin', + }; + const testInputJson = JSON.stringify(testInputObj); + + JSON.parse(testInputJson, loot.Plugin.fromJson).should.be.instanceof(loot.Plugin); + }); + }); + + describe('#tagFromRowData()', () => { + it('should throw if passed nothing', () => { + (() => { loot.Plugin.tagFromRowData(); }).should.throw(); + }); + + it('should return an empty object if passed nothing', () => { + (() => { loot.Plugin.tagFromRowData({}); }).should.throw(); + }); + + it('should return a raw metadata object if passed a row data object that removes a tag', () => { + loot.Plugin.tagFromRowData({ + condition: 'foo', + type: 'remove', + name: 'bar', + }).should.deepEqual({ + condition: 'foo', + name: '-bar', + }); + }); + + it('should return a raw metadata object if passed a row data object that adds a tag', () => { + loot.Plugin.tagFromRowData({ + condition: 'foo', + type: 'add', + name: 'bar', + }).should.deepEqual({ + condition: 'foo', + name: 'bar', + }); + }); + }); + + describe('#tagToRowData()', () => { + it('should throw if passed nothing', () => { + (() => { loot.Plugin.tagToRowData(); }).should.throw(); + }); + + it('should return an empty object if passed nothing', () => { + (() => { loot.Plugin.tagToRowData({}); }).should.throw(); + }); + + it('should return a row data object if passed a raw metadata object that removes a tag', () => { + loot.Plugin.tagToRowData({ + condition: 'foo', + name: '-bar', + }).should.deepEqual({ + condition: 'foo', + type: 'remove', + name: 'bar', + }); + }); + + it('should return a row data object if passed a raw metadata object that adds a tag', () => { + loot.Plugin.tagToRowData({ + condition: 'foo', + name: 'bar', + }).should.deepEqual({ + condition: 'foo', + type: 'add', + name: 'bar', + }); + }); + }); + + describe('#tagStrings', () => { + it('should return an object containing empty strings if no tags are set', () => { + const plugin = new loot.Plugin({ name: 'test' }); + + plugin.tagStrings.should.deepEqual({ + added: '', + removed: '', + }); + }); + + it('should return an object containing strings of comma-separated tag names if tags are set', () => { + const plugin = new loot.Plugin({ + name: 'test', + tags: [ + { name: 'Relev' }, + { name: 'Delev' }, + { name: 'Names' }, + { name: '-C.Climate' }, + { name: '-Actor.ABCS' }, + ], + }); + + plugin.tagStrings.should.deepEqual({ + added: 'Relev, Delev, Names', + removed: 'C.Climate, Actor.ABCS', + }); + }); + + it('should output a tag in the removed string if it appears as both added and removed', () => { + const plugin = new loot.Plugin({ + name: 'test', + tags: [ + { name: 'Relev' }, + { name: '-Relev' }, + ], + }); + + plugin.tagStrings.should.deepEqual({ + added: '', + removed: 'Relev', + }); + }); + }); + + describe('#priorityString', () => { + it('should return an empty string if priority is undefined', () => { + const plugin = new loot.Plugin({ name: 'test' }); + + plugin.priorityString.should.equal(''); + }); + + it('should return an empty string if priority is zero', () => { + const plugin = new loot.Plugin({ + name: 'test', + modPriority: 0, + }); + + plugin.priorityString.should.equal(''); + }); + + it('should return priority valueAs string if non zero', () => { + const plugin = new loot.Plugin({ + name: 'test', + modPriority: -50, + }); + + plugin.priorityString.should.equal('-50'); + }); + }); + + describe('#crcString', () => { + it('should return an empty string if crc is undefined', () => { + const plugin = new loot.Plugin({ name: 'test' }); + + plugin.crcString.should.equal(''); + }); + + it('should return an empty string if crc is zero', () => { + const plugin = new loot.Plugin({ + name: 'test', + crc: 0, + }); + + plugin.crcString.should.equal(''); + }); + + it('should return crc value as string if non zero', () => { + const plugin = new loot.Plugin({ + name: 'test', + crc: 0xDEADBEEF, + }); + + plugin.crcString.should.equal('DEADBEEF'); + }); + + it('should pad crc value to eight digits', () => { + const plugin = new loot.Plugin({ + name: 'test', + crc: 0xBEEF, + }); + + plugin.crcString.should.equal('0000BEEF'); + }); + }); + + describe('#messages', () => { + let handleEvent; + + afterEach(() => { + document.removeEventListener('loot-plugin-message-change', handleEvent); + }); + + it('getting messages if they are undefined should return undefined', () => { + const plugin = new loot.Plugin({ name: 'test' }); + + plugin.should.not.have.ownProperty('messages'); + }); + + it('getting messages if the array is empty should return an empty array', () => { + const plugin = new loot.Plugin({ + name: 'test', + messages: [], + }); + + plugin.messages.should.be.Array(); + plugin.messages.should.be.empty(); + }); + + it('getting messages should return any that are set', () => { + const messages = [{ + type: 'say', + content: 'test message', + }]; + const plugin = new loot.Plugin({ + name: 'test', + messages: messages, + }); + + plugin.messages.should.be.deepEqual(messages); + }); + + it('setting messages should store any set', () => { + const plugin = new loot.Plugin({ + name: 'test', + messages: [], + }); + const messages = [{ + type: 'say', + content: 'test message', + }]; + + plugin.messages = messages; + + plugin.messages.should.be.deepEqual(messages); + }); + + it('setting messages should not fire an event if no message counts were changed', (done) => { + const plugin = new loot.Plugin({ + name: 'test', + messages: [{ + type: 'say', + content: 'test message', + }], + }); + const messages = [{ + type: 'say', + content: 'another test message', + }]; + + handleEvent = () => { + done(new Error('Should not have fired an event')); + }; + + document.addEventListener('loot-plugin-message-change', handleEvent); + + plugin.messages = messages; + + setTimeout(done, 100); + }); + + it('setting messages should fire an event if message counts were changed', (done) => { + const plugin = new loot.Plugin({ + name: 'test', + messages: [], + }); + const messages = [{ + type: 'error', + content: 'test message', + }]; + + handleEvent = (evt) => { + evt.detail.totalDiff.should.equal(1); + evt.detail.warningDiff.should.equal(0); + evt.detail.errorDiff.should.equal(1); + done(); + }; + + document.addEventListener('loot-plugin-message-change', handleEvent); + + plugin.messages = messages; + }); + }); + + describe('#isDirty', () => { + let handleEvent; + + afterEach(() => { + document.removeEventListener('loot-plugin-isdirty-change', handleEvent); + }); + + it('getting value should return false if isDirty has not been set in the constructor', () => { + const plugin = new loot.Plugin({ name: 'test' }); + + plugin.isDirty.should.be.false(); + }); + + it('getting value should return true if isDirty is set to true in the constructor', () => { + const plugin = new loot.Plugin({ + name: 'test', + isDirty: true, + }); + + plugin.isDirty.should.be.true(); + }); + + it('setting value should store set value', () => { + const plugin = new loot.Plugin({ name: 'test' }); + + plugin.isDirty = true; + + plugin.isDirty.should.be.true(); + }); + + it('setting value to the current value should not fire an event', (done) => { + const plugin = new loot.Plugin({ name: 'test' }); + + handleEvent = () => { + done(new Error('Should not have fired an event')); + }; + + document.addEventListener('loot-plugin-isdirty-change', handleEvent); + + plugin.isDirty = plugin.isDirty; + + setTimeout(done, 100); + }); + + it('setting value not equal to the current value should fire an event', (done) => { + const plugin = new loot.Plugin({ name: 'test' }); + + handleEvent = (evt) => { + evt.detail.isDirty.should.be.true(); + done(); + }; + + document.addEventListener('loot-plugin-isdirty-change', handleEvent); + + plugin.isDirty = !plugin.isDirty; + }); + }); +});