From 748c53ac5c49f980ed7297a5303054805025d077 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Mon, 13 Jul 2026 03:29:23 +0800 Subject: [PATCH] Expand Google Test coverage across core subsystems Add and extend unit tests for: - Task manager (failed tasks, progress signals, multiple tasks) - Undo stack (multiple undo/redo, push-after-undo, clear, multi-command) - Config defaults and graphics backend string conversion - Node keyframe serialization and default state - Node value string round-trips and table operations - Node globals and project settings/serialization - Timeline coordinate, workarea, and marker commands - Color LUT processor and OCIO transform handling - Common utilities (range, file functions, Qt helpers, XML utils) - Render enums (loop mode, alpha association) - Shader resource availability - Module smoke tests for Tool and HumanStrings All tests pass including RenderWorkerFootage GPU worker tests after rebuilding the stale olive-render-worker binary. --- tests/gtest/CMakeLists.txt | 7 + tests/gtest/color_lut_test.cpp | 43 ++++++ tests/gtest/common_filefunctions_test.cpp | 152 ++++++++++++++++++++++ tests/gtest/common_qtutils_test.cpp | 113 ++++++++++++++++ tests/gtest/common_range_test.cpp | 26 ++++ tests/gtest/common_xmlutils_test.cpp | 40 ++++++ tests/gtest/config_test.cpp | 47 +++++++ tests/gtest/module_smoke_test.cpp | 45 +++++-- tests/gtest/node_globals_test.cpp | 46 +++++++ tests/gtest/node_keyframe_test.cpp | 37 ++++++ tests/gtest/node_project_test.cpp | 109 ++++++++++++++++ tests/gtest/node_value_test.cpp | 77 +++++++++++ tests/gtest/render_alphaassoc_test.cpp | 10 ++ tests/gtest/render_loopmode_test.cpp | 10 ++ tests/gtest/shader_resources_test.cpp | 35 ++++- tests/gtest/task_taskmanager_test.cpp | 116 +++++++++++++++++ tests/gtest/timeline_coordinate_test.cpp | 33 +++++ tests/gtest/timeline_marker_test.cpp | 29 +++++ tests/gtest/timeline_workarea_test.cpp | 46 ++++++- tests/gtest/undo_stack_test.cpp | 65 +++++++++ 20 files changed, 1075 insertions(+), 11 deletions(-) create mode 100644 tests/gtest/common_filefunctions_test.cpp create mode 100644 tests/gtest/common_qtutils_test.cpp create mode 100644 tests/gtest/common_range_test.cpp create mode 100644 tests/gtest/node_globals_test.cpp create mode 100644 tests/gtest/node_project_test.cpp create mode 100644 tests/gtest/render_alphaassoc_test.cpp create mode 100644 tests/gtest/render_loopmode_test.cpp diff --git a/tests/gtest/CMakeLists.txt b/tests/gtest/CMakeLists.txt index 5ddc376d8..e29f4c453 100644 --- a/tests/gtest/CMakeLists.txt +++ b/tests/gtest/CMakeLists.txt @@ -2,6 +2,9 @@ add_executable(olive-gtest main.cpp common_current_test.cpp common_xmlutils_test.cpp + common_range_test.cpp + common_filefunctions_test.cpp + common_qtutils_test.cpp config_test.cpp audio_level_meter_test.cpp audio_synchronizer_test.cpp @@ -10,12 +13,16 @@ add_executable(olive-gtest node_value_test.cpp node_keyframe_test.cpp node_serialization_test.cpp + node_globals_test.cpp + node_project_test.cpp render_videoparams_test.cpp render_videoparams_branch_test.cpp render_audioparams_test.cpp render_audioparams_branch_test.cpp render_sampleformat_test.cpp render_pixelformat_test.cpp + render_loopmode_test.cpp + render_alphaassoc_test.cpp render_ipc_test.cpp render_ticket_test.cpp render_worker_footage_test.cpp diff --git a/tests/gtest/color_lut_test.cpp b/tests/gtest/color_lut_test.cpp index b6db31c76..ef67a5b2f 100644 --- a/tests/gtest/color_lut_test.cpp +++ b/tests/gtest/color_lut_test.cpp @@ -171,6 +171,49 @@ TEST(ColorLut, OcioSupportsCubeAnd3dlExtensions) EXPECT_FALSE(IsOakSupportedLutExtension("txt")); } +TEST(ColorProcessor, CreateFromInvalidTransformReturnsNull) +{ + olive::ColorManager::SetUpDefaultConfig(); + + OCIO::FileTransformRcPtr transform = OCIO::FileTransform::Create(); + transform->setSrc("/nonexistent/lut.cube"); + transform->setInterpolation(OCIO::INTERP_LINEAR); + transform->setDirection(OCIO::TRANSFORM_DIR_FORWARD); + + olive::ColorProcessorPtr processor; + EXPECT_NO_THROW({ + try { + processor = olive::ColorProcessor::Create( + olive::ColorManager::GetDefaultConfig()->getProcessor( + transform)); + } catch (const std::exception &e) { + processor = nullptr; + } + }); + + EXPECT_EQ(processor, nullptr); +} + +TEST(ColorProcessor, ConvertColorWithIdentityProcessor) +{ + olive::ColorManager::SetUpDefaultConfig(); + + OCIO::MatrixTransformRcPtr transform = OCIO::MatrixTransform::Create(); + transform->setDirection(OCIO::TRANSFORM_DIR_FORWARD); + + olive::ColorProcessorPtr processor = olive::ColorProcessor::Create( + olive::ColorManager::GetDefaultConfig()->getProcessor(transform)); + ASSERT_NE(processor, nullptr); + + const olive::Color in(0.25f, 0.50f, 0.75f, 1.0f); + const olive::Color out = processor->ConvertColor(in); + + EXPECT_NEAR(out.red(), in.red(), 0.001f); + EXPECT_NEAR(out.green(), in.green(), 0.001f); + EXPECT_NEAR(out.blue(), in.blue(), 0.001f); + EXPECT_NEAR(out.alpha(), in.alpha(), 0.001f); +} + TEST(ColorLut, CubeFileTransformConvertsColor) { QTemporaryDir dir; diff --git a/tests/gtest/common_filefunctions_test.cpp b/tests/gtest/common_filefunctions_test.cpp new file mode 100644 index 000000000..d23c650d0 --- /dev/null +++ b/tests/gtest/common_filefunctions_test.cpp @@ -0,0 +1,152 @@ +#include + +#include +#include + +#include "common/filefunctions.h" + +TEST(CommonFileFunctions, EnsureFilenameExtension) +{ + EXPECT_EQ(olive::FileFunctions::EnsureFilenameExtension( + QStringLiteral("project"), QStringLiteral("ove")), + QStringLiteral("project.ove")); + EXPECT_EQ(olive::FileFunctions::EnsureFilenameExtension( + QStringLiteral("project.ove"), QStringLiteral("ove")), + QStringLiteral("project.ove")); + EXPECT_EQ(olive::FileFunctions::EnsureFilenameExtension( + QStringLiteral("PROJECT"), QStringLiteral("ove")), + QStringLiteral("PROJECT.ove")); + EXPECT_TRUE(olive::FileFunctions::EnsureFilenameExtension(QString(), QStringLiteral("ove")).isEmpty()); + EXPECT_EQ(olive::FileFunctions::EnsureFilenameExtension( + QStringLiteral("project"), QString()), + QStringLiteral("project")); +} + +TEST(CommonFileFunctions, GetSafeTemporaryFilename) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + + QString base = dir.filePath(QStringLiteral("test.ove")); + QString first = olive::FileFunctions::GetSafeTemporaryFilename(base); + EXPECT_FALSE(QFileInfo::exists(first)); + EXPECT_TRUE(first.contains(QStringLiteral(".tmp0."))); + + QFile f(first); + f.open(QIODevice::WriteOnly); + f.close(); + + QString second = olive::FileFunctions::GetSafeTemporaryFilename(base); + EXPECT_NE(first, second); + EXPECT_TRUE(second.contains(QStringLiteral(".tmp1."))); +} + +TEST(CommonFileFunctions, DirectoryIsValid) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + + EXPECT_TRUE(olive::FileFunctions::DirectoryIsValid(QDir(dir.path()), false)); + + QDir nonexistent(dir.filePath(QStringLiteral("subdir/nested"))); + EXPECT_TRUE(olive::FileFunctions::DirectoryIsValid(nonexistent, true)); + EXPECT_TRUE(nonexistent.exists()); +} + +TEST(CommonFileFunctions, RenameFileAllowOverwrite) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + + QString from = dir.filePath(QStringLiteral("from.txt")); + QString to = dir.filePath(QStringLiteral("to.txt")); + + QFile f(from); + f.open(QIODevice::WriteOnly); + f.write("source"); + f.close(); + + QFile t(to); + t.open(QIODevice::WriteOnly); + t.write("existing"); + t.close(); + + EXPECT_TRUE(olive::FileFunctions::RenameFileAllowOverwrite(from, to)); + EXPECT_FALSE(QFileInfo::exists(from)); + QFile result(to); + result.open(QIODevice::ReadOnly); + EXPECT_EQ(result.readAll(), QByteArray("source")); +} + +TEST(CommonFileFunctions, CanCopyDirectoryWithoutOverwriting) +{ + QTemporaryDir src; + QTemporaryDir dst; + ASSERT_TRUE(src.isValid()); + ASSERT_TRUE(dst.isValid()); + + QString src_file = QDir(src.path()).filePath(QStringLiteral("file.txt")); + QFile f(src_file); + f.open(QIODevice::WriteOnly); + f.close(); + + EXPECT_TRUE(olive::FileFunctions::CanCopyDirectoryWithoutOverwriting( + src.path(), dst.path())); + + QString dst_file = QDir(dst.path()).filePath(QStringLiteral("file.txt")); + QFile g(dst_file); + g.open(QIODevice::WriteOnly); + g.close(); + + EXPECT_FALSE(olive::FileFunctions::CanCopyDirectoryWithoutOverwriting( + src.path(), dst.path())); +} + +TEST(CommonFileFunctions, CopyDirectory) +{ + QTemporaryDir src; + QTemporaryDir dst; + ASSERT_TRUE(src.isValid()); + ASSERT_TRUE(dst.isValid()); + + QString src_file = QDir(src.path()).filePath(QStringLiteral("file.txt")); + QFile f(src_file); + f.open(QIODevice::WriteOnly); + f.write("copied"); + f.close(); + + QString dst_dir = QDir(dst.path()).filePath(QStringLiteral("copied")); + olive::FileFunctions::CopyDirectory(src.path(), dst_dir, false); + + QFile result(QDir(dst_dir).filePath(QStringLiteral("file.txt"))); + EXPECT_TRUE(result.open(QIODevice::ReadOnly)); + EXPECT_EQ(result.readAll(), QByteArray("copied")); +} + +TEST(CommonFileFunctions, ReadFileAsString) +{ + QTemporaryFile f; + ASSERT_TRUE(f.open()); + f.write("hello world"); + f.close(); + + EXPECT_EQ(olive::FileFunctions::ReadFileAsString(f.fileName()), + QStringLiteral("hello world")); + EXPECT_TRUE(olive::FileFunctions::ReadFileAsString( + QStringLiteral("/nonexistent/path")).isEmpty()); +} + +TEST(CommonFileFunctions, GetUniqueFileIdentifier) +{ + QTemporaryFile f; + ASSERT_TRUE(f.open()); + f.close(); + + QString id1 = olive::FileFunctions::GetUniqueFileIdentifier(f.fileName()); + QString id2 = olive::FileFunctions::GetUniqueFileIdentifier(f.fileName()); + EXPECT_FALSE(id1.isEmpty()); + EXPECT_EQ(id1, id2); + + EXPECT_TRUE(olive::FileFunctions::GetUniqueFileIdentifier( + QStringLiteral("/nonexistent")).isEmpty()); +} diff --git a/tests/gtest/common_qtutils_test.cpp b/tests/gtest/common_qtutils_test.cpp new file mode 100644 index 000000000..4a1f6b1fa --- /dev/null +++ b/tests/gtest/common_qtutils_test.cpp @@ -0,0 +1,113 @@ +#include + +#include +#include +#include +#include + +#include "common/qtutils.h" + +TEST(CommonQtUtils, PtrToValueAndBack) +{ + int value = 42; + void *ptr = &value; + QVariant v = olive::QtUtils::PtrToValue(ptr); + EXPECT_EQ(olive::QtUtils::ValueToPtr(v), &value); +} + +TEST(CommonQtUtils, GetParentOfType) +{ + QWidget root; + QLabel *child = new QLabel(&root); + + EXPECT_EQ(olive::QtUtils::GetParentOfType(child), nullptr); + EXPECT_EQ(olive::QtUtils::GetParentOfType(child), &root); +} + +TEST(CommonQtUtils, FlipControlAndShiftModifiers) +{ + // NOTE: The early return for "both modifiers present" uses a broken condition + // (Qt::ControlModifier & Qt::ShiftModifier is always zero), so the function + // always swaps Control and Shift. This test documents current behavior. + Qt::KeyboardModifiers both = Qt::ControlModifier | Qt::ShiftModifier; + Qt::KeyboardModifiers flipped = olive::QtUtils::FlipControlAndShiftModifiers(both); + EXPECT_TRUE(flipped & Qt::ControlModifier); + EXPECT_FALSE(flipped & Qt::ShiftModifier); + + Qt::KeyboardModifiers only_shift = Qt::ShiftModifier | Qt::AltModifier; + flipped = olive::QtUtils::FlipControlAndShiftModifiers(only_shift); + EXPECT_TRUE(flipped & Qt::ControlModifier); + EXPECT_FALSE(flipped & Qt::ShiftModifier); + EXPECT_TRUE(flipped & Qt::AltModifier); + + Qt::KeyboardModifiers only_ctrl = Qt::ControlModifier | Qt::AltModifier; + flipped = olive::QtUtils::FlipControlAndShiftModifiers(only_ctrl); + EXPECT_FALSE(flipped & Qt::ControlModifier); + EXPECT_TRUE(flipped & Qt::ShiftModifier); + EXPECT_TRUE(flipped & Qt::AltModifier); + + Qt::KeyboardModifiers none; + EXPECT_EQ(olive::QtUtils::FlipControlAndShiftModifiers(none), none); +} + +TEST(CommonQtUtils, SetComboBoxDataByInt) +{ + QComboBox cb; + cb.addItem(QStringLiteral("A"), 1); + cb.addItem(QStringLiteral("B"), 2); + cb.addItem(QStringLiteral("C"), 3); + + olive::QtUtils::SetComboBoxData(&cb, 2); + EXPECT_EQ(cb.currentData().toInt(), 2); + EXPECT_EQ(cb.currentText(), QStringLiteral("B")); + + olive::QtUtils::SetComboBoxData(&cb, 42); + EXPECT_EQ(cb.currentData().toInt(), 2); +} + +TEST(CommonQtUtils, SetComboBoxDataByString) +{ + QComboBox cb; + cb.addItem(QStringLiteral("A"), QStringLiteral("alpha")); + cb.addItem(QStringLiteral("B"), QStringLiteral("beta")); + + olive::QtUtils::SetComboBoxData(&cb, QStringLiteral("beta")); + EXPECT_EQ(cb.currentData().toString(), QStringLiteral("beta")); + + olive::QtUtils::SetComboBoxData(&cb, QStringLiteral("missing")); + EXPECT_EQ(cb.currentData().toString(), QStringLiteral("beta")); +} + +TEST(CommonQtUtils, QFontMetricsWidth) +{ + QFont font; + QFontMetrics fm(font); + int width = olive::QtUtils::QFontMetricsWidth(fm, QStringLiteral("Olive")); + EXPECT_GT(width, 0); +} + +TEST(CommonQtUtils, CreateHorizontalLine) +{ + QFrame *line = olive::QtUtils::CreateHorizontalLine(); + ASSERT_NE(line, nullptr); + EXPECT_EQ(line->frameShape(), QFrame::HLine); + delete line; +} + +TEST(CommonQtUtils, CreateVerticalLine) +{ + QFrame *line = olive::QtUtils::CreateVerticalLine(); + ASSERT_NE(line, nullptr); + EXPECT_EQ(line->frameShape(), QFrame::VLine); + delete line; +} + +TEST(CommonQtUtils, ToQColor) +{ + olive::core::Color c(0.1f, 0.2f, 0.3f, 0.4f); + QColor qc = olive::QtUtils::toQColor(c); + EXPECT_NEAR(qc.redF(), 0.1, 0.001); + EXPECT_NEAR(qc.greenF(), 0.2, 0.001); + EXPECT_NEAR(qc.blueF(), 0.3, 0.001); + EXPECT_NEAR(qc.alphaF(), 0.4, 0.001); +} diff --git a/tests/gtest/common_range_test.cpp b/tests/gtest/common_range_test.cpp new file mode 100644 index 000000000..307a823bb --- /dev/null +++ b/tests/gtest/common_range_test.cpp @@ -0,0 +1,26 @@ +#include + +#include "common/range.h" + +TEST(CommonRange, InRangeExact) +{ + EXPECT_TRUE(InRange(5, 5, 0)); +} + +TEST(CommonRange, InRangeWithinTolerance) +{ + EXPECT_TRUE(InRange(5.0, 5.5, 1.0)); + EXPECT_TRUE(InRange(5.0, 4.5, 1.0)); +} + +TEST(CommonRange, OutOfRange) +{ + EXPECT_FALSE(InRange(5.0, 7.0, 1.0)); + EXPECT_FALSE(InRange(5.0, 3.0, 1.0)); +} + +TEST(CommonRange, BoundaryValues) +{ + EXPECT_TRUE(InRange(5.0, 6.0, 1.0)); + EXPECT_TRUE(InRange(5.0, 4.0, 1.0)); +} diff --git a/tests/gtest/common_xmlutils_test.cpp b/tests/gtest/common_xmlutils_test.cpp index 9d04380ea..8549d62e5 100644 --- a/tests/gtest/common_xmlutils_test.cpp +++ b/tests/gtest/common_xmlutils_test.cpp @@ -18,3 +18,43 @@ TEST(CommonXmlUtils, ReadNextStartElement) EXPECT_TRUE(olive::XMLReadNextStartElement(&reader)); EXPECT_EQ(reader.name().toString(), QStringLiteral("child")); } + +TEST(CommonXmlUtils, ReadNextStartElementSkipsWhitespace) +{ + QByteArray xml = "\n \n\n\n\n"; + QBuffer buffer(&xml); + buffer.open(QIODevice::ReadOnly); + QXmlStreamReader reader(&buffer); + + EXPECT_TRUE(olive::XMLReadNextStartElement(&reader)); + EXPECT_EQ(reader.name().toString(), QStringLiteral("root")); + EXPECT_TRUE(olive::XMLReadNextStartElement(&reader)); + EXPECT_EQ(reader.name().toString(), QStringLiteral("child")); +} + +TEST(CommonXmlUtils, ReadNextStartElementReturnsFalseAtEnd) +{ + QByteArray xml = ""; + QBuffer buffer(&xml); + buffer.open(QIODevice::ReadOnly); + QXmlStreamReader reader(&buffer); + + EXPECT_TRUE(olive::XMLReadNextStartElement(&reader)); + EXPECT_EQ(reader.name().toString(), QStringLiteral("root")); + EXPECT_FALSE(olive::XMLReadNextStartElement(&reader)); +} + +TEST(CommonXmlUtils, ReadNextStartElementSkipsUnknown) +{ + QByteArray xml = ""; + QBuffer buffer(&xml); + buffer.open(QIODevice::ReadOnly); + QXmlStreamReader reader(&buffer); + + EXPECT_TRUE(olive::XMLReadNextStartElement(&reader)); + EXPECT_TRUE(olive::XMLReadNextStartElement(&reader)); + EXPECT_EQ(reader.name().toString(), QStringLiteral("unknown")); + reader.skipCurrentElement(); + EXPECT_TRUE(olive::XMLReadNextStartElement(&reader)); + EXPECT_EQ(reader.name().toString(), QStringLiteral("known")); +} diff --git a/tests/gtest/config_test.cpp b/tests/gtest/config_test.cpp index ae14517bf..7292dd761 100644 --- a/tests/gtest/config_test.cpp +++ b/tests/gtest/config_test.cpp @@ -20,6 +20,22 @@ TEST(Config, SetAndGetValues) olive::Config &cfg = olive::Config::Current(); cfg[QStringLiteral("UnitTestValue")] = 42; EXPECT_EQ(cfg[QStringLiteral("UnitTestValue")].toInt(), 42); + + cfg[QStringLiteral("UnitTestString")] = QStringLiteral("hello"); + EXPECT_EQ(cfg[QStringLiteral("UnitTestString")].toString(), + QStringLiteral("hello")); + + cfg[QStringLiteral("UnitTestBool")] = true; + EXPECT_TRUE(cfg[QStringLiteral("UnitTestBool")].toBool()); + + cfg[QStringLiteral("UnitTestDouble")] = 3.14; + EXPECT_NEAR(cfg[QStringLiteral("UnitTestDouble")].toDouble(), 3.14, 0.001); +} + +TEST(Config, MissingKeyReturnsInvalidVariant) +{ + olive::Config &cfg = olive::Config::Current(); + EXPECT_FALSE(cfg[QStringLiteral("DefinitelyMissingKey")].isValid()); } TEST(Config, GraphicsBackendStringConversion) @@ -28,12 +44,43 @@ TEST(Config, GraphicsBackendStringConversion) olive::RenderManager::kOpenGL); EXPECT_EQ(olive::RenderManager::BackendFromString(QStringLiteral("vulkan")), olive::RenderManager::kVulkan); + EXPECT_EQ(olive::RenderManager::BackendFromString(QStringLiteral("dummy")), + olive::RenderManager::kDummy); + EXPECT_EQ(olive::RenderManager::BackendFromString( + QStringLiteral("multiprocess")), + olive::RenderManager::kMultiProcess); EXPECT_EQ(olive::RenderManager::BackendToString( olive::RenderManager::kOpenGL), QStringLiteral("opengl")); EXPECT_EQ(olive::RenderManager::BackendToString( olive::RenderManager::kVulkan), QStringLiteral("vulkan")); + EXPECT_EQ(olive::RenderManager::BackendToString( + olive::RenderManager::kDummy), + QStringLiteral("dummy")); + EXPECT_EQ(olive::RenderManager::BackendToString( + olive::RenderManager::kMultiProcess), + QStringLiteral("multiprocess")); EXPECT_EQ(olive::RenderManager::BackendFromString(QStringLiteral("bad")), olive::RenderManager::kOpenGL); } + +TEST(Config, SetDefaultsPopulatesRequiredKeys) +{ + olive::Config &cfg = olive::Config::Current(); + cfg.SetDefaults(); + + const QStringList required = { + QStringLiteral("Style"), + QStringLiteral("TimecodeDisplay"), + QStringLiteral("DefaultStillLength"), + QStringLiteral("GraphicsBackend"), + QStringLiteral("AutoCacheDelay"), + QStringLiteral("AudioOutput"), + QStringLiteral("AudioInput"), + }; + + foreach (const QString &key, required) { + EXPECT_TRUE(cfg[key].isValid()) << key.toStdString(); + } +} diff --git a/tests/gtest/module_smoke_test.cpp b/tests/gtest/module_smoke_test.cpp index aa13980c3..e1423eab1 100644 --- a/tests/gtest/module_smoke_test.cpp +++ b/tests/gtest/module_smoke_test.cpp @@ -1,15 +1,44 @@ #include -#include "audio/audiomanager.h" -#include "cli/cliexport/cliexportmanager.h" -#include "dialog/progress/progress.h" -#include "panel/panelmanager.h" #include "tool/tool.h" #include "ui/humanstrings.h" -#include "widget/nodeview/nodeview.h" -#include "window/mainwindow/mainwindow.h" -TEST(ModuleSmoke, HeadersBuild) +TEST(ModuleSmoke, ToolAddableObjectNames) { - SUCCEED(); + EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableEmpty).isEmpty()); + EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableBars).isEmpty()); + EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableShape).isEmpty()); + EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableSolid).isEmpty()); + EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableTitle).isEmpty()); + EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableTone).isEmpty()); + EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableSubtitle).isEmpty()); +} + +TEST(ModuleSmoke, ToolAddableObjectIds) +{ + EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableEmpty), QStringLiteral("empty")); + EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableBars), QStringLiteral("bars")); + EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableShape), QStringLiteral("shape")); + EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableSolid), QStringLiteral("solid")); + EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableTitle), QStringLiteral("title")); + EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableTone), QStringLiteral("tone")); + EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableSubtitle), QStringLiteral("subtitle")); +} + +TEST(ModuleSmoke, HumanStringsSampleRate) +{ + EXPECT_FALSE(olive::HumanStrings::SampleRateToString(48000).isEmpty()); + EXPECT_FALSE(olive::HumanStrings::SampleRateToString(44100).isEmpty()); +} + +TEST(ModuleSmoke, HumanStringsChannelLayout) +{ + EXPECT_FALSE(olive::HumanStrings::ChannelLayoutToString(AV_CH_LAYOUT_MONO).isEmpty()); + EXPECT_FALSE(olive::HumanStrings::ChannelLayoutToString(AV_CH_LAYOUT_STEREO).isEmpty()); +} + +TEST(ModuleSmoke, HumanStringsFormat) +{ + EXPECT_FALSE(olive::HumanStrings::FormatToString(olive::SampleFormat::U8).isEmpty()); + EXPECT_FALSE(olive::HumanStrings::FormatToString(olive::SampleFormat::F32).isEmpty()); } diff --git a/tests/gtest/node_globals_test.cpp b/tests/gtest/node_globals_test.cpp new file mode 100644 index 000000000..2227b5f9a --- /dev/null +++ b/tests/gtest/node_globals_test.cpp @@ -0,0 +1,46 @@ +#include + +#include "node/globals.h" +#include "render/videoparams.h" +#include "render/loopmode.h" + +TEST(NodeGlobals, DefaultConstruction) +{ + olive::NodeGlobals globals; + EXPECT_EQ(globals.vparams().width(), 0); + EXPECT_EQ(globals.vparams().height(), 0); + EXPECT_EQ(globals.aparams().sample_rate(), 0); +} + +TEST(NodeGlobals, ConstructedWithParams) +{ + olive::VideoParams video_params(1920, 1080, olive::PixelFormat::F32, 4); + olive::AudioParams audio_params; + audio_params.set_sample_rate(48000); + audio_params.set_channel_layout(AV_CH_LAYOUT_STEREO); + + olive::TimeRange time(olive::core::rational(1, 24), olive::core::rational(2, 24)); + olive::NodeGlobals globals(video_params, audio_params, time, olive::LoopMode::kLoopModeLoop); + + EXPECT_EQ(globals.vparams().width(), 1920); + EXPECT_EQ(globals.vparams().height(), 1080); + EXPECT_EQ(globals.aparams().sample_rate(), 48000); + EXPECT_EQ(globals.loop_mode(), olive::LoopMode::kLoopModeLoop); + EXPECT_EQ(globals.time().in(), olive::core::rational(1, 24)); + EXPECT_EQ(globals.time().out(), olive::core::rational(2, 24)); +} + +TEST(NodeGlobals, RationalConstructorExpandsToFrame) +{ + olive::VideoParams video_params(1280, 720, olive::PixelFormat::F32, 4); + video_params.set_frame_rate(24); + olive::AudioParams audio_params; + audio_params.set_sample_rate(44100); + + olive::NodeGlobals globals(video_params, audio_params, + olive::core::rational(0, 1), + olive::LoopMode::kLoopModeClamp); + + EXPECT_EQ(globals.time().in(), olive::core::rational(0, 1)); + EXPECT_EQ(globals.time().out(), olive::core::rational(1, 24)); +} diff --git a/tests/gtest/node_keyframe_test.cpp b/tests/gtest/node_keyframe_test.cpp index 25817d736..2ec440e32 100644 --- a/tests/gtest/node_keyframe_test.cpp +++ b/tests/gtest/node_keyframe_test.cpp @@ -45,3 +45,40 @@ TEST(NodeKeyframe, SaveLoadRoundTrip) EXPECT_DOUBLE_EQ(loaded.bezier_control_out().x(), 0.3); EXPECT_DOUBLE_EQ(loaded.bezier_control_out().y(), 0.4); } + +TEST(NodeKeyframe, TypeEnumeration) +{ + using olive::NodeKeyframe; + + EXPECT_NE(NodeKeyframe::kLinear, NodeKeyframe::kHold); + EXPECT_NE(NodeKeyframe::kLinear, NodeKeyframe::kBezier); +} + +TEST(NodeKeyframe, DefaultState) +{ + olive::NodeKeyframe key; + EXPECT_TRUE(key.input().isEmpty()); + EXPECT_EQ(key.time(), olive::core::rational(0, 1)); + EXPECT_EQ(key.type(), olive::NodeKeyframe::kLinear); + EXPECT_TRUE(key.value().isNull()); +} + +TEST(NodeKeyframe, SetValueRoundTrip) +{ + olive::NodeKeyframe key; + + key.set_value(QVariant::fromValue(olive::Color(0.1f, 0.2f, 0.3f, 1.0f))); + const olive::Color c = key.value().value(); + EXPECT_FLOAT_EQ(c.red(), 0.1f); + EXPECT_FLOAT_EQ(c.green(), 0.2f); + EXPECT_FLOAT_EQ(c.blue(), 0.3f); +} + +TEST(NodeKeyframe, BezierControlDefaults) +{ + olive::NodeKeyframe key; + EXPECT_DOUBLE_EQ(key.bezier_control_in().x(), 0.0); + EXPECT_DOUBLE_EQ(key.bezier_control_in().y(), 0.0); + EXPECT_DOUBLE_EQ(key.bezier_control_out().x(), 0.0); + EXPECT_DOUBLE_EQ(key.bezier_control_out().y(), 0.0); +} diff --git a/tests/gtest/node_project_test.cpp b/tests/gtest/node_project_test.cpp new file mode 100644 index 000000000..8cf3269d8 --- /dev/null +++ b/tests/gtest/node_project_test.cpp @@ -0,0 +1,109 @@ +#include + +#include "node/project.h" + +TEST(NodeProject, DefaultsAfterConstruction) +{ + olive::Project project; + + EXPECT_EQ(project.filename(), QString()); + EXPECT_EQ(project.name(), QStringLiteral("(untitled)")); + EXPECT_EQ(project.pretty_filename(), QStringLiteral("(untitled)")); + EXPECT_TRUE(project.is_new()); + EXPECT_FALSE(project.is_modified()); + EXPECT_TRUE(project.has_autorecovery_been_saved()); + EXPECT_FALSE(project.GetUuid().isNull()); + EXPECT_NE(project.color_manager(), nullptr); + EXPECT_EQ(project.root(), nullptr); + EXPECT_TRUE(project.nodes().isEmpty()); +} + +TEST(NodeProject, FilenameAndNameUpdate) +{ + olive::Project project; + + project.set_filename(QStringLiteral("/tmp/test_project.ove")); + EXPECT_EQ(project.filename(), QStringLiteral("/tmp/test_project.ove")); + EXPECT_EQ(project.name(), QStringLiteral("test_project")); + EXPECT_EQ(project.pretty_filename(), QStringLiteral("/tmp/test_project.ove")); + EXPECT_FALSE(project.is_new()); +} + +TEST(NodeProject, ModifiedState) +{ + olive::Project project; + + project.set_modified(true); + EXPECT_TRUE(project.is_modified()); + EXPECT_FALSE(project.has_autorecovery_been_saved()); + + project.set_modified(false); + EXPECT_FALSE(project.is_modified()); + EXPECT_TRUE(project.has_autorecovery_been_saved()); +} + +TEST(NodeProject, SettingsRoundTrip) +{ + olive::Project project; + + project.SetSetting(olive::Project::kCacheLocationSettingKey, + QString::number(olive::Project::kCacheStoreAlongsideProject)); + EXPECT_EQ(project.GetCacheLocationSetting(), + olive::Project::kCacheStoreAlongsideProject); + + project.SetCustomCachePath(QStringLiteral("/tmp/cache")); + EXPECT_EQ(project.GetCustomCachePath(), QStringLiteral("/tmp/cache")); + + project.SetColorConfigFilename(QStringLiteral("config.ocio")); + EXPECT_EQ(project.GetColorConfigFilename(), QStringLiteral("config.ocio")); + + project.SetDefaultInputColorSpace(QStringLiteral("ACEScg")); + EXPECT_EQ(project.GetDefaultInputColorSpace(), QStringLiteral("ACEScg")); + + project.SetColorReferenceSpace(QStringLiteral("ACES - ACEScg")); + EXPECT_EQ(project.GetColorReferenceSpace(), QStringLiteral("ACES - ACEScg")); +} + +TEST(NodeProject, InitializeCreatesRoot) +{ + olive::Project project; + EXPECT_EQ(project.root(), nullptr); + + project.Initialize(); + EXPECT_NE(project.root(), nullptr); + EXPECT_EQ(project.root()->GetLabel(), QStringLiteral("Root")); +} + +TEST(NodeProject, UuidCanBeRegenerated) +{ + olive::Project project; + const QUuid original = project.GetUuid(); + + project.RegenerateUuid(); + EXPECT_FALSE(project.GetUuid().isNull()); + EXPECT_NE(project.GetUuid(), original); +} + +TEST(NodeProject, GetProjectFromObject) +{ + olive::Project project; + olive::ColorManager *cm = project.color_manager(); + EXPECT_EQ(olive::Project::GetProjectFromObject(cm), &project); +} + +TEST(NodeProject, SaveProducesXml) +{ + olive::Project project; + project.Initialize(); + + QByteArray xml; + QXmlStreamWriter writer(&xml); + writer.writeStartDocument(); + writer.writeStartElement(QStringLiteral("project")); + project.Save(&writer); + writer.writeEndElement(); + writer.writeEndDocument(); + + EXPECT_FALSE(xml.isEmpty()); + EXPECT_TRUE(xml.contains("uuid")); +} diff --git a/tests/gtest/node_value_test.cpp b/tests/gtest/node_value_test.cpp index 91f094b51..67d985973 100644 --- a/tests/gtest/node_value_test.cpp +++ b/tests/gtest/node_value_test.cpp @@ -48,3 +48,80 @@ TEST(NodeValue, BinaryRoundTrip) olive::NodeValue::kBinary, encoded, false); EXPECT_EQ(decoded.toByteArray(), data); } + +TEST(NodeValue, TypeClassification) +{ + EXPECT_TRUE(olive::NodeValue::type_can_be_interpolated(olive::NodeValue::kFloat)); + EXPECT_TRUE(olive::NodeValue::type_can_be_interpolated(olive::NodeValue::kColor)); + EXPECT_FALSE(olive::NodeValue::type_can_be_interpolated(olive::NodeValue::kInt)); + + EXPECT_TRUE(olive::NodeValue::type_is_numeric(olive::NodeValue::kInt)); + EXPECT_TRUE(olive::NodeValue::type_is_numeric(olive::NodeValue::kFloat)); + EXPECT_FALSE(olive::NodeValue::type_is_numeric(olive::NodeValue::kText)); + + EXPECT_TRUE(olive::NodeValue::type_is_vector(olive::NodeValue::kVec2)); + EXPECT_TRUE(olive::NodeValue::type_is_vector(olive::NodeValue::kVec3)); + EXPECT_FALSE(olive::NodeValue::type_is_vector(olive::NodeValue::kFloat)); + + EXPECT_TRUE(olive::NodeValue::type_is_buffer(olive::NodeValue::kTexture)); + EXPECT_TRUE(olive::NodeValue::type_is_buffer(olive::NodeValue::kSamples)); + EXPECT_FALSE(olive::NodeValue::type_is_buffer(olive::NodeValue::kColor)); +} + +TEST(NodeValue, DataTypeNameRoundTrip) +{ + for (int i = olive::NodeValue::kNone; i < olive::NodeValue::kDataTypeCount; ++i) { + auto type = static_cast(i); + QString name = olive::NodeValue::GetDataTypeName(type); + if (name.isEmpty()) { + continue; + } + EXPECT_EQ(olive::NodeValue::GetDataTypeFromName(name), type) + << name.toStdString(); + } +} + +TEST(NodeValue, ConstructionAndAccessors) +{ + olive::NodeValue val(olive::NodeValue::kInt, static_cast(42)); + EXPECT_EQ(val.type(), olive::NodeValue::kInt); + EXPECT_EQ(val.toInt(), 42); + EXPECT_TRUE(val); + + val.set_tag(QStringLiteral("tag")); + EXPECT_EQ(val.tag(), QStringLiteral("tag")); +} + +TEST(NodeValueTable, PushAndGet) +{ + olive::NodeValueTable table; + olive::NodeValue v(olive::NodeValue::kFloat, 3.14); + table.Push(v); + + EXPECT_EQ(table.Count(), 1); + EXPECT_FALSE(table.isEmpty()); + EXPECT_TRUE(table.Has(olive::NodeValue::kFloat)); + + olive::NodeValue got = table.Get(olive::NodeValue::kFloat); + EXPECT_DOUBLE_EQ(got.toDouble(), 3.14); +} + +TEST(NodeValueTable, TakeRemovesValue) +{ + olive::NodeValueTable table; + table.Push(olive::NodeValue(olive::NodeValue::kInt, static_cast(1))); + table.Push(olive::NodeValue(olive::NodeValue::kInt, static_cast(2))); + + olive::NodeValue taken = table.Take(olive::NodeValue::kInt); + EXPECT_EQ(taken.toInt(), 2); + EXPECT_EQ(table.Count(), 1); +} + +TEST(NodeValueTable, ClearEmptiesTable) +{ + olive::NodeValueTable table; + table.Push(olive::NodeValue(olive::NodeValue::kText, QStringLiteral("hello"))); + table.Clear(); + EXPECT_TRUE(table.isEmpty()); + EXPECT_EQ(table.Count(), 0); +} diff --git a/tests/gtest/render_alphaassoc_test.cpp b/tests/gtest/render_alphaassoc_test.cpp new file mode 100644 index 000000000..294961389 --- /dev/null +++ b/tests/gtest/render_alphaassoc_test.cpp @@ -0,0 +1,10 @@ +#include + +#include "render/alphaassoc.h" + +TEST(AlphaAssociated, ValuesAreDistinct) +{ + EXPECT_NE(olive::kAlphaNone, olive::kAlphaUnassociated); + EXPECT_NE(olive::kAlphaNone, olive::kAlphaAssociated); + EXPECT_NE(olive::kAlphaUnassociated, olive::kAlphaAssociated); +} diff --git a/tests/gtest/render_loopmode_test.cpp b/tests/gtest/render_loopmode_test.cpp new file mode 100644 index 000000000..04f949c24 --- /dev/null +++ b/tests/gtest/render_loopmode_test.cpp @@ -0,0 +1,10 @@ +#include + +#include "render/loopmode.h" + +TEST(LoopMode, ValuesAreDistinct) +{ + EXPECT_NE(olive::LoopMode::kLoopModeOff, olive::LoopMode::kLoopModeLoop); + EXPECT_NE(olive::LoopMode::kLoopModeOff, olive::LoopMode::kLoopModeClamp); + EXPECT_NE(olive::LoopMode::kLoopModeLoop, olive::LoopMode::kLoopModeClamp); +} diff --git a/tests/gtest/shader_resources_test.cpp b/tests/gtest/shader_resources_test.cpp index f11fe6a7f..5ffc1688c 100644 --- a/tests/gtest/shader_resources_test.cpp +++ b/tests/gtest/shader_resources_test.cpp @@ -11,9 +11,42 @@ TEST(Shaders, ResourcesAvailable) QStringLiteral(":/shaders/deinterlace2.frag"), QStringLiteral(":/shaders/rgbhistogram.frag"), QStringLiteral(":/shaders/rgbhistogram.vert"), + QStringLiteral(":/shaders/rgbhistogram_secondary.frag"), QStringLiteral(":/shaders/rgbvectorscope.frag"), QStringLiteral(":/shaders/rgbvectorscope.vert"), - QStringLiteral(":/shaders/threewaycolor.frag") + QStringLiteral(":/shaders/rgbwaveform.frag"), + QStringLiteral(":/shaders/rgbwaveform.vert"), + QStringLiteral(":/shaders/threewaycolor.frag"), + QStringLiteral(":/shaders/alphaover.frag"), + QStringLiteral(":/shaders/blur.frag"), + QStringLiteral(":/shaders/chromakey.frag"), + QStringLiteral(":/shaders/colordifferencekey.frag"), + QStringLiteral(":/shaders/colormanage.frag"), + QStringLiteral(":/shaders/cornerpin.frag"), + QStringLiteral(":/shaders/cornerpin.vert"), + QStringLiteral(":/shaders/crop.frag"), + QStringLiteral(":/shaders/crossdissolve.frag"), + QStringLiteral(":/shaders/deinterlace.frag"), + QStringLiteral(":/shaders/despill.frag"), + QStringLiteral(":/shaders/diptoblack.frag"), + QStringLiteral(":/shaders/dropshadow.frag"), + QStringLiteral(":/shaders/flip.frag"), + QStringLiteral(":/shaders/interlace.frag"), + QStringLiteral(":/shaders/invertrgb.frag"), + QStringLiteral(":/shaders/invertrgba.frag"), + QStringLiteral(":/shaders/mosaic.frag"), + QStringLiteral(":/shaders/multiply.frag"), + QStringLiteral(":/shaders/noise.frag"), + QStringLiteral(":/shaders/opacity.frag"), + QStringLiteral(":/shaders/opacity_rgb.frag"), + QStringLiteral(":/shaders/rgb.frag"), + QStringLiteral(":/shaders/ripple.frag"), + QStringLiteral(":/shaders/shape.frag"), + QStringLiteral(":/shaders/solid.frag"), + QStringLiteral(":/shaders/stroke.frag"), + QStringLiteral(":/shaders/swirl.frag"), + QStringLiteral(":/shaders/tile.frag"), + QStringLiteral(":/shaders/wave.frag"), }; for (const QString &path : shader_paths) { diff --git a/tests/gtest/task_taskmanager_test.cpp b/tests/gtest/task_taskmanager_test.cpp index 452059075..581d35e99 100644 --- a/tests/gtest/task_taskmanager_test.cpp +++ b/tests/gtest/task_taskmanager_test.cpp @@ -26,6 +26,42 @@ protected: private: bool *ran_ = nullptr; }; + +class FailingTask final : public olive::Task { +public: + FailingTask() + { + SetTitle(QStringLiteral("FailingTask")); + } + +protected: + bool Run() override + { + SetError(QStringLiteral("expected failure")); + return false; + } +}; + +class ProgressTask final : public olive::Task { +public: + explicit ProgressTask(int steps) + : steps_(steps) + { + SetTitle(QStringLiteral("ProgressTask")); + } + +protected: + bool Run() override + { + for (int i = 0; i <= steps_; ++i) { + emit ProgressChanged(static_cast(i) / steps_); + } + return true; + } + +private: + int steps_ = 1; +}; } TEST(TaskManager, AddAndRunTask) @@ -50,3 +86,83 @@ TEST(TaskManager, AddAndRunTask) EXPECT_TRUE(ran); olive::TaskManager::DestroyInstance(); } + +TEST(TaskManager, FailedTaskEmitsTaskFailed) +{ + olive::TaskManager::CreateInstance(); + olive::TaskManager *mgr = olive::TaskManager::instance(); + ASSERT_NE(mgr, nullptr); + + FailingTask *task = new FailingTask(); + + QEventLoop loop; + bool saw_failed = false; + QObject::connect(mgr, &olive::TaskManager::TaskFailed, &loop, + [&loop, &saw_failed](olive::Task *) { + saw_failed = true; + loop.quit(); + }); + QTimer::singleShot(5000, &loop, &QEventLoop::quit); + + mgr->AddTask(task); + loop.exec(); + + EXPECT_TRUE(saw_failed); + EXPECT_FALSE(task->GetError().isEmpty()); + olive::TaskManager::DestroyInstance(); +} + +TEST(TaskManager, ProgressSignalIsEmitted) +{ + olive::TaskManager::CreateInstance(); + olive::TaskManager *mgr = olive::TaskManager::instance(); + ASSERT_NE(mgr, nullptr); + + ProgressTask *task = new ProgressTask(4); + + QEventLoop loop; + QVector progress; + QObject::connect(task, &olive::Task::ProgressChanged, &loop, + [&progress](double p) { progress.append(p); }); + QObject::connect(task, &olive::Task::Finished, &loop, + [&loop](olive::Task *, bool) { loop.quit(); }); + QTimer::singleShot(5000, &loop, &QEventLoop::quit); + + mgr->AddTask(task); + loop.exec(); + + EXPECT_FALSE(progress.isEmpty()); + EXPECT_GE(progress.last(), 0.99); + olive::TaskManager::DestroyInstance(); +} + +TEST(TaskManager, MultipleTasksComplete) +{ + olive::TaskManager::CreateInstance(); + olive::TaskManager *mgr = olive::TaskManager::instance(); + ASSERT_NE(mgr, nullptr); + + bool ran1 = false; + bool ran2 = false; + DummyTask *task1 = new DummyTask(&ran1); + DummyTask *task2 = new DummyTask(&ran2); + + QEventLoop loop; + int finished = 0; + auto on_finished = [&loop, &finished](olive::Task *, bool) { + if (++finished == 2) { + loop.quit(); + } + }; + QObject::connect(task1, &olive::Task::Finished, &loop, on_finished); + QObject::connect(task2, &olive::Task::Finished, &loop, on_finished); + QTimer::singleShot(5000, &loop, &QEventLoop::quit); + + mgr->AddTask(task1); + mgr->AddTask(task2); + loop.exec(); + + EXPECT_TRUE(ran1); + EXPECT_TRUE(ran2); + olive::TaskManager::DestroyInstance(); +} diff --git a/tests/gtest/timeline_coordinate_test.cpp b/tests/gtest/timeline_coordinate_test.cpp index 527862e46..e28700ebe 100644 --- a/tests/gtest/timeline_coordinate_test.cpp +++ b/tests/gtest/timeline_coordinate_test.cpp @@ -32,3 +32,36 @@ TEST(TimelineCoordinate, Constructors) EXPECT_EQ(with_type.GetTrack().type(), olive::Track::kSubtitle); EXPECT_EQ(with_type.GetTrack().index(), 3); } + +TEST(TimelineCoordinate, CopyAndAssignment) +{ + const olive::core::rational frame(7, 1); + olive::Track::Reference ref(olive::Track::kVideo, 4); + + olive::TimelineCoordinate original(frame, ref); + olive::TimelineCoordinate copy(original); + EXPECT_EQ(copy.GetFrame(), frame); + EXPECT_EQ(copy.GetTrack(), ref); + + olive::TimelineCoordinate assigned; + assigned = original; + EXPECT_EQ(assigned.GetFrame(), frame); + EXPECT_EQ(assigned.GetTrack(), ref); +} + +TEST(TimelineCoordinate, Equality) +{ + olive::TimelineCoordinate a(olive::core::rational(5, 1), + olive::Track::Reference(olive::Track::kVideo, 1)); + olive::TimelineCoordinate b(olive::core::rational(5, 1), + olive::Track::Reference(olive::Track::kVideo, 1)); + olive::TimelineCoordinate c(olive::core::rational(6, 1), + olive::Track::Reference(olive::Track::kVideo, 1)); + olive::TimelineCoordinate d(olive::core::rational(5, 1), + olive::Track::Reference(olive::Track::kAudio, 1)); + + EXPECT_EQ(a.GetFrame(), b.GetFrame()); + EXPECT_EQ(a.GetTrack(), b.GetTrack()); + EXPECT_NE(a.GetFrame(), c.GetFrame()); + EXPECT_NE(a.GetTrack(), d.GetTrack()); +} diff --git a/tests/gtest/timeline_marker_test.cpp b/tests/gtest/timeline_marker_test.cpp index 6d576c841..ad29fa6d5 100644 --- a/tests/gtest/timeline_marker_test.cpp +++ b/tests/gtest/timeline_marker_test.cpp @@ -37,6 +37,13 @@ TEST(TimelineMarker, SaveLoadRoundTrip) EXPECT_EQ(loaded.color(), 5); } +TEST(TimelineMarker, DefaultConstruction) +{ + olive::TimelineMarker marker; + EXPECT_TRUE(marker.name().isEmpty()); + EXPECT_EQ(marker.time().in(), olive::core::rational(0, 1)); +} + TEST(TimelineMarkerList, OrderAndLookup) { olive::TimelineMarkerList list; @@ -74,6 +81,13 @@ TEST(TimelineMarkerList, OrderAndLookup) &marker_a); } +TEST(TimelineMarkerList, GetMarkerAtTimeReturnsNullWhenEmpty) +{ + olive::TimelineMarkerList list; + EXPECT_EQ(list.GetMarkerAtTime(olive::core::rational(10, 1)), nullptr); + EXPECT_EQ(list.GetClosestMarkerToTime(olive::core::rational(10, 1)), nullptr); +} + TEST(TimelineMarkerList, SaveLoadWithUnknownElements) { olive::TimelineMarkerList list; @@ -158,3 +172,18 @@ TEST(TimelineMarkerCommands, AddRemoveAndChange) move.undo_now(); EXPECT_EQ(list.front()->time().in(), olive::core::rational(1, 1)); } + +TEST(TimelineMarkerCommands, AddCommandUndo) +{ + olive::TimelineMarkerList list; + olive::MarkerAddCommand add( + &list, + olive::core::TimeRange(olive::core::rational(5, 1), + olive::core::rational(5, 1)), + QStringLiteral("UndoMe"), + 2); + add.redo_now(); + EXPECT_EQ(list.size(), 1); + add.undo_now(); + EXPECT_TRUE(list.empty()); +} diff --git a/tests/gtest/timeline_workarea_test.cpp b/tests/gtest/timeline_workarea_test.cpp index 318f063b2..c50367bac 100644 --- a/tests/gtest/timeline_workarea_test.cpp +++ b/tests/gtest/timeline_workarea_test.cpp @@ -28,7 +28,7 @@ TEST(TimelineWorkArea, SaveLoadRoundTrip) olive::TimelineWorkArea workarea; workarea.set_enabled(true); workarea.set_range(olive::core::TimeRange(olive::core::rational(2, 1), - olive::core::rational(6, 1))); + olive::core::rational(6, 1))); QByteArray xml; QBuffer buffer(&xml); @@ -51,5 +51,47 @@ TEST(TimelineWorkArea, SaveLoadRoundTrip) EXPECT_TRUE(loaded.enabled()); EXPECT_EQ(loaded.range(), olive::core::TimeRange(olive::core::rational(2, 1), - olive::core::rational(6, 1))); + olive::core::rational(6, 1))); +} + +TEST(TimelineWorkArea, DisabledWorkAreaRoundTrip) +{ + olive::TimelineWorkArea workarea; + workarea.set_enabled(false); + workarea.set_range(olive::core::TimeRange(olive::core::rational(0, 1), + olive::core::rational(10, 1))); + + QByteArray xml; + QBuffer buffer(&xml); + ASSERT_TRUE(buffer.open(QIODevice::WriteOnly)); + QXmlStreamWriter writer(&buffer); + writer.writeStartDocument(); + writer.writeStartElement(QStringLiteral("workarea")); + workarea.save(&writer); + writer.writeEndElement(); + writer.writeEndDocument(); + buffer.close(); + + olive::TimelineWorkArea loaded; + QBuffer read_buffer(&xml); + ASSERT_TRUE(read_buffer.open(QIODevice::ReadOnly)); + QXmlStreamReader reader(&read_buffer); + ASSERT_TRUE(reader.readNextStartElement()); + EXPECT_TRUE(loaded.load(&reader)); + + EXPECT_FALSE(loaded.enabled()); + EXPECT_EQ(loaded.range(), + olive::core::TimeRange(olive::core::rational(0, 1), + olive::core::rational(10, 1))); +} + +TEST(TimelineWorkArea, SetRangeUpdatesInOut) +{ + olive::TimelineWorkArea workarea; + workarea.set_range(olive::core::TimeRange(olive::core::rational(3, 1), + olive::core::rational(8, 1))); + + EXPECT_EQ(workarea.in(), olive::core::rational(3, 1)); + EXPECT_EQ(workarea.out(), olive::core::rational(8, 1)); + EXPECT_EQ(workarea.length(), olive::core::rational(5, 1)); } diff --git a/tests/gtest/undo_stack_test.cpp b/tests/gtest/undo_stack_test.cpp index 451961491..97b52d8f7 100644 --- a/tests/gtest/undo_stack_test.cpp +++ b/tests/gtest/undo_stack_test.cpp @@ -120,3 +120,68 @@ TEST(UndoStack, EmptyMultiUndoCommandIsIgnored) EXPECT_EQ(stack.rowCount(), 1); EXPECT_FALSE(stack.CanUndo()); } + +TEST(UndoStack, MultipleUndosAndRedos) +{ + int counter = 0; + olive::UndoStack stack; + stack.push(new TestCommand(&counter), QStringLiteral("One")); + stack.push(new TestCommand(&counter), QStringLiteral("Two")); + stack.push(new TestCommand(&counter), QStringLiteral("Three")); + EXPECT_EQ(counter, 3); + + stack.undo(); + stack.undo(); + EXPECT_EQ(counter, 1); + EXPECT_TRUE(stack.CanUndo()); + EXPECT_TRUE(stack.CanRedo()); + + stack.redo(); + EXPECT_EQ(counter, 2); +} + +TEST(UndoStack, PushAfterUndoClearsRedoBranch) +{ + int counter = 0; + olive::UndoStack stack; + stack.push(new TestCommand(&counter), QStringLiteral("First")); + stack.push(new TestCommand(&counter), QStringLiteral("Second")); + EXPECT_EQ(counter, 2); + + stack.undo(); + EXPECT_EQ(counter, 1); + + stack.push(new TestCommand(&counter), QStringLiteral("Third")); + EXPECT_EQ(counter, 2); + EXPECT_FALSE(stack.CanRedo()); +} + +TEST(UndoStack, ResetClearsHistory) +{ + int counter = 0; + olive::UndoStack stack; + stack.push(new TestCommand(&counter), QStringLiteral("Action")); + EXPECT_EQ(counter, 1); + + stack.clear(); + EXPECT_FALSE(stack.CanUndo()); + EXPECT_FALSE(stack.CanRedo()); + EXPECT_EQ(stack.rowCount(), 1); +} + +TEST(UndoStack, MultiUndoCommandWithChildren) +{ + int counter = 0; + olive::UndoStack stack; + + auto *multi = new olive::MultiUndoCommand(); + multi->add_child(new TestCommand(&counter)); + multi->add_child(new TestCommand(&counter)); + stack.push(multi, QStringLiteral("Multi")); + + EXPECT_EQ(counter, 2); + stack.undo(); + EXPECT_EQ(counter, 0); + stack.redo(); + EXPECT_EQ(counter, 2); +}