diff --git a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp index e4a6d04df8..1183d4f34c 100644 --- a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp +++ b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp @@ -60,8 +60,8 @@ SimpleWidget& ShuffleboardContainer::Add( auto widget = std::make_unique(*this, title); auto ptr = widget.get(); - widget->GetEntry().SetDefaultValue(defaultValue); m_components.emplace_back(std::move(widget)); + ptr->GetEntry().SetDefaultValue(defaultValue); return *ptr; } @@ -85,6 +85,11 @@ SimpleWidget& ShuffleboardContainer::Add(const wpi::Twine& title, return Add(title, nt::Value::MakeString(defaultValue)); } +SimpleWidget& ShuffleboardContainer::Add(const wpi::Twine& title, + const char* defaultValue) { + return Add(title, nt::Value::MakeString(defaultValue)); +} + SimpleWidget& ShuffleboardContainer::Add(const wpi::Twine& title, wpi::ArrayRef defaultValue) { return Add(title, nt::Value::MakeBooleanArray(defaultValue)); diff --git a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardLayout.cpp b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardLayout.cpp index 78281193b7..8418b776e6 100644 --- a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardLayout.cpp +++ b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardLayout.cpp @@ -12,7 +12,7 @@ using namespace frc; ShuffleboardLayout::ShuffleboardLayout(ShuffleboardContainer& parent, const wpi::Twine& name, const wpi::Twine& type) - : ShuffleboardValue(name), + : ShuffleboardValue(type), ShuffleboardComponent(parent, type, name), ShuffleboardContainer(name) { m_isLayout = true; diff --git a/wpilibc/src/main/native/cpp/shuffleboard/SimpleWidget.cpp b/wpilibc/src/main/native/cpp/shuffleboard/SimpleWidget.cpp index cdcca6302f..89af25d6d8 100644 --- a/wpilibc/src/main/native/cpp/shuffleboard/SimpleWidget.cpp +++ b/wpilibc/src/main/native/cpp/shuffleboard/SimpleWidget.cpp @@ -15,12 +15,11 @@ using namespace frc; SimpleWidget::SimpleWidget(ShuffleboardContainer& parent, const wpi::Twine& title) - : ShuffleboardValue(title), ShuffleboardWidget(parent, title) {} + : ShuffleboardValue(title), ShuffleboardWidget(parent, title), m_entry() {} nt::NetworkTableEntry SimpleWidget::GetEntry() { - if (!m_entryInitialized) { + if (!m_entry) { ForceGenerate(); - m_entryInitialized = true; } return m_entry; } @@ -28,9 +27,8 @@ nt::NetworkTableEntry SimpleWidget::GetEntry() { void SimpleWidget::BuildInto(std::shared_ptr parentTable, std::shared_ptr metaTable) { BuildMetadata(metaTable); - if (!m_entryInitialized) { + if (!m_entry) { m_entry = parentTable->GetEntry(GetTitle()); - m_entryInitialized = true; } } diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponentBase.h b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponentBase.h index 3247d1e797..c63e5179b1 100644 --- a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponentBase.h +++ b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardComponentBase.h @@ -42,7 +42,7 @@ class ShuffleboardComponentBase : public virtual ShuffleboardValue { protected: wpi::StringMap> m_properties; - bool m_metadataDirty = false; + bool m_metadataDirty = true; int m_column = -1; int m_row = -1; int m_width = -1; diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h index 26feba9eb4..2f6476589d 100644 --- a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h +++ b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h @@ -139,6 +139,18 @@ class ShuffleboardContainer : public virtual ShuffleboardValue { */ SimpleWidget& Add(const wpi::Twine& title, const wpi::Twine& defaultValue); + /** + * Adds a widget to this container to display the given data. + * + * @param title the title of the widget + * @param defaultValue the default value of the widget + * @return a widget to display the sendable data + * @throws IllegalArgumentException if a widget already exists in this + * container with the given title + * @see #addPersistent(String, Object) add(String title, Object defaultValue) + */ + SimpleWidget& Add(const wpi::Twine& title, const char* defaultValue); + /** * Adds a widget to this container to display the given data. * diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/SimpleWidget.h b/wpilibc/src/main/native/include/frc/shuffleboard/SimpleWidget.h index 2f1cea9de3..973f8cb718 100644 --- a/wpilibc/src/main/native/include/frc/shuffleboard/SimpleWidget.h +++ b/wpilibc/src/main/native/include/frc/shuffleboard/SimpleWidget.h @@ -37,7 +37,6 @@ class SimpleWidget final : public ShuffleboardWidget { private: nt::NetworkTableEntry m_entry; - bool m_entryInitialized = false; void ForceGenerate(); }; diff --git a/wpilibc/src/test/native/cpp/shuffleboard/MockActuatorSendable.cpp b/wpilibc/src/test/native/cpp/shuffleboard/MockActuatorSendable.cpp new file mode 100644 index 0000000000..3c9e411fd6 --- /dev/null +++ b/wpilibc/src/test/native/cpp/shuffleboard/MockActuatorSendable.cpp @@ -0,0 +1,19 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "shuffleboard/MockActuatorSendable.h" + +using namespace frc; + +MockActuatorSendable::MockActuatorSendable(wpi::StringRef name) + : SendableBase(false) { + SetName(name); +} + +void MockActuatorSendable::InitSendable(SendableBuilder& builder) { + builder.SetActuator(true); +} diff --git a/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp b/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp new file mode 100644 index 0000000000..6e0303bc17 --- /dev/null +++ b/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardInstanceTest.cpp @@ -0,0 +1,105 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "frc/shuffleboard/ShuffleboardInstance.h" // NOLINT(build/include_order) + +#include +#include + +#include +#include + +#include "frc/shuffleboard/ShuffleboardInstance.h" +#include "gtest/gtest.h" +#include "shuffleboard/MockActuatorSendable.h" + +using namespace frc; + +class ShuffleboardInstanceTest : public testing::Test { + void SetUp() override { + m_ntInstance = nt::NetworkTableInstance::Create(); + m_shuffleboardInstance = + std::make_unique(m_ntInstance); + } + + protected: + nt::NetworkTableInstance m_ntInstance; + std::unique_ptr m_shuffleboardInstance; +}; + +TEST_F(ShuffleboardInstanceTest, PathFluent) { + auto entry = m_shuffleboardInstance->GetTab("Tab Title") + .GetLayout("List", "List Layout") + .Add("Data", "string") + .WithWidget("Text View") + .GetEntry(); + + EXPECT_EQ("string", entry.GetString("")) << "Wrong entry value"; + EXPECT_EQ("/Shuffleboard/Tab Title/List Layout/Data", entry.GetName()) + << "Entry path generated incorrectly"; +} + +TEST_F(ShuffleboardInstanceTest, NestedLayoutsFluent) { + auto entry = m_shuffleboardInstance->GetTab("Tab") + .GetLayout("List", "First") + .GetLayout("List", "Second") + .GetLayout("List", "Third") + .GetLayout("List", "Fourth") + .Add("Value", "string") + .GetEntry(); + + EXPECT_EQ("string", entry.GetString("")) << "Wrong entry value"; + EXPECT_EQ("/Shuffleboard/Tab/First/Second/Third/Fourth/Value", + entry.GetName()) + << "Entry path generated incorrectly"; +} + +TEST_F(ShuffleboardInstanceTest, NestedLayoutsOop) { + ShuffleboardTab& tab = m_shuffleboardInstance->GetTab("Tab"); + ShuffleboardLayout& first = tab.GetLayout("List", "First"); + ShuffleboardLayout& second = first.GetLayout("List", "Second"); + ShuffleboardLayout& third = second.GetLayout("List", "Third"); + ShuffleboardLayout& fourth = third.GetLayout("List", "Fourth"); + SimpleWidget& widget = fourth.Add("Value", "string"); + auto entry = widget.GetEntry(); + + EXPECT_EQ("string", entry.GetString("")) << "Wrong entry value"; + EXPECT_EQ("/Shuffleboard/Tab/First/Second/Third/Fourth/Value", + entry.GetName()) + << "Entry path generated incorrectly"; +} + +TEST_F(ShuffleboardInstanceTest, LayoutTypeIsSet) { + std::string layoutType = "Type"; + m_shuffleboardInstance->GetTab("Tab").GetLayout(layoutType, "Title"); + m_shuffleboardInstance->Update(); + nt::NetworkTableEntry entry = m_ntInstance.GetEntry( + "/Shuffleboard/.metadata/Tab/Title/PreferredComponent"); + EXPECT_EQ(layoutType, entry.GetString("Not Set")) << "Layout type not set"; +} + +TEST_F(ShuffleboardInstanceTest, NestedActuatoWidgetsAreDisabled) { + MockActuatorSendable sendable("Actuator"); + m_shuffleboardInstance->GetTab("Tab") + .GetLayout("Layout", "Title") + .Add(sendable); + auto controllableEntry = + m_ntInstance.GetEntry("/Shuffleboard/Tab/Title/Actuator/.controllable"); + m_shuffleboardInstance->Update(); + + // Note: we use the unsafe `GetBoolean()` method because if the value is NOT + // a boolean, or if it is not present, then something has clearly gone very, + // very wrong + bool controllable = controllableEntry.GetValue()->GetBoolean(); + // Sanity check + EXPECT_TRUE(controllable) + << "The nested actuator widget should be enabled by default"; + m_shuffleboardInstance->DisableActuatorWidgets(); + controllable = controllableEntry.GetValue()->GetBoolean(); + EXPECT_FALSE(controllable) + << "The nested actuator widget should have been disabled"; +} diff --git a/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardTabTest.cpp b/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardTabTest.cpp new file mode 100644 index 0000000000..23f3e3ac2c --- /dev/null +++ b/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardTabTest.cpp @@ -0,0 +1,115 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include +#include +#include + +#include +#include + +#include "frc/commands/InstantCommand.h" +#include "frc/shuffleboard/ShuffleboardInstance.h" +#include "frc/shuffleboard/ShuffleboardTab.h" +#include "frc/smartdashboard/Sendable.h" +#include "gtest/gtest.h" + +using namespace frc; + +class ShuffleboardTabTest : public testing::Test { + void SetUp() override { + m_ntInstance = nt::NetworkTableInstance::Create(); + m_instance = std::make_unique(m_ntInstance); + m_tab = &(m_instance->GetTab("Tab")); + } + + protected: + nt::NetworkTableInstance m_ntInstance; + ShuffleboardTab* m_tab; + std::unique_ptr m_instance; +}; + +TEST_F(ShuffleboardTabTest, AddDouble) { + auto entry = m_tab->Add("Double", 1.0).GetEntry(); + EXPECT_EQ("/Shuffleboard/Tab/Double", entry.GetName()); + EXPECT_FLOAT_EQ(1.0, entry.GetValue()->GetDouble()); +} + +TEST_F(ShuffleboardTabTest, AddInteger) { + auto entry = m_tab->Add("Int", 1).GetEntry(); + EXPECT_EQ("/Shuffleboard/Tab/Int", entry.GetName()); + EXPECT_FLOAT_EQ(1.0, entry.GetValue()->GetDouble()); +} + +TEST_F(ShuffleboardTabTest, AddBoolean) { + auto entry = m_tab->Add("Bool", false).GetEntry(); + EXPECT_EQ("/Shuffleboard/Tab/Bool", entry.GetName()); + EXPECT_FALSE(entry.GetValue()->GetBoolean()); +} + +TEST_F(ShuffleboardTabTest, AddString) { + auto entry = m_tab->Add("String", "foobar").GetEntry(); + EXPECT_EQ("/Shuffleboard/Tab/String", entry.GetName()); + EXPECT_EQ("foobar", entry.GetValue()->GetString()); +} + +TEST_F(ShuffleboardTabTest, AddNamedSendableWithProperties) { + InstantCommand sendable("Command"); + std::string widgetType = "Command Widget"; + wpi::StringMap> map; + map.try_emplace("foo", nt::Value::MakeDouble(1234)); + map.try_emplace("bar", nt::Value::MakeString("baz")); + m_tab->Add(sendable).WithWidget(widgetType).WithProperties(map); + + m_instance->Update(); + std::string meta = "/Shuffleboard/.metadata/Tab/Command"; + + EXPECT_EQ(1234, m_ntInstance.GetEntry(meta + "/Properties/foo").GetDouble(-1)) + << "Property 'foo' not set correctly"; + EXPECT_EQ("baz", + m_ntInstance.GetEntry(meta + "/Properties/bar").GetString("")) + << "Property 'bar' not set correctly"; + EXPECT_EQ(widgetType, + m_ntInstance.GetEntry(meta + "/PreferredComponent").GetString("")) + << "Preferred component not set correctly"; +} + +TEST_F(ShuffleboardTabTest, AddNumberArray) { + std::array expect = {{1.0, 2.0, 3.0}}; + auto entry = m_tab->Add("DoubleArray", expect).GetEntry(); + EXPECT_EQ("/Shuffleboard/Tab/DoubleArray", entry.GetName()); + + auto actual = entry.GetValue()->GetDoubleArray(); + EXPECT_EQ(expect.size(), actual.size()); + for (size_t i = 0; i < expect.size(); i++) { + EXPECT_FLOAT_EQ(expect[i], actual[i]); + } +} + +TEST_F(ShuffleboardTabTest, AddBooleanArray) { + std::array expect = {{true, false}}; + auto entry = m_tab->Add("BoolArray", expect).GetEntry(); + EXPECT_EQ("/Shuffleboard/Tab/BoolArray", entry.GetName()); + + auto actual = entry.GetValue()->GetBooleanArray(); + EXPECT_EQ(expect.size(), actual.size()); + for (size_t i = 0; i < expect.size(); i++) { + EXPECT_EQ(expect[i], actual[i]); + } +} + +TEST_F(ShuffleboardTabTest, AddStringArray) { + std::array expect = {{"foo", "bar"}}; + auto entry = m_tab->Add("StringArray", expect).GetEntry(); + EXPECT_EQ("/Shuffleboard/Tab/StringArray", entry.GetName()); + + auto actual = entry.GetValue()->GetStringArray(); + EXPECT_EQ(expect.size(), actual.size()); + for (size_t i = 0; i < expect.size(); i++) { + EXPECT_EQ(expect[i], actual[i]); + } +} diff --git a/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardTest.cpp b/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardTest.cpp new file mode 100644 index 0000000000..d39d59d399 --- /dev/null +++ b/wpilibc/src/test/native/cpp/shuffleboard/ShuffleboardTest.cpp @@ -0,0 +1,20 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "frc/shuffleboard/Shuffleboard.h" +#include "frc/shuffleboard/ShuffleboardTab.h" +#include "gtest/gtest.h" + +using namespace frc; + +class ShuffleboardTest : public testing::Test {}; + +TEST_F(ShuffleboardTest, TabObjectsCached) { + ShuffleboardTab& tab1 = Shuffleboard::GetTab("testTabObjectsCached"); + ShuffleboardTab& tab2 = Shuffleboard::GetTab("testTabObjectsCached"); + EXPECT_EQ(&tab1, &tab2) << "Tab objects were not cached"; +} diff --git a/wpilibc/src/test/native/include/shuffleboard/MockActuatorSendable.h b/wpilibc/src/test/native/include/shuffleboard/MockActuatorSendable.h new file mode 100644 index 0000000000..f56215c42e --- /dev/null +++ b/wpilibc/src/test/native/include/shuffleboard/MockActuatorSendable.h @@ -0,0 +1,27 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include + +#include "frc/smartdashboard/SendableBase.h" +#include "frc/smartdashboard/SendableBuilder.h" + +namespace frc { + +/** + * A mock sendable that marks itself as an actuator. + */ +class MockActuatorSendable : public SendableBase { + public: + explicit MockActuatorSendable(wpi::StringRef name); + + void InitSendable(SendableBuilder& builder) override; +}; + +} // namespace frc diff --git a/wpilibcIntegrationTests/src/main/native/cpp/shuffleboard/ShuffleboardTabTest.cpp b/wpilibcIntegrationTests/src/main/native/cpp/shuffleboard/ShuffleboardTabTest.cpp index d1567577c5..23f3e3ac2c 100644 --- a/wpilibcIntegrationTests/src/main/native/cpp/shuffleboard/ShuffleboardTabTest.cpp +++ b/wpilibcIntegrationTests/src/main/native/cpp/shuffleboard/ShuffleboardTabTest.cpp @@ -79,37 +79,37 @@ TEST_F(ShuffleboardTabTest, AddNamedSendableWithProperties) { } TEST_F(ShuffleboardTabTest, AddNumberArray) { - std::array expect = {1.0, 2.0, 3.0}; + std::array expect = {{1.0, 2.0, 3.0}}; auto entry = m_tab->Add("DoubleArray", expect).GetEntry(); EXPECT_EQ("/Shuffleboard/Tab/DoubleArray", entry.GetName()); auto actual = entry.GetValue()->GetDoubleArray(); - EXPECT_EQ(sizeof(expect), sizeof(actual)); - for (size_t i = 0; i < sizeof(expect); i++) { + EXPECT_EQ(expect.size(), actual.size()); + for (size_t i = 0; i < expect.size(); i++) { EXPECT_FLOAT_EQ(expect[i], actual[i]); } } TEST_F(ShuffleboardTabTest, AddBooleanArray) { - std::array expect = {true, false}; + std::array expect = {{true, false}}; auto entry = m_tab->Add("BoolArray", expect).GetEntry(); EXPECT_EQ("/Shuffleboard/Tab/BoolArray", entry.GetName()); auto actual = entry.GetValue()->GetBooleanArray(); - EXPECT_EQ(sizeof(expect), sizeof(actual)); - for (size_t i = 0; i < sizeof(expect); i++) { + EXPECT_EQ(expect.size(), actual.size()); + for (size_t i = 0; i < expect.size(); i++) { EXPECT_EQ(expect[i], actual[i]); } } TEST_F(ShuffleboardTabTest, AddStringArray) { - std::array expect = {"foo", "bar"}; + std::array expect = {{"foo", "bar"}}; auto entry = m_tab->Add("StringArray", expect).GetEntry(); EXPECT_EQ("/Shuffleboard/Tab/StringArray", entry.GetName()); auto actual = entry.GetValue()->GetStringArray(); - EXPECT_EQ(sizeof(expect), sizeof(actual)); - for (size_t i = 0; i < sizeof(expect); i++) { + EXPECT_EQ(expect.size(), actual.size()); + for (size_t i = 0; i < expect.size(); i++) { EXPECT_EQ(expect[i], actual[i]); } }