void SpawnerBaseConfigSpec::Define()

in Ambit/Source/Ambit/Actors/SpawnerConfigs/SpawnerBaseConfig.spec.cpp [33:529]


void SpawnerBaseConfigSpec::Define()
{
    Describe("SerializeToJson()", [this]()
    {
        BeforeEach([this]()
        {
            // Create a fresh SpawnerBaseConfiguration instance.
            Config = FSpawnerBaseConfig{};
        });

        Describe("the returned JsonObject", [this]()
        {
            It("when MatchBy value is EMatchBy::NameOrTags, " "JSON has a MatchBy value of 'NameOrTags'", [this]()
            {
                Config.MatchBy = NameOrTags;

                const FString Value = Config.SerializeToJson()->GetStringField("MatchBy");

                TestEqual("MatchBy", Value, "NameOrTags");
            });

            It("when MatchBy value is EMatchBy::NameAndTags, " "JSON has a MatchBy value of 'NameAndTags'", [this]()
            {
                Config.MatchBy = NameAndTags;

                const FString Value = Config.SerializeToJson()->GetStringField("MatchBy");

                TestEqual("MatchBy", Value, "NameAndTags");
            });

            It("has SurfaceNamePattern value", [this]()
            {
                const FString ExpectedValue = "Sample name pattern";
                Config.SurfaceNamePattern = ExpectedValue;

                const FString Value = Config.SerializeToJson()->GetStringField("SurfaceNamePattern");

                TestEqual("SurfaceNamePattern", Value, ExpectedValue);
            });

            It("given one SurfaceTags item, JSON SurfaceTags contains that tag", [this]()
            {
                Config.SurfaceTags.Add(FName("TagA"));

                TArray<TSharedPtr<FJsonValue>> TagsAsJsonValues = Config.SerializeToJson()->
                                                                         GetArrayField("SurfaceTags");

                TestEqual("tag", TagsAsJsonValues[0]->AsString(), "TagA");
            });

            It("given multiple SurfaceTags items, JSON SurfaceTags contains all tags", [this]()
            {
                Config.SurfaceTags.Add(FName("TagA"));
                Config.SurfaceTags.Add(FName("TagB"));

                TArray<TSharedPtr<FJsonValue>> TagsAsJsonValues = Config.SerializeToJson()->
                                                                         GetArrayField("SurfaceTags");

                const int TagCount = TagsAsJsonValues.Num();
                if (TagCount != 2)
                {
                    const FString Message = FString::Printf(TEXT("incorrect array length %d"), TagCount);
                    AddError(Message);
                }
                else
                {
                    TestEqual("tag 0", TagsAsJsonValues[0]->AsString(), "TagA");
                    TestEqual("tag 1", TagsAsJsonValues[1]->AsString(), "TagB");
                }
            });

            It("has SpawnerLocation value", [this]()
            {
                const FVector ExpectedLocation(0, 0, 40.0);
                Config.SpawnerLocation = ExpectedLocation;

                const FVector Result = FJsonHelpers::DeserializeToVector3(
                    Config.SerializeToJson()->GetArrayField("SpawnerLocation"));

                TestEqual("SpawnerLocation", Result, ExpectedLocation);
            });

            It("has SpawnerRotation value", [this]()
            {
                const FRotator ExpectedRotation(0, 30, 0);
                Config.SpawnerRotation = ExpectedRotation;

                const FRotator Result = FJsonHelpers::DeserializeToRotation(
                    Config.SerializeToJson()->GetArrayField("SpawnerRotation"));

                TestEqual("SpawnerRotation", Result, ExpectedRotation);
            });

            It("has DensityMin value", [this]()
            {
                const double ExpectedValue = 0.345;
                Config.DensityMin = ExpectedValue;

                const double Result = Config.SerializeToJson()->GetNumberField("DensityMin");

                TestEqual("density", Result, ExpectedValue);
            });

            It("has DensityMax value", [this]()
            {
                const double ExpectedValue = 0.123;
                Config.DensityMax = ExpectedValue;

                const double Result = Config.SerializeToJson()->GetNumberField("DensityMax");

                TestEqual("density", Result, ExpectedValue);
            });

            It("has RotationMin value", [this]()
            {
                const double ExpectedValue = 0.345;
                Config.RotationMin = ExpectedValue;

                const double Result = Config.SerializeToJson()->GetNumberField("RotationMin");

                TestEqual("rotation", Result, ExpectedValue);
            });

            It("has RotationMax value", [this]()
            {
                const double ExpectedValue = 0.123;
                Config.RotationMax = ExpectedValue;

                const double Result = Config.SerializeToJson()->GetNumberField("RotationMax");

                TestEqual("rotation", Result, ExpectedValue);
            });

            It("has AddPhysics value", [this]()
            {
                Config.bAddPhysics = true;

                const bool Result = Config.SerializeToJson()->GetBoolField("AddPhysics");

                TestTrue("add physics", Result);
            });

            It("has ActorsToSpawn value when it has one valid element", [this]()
            {
                // Set a known AActor subclass as the ActorsToSpawn value.
                const FString ExpectedValue = "/Ambit/Test/Props/BP_Box01.BP_Box01_C";
                const FSoftClassPath ClassPath(ExpectedValue);
                TArray<TSubclassOf<AActor>> ExpectedActors;
                const TSubclassOf<AActor>& ExpectedActor = ClassPath.TryLoadClass<UObject>();
                ExpectedActors.Add(ExpectedActor);
                Config.ActorsToSpawn = ExpectedActors;

                const TArray<TSharedPtr<FJsonValue>> Result = Config.SerializeToJson()->GetArrayField("ActorsToSpawn");

                TestEqual("class name", Result[0]->AsString(), ExpectedValue);
            });

            It("sets ActorsToSpawn json to null if ActorsToSpawn has one nullptr element", [this]()
            {
                AddExpectedError("An element of the ActorsToSpawn set is not specified.",
                                 EAutomationExpectedErrorFlags::Exact, 1);
                TArray<TSubclassOf<AActor>> ExpectedActors;
                ExpectedActors.Add(nullptr);
                Config.ActorsToSpawn = ExpectedActors;

                TestTrue("class name", Config.SerializeToJson()->HasTypedField<EJson::Null>("ActorsToSpawn"));
            });

            It("does not add array field for ActorsToSpawn in Json if said element is nullptr", [this]()
            {
                AddExpectedError("An element of the ActorsToSpawn set is not specified.",
                                 EAutomationExpectedErrorFlags::Exact, 1);

                const FString ExpectedValue = "/Ambit/Test/Props/BP_Box01.BP_Box01_C";
                const FSoftClassPath ClassPath(ExpectedValue);
                TArray<TSubclassOf<AActor>> ExpectedActors;
                const TSubclassOf<AActor>& ExpectedActor = ClassPath.TryLoadClass<UObject>();
                ExpectedActors.Add(ExpectedActor);
                ExpectedActors.Add(nullptr);
                Config.ActorsToSpawn = ExpectedActors;

                TestTrue("class name", Config.SerializeToJson()->HasTypedField<EJson::Null>("ActorsToSpawn"));
            });

            It("has ActorsToSpawn value when it has multiple valid elements", [this]()
            {
                // Set known AActor subclasses as the ActorsToSpawn value.

                TArray<TSubclassOf<AActor>> ExpectedActors;
                TSet<FString> Expected;
                const FString ExpectedValueOne = "/Ambit/Test/Props/BP_Box01.BP_Box01_C";
                const FSoftClassPath ClassPathOne(ExpectedValueOne);
                const TSubclassOf<AActor> ExpectedActorOne = ClassPathOne.TryLoadClass<UObject>();
                const FString ExpectedValueTwo = "/Engine/EngineSky/BP_Sky_Sphere.BP_Sky_Sphere_C";
                const FSoftClassPath ClassPathTwo(ExpectedValueTwo);
                const TSubclassOf<AActor> ExpectedActorTwo = ClassPathTwo.TryLoadClass<UObject>();

                ExpectedActors.Add(ExpectedActorOne);
                ExpectedActors.Add(ExpectedActorTwo);
                Expected.Add(ExpectedValueOne);
                Expected.Add(ExpectedValueTwo);

                Config.ActorsToSpawn = ExpectedActors;

                const TArray<TSharedPtr<FJsonValue>> ResultJson = Config.SerializeToJson()->GetArrayField(
                    "ActorsToSpawn");

                TSet<FString> Result;
                for (const auto& Json : ResultJson)
                {
                    Result.Add(Json->AsString());
                }

                TestTrue("class name", Result.Includes(Expected) && Expected.Includes(Result));
            });

            It("when ActorsToSpawn is null, JSON ActorsToSpawn is null", [this]()
            {
                const TSharedPtr<FJsonValue> Result = Config.SerializeToJson()->GetField<EJson::None>("ActorsToSpawn");

                TestTrue("is null", Result->Type == EJson::Null);
            });

            It("has RemoveOverlaps", [this]()
            {
                Config.bRemoveOverlaps = false;
                const bool Result = Config.SerializeToJson()->GetBoolField("RemoveOverlaps");

                TestFalse("remove overlaps", Result);
            });

            It("has RandomSeed value", [this]()
            {
                const int ExpectedValue = 5;
                Config.RandomSeed = ExpectedValue;

                const int Result = Config.SerializeToJson()->GetNumberField("RandomSeed");

                TestEqual("random seed", Result, ExpectedValue);
            });
        });
    });

    Describe("DeserializeFromJson()", [this]()
    {
        BeforeEach([this]()
        {
            // Create fresh config value.
            Config = FSpawnerBaseConfig{};

            // Create a sample SpawnerBaseConfiguration JSON object.
            // NOTE: We're wrapping JSON keys and strings in single quotes to improve
            // readability. Then we replace all single quotes with double quotes (per the
            // JSON standard) before parsing the string to a JSON object.
            const FString JsonString = FString(
                "{"
                "    'SpawnerLocation': [0, 0, 0],"
                "    'SpawnerRotation': [0, 0, 0],"
                "    'MatchBy': 'NameOrTags',"
                "    'SurfaceNamePattern': 'MyPattern',"
                "    'SurfaceTags': [],"
                "    'DensityMin': 0.1,"
                "    'DensityMax': 0.4,"
                "    'RotationMin': 45.0,"
                "    'RotationMax': 90.0,"
                "    'AddPhysics': false,"
                "    'ActorsToSpawn': null,"
                "    'RemoveOverlaps': true,"
                "    'RandomSeed': 5"
                "}")
                .Replace(TEXT("'"), TEXT("\""));
            Json = FJsonHelpers::DeserializeJson(JsonString);
        });

        It("when JSON MatchBy is 'NameOrTags', sets MatchBy correctly", [this]()
        {
            Json->SetStringField("MatchBy", "NameOrTags");

            Config.DeserializeFromJson(Json);

            TestEqual("MatchBy", Config.MatchBy, EMatchBy::NameOrTags);
        });

        It("when JSON MatchBy is 'NameAndTags', sets MatchBy correctly", [this]()
        {
            Json->SetStringField("MatchBy", "NameAndTags");

            Config.DeserializeFromJson(Json);

            TestEqual("MatchBy", Config.MatchBy, EMatchBy::NameAndTags);
        });

        It("sets SurfaceNamePattern", [this]()
        {
            const FString ExpectedValue = "SampleName";
            Json->SetStringField("SurfaceNamePattern", ExpectedValue);

            Config.DeserializeFromJson(Json);

            TestEqual("SurfaceNamePattern", Config.SurfaceNamePattern, ExpectedValue);
        });

        It("given one surface tag, sets SurfaceTags correctly", [this]()
        {
            TArray<TSharedPtr<FJsonValue>> TagsJson;
            TagsJson.Add(MakeShareable(new FJsonValueString("TagA")));
            Json->SetArrayField("SurfaceTags", TagsJson);

            Config.DeserializeFromJson(Json);

            const int32 TagCount = Config.SurfaceTags.Num();
            if (TagCount != 1)
            {
                FString Message = "Expected SurfaceTags count of 1 but was ";
                Message.AppendInt(TagCount);
                AddError(Message);
            }
            else
            {
                TestEqual("first tag", Config.SurfaceTags[0], FName("TagA"));
            }
        });

        It("given multiple surface tags, sets SurfaceTags correctly", [this]()
        {
            TArray<TSharedPtr<FJsonValue>> TagsJson;
            TagsJson.Add(MakeShareable(new FJsonValueString("TagA")));
            TagsJson.Add(MakeShareable(new FJsonValueString("TagB")));
            Json->SetArrayField("SurfaceTags", TagsJson);

            Config.DeserializeFromJson(Json);

            const int32 TagCount = Config.SurfaceTags.Num();
            if (TagCount != 2)
            {
                FString Message = "Expected SurfaceTags count of 2 but was ";
                Message.AppendInt(TagCount);
                AddError(Message);
            }
            else
            {
                TestEqual("first tag", Config.SurfaceTags[0], FName("TagA"));
                TestEqual("second tag", Config.SurfaceTags[1], FName("TagB"));
            }
        });

        It("when called multiple times, SurfaceTags should not accumulate tags", [this]()
        {
            TArray<TSharedPtr<FJsonValue>> TagsJson;
            TagsJson.Add(MakeShareable(new FJsonValueString("TagA")));
            Json->SetArrayField("SurfaceTags", TagsJson);

            Config.DeserializeFromJson(Json);
            // Intentional second call.
            Config.DeserializeFromJson(Json);

            TestEqual("tag count", Config.SurfaceTags.Num(), 1);
        });

        It("sets SpawnerLocation value", [this]()
        {
            const FVector ExpectedLocation(100, 100, 0);
            Json->SetArrayField("SpawnerLocation", FJsonHelpers::SerializeVector3(ExpectedLocation));
            Config.DeserializeFromJson(Json);
            TestEqual("SpawnerLocation", Config.SpawnerLocation, ExpectedLocation);
        });

        It("sets SpawnerRotation value", [this]()
        {
            const FRotator ExpectedRotation(0, 30, 0);
            Json->SetArrayField("SpawnerRotation", FJsonHelpers::SerializeRotation(ExpectedRotation));
            Config.DeserializeFromJson(Json);
            TestEqual("SpawnerRotation", Config.SpawnerRotation, ExpectedRotation);
        });

        It("sets DensityMin", [this]()
        {
            Json->SetNumberField("DensityMin", 0.1);

            Config.DeserializeFromJson(Json);

            TestEqual("DensityMin", Config.DensityMin, 0.1f);
        });

        It("sets DensityMax", [this]()
        {
            Json->SetNumberField("DensityMax", 0.9);

            Config.DeserializeFromJson(Json);

            TestEqual("DensityMax", Config.DensityMax, 0.9f);
        });

        It("sets RotationMin", [this]()
        {
            Json->SetNumberField("RotationMin", 45.0);

            Config.DeserializeFromJson(Json);

            TestEqual("RotationMin", Config.RotationMin, 45.0f);
        });

        It("sets RotationMax", [this]()
        {
            Json->SetNumberField("RotationMax", 90.0);

            Config.DeserializeFromJson(Json);

            TestEqual("RotationMax", Config.RotationMax, 90.0f);
        });

        It("sets AddPhysics", [this]()
        {
            Json->SetBoolField("AddPhysics", true);
            Config.DeserializeFromJson(Json);
            TestTrue("AddPhysics", Config.bAddPhysics);
        });

        It("when JSON ActorsToSpawn is valid array with one element," " sets ActorsToSpawn correctly", [this]()
        {
            // Get a reference to a sample AActor subclass.
            const FString ActorPath = "/Ambit/Test/Props/BP_Box01.BP_Box01_C";
            const FSoftClassPath ClassPath(ActorPath);
            const TSubclassOf<AActor> ExpectedValue = ClassPath.TryLoadClass<UObject>();
            TArray<TSubclassOf<AActor>> ExpectedActors;
            TArray<TSharedPtr<FJsonValue>> ExpectedJson;
            ExpectedJson.Add(MakeShareable(new FJsonValueString(ActorPath)));
            ExpectedActors.Add(ExpectedValue);
            Json->SetArrayField("ActorsToSpawn", ExpectedJson);

            Config.DeserializeFromJson(Json);

            TestEqual("ActorsToSpawn", Config.ActorsToSpawn.Num(), ExpectedActors.Num());
            for (int32 i = 0; i < ExpectedActors.Num(); i++)
            {
                TestEqual("ActorsToSpawn", Config.ActorsToSpawn[i], ExpectedActors[i]);
            }
        });

        It("when JSON ActorsToSpawn is valid array with multiple elements," " sets ActorsToSpawn correctly", [this]()
        {
            // Get a reference to a sample AActor subclass.
            const FString ActorPathOne = "/Ambit/Test/Props/BP_Box01.BP_Box01_C";
            const FSoftClassPath ClassPathOne(ActorPathOne);
            const TSubclassOf<AActor> ExpectedValueOne = ClassPathOne.TryLoadClass<UObject>();
            const FString ActorPathTwo = "/Engine/EngineSky/BP_Sky_Sphere.BP_Sky_Sphere_C";
            const FSoftClassPath ClassPathTwo(ActorPathTwo);
            const TSubclassOf<AActor> ExpectedValueTwo = ClassPathTwo.TryLoadClass<UObject>();
            TArray<TSubclassOf<AActor>> ExpectedActors;
            TArray<TSharedPtr<FJsonValue>> ExpectedJson;
            ExpectedJson.Add(MakeShareable(new FJsonValueString(ActorPathOne)));
            ExpectedActors.Add(ExpectedValueOne);
            ExpectedJson.Add(MakeShareable(new FJsonValueString(ActorPathTwo)));
            ExpectedActors.Add(ExpectedValueTwo);
            Json->SetArrayField("ActorsToSpawn", ExpectedJson);

            Config.DeserializeFromJson(Json);
            TestEqual("ActorsToSpawn", Config.ActorsToSpawn.Num(), ExpectedActors.Num());
            for (int32 i = 0; i < ExpectedActors.Num(); i++)
            {
                TestEqual("ActorsToSpawn", Config.ActorsToSpawn[i], ExpectedActors[i]);
            }
        });

        It("when called multiple times should not accumulate ActorsToSpawn", [this]()
        {
            // Get a reference to a sample AActor subclass.
            const FString ActorPath = "/Ambit/Test/Props/BP_Box01.BP_Box01_C";
            TArray<TSharedPtr<FJsonValue>> ExpectedJson;
            ExpectedJson.Add(MakeShareable(new FJsonValueString(ActorPath)));
            Json->SetArrayField("ActorsToSpawn", ExpectedJson);

            Config.DeserializeFromJson(Json);
            // Intentional second call
            Config.DeserializeFromJson(Json);

            TestEqual("ActorsToSpawn", Config.ActorsToSpawn.Num(), 1);
        });

        It("sets RemoveOverlaps", [this]()
        {
            Json->SetBoolField("RemoveOverlaps", false);
            Config.DeserializeFromJson(Json);

            TestFalse("RemoveOverlaps", Config.bRemoveOverlaps);
        });

        It("sets RandomSeed", [this]()
        {
            Json->SetNumberField("RandomSeed", 12);

            Config.DeserializeFromJson(Json);

            TestEqual("RandomSeed", Config.RandomSeed, 12);
        });
    });
}