Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
project_json_serializer_test.h
1// SPDX-FileCopyrightText: © 2026 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include <functional>
7#include <memory>
8#include <regex>
9#include <set>
10#include <string>
11
12#include "dsp/juce_hardware_audio_interface.h"
13#include "structure/project/project.h"
14#include "structure/project/project_json_serializer.h"
15#include "utils/io_utils.h"
16
17#include "helpers/mock_audio_io_device.h"
18#include "helpers/mock_settings_backend.h"
19#include "helpers/scoped_juce_qapplication.h"
20
21#include <gtest/gtest.h>
22#include <nlohmann/json.hpp>
23
24namespace zrythm::structure::project
25{
26
27// ============================================================================
28// Helper Functions
29// ============================================================================
30
32inline nlohmann::json
33create_minimal_valid_project_json ()
34{
35 nlohmann::json j;
36
37 // Top-level required fields
38 j["documentType"] = "ZrythmProject";
39 j["schemaVersion"] = nlohmann::json::object ();
40 j["schemaVersion"]["major"] = 2;
41 j["schemaVersion"]["minor"] = 1;
42 j["appVersion"] = nlohmann::json::object ();
43 j["appVersion"]["major"] = 2;
44 j["appVersion"]["minor"] = 0;
45 j["appVersion"]["patch"] = 0;
46 j["datetime"] = "2026-02-16T12:00:00Z";
47 j["title"] = "Test Project";
48
49 // projectData section
50 auto &pd = j["projectData"];
51
52 // tempoMap (required)
53 pd["tempoMap"] = nlohmann::json::object ();
54 pd["tempoMap"]["timeSignatures"] = nlohmann::json::array ();
55 pd["tempoMap"]["tempoChanges"] = nlohmann::json::array ();
56
57 // transport (required)
58 pd["transport"] = nlohmann::json::object ();
59
60 // tracklist (required)
61 pd["tracklist"] = nlohmann::json::object ();
62 pd["tracklist"]["tracks"] = nlohmann::json::array ();
63 pd["tracklist"]["pinnedTracksCutoff"] = 0;
64 pd["tracklist"]["trackRoutes"] = nlohmann::json::array ();
65
66 // registries (required)
67 pd["registries"] = nlohmann::json::object ();
68 pd["registries"]["portRegistry"] = nlohmann::json::array ();
69 pd["registries"]["paramRegistry"] = nlohmann::json::array ();
70 pd["registries"]["pluginRegistry"] = nlohmann::json::array ();
71 pd["registries"]["trackRegistry"] = nlohmann::json::array ();
72 pd["registries"]["arrangerObjectRegistry"] = nlohmann::json::array ();
73 pd["registries"]["fileAudioSourceRegistry"] = nlohmann::json::array ();
74
75 return j;
76}
77
79inline std::set<std::string>
80extract_all_uuids (const nlohmann::json &j)
81{
82 std::set<std::string> uuids;
83
84 const auto extract = [&] (auto &&self, const nlohmann::json &obj) -> void {
85 if (obj.is_object ())
86 {
87 if (obj.contains ("id") && obj["id"].is_string ())
88 {
89 uuids.insert (obj["id"].get<std::string> ());
90 }
91 for (const auto &[key, val] : obj.items ())
92 {
93 self (self, val);
94 }
95 }
96 else if (obj.is_array ())
97 {
98 for (const auto &elem : obj)
99 {
100 self (self, elem);
101 }
102 }
103 };
104
105 extract (extract, j);
106 return uuids;
107}
108
110inline size_t
111count_objects (const nlohmann::json &j)
112{
113 size_t count = 0;
114
115 const auto count_fn = [&] (auto &&self, const nlohmann::json &obj) -> void {
116 if (obj.is_object ())
117 {
118 ++count;
119 for (const auto &[key, val] : obj.items ())
120 {
121 self (self, val);
122 }
123 }
124 else if (obj.is_array ())
125 {
126 for (const auto &elem : obj)
127 {
128 self (self, elem);
129 }
130 }
131 };
132
133 count_fn (count_fn, j);
134 return count;
135}
136
137// ============================================================================
138// Test Fixture
139// ============================================================================
140
143 : public ::testing::Test,
145{
146protected:
147 void SetUp () override
148 {
149 // Create a temporary directory for the project
150 temp_dir_obj = utils::io::make_tmp_dir ();
151 project_dir =
152 utils::Utf8String::from_qstring (temp_dir_obj->path ()).to_path ();
153
154 // Create audio device manager with dummy device
155 audio_device_manager =
156 test_helpers::create_audio_device_manager_with_dummy_device ();
157
158 // Create hardware audio interface wrapper
159 hw_interface =
160 dsp::JuceHardwareAudioInterface::create (audio_device_manager);
161
162 plugin_format_manager = std::make_shared<juce::AudioPluginFormatManager> ();
163 plugin_format_manager->addDefaultFormats ();
164
165 // Create a mock settings backend
166 auto mock_backend = std::make_unique<test_helpers::MockSettingsBackend> ();
167 mock_backend_ptr = mock_backend.get ();
168
169 // Set up default expectations for common settings
170 ON_CALL (*mock_backend_ptr, value (testing::_, testing::_))
171 .WillByDefault (testing::Return (QVariant ()));
172
173 app_settings =
174 std::make_unique<utils::AppSettings> (std::move (mock_backend));
175
176 // Create port registry and monitor fader
177 port_registry = std::make_unique<dsp::PortRegistry> (nullptr);
178 param_registry = std::make_unique<dsp::ProcessorParameterRegistry> (
179 *port_registry, nullptr);
180 monitor_fader = utils::make_qobject_unique<dsp::Fader> (
182 .port_registry_ = *port_registry,
183 .param_registry_ = *param_registry,
184 },
185 dsp::PortType::Audio,
186 true, // hard_limit_output
187 false, // make_params_automatable
188 [] () -> utils::Utf8String { return u8"Test Control Room"; },
189 [] (bool fader_solo_status) { return false; });
190
191 // Create metronome with test samples
192 juce::AudioSampleBuffer emphasis_sample (2, 512);
193 juce::AudioSampleBuffer normal_sample (2, 512);
194 metronome = utils::make_qobject_unique<dsp::Metronome> (
196 .port_registry_ = *port_registry,
197 .param_registry_ = *param_registry,
198 },
199 emphasis_sample, normal_sample, true, 1.0f, nullptr);
200 }
201
202 void TearDown () override
203 {
204 metronome.reset ();
205 monitor_fader.reset ();
206 param_registry.reset ();
207 port_registry.reset ();
208 app_settings.reset ();
209 plugin_format_manager.reset ();
210 hw_interface.reset ();
211 }
212
213 std::unique_ptr<Project> create_minimal_project ()
214 {
215 structure::project::Project::ProjectDirectoryPathProvider path_provider =
216 [this] (bool for_backup) {
217 if (for_backup)
218 {
219 return project_dir / "backups";
220 }
221 return project_dir;
222 };
223
224 plugins::PluginHostWindowFactory window_factory =
225 [] (plugins::Plugin &) -> std::unique_ptr<plugins::IPluginHostWindow> {
226 return nullptr;
227 };
228
229 auto project = std::make_unique<Project> (
230 *app_settings, path_provider, *hw_interface, plugin_format_manager,
231 window_factory, *metronome, *monitor_fader);
232
233 return project;
234 }
235
236 // Helper to verify a UUID string format
237 static void assert_valid_uuid (const std::string &uuid_str)
238 {
239 // UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
240 static const std::regex uuid_regex (
241 R"([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})",
242 std::regex_constants::icase);
243 EXPECT_TRUE (std::regex_match (uuid_str, uuid_regex))
244 << "Invalid UUID format: " << uuid_str;
245 }
246
247 // Helper to verify a hex color format (#RRGGBB)
248 static void assert_valid_color (const std::string &color_str)
249 {
250 static const std::regex color_regex (R"(#[0-9A-Fa-f]{6})");
251 EXPECT_TRUE (std::regex_match (color_str, color_regex))
252 << "Invalid color format: " << color_str;
253 }
254
255 static constexpr utils::Version TEST_APP_VERSION{ 2, 0, {} };
256 static constexpr utils::Version TEST_APP_VERSION_WITH_PATCH{ 2, 0, 1 };
257
258 std::unique_ptr<QTemporaryDir> temp_dir_obj;
259 fs::path project_dir;
260 std::shared_ptr<juce::AudioDeviceManager> audio_device_manager;
261 std::unique_ptr<dsp::IHardwareAudioInterface> hw_interface;
262 std::shared_ptr<juce::AudioPluginFormatManager> plugin_format_manager;
263 test_helpers::MockSettingsBackend * mock_backend_ptr{};
264 std::unique_ptr<utils::AppSettings> app_settings;
265 std::unique_ptr<dsp::PortRegistry> port_registry;
266 std::unique_ptr<dsp::ProcessorParameterRegistry> param_registry;
269};
270
271} // namespace zrythm::structure::project
static std::unique_ptr< IHardwareAudioInterface > create(std::shared_ptr< juce::AudioDeviceManager > device_manager)
Creates a JUCE-based hardware audio interface.
DSP processing plugin.
Definition plugin.h:30
Fixture for testing Project serialization with a minimal Project setup.
QApplication wrapper that also spins the JUCE message loop.
A unique pointer for QObject objects that also works with QObject-based ownership.
Definition qt.h:38
Lightweight UTF-8 string wrapper with safe conversions.
Definition utf8_string.h:38
Represents a semantic version with major, minor, and optional patch.
Definition version.h:29