diff --git a/.changeset/flutter-version-on-lifecycle.md b/.changeset/flutter-version-on-lifecycle.md new file mode 100644 index 00000000..2d5a8af3 --- /dev/null +++ b/.changeset/flutter-version-on-lifecycle.md @@ -0,0 +1,5 @@ +--- +"posthog_flutter": minor +--- + +Send `$flutter_version` only on the `Application Installed` and `Application Updated` events on iOS, macOS and Android instead of on every event, and stop sending it on web diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift index 60d99c7e..d86c5099 100644 --- a/example/ios/RunnerTests/RunnerTests.swift +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -64,6 +64,35 @@ class RunnerTests: XCTestCase { XCTAssertNil(records.last?.spanId) } + func testFlutterVersionAddedOnlyToInstallAndUpdateEvents() throws { + var flutterVersions: [String: String] = [:] + let config = PostHogConfig(projectToken: "flutter-version-test", host: "http://127.0.0.1:1") + config.preloadFeatureFlags = false + config.captureApplicationLifecycleEvents = false + config.setBeforeSend( + try XCTUnwrap(PosthogFlutterPlugin.flutterVersionBeforeSend(["flutterVersion": "3.35.0"])), + { event in + flutterVersions[event.event] = event.properties["$flutter_version"] as? String ?? "none" + return nil + } + ) + PostHogSDK.shared.close() + PostHogSDK.shared.setup(config) + defer { PostHogSDK.shared.close() } + + for event in ["Application Installed", "Application Updated", "Application Opened", "custom"] { + PostHogSDK.shared.capture(event) + } + + XCTAssertEqual(flutterVersions, [ + "Application Installed": "3.35.0", + "Application Updated": "3.35.0", + "Application Opened": "none", + "custom": "none", + ]) + XCTAssertNil(PosthogFlutterPlugin.flutterVersionBeforeSend([:])) + } + func testCaptureLogMissingBodyReturnsError() { let plugin = PosthogFlutterPlugin() diff --git a/posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt b/posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt index 5474b8cd..e3508a11 100644 --- a/posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt +++ b/posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt @@ -23,6 +23,7 @@ import androidx.annotation.RequiresApi import androidx.annotation.VisibleForTesting import com.posthog.PersonProfiles import com.posthog.PostHog +import com.posthog.PostHogBeforeSend import com.posthog.PostHogBootstrapConfig import com.posthog.PostHogCompression import com.posthog.PostHogConfig @@ -808,6 +809,21 @@ class PosthogFlutterPlugin : sdkName = "posthog-flutter" sdkVersion = postHogVersion + // Native lifecycle events never pass through Dart, so the version Dart + // reports at setup is added here, once per app build. + posthogConfig.getIfNotNull("flutterVersion") { flutterVersion -> + if (flutterVersion.isNotEmpty()) { + addBeforeSend( + PostHogBeforeSend { event -> + if (event.event == "Application Installed" || event.event == "Application Updated") { + event.properties?.put("\$flutter_version", flutterVersion) + } + event + }, + ) + } + } + onFeatureFlags = PostHogOnFeatureFlags { Log.i("PostHogFlutter", "Android onFeatureFlags triggered. Notifying Dart.") diff --git a/posthog_flutter/android/src/test/kotlin/com/posthog/flutter/PosthogFlutterPluginTest.kt b/posthog_flutter/android/src/test/kotlin/com/posthog/flutter/PosthogFlutterPluginTest.kt index 3086d520..fc333ff8 100644 --- a/posthog_flutter/android/src/test/kotlin/com/posthog/flutter/PosthogFlutterPluginTest.kt +++ b/posthog_flutter/android/src/test/kotlin/com/posthog/flutter/PosthogFlutterPluginTest.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.os.BadParcelableException import com.google.firebase.FirebaseApp import com.posthog.PostHogCompression +import com.posthog.PostHogEvent import com.posthog.android.replay.PostHogScreenshotColorMode import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding @@ -224,6 +225,28 @@ internal class PosthogFlutterPluginTest { assertEquals(PostHogCompression.NONE, assertNotNull(plugin.lastBuiltConfig).compression) } + @Test + fun setup_flutterVersion_addedOnlyToInstallAndUpdateEvents() { + val plugin = PosthogFlutterPlugin() + attach(plugin, Mockito.mock(BinaryMessenger::class.java)) + plugin.onMethodCall( + MethodCall("setup", mapOf("projectToken" to "test-token", "flutterVersion" to "3.35.0")), + Mockito.mock(MethodChannel.Result::class.java), + ) + val beforeSend = assertNotNull(plugin.lastBuiltConfig).beforeSendList + + fun propertiesOf(name: String): Map? = + beforeSend + .fold(PostHogEvent(name, "id", properties = mutableMapOf())) { event, hook -> + assertNotNull(hook.run(event)) + }.properties + + assertEquals("3.35.0", propertiesOf("Application Installed")?.get("\$flutter_version")) + assertEquals("3.35.0", propertiesOf("Application Updated")?.get("\$flutter_version")) + assertNull(propertiesOf("Application Opened")?.get("\$flutter_version")) + assertNull(propertiesOf("custom")?.get("\$flutter_version")) + } + @Test fun setup_screenshotControls_preserveDefaults() { val plugin = PosthogFlutterPlugin() diff --git a/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift b/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift index f00c34e9..9d74aa94 100644 --- a/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift +++ b/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift @@ -153,6 +153,24 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin { ]) } + /// Returns a `beforeSend` block that adds `$flutter_version` to the + /// `Application Installed` and `Application Updated` events, or `nil` when + /// setup carries no `flutterVersion` (Info.plist auto-init, Flutter < 3.32). + /// + /// Native lifecycle events never pass through Dart, so the version Dart + /// reports at setup is added here, once per app build. + static func flutterVersionBeforeSend(_ posthogConfig: [String: Any]) -> BeforeSendBlock? { + guard let flutterVersion = posthogConfig["flutterVersion"] as? String, !flutterVersion.isEmpty else { + return nil + } + return { event in + if event.event == "Application Installed" || event.event == "Application Updated" { + event.properties["$flutter_version"] = flutterVersion + } + return event + } + } + // `anchor` is the engine that called setup(); the static `instance` is only the // Info.plist auto-setup fallback. Resolving from `instance` would bind the mint // route to whichever engine registered last. @@ -436,6 +454,10 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin { } } + if let flutterVersionBeforeSend = flutterVersionBeforeSend(posthogConfig) { + config.setBeforeSend(flutterVersionBeforeSend) + } + // Update SDK name and version postHogSdkName = "posthog-flutter" postHogVersion = postHogFlutterVersion diff --git a/posthog_flutter/lib/posthog_flutter_web.dart b/posthog_flutter/lib/posthog_flutter_web.dart index 698c9585..1b6d2f44 100644 --- a/posthog_flutter/lib/posthog_flutter_web.dart +++ b/posthog_flutter/lib/posthog_flutter_web.dart @@ -19,7 +19,6 @@ import 'src/posthog_flutter_web_handler.dart'; import 'src/utils/before_send.dart'; import 'src/replay/web/web_canvas_mask_provider.dart'; import 'src/utils/capture_utils.dart'; -import 'src/utils/flutter_version.dart'; /// A web implementation of the PosthogFlutterPlatform of the PosthogFlutter plugin. class PosthogFlutterWeb extends PosthogFlutterPlatformInterface { @@ -169,22 +168,6 @@ class PosthogFlutterWeb extends PosthogFlutterPlatformInterface { Map? properties, Map? userProperties, Map? userPropertiesSetOnce, - }) { - return _capture( - eventName: eventName, - properties: withFlutterVersion(properties), - userProperties: userProperties, - userPropertiesSetOnce: userPropertiesSetOnce, - ); - } - - /// Captures [properties] as given. Callers add `$flutter_version` first; - /// renamed screen/exception events skip it so a beforeSend removal sticks. - Future _capture({ - required String eventName, - Map? properties, - Map? userProperties, - Map? userPropertiesSetOnce, }) async { final processedEvent = await _runBeforeSend( eventName, @@ -269,7 +252,7 @@ class PosthogFlutterWeb extends PosthogFlutterPlatformInterface { final processedEvent = await _runBeforeSend( PostHogEventName.screen, - withFlutterVersion(propsWithScreenName), + propsWithScreenName, ); if (processedEvent == null) { printIfDebug('[PostHog] Screen event dropped by beforeSend: $screenName'); @@ -277,7 +260,7 @@ class PosthogFlutterWeb extends PosthogFlutterPlatformInterface { } if (processedEvent.event != PostHogEventName.screen) { - await _capture( + await capture( eventName: processedEvent.event, properties: processedEvent.properties?.cast(), ); @@ -508,7 +491,7 @@ class PosthogFlutterWeb extends PosthogFlutterPlatformInterface { final processedEvent = await _runBeforeSend( PostHogEventName.exception, - withFlutterVersion(exceptionData.cast()), + exceptionData.cast(), ); if (processedEvent == null) { printIfDebug( @@ -518,7 +501,7 @@ class PosthogFlutterWeb extends PosthogFlutterPlatformInterface { } if (processedEvent.event != PostHogEventName.exception) { - await _capture( + await capture( eventName: processedEvent.event, properties: processedEvent.properties?.cast(), ); diff --git a/posthog_flutter/lib/src/posthog_flutter_io.dart b/posthog_flutter/lib/src/posthog_flutter_io.dart index 4ed438be..927aaed7 100644 --- a/posthog_flutter/lib/src/posthog_flutter_io.dart +++ b/posthog_flutter/lib/src/posthog_flutter_io.dart @@ -205,7 +205,10 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { _beforeSendCallbacks = config.beforeSend; try { - await _methodChannel.invokeMethod('setup', config.toMap()); + await _methodChannel.invokeMethod('setup', { + ...config.toMap(), + ...flutterVersionSetupArguments(), + }); } on PlatformException catch (exception) { printIfDebug('Exeption on setup: $exception'); } @@ -290,22 +293,6 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { Map? properties, Map? userProperties, Map? userPropertiesSetOnce, - }) { - return _capture( - eventName: eventName, - properties: withFlutterVersion(properties), - userProperties: userProperties, - userPropertiesSetOnce: userPropertiesSetOnce, - ); - } - - /// Captures [properties] as given. Callers add `$flutter_version` first; - /// renamed screen/exception events skip it so a beforeSend removal sticks. - Future _capture({ - required String eventName, - Map? properties, - Map? userProperties, - Map? userPropertiesSetOnce, }) async { if (!isSupportedPlatform()) { return; @@ -379,7 +366,7 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { // Apply beforeSend callback - screen events are captured as $screen final processedEvent = await _runBeforeSend( PostHogEventName.screen, - withFlutterVersion(propsWithScreenName), + propsWithScreenName, ); if (processedEvent == null) { printIfDebug('[PostHog] Screen event dropped by beforeSend: $screenName'); @@ -388,7 +375,7 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { // If event name was changed, use regular capture() instead if (processedEvent.event != PostHogEventName.screen) { - await _capture( + await capture( eventName: processedEvent.event, properties: processedEvent.properties?.cast(), ); @@ -403,9 +390,10 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { processedEvent.properties?.remove(PostHogPropertyName.screenName); try { - final eventProperties = processedEvent.properties?.cast(); - final normalizedProperties = eventProperties?.isNotEmpty == true - ? PropertyNormalizer.normalize(eventProperties!) + final normalizedProperties = processedEvent.properties?.isNotEmpty == true + ? PropertyNormalizer.normalize( + processedEvent.properties!.cast(), + ) : null; await _methodChannel.invokeMethod('screen', { @@ -787,7 +775,7 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { // Apply beforeSend callback - exception events are captured as $exception final processedEvent = await _runBeforeSend( PostHogEventName.exception, - withFlutterVersion(exceptionProps.cast()), + exceptionProps.cast(), ); if (processedEvent == null) { printIfDebug( @@ -798,7 +786,7 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { // If event name was changed, use capture() instead if (processedEvent.event != PostHogEventName.exception) { - await _capture( + await capture( eventName: processedEvent.event, properties: processedEvent.properties?.cast(), ); @@ -807,9 +795,10 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { // Add timestamp from Flutter side (will be used and removed from native plugins) final timestamp = DateTime.now().millisecondsSinceEpoch; - final eventProperties = processedEvent.properties?.cast(); - final normalizedData = eventProperties != null - ? PropertyNormalizer.normalize(eventProperties) + final normalizedData = processedEvent.properties != null + ? PropertyNormalizer.normalize( + processedEvent.properties!.cast(), + ) : {}; await _methodChannel.invokeMethod('captureException', { diff --git a/posthog_flutter/lib/src/utils/flutter_version.dart b/posthog_flutter/lib/src/utils/flutter_version.dart index 2632f3be..c6cd54f6 100644 --- a/posthog_flutter/lib/src/utils/flutter_version.dart +++ b/posthog_flutter/lib/src/utils/flutter_version.dart @@ -3,20 +3,16 @@ // that class is missing from the older Flutter versions this package supports. const _flutterVersion = String.fromEnvironment('FLUTTER_VERSION'); -/// Returns the `$flutter_version` property, or an empty map when the build +/// Returns the `flutterVersion` setup argument, or an empty map when the build /// doesn't report a Flutter version (Flutter < 3.32). -Map flutterVersionProperties({ +/// +/// The native plugins add it as `$flutter_version` to the +/// `Application Installed` and `Application Updated` events the native SDKs +/// capture, since the version only changes with a new app build. +Map flutterVersionSetupArguments({ String flutterVersion = _flutterVersion, }) { return { - if (flutterVersion.isNotEmpty) r'$flutter_version': flutterVersion, + if (flutterVersion.isNotEmpty) 'flutterVersion': flutterVersion, }; } - -/// Adds the `$flutter_version` property to [properties], replacing any value -/// the caller set. `beforeSend` runs afterwards and can still change it. -Map? withFlutterVersion(Map? properties) { - final versionProperties = flutterVersionProperties(); - if (versionProperties.isEmpty) return properties; - return {...?properties, ...versionProperties}; -} diff --git a/posthog_flutter/test/event_shape_snapshot_test.dart b/posthog_flutter/test/event_shape_snapshot_test.dart index 65d1d361..e15e1525 100644 --- a/posthog_flutter/test/event_shape_snapshot_test.dart +++ b/posthog_flutter/test/event_shape_snapshot_test.dart @@ -264,14 +264,15 @@ void main() { // The Flutter version depends on the SDK running the tests, and Flutter < 3.32 // doesn't report one, so it's checked here and left out of the snapshots. Object? _withoutFlutterVersion(Object? arguments) { - if (arguments is! Map || arguments['properties'] is! Map) return arguments; - final properties = Map.from(arguments['properties'] as Map); - if (!properties.containsKey(r'$flutter_version')) return arguments; + if (arguments is! Map || !arguments.containsKey('flutterVersion')) { + return arguments; + } + final withoutVersion = Map.from(arguments); expect( - properties.remove(r'$flutter_version'), + withoutVersion.remove('flutterVersion'), const String.fromEnvironment('FLUTTER_VERSION'), ); - return {...arguments, 'properties': properties}; + return withoutVersion; } Future _expectSnapshot(String name, List calls) async { diff --git a/posthog_flutter/test/posthog_flutter_io_test.dart b/posthog_flutter/test/posthog_flutter_io_test.dart index b2469903..233c7185 100644 --- a/posthog_flutter/test/posthog_flutter_io_test.dart +++ b/posthog_flutter/test/posthog_flutter_io_test.dart @@ -7,7 +7,6 @@ import 'package:posthog_flutter/src/posthog_config.dart'; import 'package:posthog_flutter/src/posthog_event.dart'; import 'package:posthog_flutter/src/posthog_flutter_io.dart'; import 'package:posthog_flutter/src/posthog_internal_events.dart'; -import 'package:posthog_flutter/src/utils/flutter_version.dart'; // Simplified void callback for feature flags void emptyCallback() {} @@ -122,6 +121,22 @@ void main() { expect(args['apiKey'], equals('test_project_token')); }); + test( + 'setup sends the Flutter version for native lifecycle events', + skip: const String.fromEnvironment('FLUTTER_VERSION').isEmpty + ? 'FLUTTER_VERSION is only defined on Flutter 3.32+' + : null, + () async { + await posthogFlutterIO.setup(PostHogConfig('test_project_token')); + + final call = log.firstWhere((c) => c.method == 'setup'); + expect( + (call.arguments as Map)['flutterVersion'], + const String.fromEnvironment('FLUTTER_VERSION'), + ); + }, + ); + test( 'invokes callback when native sends onFeatureFlagsCallback event', () async { @@ -362,160 +377,6 @@ void main() { }); }); - const flutterVersion = String.fromEnvironment('FLUTTER_VERSION'); - - group('PosthogFlutterIO \$flutter_version', - skip: flutterVersion.isEmpty - ? 'Flutter < 3.32 does not report its version' - : false, () { - setUp(() async { - await posthogFlutterIO.setup(PostHogConfig('test_project_token')); - }); - - Map propertiesOf(String method) { - final call = log.lastWhere((c) => c.method == method); - return Map.from( - (call.arguments as Map)['properties'] as Map, - ); - } - - test('capture attaches the compile-time Flutter version', () async { - await posthogFlutterIO.capture(eventName: 'checkout'); - - expect(propertiesOf('capture'), {r'$flutter_version': flutterVersion}); - }); - - test('screen attaches the compile-time Flutter version', () async { - await posthogFlutterIO.screen(screenName: 'Home'); - - expect(propertiesOf('screen'), {r'$flutter_version': flutterVersion}); - }); - - test('captureException attaches the compile-time Flutter version', - () async { - await posthogFlutterIO.captureException(error: StateError('boom')); - - expect( - propertiesOf('captureException'), - containsPair(r'$flutter_version', flutterVersion), - ); - }); - - test('replaces a Flutter version the caller set', () async { - await posthogFlutterIO.capture( - eventName: 'checkout', - properties: {r'$flutter_version': 'custom'}, - ); - - expect(propertiesOf('capture'), {r'$flutter_version': flutterVersion}); - }); - - test('omits the property when the build reports no Flutter version', () { - expect(flutterVersionProperties(flutterVersion: ''), isEmpty); - }); - }); - - group('PosthogFlutterIO \$flutter_version and beforeSend', - skip: flutterVersion.isEmpty - ? 'Flutter < 3.32 does not report its version' - : false, () { - final seen = {}; - - Future setUpRemovingFlutterVersion() async { - seen.clear(); - await posthogFlutterIO.setup(PostHogConfig( - 'test_project_token', - beforeSend: [ - (event) { - seen[event.event] = event.properties?[r'$flutter_version']; - event.properties?.remove(r'$flutter_version'); - return event; - }, - ], - )); - } - - Map propertiesOf(String method) { - final call = log.lastWhere((c) => c.method == method); - return Map.from( - ((call.arguments as Map)['properties'] as Map?) ?? {}, - ); - } - - test('capture: beforeSend sees and can remove it', () async { - await setUpRemovingFlutterVersion(); - await posthogFlutterIO.capture(eventName: 'checkout'); - - expect(seen['checkout'], flutterVersion); - expect(propertiesOf('capture'), isNot(contains(r'$flutter_version'))); - }); - - test('screen: beforeSend sees and can remove it', () async { - await setUpRemovingFlutterVersion(); - await posthogFlutterIO.screen(screenName: 'Home'); - - expect(seen[r'$screen'], flutterVersion); - expect(propertiesOf('screen'), isNot(contains(r'$flutter_version'))); - }); - - test('captureException: beforeSend sees and can remove it', () async { - await setUpRemovingFlutterVersion(); - await posthogFlutterIO.captureException(error: StateError('boom')); - - expect(seen[r'$exception'], flutterVersion); - expect(propertiesOf('captureException'), - isNot(contains(r'$flutter_version'))); - }); - - // Renamed screen/exception events fall back to capture, which must not - // add the version back after beforeSend removed it. - Future setUpRemoveAndRename() async { - await posthogFlutterIO.setup(PostHogConfig( - 'test_project_token', - beforeSend: [ - (event) { - if (event.event == r'$screen' || event.event == r'$exception') { - event.properties?.remove(r'$flutter_version'); - event.event = 'redacted_event'; - } - return event; - }, - ], - )); - } - - for (final callerValue in [null, 'caller']) { - final props = {if (callerValue != null) r'$flutter_version': callerValue}; - final label = callerValue == null ? 'automatic' : 'caller-set'; - - test('screen renamed by beforeSend keeps the $label version removed', - () async { - await setUpRemoveAndRename(); - await posthogFlutterIO.screen(screenName: 'Home', properties: props); - - final captures = log.where((c) => c.method == 'capture').toList(); - expect(captures, hasLength(1)); - expect( - (captures.single.arguments as Map)['eventName'], 'redacted_event'); - expect(propertiesOf('capture'), isNot(contains(r'$flutter_version'))); - }); - - test( - 'captureException renamed by beforeSend keeps the $label version removed', - () async { - await setUpRemoveAndRename(); - await posthogFlutterIO.captureException( - error: StateError('boom'), properties: props); - - final captures = log.where((c) => c.method == 'capture').toList(); - expect(captures, hasLength(1)); - expect( - (captures.single.arguments as Map)['eventName'], 'redacted_event'); - expect(propertiesOf('capture'), isNot(contains(r'$flutter_version'))); - }); - } - }); - group('PosthogFlutterIO beforeSend callback', () { test( 'capture sends event unchanged when no beforeSend registered', @@ -531,8 +392,7 @@ void main() { final captureCall = log.firstWhere((c) => c.method == 'capture'); final args = Map.from(captureCall.arguments as Map); expect(args['eventName'], 'test_event'); - expect(args['properties'], - {...flutterVersionProperties(), 'key': 'value'}); + expect(args['properties'], {'key': 'value'}); }, ); @@ -574,7 +434,6 @@ void main() { final captureCall = log.firstWhere((c) => c.method == 'capture'); final args = Map.from(captureCall.arguments as Map); - // beforeSend replaced the properties, so the SDK's $flutter_version is gone too. expect(args['properties'], {'modified': true}); }); @@ -756,8 +615,7 @@ void main() { expect(capturedEvent, isNotNull); expect(capturedEvent!.event, 'test_event'); - expect(capturedEvent!.properties, - {...flutterVersionProperties(), 'prop': 'value'}); + expect(capturedEvent!.properties, {'prop': 'value'}); expect(capturedEvent!.userProperties, {'user_prop': 'user_value'}); expect(capturedEvent!.userPropertiesSetOnce, { 'set_once_prop': 'set_once_value', @@ -789,8 +647,7 @@ void main() { final captureCall = log.firstWhere((c) => c.method == 'capture'); final args = Map.from(captureCall.arguments as Map); - expect(args['properties'], - {...flutterVersionProperties(), 'event_prop': 'value'}); + expect(args['properties'], {'event_prop': 'value'}); expect(args['userProperties'], {'developer_name': 'John'}); }, ); @@ -819,8 +676,7 @@ void main() { final captureCall = log.firstWhere((c) => c.method == 'capture'); final args = Map.from(captureCall.arguments as Map); - expect(args['properties'], - {...flutterVersionProperties(), 'event_prop': 'value'}); + expect(args['properties'], {'event_prop': 'value'}); expect(args['userPropertiesSetOnce'], {'first_seen': '2025-01-01'}); }, ); @@ -848,8 +704,7 @@ void main() { final captureCall = log.firstWhere((c) => c.method == 'capture'); final args = Map.from(captureCall.arguments as Map); - expect(args['properties'], - {...flutterVersionProperties(), 'event_prop': 'value'}); + expect(args['properties'], {'event_prop': 'value'}); expect(args['userProperties'], { 'from_legacy': 'legacy_value', 'from_direct': 'direct_value', diff --git a/posthog_flutter/test/web_before_send_test.dart b/posthog_flutter/test/web_before_send_test.dart index f5972a88..493fea90 100644 --- a/posthog_flutter/test/web_before_send_test.dart +++ b/posthog_flutter/test/web_before_send_test.dart @@ -85,102 +85,4 @@ void main() { expect(captures, isEmpty); }); - - group(r'$flutter_version', - skip: const String.fromEnvironment('FLUTTER_VERSION').isEmpty - ? 'Flutter < 3.32 does not report its version' - : false, () { - const flutterVersion = String.fromEnvironment('FLUTTER_VERSION'); - - Map propertiesOf(String event) => - captures.lastWhere((c) => c['event'] == event)['properties'] as Map; - - Future captureAll(PosthogFlutterWeb web, - [Map? props]) async { - await web.capture(eventName: 'checkout', properties: props); - await web.screen(screenName: 'Home', properties: props); - await web.captureException(error: StateError('boom'), properties: props); - } - - test('is sent and visible to beforeSend', () async { - final seen = {}; - final web = PosthogFlutterWeb(); - await web.setup(PostHogConfig('test_token', beforeSend: [ - (event) { - seen[event.event] = event.properties?[r'$flutter_version']; - return event; - }, - ])); - - await captureAll(web); - - expect(seen, { - 'checkout': flutterVersion, - r'$screen': flutterVersion, - r'$exception': flutterVersion, - }); - for (final event in ['checkout', r'$screen', r'$exception']) { - expect(propertiesOf(event)[r'$flutter_version'], flutterVersion); - } - }); - - test('replaces a caller-set value', () async { - final web = PosthogFlutterWeb(); - await web.setup(PostHogConfig('test_token')); - - await captureAll(web, {r'$flutter_version': 'custom'}); - - for (final event in ['checkout', r'$screen', r'$exception']) { - expect(propertiesOf(event)[r'$flutter_version'], flutterVersion); - } - }); - - for (final props in [ - {}, - {r'$flutter_version': 'caller'}, - ]) { - test('stays removed when beforeSend removes it (caller props: $props)', - () async { - final web = PosthogFlutterWeb(); - await web.setup(PostHogConfig('test_token', beforeSend: [ - (event) { - event.properties?.remove(r'$flutter_version'); - return event; - }, - ])); - - await captureAll(web, props); - - expect(captures, hasLength(3)); - for (final capture in captures) { - expect(capture['properties'] as Map, - isNot(contains(r'$flutter_version'))); - } - }); - } - - test('stays removed when beforeSend removes it and renames the event', - () async { - final web = PosthogFlutterWeb(); - await web.setup(PostHogConfig('test_token', beforeSend: [ - (event) { - if (event.event == r'$screen' || event.event == r'$exception') { - event.properties?.remove(r'$flutter_version'); - event.event = 'redacted_event'; - } - return event; - }, - ])); - - await web.screen(screenName: 'Home'); - await web.captureException(error: StateError('boom')); - - expect(captures.map((c) => c['event']), - ['redacted_event', 'redacted_event']); - for (final capture in captures) { - expect( - capture['properties'] as Map, isNot(contains(r'$flutter_version'))); - } - }); - }); }