|
| 1 | +#include "internal/scratch3reader.h" |
| 2 | +#include "scratch/stage.h" |
| 3 | +#include "scratch/sprite.h" |
| 4 | +#include "engine/engine.h" |
| 5 | +#include "../common.h" |
| 6 | + |
| 7 | +#define ASSERT_VAR(target, varName) \ |
| 8 | + ASSERT_NE(target->findVariable(varName), -1); \ |
| 9 | + ASSERT_TRUE(target->variableAt(target->findVariable(varName))) |
| 10 | +#define GET_VAR(target, varName) target->variableAt(target->findVariable(varName)) |
| 11 | + |
| 12 | +#define ASSERT_LIST(target, listName) \ |
| 13 | + ASSERT_NE(target->findList(listName), -1); \ |
| 14 | + ASSERT_TRUE(target->listAt(target->findList(listName))) |
| 15 | +#define GET_LIST(target, listName) target->listAt(target->findList(listName)) |
| 16 | + |
| 17 | +#define ASSERT_INPUT(block, inputName) \ |
| 18 | + ASSERT_NE(block->findInput(inputName), -1); \ |
| 19 | + ASSERT_TRUE(block->inputAt(block->findInput(inputName))) |
| 20 | +#define GET_INPUT(block, inputName) block->inputAt(block->findInput(inputName)) |
| 21 | + |
| 22 | +#define ASSERT_FIELD(block, fieldName) \ |
| 23 | + ASSERT_NE(block->findField(fieldName), -1); \ |
| 24 | + ASSERT_TRUE(block->fieldAt(block->findField(fieldName))) |
| 25 | +#define GET_FIELD(block, fieldName) block->fieldAt(block->findField(fieldName)) |
| 26 | + |
| 27 | +using namespace libscratchcpp; |
| 28 | + |
| 29 | +static Scratch3Reader s3reader; |
| 30 | +static const std::vector<IProjectReader *> readers = { &s3reader }; |
| 31 | +static const std::unordered_map<IProjectReader *, std::string> fileExtensions = { { &s3reader, ".sb3" } }; |
| 32 | + |
| 33 | +TEST(LoadProjectTest, EmptyProject) |
| 34 | +{ |
| 35 | + for (auto reader : readers) { |
| 36 | + reader->clear(); |
| 37 | + reader->setFileName("empty_project" + fileExtensions.at(reader)); |
| 38 | + |
| 39 | + ASSERT_TRUE(reader->load()); |
| 40 | + ASSERT_TRUE(reader->isValid()); |
| 41 | + ASSERT_EQ(reader->targets().size(), 1); |
| 42 | + ASSERT_EQ(reader->extensions().size(), 0); |
| 43 | + ASSERT_EQ(reader->broadcasts().size(), 0); |
| 44 | + |
| 45 | + std::shared_ptr<Stage> stage = std::reinterpret_pointer_cast<Stage>(reader->targets().at(0)); |
| 46 | + ASSERT_TRUE(stage->isStage()); |
| 47 | + ASSERT_EQ(stage->name(), "Stage"); |
| 48 | + ASSERT_EQ(stage->variables().size(), 0); |
| 49 | + ASSERT_EQ(stage->lists().size(), 0); |
| 50 | + ASSERT_EQ(stage->blocks().size(), 0); |
| 51 | + ASSERT_EQ(stage->costumes().size(), 1); |
| 52 | + // TODO: Add comments |
| 53 | + ASSERT_EQ(stage->currentCostume(), 0); |
| 54 | + ASSERT_EQ(stage->sounds().size(), 0); |
| 55 | + ASSERT_EQ(stage->layerOrder(), 0); |
| 56 | + ASSERT_EQ(stage->volume(), 100); |
| 57 | + ASSERT_EQ(stage->tempo(), 60); |
| 58 | + ASSERT_EQ(stage->videoState(), Stage::VideoState::On); |
| 59 | + ASSERT_EQ(stage->videoTransparency(), 50); |
| 60 | + ASSERT_TRUE(stage->textToSpeechLanguage().empty()); |
| 61 | + |
| 62 | + auto backdrop = stage->costumeAt(0); |
| 63 | + ASSERT_EQ(backdrop.name(), "backdrop1"); |
| 64 | + ASSERT_FALSE(backdrop.assetId().empty()); |
| 65 | + ASSERT_EQ(backdrop.md5ext(), backdrop.assetId() + ".svg"); |
| 66 | + ASSERT_EQ(backdrop.dataFormat(), "svg"); |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +TEST(LoadProjectTest, LoadTestProject) |
| 71 | +{ |
| 72 | + for (auto reader : readers) { |
| 73 | + reader->clear(); |
| 74 | + reader->setFileName("load_test" + fileExtensions.at(reader)); |
| 75 | + |
| 76 | + ASSERT_TRUE(reader->load()); |
| 77 | + ASSERT_TRUE(reader->isValid()); |
| 78 | + ASSERT_EQ(reader->targets().size(), 3); |
| 79 | + ASSERT_EQ(reader->extensions().size(), 0); |
| 80 | + ASSERT_EQ(reader->broadcasts().size(), 1); |
| 81 | + Engine engine; |
| 82 | + engine.setTargets(reader->targets()); |
| 83 | + engine.setBroadcasts(reader->broadcasts()); |
| 84 | + engine.setExtensions(reader->extensions()); |
| 85 | + engine.compile(); |
| 86 | + |
| 87 | + // Stage |
| 88 | + ASSERT_NE(engine.findTarget("Stage"), -1); |
| 89 | + Stage *stage = dynamic_cast<Stage *>(engine.targetAt(engine.findTarget("Stage"))); |
| 90 | + ASSERT_EQ(stage->variables().size(), 2); |
| 91 | + ASSERT_EQ(stage->lists().size(), 1); |
| 92 | + |
| 93 | + // Stage: variables |
| 94 | + ASSERT_VAR(stage, "var1"); |
| 95 | + ASSERT_EQ(GET_VAR(stage, "var1")->value().toString(), "Hello, world!"); |
| 96 | + ASSERT_VAR(stage, "var3"); |
| 97 | + ASSERT_EQ(GET_VAR(stage, "var3")->value().toDouble(), std::pow(10, 50)); |
| 98 | + |
| 99 | + // Stage: lists |
| 100 | + ASSERT_LIST(stage, "list1"); |
| 101 | + ASSERT_EQ(GET_LIST(stage, "list1")->toString(), "3 1 4 8 7 6 7 7 2 4"); |
| 102 | + |
| 103 | + // Sprite1 |
| 104 | + ASSERT_NE(engine.findTarget("Sprite1"), -1); |
| 105 | + Sprite *sprite1 = dynamic_cast<Sprite *>(engine.targetAt(engine.findTarget("Sprite1"))); |
| 106 | + ASSERT_FALSE(sprite1->isStage()); |
| 107 | + ASSERT_EQ(sprite1->name(), "Sprite1"); |
| 108 | + ASSERT_EQ(sprite1->variables().size(), 1); |
| 109 | + ASSERT_EQ(sprite1->lists().size(), 1); |
| 110 | + ASSERT_EQ(sprite1->blocks().size(), 18); |
| 111 | + ASSERT_EQ(sprite1->costumes().size(), 2); |
| 112 | + ASSERT_EQ(sprite1->sounds().size(), 1); |
| 113 | + ASSERT_TRUE(sprite1->visible()); |
| 114 | + ASSERT_EQ(sprite1->x(), 0); |
| 115 | + ASSERT_EQ(sprite1->y(), 0); |
| 116 | + ASSERT_EQ(sprite1->size(), 100); |
| 117 | + ASSERT_EQ(sprite1->direction(), 90); |
| 118 | + ASSERT_FALSE(sprite1->draggable()); |
| 119 | + ASSERT_EQ(sprite1->rotationStyleStr(), "all around"); |
| 120 | + ASSERT_EQ(sprite1->rotationStyle(), Sprite::RotationStyle::AllAround); |
| 121 | + |
| 122 | + // Sprite1: sounds |
| 123 | + auto sound = sprite1->soundAt(0); |
| 124 | + ASSERT_EQ(sound.name(), "Meow"); |
| 125 | + ASSERT_FALSE(sound.assetId().empty()); |
| 126 | + ASSERT_EQ(sound.md5ext(), sound.assetId() + ".wav"); |
| 127 | + ASSERT_EQ(sound.dataFormat(), "wav"); |
| 128 | + |
| 129 | + // Sprite1: variables |
| 130 | + ASSERT_VAR(sprite1, "var2"); |
| 131 | + ASSERT_EQ(GET_VAR(sprite1, "var2")->value().toDouble(), 0.5); |
| 132 | + |
| 133 | + // Sprite1: lists |
| 134 | + ASSERT_LIST(sprite1, "list2"); |
| 135 | + ASSERT_EQ(GET_LIST(sprite1, "list2")->toString(), "9 6 3 8 0 4 4 2 9 7"); |
| 136 | + |
| 137 | + // Sprite1: blocks |
| 138 | + ASSERT_EQ(sprite1->greenFlagBlocks().size(), 1); |
| 139 | + ASSERT_TRUE(sprite1->greenFlagBlocks()[0]); |
| 140 | + ASSERT_EQ(sprite1->greenFlagBlocks()[0]->opcode(), "event_whenflagclicked"); |
| 141 | + auto block = sprite1->greenFlagBlocks()[0]->next(); |
| 142 | + ASSERT_TRUE(block); |
| 143 | + ASSERT_EQ(block->parent(), sprite1->greenFlagBlocks()[0]); |
| 144 | + ASSERT_EQ(block->opcode(), "control_forever"); |
| 145 | + ASSERT_INPUT(block, "SUBSTACK"); |
| 146 | + block = GET_INPUT(block, "SUBSTACK")->valueBlock(); |
| 147 | + ASSERT_TRUE(block); |
| 148 | + |
| 149 | + ASSERT_EQ(block->opcode(), "motion_goto"); |
| 150 | + ASSERT_INPUT(block, "TO"); |
| 151 | + auto inputBlock = GET_INPUT(block, "TO")->valueBlock(); |
| 152 | + ASSERT_TRUE(inputBlock); |
| 153 | + ASSERT_EQ(inputBlock->opcode(), "motion_goto_menu"); |
| 154 | + ASSERT_FIELD(inputBlock, "TO"); |
| 155 | + ASSERT_EQ(GET_FIELD(inputBlock, "TO")->value().toString(), "_random_"); |
| 156 | + block = block->next(); |
| 157 | + ASSERT_TRUE(block); |
| 158 | + |
| 159 | + ASSERT_EQ(block->opcode(), "data_setvariableto"); |
| 160 | + ASSERT_FIELD(block, "VARIABLE"); |
| 161 | + ASSERT_EQ(GET_FIELD(block, "VARIABLE")->valuePtr(), GET_VAR(stage, "var1")); |
| 162 | + ASSERT_INPUT(block, "VALUE"); |
| 163 | + ASSERT_EQ(GET_INPUT(block, "VALUE")->primaryValue()->value().toInt(), 0); |
| 164 | + block = block->next(); |
| 165 | + ASSERT_TRUE(block); |
| 166 | + |
| 167 | + ASSERT_INPUT(block, "VALUE"); |
| 168 | + inputBlock = GET_INPUT(block, "VALUE")->valueBlock(); |
| 169 | + ASSERT_TRUE(inputBlock); |
| 170 | + ASSERT_EQ(inputBlock->opcode(), "operator_random"); |
| 171 | + ASSERT_INPUT(inputBlock, "FROM"); |
| 172 | + ASSERT_EQ(GET_INPUT(inputBlock, "FROM")->primaryValue()->value().toInt(), 1); |
| 173 | + ASSERT_INPUT(inputBlock, "TO"); |
| 174 | + ASSERT_EQ(GET_INPUT(inputBlock, "TO")->primaryValue()->value().toInt(), 10); |
| 175 | + block = block->next(); |
| 176 | + ASSERT_TRUE(block); |
| 177 | + |
| 178 | + ASSERT_EQ(block->opcode(), "procedures_call"); |
| 179 | + auto prototype = block->mutationPrototype(); |
| 180 | + ASSERT_TRUE(prototype->warp()); |
| 181 | + ASSERT_EQ(prototype->argumentIds().size(), 2); |
| 182 | + ASSERT_EQ(prototype->procCode(), "custom block %s %b"); |
| 183 | + ASSERT_INPUT(block, prototype->argumentIds()[0]); |
| 184 | + ASSERT_EQ(GET_INPUT(block, prototype->argumentIds()[0])->primaryValue()->value().toString(), "test"); |
| 185 | + ASSERT_INPUT(block, prototype->argumentIds()[1]); |
| 186 | + ASSERT_TRUE(GET_INPUT(block, prototype->argumentIds()[1])->valueBlock()); |
| 187 | + |
| 188 | + ASSERT_FALSE(block->next()); |
| 189 | + |
| 190 | + auto blocks = sprite1->blocks(); |
| 191 | + std::shared_ptr<Block> defineBlock = nullptr; |
| 192 | + for (auto b : blocks) { |
| 193 | + if (b->opcode() == "procedures_definition") { |
| 194 | + defineBlock = b; |
| 195 | + break; |
| 196 | + } |
| 197 | + } |
| 198 | + ASSERT_TRUE(defineBlock); |
| 199 | + ASSERT_INPUT(defineBlock, "custom_block"); |
| 200 | + auto blockPrototype = GET_INPUT(defineBlock, "custom_block")->valueBlock(); |
| 201 | + ASSERT_TRUE(blockPrototype); |
| 202 | + prototype = blockPrototype->mutationPrototype(); |
| 203 | + ASSERT_TRUE(prototype->warp()); |
| 204 | + ASSERT_EQ(prototype->argumentIds().size(), 2); |
| 205 | + ASSERT_EQ(prototype->argumentNames(), std::vector<std::string>({ "num or text", "bool" })); |
| 206 | + ASSERT_EQ(prototype->argumentTypes(), std::vector<BlockPrototype::ArgType>({ BlockPrototype::ArgType::StringNum, BlockPrototype::ArgType::Bool })); |
| 207 | + ASSERT_EQ(prototype->argumentDefaults(), std::vector<Value>({ 0, false })); |
| 208 | + ASSERT_EQ(prototype->procCode(), "custom block %s %b"); |
| 209 | + ASSERT_INPUT(blockPrototype, prototype->argumentIds()[0]); |
| 210 | + auto argBlock = GET_INPUT(blockPrototype, prototype->argumentIds()[0])->valueBlock(); |
| 211 | + ASSERT_TRUE(argBlock); |
| 212 | + ASSERT_EQ(argBlock->opcode(), "argument_reporter_string_number"); |
| 213 | + ASSERT_INPUT(blockPrototype, prototype->argumentIds()[1]); |
| 214 | + argBlock = GET_INPUT(blockPrototype, prototype->argumentIds()[1])->valueBlock(); |
| 215 | + ASSERT_TRUE(argBlock); |
| 216 | + ASSERT_EQ(argBlock->opcode(), "argument_reporter_boolean"); |
| 217 | + block = defineBlock->next(); |
| 218 | + ASSERT_TRUE(block); |
| 219 | + |
| 220 | + ASSERT_EQ(block->opcode(), "data_addtolist"); |
| 221 | + ASSERT_FIELD(block, "LIST"); |
| 222 | + ASSERT_EQ(GET_FIELD(block, "LIST")->valuePtr(), GET_LIST(stage, "list1")); |
| 223 | + ASSERT_INPUT(block, "ITEM"); |
| 224 | + argBlock = GET_INPUT(block, "ITEM")->valueBlock(); |
| 225 | + ASSERT_TRUE(argBlock); |
| 226 | + ASSERT_EQ(argBlock->opcode(), "argument_reporter_string_number"); |
| 227 | + |
| 228 | + // Balloon1 |
| 229 | + ASSERT_NE(engine.findTarget("Balloon1"), -1); |
| 230 | + Sprite *sprite2 = dynamic_cast<Sprite *>(engine.targetAt(engine.findTarget("Balloon1"))); |
| 231 | + ASSERT_FALSE(sprite2->isStage()); |
| 232 | + ASSERT_EQ(sprite2->name(), "Balloon1"); |
| 233 | + ASSERT_EQ(sprite2->variables().size(), 1); |
| 234 | + ASSERT_EQ(sprite2->lists().size(), 1); |
| 235 | + ASSERT_EQ(sprite2->blocks().size(), 5); |
| 236 | + ASSERT_EQ(sprite2->costumes().size(), 3); |
| 237 | + ASSERT_EQ(sprite2->sounds().size(), 1); |
| 238 | + ASSERT_FALSE(sprite2->visible()); |
| 239 | + ASSERT_EQ(std::round(sprite2->x()), 143); |
| 240 | + ASSERT_EQ(std::round(sprite2->y()), 13); |
| 241 | + ASSERT_EQ(sprite2->size(), 75); |
| 242 | + ASSERT_EQ(sprite2->direction(), 45); |
| 243 | + ASSERT_TRUE(sprite2->draggable()); |
| 244 | + ASSERT_EQ(sprite2->rotationStyleStr(), "don't rotate"); |
| 245 | + ASSERT_EQ(sprite2->rotationStyle(), Sprite::RotationStyle::DoNotRotate); |
| 246 | + |
| 247 | + // Balloon1: sounds |
| 248 | + sound = sprite2->soundAt(0); |
| 249 | + ASSERT_EQ(sound.name(), "Pop"); |
| 250 | + ASSERT_FALSE(sound.assetId().empty()); |
| 251 | + ASSERT_EQ(sound.md5ext(), sound.assetId() + ".wav"); |
| 252 | + ASSERT_EQ(sound.dataFormat(), "wav"); |
| 253 | + |
| 254 | + // Balloon1: variables |
| 255 | + ASSERT_VAR(sprite2, "var2"); |
| 256 | + ASSERT_TRUE(GET_VAR(sprite2, "var2")->value().toBool()); |
| 257 | + |
| 258 | + // Balloon1: lists |
| 259 | + ASSERT_LIST(sprite2, "list2"); |
| 260 | + ASSERT_EQ(GET_LIST(sprite2, "list2")->toString(), "0 4 3 4 1 5 6 9 4 8"); |
| 261 | + } |
| 262 | +} |
0 commit comments