From 8e2cc3aa277f15b24657998853b27edc72d5a1c5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 22 May 2026 10:04:07 +0000 Subject: [PATCH] test(ensemble): cover Page dispose of header storage poll timers Adds a widget regression test for the fix that cancels periodic titleBarHeight and collapsibleHeader visibility timers and avoids duplicate storage listeners. Verifies unmounting the Page does not leave periodic timers firing setState on disposed state. Co-authored-by: Sharjeel Yunus --- .../page_header_timer_dispose_test.dart | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 modules/ensemble/test/widget/page_header_timer_dispose_test.dart diff --git a/modules/ensemble/test/widget/page_header_timer_dispose_test.dart b/modules/ensemble/test/widget/page_header_timer_dispose_test.dart new file mode 100644 index 000000000..65c56cbd6 --- /dev/null +++ b/modules/ensemble/test/widget/page_header_timer_dispose_test.dart @@ -0,0 +1,59 @@ +import 'package:ensemble/framework/data_context.dart'; +import 'package:ensemble/framework/view/page.dart'; +import 'package:ensemble/page_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:yaml/yaml.dart'; + +void main() { + testWidgets( + 'Page with storage-driven header disposes without timer callbacks after unmount', + (tester) async { + final doc = loadYaml( + ''' +View: + header: + titleText: Test + collapsibleHeader: + enabled: true + visible: ensemble.storage.visFlag + styles: + listenTitleBarHeightStorage: true + titleBarHeight: ensemble.storage.tbhKey + body: + Column: + children: + - Text: + text: body +''', + ); + + final pageModel = PageModel.fromYaml(doc as YamlMap) as SinglePageModel; + + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (context) => Page( + dataContext: DataContext(buildContext: context), + pageModel: pageModel, + onRendered: () {}, + ), + ), + ), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); + + await tester.pumpWidget( + const MaterialApp(home: SizedBox.shrink()), + ); + await tester.pump(); + + // Undisposed Timer.periodic callbacks would still fire and call setState + // on the disposed PageState, surfacing as a FlutterError. + await tester.pump(const Duration(milliseconds: 400)); + + expect(tester.takeException(), isNull); + }, + ); +}