test(app): add gtest coverage for previously untested app modules
Add 312 test cases across 14 new files under tests/gtest, covering: - app/common utility headers (tooltypes, projecttypes, nodedatatypes, trackreferencehandle, undowrapper, configwrapper, subtitleapp, ...) - all 8 standardcombos widgets - slider base classes (NumericSliderBase, SliderLadder) - timeline/track handles, marker handle/painting, drag button, elapsed counter - timebased views, selection manager, time target, resizable timeline scrollbar - viewer sizer, display buffer, playback timer, text editor - timeline common helpers, TimelineAndTrackView, TrackView(-item/-splitter) - project explorer views, navigation and undo commands - node view items/edges/connectors/minimap/toolbar/context - node param view sub-widgets - about/export/sequence/footage-properties dialogs - scope widgets (histogram/vectorscope/waveform), managed display - main menu, main window undo commands, viewer/footage panels, engine event bridge - keyframe view handle/input connection, multicam display, node combo box, audio monitor Includes regression tests for the TimelineAndTrackView teardown and TimeScaledObject inverted-limits fixes. Full suite: 2341 tests, 0 failures (64 skips are pre-existing GL-dependent cases under the offscreen QPA).
This commit is contained in:
@@ -0,0 +1,660 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QEvent>
|
||||
#include <QPushButton>
|
||||
#include <QSignalSpy>
|
||||
#include <QTreeWidget>
|
||||
|
||||
#include <olive/core/render/audioparams.h>
|
||||
#include <olive/core/render/samplebuffer.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "dialog/configbase/configdialogbasetab.h"
|
||||
#include "dialog/otioproperties/otiopropertiesdialog.h"
|
||||
#include "node/color/colormanager/colormanager.h"
|
||||
#include "node/factory.h"
|
||||
#include "node/input/multicam/multicamnode.h"
|
||||
#include "node/keyframe.h"
|
||||
#include "node/math/math/math.h"
|
||||
#include "node/nodeundo.h"
|
||||
#include "node/project.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "oakengine/node.h"
|
||||
#include "render/diskmanager.h"
|
||||
#include "widget/audiomonitor/audiomonitor.h"
|
||||
#include "widget/keyframeview/keyframehandle.h"
|
||||
#include "widget/keyframeview/keyframeview.h"
|
||||
#include "widget/keyframeview/keyframeviewinputconnection.h"
|
||||
#include "widget/multicam/multicamdisplay.h"
|
||||
#include "widget/nodecombobox/nodecombobox.h"
|
||||
|
||||
using namespace olive;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Keyframe views and the facade event subscriptions need the application
|
||||
// singletons; create them once and leak them (same pattern as the other
|
||||
// widget suites)
|
||||
void ensure_app_singletons()
|
||||
{
|
||||
if (!olive::Core::instance()) {
|
||||
new olive::Core(); // intentionally leaked
|
||||
}
|
||||
if (!olive::DiskManager::instance()) {
|
||||
olive::DiskManager::create_instance();
|
||||
}
|
||||
}
|
||||
|
||||
NodeKeyframe *insert_keyframe(Node *node, const QString &input,
|
||||
const Rational &time, const QVariant &value,
|
||||
int track = 0)
|
||||
{
|
||||
auto *key =
|
||||
new NodeKeyframe(time, value, NodeKeyframe::k_linear, track, -1, input);
|
||||
NodeParamInsertKeyframeCommand(node, key).redo_now();
|
||||
return key;
|
||||
}
|
||||
|
||||
oak::KeyframeTrackRef track_ref(Node *n, const QString &input, int track)
|
||||
{
|
||||
return oak::KeyframeTrackRef(
|
||||
oak::Input(reinterpret_cast<OakEngineNode *>(n), input), track);
|
||||
}
|
||||
|
||||
OakEngineKeyframe *handle_of(NodeKeyframe *key)
|
||||
{
|
||||
return reinterpret_cast<OakEngineKeyframe *>(key);
|
||||
}
|
||||
|
||||
// Minimal concrete tab: the base class only provides a default validate()
|
||||
class RecordingTab : public olive::ConfigDialogBaseTab {
|
||||
public:
|
||||
virtual void accept(void *parent) override
|
||||
{
|
||||
accepted_with = parent;
|
||||
}
|
||||
|
||||
void *accepted_with = nullptr;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
//
|
||||
// keyframehandle.h: facade accessors over OakEngineKeyframe identity handles
|
||||
//
|
||||
class KeyframeHandleTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
ColorManager::set_up_default_config();
|
||||
ensure_app_singletons();
|
||||
|
||||
project_ = std::make_unique<Project>();
|
||||
project_->initialize();
|
||||
|
||||
node_ = new MathNode();
|
||||
node_->setParent(project_.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<Project> project_;
|
||||
MathNode *node_ = nullptr;
|
||||
};
|
||||
|
||||
TEST_F(KeyframeHandleTest, AccessorsReadEngineState)
|
||||
{
|
||||
NodeKeyframe *key = insert_keyframe(node_, MathNode::k_param_a_in,
|
||||
Rational(1, 2), 2.5);
|
||||
const OakEngineKeyframe *handle = handle_of(key);
|
||||
|
||||
EXPECT_EQ(key_node(handle), reinterpret_cast<OakEngineNode *>(node_));
|
||||
EXPECT_EQ(key_time(handle), Rational(1, 2));
|
||||
EXPECT_EQ(key_easing(handle), 0); // facade order: 0 = linear
|
||||
EXPECT_EQ(key_input_id(handle), MathNode::k_param_a_in);
|
||||
EXPECT_EQ(key_track(handle), 0);
|
||||
EXPECT_EQ(key_element(handle), -1);
|
||||
}
|
||||
|
||||
TEST_F(KeyframeHandleTest, ValueAsDoubleConvertsNumericTypes)
|
||||
{
|
||||
NodeKeyframe *float_key = insert_keyframe(node_, MathNode::k_param_a_in,
|
||||
Rational(0), 2.5);
|
||||
const oak_node_value float_value = key_value(handle_of(float_key));
|
||||
EXPECT_EQ(float_value.type, OAK_NODE_VALUE_FLOAT);
|
||||
EXPECT_DOUBLE_EQ(key_value_as_double(handle_of(float_key)), 2.5);
|
||||
|
||||
// The base-class enabled input carries boolean keyframes
|
||||
NodeKeyframe *bool_key = insert_keyframe(node_, Node::k_enabled_input,
|
||||
Rational(0), true);
|
||||
const oak_node_value bool_value = key_value(handle_of(bool_key));
|
||||
EXPECT_EQ(bool_value.type, OAK_NODE_VALUE_BOOL);
|
||||
EXPECT_DOUBLE_EQ(key_value_as_double(handle_of(bool_key)), 1.0);
|
||||
|
||||
NodeKeyframe *false_key = insert_keyframe(node_, Node::k_enabled_input,
|
||||
Rational(1), false);
|
||||
EXPECT_DOUBLE_EQ(key_value_as_double(handle_of(false_key)), 0.0);
|
||||
}
|
||||
|
||||
TEST_F(KeyframeHandleTest, LiveValueSetUpdatesFacadeReads)
|
||||
{
|
||||
NodeKeyframe *key = insert_keyframe(node_, MathNode::k_param_a_in,
|
||||
Rational(0), 1.0);
|
||||
|
||||
oak_node_value v;
|
||||
std::memset(&v, 0, sizeof(v));
|
||||
v.type = OAK_NODE_VALUE_FLOAT;
|
||||
v.f[0] = 7.25;
|
||||
key_set_value_live(handle_of(key), v);
|
||||
|
||||
EXPECT_DOUBLE_EQ(key_value_as_double(handle_of(key)), 7.25);
|
||||
EXPECT_DOUBLE_EQ(key->value().toDouble(), 7.25);
|
||||
}
|
||||
|
||||
TEST_F(KeyframeHandleTest, LiveTimeSetMovesKeyframe)
|
||||
{
|
||||
NodeKeyframe *key = insert_keyframe(node_, MathNode::k_param_a_in,
|
||||
Rational(0), 0.0);
|
||||
|
||||
key_set_time_live(handle_of(key), Rational(2));
|
||||
EXPECT_EQ(key_time(handle_of(key)), Rational(2));
|
||||
EXPECT_EQ(key->time(), Rational(2));
|
||||
|
||||
// The selection ADL wrappers route through the same accessors
|
||||
EXPECT_EQ(selection_time(handle_of(key)), Rational(2));
|
||||
selection_set_time(handle_of(key), Rational(4));
|
||||
EXPECT_EQ(key->time(), Rational(4));
|
||||
}
|
||||
|
||||
TEST_F(KeyframeHandleTest, BezierPointRoundTrip)
|
||||
{
|
||||
NodeKeyframe *key = insert_keyframe(node_, MathNode::k_param_a_in,
|
||||
Rational(0), 0.0);
|
||||
key->set_type(NodeKeyframe::k_bezier);
|
||||
ASSERT_EQ(key_easing(handle_of(key)), 1); // facade order: 1 = bezier
|
||||
|
||||
key_set_bezier_point_live(handle_of(key), 0, QPointF(0.25, 0.75));
|
||||
key_set_bezier_point_live(handle_of(key), 1, QPointF(-0.5, 1.5));
|
||||
|
||||
EXPECT_EQ(key_bezier_point(handle_of(key), 0), QPointF(0.25, 0.75));
|
||||
EXPECT_EQ(key_bezier_point(handle_of(key), 1), QPointF(-0.5, 1.5));
|
||||
|
||||
// For a bezier keyframe the "valid" points are the actual points
|
||||
EXPECT_EQ(key_valid_bezier_point(handle_of(key), 0), QPointF(0.25, 0.75));
|
||||
EXPECT_EQ(key_valid_bezier_point(handle_of(key), 1), QPointF(-0.5, 1.5));
|
||||
}
|
||||
|
||||
TEST_F(KeyframeHandleTest, HasSiblingAtTimeExcludesSelf)
|
||||
{
|
||||
// The sibling lookup goes through Node::get_keyframe_at_time_on_track(),
|
||||
// which only searches tracks that are not on the standard value, so
|
||||
// keyframing must be enabled on the input first
|
||||
node_->set_input_is_keyframing(MathNode::k_param_a_in, true);
|
||||
|
||||
NodeKeyframe *key_a = insert_keyframe(node_, MathNode::k_param_a_in,
|
||||
Rational(0), 0.0);
|
||||
NodeKeyframe *key_b = insert_keyframe(node_, MathNode::k_param_a_in,
|
||||
Rational(1), 1.0);
|
||||
|
||||
// Another keyframe sits at t=1
|
||||
EXPECT_TRUE(key_has_sibling_at_time(handle_of(key_a), Rational(1)));
|
||||
// t=0 resolves to key_a itself, which does not count as a sibling
|
||||
EXPECT_FALSE(key_has_sibling_at_time(handle_of(key_a), Rational(0)));
|
||||
// Nothing at t=5
|
||||
EXPECT_FALSE(key_has_sibling_at_time(handle_of(key_a), Rational(5)));
|
||||
|
||||
EXPECT_TRUE(selection_has_sibling_at_time(handle_of(key_b), Rational(0)));
|
||||
EXPECT_EQ(selection_time_target_parent(handle_of(key_a)),
|
||||
reinterpret_cast<OakEngineNode *>(node_));
|
||||
}
|
||||
|
||||
//
|
||||
// keyframeviewinputconnection.h: per-track connection between engine keyframe
|
||||
// events and a KeyframeView
|
||||
//
|
||||
class KeyframeConnectionTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
ColorManager::set_up_default_config();
|
||||
ensure_app_singletons();
|
||||
|
||||
project_ = std::make_unique<Project>();
|
||||
project_->initialize();
|
||||
|
||||
node_ = new MathNode();
|
||||
node_->setParent(project_.get());
|
||||
}
|
||||
|
||||
oak::KeyframeTrackRef param_a_ref() const
|
||||
{
|
||||
return track_ref(node_, MathNode::k_param_a_in, 0);
|
||||
}
|
||||
|
||||
std::unique_ptr<Project> project_;
|
||||
MathNode *node_ = nullptr;
|
||||
};
|
||||
|
||||
TEST_F(KeyframeConnectionTest, DefaultsAndAccessors)
|
||||
{
|
||||
KeyframeView view;
|
||||
const oak::KeyframeTrackRef ref = param_a_ref();
|
||||
KeyframeViewInputConnection connection(ref, &view);
|
||||
|
||||
EXPECT_EQ(connection.get_keyframe_y(), 0);
|
||||
EXPECT_EQ(connection.get_brush().color(), QColor(Qt::white));
|
||||
EXPECT_EQ(connection.get_reference(), ref);
|
||||
EXPECT_TRUE(connection.get_keyframes().isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(KeyframeConnectionTest, SetKeyframeYEmitsOnlyOnChange)
|
||||
{
|
||||
KeyframeView view;
|
||||
KeyframeViewInputConnection connection(param_a_ref(), &view);
|
||||
QSignalSpy spy(&connection, &KeyframeViewInputConnection::require_update);
|
||||
|
||||
connection.set_keyframe_y(10);
|
||||
EXPECT_EQ(connection.get_keyframe_y(), 10);
|
||||
EXPECT_EQ(spy.count(), 1);
|
||||
|
||||
// Same value is a no-op
|
||||
connection.set_keyframe_y(10);
|
||||
EXPECT_EQ(spy.count(), 1);
|
||||
|
||||
connection.set_keyframe_y(-3);
|
||||
EXPECT_EQ(connection.get_keyframe_y(), -3);
|
||||
EXPECT_EQ(spy.count(), 2);
|
||||
}
|
||||
|
||||
TEST_F(KeyframeConnectionTest, SetBrushEmitsOnlyOnChange)
|
||||
{
|
||||
KeyframeView view;
|
||||
KeyframeViewInputConnection connection(param_a_ref(), &view);
|
||||
QSignalSpy spy(&connection, &KeyframeViewInputConnection::require_update);
|
||||
|
||||
// Same as the default brush: no emission
|
||||
connection.set_brush(QBrush(Qt::white));
|
||||
EXPECT_EQ(spy.count(), 0);
|
||||
|
||||
connection.set_brush(QBrush(Qt::red));
|
||||
EXPECT_EQ(connection.get_brush().color(), QColor(Qt::red));
|
||||
EXPECT_EQ(spy.count(), 1);
|
||||
}
|
||||
|
||||
TEST_F(KeyframeConnectionTest, SetYBehaviorEmitsOnlyOnChange)
|
||||
{
|
||||
KeyframeView view;
|
||||
KeyframeViewInputConnection connection(param_a_ref(), &view);
|
||||
QSignalSpy spy(&connection, &KeyframeViewInputConnection::require_update);
|
||||
|
||||
// Default behavior is k_single_row
|
||||
connection.set_y_behavior(KeyframeViewInputConnection::k_single_row);
|
||||
EXPECT_EQ(spy.count(), 0);
|
||||
|
||||
connection.set_y_behavior(KeyframeViewInputConnection::k_value_is_height);
|
||||
EXPECT_EQ(spy.count(), 1);
|
||||
|
||||
connection.set_y_behavior(KeyframeViewInputConnection::k_value_is_height);
|
||||
EXPECT_EQ(spy.count(), 1);
|
||||
}
|
||||
|
||||
TEST_F(KeyframeConnectionTest, MatchingKeyframeInsertionEmitsRequireUpdate)
|
||||
{
|
||||
KeyframeView view;
|
||||
KeyframeViewInputConnection connection(param_a_ref(), &view);
|
||||
QSignalSpy spy(&connection, &KeyframeViewInputConnection::require_update);
|
||||
|
||||
insert_keyframe(node_, MathNode::k_param_a_in, Rational(0), 0.0);
|
||||
EXPECT_EQ(spy.count(), 1);
|
||||
ASSERT_EQ(connection.get_keyframes().size(), 1);
|
||||
|
||||
// A keyframe on a different input of the same node does not match
|
||||
insert_keyframe(node_, MathNode::k_param_b_in, Rational(0), 0.0);
|
||||
EXPECT_EQ(spy.count(), 1);
|
||||
EXPECT_EQ(connection.get_keyframes().size(), 1);
|
||||
}
|
||||
|
||||
TEST_F(KeyframeConnectionTest, LiveValueAndTimeChangesEmitRequireUpdate)
|
||||
{
|
||||
NodeKeyframe *key = insert_keyframe(node_, MathNode::k_param_a_in,
|
||||
Rational(0), 0.0);
|
||||
|
||||
KeyframeView view;
|
||||
KeyframeViewInputConnection connection(param_a_ref(), &view);
|
||||
QSignalSpy spy(&connection, &KeyframeViewInputConnection::require_update);
|
||||
|
||||
oak_node_value v;
|
||||
std::memset(&v, 0, sizeof(v));
|
||||
v.type = OAK_NODE_VALUE_FLOAT;
|
||||
v.f[0] = 3.0;
|
||||
key_set_value_live(handle_of(key), v);
|
||||
EXPECT_EQ(spy.count(), 1);
|
||||
|
||||
key_set_time_live(handle_of(key), Rational(3));
|
||||
EXPECT_EQ(spy.count(), 2);
|
||||
}
|
||||
|
||||
TEST_F(KeyframeConnectionTest, TypeChangeEmitsTypeChangedAndRequireUpdate)
|
||||
{
|
||||
// Node::invalidate_from_keyframe_type_changed() returns early when the
|
||||
// track holds a single keyframe (interpolation is a no-op), so the type
|
||||
// change signal only reaches the connection with two or more keyframes
|
||||
NodeKeyframe *key = insert_keyframe(node_, MathNode::k_param_a_in,
|
||||
Rational(0), 0.0);
|
||||
insert_keyframe(node_, MathNode::k_param_a_in, Rational(1), 1.0);
|
||||
|
||||
KeyframeView view;
|
||||
KeyframeViewInputConnection connection(param_a_ref(), &view);
|
||||
QSignalSpy update_spy(&connection,
|
||||
&KeyframeViewInputConnection::require_update);
|
||||
QSignalSpy type_spy(&connection,
|
||||
&KeyframeViewInputConnection::type_changed);
|
||||
|
||||
key->set_type(NodeKeyframe::k_hold);
|
||||
EXPECT_EQ(update_spy.count(), 1);
|
||||
EXPECT_EQ(type_spy.count(), 1);
|
||||
|
||||
// Setting the same type again emits nothing
|
||||
key->set_type(NodeKeyframe::k_hold);
|
||||
EXPECT_EQ(update_spy.count(), 1);
|
||||
EXPECT_EQ(type_spy.count(), 1);
|
||||
}
|
||||
|
||||
TEST_F(KeyframeConnectionTest, RemovalEmitsRequireUpdate)
|
||||
{
|
||||
NodeKeyframe *key = insert_keyframe(node_, MathNode::k_param_a_in,
|
||||
Rational(0), 0.0);
|
||||
|
||||
KeyframeView view;
|
||||
KeyframeViewInputConnection connection(param_a_ref(), &view);
|
||||
QSignalSpy spy(&connection, &KeyframeViewInputConnection::require_update);
|
||||
|
||||
NodeParamRemoveKeyframeCommand(key).redo_now();
|
||||
EXPECT_EQ(spy.count(), 1);
|
||||
EXPECT_TRUE(connection.get_keyframes().isEmpty());
|
||||
}
|
||||
|
||||
//
|
||||
// multicamdisplay.h: the shader/paint paths need a GL renderer, but the
|
||||
// widget itself constructs offscreen and its node contract is facade-readable
|
||||
//
|
||||
TEST(WidgetMulticamDisplay, ConstructsWithoutNode)
|
||||
{
|
||||
ensure_app_singletons();
|
||||
|
||||
MulticamDisplay display;
|
||||
EXPECT_FALSE(display.isVisible());
|
||||
|
||||
// Accepts (and clears) a null node without touching the paint path
|
||||
display.set_multicam_node(nullptr);
|
||||
}
|
||||
|
||||
TEST(WidgetMulticamDisplay, MulticamNodeStateMatchesPaintContract)
|
||||
{
|
||||
ensure_app_singletons();
|
||||
|
||||
MulticamDisplay display;
|
||||
|
||||
MultiCamNode node;
|
||||
node.input_array_resize(MultiCamNode::k_sources_input, 3);
|
||||
node.set_standard_value(MultiCamNode::k_current_input, 1);
|
||||
|
||||
auto *handle = reinterpret_cast<OakEngineNode *>(&node);
|
||||
display.set_multicam_node(handle);
|
||||
|
||||
// on_paint() lays the highlight rect out from exactly these facade reads
|
||||
EXPECT_EQ(oakengine_multicam_get_source_count(handle), 3);
|
||||
EXPECT_EQ(oakengine_multicam_get_current_source(handle), 1);
|
||||
|
||||
int rows = 0, cols = 0;
|
||||
oakengine_multicam_get_rows_and_columns(3, &rows, &cols);
|
||||
EXPECT_EQ(rows, 2);
|
||||
EXPECT_EQ(cols, 2);
|
||||
|
||||
int row = -1, col = -1;
|
||||
oakengine_multicam_index_to_row_cols(1, rows, cols, &row, &col);
|
||||
EXPECT_EQ(row, 0);
|
||||
EXPECT_EQ(col, 1);
|
||||
|
||||
display.set_multicam_node(nullptr);
|
||||
}
|
||||
|
||||
//
|
||||
// nodecombobox.h: the popup path is modal; the retranslation and factory-name
|
||||
// paths are not (selection round-trips are covered in widget_combos_test.cpp)
|
||||
//
|
||||
TEST(WidgetNodeComboBox, LanguageChangeRetranslatesSelection)
|
||||
{
|
||||
NodeFactory::initialize();
|
||||
|
||||
{
|
||||
NodeComboBox combo;
|
||||
const QString id = QStringLiteral("org.olivevideoeditor.Olive.math");
|
||||
combo.set_node(id);
|
||||
ASSERT_EQ(combo.count(), 1);
|
||||
const QString before = combo.itemText(0);
|
||||
EXPECT_EQ(before, NodeFactory::get_name_from_id(id));
|
||||
ASSERT_FALSE(before.isEmpty());
|
||||
|
||||
QEvent lang(QEvent::LanguageChange);
|
||||
QApplication::sendEvent(&combo, &lang);
|
||||
|
||||
// The selection survives and the label is re-fetched from the factory
|
||||
EXPECT_EQ(combo.get_selected_node(), id);
|
||||
ASSERT_EQ(combo.count(), 1);
|
||||
EXPECT_EQ(combo.itemText(0), before);
|
||||
}
|
||||
|
||||
NodeFactory::destroy();
|
||||
}
|
||||
|
||||
TEST(WidgetNodeComboBox, UnknownIdProducesEmptyText)
|
||||
{
|
||||
NodeFactory::initialize();
|
||||
|
||||
{
|
||||
NodeComboBox combo;
|
||||
combo.set_node(QStringLiteral("org.example.bogus"));
|
||||
|
||||
// The id is stored even though the factory has no name for it
|
||||
EXPECT_EQ(combo.get_selected_node(), QStringLiteral("org.example.bogus"));
|
||||
ASSERT_EQ(combo.count(), 1);
|
||||
EXPECT_TRUE(combo.itemText(0).isEmpty());
|
||||
}
|
||||
|
||||
NodeFactory::destroy();
|
||||
}
|
||||
|
||||
//
|
||||
// audiomonitor.h: painting needs a GL context; the playback state machine,
|
||||
// parameter handling and the static instance broadcasters do not
|
||||
//
|
||||
TEST(WidgetAudioMonitor, ConstructionDefaults)
|
||||
{
|
||||
AudioMonitor monitor;
|
||||
|
||||
EXPECT_FALSE(monitor.is_playing());
|
||||
// The ctor sizes the minimum width to the font height
|
||||
EXPECT_EQ(monitor.minimumWidth(), monitor.fontMetrics().height());
|
||||
}
|
||||
|
||||
TEST(WidgetAudioMonitor, StartWaveformWithNullCacheStaysStopped)
|
||||
{
|
||||
AudioMonitor monitor;
|
||||
|
||||
// A null cache reports sample rate 0, so the start request is rejected
|
||||
monitor.start_waveform(nullptr, Rational(0), 1);
|
||||
EXPECT_FALSE(monitor.is_playing());
|
||||
|
||||
monitor.stop();
|
||||
EXPECT_FALSE(monitor.is_playing());
|
||||
}
|
||||
|
||||
TEST(WidgetAudioMonitor, PushSampleBufferWithoutParamsIsIgnored)
|
||||
{
|
||||
AudioMonitor monitor;
|
||||
|
||||
// No params set -> channel_count() is 0 -> early return
|
||||
monitor.push_sample_buffer(SampleBuffer());
|
||||
EXPECT_FALSE(monitor.is_playing());
|
||||
}
|
||||
|
||||
TEST(WidgetAudioMonitor, PushSampleBufferWithParamsDoesNotStartPlayback)
|
||||
{
|
||||
const olive::core::AudioParams params(48000,
|
||||
olive::core::k_channel_layout_stereo,
|
||||
olive::core::SampleFormat::f32_p);
|
||||
|
||||
AudioMonitor monitor;
|
||||
monitor.set_params(params);
|
||||
|
||||
SampleBuffer buffer(params, size_t(64));
|
||||
ASSERT_TRUE(buffer.is_allocated());
|
||||
float *left = buffer.data(0);
|
||||
ASSERT_NE(left, nullptr);
|
||||
for (int i = 0; i < 64; i++) {
|
||||
left[i] = 0.5f;
|
||||
}
|
||||
|
||||
// Level analysis runs, but sample playback is not waveform playback
|
||||
monitor.push_sample_buffer(buffer);
|
||||
EXPECT_FALSE(monitor.is_playing());
|
||||
|
||||
monitor.stop();
|
||||
EXPECT_FALSE(monitor.is_playing());
|
||||
}
|
||||
|
||||
TEST(WidgetAudioMonitor, StaticBroadcastsReachAllInstances)
|
||||
{
|
||||
const olive::core::AudioParams params(48000,
|
||||
olive::core::k_channel_layout_stereo,
|
||||
olive::core::SampleFormat::f32_p);
|
||||
|
||||
AudioMonitor first;
|
||||
AudioMonitor second;
|
||||
first.set_params(params);
|
||||
second.set_params(params);
|
||||
|
||||
// A null waveform is rejected on every registered instance
|
||||
AudioMonitor::start_waveform_on_all(nullptr, Rational(0), 1);
|
||||
EXPECT_FALSE(first.is_playing());
|
||||
EXPECT_FALSE(second.is_playing());
|
||||
|
||||
SampleBuffer buffer(params, size_t(32));
|
||||
AudioMonitor::push_sample_buffer_on_all(buffer);
|
||||
EXPECT_FALSE(first.is_playing());
|
||||
EXPECT_FALSE(second.is_playing());
|
||||
|
||||
AudioMonitor::stop_on_all();
|
||||
EXPECT_FALSE(first.is_playing());
|
||||
EXPECT_FALSE(second.is_playing());
|
||||
}
|
||||
|
||||
//
|
||||
// configdialogbasetab.h
|
||||
//
|
||||
TEST(DialogConfigBaseTab, DefaultValidateAccepts)
|
||||
{
|
||||
RecordingTab tab;
|
||||
EXPECT_TRUE(tab.validate());
|
||||
}
|
||||
|
||||
TEST(DialogConfigBaseTab, AcceptReceivesParentPointer)
|
||||
{
|
||||
RecordingTab tab;
|
||||
int parent_token = 0;
|
||||
|
||||
tab.accept(&parent_token);
|
||||
EXPECT_EQ(tab.accepted_with, static_cast<void *>(&parent_token));
|
||||
}
|
||||
|
||||
//
|
||||
// otiopropertiesdialog.h: the per-sequence Settings button opens a modal
|
||||
// SequenceDialog (not exercised); construction, listing and OK/Cancel are not
|
||||
// modal
|
||||
//
|
||||
TEST(DialogOTIOProperties, ListsSequencesWithIndexedSettingsButtons)
|
||||
{
|
||||
ColorManager::set_up_default_config();
|
||||
|
||||
Project project;
|
||||
project.initialize();
|
||||
|
||||
auto *seq_a = new Sequence();
|
||||
seq_a->setParent(&project);
|
||||
seq_a->set_label(QStringLiteral("Alpha"));
|
||||
auto *seq_b = new Sequence();
|
||||
seq_b->setParent(&project);
|
||||
seq_b->set_label(QStringLiteral("Beta"));
|
||||
|
||||
const QList<OakEngineSequence *> sequences = {
|
||||
reinterpret_cast<OakEngineSequence *>(seq_a),
|
||||
reinterpret_cast<OakEngineSequence *>(seq_b),
|
||||
};
|
||||
|
||||
OTIOPropertiesDialog dialog(
|
||||
sequences, reinterpret_cast<OakEngineProject *>(&project));
|
||||
|
||||
EXPECT_EQ(dialog.windowTitle(), QStringLiteral("Load OpenTimelineIO Project"));
|
||||
|
||||
auto *table = dialog.findChild<QTreeWidget *>();
|
||||
ASSERT_NE(table, nullptr);
|
||||
EXPECT_EQ(table->columnCount(), 2);
|
||||
EXPECT_EQ(table->headerItem()->text(0), QStringLiteral("Sequence"));
|
||||
EXPECT_EQ(table->headerItem()->text(1), QStringLiteral("Actions"));
|
||||
EXPECT_FALSE(table->rootIsDecorated());
|
||||
|
||||
ASSERT_EQ(table->topLevelItemCount(), 2);
|
||||
EXPECT_EQ(table->topLevelItem(0)->text(0), QStringLiteral("Alpha"));
|
||||
EXPECT_EQ(table->topLevelItem(1)->text(0), QStringLiteral("Beta"));
|
||||
|
||||
// Each row carries a Settings button whose "index" property is its row
|
||||
for (int i = 0; i < 2; i++) {
|
||||
QWidget *actions = table->itemWidget(table->topLevelItem(i), 1);
|
||||
ASSERT_NE(actions, nullptr) << "row " << i;
|
||||
auto *button = actions->findChild<QPushButton *>();
|
||||
ASSERT_NE(button, nullptr) << "row " << i;
|
||||
EXPECT_EQ(button->text(), QStringLiteral("Settings"));
|
||||
EXPECT_EQ(button->property("index").toInt(), i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DialogOTIOProperties, EmptyListAndButtonBoxResults)
|
||||
{
|
||||
ColorManager::set_up_default_config();
|
||||
|
||||
Project project;
|
||||
project.initialize();
|
||||
|
||||
{
|
||||
OTIOPropertiesDialog dialog(
|
||||
{}, reinterpret_cast<OakEngineProject *>(&project));
|
||||
|
||||
auto *table = dialog.findChild<QTreeWidget *>();
|
||||
ASSERT_NE(table, nullptr);
|
||||
EXPECT_EQ(table->topLevelItemCount(), 0);
|
||||
|
||||
auto *buttons = dialog.findChild<QDialogButtonBox *>();
|
||||
ASSERT_NE(buttons, nullptr);
|
||||
QPushButton *ok = buttons->button(QDialogButtonBox::Ok);
|
||||
ASSERT_NE(ok, nullptr);
|
||||
ok->click();
|
||||
EXPECT_EQ(dialog.result(), QDialog::Accepted);
|
||||
}
|
||||
|
||||
{
|
||||
OTIOPropertiesDialog dialog(
|
||||
{}, reinterpret_cast<OakEngineProject *>(&project));
|
||||
|
||||
auto *buttons = dialog.findChild<QDialogButtonBox *>();
|
||||
ASSERT_NE(buttons, nullptr);
|
||||
QPushButton *cancel = buttons->button(QDialogButtonBox::Cancel);
|
||||
ASSERT_NE(cancel, nullptr);
|
||||
cancel->click();
|
||||
EXPECT_EQ(dialog.result(), QDialog::Rejected);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user