added tests, fixed issues with overriding/format

In the documentation it states that child values can override the parent
values. Originally this code did not support that but now it does. Also
added in testing inspired by the previous tests.

Signed-off-by: cullvox <cullvox@outlook.com>
This commit is contained in:
cullvox 2023-09-12 21:34:42 -04:00
parent df88ccd419
commit a4e6530513
9 changed files with 321 additions and 98 deletions

View File

@ -179,95 +179,137 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level)
return true; return true;
} }
struct TextFormat { struct TextFormatter {
QString color = "#000000"; // left is value, right is if the value was explicitly written
bool bold = false; QPair<QString, bool> color = { "#000000", false };
bool italic = false; QPair<bool, bool> bold = { false, false };
bool underlined = false; QPair<bool, bool> italic = { false, false };
bool strikethrough = false; QPair<bool, bool> underlined = { false, false };
bool is_linked = false; QPair<bool, bool> strikethrough = { false, false };
QString link_url = ""; QPair<bool, bool> is_linked = { false, false };
}; QPair<QString, bool> link_url = { "", false };
bool readFormat(const QJsonObject& obj, TextFormat& format) void setColor(const QString& new_color, bool written) { color = { new_color, written}; }
void setBold(bool new_bold, bool written) { bold = { new_bold, written}; }
void setItalic(bool new_italic, bool written) { italic = { new_italic, written}; }
void setUnderlined(bool new_underlined, bool written) { underlined = { new_underlined, written}; }
void setStrikethrough(bool new_strikethrough, bool written) { strikethrough = { new_strikethrough, written}; }
void setIsLinked(bool new_is_linked, bool written) { is_linked = { new_is_linked, written}; }
void setLinkURL(const QString& new_url, bool written) { link_url = { new_url, written}; }
void overrideFrom(const TextFormatter& child)
{ {
format.color = Json::ensureString(obj, "color"); if (child.color.second)
format.bold = Json::ensureBoolean(obj, "bold", false); color.first = child.color.first;
format.italic = Json::ensureBoolean(obj, "italic", false); if (child.bold.second)
format.underlined = Json::ensureBoolean(obj, "underlined", false); bold.first = child.bold.first;
format.strikethrough = Json::ensureBoolean(obj, "strikethrough", false); if (child.italic.second)
italic.first = child.italic.first;
if (child.underlined.second)
underlined.first = child.underlined.first;
if (child.strikethrough.second)
strikethrough.first = child.strikethrough.first;
if (child.is_linked.second)
is_linked.first = child.is_linked.first;
if (child.link_url.second)
link_url.first = child.link_url.first;
}
QString format(QString text)
{
if (text.isEmpty())
return QString();
QString result;
if (color.first != "#000000")
result.append("<font color=\"" + color.first + "\">");
if (bold.first)
result.append("<b>");
if (italic.first)
result.append("<i>");
if (underlined.first)
result.append("<u>");
if (strikethrough.first)
result.append("<s>");
if (is_linked.first)
result.append("<a href=\"" + link_url.first + "\">");
result.append(text);
if (is_linked.first)
result.append("</a>");
if (strikethrough.first)
result.append("</s>");
if (underlined.first)
result.append("</u>");
if (italic.first)
result.append("</i>");
if (bold.first)
result.append("</b>");
if (color.first != "#000000")
result.append("</font>");
return result;
}
bool readFormat(const QJsonObject& obj)
{
setColor(Json::ensureString(obj, "color", "#000000"), obj.contains("color"));
setBold(Json::ensureBoolean(obj, "bold", false), obj.contains("bold"));
setItalic(Json::ensureBoolean(obj, "italic", false), obj.contains("italic"));
setUnderlined(Json::ensureBoolean(obj, "underlined", false), obj.contains("underlined"));
setStrikethrough(Json::ensureBoolean(obj, "strikethrough", false), obj.contains("strikethrough"));
auto click_event = Json::ensureObject(obj, "clickEvent"); auto click_event = Json::ensureObject(obj, "clickEvent");
format.is_linked = Json::ensureBoolean(click_event, "open_url", false); setIsLinked(Json::ensureBoolean(click_event, "open_url", false), click_event.contains("open_url"));
format.link_url = Json::ensureString(click_event, "value"); setLinkURL(Json::ensureString(click_event, "value"), click_event.contains("value"));
return true; return true;
} }
};
void appendBeginFormat(TextFormat& format, QString& toAppend) bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat)
{ {
toAppend.append("<font color=\"" + format.color + "\">"); TextFormatter formatter;
if (format.bold)
toAppend.append("<b>");
if (format.italic)
toAppend.append("<i>");
if (format.underlined)
toAppend.append("<u>");
if (format.strikethrough)
toAppend.append("<s>");
if (format.is_linked)
toAppend.append("<a href=\"" + format.link_url + "\">");
}
void appendEndFormat(TextFormat& format, QString& toAppend) if (parentFormat)
{ formatter = *parentFormat;
if (format.is_linked)
toAppend.append("</a>");
if (format.strikethrough)
toAppend.append("</s>");
if (format.italic)
toAppend.append("</i>");
if (format.underlined)
toAppend.append("</u>");
if (format.bold)
toAppend.append("</b>");
toAppend.append("</font>");
}
bool processComponentList(const QJsonArray& arr, QString& result)
{
for (const QJsonValue& val : arr) {
if (!processComponent(val, result))
return false;
}
return true;
}
bool processComponent(const QJsonValue& value, QString& result)
{
if (value.isString()) { if (value.isString()) {
result.append(value.toString()); result.append(formatter.format(value.toString()));
} else if (value.isBool()) {
result.append(formatter.format(value.toBool() ? "true" : "false"));
} else if (value.isDouble()) {
result.append(formatter.format(QString::number(value.toDouble())));
} else if (value.isObject()) { } else if (value.isObject()) {
auto obj = value.toObject(); auto obj = value.toObject();
TextFormat format{}; if (not formatter.readFormat(obj))
if (!readFormat(obj, format))
return false; return false;
appendBeginFormat(format, result); // override the parent format with our new one
TextFormatter mixed;
if (parentFormat)
mixed = *parentFormat;
auto text = obj.value("text"); mixed.overrideFrom(formatter);
if (text.isString())
result.append(text.toString());
result.append(mixed.format(Json::ensureString(obj, "text")));
// process any 'extra' children with this format
auto extra = obj.value("extra"); auto extra = obj.value("extra");
if (extra.isArray()) if (not extra.isUndefined())
if (!processComponentList(extra.toArray(), result)) return processComponent(extra, result, &mixed);
return false;
appendEndFormat(format, result); } else if (value.isArray()) {
auto array = value.toArray();
for (const QJsonValue& current : array) {
if (not processComponent(current, result, parentFormat)) {
return false;
}
}
} else { } else {
qWarning() << "Invalid component type!"; qWarning() << "Invalid component type!";
return false; return false;
@ -286,29 +328,12 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
// description could be many things according to minecraft
auto desc_val = pack_obj.value("description"); auto desc_val = pack_obj.value("description");
QString desc{};
QString desc; if (not processComponent(desc_val, desc))
if (desc_val.isString()) {
desc = desc_val.toString();
} else if (desc_val.isArray()) {
if (!processComponentList(desc_val.toArray(), desc))
return false; return false;
} else if (desc_val.isObject()) {
if (!processComponent(desc_val, desc))
return false;
} else if (desc_val.isBool()) {
desc = desc_val.toBool() ? "true" : "false";
} else if (desc_val.isDouble()) {
desc = QString::number(desc_val.toDouble());
} else {
qWarning() << "Invalid description type!";
return false;
}
qInfo() << desc; qInfo() << desc;
pack.setDescription(desc); pack.setDescription(desc);
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {

View File

@ -35,8 +35,8 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Ful
bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processComponent(const QJsonValue& value, QString& result); struct TextFormatter;
bool processComponentList(const QJsonArray& value, QString& result); bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat = nullptr);
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data); bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data); bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data);

View File

@ -56,3 +56,6 @@ ecm_add_test(Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}:
ecm_add_test(Version_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test ecm_add_test(Version_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME Version) TEST_NAME Version)
ecm_add_test(MetaComponentParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME MetaComponentParse)

View File

@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QTest>
#include <QTimer>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <FileSystem.h>
#include <minecraft/mod/tasks/LocalResourcePackParseTask.h>
class MetaComponentParseTest : public QObject {
Q_OBJECT
void doTest(QString name)
{
QString source = QFINDTESTDATA("testdata/MetaComponentParse");
QString comp_rp = FS::PathCombine(source, name);
QFile file;
file.setFileName(comp_rp);
QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text));
QString data = file.readAll();
file.close();
QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8());
QJsonObject obj = doc.object();
QJsonValue description_json = obj.value("description");
QJsonValue expected_json = obj.value("expected_output");
QVERIFY(description_json.isUndefined() == false);
QVERIFY(expected_json.isString() == true);
QString expected = expected_json.toString();
QString processed;
bool valid = ResourcePackUtils::processComponent(description_json, processed);
QVERIFY(processed == expected);
QVERIFY(valid == true);
}
private slots:
void test_parseComponentBasic()
{
doTest("component_basic.json");
}
void test_parseComponentWithFormat()
{
doTest("component_with_format.json");
}
void test_parseComponentWithExtra()
{
doTest("component_with_extra.json");
}
void test_parseComponentWithLink()
{
doTest("component_with_link.json");
}
void test_parseComponentWithMixed()
{
doTest("component_with_mixed.json");
}
};
QTEST_GUILESS_MAIN(MetaComponentParseTest)
#include "MetaComponentParse_test.moc"

