/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "dusktest.h" #include "rpg/cutscene/cutscenesystem.h" #include "time/time.h" #include "util/memory.h" static void test_cutsceneWaitCompletesAfterItsDuration(void **state) { cutsceneitem_t item = CUTSCENE_WAIT(1.0f); cutsceneitemdata_t data; memoryZero(&data, sizeof(data)); cutsceneWaitStart(&item, &data); TIME.delta = 0.5f; assert_false(cutsceneWaitUpdate(&item, &data)); TIME.delta = 0.6f; assert_true(cutsceneWaitUpdate(&item, &data)); } static void *lastUserData; static uint8_t callbackCallCount; static void recordCallback(void *userData) { lastUserData = userData; callbackCallCount++; } static void test_cutsceneCallbackFiresOnStartWithUserData(void **state) { callbackCallCount = 0; lastUserData = NULL; cutsceneitem_t item = CUTSCENE_CALLBACK(recordCallback); cutsceneitemdata_t data; cutsceneCallbackStart(&item, &data); assert_int_equal(callbackCallCount, 1); assert_ptr_equal(lastUserData, CUTSCENE_SYSTEM.userData); // Callback items always complete immediately -- the effect already // happened in Start, not Update. assert_true(cutsceneCallbackUpdate(&item, &data)); assert_int_equal(callbackCallCount, 1);// Update doesn't fire it again } static void test_cutsceneCallbackNullIsNoop(void **state) { callbackCallCount = 0; cutsceneitem_t item = { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = NULL }; cutsceneitemdata_t data; cutsceneCallbackStart(&item, &data);// should not crash assert_int_equal(callbackCallCount, 0); } static void test_cutsceneSetPauseAppliesImmediately(void **state) { CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE; cutsceneitem_t item = CUTSCENE_SET_PAUSE(CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_BATTLE); cutsceneitemdata_t data; cutsceneSetPauseStart(&item, &data); assert_int_equal( CUTSCENE_SYSTEM.pause, CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_BATTLE ); assert_true(cutsceneSetPauseUpdate(&item, &data)); } static void test_cutsceneConcurrentCompletesOnceAllChildrenDo(void **state) { cutsceneitem_t item = CUTSCENE_CONCURRENT( CUTSCENE_WAIT(0.2f), CUTSCENE_WAIT(0.5f) ); cutsceneitemdata_t data; memoryZero(&data, sizeof(data)); cutsceneConcurrentStart(&item, &data); TIME.delta = 0.3f; // Child 0 (0.2s) elapses; child 1 (0.5s) still has 0.2s left. assert_false(cutsceneConcurrentUpdate(&item, &data)); TIME.delta = 0.3f; // Child 1 now elapses too. assert_true(cutsceneConcurrentUpdate(&item, &data)); } static void test_cutsceneConcurrentDoesNotReUpdateFinishedChildren( void **state ) { cutsceneitem_t item = CUTSCENE_CONCURRENT( CUTSCENE_WAIT(0.1f), CUTSCENE_WAIT(10.0f) ); cutsceneitemdata_t data; memoryZero(&data, sizeof(data)); cutsceneConcurrentStart(&item, &data); TIME.delta = 0.2f; cutsceneConcurrentUpdate(&item, &data);// child 0 finishes // If child 0 were re-updated, its stored wait value would keep dropping // further below zero -- not observable directly, but the overall result // must not falsely report done just because child 0 keeps completing. TIME.delta = 0.2f; assert_false(cutsceneConcurrentUpdate(&item, &data)); } static void test_cutsceneConcurrentCannotNest(void **state) { cutsceneitem_t outer = CUTSCENE_CONCURRENT(CUTSCENE_CONCURRENT(CUTSCENE_WAIT(1.0f))); cutsceneitemdata_t data; expect_assert_failure(cutsceneConcurrentStart(&outer, &data)); } int main(int argc, char** argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_cutsceneWaitCompletesAfterItsDuration), cmocka_unit_test(test_cutsceneCallbackFiresOnStartWithUserData), cmocka_unit_test(test_cutsceneCallbackNullIsNoop), cmocka_unit_test(test_cutsceneSetPauseAppliesImmediately), cmocka_unit_test(test_cutsceneConcurrentCompletesOnceAllChildrenDo), cmocka_unit_test(test_cutsceneConcurrentDoesNotReUpdateFinishedChildren), cmocka_unit_test(test_cutsceneConcurrentCannotNest), }; return cmocka_run_group_tests(tests, NULL, NULL); }