logo SERENITY
Game Systems Foundation
Search Results for

    Show / Hide Table of Contents

    Class WaveCreatorTests

    EditMode tests for WaveCreator. Every test targets fixture paths under a private temp folder — the real project's own Wave/Stage/Character assets, if any, are never touched — and every test passes an explicit DuplicateIdSearchFolder/ CharacterPrefabSearchFolder pinned to Serenity.Wave.Tests.Editor.Builder.WaveCreatorTests.TempFolder so every project-wide scan is hermetic by construction.

    Inheritance
    object
    WaveCreatorTests
    Inherited Members
    object.ToString()
    object.Equals(object)
    object.Equals(object, object)
    object.ReferenceEquals(object, object)
    object.GetHashCode()
    object.GetType()
    object.MemberwiseClone()
    Namespace: Serenity.Wave.Tests.Editor.Builder
    Assembly: Serenity.Wave.Tests.Editor.dll
    Syntax
    public class WaveCreatorTests

    Constructors

    WaveCreatorTests()

    Declaration
    public WaveCreatorTests()

    Methods

    Create_ChangedWaveIdNotMatchingAnyExistingAssetInFolder_CreatesANewAssetInsteadOfEditing()

    Declaration
    public void Create_ChangedWaveIdNotMatchingAnyExistingAssetInFolder_CreatesANewAssetInsteadOfEditing()

    Create_ExistingAssetFileRemovedBeforeSecondRun_RollsBackTheFullFieldSet()

    Declaration
    public void Create_ExistingAssetFileRemovedBeforeSecondRun_RollsBackTheFullFieldSet()

    Create_ExistingAssetSeededOnEverySerializedField_RestoresThemAll()

    Field-by-field regression guard for the generic capture Create(Input) now rolls back through (transaction.TrackExistingAsset), replacing the hand-rolled WaveConfigSnapshot / SpawnEntrySnapshot classes this migration deleted. Writes a distinct value into EVERY serialized field of a real UnityWaveConfigSO via reflection — including the nested UnitySpawnEntry list ApplyWaveFields only ever rewrites through UnityEditor.SerializedProperty, never by field name — forces the update run to fail via the same planted-and-deleted-file technique Create_ExistingAssetFileRemovedBeforeSecondRun_RollsBackTheFullFieldSet() uses, and asserts every field comes back. Proves the capture covers whatever the type declares, not a hand-maintained field list a future field addition could silently outgrow.

    Declaration
    public void Create_ExistingAssetSeededOnEverySerializedField_RestoresThemAll()

    Create_ExistingSetReorderedAndExtendedFailsAtItsOwnVerify_RestoresOriginalOrderAndRollsBackTheNewWave()

    CRITICAL coverage for the Wave Set's existing-asset restore, previously unreachable from this suite. The first run creates a two-wave set [wave_a, wave_b]. The second run mutates the EXISTING set through ApplyWaveSetFields — reordering to [wave_b, wave_a] AND adding a brand-new third wave (wave_c) — then the set's own VerifyAssetWasWrittenToDisk fails (its file was deleted directly off disk, the same "existing asset, file removed" technique as the wave-config rollback test above — a pure C# throw, no Debug.LogError, so no LogAssert is needed here either).

    ApplyWaveSetFields wrote a live reference to wave_c into the Wave Set before the verify-and-throw step, but transaction.Rollback() replays the Wave Set's captured transaction.TrackExistingAsset restore FIRST, undoing that reference, and only then deletes the freshly-created wave_c asset. The assertion that wave_c's file is actually gone from disk confirms the delete still runs on schedule once the restore has cleared the way.

    Declaration
    public void Create_ExistingSetReorderedAndExtendedFailsAtItsOwnVerify_RestoresOriginalOrderAndRollsBackTheNewWave()

    Create_ExistingWaveSetLosesAWave_WarnsUnlinkButLeavesTheFileOnDisk()

    Declaration
    public void Create_ExistingWaveSetLosesAWave_WarnsUnlinkButLeavesTheFileOnDisk()

    Create_FreshRunIntoMissingFolder_CreatesTheFolderLazily()

    Proves the two top-of-Create ensures that were removed are not load-bearing: with neither the stage folder nor its "Waves" subfolder existing beforehand, a fresh run that resolves at least one wave still creates both, lazily, through the by-Id resolves. The wave (referencing "sp_a", the same spawn point id used elsewhere in this file) is required for the "Waves" assertion to be backed by a real write — a missing stage folder only degrades spawn-point validation to a warning, it never blocks Create(), so no stage prefab fixture is needed for this to succeed. This is the only one of this pair that can actually fail if the lazy ensure inside SettingsAssetResolution.ResolveOrCreateByIdInFolder were missing.

    Declaration
    public void Create_FreshRunIntoMissingFolder_CreatesTheFolderLazily()

    Create_FreshRunWithoutWaves_DoesNotCreateTheWavesFolder()

    This is the stray-folder improvement the deleted top-of-Create ensure used to violate: with zero waves, the resolve-into-wavesFolder loop never runs, so nothing ever asks SettingsAssetResolution.ResolveOrCreateByIdInFolder to ensure the "Waves" subfolder — and with the unconditional ensure gone, nothing else does either. Before this PR, SettingsAssetAuthoring.EnsureProjectFolderExists(wavesFolder, ...) ran unconditionally at the top of Create, so a zero-wave run still left a stray empty "Waves" folder behind.

    Declaration
    public void Create_FreshRunWithoutWaves_DoesNotCreateTheWavesFolder()

    Create_OrderIsInputOrderNotWaveIndexOrder()

    Declaration
    public void Create_OrderIsInputOrderNotWaveIndexOrder()

    Create_RegisterAsAddressablesFalse_LeavesAddressablesUntouched()

    Declaration
    public void Create_RegisterAsAddressablesFalse_LeavesAddressablesUntouched()

    Create_RegisterAsAddressablesTrue_TagsBothWaveConfigsAndWaveSetWithModuleWave()

    Declaration
    public void Create_RegisterAsAddressablesTrue_TagsBothWaveConfigsAndWaveSetWithModuleWave()

    Create_ReorderingAnExistingSet_PersistsTheNewOrder()

    Declaration
    public void Create_ReorderingAnExistingSet_PersistsTheNewOrder()

    Create_ToConfig_TotalActorsEqualsEntryCountNotTheActorSum()

    Declaration
    public void Create_ToConfig_TotalActorsEqualsEntryCountNotTheActorSum()

    Create_TwoWaveSetsWithDefaultIdsOnOneStage_DoNotShareWaveAssets()

    Declaration
    public void Create_TwoWaveSetsWithDefaultIdsOnOneStage_DoNotShareWaveAssets()

    Create_UpdateRunAgainstExistingAssets_CreatesNoNewFolder()

    Locks the invariant the deletion relies on: once both folders already exist, an update run against the same Id creates no new subfolder anywhere under the fixture. This test alone would not have caught a missing lazy ensure (the folders already exist from the first run) — it only guards against a regression that makes the update path write where it should not.

    Declaration
    public void Create_UpdateRunAgainstExistingAssets_CreatesNoNewFolder()

    Create_UpdatingExistingAssets_PreservesTheOriginalGuids()

    Declaration
    public void Create_UpdatingExistingAssets_PreservesTheOriginalGuids()

    Create_ValidInput_AssignsANonEmptyGuidToEachAsset()

    Declaration
    public void Create_ValidInput_AssignsANonEmptyGuidToEachAsset()

    Create_ValidInput_CreatesWaveConfigsAndOrderedWaveSet()

    Declaration
    public void Create_ValidInput_CreatesWaveConfigsAndOrderedWaveSet()

    Create_ValidInput_SetsNeedsCompositionRootWiringTrue()

    Declaration
    public void Create_ValidInput_SetsNeedsCompositionRootWiringTrue()

    OneTimeTearDown()

    Declaration
    public void OneTimeTearDown()

    Preview_ExistingAssets_ReportsNotNewWithTheirRealPaths()

    Declaration
    public void Preview_ExistingAssets_ReportsNotNewWithTheirRealPaths()

    Preview_MultiCountEntry_ReportsEntryCountDifferentFromActorSum()

    Declaration
    public void Preview_MultiCountEntry_ReportsEntryCountDifferentFromActorSum()

    Preview_NeverMutatesTheProject()

    Declaration
    public void Preview_NeverMutatesTheProject()

    SetUp()

    Declaration
    public void SetUp()

    TearDown()

    Declaration
    public void TearDown()

    Validate_ActorMatchingAKnownCharacterPrefab_DoesNotWarnAboutIt()

    Declaration
    public void Validate_ActorMatchingAKnownCharacterPrefab_DoesNotWarnAboutIt()

    Validate_ActorNotAKnownCharacterPrefab_WarnsWithoutBlocking()

    Declaration
    public void Validate_ActorNotAKnownCharacterPrefab_WarnsWithoutBlocking()

    Validate_CaseVariantWaveIdInSameFolder_IsRejectedBecauseAssetFileNamesCollide()

    Serenity Ids are case-sensitive, but a wave's Id also becomes its asset FILE NAME (NamingSanitizer.FileNameSafe), and file names are NOT case-sensitive on Windows/macOS. So 'wave_orange' and 'Wave_Orange' would collide on disk — one asset silently overwriting the other — even though they are different, unrelated Ids at runtime. Validate(Input) must reject this with an error (not merely warn), mirroring PrefetchPolicyCreator's own case-insensitive uniqueness check, and Create(Input) must refuse to run at all.

    Declaration
    public void Validate_CaseVariantWaveIdInSameFolder_IsRejectedBecauseAssetFileNamesCollide()

    Validate_DuplicateWaveConfigIdElsewhereDifferingOnlyByCase_StillWarns()

    A case-variant Id in ANOTHER folder is not the file-name-collision defect Validate_CaseVariantWaveIdInSameFolder_IsRejectedBecauseAssetFileNamesCollide() covers — two different folders can each hold their own same-named file on disk. It is also not a runtime ambiguity — Serenity Ids are case-sensitive, so 'wave_orange' and 'Wave_Orange' resolve as two distinct, unambiguous waves. It is still worth a WARNING (never an error) because it is very likely a typo — see Serenity.Global.Infrastructure.Editor.SerenityAssetIdRules.WarnAboutCaseVariantElsewhere``1(Serenity.Global.Infrastructure.Editor.SerenityAssetIdLookup.IdMatches{``0},System.String,System.String,Serenity.Global.Infrastructure.Editor.SerenityCreationValidationResult).

    Declaration
    public void Validate_DuplicateWaveConfigIdElsewhereDifferingOnlyByCase_StillWarns()

    Validate_DuplicateWaveConfigIdElsewhere_WarnsWithoutBlocking()

    Declaration
    public void Validate_DuplicateWaveConfigIdElsewhere_WarnsWithoutBlocking()

    Validate_DuplicateWaveIdWithinBatch_ReturnsError()

    Declaration
    public void Validate_DuplicateWaveIdWithinBatch_ReturnsError()

    Validate_DuplicateWaveIndexAcrossWaves_WarnsWithoutBlocking()

    Declaration
    public void Validate_DuplicateWaveIndexAcrossWaves_WarnsWithoutBlocking()

    Validate_DuplicateWaveSetIdElsewhere_WarnsWithoutBlocking()

    Declaration
    public void Validate_DuplicateWaveSetIdElsewhere_WarnsWithoutBlocking()

    Validate_EmptyWaveId_ReturnsError()

    Declaration
    public void Validate_EmptyWaveId_ReturnsError()

    Validate_EmptyWaveSetId_ReturnsError()

    Declaration
    public void Validate_EmptyWaveSetId_ReturnsError()

    Validate_EntryCountZero_WarnsWithoutBlocking()

    Deviation from a literal "count = 0 error" reading: Serenity.Wave.Domain.Entities.SpawnEntry is a bare abstract class with no validating constructor — nothing in the domain throws on Count < 1, unlike CharacterConfig's real MaxHealth < 1 throw. Per the project's own established convention (a Unity [Min] inspector hint the domain never enforces is a warning, not a blocking error — see CharacterConfigCreatorTests), this is a warning.

    Declaration
    public void Validate_EntryCountZero_WarnsWithoutBlocking()

    Validate_InvalidStageFolderPath_ReturnsError()

    Declaration
    public void Validate_InvalidStageFolderPath_ReturnsError()

    Validate_NegativeSpawnDelayAndCadence_WarnEachWithoutBlocking()

    Declaration
    public void Validate_NegativeSpawnDelayAndCadence_WarnEachWithoutBlocking()

    Validate_NegativeWaveIndex_ReturnsError()

    Declaration
    public void Validate_NegativeWaveIndex_ReturnsError()

    Validate_NoActorAssigned_WarnsMirroringTheDrawerText()

    Declaration
    public void Validate_NoActorAssigned_WarnsMirroringTheDrawerText()

    Validate_NullInput_ReturnsMissingInputError()

    Declaration
    public void Validate_NullInput_ReturnsMissingInputError()

    Validate_ResolvableSpawnPointId_NoErrorAndNoWarningForIt()

    Declaration
    public void Validate_ResolvableSpawnPointId_NoErrorAndNoWarningForIt()

    Validate_SameWaveConfigIdInItsOwnFolder_IsExcludedFromTheDuplicateWarning()

    Declaration
    public void Validate_SameWaveConfigIdInItsOwnFolder_IsExcludedFromTheDuplicateWarning()

    Validate_SameWaveSetIdInItsOwnFolder_IsExcludedFromTheDuplicateWarning()

    Declaration
    public void Validate_SameWaveSetIdInItsOwnFolder_IsExcludedFromTheDuplicateWarning()

    Validate_StageFolderDoesNotExistYet_WarnsDegradedWithoutBlocking()

    Declaration
    public void Validate_StageFolderDoesNotExistYet_WarnsDegradedWithoutBlocking()

    Validate_StageFolderExistsWithNoPrefab_WarnsDegradedWithoutBlocking()

    Declaration
    public void Validate_StageFolderExistsWithNoPrefab_WarnsDegradedWithoutBlocking()

    Validate_StagePrefabHasNoSpawnPointComponents_WarnsWithTheNoComponentsMessage()

    Distinguishes the two situations ScanStagePrefab(string) collapses into the same null/empty result: zero SpawnPoint components at all.

    Declaration
    public void Validate_StagePrefabHasNoSpawnPointComponents_WarnsWithTheNoComponentsMessage()

    Validate_StagePrefabHasSpawnPointsWithBlankIds_WarnsWithTheBlankIdsMessage()

    The other half of the same distinction: one or more SpawnPoint components exist, but every one carries a blank id — ScanStagePrefab filters blank ids out before returning, so this must not be reported as "no SpawnPoint components".

    Declaration
    public void Validate_StagePrefabHasSpawnPointsWithBlankIds_WarnsWithTheBlankIdsMessage()

    Validate_UnresolvableSpawnPointIdWithARealPrefab_ReturnsError()

    Declaration
    public void Validate_UnresolvableSpawnPointIdWithARealPrefab_ReturnsError()

    Validate_WaveIdMatchesAnOrphanWaveAsset_Warns()

    Declaration
    public void Validate_WaveIdMatchesAnOrphanWaveAsset_Warns()

    Validate_WaveIdReferencedByAnotherWaveSetInTheStage_Errors()

    Declaration
    public void Validate_WaveIdReferencedByAnotherWaveSetInTheStage_Errors()

    Validate_WaveIdReferencedByTheSameWaveSet_IsAllowed()

    Declaration
    public void Validate_WaveIdReferencedByTheSameWaveSet_IsAllowed()

    Validate_WaveIndexNotMatchingPlayOrderPosition_WarnsWithoutBlocking()

    Declaration
    public void Validate_WaveIndexNotMatchingPlayOrderPosition_WarnsWithoutBlocking()

    Validate_ZeroWaves_WarnsWithoutBlocking()

    Declaration
    public void Validate_ZeroWaves_WarnsWithoutBlocking()
    In this article
    © 2026 Serenity. All Rights Reserved