View File

@ -0,0 +1,4 @@
{
"description": [{"text": "Hello, Component!"}],
"expected_output": "Hello, Component!"
}

View File

@ -0,0 +1,18 @@
{
"description": [
{
"text": "Hello, ",
"color": "red",
"bold": true,
"italic": true,
"extra": [
{
"extra": "Component!",
"bold": false,
"italic": false
}
]
}
],
"expected_output": "<font color=\"red\"><b><i>Hello, </i></b></font><font color=\"red\">Component!</font>"
}

View File

@ -0,0 +1,13 @@
{
"description": [
{
"text": "Hello, Component!",
"color": "blue",
"bold": true,
"italic": true,
"underlined": true,
"strikethrough": true
}
],
"expected_output": "<font color=\"blue\"><b><i><u><s>Hello, Component!</s></u></i></b></font>"
}

View File

@ -0,0 +1,12 @@
{
"description": [
{
"text": "Hello, Component!",
"clickEvent": {
"open_url": true,
"value": "https://google.com"
}
}
],
"expected_output": "<a href=\"https://google.com\">Hello, Component!</a>"
}

View File

@ -0,0 +1,40 @@
{
"description": [
{
"text": "The quick ",
"color": "blue",
"italic": true
},
{
"text": "brown fox ",
"color": "#873600",
"bold": true,
"underlined": true,
"extra": {
"text": "jumped over ",
"color": "blue",
"bold": false,
"underlined": false,
"italic": true,
"strikethrough": true
}
},
{
"text": "the lazy dog's back. ",
"color": "green",
"bold": true,
"italic": true,
"underlined": true,
"strikethrough": true,
"extra": [
{
"text": "1234567890 ",
"color": "black",
"strikethrough": false,
"extra": "How vexingly quick daft zebras jump!"
}
]
}
],
"expected_output": "<font color=\"blue\"><i>The quick </i></font><font color=\"#873600\"><b><u>brown fox </u></b></font><font color=\"blue\"><i><s>jumped over </s></i></font><font color=\"green\"><b><i><u><s>the lazy dog's back. </s></u></i></b></font><font color=\"black\"><b><i><u>1234567890 </u></i></b></font><font color=\"black\"><b><i><u>How vexingly quick daft zebras jump!</u></i></b></font>"
}