Skip to content

Commit 41f8b49

Browse files
committed
Add a way to change the data source for blocks and assets
1 parent 763c2c9 commit 41f8b49

File tree

4 files changed

+239
-4
lines changed

4 files changed

+239
-4
lines changed

include/scratchcpp/target.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ class LIBSCRATCHCPP_EXPORT Target
7171
IEngine *engine() const;
7272
void setEngine(IEngine *engine);
7373

74+
protected:
75+
/*! Override this method to set a custom data source for blocks and assets. */
76+
virtual Target *dataSource() const { return nullptr; }
77+
7478
private:
7579
spimpl::unique_impl_ptr<TargetPrivate> impl;
7680
};

src/scratch/target.cpp

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,18 @@ int Target::findListById(const std::string &id) const
132132
/*! Returns the list of blocks. */
133133
const std::vector<std::shared_ptr<Block>> &Target::blocks() const
134134
{
135+
if (Target *source = dataSource())
136+
return source->blocks();
137+
135138
return impl->blocks;
136139
}
137140

138141
/*! Adds a block and returns its index. */
139142
int Target::addBlock(std::shared_ptr<Block> block)
140143
{
144+
if (Target *source = dataSource())
145+
return source->addBlock(block);
146+
141147
auto it = std::find(impl->blocks.begin(), impl->blocks.end(), block);
142148

143149
if (it != impl->blocks.end())
@@ -150,6 +156,9 @@ int Target::addBlock(std::shared_ptr<Block> block)
150156
/*! Returns the block at index. */
151157
std::shared_ptr<Block> Target::blockAt(int index) const
152158
{
159+
if (Target *source = dataSource())
160+
return source->blockAt(index);
161+
153162
if (index < 0 || index >= impl->blocks.size())
154163
return nullptr;
155164

@@ -159,6 +168,9 @@ std::shared_ptr<Block> Target::blockAt(int index) const
159168
/*! Returns the index of the block with the given ID. */
160169
int Target::findBlock(const std::string &id) const
161170
{
171+
if (Target *source = dataSource())
172+
return source->findBlock(id);
173+
162174
int i = 0;
163175
for (auto block : impl->blocks) {
164176
if (block->id() == id)
@@ -172,7 +184,9 @@ int Target::findBlock(const std::string &id) const
172184
std::vector<std::shared_ptr<Block>> Target::greenFlagBlocks() const
173185
{
174186
std::vector<std::shared_ptr<Block>> ret;
175-
for (auto block : impl->blocks) {
187+
const auto &blockList = blocks();
188+
189+
for (auto block : blockList) {
176190
if (block->opcode() == "event_whenflagclicked")
177191
ret.push_back(block);
178192
}
@@ -195,12 +209,18 @@ void Target::setCurrentCostume(int newCostume)
195209
/*! Returns the list of costumes. */
196210
const std::vector<std::shared_ptr<Costume>> &Target::costumes() const
197211
{
212+
if (Target *source = dataSource())
213+
return source->costumes();
214+
198215
return impl->costumes;
199216
}
200217

201218
/*! Adds a costume and returns its index. */
202219
int Target::addCostume(std::shared_ptr<Costume> costume)
203220
{
221+
if (Target *source = dataSource())
222+
return source->addCostume(costume);
223+
204224
auto it = std::find(impl->costumes.begin(), impl->costumes.end(), costume);
205225

206226
if (it != impl->costumes.end())
@@ -213,13 +233,19 @@ int Target::addCostume(std::shared_ptr<Costume> costume)
213233
/*! Returns the costume at index. */
214234
std::shared_ptr<Costume> Target::costumeAt(int index) const
215235
{
236+
if (Target *source = dataSource())
237+
return source->costumeAt(index);
238+
216239
// TODO: Add range check
217240
return impl->costumes[index];
218241
}
219242

220243
/*! Returns the index of the given costume. */
221244
int Target::findCostume(const std::string &costumeName) const
222245
{
246+
if (Target *source = dataSource())
247+
return source->findCostume(costumeName);
248+
223249
int i = 0;
224250
for (auto costume : impl->costumes) {
225251
if (costume->name() == costumeName)
@@ -232,12 +258,18 @@ int Target::findCostume(const std::string &costumeName) const
232258
/*! Returns the list of sounds. */
233259
const std::vector<std::shared_ptr<Sound>> &Target::sounds() const
234260
{
261+
if (Target *source = dataSource())
262+
return source->sounds();
263+
235264
return impl->sounds;
236265
}
237266

238267
/*! Adds a sound and returns its index. */
239268
int Target::addSound(std::shared_ptr<Sound> sound)
240269
{
270+
if (Target *source = dataSource())
271+
return source->addSound(sound);
272+
241273
auto it = std::find(impl->sounds.begin(), impl->sounds.end(), sound);
242274

243275
if (it != impl->sounds.end())
@@ -250,13 +282,19 @@ int Target::addSound(std::shared_ptr<Sound> sound)
250282
/*! Returns the sound at index. */
251283
std::shared_ptr<Sound> Target::soundAt(int index) const
252284
{
285+
if (Target *source = dataSource())
286+
return source->soundAt(index);
287+
253288
// TODO: Add range check
254289
return impl->sounds[index];
255290
}
256291

257292
/*! Returns the index of the sound with the given name. */
258293
int Target::findSound(const std::string &soundName) const
259294
{
295+
if (Target *source = dataSource())
296+
return source->findSound(soundName);
297+
260298
int i = 0;
261299
for (auto sound : impl->sounds) {
262300
if (sound->name() == soundName)

test/mocks/targetmock.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#pragma once
2+
3+
#include <scratchcpp/target.h>
4+
#include <gmock/gmock.h>
5+
6+
using namespace libscratchcpp;
7+
8+
class TargetMock : public Target
9+
{
10+
public:
11+
MOCK_METHOD(Target *, dataSource, (), (const override));
12+
13+
Target *dataSourceBase() const { return Target::dataSource(); };
14+
};

test/scratch_classes/target_test.cpp

Lines changed: 182 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
#include <scratchcpp/costume.h>
66
#include <scratchcpp/sound.h>
77
#include <enginemock.h>
8+
#include <targetmock.h>
89

910
#include "../common.h"
1011

1112
using namespace libscratchcpp;
1213

14+
using ::testing::Return;
15+
1316
TEST(TargetTest, IsStage)
1417
{
1518
Target target;
@@ -90,7 +93,9 @@ TEST(TargetTest, Blocks)
9093
auto b3 = std::make_shared<Block>("c", "motion_ifonedgebounce");
9194
auto b4 = std::make_shared<Block>("d", "event_whenflagclicked");
9295

93-
Target target;
96+
TargetMock target;
97+
EXPECT_CALL(target, dataSource()).Times(18).WillRepeatedly(Return(nullptr));
98+
9499
ASSERT_EQ(target.addBlock(b1), 0);
95100
ASSERT_EQ(target.addBlock(b2), 1);
96101
ASSERT_EQ(target.addBlock(b3), 2);
@@ -112,6 +117,62 @@ TEST(TargetTest, Blocks)
112117
ASSERT_EQ(target.findBlock("d"), 3);
113118

114119
ASSERT_EQ(target.greenFlagBlocks(), std::vector<std::shared_ptr<Block>>({ b1, b4 }));
120+
121+
// Test with custom data source
122+
Target source;
123+
124+
EXPECT_CALL(target, dataSource()).WillOnce(Return(&source));
125+
126+
ASSERT_TRUE(target.blocks().empty());
127+
128+
TargetMock target2;
129+
EXPECT_CALL(target2, dataSource()).Times(18).WillRepeatedly(Return(&source));
130+
131+
ASSERT_EQ(target2.addBlock(b1), 0);
132+
ASSERT_EQ(target2.addBlock(b2), 1);
133+
ASSERT_EQ(target2.addBlock(b3), 2);
134+
ASSERT_EQ(target2.addBlock(b4), 3);
135+
ASSERT_EQ(target2.addBlock(b2), 1); // add existing block
136+
137+
ASSERT_EQ(target2.blockAt(0), b1);
138+
ASSERT_EQ(target2.blockAt(1), b2);
139+
ASSERT_EQ(target2.blockAt(2), b3);
140+
ASSERT_EQ(target2.blockAt(3), b4);
141+
ASSERT_EQ(target2.blockAt(4), nullptr);
142+
ASSERT_EQ(target2.blockAt(-1), nullptr);
143+
144+
ASSERT_EQ(target2.findBlock("e"), -1);
145+
ASSERT_EQ(target2.findBlock("a"), 0);
146+
ASSERT_EQ(target2.findBlock("b"), 1);
147+
ASSERT_EQ(target2.findBlock("c"), 2);
148+
ASSERT_EQ(target2.findBlock("d"), 3);
149+
150+
ASSERT_EQ(target2.greenFlagBlocks(), std::vector<std::shared_ptr<Block>>({ b1, b4 }));
151+
152+
ASSERT_EQ(target2.blocks(), source.blocks());
153+
154+
auto b5 = std::make_shared<Block>("e", "data_setvariableto");
155+
ASSERT_EQ(source.addBlock(b5), 4);
156+
157+
EXPECT_CALL(target2, dataSource()).WillOnce(Return(&source));
158+
ASSERT_EQ(target2.blocks(), source.blocks());
159+
160+
ASSERT_EQ(source.blockAt(0), b1);
161+
ASSERT_EQ(source.blockAt(1), b2);
162+
ASSERT_EQ(source.blockAt(2), b3);
163+
ASSERT_EQ(source.blockAt(3), b4);
164+
ASSERT_EQ(source.blockAt(4), b5);
165+
ASSERT_EQ(source.blockAt(5), nullptr);
166+
ASSERT_EQ(source.blockAt(-1), nullptr);
167+
168+
ASSERT_EQ(source.findBlock("f"), -1);
169+
ASSERT_EQ(source.findBlock("a"), 0);
170+
ASSERT_EQ(source.findBlock("b"), 1);
171+
ASSERT_EQ(source.findBlock("c"), 2);
172+
ASSERT_EQ(source.findBlock("d"), 3);
173+
ASSERT_EQ(source.findBlock("e"), 4);
174+
175+
ASSERT_EQ(source.greenFlagBlocks(), std::vector<std::shared_ptr<Block>>({ b1, b4 }));
115176
}
116177

117178
TEST(TargetTest, CurrentCostume)
@@ -128,7 +189,9 @@ TEST(TargetTest, Costumes)
128189
auto c2 = std::make_shared<Costume>("costume2", "", "png");
129190
auto c3 = std::make_shared<Costume>("costume3", "", "svg");
130191

131-
Target target;
192+
TargetMock target;
193+
EXPECT_CALL(target, dataSource()).Times(15).WillRepeatedly(Return(nullptr));
194+
132195
ASSERT_EQ(target.addCostume(c1), 0);
133196
ASSERT_EQ(target.addCostume(c2), 1);
134197
ASSERT_EQ(target.addCostume(c3), 2);
@@ -149,6 +212,60 @@ TEST(TargetTest, Costumes)
149212
ASSERT_EQ(target.findCostume("costume1"), 0);
150213
ASSERT_EQ(target.findCostume("costume2"), 1);
151214
ASSERT_EQ(target.findCostume("costume3"), 2);
215+
216+
// Test with custom data source
217+
Target source;
218+
219+
EXPECT_CALL(target, dataSource()).WillOnce(Return(&source));
220+
221+
ASSERT_TRUE(target.costumes().empty());
222+
223+
TargetMock target2;
224+
EXPECT_CALL(target2, dataSource()).Times(16).WillRepeatedly(Return(&source));
225+
226+
ASSERT_EQ(target2.addCostume(c1), 0);
227+
ASSERT_EQ(target2.addCostume(c2), 1);
228+
ASSERT_EQ(target2.addCostume(c3), 2);
229+
ASSERT_EQ(target2.addCostume(c2), 1); // add existing costume
230+
231+
ASSERT_EQ(target2.costumes().size(), 3);
232+
ASSERT_EQ(target2.costumes()[0]->name(), c1->name());
233+
ASSERT_EQ(target2.costumes()[1]->name(), c2->name());
234+
ASSERT_EQ(target2.costumes()[2]->name(), c3->name());
235+
236+
ASSERT_EQ(target2.costumeAt(0)->name(), c1->name());
237+
ASSERT_EQ(target2.costumeAt(1)->name(), c2->name());
238+
ASSERT_EQ(target2.costumeAt(2)->name(), c3->name());
239+
240+
ASSERT_EQ(target2.findCostume("invalid"), -1);
241+
ASSERT_EQ(target2.findCostume("costume1"), 0);
242+
ASSERT_EQ(target2.findCostume("costume2"), 1);
243+
ASSERT_EQ(target2.findCostume("costume3"), 2);
244+
245+
ASSERT_EQ(target2.costumes(), source.costumes());
246+
247+
auto c4 = std::make_shared<Costume>("costume4", "", "png");
248+
ASSERT_EQ(source.addCostume(c4), 3);
249+
250+
EXPECT_CALL(target2, dataSource()).WillOnce(Return(&source));
251+
ASSERT_EQ(target2.costumes(), source.costumes());
252+
253+
ASSERT_EQ(source.costumes().size(), 4);
254+
ASSERT_EQ(source.costumes()[0]->name(), c1->name());
255+
ASSERT_EQ(source.costumes()[1]->name(), c2->name());
256+
ASSERT_EQ(source.costumes()[2]->name(), c3->name());
257+
ASSERT_EQ(source.costumes()[3]->name(), c4->name());
258+
259+
ASSERT_EQ(source.costumeAt(0)->name(), c1->name());
260+
ASSERT_EQ(source.costumeAt(1)->name(), c2->name());
261+
ASSERT_EQ(source.costumeAt(2)->name(), c3->name());
262+
ASSERT_EQ(source.costumeAt(3)->name(), c4->name());
263+
264+
ASSERT_EQ(source.findCostume("invalid"), -1);
265+
ASSERT_EQ(source.findCostume("costume1"), 0);
266+
ASSERT_EQ(source.findCostume("costume2"), 1);
267+
ASSERT_EQ(source.findCostume("costume3"), 2);
268+
ASSERT_EQ(source.findCostume("costume4"), 3);
152269
}
153270

154271
TEST(TargetTest, Sounds)
@@ -157,7 +274,9 @@ TEST(TargetTest, Sounds)
157274
auto s2 = std::make_shared<Sound>("sound2", "", "wav");
158275
auto s3 = std::make_shared<Sound>("sound3", "", "mp3");
159276

160-
Target target;
277+
TargetMock target;
278+
EXPECT_CALL(target, dataSource()).Times(15).WillRepeatedly(Return(nullptr));
279+
161280
ASSERT_EQ(target.addSound(s1), 0);
162281
ASSERT_EQ(target.addSound(s2), 1);
163282
ASSERT_EQ(target.addSound(s3), 2);
@@ -178,6 +297,60 @@ TEST(TargetTest, Sounds)
178297
ASSERT_EQ(target.findSound("sound1"), 0);
179298
ASSERT_EQ(target.findSound("sound2"), 1);
180299
ASSERT_EQ(target.findSound("sound3"), 2);
300+
301+
// Test with custom data source
302+
Target source;
303+
304+
EXPECT_CALL(target, dataSource()).WillOnce(Return(&source));
305+
306+
ASSERT_TRUE(target.sounds().empty());
307+
308+
TargetMock target2;
309+
EXPECT_CALL(target2, dataSource()).Times(16).WillRepeatedly(Return(&source));
310+
311+
ASSERT_EQ(target2.addSound(s1), 0);
312+
ASSERT_EQ(target2.addSound(s2), 1);
313+
ASSERT_EQ(target2.addSound(s3), 2);
314+
ASSERT_EQ(target2.addSound(s2), 1); // add existing Sound
315+
316+
ASSERT_EQ(target2.sounds().size(), 3);
317+
ASSERT_EQ(target2.sounds()[0]->name(), s1->name());
318+
ASSERT_EQ(target2.sounds()[1]->name(), s2->name());
319+
ASSERT_EQ(target2.sounds()[2]->name(), s3->name());
320+
321+
ASSERT_EQ(target2.soundAt(0)->name(), s1->name());
322+
ASSERT_EQ(target2.soundAt(1)->name(), s2->name());
323+
ASSERT_EQ(target2.soundAt(2)->name(), s3->name());
324+
325+
ASSERT_EQ(target2.findSound("invalid"), -1);
326+
ASSERT_EQ(target2.findSound("sound1"), 0);
327+
ASSERT_EQ(target2.findSound("sound2"), 1);
328+
ASSERT_EQ(target2.findSound("sound3"), 2);
329+
330+
ASSERT_EQ(target2.sounds(), source.sounds());
331+
332+
auto s4 = std::make_shared<Sound>("sound4", "", "wav");
333+
ASSERT_EQ(source.addSound(s4), 3);
334+
335+
EXPECT_CALL(target2, dataSource()).WillOnce(Return(&source));
336+
ASSERT_EQ(target2.sounds(), source.sounds());
337+
338+
ASSERT_EQ(source.sounds().size(), 4);
339+
ASSERT_EQ(source.sounds()[0]->name(), s1->name());
340+
ASSERT_EQ(source.sounds()[1]->name(), s2->name());
341+
ASSERT_EQ(source.sounds()[2]->name(), s3->name());
342+
ASSERT_EQ(source.sounds()[3]->name(), s4->name());
343+
344+
ASSERT_EQ(source.soundAt(0)->name(), s1->name());
345+
ASSERT_EQ(source.soundAt(1)->name(), s2->name());
346+
ASSERT_EQ(source.soundAt(2)->name(), s3->name());
347+
ASSERT_EQ(source.soundAt(3)->name(), s4->name());
348+
349+
ASSERT_EQ(source.findSound("invalid"), -1);
350+
ASSERT_EQ(source.findSound("sound1"), 0);
351+
ASSERT_EQ(source.findSound("sound2"), 1);
352+
ASSERT_EQ(source.findSound("sound3"), 2);
353+
ASSERT_EQ(source.findSound("sound4"), 3);
181354
}
182355

183356
TEST(TargetTest, LayerOrder)
@@ -205,3 +378,9 @@ TEST(TargetTest, Engine)
205378
target.setEngine(&engine);
206379
ASSERT_EQ(target.engine(), &engine);
207380
}
381+
382+
TEST(TargetTest, DataSource)
383+
{
384+
TargetMock target;
385+
ASSERT_EQ(target.dataSourceBase(), nullptr);
386+
}

0 commit comments

Comments
 (0)