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.
Inherited Members
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()