mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Add support for 'clean' metadata key
Display an icon with tooltip on the cards of cleaned plugins.
This commit is contained in:
@@ -199,7 +199,7 @@ var {
|
||||
<li><a href="#structs-file">File Data Structure</a>
|
||||
<li><a href="#structs-message">Message Data Structure</a>
|
||||
<li><a href="#structs-location">Location Data Structure</a>
|
||||
<li><a href="#structs-dirty">Dirty Info Data Structure</a>
|
||||
<li><a href="#structs-dirty">Cleaning Data Structure</a>
|
||||
<li><a href="#structs-plugin">Plugin Data Structure</a>
|
||||
</ol>
|
||||
<li><a href="#cond">Condition Strings</a>
|
||||
@@ -335,7 +335,7 @@ display: 'OBSE v18+'
|
||||
<tr><td><code>condition</code><td>string<td>✗<td>A condition string that is evaluated to determine whether the message should be displayed: if it evaluates to true, the message is displayed, otherwise it is not. See <a href="#cond">Condition Strings</a> for details.
|
||||
<tr><td><code>subs</code><td>string list<td>✗<td>A list of strings to be substituted into the message content string. The content string must use numbered specifiers (<code>%1%</code>, <code>%2%</code>, etc.), where the numbers correspond to the position of the substitution string in this list to use, to denote where these strings are to be substituted.
|
||||
</table>
|
||||
<p>LOOT supports formatting of messages using <a href="https://help.github.com/articles/github-flavored-markdown">GitHub Flavored Markdown</a>. Support is provided by the <a href="https://github.com/chjj/marked">Marked</a> library (v0.3). Strings that get substituted into messages, such as file display names and dirty info utility strings, also support the same formatting options.
|
||||
<p>LOOT supports formatting of messages using <a href="https://help.github.com/articles/github-flavored-markdown">GitHub Flavored Markdown</a>. Support is provided by the <a href="https://github.com/chjj/marked">Marked</a> library (v0.3). Strings that get substituted into messages, such as file display names and cleaning data utility strings, also support the same formatting options.
|
||||
<p>LOOT handles messages and languages as follows:
|
||||
<ol>
|
||||
<li>If a message's <code>content</code> value is a string, the message will use the string as its content if displayed.
|
||||
@@ -427,27 +427,26 @@ Note: A newer version of this plugin <a href="http://www.example.com">is availab
|
||||
name: 'Unofficial Skyrim Patch on Steam Workshop'
|
||||
</code></pre>
|
||||
|
||||
<h3 id="structs-dirty">Dirty Info Data Structure</h3>
|
||||
<p>This structure holds information on which versions of a plugin are dirty, and how many identical-to-master records, deleted records and deleted navmeshes (if applicable) it contains. Dirty info is given as a key-value map.
|
||||
<h3 id="structs-dirty">Cleaning Data Structure</h3>
|
||||
<p>This structure holds information on which versions of a plugin are dirty or clean, and if dirty, how many identical-to-master records, deleted records and deleted navmeshes (if applicable) it contains. Cleaning data is given as a key-value map.
|
||||
<table>
|
||||
<thead><tr><th>Key Name<th>Value Type<th>Required<th>Notes
|
||||
<tbody>
|
||||
<tr><td><code>crc</code><td>hexadecimal integer<td>✓<td>The CRC-32 checksum of the dirty plugin, before cleaning. LOOT displays the CRCs of installed plugins in its report. The 8-character CRC should be preceded by <code>0x</code> so that it is correctly interpreted.
|
||||
<tr><td><code>util</code><td>string<td>✓<td>The utility that should be used to clean the plugin.
|
||||
<tr><td><code>itm</code><td>integer<td>✗<td>The number of identical-to-master records reported for the dirty plugin. If the number is unknown, this field should not be supplied. If the number is known and zero, this field should be supplied.
|
||||
<tr><td><code>udr</code><td>integer<td>✗<td>The number of undeleted records reported for the dirty plugin. If the number is unknown, this field should not be supplied. If the number is known and zero, this field should be supplied.
|
||||
<tr><td><code>nav</code><td>integer<td>✗<td>The number of deleted navmeshes reported for the dirty plugin. If the number is unknown, this field should not be supplied. If the number is known and zero, this field should be supplied.
|
||||
<tr><td><code>crc</code><td>hexadecimal integer<td>✓<td>The CRC-32 checksum of the plugin. If the plugin is dirty, this needs to be the CRC of the plugin before before cleaning. LOOT displays the CRCs of installed plugins in its report. The 8-character CRC should be preceded by <code>0x</code> so that it is correctly interpreted.
|
||||
<tr><td><code>util</code><td>string<td>✓<td>The utility that should be or that was used to clean the plugin.
|
||||
<tr><td><code>itm</code><td>integer<td>✗<td>The number of identical-to-master records reported for the dirty plugin. If the number is unknown or zero, this field should not be supplied.
|
||||
<tr><td><code>udr</code><td>integer<td>✗<td>The number of undeleted records reported for the dirty plugin. If the number is unknown or zero, this field should not be supplied.
|
||||
<tr><td><code>nav</code><td>integer<td>✗<td>The number of deleted navmeshes reported for the dirty plugin. If the number is unknown or zero, this field should not be supplied.
|
||||
</table>
|
||||
|
||||
<h4>Equality</h4>
|
||||
<p>Two location data structures are equal if the values of their <code>crc</code> keys are identical.
|
||||
<p>Two cleaning data structures are equal if the values of their <code>crc</code> keys are identical.
|
||||
|
||||
<h4>Examples</h4>
|
||||
<pre><code>crc: 0x3DF62ABC
|
||||
util: '[TES5Edit](http://www.creationkit.com/TES5Edit_Cleaning_Guide_-_TES5Edit)'
|
||||
itm: 4
|
||||
udr: 160
|
||||
nav: 0
|
||||
</code></pre>
|
||||
|
||||
|
||||
@@ -471,7 +470,8 @@ nav: 0
|
||||
<tr><td><code>msg</code><td>message list<td>✗<td>The messages attached to this plugin. The messages will be displayed in the order that they are listed.
|
||||
<tr><td><code>tag</code><td>tag set<td>✗<td>An unordered set of Bash Tags suggested for this plugin. If a Bash Tag is suggested for both addition and removal, the latter will override the former when the list is evaluated.
|
||||
<tr><td><code>url</code><td>location set<td>✗<td>An unordered set of locations for this plugin. If the same version can be found at multiple locations, only one location should be recorded. This metadata is not currently used by LOOT.
|
||||
<tr><td><code>dirty</code><td>dirty info set<td>✗<td>An unordered set of dirty info structures for this plugin. Plugin entries with regular expression filenames <strong>must not</strong> contain dirty info.
|
||||
<tr><td><code>dirty</code><td>cleaning data set<td>✗<td>An unordered set of cleaning data structures for this plugin, identifying dirty plugins. Plugin entries with regular expression filenames <strong>must not</strong> contain cleaning data.
|
||||
<tr><td><code>clean</code><td>cleaning data set<td>✗<td>An unordered set of cleaning data structures for this plugin, identifying clean plugins. Plugin entries with regular expression filenames <strong>must not</strong> contain cleaning data. The <code>itm</code>, <code>udr</code> and <code>nav</code> fields are unused in this context, as they're assumed to be zero.
|
||||
</table>
|
||||
|
||||
<h4>Equality</h4>
|
||||
@@ -495,7 +495,8 @@ nav: 0
|
||||
<tr><td><code>msg</code><td>Merged. If B's message list contains an item that is equal to one already present in A's message list, B's item is discarded.
|
||||
<tr><td><code>tag</code><td>Merged.If B's tag set contains an item that is equal to one already present in A's tag set, B's item is discarded.
|
||||
<tr><td><code>url</code><td>Merged. If B's location set contains an item that is equal to one already present in A's location set, B's item is discarded.
|
||||
<tr><td><code>dirty</code><td>Merged.If B's dirty info set contains an item that is equal to one already present in A's dirty info set, B's item is discarded.
|
||||
<tr><td><code>dirty</code><td>Merged.If B's dirty data set contain an item that is equal to one already present in A's dirty data set, B's item is discarded.
|
||||
<tr><td><code>clean</code><td>Merged.If B's clean data set contain an item that is equal to one already present in A's clean data set, B's item is discarded.
|
||||
</table>
|
||||
|
||||
<h4>Examples</h4>
|
||||
|
||||
+139
-130
File diff suppressed because it is too large
Load Diff
@@ -82,6 +82,7 @@ void PluginMetadata::MergeMetadata(const PluginMetadata& plugin) {
|
||||
messages_.insert(end(messages_), begin(plugin.messages_), end(plugin.messages_));
|
||||
|
||||
dirtyInfo_.insert(begin(plugin.dirtyInfo_), end(plugin.dirtyInfo_));
|
||||
cleanInfo_.insert(begin(plugin.cleanInfo_), end(plugin.cleanInfo_));
|
||||
locations_.insert(begin(plugin.locations_), end(plugin.locations_));
|
||||
|
||||
return;
|
||||
@@ -152,6 +153,14 @@ PluginMetadata PluginMetadata::DiffMetadata(const PluginMetadata& plugin) const
|
||||
inserter(dirtDiff, begin(dirtDiff)));
|
||||
p.DirtyInfo(dirtDiff);
|
||||
|
||||
set<PluginCleaningData> cleanDiff;
|
||||
set_symmetric_difference(begin(cleanInfo_),
|
||||
end(cleanInfo_),
|
||||
begin(plugin.cleanInfo_),
|
||||
end(plugin.cleanInfo_),
|
||||
inserter(cleanDiff, begin(cleanDiff)));
|
||||
p.CleanInfo(cleanDiff);
|
||||
|
||||
set<Location> locationsDiff;
|
||||
set_symmetric_difference(begin(locations_),
|
||||
end(locations_),
|
||||
@@ -222,6 +231,14 @@ PluginMetadata PluginMetadata::NewMetadata(const PluginMetadata& plugin) const {
|
||||
inserter(dirtDiff, begin(dirtDiff)));
|
||||
p.DirtyInfo(dirtDiff);
|
||||
|
||||
set<PluginCleaningData> cleanDiff;
|
||||
set_difference(begin(cleanInfo_),
|
||||
end(cleanInfo_),
|
||||
begin(plugin.cleanInfo_),
|
||||
end(plugin.cleanInfo_),
|
||||
inserter(cleanDiff, begin(cleanDiff)));
|
||||
p.CleanInfo(cleanDiff);
|
||||
|
||||
set<Location> locationsDiff;
|
||||
set_difference(begin(locations_),
|
||||
end(locations_),
|
||||
@@ -277,6 +294,10 @@ std::set<PluginCleaningData> PluginMetadata::DirtyInfo() const {
|
||||
return dirtyInfo_;
|
||||
}
|
||||
|
||||
std::set<PluginCleaningData> PluginMetadata::CleanInfo() const {
|
||||
return cleanInfo_;
|
||||
}
|
||||
|
||||
std::set<Location> PluginMetadata::Locations() const {
|
||||
return locations_;
|
||||
}
|
||||
@@ -324,6 +345,10 @@ void PluginMetadata::DirtyInfo(const std::set<PluginCleaningData>& dirtyInfo) {
|
||||
dirtyInfo_ = dirtyInfo;
|
||||
}
|
||||
|
||||
void PluginMetadata::CleanInfo(const std::set<PluginCleaningData>& info) {
|
||||
cleanInfo_ = info;
|
||||
}
|
||||
|
||||
void PluginMetadata::Locations(const std::set<Location>& locations) {
|
||||
locations_ = locations;
|
||||
}
|
||||
@@ -364,22 +389,37 @@ PluginMetadata& PluginMetadata::EvalAllConditions(Game& game, const Language::Co
|
||||
++it;
|
||||
}
|
||||
|
||||
if (IsRegexPlugin()) // Remove any dirty metadata from a regex plugin.
|
||||
if (IsRegexPlugin()) { // Remove any dirty metadata from a regex plugin.
|
||||
dirtyInfo_.clear();
|
||||
else {
|
||||
cleanInfo_.clear();
|
||||
} else {
|
||||
for (auto it = dirtyInfo_.begin(); it != dirtyInfo_.end();) {
|
||||
if (!it->EvalCondition(game, name_))
|
||||
dirtyInfo_.erase(it++);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
for (auto it = cleanInfo_.begin(); it != cleanInfo_.end();) {
|
||||
if (!it->EvalCondition(game, name_))
|
||||
cleanInfo_.erase(it++);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool PluginMetadata::HasNameOnly() const {
|
||||
return !IsPriorityExplicit() && loadAfter_.empty() && requirements_.empty() && incompatibilities_.empty() && messages_.empty() && tags_.empty() && dirtyInfo_.empty() && locations_.empty();
|
||||
return !IsPriorityExplicit()
|
||||
&& loadAfter_.empty()
|
||||
&& requirements_.empty()
|
||||
&& incompatibilities_.empty()
|
||||
&& messages_.empty()
|
||||
&& tags_.empty()
|
||||
&& dirtyInfo_.empty()
|
||||
&& cleanInfo_.empty()
|
||||
&& locations_.empty();
|
||||
}
|
||||
|
||||
bool PluginMetadata::IsRegexPlugin() const {
|
||||
@@ -457,6 +497,9 @@ Emitter& operator << (Emitter& out, const loot::PluginMetadata& rhs) {
|
||||
if (!rhs.DirtyInfo().empty())
|
||||
out << Key << "dirty" << Value << rhs.DirtyInfo();
|
||||
|
||||
if (!rhs.CleanInfo().empty())
|
||||
out << Key << "clean" << Value << rhs.CleanInfo();
|
||||
|
||||
if (!rhs.Locations().empty())
|
||||
out << Key << "url" << Value << rhs.Locations();
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ public:
|
||||
std::list<Message> Messages() const;
|
||||
std::set<Tag> Tags() const;
|
||||
std::set<PluginCleaningData> DirtyInfo() const;
|
||||
std::set<PluginCleaningData> CleanInfo() const;
|
||||
std::set<Location> Locations() const;
|
||||
|
||||
void Enabled(const bool enabled);
|
||||
@@ -88,6 +89,7 @@ public:
|
||||
void Messages(const std::list<Message>& messages);
|
||||
void Tags(const std::set<Tag>& tags);
|
||||
void DirtyInfo(const std::set<PluginCleaningData>& info);
|
||||
void CleanInfo(const std::set<PluginCleaningData>& info);
|
||||
void Locations(const std::set<Location>& locations);
|
||||
|
||||
PluginMetadata& EvalAllConditions(Game& game, const Language::Code language);
|
||||
@@ -116,6 +118,7 @@ private:
|
||||
std::set<File> requirements_;
|
||||
std::set<File> incompatibilities_;
|
||||
std::set<PluginCleaningData> dirtyInfo_;
|
||||
std::set<PluginCleaningData> cleanInfo_;
|
||||
std::set<Location> locations_;
|
||||
};
|
||||
}
|
||||
@@ -154,6 +157,8 @@ struct convert<loot::PluginMetadata> {
|
||||
node["tag"] = rhs.Tags();
|
||||
if (!rhs.DirtyInfo().empty())
|
||||
node["dirty"] = rhs.DirtyInfo();
|
||||
if (!rhs.CleanInfo().empty())
|
||||
node["clean"] = rhs.CleanInfo();
|
||||
if (!rhs.Locations().empty())
|
||||
node["url"] = rhs.Locations();
|
||||
|
||||
@@ -188,23 +193,29 @@ struct convert<loot::PluginMetadata> {
|
||||
}
|
||||
|
||||
if (node["after"])
|
||||
rhs.LoadAfter(node["after"].as< std::set<loot::File> >());
|
||||
rhs.LoadAfter(node["after"].as<std::set<loot::File>>());
|
||||
if (node["req"])
|
||||
rhs.Reqs(node["req"].as< std::set<loot::File> >());
|
||||
rhs.Reqs(node["req"].as<std::set<loot::File>>());
|
||||
if (node["inc"])
|
||||
rhs.Incs(node["inc"].as< std::set<loot::File> >());
|
||||
rhs.Incs(node["inc"].as<std::set<loot::File>>());
|
||||
if (node["msg"])
|
||||
rhs.Messages(node["msg"].as< std::list<loot::Message> >());
|
||||
rhs.Messages(node["msg"].as<std::list<loot::Message>>());
|
||||
if (node["tag"])
|
||||
rhs.Tags(node["tag"].as< std::set<loot::Tag> >());
|
||||
rhs.Tags(node["tag"].as<std::set<loot::Tag>>());
|
||||
if (node["dirty"]) {
|
||||
if (rhs.IsRegexPlugin())
|
||||
throw RepresentationException(node.Mark(), "bad conversion: 'dirty' key must not be present in a regex 'plugin metadata' object");
|
||||
else
|
||||
rhs.DirtyInfo(node["dirty"].as< std::set<loot::PluginCleaningData> >());
|
||||
rhs.DirtyInfo(node["dirty"].as<std::set<loot::PluginCleaningData>>());
|
||||
}
|
||||
if (node["clean"]) {
|
||||
if (rhs.IsRegexPlugin())
|
||||
throw RepresentationException(node.Mark(), "bad conversion: 'clean' key must not be present in a regex 'plugin metadata' object");
|
||||
else
|
||||
rhs.CleanInfo(node["clean"].as<std::set<loot::PluginCleaningData>>());
|
||||
}
|
||||
if (node["url"])
|
||||
rhs.Locations(node["url"].as< std::set<loot::Location> >());
|
||||
rhs.Locations(node["url"].as<std::set<loot::Location>>());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,18 @@
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template id="cleanInfoRow">
|
||||
<tr>
|
||||
<td style="width: 92px;"><paper-input error-message="A CRC is required." class="crc" maxlength="8" minlength="8" pattern="[0-9A-Fa-f]{8}" required auto-validate no-label-float></paper-input></td>
|
||||
<td><paper-input error-message="A utility name is required." class="util" required auto-validate no-label-float></paper-input></td>
|
||||
<td>
|
||||
<span>
|
||||
<paper-icon-button class="delete" icon="delete"></paper-icon-button>
|
||||
<paper-tooltip position="left">Delete Row</paper-tooltip>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template id="locationRow">
|
||||
<tr>
|
||||
<td><paper-input error-message="A link is required." class="link" required auto-validate no-label-float></paper-input></td>
|
||||
|
||||
@@ -110,6 +110,8 @@
|
||||
<paper-tooltip for="isEmpty">Empty Plugin</paper-tooltip>
|
||||
<iron-icon id="loadsArchive" icon="attachment"></iron-icon>
|
||||
<paper-tooltip for="loadsArchive">Loads Archive</paper-tooltip>
|
||||
<iron-icon id="isClean" icon="loot-custom-icons:droplet"></iron-icon>
|
||||
<paper-tooltip id="isCleanTooltip" for="isClean"></paper-tooltip>
|
||||
<iron-icon id="hasUserEdits" icon="account-circle"></iron-icon>
|
||||
<paper-tooltip for="hasUserEdits">Has User Metadata</paper-tooltip>
|
||||
<paper-menu-button id="menu" horizontal-align="right">
|
||||
@@ -221,6 +223,8 @@
|
||||
|
||||
this.getElementsByClassName('crc')[0].textContent = cardContent.crc;
|
||||
|
||||
this.updateIsCleanIcon();
|
||||
|
||||
if (updateBodyContent) {
|
||||
this._setTagsContent(cardContent.tags);
|
||||
this._setMessagesContent(cardContent.messages);
|
||||
@@ -233,6 +237,11 @@
|
||||
}
|
||||
},
|
||||
|
||||
updateIsCleanIcon() {
|
||||
this.$.isClean.hidden = !this.data.cleanedWith;
|
||||
this.$.isCleanTooltip.textContent = loot.l10n.translate('Verified clean by %s', this.data.cleanedWith);
|
||||
},
|
||||
|
||||
updateStyling() {
|
||||
if (this.data) {
|
||||
this.$.hasUserEdits.hidden = !this.data.hasUserEdits;
|
||||
|
||||
@@ -126,7 +126,8 @@ loot-editor-close
|
||||
<paper-tab data-for="inc">Incompatibilities</paper-tab>
|
||||
<paper-tab data-for="message">Messages</paper-tab>
|
||||
<paper-tab data-for="tags">Bash Tags</paper-tab>
|
||||
<paper-tab data-for="dirty">Dirty Info</paper-tab>
|
||||
<paper-tab data-for="dirty">Dirty Plugin Info</paper-tab>
|
||||
<paper-tab data-for="clean">Clean Plugin Info</paper-tab>
|
||||
<paper-tab data-for="url">Locations</paper-tab>
|
||||
</paper-tabs>
|
||||
</paper-toolbar>
|
||||
@@ -206,6 +207,16 @@ loot-editor-close
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="clean">
|
||||
<table is="editable-table" data-template="cleanInfoRow">
|
||||
<thead>
|
||||
<tr><th>CRC</th><th>Cleaning Utility</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Clean info rows go here. -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="url">
|
||||
<table is="editable-table" data-template="locationRow">
|
||||
<thead>
|
||||
@@ -314,6 +325,8 @@ loot-editor-close
|
||||
plugin.userlist.tag = rowsData.map(loot.Plugin.tagFromRowData);
|
||||
} else if (tables[j].parentElement.id === 'dirty') {
|
||||
plugin.userlist.dirty = rowsData.map(this._rowDataToCrc);
|
||||
} else if (tables[j].parentElement.id === 'clean') {
|
||||
plugin.userlist.clean = rowsData.map(this._rowDataToCrc);
|
||||
} else if (tables[j].parentElement.id === 'url') {
|
||||
plugin.userlist.url = rowsData;
|
||||
}
|
||||
@@ -404,6 +417,13 @@ loot-editor-close
|
||||
if (newData.userlist && newData.userlist.dirty) {
|
||||
newData.userlist.dirty.map(this._dirtyInfoToRowData).forEach(tables[j].addRow, tables[j]);
|
||||
}
|
||||
} else if (tables[j].parentElement.id === 'clean') {
|
||||
if (newData.masterlist && newData.masterlist.clean) {
|
||||
newData.masterlist.clean.map(this._dirtyInfoToRowData).forEach(tables[j].addReadOnlyRow, tables[j]);
|
||||
}
|
||||
if (newData.userlist && newData.userlist.clean) {
|
||||
newData.userlist.clean.map(this._dirtyInfoToRowData).forEach(tables[j].addRow, tables[j]);
|
||||
}
|
||||
} else {
|
||||
if (newData.masterlist && newData.masterlist[tables[j].parentElement.id]) {
|
||||
newData.masterlist[tables[j].parentElement.id].forEach(tables[j].addReadOnlyRow, tables[j]);
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
/* Set up handler for plugin data changes. */
|
||||
document.addEventListener('loot-plugin-message-change', Plugin.onMessageChange);
|
||||
document.addEventListener('loot-plugin-message-change', Plugin.onContentChange);
|
||||
document.addEventListener('loot-plugin-isdirty-change', Plugin.onIsDirtyChange);
|
||||
document.addEventListener('loot-plugin-cleaning-data-change', Plugin.onCleaningDataChange);
|
||||
document.addEventListener('loot-plugin-card-content-change', Plugin.onContentChange);
|
||||
document.addEventListener('loot-plugin-card-styling-change', Plugin.onCardStylingChange);
|
||||
document.addEventListener('loot-plugin-item-content-change', Plugin.onItemContentChange);
|
||||
|
||||
@@ -182,6 +182,7 @@
|
||||
this._tags = obj.tags || [];
|
||||
this._isDirty = obj.isDirty || false;
|
||||
this.loadOrderIndex = obj.loadOrderIndex;
|
||||
this.cleanedWith = obj.cleanedWith || '';
|
||||
|
||||
/* UI state variables */
|
||||
this.id = this.name.replace(/\s+/g, '');
|
||||
@@ -329,9 +330,26 @@
|
||||
if (dirty !== this._isDirty) {
|
||||
this._isDirty = dirty;
|
||||
|
||||
document.dispatchEvent(new CustomEvent('loot-plugin-isdirty-change', {
|
||||
document.dispatchEvent(new CustomEvent('loot-plugin-cleaning-data-change', {
|
||||
detail: {
|
||||
isDirty: dirty,
|
||||
isDirty: this.isDirty,
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
get cleanedWith() {
|
||||
return this._cleanedWith;
|
||||
}
|
||||
|
||||
set cleanedWith(cleanedWith) {
|
||||
if (cleanedWith !== this._cleanedWith) {
|
||||
this._cleanedWith = cleanedWith;
|
||||
|
||||
document.dispatchEvent(new CustomEvent('loot-plugin-cleaning-data-change', {
|
||||
detail: {
|
||||
cleanedWith: this.cleanedWith,
|
||||
pluginId: this.id,
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -437,11 +455,19 @@
|
||||
document.getElementById('totalErrorNo').textContent = parseInt(document.getElementById('totalErrorNo').textContent, 10) + evt.detail.errorDiff;
|
||||
}
|
||||
|
||||
static onIsDirtyChange(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;
|
||||
static onCleaningDataChange(evt) {
|
||||
if (evt.detail.isDirty !== undefined) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (evt.detail.cleanedWith !== undefined) {
|
||||
const card = document.getElementById(evt.detail.pluginId);
|
||||
if (card) {
|
||||
card.updateIsCleanIcon();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
pluginEditor.getElementById('tableTabs').querySelector('[data-for=inc]').textContent = l10n.translate('Incompatibilities');
|
||||
pluginEditor.getElementById('tableTabs').querySelector('[data-for=message]').textContent = l10n.translate('Messages');
|
||||
pluginEditor.getElementById('tableTabs').querySelector('[data-for=tags]').textContent = l10n.translate('Bash Tags');
|
||||
pluginEditor.getElementById('tableTabs').querySelector('[data-for=dirty]').textContent = l10n.translate('Dirty Info');
|
||||
pluginEditor.getElementById('tableTabs').querySelector('[data-for=dirty]').textContent = l10n.translate('Dirty Plugin Info');
|
||||
pluginEditor.getElementById('tableTabs').querySelector('[data-for=clean]').textContent = l10n.translate('Clean Plugin Info');
|
||||
pluginEditor.getElementById('tableTabs').querySelector('[data-for=url]').textContent = l10n.translate('Locations');
|
||||
|
||||
pluginEditor.getElementById('after').querySelector('th:first-child').textContent = l10n.translate('Filename');
|
||||
@@ -70,6 +71,9 @@
|
||||
pluginEditor.getElementById('dirty').querySelector('th:nth-child(4)').textContent = l10n.translate('Deleted Navmeshes');
|
||||
pluginEditor.getElementById('dirty').querySelector('th:nth-child(5)').textContent = l10n.translate('Cleaning Utility');
|
||||
|
||||
pluginEditor.getElementById('clean').querySelector('th:first-child').textContent = l10n.translate('CRC');
|
||||
pluginEditor.getElementById('clean').querySelector('th:nth-child(2)').textContent = l10n.translate('Cleaning Utility');
|
||||
|
||||
pluginEditor.getElementById('url').querySelector('th:first-child').textContent = l10n.translate('URL');
|
||||
pluginEditor.getElementById('url').querySelector('th:nth-child(2)').textContent = l10n.translate('Name');
|
||||
|
||||
@@ -135,6 +139,15 @@
|
||||
dirtyInfoRow.querySelector('paper-tooltip').textContent = l10n.translate('Delete Row');
|
||||
}
|
||||
|
||||
function translateCleanInfoRowTemplate(l10n) {
|
||||
/* Dirty Info row template */
|
||||
const cleanInfoRow = document.getElementById('cleanInfoRow').content;
|
||||
|
||||
cleanInfoRow.querySelector('.crc').setAttribute('error-message', l10n.translate('A CRC is required.'));
|
||||
cleanInfoRow.querySelector('.util').setAttribute('error-message', l10n.translate('A utility name is required.'));
|
||||
cleanInfoRow.querySelector('paper-tooltip').textContent = l10n.translate('Delete Row');
|
||||
}
|
||||
|
||||
function translateLocationRowTemplate(l10n) {
|
||||
/* Location row template */
|
||||
const locationRow = document.getElementById('locationRow').content;
|
||||
@@ -286,6 +299,7 @@
|
||||
translateMessageRowTemplate(l10n);
|
||||
translateTagRowTemplate(l10n);
|
||||
translateDirtyInfoRowTemplate(l10n);
|
||||
translateCleanInfoRowTemplate(l10n);
|
||||
translateLocationRowTemplate(l10n);
|
||||
translateGameRowTemplate(l10n);
|
||||
translateNewRowTemplate(l10n);
|
||||
|
||||
@@ -465,6 +465,8 @@ std::string QueryHandler::ApplyUserEdits(const YAML::Node& pluginMetadata) {
|
||||
newUserlistEntry.Tags(pluginMetadata["userlist"]["tag"].as<set<Tag>>());
|
||||
if (pluginMetadata["userlist"]["dirty"])
|
||||
newUserlistEntry.DirtyInfo(pluginMetadata["userlist"]["dirty"].as<set<PluginCleaningData>>());
|
||||
if (pluginMetadata["userlist"]["clean"])
|
||||
newUserlistEntry.CleanInfo(pluginMetadata["userlist"]["clean"].as<set<PluginCleaningData>>());
|
||||
if (pluginMetadata["userlist"]["url"])
|
||||
newUserlistEntry.Locations(pluginMetadata["userlist"]["url"].as<set<Location>>());
|
||||
|
||||
@@ -681,6 +683,7 @@ void QueryHandler::GetGameData(CefRefPtr<CefFrame> frame, CefRefPtr<Callback> ca
|
||||
pluginNode["masterlist"]["msg"] = mlistPlugin.Messages();
|
||||
pluginNode["masterlist"]["tag"] = mlistPlugin.Tags();
|
||||
pluginNode["masterlist"]["dirty"] = mlistPlugin.DirtyInfo();
|
||||
pluginNode["masterlist"]["clean"] = mlistPlugin.CleanInfo();
|
||||
pluginNode["masterlist"]["url"] = mlistPlugin.Locations();
|
||||
}
|
||||
|
||||
@@ -693,6 +696,7 @@ void QueryHandler::GetGameData(CefRefPtr<CefFrame> frame, CefRefPtr<Callback> ca
|
||||
pluginNode["userlist"]["msg"] = ulistPlugin.Messages();
|
||||
pluginNode["userlist"]["tag"] = ulistPlugin.Tags();
|
||||
pluginNode["userlist"]["dirty"] = ulistPlugin.DirtyInfo();
|
||||
pluginNode["userlist"]["clean"] = ulistPlugin.CleanInfo();
|
||||
pluginNode["userlist"]["url"] = ulistPlugin.Locations();
|
||||
// The raw priority data isn't used, but should be set
|
||||
// that LOOT knows it exists.
|
||||
@@ -780,6 +784,7 @@ void QueryHandler::UpdateMasterlist(CefRefPtr<Callback> callback) {
|
||||
pluginNode["masterlist"]["msg"] = mlistPlugin.Messages();
|
||||
pluginNode["masterlist"]["tag"] = mlistPlugin.Tags();
|
||||
pluginNode["masterlist"]["dirty"] = mlistPlugin.DirtyInfo();
|
||||
pluginNode["masterlist"]["clean"] = mlistPlugin.CleanInfo();
|
||||
pluginNode["masterlist"]["url"] = mlistPlugin.Locations();
|
||||
}
|
||||
|
||||
@@ -972,6 +977,12 @@ YAML::Node QueryHandler::GenerateDerivedMetadata(const Plugin& file, const Plugi
|
||||
pluginNode["isDirty"] = isDirty;
|
||||
pluginNode["loadOrderIndex"] = lootState_.getCurrentGame().GetActiveLoadOrderIndex(tempPlugin.Name());
|
||||
|
||||
if (!tempPlugin.CleanInfo().empty()) {
|
||||
pluginNode["cleanedWith"] = tempPlugin.CleanInfo().begin()->CleaningUtility();
|
||||
} else {
|
||||
pluginNode["cleanedWith"] = "";
|
||||
}
|
||||
|
||||
return pluginNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -266,6 +266,18 @@ TEST_P(PluginMetadataTest, mergeMetadataShouldMergeDirtyInfoData) {
|
||||
|
||||
EXPECT_EQ(std::set<PluginCleaningData>({info1, info2}), plugin1.DirtyInfo());
|
||||
}
|
||||
TEST_P(PluginMetadataTest, mergeMetadataShouldMergeCleanInfoData) {
|
||||
PluginMetadata plugin1;
|
||||
PluginMetadata plugin2;
|
||||
PluginCleaningData info1(0x5, "utility");
|
||||
PluginCleaningData info2(0xA, "utility");
|
||||
|
||||
plugin1.CleanInfo({info1});
|
||||
plugin2.CleanInfo({info1, info2});
|
||||
plugin1.MergeMetadata(plugin2);
|
||||
|
||||
EXPECT_EQ(std::set<PluginCleaningData>({info1, info2}), plugin1.CleanInfo());
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, mergeMetadataShouldMergeLocationData) {
|
||||
PluginMetadata plugin1;
|
||||
@@ -423,6 +435,20 @@ TEST_P(PluginMetadataTest, diffMetadataShouldOutputDirtyInfoObjectsThatAreNotCom
|
||||
EXPECT_EQ(std::set<PluginCleaningData>({info2, info3}), diff.DirtyInfo());
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, diffMetadataShouldOutputCleanInfoObjectsThatAreNotCommonToBothInputPlugins) {
|
||||
PluginMetadata plugin1;
|
||||
PluginMetadata plugin2;
|
||||
PluginCleaningData info1(0x5, "utility");
|
||||
PluginCleaningData info2(0xA, "utility");
|
||||
PluginCleaningData info3(0x1, "utility");
|
||||
|
||||
plugin1.CleanInfo({info1, info2});
|
||||
plugin2.CleanInfo({info1, info3});
|
||||
PluginMetadata diff = plugin1.DiffMetadata(plugin2);
|
||||
|
||||
EXPECT_EQ(std::set<PluginCleaningData>({info2, info3}), diff.CleanInfo());
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, diffMetadataShouldOutputLocationsThatAreNotCommonToBothInputPlugins) {
|
||||
PluginMetadata plugin1;
|
||||
PluginMetadata plugin2;
|
||||
@@ -558,6 +584,20 @@ TEST_P(PluginMetadataTest, newMetadataShouldOutputDirtyInfoObjectsThatAreNotComm
|
||||
EXPECT_EQ(std::set<PluginCleaningData>({info2}), newMetadata.DirtyInfo());
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, newMetadataShouldOutputCleanInfoObjectsThatAreNotCommonToBothInputPlugins) {
|
||||
PluginMetadata plugin1;
|
||||
PluginMetadata plugin2;
|
||||
PluginCleaningData info1(0x5, "utility");
|
||||
PluginCleaningData info2(0xA, "utility");
|
||||
PluginCleaningData info3(0x1, "utility");
|
||||
|
||||
plugin1.CleanInfo({info1, info2});
|
||||
plugin2.CleanInfo({info1, info3});
|
||||
PluginMetadata newMetadata = plugin1.NewMetadata(plugin2);
|
||||
|
||||
EXPECT_EQ(std::set<PluginCleaningData>({info2}), newMetadata.CleanInfo());
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, newMetadataShouldOutputLocationsThatAreNotCommonToBothInputPlugins) {
|
||||
PluginMetadata plugin1;
|
||||
PluginMetadata plugin2;
|
||||
@@ -637,6 +677,7 @@ TEST_P(PluginMetadataTest, evalAllConditionsShouldEvaluateAllMetadataConditions)
|
||||
PluginCleaningData info1(blankEsmCrc, 1, 2, 3, "utility");
|
||||
PluginCleaningData info2(0xDEADBEEF, 1, 2, 3, "utility");
|
||||
plugin.DirtyInfo({info1, info2});
|
||||
plugin.CleanInfo({info1, info2});
|
||||
|
||||
EXPECT_NO_THROW(plugin.EvalAllConditions(game, Language::Code::english));
|
||||
|
||||
@@ -647,6 +688,7 @@ TEST_P(PluginMetadataTest, evalAllConditionsShouldEvaluateAllMetadataConditions)
|
||||
EXPECT_EQ(std::list<Message>({message1}), plugin.Messages());
|
||||
EXPECT_EQ(std::set<Tag>({tag1}), plugin.Tags());
|
||||
EXPECT_EQ(std::set<PluginCleaningData>({info1}), plugin.DirtyInfo());
|
||||
EXPECT_EQ(std::set<PluginCleaningData>({info1}), plugin.CleanInfo());
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, hasNameOnlyShouldBeTrueForADefaultConstructedPluginMetadataObject) {
|
||||
@@ -717,6 +759,13 @@ TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfDirtyInfoExists) {
|
||||
EXPECT_FALSE(plugin.HasNameOnly());
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfCleanInfoExists) {
|
||||
PluginMetadata plugin(blankEsp);
|
||||
plugin.CleanInfo({PluginCleaningData(5, "utility")});
|
||||
|
||||
EXPECT_FALSE(plugin.HasNameOnly());
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfLocationsExist) {
|
||||
PluginMetadata plugin(blankEsp);
|
||||
plugin.Locations({Location("http://www.example.com")});
|
||||
@@ -874,6 +923,19 @@ TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithDirtyInfoCorrect
|
||||
" nav: 2", emitter.c_str());
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithCleanInfoCorrectly) {
|
||||
PluginMetadata plugin(blankEsp);
|
||||
plugin.CleanInfo({PluginCleaningData(5, "utility")});
|
||||
|
||||
YAML::Emitter emitter;
|
||||
emitter << plugin;
|
||||
|
||||
EXPECT_STREQ("name: 'Blank.esp'\n"
|
||||
"clean:\n"
|
||||
" - crc: 0x5\n"
|
||||
" util: 'utility'", emitter.c_str());
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithLocationsCorrectly) {
|
||||
PluginMetadata plugin(blankEsp);
|
||||
plugin.Locations({Location("http://www.example.com")});
|
||||
@@ -900,6 +962,7 @@ TEST_P(PluginMetadataTest, encodingAsYamlShouldOmitAllUnsetFields) {
|
||||
EXPECT_FALSE(node["msg"]);
|
||||
EXPECT_FALSE(node["tag"]);
|
||||
EXPECT_FALSE(node["dirty"]);
|
||||
EXPECT_FALSE(node["clean"]);
|
||||
EXPECT_FALSE(node["url"]);
|
||||
}
|
||||
|
||||
@@ -975,6 +1038,15 @@ TEST_P(PluginMetadataTest, encodingAsYamlShouldSetDirtyFieldIfDirtyInfoExists) {
|
||||
EXPECT_EQ(plugin.DirtyInfo(), node["dirty"].as<std::set<PluginCleaningData>>());
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, encodingAsYamlShouldSetCleanFieldIfCleanInfoExists) {
|
||||
PluginMetadata plugin(blankEsp);
|
||||
plugin.CleanInfo({PluginCleaningData(5, "utility")});
|
||||
YAML::Node node;
|
||||
node = plugin;
|
||||
|
||||
EXPECT_EQ(plugin.CleanInfo(), node["clean"].as<std::set<PluginCleaningData>>());
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, encodingAsYamlShouldSetUrlFieldIfLocationsExist) {
|
||||
PluginMetadata plugin(blankEsp);
|
||||
plugin.Locations({Location("http://www.example.com")});
|
||||
@@ -1014,6 +1086,9 @@ TEST_P(PluginMetadataTest, decodingFromYamlShouldStoreAllGivenData) {
|
||||
" util: 'utility'\n"
|
||||
" udr: 1\n"
|
||||
" nav: 2\n"
|
||||
"clean:\n"
|
||||
" - crc: 0x6\n"
|
||||
" util: 'utility'\n"
|
||||
"url:\n"
|
||||
" - 'http://www.example.com'");
|
||||
PluginMetadata plugin = node.as<PluginMetadata>();
|
||||
@@ -1041,6 +1116,9 @@ TEST_P(PluginMetadataTest, decodingFromYamlShouldStoreAllGivenData) {
|
||||
EXPECT_EQ(std::set<PluginCleaningData>({
|
||||
PluginCleaningData(5, 0, 1, 2, "utility")
|
||||
}), plugin.DirtyInfo());
|
||||
EXPECT_EQ(std::set<PluginCleaningData>({
|
||||
PluginCleaningData(6, "utility")
|
||||
}), plugin.CleanInfo());
|
||||
EXPECT_EQ(std::set<Location>({
|
||||
Location("http://www.example.com")
|
||||
}), plugin.Locations());
|
||||
@@ -1057,6 +1135,15 @@ TEST_P(PluginMetadataTest, decodingFromYamlWithDirtyInfoInARegexPluginMetadataOb
|
||||
EXPECT_THROW(node.as<PluginMetadata>(), YAML::RepresentationException);
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, decodingFromYamlWithCleanInfoInARegexPluginMetadataObjectShouldThrow) {
|
||||
YAML::Node node = YAML::Load("name: 'Blank\\.esp'\n"
|
||||
"clean:\n"
|
||||
" - crc: 0x5\n"
|
||||
" util: 'utility'");
|
||||
|
||||
EXPECT_THROW(node.as<PluginMetadata>(), YAML::RepresentationException);
|
||||
}
|
||||
|
||||
TEST_P(PluginMetadataTest, decodingFromYamlWithAnInvalidRegexNameShouldThrow) {
|
||||
YAML::Node node = YAML::Load("name: 'RagnvaldBook(Farengar(+Ragnvald)?)?\\.esp'\n"
|
||||
"dirty:\n"
|
||||
|
||||
@@ -250,6 +250,21 @@ describe('Plugin', () => {
|
||||
plugin.isDirty.should.be.true();
|
||||
});
|
||||
|
||||
it('should set cleanedWith value to an empty string if no key was passed', () => {
|
||||
const plugin = new loot.Plugin({ name: 'test' });
|
||||
|
||||
plugin.cleanedWith.should.equal('');
|
||||
});
|
||||
|
||||
it('should set cleanedWith to passed key\'s value', () => {
|
||||
const plugin = new loot.Plugin({
|
||||
name: 'test',
|
||||
cleanedWith: 'TES5Edit 3.11',
|
||||
});
|
||||
|
||||
plugin.cleanedWith.should.equal('TES5Edit 3.11');
|
||||
});
|
||||
|
||||
it('should set id to the plugins name without spaces', () => {
|
||||
const plugin = new loot.Plugin({ name: 'test plugin name' });
|
||||
|
||||
@@ -510,7 +525,7 @@ describe('Plugin', () => {
|
||||
let handleEvent;
|
||||
|
||||
afterEach(() => {
|
||||
document.removeEventListener('loot-plugin-isdirty-change', handleEvent);
|
||||
document.removeEventListener('loot-plugin-cleaning-data-change', handleEvent);
|
||||
});
|
||||
|
||||
it('getting value should return false if isDirty has not been set in the constructor', () => {
|
||||
@@ -543,7 +558,7 @@ describe('Plugin', () => {
|
||||
done(new Error('Should not have fired an event'));
|
||||
};
|
||||
|
||||
document.addEventListener('loot-plugin-isdirty-change', handleEvent);
|
||||
document.addEventListener('loot-plugin-cleaning-data-change', handleEvent);
|
||||
|
||||
plugin.isDirty = plugin.isDirty;
|
||||
|
||||
@@ -558,12 +573,70 @@ describe('Plugin', () => {
|
||||
done();
|
||||
};
|
||||
|
||||
document.addEventListener('loot-plugin-isdirty-change', handleEvent);
|
||||
document.addEventListener('loot-plugin-cleaning-data-change', handleEvent);
|
||||
|
||||
plugin.isDirty = !plugin.isDirty;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#cleanedWith', () => {
|
||||
let handleEvent;
|
||||
|
||||
afterEach(() => {
|
||||
document.removeEventListener('loot-plugin-cleaning-data-change', handleEvent);
|
||||
});
|
||||
|
||||
it('getting value should return an empty string if cleanedWith has not been set in the constructor', () => {
|
||||
const plugin = new loot.Plugin({ name: 'test' });
|
||||
|
||||
plugin.cleanedWith.should.equal('');
|
||||
});
|
||||
|
||||
it('getting value should return a string if cleanedWith is set in the constructor', () => {
|
||||
const plugin = new loot.Plugin({
|
||||
name: 'test',
|
||||
cleanedWith: 'utility',
|
||||
});
|
||||
|
||||
plugin.cleanedWith.should.equal('utility');
|
||||
});
|
||||
|
||||
it('setting value should store set value', () => {
|
||||
const plugin = new loot.Plugin({ name: 'test' });
|
||||
|
||||
plugin.cleanedWith = 'utility';
|
||||
|
||||
plugin.cleanedWith.should.equal('utility');
|
||||
});
|
||||
|
||||
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-cleaning-data-change', handleEvent);
|
||||
|
||||
plugin.cleanedWith = plugin.cleanedWith;
|
||||
|
||||
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.cleanedWith.should.equal('utility');
|
||||
done();
|
||||
};
|
||||
|
||||
document.addEventListener('loot-plugin-cleaning-data-change', handleEvent);
|
||||
|
||||
plugin.cleanedWith = 'utility';
|
||||
});
|
||||
});
|
||||
|
||||
describe('#crc', () => {
|
||||
let handleEvent;
|
||||
|
||||
@@ -574,7 +647,7 @@ describe('Plugin', () => {
|
||||
it('getting value should return 0 if crc has not been set in the constructor', () => {
|
||||
const plugin = new loot.Plugin({ name: 'test' });
|
||||
|
||||
plugin.isDirty.should.be.false();
|
||||
plugin.crc.should.equal(0);
|
||||
});
|
||||
|
||||
it('getting value should return 0xDEADBEEF if it was set in the constructor', () => {
|
||||
|
||||
Reference in New Issue
Block a user