Skip to content
Prev Previous commit
Next Next commit
improve
  • Loading branch information
buenaflor committed Jan 26, 2024
commit 784f1205ef84385eacd3087ac2de2e1fa6a36414
31 changes: 25 additions & 6 deletions flutter/lib/src/screenshot/sentry_screenshot_widget.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

import '../../sentry_flutter.dart';

/// Key which is used to identify the [RepaintBoundary]
@internal
final sentryScreenshotWidgetGlobalKey =
Expand All @@ -23,20 +25,37 @@ final sentryScreenshotWidgetGlobalKey =
/// times.
@Deprecated('Use [SentryWidget] instead')
class SentryScreenshotWidget extends StatefulWidget {
const SentryScreenshotWidget({super.key, required this.child});

final Widget child;
late final Hub _hub;

SentryFlutterOptions? get _options =>
// ignore: invalid_use_of_internal_member
_hub.options is SentryFlutterOptions
// ignore: invalid_use_of_internal_member
? _hub.options as SentryFlutterOptions
: null;

SentryScreenshotWidget({
super.key,
required this.child,
@internal Hub? hub,
}) : _hub = hub ?? HubAdapter();

@override
_SentryScreenshotWidgetState createState() => _SentryScreenshotWidgetState();
}

class _SentryScreenshotWidgetState extends State<SentryScreenshotWidget> {
SentryFlutterOptions? get _options => widget._options;

@override
Widget build(BuildContext context) {
return RepaintBoundary(
key: sentryScreenshotWidgetGlobalKey,
child: widget.child,
);
if (_options?.attachScreenshot ?? false) {
return RepaintBoundary(
key: sentryScreenshotWidgetGlobalKey,
child: widget.child,
);
}
return widget.child;
}
}
7 changes: 0 additions & 7 deletions flutter/lib/src/sentry_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ typedef FlutterOptionsConfiguration = FutureOr<void> Function(
mixin SentryFlutter {
static const _channel = MethodChannel('sentry_flutter');

static SentryFlutterOptions _flutterOptions = SentryFlutterOptions();

@internal
static SentryFlutterOptions get flutterOptions => _flutterOptions;

static Future<void> init(
FlutterOptionsConfiguration optionsConfiguration, {
AppRunner? appRunner,
Expand Down Expand Up @@ -101,8 +96,6 @@ mixin SentryFlutter {
// ignore: invalid_use_of_internal_member
SentryNativeProfilerFactory.attachTo(Sentry.currentHub, _native!);
}

SentryFlutter._flutterOptions = flutterOptions;
}

static Future<void> _initDefaultValues(
Expand Down
28 changes: 4 additions & 24 deletions flutter/lib/src/sentry_widget.dart
Original file line number Diff line number Diff line change
@@ -1,43 +1,23 @@
import 'package:flutter/cupertino.dart';
import 'package:meta/meta.dart';
import '../sentry_flutter.dart';

/// This widget serves as a wrapper to conditionally include Sentry widgets such
/// This widget serves as a wrapper to include Sentry widgets such
/// as [SentryScreenshotWidget] and [SentryUserInteractionWidget].
class SentryWidget extends StatefulWidget {
late final Hub _hub;
final Widget child;

SentryWidget({super.key, required this.child, @internal Hub? hub})
: _hub = hub ?? HubAdapter();
const SentryWidget({super.key, required this.child});

@override
_SentryWidgetState createState() => _SentryWidgetState();
}

class _SentryWidgetState extends State<SentryWidget> {
final _options = SentryFlutter.flutterOptions;

@override
Widget build(BuildContext context) {
Widget content = widget.child;
content = _wrapWithScreenshotIfNeeded(content);
content = _wrapWithUserInteractionIfNeeded(content);
content = SentryScreenshotWidget(child: content);
content = SentryUserInteractionWidget(child: content);
return content;
}

Widget _wrapWithScreenshotIfNeeded(Widget child) {
if (_options.attachScreenshot) {
return SentryScreenshotWidget(child: child);
}
return child;
}

Widget _wrapWithUserInteractionIfNeeded(Widget child) {
if (_options.enableUserInteractionTracing ||
_options.enableUserInteractionBreadcrumbs) {
return SentryUserInteractionWidget(hub: widget._hub, child: child);
}
return child;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -282,12 +282,16 @@ class _SentryUserInteractionWidgetState

@override
Widget build(BuildContext context) {
return Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: _onPointerDown,
onPointerUp: _onPointerUp,
child: widget.child,
);
if ((_options?.enableUserInteractionTracing ?? true) ||
(_options?.enableUserInteractionBreadcrumbs ?? true)) {
return Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: _onPointerDown,
onPointerUp: _onPointerUp,
child: widget.child,
);
}
return widget.child;
}

void _onPointerDown(PointerDownEvent event) {
Expand Down
77 changes: 77 additions & 0 deletions flutter/test/screenshot/sentry_screenshot_widget_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
@TestOn('vm')
// ignore_for_file: invalid_use_of_internal_member

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

import '../mocks.dart';

void main() {
late Fixture fixture;
setUp(() {
fixture = Fixture();
TestWidgetsFlutterBinding.ensureInitialized();
});

testWidgets(
'$SentryScreenshotWidget does not apply when attachScreenshot is false',
(tester) async {
await tester.pumpWidget(
fixture.getSut(
attachScreenshot: false,
),
);

final widget = find.byType(MyApp);
final repaintBoundaryFinder = find.descendant(
of: widget,
matching: find.byType(RepaintBoundary),
);
expect(repaintBoundaryFinder, findsNothing);
},
);

testWidgets(
'$SentryScreenshotWidget applies when attachScreenshot is true',
(tester) async {
await tester.pumpWidget(
fixture.getSut(
attachScreenshot: true,
),
);

final widget = find.byType(MyApp);
final repaintBoundaryFinder = find.ancestor(
of: widget,
matching: find.byKey(sentryScreenshotWidgetGlobalKey),
);
expect(repaintBoundaryFinder, findsOneWidget);
},
);
}

class Fixture {
final _options = SentryFlutterOptions(dsn: fakeDsn);
late Hub hub;

SentryScreenshotWidget getSut({
bool attachScreenshot = false,
}) {
_options.attachScreenshot = attachScreenshot;

hub = Hub(_options);

return SentryScreenshotWidget(
hub: hub,
child: MaterialApp(home: MyApp()),
);
}
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const Text('test');
}
}
125 changes: 20 additions & 105 deletions flutter/test/sentry_widget_test.dart
Original file line number Diff line number Diff line change
@@ -1,122 +1,37 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

import 'mocks.dart';
import 'user_interaction/sentry_user_interaction_widget_test.dart';

void main() {
group('SentryWidget', () {
late Fixture fixture;
const testChild = Text('Test Child');

setUp(() async {
fixture = Fixture();
TestWidgetsFlutterBinding.ensureInitialized();
});

testWidgets('attaches screenshot widget when enabled', (tester) async {
await tester.runAsync(() async {
await tester.pumpWidget(fixture.getSut());

expect(find.byType(SentryScreenshotWidget), findsOneWidget);
});
});

testWidgets('does not attach screenshot widget when disabled',
(tester) async {
await tester.runAsync(() async {
await tester.pumpWidget(fixture.getSut(attachScreenshot: false));

expect(find.byType(SentryScreenshotWidget), findsNothing);
});
});

testWidgets(
'attaches user interaction widget when enableUserInteractionTracing is enabled',
(tester) async {
await tester.runAsync(() async {
await tester.pumpWidget(fixture.getSut(
enableUserInteractionTracing: true,
enableUserInteractionBreadcrumbs: false));

expect(find.byType(SentryUserInteractionWidget), findsOneWidget);
});
});

testWidgets(
'attaches user interaction widget when enableUserInteractionBreadcrumbs is enabled',
(tester) async {
await tester.runAsync(() async {
await tester.pumpWidget(fixture.getSut(
enableUserInteractionTracing: false,
enableUserInteractionBreadcrumbs: true));
testWidgets('SentryWidget wraps child with SentryUserInteractionWidget',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: SentryWidget(child: testChild),
),
);

expect(find.byType(SentryUserInteractionWidget), findsOneWidget);
});
expect(find.byType(SentryUserInteractionWidget), findsOneWidget);
expect(find.byWidget(testChild), findsOneWidget);
});

testWidgets('does not attach user interaction widget when disabled',
(tester) async {
await tester.runAsync(() async {
await tester.pumpWidget(fixture.getSut(
enableUserInteractionTracing: false,
enableUserInteractionBreadcrumbs: false));
testWidgets('SentryWidget wraps child with SentryScreenshotWidget',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: SentryWidget(child: testChild),
),
);

expect(find.byType(SentryUserInteractionWidget), findsNothing);
});
expect(find.byType(SentryScreenshotWidget), findsOneWidget);
expect(find.byWidget(testChild), findsOneWidget);
});
});

group('SentryWidget', () {
test('test options are true in SentryFlutter.init', () async {
await SentryFlutter.init((options) => {
options.dsn = fakeDsn,
options.attachScreenshot = true,
options.enableUserInteractionTracing = true,
options.enableUserInteractionBreadcrumbs = true,
});

expect(SentryFlutter.flutterOptions.attachScreenshot, true);
expect(SentryFlutter.flutterOptions.enableUserInteractionTracing, true);
expect(
SentryFlutter.flutterOptions.enableUserInteractionBreadcrumbs, true);
});

test('test options are false in SentryFlutter.init', () async {
await SentryFlutter.init((options) => {
options.dsn = fakeDsn,
options.attachScreenshot = false,
options.enableUserInteractionTracing = false,
options.enableUserInteractionBreadcrumbs = false,
});

expect(SentryFlutter.flutterOptions.attachScreenshot, false);
expect(SentryFlutter.flutterOptions.enableUserInteractionTracing, false);
expect(
SentryFlutter.flutterOptions.enableUserInteractionBreadcrumbs, false);
});
});
}

class Fixture {
// SentryWidget uses an internal static flutterOptions reference to get the options
final _options = SentryFlutter.flutterOptions;
late Hub hub;

SentryWidget getSut({
bool enableUserInteractionTracing = true,
bool enableUserInteractionBreadcrumbs = true,
bool attachScreenshot = true,
}) {
_options.dsn = fakeDsn;
_options.attachScreenshot = attachScreenshot;
_options.enableUserInteractionTracing = enableUserInteractionTracing;
_options.enableUserInteractionBreadcrumbs =
enableUserInteractionBreadcrumbs;

hub = Hub(_options);

return SentryWidget(
child: MyApp(),
);
}
}
Loading