Files
Flora/tests/vaultindex_test.cpp
grendervill 417d0273b7 Инкрементальный индекс, пресеты автосейва, диалог Настройки
- VaultIndex: updateFile/removeFile/syncIncremental (mtime+size), полный рескан только на старте/смене/внешних изменениях; relink висячих [[ссылок]] при создании файла; FileStore.externalChanged для внешних правок
- QML: сейвы и правки идут точечно, дерево — через syncIncremental
- Настройки: пресеты safe/balanced/eco/manual (диск 2/5/10с/выкл, коммит после паузы 5/15/60с/вручную), диалог с +/-, шкалами и подсказкой; коммит только в простое, в manual — по Ctrl+S
- Удалены highlightedCode, словный merge, fileDiffText/Html, 2-arg fileHistory; renderInner без побочек
- Тесты: инкремент индекса, пресеты, autosaveEnabled, ~~~-блоки
2026-09-06 23:02:05 +03:00

161 lines
6.5 KiB
C++

// Тест VaultIndex (Фаза 7): теги, [[ссылки]], md-ссылки, граф, списки файлов по тегу.
#include <QCoreApplication>
#include <QTemporaryDir>
#include <QDir>
#include <QFile>
#include <cstdio>
#include "core/VaultIndex.h"
static int fail(const char *msg, const QString &detail = QString())
{
std::fprintf(stderr, "FAIL: %s | %s\n", msg, qPrintable(detail));
return 1;
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QTemporaryDir dir;
if (!dir.isValid())
return fail("temp dir");
QDir(dir.path()).mkpath("sub");
auto put = [&](const QString &rel, const QString &body) {
QFile f(dir.path() + "/" + rel);
f.open(QIODevice::WriteOnly);
f.write(body.toUtf8());
f.close();
};
put("a.md", "# Заголовок #тег1\n\nСсылка на [[b]] и [[b]].\nТег ещё #тег2 и #тег1.\n");
put("sub/b.md", "# B\n\nпростой текст #тег2\n");
put("c.md", "[Перейти на b](sub/b.md)\n\n```\n# не тег\nint x;\n```\n");
VaultIndex idx;
idx.rebuild(dir.path());
if (idx.fileCount() != 3)
return fail("fileCount", QString::number(idx.fileCount()));
const QString a = dir.path() + "/a.md";
const QString b = dir.path() + "/sub/b.md";
const QString c = dir.path() + "/c.md";
// Теги файла a: тег1, тег2 (заголовок "# Заголовок" не тег)
QStringList tagsA = idx.tagsOfFile(a);
if (!tagsA.contains("тег1") || !tagsA.contains("тег2") || tagsA.size() != 2)
return fail("tagsOfFile(a)", tagsA.join(","));
// Ссылки a → b (вики)
auto linksA = idx.linksOfFile(a);
if (linksA.isEmpty())
return fail("linksOfFile(a) пусто");
bool foundB = false;
for (const auto &l : linksA) {
if (l.toMap().value("path").toString() == b)
foundB = true;
}
if (!foundB)
return fail("вики-ссылка на b не разрешена");
// c → b по md-ссылке
auto linksC = idx.linksOfFile(c);
foundB = false;
for (const auto &l : linksC)
if (l.toMap().value("path").toString() == b)
foundB = true;
if (!foundB)
return fail("md-ссылка на b не разрешена", QString());
// Граф: узлы-файлы и рёбра; тег как узел
bool haveTagNode = false;
for (const auto &n : idx.nodes()) {
if (n.toMap().value("type").toString() == "tag")
haveTagNode = true;
}
if (!haveTagNode)
return fail("нет узла-тега в графе");
if (idx.edges().isEmpty())
return fail("нет рёбер в графе");
// filesWithTag
QStringList files1 = idx.filesWithTag("тег1");
if (files1.size() != 1 || files1.first() != a)
return fail("filesWithTag(тег1)", files1.join(","));
QStringList files2 = idx.filesWithTag("тег2");
if (files2.size() != 2)
return fail("filesWithTag(тег2)", QString::number(files2.size()));
// --- инкремент: updateFile меняет только один файл (убираем теги из a.md) ---
put("a.md", "# Заголовок\n\nСсылка на [[b]].\n");
idx.updateFile(a);
if (idx.fileCount() != 3)
return fail("updateFile fileCount", QString::number(idx.fileCount()));
// a.md теперь без тегов — тег1 остался только... нигде: filesWithTag пуст
if (!idx.filesWithTag("тег1").isEmpty())
return fail("updateFile снял тег1", idx.filesWithTag("тег1").join(","));
if (idx.tagsOfFile(a).size() != 0)
return fail("updateFile tagsOfFile", idx.tagsOfFile(a).join(","));
// b.md не трогали — его теги на месте
if (!idx.tagsOfFile(b).contains("тег2"))
return fail("updateFile не тронул b");
// --- инкремент: новый файл чинит висячую ссылку (relink) ---
put("d.md", "Ссылка на [[future]].\n");
const QString d = dir.path() + "/d.md";
idx.updateFile(d);
if (idx.fileCount() != 4)
return fail("updateFile new fileCount", QString::number(idx.fileCount()));
if (!idx.linksOfFile(d).isEmpty())
return fail("висячая ссылка должна быть пустой");
put("sub/future.md", "# Future\n");
const QString future = dir.path() + "/sub/future.md";
idx.updateFile(future);
{
bool foundFuture = false;
for (const auto &l : idx.linksOfFile(d))
if (l.toMap().value("path").toString() == future)
foundFuture = true;
if (!foundFuture)
return fail("relink не починил [[future]]");
}
// --- инкремент: removeFile чистит теги/ссылки/узел ---
// (в реальности вызывается потому что файл удалён — удаляем и с диска)
QFile::remove(future);
idx.removeFile(future);
if (idx.fileCount() != 4)
return fail("removeFile fileCount", QString::number(idx.fileCount()));
{
bool foundFuture = false;
for (const auto &l : idx.linksOfFile(d))
if (l.toMap().value("path").toString() == future)
foundFuture = true;
if (foundFuture)
return fail("removeFile не убрал входящую ссылку");
}
// removeFile несуществующего — тихо, без падения
idx.removeFile(dir.path() + "/нет-такого.md");
idx.updateFile(dir.path() + "/нет-такого.md");
// --- инкремент: syncIncremental догоняет внешние изменения ---
put("ext.md", "Внешний #внешний\n");
const QString ext = dir.path() + "/ext.md";
if (idx.fileCount() != 4)
return fail("до sync файл не виден", QString::number(idx.fileCount()));
idx.syncIncremental();
if (idx.fileCount() != 5)
return fail("sync подхватил файл", QString::number(idx.fileCount()));
if (!idx.tagsOfFile(ext).contains("внешний"))
return fail("sync теги");
QFile::remove(ext);
idx.syncIncremental();
if (idx.fileCount() != 4)
return fail("sync убрал файл", QString::number(idx.fileCount()));
if (idx.tagsOfFile(ext).size() != 0)
return fail("sync теги удалены");
std::printf("VAULTINDEX TEST OK\n");
return 0;
}