commit d4d0a5e1549a19fcb12e9de3fd0b76d954db3112
parent c2777c139fa705579b18a37784eb9e6847090546
Author: cfillion <cfillion@users.noreply.github.com>
Date: Wed, 13 Sep 2017 21:29:20 -0400
redesign report dialog
Diffstat:
26 files changed, 389 insertions(+), 403 deletions(-)
diff --git a/src/about.cpp b/src/about.cpp
@@ -24,7 +24,6 @@
#include "index.hpp"
#include "listview.hpp"
#include "menu.hpp"
-#include "ostream.hpp"
#include "reapack.hpp"
#include "registry.hpp"
#include "remote.hpp"
@@ -37,6 +36,7 @@
#include <boost/algorithm/string.hpp>
#include <iomanip>
+#include <sstream>
using namespace std;
@@ -556,7 +556,7 @@ void AboutPackageDelegate::updateList(const int index)
return;
const Version *ver = m_package->version(index);
- OutputStream stream;
+ ostringstream stream;
stream << *ver;
Win32::setWindowText(m_dialog->getControl(IDC_CHANGELOG), stream.str().c_str());
diff --git a/src/errors.hpp b/src/errors.hpp
@@ -18,6 +18,7 @@
#ifndef REAPACK_ERRORS_HPP
#define REAPACK_ERRORS_HPP
+#include <ostream>
#include <stdexcept>
#include "string.hpp"
@@ -32,4 +33,10 @@ struct ErrorInfo {
std::string context;
};
+inline std::ostream &operator<<(std::ostream &os, const ErrorInfo &err)
+{
+ os << err.context << ":\r\n" << String::indent(err.message) << "\r\n";
+ return os;
+}
+
#endif
diff --git a/src/ostream.cpp b/src/ostream.cpp
@@ -1,66 +0,0 @@
-/* ReaPack: Package manager for REAPER
- * Copyright (C) 2015-2017 Christian Fillion
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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 Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "ostream.hpp"
-
-#include "version.hpp"
-
-#include <boost/algorithm/string/trim.hpp>
-#include <locale>
-
-using namespace std;
-
-OutputStream::OutputStream()
-{
- // enable number formatting (ie. "1,234" instead of "1234")
- m_stream.imbue(locale(""));
-}
-
-void OutputStream::indented(const string &text)
-{
- istringstream stream(text);
- string line;
-
- while(getline(stream, line, '\n')) {
- boost::algorithm::trim(line);
-
- if(!line.empty())
- m_stream << "\x20\x20" << line;
-
- m_stream << "\r\n";
- }
-}
-
-OutputStream &OutputStream::operator<<(const Version &ver)
-{
- m_stream << 'v' << ver.name().toString();
-
- if(!ver.author().empty())
- m_stream << " by " << ver.author();
-
- const string &date = ver.time().toString();
- if(!date.empty())
- m_stream << " – " << date;
-
- m_stream << "\r\n";
-
- const string &changelog = ver.changelog();
- indented(changelog.empty() ? "No changelog" : changelog);
-
- return *this;
-}
-
diff --git a/src/ostream.hpp b/src/ostream.hpp
@@ -1,42 +0,0 @@
-/* ReaPack: Package manager for REAPER
- * Copyright (C) 2015-2017 Christian Fillion
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * 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 Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef REAPACK_OSTREAM_HPP
-#define REAPACK_OSTREAM_HPP
-
-#include <sstream>
-
-class Version;
-
-class OutputStream {
-public:
- OutputStream();
-
- std::ostringstream::pos_type tellp() { return m_stream.tellp(); }
- std::string str() const { return m_stream.str(); }
-
- void indented(const std::string &);
-
- template<typename T>
- OutputStream &operator<<(T t) { m_stream << t; return *this; }
- OutputStream &operator<<(const Version &);
-
-private:
- std::ostringstream m_stream;
-};
-
-#endif
diff --git a/src/path.hpp b/src/path.hpp
@@ -77,6 +77,11 @@ private:
bool m_absolute;
};
+inline std::ostream &operator<<(std::ostream &os, const Path &p)
+{
+ return os << p.join();
+};
+
class UseRootPath {
public:
UseRootPath(const Path &);
diff --git a/src/reapack.cpp b/src/reapack.cpp
@@ -358,7 +358,7 @@ Transaction *ReaPack::setupTransaction()
LockDialog managerLock(m_manager);
LockDialog browserLock(m_browser);
- Dialog::Show<Report>(m_instance, m_mainWindow, *m_tx->receipt());
+ Dialog::Show<Report>(m_instance, m_mainWindow, m_tx->receipt());
}
});
diff --git a/src/receipt.cpp b/src/receipt.cpp
@@ -19,10 +19,13 @@
#include "index.hpp"
+#include <boost/range/adaptor/reversed.hpp>
+#include <sstream>
+
using namespace std;
Receipt::Receipt()
- : m_needRestart(false)
+ : m_flags(NoFlags)
{
}
@@ -30,24 +33,86 @@ bool Receipt::empty() const
{
return
m_installs.empty() &&
- m_updates.empty() &&
m_removals.empty() &&
m_exports.empty() &&
m_errors.empty();
}
-void Receipt::addInstall(const InstallTicket &ticket)
+void Receipt::addInstall(const Version *ver, const Registry::Entry &entry)
{
- if(ticket.previous && ticket.previous.version < ticket.version->name())
- m_updates.push_back(ticket);
- else
- m_installs.push_back(ticket);
+ m_installs.emplace_back(InstallTicket{ver, entry});
+
+ if(ver->package()->type() == Package::ExtensionType)
+ m_flags |= RestartNeeded;
+}
- m_indexes.insert(ticket.version->package()->category()
- ->index()->shared_from_this());
+void Receipt::addRemoval(const Path &path)
+{
+ m_removals.insert(path);
}
void Receipt::addRemovals(const set<Path> &pathList)
{
m_removals.insert(pathList.begin(), pathList.end());
}
+
+void Receipt::addExport(const Path &path)
+{
+ m_exports.insert(path);
+}
+
+void Receipt::addError(const ErrorInfo &err)
+{
+ m_errors.push_back(err);
+}
+
+ReceiptPages Receipt::pages() const
+{
+ return ReceiptPages{{
+ {m_installs, "Installed"},
+ {m_removals, "Removed"},
+ {m_exports, "Exported"},
+ {m_errors, "Error", "Errors"},
+ }};
+}
+
+void ReceiptPage::setTitle(const char *title)
+{
+ ostringstream stream;
+
+ // enable number formatting (ie. "1,234" instead of "1234")
+ stream.imbue(locale(""));
+
+ stream << title << " (" << m_size << ')';
+ m_title = stream.str();
+}
+
+InstallTicket::InstallTicket(const Version *ver, const Registry::Entry &previous)
+ : m_version(ver), m_previous(previous.version)
+{
+ m_isUpdate = previous && previous.version < ver->name();
+ m_index = ver->package()->category()->index()->shared_from_this();
+}
+
+ostream &operator<<(ostream &os, const InstallTicket &t)
+{
+ if(os.tellp() > 0)
+ os << "\r\n";
+
+ os << t.m_version->package()->fullName() << "\r\n";
+
+ if(t.m_isUpdate) {
+ const auto &versions = t.m_version->package()->versions();
+
+ for(const Version *ver : versions | boost::adaptors::reversed) {
+ if(ver->name() <= t.m_previous)
+ break;
+ else if(ver->name() <= t.m_version->name())
+ os << *ver;
+ }
+ }
+ else
+ os << *t.m_version;
+
+ return os;
+}
diff --git a/src/receipt.hpp b/src/receipt.hpp
@@ -18,58 +18,91 @@
#ifndef REAPACK_RECEIPT_HPP
#define REAPACK_RECEIPT_HPP
-#include "errors.hpp"
#include "registry.hpp"
+#include "errors.hpp"
-#include <set>
-#include <string>
-#include <unordered_set>
+#include <array>
#include <vector>
+#include <set>
class Index;
+class InstallTicket;
class Path;
+class ReceiptPage;
+class Version;
typedef std::shared_ptr<const Index> IndexPtr;
-
-struct InstallTicket {
- const Version *version;
- Registry::Entry previous;
-};
+typedef std::array<ReceiptPage, 4> ReceiptPages;
class Receipt {
public:
- Receipt();
+ enum Flag {
+ NoFlags = 0,
+ RestartNeeded = 1<<0,
+ };
- bool empty() const;
+ Receipt();
- bool isRestartNeeded() const { return m_needRestart; }
- void setRestartNeeded(bool newVal) { m_needRestart = newVal; }
+ bool test(Flag f) const { return (m_flags & f) != 0; }
- void addInstall(const InstallTicket &);
- const std::vector<InstallTicket> &installs() const { return m_installs; }
- const std::vector<InstallTicket> &updates() const { return m_updates; }
+ bool empty() const;
+ ReceiptPages pages() const;
- void addRemoval(const Path &p) { m_removals.insert(p); }
+ void addInstall(const Version *, const Registry::Entry &);
+ void addRemoval(const Path &p);
void addRemovals(const std::set<Path> &);
- const std::set<Path> &removals() const { return m_removals; }
-
- void addExport(const Path &p) { m_exports.insert(p); }
- const std::set<Path> &exports() const { return m_exports; }
-
- void addError(const ErrorInfo &err) { m_errors.push_back(err); }
- const std::vector<ErrorInfo> &errors() const { return m_errors; }
- bool hasErrors() const { return !m_errors.empty(); }
+ void addExport(const Path &p);
+ void addError(const ErrorInfo &);
private:
- bool m_needRestart;
-
+ int m_flags;
std::vector<InstallTicket> m_installs;
- std::vector<InstallTicket> m_updates;
std::set<Path> m_removals;
std::set<Path> m_exports;
std::vector<ErrorInfo> m_errors;
+};
- std::unordered_set<IndexPtr> m_indexes; // keep them alive!
+class ReceiptPage {
+public:
+ template<typename T>
+ ReceiptPage(const T &list, const char *singular, const char *plural = nullptr)
+ : m_size(list.size())
+ {
+ setTitle(m_size == 1 || !plural ? singular : plural);
+
+ std::ostringstream stream;
+
+ for(const auto &item : list)
+ stream << item << "\r\n";
+
+ m_contents = stream.str();
+ }
+
+ const std::string &title() const { return m_title; }
+ const std::string &contents() const { return m_contents; }
+ bool empty() const { return m_size == 0; }
+
+private:
+ void setTitle(const char *title);
+ size_t m_size;
+ std::string m_title;
+ std::string m_contents;
+};
+
+class InstallTicket {
+public:
+ InstallTicket(const Version *ver, const Registry::Entry &previousEntry);
+
+private:
+ friend std::ostream &operator<<(std::ostream &, const InstallTicket &);
+
+ const Version *m_version;
+ VersionName m_previous;
+ bool m_isUpdate;
+
+ IndexPtr m_index; // to keep it alive long enough
};
+std::ostream &operator<<(std::ostream &, const InstallTicket &);
+
#endif
diff --git a/src/report.cpp b/src/report.cpp
@@ -17,17 +17,14 @@
#include "report.hpp"
-#include "package.hpp"
+#include "receipt.hpp"
#include "resource.hpp"
-#include "source.hpp"
-#include "transaction.hpp"
+#include "tabbar.hpp"
#include "win32.hpp"
-#include <boost/range/adaptor/reversed.hpp>
-
using namespace std;
-Report::Report(const Receipt &receipt)
+Report::Report(const Receipt *receipt)
: Dialog(IDD_REPORT_DIALOG), m_receipt(receipt)
{
}
@@ -36,120 +33,37 @@ void Report::onInit()
{
Dialog::onInit();
- fillReport();
-
- SetFocus(getControl(IDOK));
-}
-
-void Report::printHeader(const char *title)
-{
- if(m_stream.tellp())
- m_stream << "\r\n";
-
- const string sep(10, '=');
- m_stream << sep << ' ' << title << ": " << sep << "\r\n\r\n";
-}
-
-void Report::fillReport()
-{
- const size_t installs = m_receipt.installs().size();
- const size_t updates = m_receipt.updates().size();
- const size_t removals = m_receipt.removals().size();
- const size_t errors = m_receipt.errors().size();
-
- m_stream << installs << " installed package";
- if(installs != 1) m_stream << 's';
-
- m_stream << ", " << updates << " update";
- if(updates != 1) m_stream << 's';
-
- m_stream << ", " << removals << " removed file";
- if(removals != 1) m_stream << 's';
-
- m_stream << " and " << errors << " error";
- if(errors != 1) m_stream << 's';
-
- m_stream << "\r\n";
-
- if(m_receipt.isRestartNeeded()) {
- m_stream
- << "\r\n"
- << "Notice: One or more native REAPER extensions were installed.\r\n"
- << "The newly installed files won't be loaded until REAPER is restarted."
- << "\r\n";
- }
-
- if(errors)
- printErrors();
-
- if(installs)
- printInstalls();
+ HWND report = getControl(IDC_REPORT);
+ TabBar *tabbar = createControl<TabBar>(IDC_TABS, this);
- if(updates)
- printUpdates();
+ tabbar->onTabChange([=] (const int i) {
+ Win32::setWindowText(report, m_pages[i].c_str());
+ });
- if(removals)
- printRemovals();
+ bool firstPage = true;
- if(installs + updates + removals == 0) {
- Win32::setWindowText(getControl(IDC_LABEL),
- "Oops! The following error(s) occured:");
- }
-
- Win32::setWindowText(getControl(IDC_REPORT), m_stream.str().c_str());
-}
-
-void Report::printInstalls()
-{
- printHeader("Installed packages");
-
- for(const InstallTicket &ticket : m_receipt.installs())
- m_stream << ticket.version->fullName() << "\r\n";
-}
-
-void Report::printUpdates()
-{
- printHeader("Updates");
-
- const auto start = m_stream.tellp();
+ for(const ReceiptPage &page : m_receipt->pages()) {
+ m_pages.emplace_back(page.contents());
+ tabbar->addTab({page.title().c_str()});
- for(const InstallTicket &ticket : m_receipt.updates()) {
- const Package *pkg = ticket.version->package();
- const auto &versions = pkg->versions();
-
- if(m_stream.tellp() != start)
- m_stream << "\r\n";
-
- m_stream << pkg->fullName() << ":\r\n";
-
- for(const Version *ver : versions | boost::adaptors::reversed) {
- if(ver->name() <= ticket.previous.version)
- break;
- else if(ver->name() <= ticket.version->name())
- m_stream << *ver;
+ if(firstPage && !page.empty()) {
+ tabbar->setCurrentIndex(tabbar->count() - 1);
+ firstPage = false;
}
}
-}
-void Report::printErrors()
-{
- printHeader("Errors");
-
- const auto start = m_stream.tellp();
-
- for(const ErrorInfo &err : m_receipt.errors()) {
- if(m_stream.tellp() != start)
- m_stream << "\r\n";
+ SetFocus(getControl(IDOK));
- m_stream << err.context << ":\r\n";
- m_stream.indented(err.message);
- }
+ if(m_receipt->test(Receipt::RestartNeeded))
+ startTimer(1);
}
-void Report::printRemovals()
+void Report::onTimer(int timer)
{
- printHeader("Removed files");
+ stopTimer(timer);
- for(const Path &path : m_receipt.removals())
- m_stream << path.join() << "\r\n";
+ Win32::messageBox(handle(),
+ "One or more native REAPER extensions were installed.\r\n"
+ "These newly installed files won't be loaded until REAPER is restarted.",
+ "ReaPack Notice", MB_OK);
}
diff --git a/src/report.hpp b/src/report.hpp
@@ -20,32 +20,19 @@
#include "dialog.hpp"
-#include "ostream.hpp"
-#include "receipt.hpp"
-#include "registry.hpp"
-
-class Package;
-class Version;
+class Receipt;
class Report : public Dialog {
public:
- Report(const Receipt &);
+ Report(const Receipt *);
protected:
void onInit() override;
-
- void fillReport();
-
- void printHeader(const char *);
-
- void printInstalls();
- void printUpdates();
- void printErrors();
- void printRemovals();
+ void onTimer(int) override;
private:
- Receipt m_receipt;
- OutputStream m_stream;
+ const Receipt *m_receipt;
+ std::vector<std::string> m_pages;
};
#endif
diff --git a/src/resource.rc b/src/resource.rc
@@ -13,16 +13,17 @@ BEGIN
PUSHBUTTON "&Cancel", IDCANCEL, 105, 60, 50, 14, NOT WS_TABSTOP
END
-IDD_REPORT_DIALOG DIALOGEX 0, 0, 280, 260
+IDD_REPORT_DIALOG DIALOGEX 0, 0, 300, 286
STYLE DIALOG_STYLE
FONT DIALOG_FONT
CAPTION "ReaPack: Transaction Report"
BEGIN
LTEXT "All done! Description of the changes:",
IDC_LABEL, 5, 5, 270, 10
- EDITTEXT IDC_REPORT, 6, 18, 268, 215, WS_VSCROLL | ES_MULTILINE |
+ CONTROL "", IDC_TABS, WC_TABCONTROL, 0, 2, 16, 296, 246
+ EDITTEXT IDC_REPORT, 9, 34, 281, 223, WS_VSCROLL | ES_MULTILINE |
ES_READONLY | NOT WS_TABSTOP
- DEFPUSHBUTTON "&OK", IDOK, 115, 240, 50, 14
+ DEFPUSHBUTTON "&OK", IDOK, 125, 266, 50, 14
END
IDD_CONFIG_DIALOG DIALOGEX 0, 0, 370, 240
diff --git a/src/string.cpp b/src/string.cpp
@@ -17,7 +17,13 @@
#include "string.hpp"
-std::string String::format(const char *fmt, ...)
+#include <boost/algorithm/string/trim.hpp>
+#include <cstdarg>
+#include <sstream>
+
+using namespace std;
+
+string String::format(const char *fmt, ...)
{
va_list args;
@@ -25,7 +31,7 @@ std::string String::format(const char *fmt, ...)
const int size = vsnprintf(nullptr, 0, fmt, args);
va_end(args);
- std::string buf(size, 0);
+ string buf(size, 0);
va_start(args, fmt);
vsnprintf(&buf[0], size + 1, fmt, args);
@@ -33,3 +39,25 @@ std::string String::format(const char *fmt, ...)
return buf;
}
+
+string String::indent(const string &text)
+{
+ ostringstream output;
+ istringstream input(text);
+ string line;
+ bool first = true;
+
+ while(getline(input, line, '\n')) {
+ if(first)
+ first = false;
+ else
+ output << "\r\n";
+
+ boost::algorithm::trim(line);
+
+ if(!line.empty())
+ output << "\x20\x20" << line;
+ }
+
+ return output.str();
+}
diff --git a/src/string.hpp b/src/string.hpp
@@ -25,6 +25,8 @@ namespace String {
__attribute__((format(printf, 1, 2)))
#endif
std::string format(const char *fmt, ...);
+
+ std::string indent(const std::string &);
}
#endif
diff --git a/src/tabbar.cpp b/src/tabbar.cpp
@@ -117,6 +117,7 @@ void TabBar::switchPage()
}
const int index = currentIndex();
+ m_onTabChange(index);
if(index < 0 || (size_t)index >= m_pages.size()) {
m_lastPage = -1;
diff --git a/src/tabbar.hpp b/src/tabbar.hpp
@@ -20,12 +20,15 @@
#include "control.hpp"
+#include <boost/signals2.hpp>
#include <vector>
class Dialog;
class TabBar : public Control {
public:
+ typedef boost::signals2::signal<void (int index)> TabSignal;
+
typedef std::vector<HWND> Page;
struct Tab { const char *text; Page page; };
typedef std::vector<Tab> Tabs;
@@ -38,6 +41,7 @@ public:
void setFocus();
int count() const;
void clear();
+ void onTabChange(const TabSignal::slot_type &slot) { m_onTabChange.connect(slot); }
protected:
void onNotify(LPNMHDR, LPARAM) override;
@@ -48,6 +52,7 @@ private:
Dialog *m_parent;
int m_lastPage;
std::vector<Page> m_pages;
+ TabSignal m_onTabChange;
};
#endif
diff --git a/src/task.cpp b/src/task.cpp
@@ -125,13 +125,10 @@ void InstallTask::commit()
tx()->registerFile({false, m_oldEntry, file});
}
- tx()->receipt()->addInstall({m_version, m_oldEntry});
+ tx()->receipt()->addInstall(m_version, m_oldEntry);
const Registry::Entry newEntry = tx()->registry()->push(m_version);
- if(newEntry.type == Package::ExtensionType)
- tx()->receipt()->setRestartNeeded(true);
-
if(m_pin)
tx()->registry()->setPinned(newEntry, true);
diff --git a/src/time.cpp b/src/time.cpp
@@ -68,3 +68,9 @@ int Time::compare(const Time &o) const
return 0;
}
+
+ostream &operator<<(ostream &os, const Time &time)
+{
+ os << time.toString();
+ return os;
+}
diff --git a/src/time.hpp b/src/time.hpp
@@ -51,4 +51,6 @@ private:
std::tm m_tm;
};
+std::ostream &operator<<(std::ostream &os, const Time &time);
+
#endif
diff --git a/src/version.cpp b/src/version.cpp
@@ -73,6 +73,24 @@ bool Version::addSource(const Source *source)
return true;
}
+ostream &operator<<(ostream &os, const Version &ver)
+{
+ os << 'v' << ver.name().toString();
+
+ if(!ver.author().empty())
+ os << " by " << ver.author();
+
+ if(ver.time())
+ os << " – " << ver.time();
+
+ os << "\r\n";
+
+ const string &changelog = ver.changelog();
+ os << String::indent(changelog.empty() ? "No changelog" : changelog);
+
+ return os;
+}
+
VersionName::VersionName() : m_stable(true)
{}
diff --git a/src/version.hpp b/src/version.hpp
@@ -99,4 +99,6 @@ private:
std::set<Path> m_files;
};
+std::ostream &operator<<(std::ostream &, const Version &);
+
#endif
diff --git a/test/helper.cpp b/test/helper.cpp
@@ -6,12 +6,6 @@
using namespace std;
-ostream &operator<<(ostream &os, const Path &path)
-{
- os << '"' << path.join() << '"';
- return os;
-}
-
ostream &operator<<(ostream &os, const set<Path> &list)
{
os << '{';
@@ -23,15 +17,3 @@ ostream &operator<<(ostream &os, const set<Path> &list)
return os;
}
-
-ostream &operator<<(ostream &os, const Time &time)
-{
- os << time.toString();
- return os;
-}
-
-ostream &operator<<(ostream &os, const Version &ver)
-{
- os << ver.name().toString();
- return os;
-}
diff --git a/test/helper.hpp b/test/helper.hpp
@@ -3,12 +3,8 @@
class Path;
class Time;
-class Version;
-std::ostream &operator<<(std::ostream &, const Path &);
std::ostream &operator<<(std::ostream &, const std::set<Path> &);
-std::ostream &operator<<(std::ostream &, const Time &);
-std::ostream &operator<<(std::ostream &, const Version &);
// include Catch only after having declared our ostream overloads
#include <catch.hpp>
diff --git a/test/ostream.cpp b/test/ostream.cpp
@@ -1,59 +0,0 @@
-#include "helper.hpp"
-
-#include <ostream.hpp>
-
-#include <version.hpp>
-
-static const char *M = "[ostream]";
-
-TEST_CASE("test number formatting", M) {
- OutputStream stream;
- stream << 1234;
- REQUIRE(stream.str() == "1,234");
-}
-
-TEST_CASE("test indent string", M) {
- OutputStream stream;
-
- SECTION("simple")
- stream.indented("line1\nline2");
-
- SECTION("already indented")
- stream.indented(" line1\n line2");
-
- REQUIRE(stream.str() == " line1\r\n line2\r\n");
-}
-
-TEST_CASE("output version", M) {
- OutputStream stream;
- Version ver("1.2.3", nullptr);
-
- SECTION("empty version") {
- stream << ver;
- REQUIRE(stream.str() == "v1.2.3\r\n No changelog\r\n");
- }
-
- SECTION("with author") {
- ver.setAuthor("Hello World");
- stream << ver;
- REQUIRE(stream.str() == "v1.2.3 by Hello World\r\n No changelog\r\n");
- }
-
- SECTION("with time") {
- ver.setTime("2016-01-02T00:42:11Z");
- stream << ver;
- REQUIRE(stream.str() == "v1.2.3 – January 02 2016\r\n No changelog\r\n");
- }
-
- SECTION("with changelog") {
- ver.setChangelog("+ added super cool feature\n+ fixed all the bugs!");
- stream << ver;
- REQUIRE(stream.str() == "v1.2.3\r\n + added super cool feature\r\n + fixed all the bugs!\r\n");
- }
-
- SECTION("changelog with empty lines") {
- ver.setChangelog("line1\n\nline2");
- stream << ver; // no crash!
- REQUIRE(stream.str() == "v1.2.3\r\n line1\r\n\r\n line2\r\n");
- }
-}
diff --git a/test/receipt.cpp b/test/receipt.cpp
@@ -4,31 +4,23 @@
#include <index.hpp>
+using Catch::Matchers::Contains;
+
using namespace std;
static constexpr const char *M = "[receipt]";
-#define MAKE_VERSION \
- IndexPtr ri = make_shared<Index>("Index Name"); \
- Category cat("Category Name", ri.get()); \
- Package pkg(Package::ScriptType, "Package Name", &cat); \
- Version ver("1.0", &pkg);
-
-TEST_CASE("set isRestartNeeded", M) {
- Receipt r;
- REQUIRE_FALSE(r.isRestartNeeded());
- r.setRestartNeeded(true);
- REQUIRE(r.isRestartNeeded());
-}
-
TEST_CASE("non-empty receipt", M) {
- MAKE_VERSION;
-
Receipt r;
REQUIRE(r.empty());
- SECTION("install")
- r.addInstall({&ver, {}});
+ SECTION("install") {
+ IndexPtr ri = make_shared<Index>("Index Name");
+ Category cat("Category Name", ri.get());
+ Package pkg(Package::ScriptType, "Package Name", &cat);
+ Version ver("1.0", &pkg);
+ r.addInstall(&ver, {});
+ }
SECTION("removal")
r.addRemoval(Path("hello/world"));
@@ -42,35 +34,99 @@ TEST_CASE("non-empty receipt", M) {
REQUIRE_FALSE(r.empty());
}
-TEST_CASE("install scratch or downgrade", M) {
- MAKE_VERSION;
+TEST_CASE("set RestartNeeded flag", M) {
+ IndexPtr ri = make_shared<Index>("Index Name");
+ Category cat("Category Name", ri.get());
+ Package script(Package::ScriptType, "Package Name", &cat);
+ Package ext(Package::ExtensionType, "Package Name", &cat);
+ Version scriptVer("1.0", &script);
+ Version extVer("1.0", &ext);
+
Receipt r;
+ REQUIRE_FALSE(r.test(Receipt::RestartNeeded));
- REQUIRE(r.installs().empty());
+ r.addInstall(&scriptVer, {});
+ REQUIRE_FALSE(r.test(Receipt::RestartNeeded));
- SECTION("install from stratch")
- r.addInstall({&ver, {}});
+ r.addInstall(&extVer, {});
+ REQUIRE(r.test(Receipt::RestartNeeded));
+}
- SECTION("downgrade") {
- Registry::Entry entry{1};
- entry.version = VersionName("2.0");
- r.addInstall({&ver, entry});
+TEST_CASE("format receipt page title", M) {
+ SECTION("singular") {
+ ReceiptPage page{vector<int>{1}, "Singular", "Plural"};
+ REQUIRE(page.title() == "Singular (1)");
+ }
+
+ SECTION("plural") {
+ ReceiptPage page{vector<int>{1, 2, 3}, "Singular", "Plural"};
+ REQUIRE(page.title() == "Plural (3)");
+ }
+
+ SECTION("zero is plural") {
+ ReceiptPage page{vector<int>{}, "Singular", "Plural"};
+ REQUIRE(page.title() == "Plural (0)");
+ }
+
+ SECTION("no plural") {
+ ReceiptPage page{vector<int>{1, 2, 3}, "Fallback"};
+ REQUIRE(page.title() == "Fallback (3)");
}
- REQUIRE(r.installs().size() == 1);
- REQUIRE(r.updates().empty());
+ SECTION("thousand divider") {
+ ReceiptPage page{vector<int>(42'000, 42), "Singular", "Plural"};
+ REQUIRE(page.title() == "Plural (42,000)");
+ }
}
-TEST_CASE("detect update tickets", M) {
- MAKE_VERSION;
+TEST_CASE("format install ticket") {
+ IndexPtr ri = make_shared<Index>("Index Name");
+ Category cat("Category Name", ri.get());
+ Package pkg(Package::ScriptType, "Package Name", &cat);
- Receipt r;
- REQUIRE(r.updates().empty());
+ Version *v1 = new Version("1.0", &pkg);
+ v1->addSource(new Source({}, "https://google.com", v1));
+ pkg.addVersion(v1);
+
+ Version *v2 = new Version("2.0", &pkg);
+ v2->addSource(new Source({}, "https://google.com", v2));
+ pkg.addVersion(v2);
+ Version *v3 = new Version("3.0", &pkg);
+ v3->addSource(new Source({}, "https://google.com", v3));
+ pkg.addVersion(v3);
+
+ ostringstream stream;
Registry::Entry entry{1};
- entry.version = VersionName("0.9");
- r.addInstall({&ver, entry});
- REQUIRE(r.updates().size() == 1);
- REQUIRE(r.installs().empty());
+ SECTION("contains fullname") {
+ stream << InstallTicket{v3, {}};
+ REQUIRE_THAT(stream.str(), Contains(pkg.fullName()));
+ }
+
+ SECTION("prepend newline if stream nonempty") {
+ stream << "something";
+ stream << InstallTicket{v3, {}};
+ REQUIRE_THAT(stream.str(), Contains("something\r\n"));
+ }
+
+ SECTION("installed from scratch") {
+ stream << InstallTicket{v2, {}};
+ REQUIRE_THAT(stream.str(),
+ !Contains("v1.0") && Contains("v2.0") && !Contains("v3.0"));
+ }
+
+ SECTION("update") {
+ entry.version = VersionName("1.0");
+ stream << InstallTicket{v3, entry};
+ REQUIRE_THAT(stream.str(),
+ !Contains("v1.0") && Contains("v2.0") && Contains("v3.0"));
+ }
+
+ SECTION("downgrade") {
+ entry.version = VersionName("3.0");
+ stream << InstallTicket{v1, entry};
+ REQUIRE_THAT(stream.str(),
+ Contains("v1.0") && !Contains("v2.0") && !Contains("v3.0"));
+ }
}
diff --git a/test/string.cpp b/test/string.cpp
@@ -11,3 +11,15 @@ TEST_CASE("string format", M) {
CHECK(formatted.size() == 17);
REQUIRE(formatted == "100% Hello World!");
}
+
+TEST_CASE("indent string", M) {
+ string actual;
+
+ SECTION("simple")
+ actual = String::indent("line1\nline2");
+
+ SECTION("already indented")
+ actual = String::indent(" line1\n line2");
+
+ REQUIRE(actual == " line1\r\n line2");
+}
diff --git a/test/version.cpp b/test/version.cpp
@@ -292,3 +292,37 @@ TEST_CASE("version date", M) {
ver.setTime("hello world");
REQUIRE(ver.time().year() == 2016);
}
+
+TEST_CASE("output version", M) {
+ ostringstream stream;
+ Version ver("1.2.3", nullptr);
+
+ SECTION("empty version") {
+ stream << ver;
+ REQUIRE(stream.str() == "v1.2.3\r\n No changelog");
+ }
+
+ SECTION("with author") {
+ ver.setAuthor("Hello World");
+ stream << ver;
+ REQUIRE(stream.str() == "v1.2.3 by Hello World\r\n No changelog");
+ }
+
+ SECTION("with time") {
+ ver.setTime("2016-01-02T00:42:11Z");
+ stream << ver;
+ REQUIRE(stream.str() == "v1.2.3 – January 02 2016\r\n No changelog");
+ }
+
+ SECTION("with changelog") {
+ ver.setChangelog("+ added super cool feature\n+ fixed all the bugs!");
+ stream << ver;
+ REQUIRE(stream.str() == "v1.2.3\r\n + added super cool feature\r\n + fixed all the bugs!");
+ }
+
+ SECTION("changelog with empty lines") {
+ ver.setChangelog("line1\n\nline2");
+ stream << ver; // no crash!
+ REQUIRE(stream.str() == "v1.2.3\r\n line1\r\n\r\n line2");
+ }
+}