| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:convert'; | |
| import 'package:clock/clock.dart'; | |
| import 'package:file/file.dart'; | |
| import 'package:file/memory.dart'; | |
| import 'package:test/test.dart'; | |
| import 'package:unified_analytics/src/constants.dart'; | |
| import 'package:unified_analytics/src/enums.dart'; | |
| import 'package:unified_analytics/src/survey_handler.dart'; | |
| import 'package:unified_analytics/src/utils.dart'; | |
| import 'package:unified_analytics/unified_analytics.dart'; | |
| void main() { | |
| final testEvent = Event.hotReloadTime(timeMs: 10); | |
| group('Unit testing function sampleRate:', () { | |
| // Set a string that can be used in place of a survey's unique ID | |
| final iterations = 1000; | |
| final uuid = Uuid(123); | |
| final uniqueSurveyId = uuid.generateV4(); | |
| // Set how much the actual sampled rate can be (allowing 5% of variability) | |
| final marginOfError = 0.05; | |
| test('Unit testing the sampleRate method', () { | |
| // These strings had a predetermined output from the utility function | |
| final string1 = 'string1'; | |
| final string2 = 'string2'; | |
| expect(sampleRate(string1, string2), 0.40); | |
| }); | |
| test('Simulating with various sample rates', () { | |
| final sampleRateToTestList = [0.10, 0.25, 0.50, 0.75, 0.80, 0.95]; | |
| for (final sampleRateToTest in sampleRateToTestList) { | |
| var count = 0; | |
| for (var i = 0; i < iterations; i++) { | |
| // Regenerate the client id to simulate a unique user | |
| final generatedClientId = uuid.generateV4(); | |
| if (sampleRate(uniqueSurveyId, generatedClientId) <= | |
| sampleRateToTest) { | |
| count += 1; | |
| } | |
| } | |
| final actualSampledRate = count / iterations; | |
| final actualMarginOfError = (sampleRateToTest - actualSampledRate) | |
| .abs(); | |
| expect( | |
| actualMarginOfError < marginOfError, | |
| true, | |
| reason: | |
| 'Failed on sample rate = $sampleRateToTest with' | |
| ' actual rate $actualMarginOfError ' | |
| 'and a margin of error = $marginOfError', | |
| ); | |
| } | |
| }); | |
| }); | |
| group('Unit testing function checkSurveyDate:', () { | |
| final date = DateTime(2023, 5, 1); | |
| // Two surveys created, one that is within the survey date | |
| // range, and one that is not | |
| final validSurvey = Survey( | |
| uniqueId: 'uniqueId', | |
| startDate: DateTime(2023, 1, 1), | |
| endDate: DateTime(2023, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: 10, | |
| samplingRate: 1.0, | |
| excludeDashToolList: [], | |
| conditionList: <Condition>[], | |
| buttonList: [], | |
| ); | |
| final invalidSurvey = Survey( | |
| uniqueId: 'uniqueId', | |
| startDate: DateTime(2022, 1, 1), | |
| endDate: DateTime(2022, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: 10, | |
| samplingRate: 1.0, | |
| excludeDashToolList: [], | |
| conditionList: <Condition>[], | |
| buttonList: [], | |
| ); | |
| test('expired survey', () { | |
| final clock = Clock.fixed(date); | |
| withClock(clock, () { | |
| expect(SurveyHandler.checkSurveyDate(invalidSurvey), false); | |
| }); | |
| }); | |
| test('valid survey', () { | |
| final clock = Clock.fixed(date); | |
| withClock(clock, () { | |
| expect(SurveyHandler.checkSurveyDate(validSurvey), true); | |
| }); | |
| }); | |
| }); | |
| group('Unit testing function parseSurveysFromJson', () { | |
| final validContents = ''' | |
| [ | |
| { | |
| "uniqueId": "xxxxx", | |
| "startDate": "2023-06-01T09:00:00-07:00", | |
| "endDate": "2023-06-30T09:00:00-07:00", | |
| "description": "xxxxxxx", | |
| "snoozeForMinutes": "10", | |
| "samplingRate": "1.0", | |
| "excludeDashTools": [], | |
| "conditions": [ | |
| { | |
| "field": "logFileStats.recordCount", | |
| "operator": ">=", | |
| "value": 1000 | |
| }, | |
| { | |
| "field": "logFileStats.toolCount.flutter-tool", | |
| "operator": "<", | |
| "value": 3 | |
| } | |
| ], | |
| "buttons": [ | |
| { | |
| "buttonText": "Take Survey", | |
| "action": "accept", | |
| "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2", | |
| "promptRemainsVisible": false | |
| } | |
| ] | |
| } | |
| ] | |
| '''; | |
| // The value for the condition is not a valid integer | |
| final invalidConditionValueContents = ''' | |
| [ | |
| { | |
| "uniqueId": "xxxxx", | |
| "startDate": "2023-06-01T09:00:00-07:00", | |
| "endDate": "2023-06-30T09:00:00-07:00", | |
| "description": "xxxxxxx", | |
| "snoozeForMinutes": "10", | |
| "samplingRate": "1.0", | |
| "excludeDashTools": [], | |
| "conditions": [ | |
| { | |
| "field": "logFileStats.recordCount", | |
| "operator": ">=", | |
| "value": "1000xxxx" | |
| } | |
| ], | |
| "buttons": [ | |
| { | |
| "buttonText": "Take Survey", | |
| "action": "accept", | |
| "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2", | |
| "promptRemainsVisible": false | |
| } | |
| ] | |
| } | |
| ] | |
| '''; | |
| // Using a dash tool in the excludeDashTools array that is not a valid | |
| // DashTool label | |
| final invalidDashToolContents = ''' | |
| [ | |
| { | |
| "uniqueId": "xxxxx", | |
| "startDate": "2023-06-01T09:00:00-07:00", | |
| "endDate": "2023-06-30T09:00:00-07:00", | |
| "description": "xxxxxxx", | |
| "snoozeForMinutes": "10", | |
| "samplingRate": "1.0", | |
| "excludeDashTools": [ | |
| "not-a-valid-dash-tool" | |
| ], | |
| "conditions": [ | |
| { | |
| "field": "logFileStats.recordCount", | |
| "operator": ">=", | |
| "value": 1000 | |
| }, | |
| { | |
| "field": "logFileStats.toolCount.flutter-tool", | |
| "operator": "<", | |
| "value": 3 | |
| } | |
| ], | |
| "buttons": [ | |
| { | |
| "buttonText": "Take Survey", | |
| "action": "accept", | |
| "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2", | |
| "promptRemainsVisible": false | |
| } | |
| ] | |
| } | |
| ] | |
| '''; | |
| test('valid json', () { | |
| withClock(Clock.fixed(DateTime(2023, 6, 15)), () { | |
| final parsedSurveys = SurveyHandler.parseSurveysFromJson( | |
| jsonDecode(validContents) as List, | |
| ); | |
| expect(parsedSurveys.length, 1); | |
| expect(parsedSurveys.first.conditionList.length, 2); | |
| final firstCondition = parsedSurveys.first.conditionList.first; | |
| final secondCondition = parsedSurveys.first.conditionList[1]; | |
| expect(firstCondition.field, 'logFileStats.recordCount'); | |
| expect(firstCondition.operatorString, '>='); | |
| expect(firstCondition.value, 1000); | |
| expect(secondCondition.field, 'logFileStats.toolCount.flutter-tool'); | |
| expect(secondCondition.operatorString, '<'); | |
| expect(secondCondition.value, 3); | |
| expect(parsedSurveys.first.buttonList.length, 1); | |
| expect( | |
| parsedSurveys.first.buttonList.first.promptRemainsVisible, | |
| false, | |
| ); | |
| }); | |
| }); | |
| test('invalid condition json', () { | |
| withClock(Clock.fixed(DateTime(2023, 6, 15)), () { | |
| final parsedSurveys = SurveyHandler.parseSurveysFromJson( | |
| jsonDecode(invalidConditionValueContents) as List, | |
| ); | |
| expect( | |
| parsedSurveys.length, | |
| 0, | |
| reason: | |
| 'The condition value is not a ' | |
| 'proper integer so it should error returning no surveys', | |
| ); | |
| }); | |
| }); | |
| test('invalid dash tool json', () { | |
| withClock(Clock.fixed(DateTime(2023, 6, 15)), () { | |
| final parsedSurveys = SurveyHandler.parseSurveysFromJson( | |
| jsonDecode(invalidDashToolContents) as List, | |
| ); | |
| expect( | |
| parsedSurveys.length, | |
| 0, | |
| reason: | |
| 'The dash tool in the exclude array is not valid ' | |
| 'so it should error returning no surveys', | |
| ); | |
| }); | |
| }); | |
| }); | |
| group('Testing with FakeSurveyHandler', () { | |
| late Analytics analytics; | |
| late Directory homeDirectory; | |
| late MemoryFileSystem fs; | |
| late File clientIdFile; | |
| late File dismissedSurveyFile; | |
| setUp(() { | |
| fs = MemoryFileSystem.test(style: FileSystemStyle.posix); | |
| homeDirectory = fs.directory('home'); | |
| // Write the client ID file out so that we don't get | |
| // a randomly assigned id for this test generated within | |
| // the analytics constructor | |
| clientIdFile = homeDirectory | |
| .childDirectory(kDartToolDirectoryName) | |
| .childFile(kClientIdFileName); | |
| clientIdFile.createSync(recursive: true); | |
| clientIdFile.writeAsStringSync('string1'); | |
| // Assign the json file that will hold the persisted surveys | |
| dismissedSurveyFile = homeDirectory | |
| .childDirectory(kDartToolDirectoryName) | |
| .childFile(kDismissedSurveyFileName); | |
| // Setup two tools to be onboarded with this package so | |
| // that we can simulate two different tools interacting with | |
| // surveys | |
| // | |
| // This is especially useful when testing the "excludeDashTools" array | |
| // to prevent certain tools from getting a survey from this package | |
| final initialAnalyticsFlutter = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| ); | |
| final initialAnalyticsDart = Analytics.fake( | |
| tool: DashTool.dartTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| ); | |
| initialAnalyticsFlutter.clientShowedMessage(); | |
| initialAnalyticsDart.clientShowedMessage(); | |
| }); | |
| test('returns valid survey', () async { | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[ | |
| Survey( | |
| uniqueId: 'uniqueId', | |
| startDate: DateTime(2023, 1, 1), | |
| endDate: DateTime(2023, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: 10, | |
| samplingRate: 1.0, | |
| excludeDashToolList: [], | |
| conditionList: <Condition>[ | |
| Condition('logFileStats.recordCount', '>=', 50), | |
| Condition('logFileStats.toolCount.flutter-tool', '>', 0), | |
| ], | |
| buttonList: [ | |
| SurveyButton( | |
| buttonText: 'buttonText', | |
| action: 'accept', | |
| url: 'http://example.com', | |
| promptRemainsVisible: false, | |
| ), | |
| ], | |
| ), | |
| ], | |
| ), | |
| ); | |
| // Simulate 60 events to send so that the first condition is satisified | |
| for (var i = 0; i < 60; i++) { | |
| analytics.send(testEvent); | |
| } | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 1); | |
| final survey = fetchedSurveys.first; | |
| expect(survey.conditionList.length, 2); | |
| expect(survey.buttonList.length, 1); | |
| }); | |
| }); | |
| test('does not return expired survey', () async { | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[ | |
| Survey( | |
| uniqueId: 'uniqueId', | |
| startDate: DateTime(2022, 1, 1), | |
| endDate: DateTime(2022, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: 10, | |
| samplingRate: 1.0, | |
| excludeDashToolList: [], | |
| conditionList: <Condition>[ | |
| Condition('logFileStats.recordCount', '>=', 50), | |
| Condition('logFileStats.toolCount.flutter-tool', '>', 0), | |
| ], | |
| buttonList: [], | |
| ), | |
| ], | |
| ), | |
| ); | |
| // Simulate 60 events to send so that the first condition is satisified | |
| for (var i = 0; i < 60; i++) { | |
| analytics.send(testEvent); | |
| } | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 0); | |
| }); | |
| }); | |
| test('does not return survey if opted out of telemetry', () async { | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[ | |
| Survey( | |
| uniqueId: 'uniqueId', | |
| startDate: DateTime(2023, 1, 1), | |
| endDate: DateTime(2023, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: 10, | |
| samplingRate: 1.0, | |
| excludeDashToolList: [], | |
| conditionList: <Condition>[ | |
| Condition('logFileStats.recordCount', '>=', 50), | |
| Condition('logFileStats.toolCount.flutter-tool', '>', 0), | |
| ], | |
| buttonList: [], | |
| ), | |
| ], | |
| ), | |
| ); | |
| // Simulate 60 events to send so that the first condition is satisified | |
| for (var i = 0; i < 60; i++) { | |
| analytics.send(testEvent); | |
| } | |
| await analytics.setTelemetry(false); | |
| expect(analytics.okToSend, false); | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 0); | |
| }); | |
| }); | |
| test('returns valid survey from json', () async { | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromString( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| content: ''' | |
| [ | |
| { | |
| "uniqueId": "uniqueId123", | |
| "startDate": "2023-01-01T09:00:00-07:00", | |
| "endDate": "2023-12-31T09:00:00-07:00", | |
| "description": "description123", | |
| "snoozeForMinutes": "10", | |
| "samplingRate": "1.0", | |
| "excludeDashTools": [], | |
| "conditions": [ | |
| { | |
| "field": "logFileStats.recordCount", | |
| "operator": ">=", | |
| "value": 50 | |
| } | |
| ], | |
| "buttons": [ | |
| { | |
| "buttonText": "Take Survey", | |
| "action": "accept", | |
| "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2", | |
| "promptRemainsVisible": false | |
| }, | |
| { | |
| "buttonText": "Dismiss", | |
| "action": "dismiss", | |
| "url": null, | |
| "promptRemainsVisible": false | |
| }, | |
| { | |
| "buttonText": "More Info", | |
| "action": "snooze", | |
| "url": "https://docs.flutter.dev/reference/crash-reporting", | |
| "promptRemainsVisible": true | |
| } | |
| ] | |
| } | |
| ] | |
| ''', | |
| ), | |
| ); | |
| // Simulate 60 events to send so that the first condition is satisified | |
| for (var i = 0; i < 60; i++) { | |
| analytics.send(testEvent); | |
| } | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 1); | |
| final survey = fetchedSurveys.first; | |
| expect(survey.uniqueId, 'uniqueId123'); | |
| expect(survey.startDate.year, 2023); | |
| expect(survey.startDate.month, 1); | |
| expect(survey.startDate.day, 1); | |
| expect(survey.endDate.year, 2023); | |
| expect(survey.endDate.month, 12); | |
| expect(survey.endDate.day, 31); | |
| expect(survey.description, 'description123'); | |
| expect(survey.snoozeForMinutes, 10); | |
| expect(survey.samplingRate, 1.0); | |
| expect(survey.conditionList.length, 1); | |
| final condition = survey.conditionList.first; | |
| expect(condition.field, 'logFileStats.recordCount'); | |
| expect(condition.operatorString, '>='); | |
| expect(condition.value, 50); | |
| final buttonList = survey.buttonList; | |
| expect(buttonList.length, 3); | |
| expect(buttonList.first.buttonText, 'Take Survey'); | |
| expect(buttonList.first.action, 'accept'); | |
| expect( | |
| buttonList.first.url, | |
| 'https://google.qualtrics.com/jfe/form/SV_5gsB2EuG324y2', | |
| ); | |
| expect(buttonList.first.promptRemainsVisible, false); | |
| expect(buttonList.elementAt(1).buttonText, 'Dismiss'); | |
| expect(buttonList.elementAt(1).action, 'dismiss'); | |
| expect(buttonList.elementAt(1).url, isNull); | |
| expect(buttonList.elementAt(1).promptRemainsVisible, false); | |
| expect(buttonList.last.buttonText, 'More Info'); | |
| expect(buttonList.last.action, 'snooze'); | |
| expect( | |
| buttonList.last.url, | |
| 'https://docs.flutter.dev/reference/crash-reporting', | |
| ); | |
| expect(buttonList.last.promptRemainsVisible, true); | |
| }); | |
| }); | |
| test('no survey returned from malformed json', () async { | |
| // The date is not valid for the start date | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromString( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| content: ''' | |
| [ | |
| { | |
| "uniqueId": "uniqueId123", | |
| "startDate": "NOT A REAL DATE", | |
| "endDate": "2023-07-30T09:00:00-07:00", | |
| "description": "Help improve Flutter's release builds with this 3-question survey!", | |
| "snoozeForMinutes": "7200", | |
| "samplingRate": "0.1", | |
| "excludeDashTools": [], | |
| "conditions": [ | |
| { | |
| "field": "logFileStats.recordCount", | |
| "operator": ">=", | |
| "value": 50 | |
| } | |
| ], | |
| "buttons": [ | |
| { | |
| "buttonText": "Take Survey", | |
| "action": "accept", | |
| "url": "https://google.qualtrics.com/jfe/form/SV_5gsB2EuG5Et5Yy2", | |
| "promptRemainsVisible": false | |
| }, | |
| { | |
| "buttonText": "Dismiss", | |
| "action": "dismiss", | |
| "url": null, | |
| "promptRemainsVisible": false | |
| }, | |
| { | |
| "buttonText": "More Info", | |
| "action": "snooze", | |
| "url": "https://docs.flutter.dev/reference/crash-reporting", | |
| "promptRemainsVisible": false | |
| } | |
| ] | |
| } | |
| ] | |
| ''', | |
| ), | |
| ); | |
| // Simulate 60 events to send so that the first condition is satisified | |
| for (var i = 0; i < 60; i++) { | |
| analytics.send(testEvent); | |
| } | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 0); | |
| }); | |
| }); | |
| test('returns two valid survey from json', () async { | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromString( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| content: ''' | |
| [ | |
| { | |
| "uniqueId": "12345", | |
| "startDate": "2023-01-01T09:00:00-07:00", | |
| "endDate": "2023-12-31T09:00:00-07:00", | |
| "description": "xxxxxxx", | |
| "snoozeForMinutes": "10", | |
| "samplingRate": "1.0", | |
| "excludeDashTools": [], | |
| "conditions": [ | |
| { | |
| "field": "logFileStats.recordCount", | |
| "operator": ">=", | |
| "value": 50 | |
| } | |
| ], | |
| "buttons": [] | |
| }, | |
| { | |
| "uniqueId": "67890", | |
| "startDate": "2023-01-01T09:00:00-07:00", | |
| "endDate": "2023-12-31T09:00:00-07:00", | |
| "description": "xxxxxxx", | |
| "snoozeForMinutes": "10", | |
| "samplingRate": "1.0", | |
| "excludeDashTools": [], | |
| "conditions": [ | |
| { | |
| "field": "logFileStats.recordCount", | |
| "operator": ">=", | |
| "value": 50 | |
| } | |
| ], | |
| "buttons": [ | |
| { | |
| "buttonText": "More Info", | |
| "action": "snooze", | |
| "url": "https://docs.flutter.dev/reference/crash-reporting", | |
| "promptRemainsVisible": true | |
| } | |
| ] | |
| } | |
| ] | |
| ''', | |
| ), | |
| ); | |
| // Simulate 60 events to send so that the first condition is satisified | |
| for (var i = 0; i < 60; i++) { | |
| analytics.send(testEvent); | |
| } | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 2); | |
| final firstSurvey = fetchedSurveys.first; | |
| final secondSurvey = fetchedSurveys.last; | |
| expect(firstSurvey.uniqueId, '12345'); | |
| expect(secondSurvey.uniqueId, '67890'); | |
| final secondSurveyButtons = secondSurvey.buttonList; | |
| expect(secondSurveyButtons.length, 1); | |
| expect(secondSurveyButtons.first.buttonText, 'More Info'); | |
| expect(secondSurveyButtons.first.action, 'snooze'); | |
| expect( | |
| secondSurveyButtons.first.url, | |
| 'https://docs.flutter.dev/reference/crash-reporting', | |
| ); | |
| expect(secondSurveyButtons.first.promptRemainsVisible, true); | |
| }); | |
| }); | |
| test('valid survey not returned if opted out', () async { | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[ | |
| Survey( | |
| uniqueId: 'uniqueId', | |
| startDate: DateTime(2023, 1, 1), | |
| endDate: DateTime(2023, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: 10, | |
| samplingRate: 1.0, | |
| excludeDashToolList: [], | |
| conditionList: <Condition>[ | |
| Condition('logFileStats.recordCount', '>=', 50), | |
| Condition('logFileStats.toolCount.flutter-tool', '>', 0), | |
| ], | |
| buttonList: [], | |
| ), | |
| ], | |
| ), | |
| ); | |
| // Simulate 60 events to send so that the first condition is satisified | |
| for (var i = 0; i < 60; i++) { | |
| analytics.send(testEvent); | |
| } | |
| // Setting to false will prevent anything from getting returned | |
| await analytics.setTelemetry(false); | |
| var fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 0); | |
| // Setting telemetry back to true should enable the surveys to get | |
| // returned again; we will also need to send the fake events again | |
| // because on opt out, the log file will get cleared and one of | |
| // the conditions for the fake survey loaded is that we need | |
| // at least 50 records for one of the conditions | |
| await analytics.setTelemetry(true); | |
| for (var i = 0; i < 60; i++) { | |
| analytics.send(testEvent); | |
| } | |
| fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 1); | |
| }); | |
| }); | |
| test('Sampling rate correctly returns a valid survey', () async { | |
| // This test will use a predefined client ID string of `string1` | |
| // which has been set in the setup along with a predefined | |
| // string for the survey ID of `string2` to get a sample rate value | |
| // | |
| // The combination of `string1` and `string2` will return 0.40 | |
| // from the sampleRate utility function so we have set the threshold | |
| // to be 0.6 which should return surveys | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { | |
| final survey = Survey( | |
| uniqueId: 'string2', | |
| startDate: DateTime(2023, 1, 1), | |
| endDate: DateTime(2023, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: 10, | |
| samplingRate: 0.6, | |
| excludeDashToolList: [], | |
| conditionList: <Condition>[ | |
| Condition('logFileStats.recordCount', '>=', 50), | |
| Condition('logFileStats.toolCount.flutter-tool', '>', 0), | |
| ], | |
| buttonList: [], | |
| ); | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[survey], | |
| ), | |
| ); | |
| // Simulate 60 events to send so that the first condition is satisified | |
| for (var i = 0; i < 60; i++) { | |
| analytics.send(testEvent); | |
| } | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(survey.samplingRate, 0.6); | |
| expect(fetchedSurveys.length, 1); | |
| }); | |
| }); | |
| test('Sampling rate filters out a survey', () async { | |
| // We will reduce the survey's sampling rate to be 0.3 which is | |
| // less than value returned from the predefined client ID and | |
| // survey sample | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { | |
| final survey = Survey( | |
| uniqueId: 'string2', | |
| startDate: DateTime(2023, 1, 1), | |
| endDate: DateTime(2023, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: 10, | |
| samplingRate: 0.15, | |
| excludeDashToolList: [], | |
| conditionList: <Condition>[ | |
| Condition('logFileStats.recordCount', '>=', 50), | |
| Condition('logFileStats.toolCount.flutter-tool', '>', 0), | |
| ], | |
| buttonList: [], | |
| ); | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[survey], | |
| ), | |
| ); | |
| // Simulate 60 events to send so that the first condition is satisified | |
| for (var i = 0; i < 60; i++) { | |
| analytics.send(testEvent); | |
| } | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(survey.samplingRate, 0.15); | |
| expect(fetchedSurveys.length, 0); | |
| }); | |
| }); | |
| test( | |
| 'Snoozing survey is successful with snooze timeout from survey', | |
| () async { | |
| expect( | |
| dismissedSurveyFile.readAsStringSync(), | |
| '{}', | |
| reason: 'Should be an empty object', | |
| ); | |
| // Initialize the survey class that we will use for this test | |
| final minutesToSnooze = 30; | |
| final surveyToLoad = Survey( | |
| uniqueId: 'uniqueId', | |
| startDate: DateTime(2023, 1, 1), | |
| endDate: DateTime(2023, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: | |
| minutesToSnooze, // Initialized survey with `minutesToSnooze` | |
| samplingRate: 1.0, | |
| excludeDashToolList: [], | |
| conditionList: <Condition>[], | |
| buttonList: [], | |
| ); | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[surveyToLoad], | |
| ), | |
| ); | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 1); | |
| final survey = fetchedSurveys.first; | |
| expect(survey.snoozeForMinutes, minutesToSnooze); | |
| // We will snooze the survey now and it should not show up | |
| // if we fetch surveys again before the minutes to snooze time | |
| // has finished | |
| analytics.surveyShown(survey); | |
| }); | |
| // This analytics instance will be simulated to be shortly after the | |
| // first snooze, but before the snooze period has elapsed | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 15)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[surveyToLoad], | |
| ), | |
| ); | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect( | |
| fetchedSurveys.length, | |
| 0, | |
| reason: 'The snooze period has not elapsed yet', | |
| ); | |
| }); | |
| // This analytics instance will be simulated to be after the snooze | |
| // period | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 35)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[surveyToLoad], | |
| ), | |
| ); | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect( | |
| fetchedSurveys.length, | |
| 1, | |
| reason: 'The snooze period has elapsed', | |
| ); | |
| }); | |
| }, | |
| ); | |
| test('Dimissing permanently is successful', () async { | |
| final minutesToSnooze = 10; | |
| final surveyToLoad = Survey( | |
| uniqueId: 'uniqueId', | |
| startDate: DateTime(2023, 1, 1), | |
| endDate: DateTime(2023, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: minutesToSnooze, | |
| samplingRate: 1.0, | |
| excludeDashToolList: [], | |
| conditionList: <Condition>[], | |
| buttonList: [ | |
| SurveyButton( | |
| buttonText: 'buttonText', | |
| action: 'accept', | |
| promptRemainsVisible: false, | |
| ), | |
| SurveyButton( | |
| buttonText: 'buttonText', | |
| action: 'dismiss', | |
| promptRemainsVisible: false, | |
| ), | |
| ], | |
| ); | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[surveyToLoad], | |
| ), | |
| ); | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 1); | |
| // Dismissing permanently will ensure that this survey is not | |
| // shown again | |
| final survey = fetchedSurveys.first; | |
| analytics.surveyInteracted( | |
| survey: survey, | |
| surveyButton: survey.buttonList.first, | |
| ); | |
| }); | |
| // Moving out a week | |
| await withClock(Clock.fixed(DateTime(2023, 3, 10, 12, 0)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[surveyToLoad], | |
| ), | |
| ); | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 0); | |
| }); | |
| }); | |
| test('malformed persisted json file for surveys', () async { | |
| // When the survey handler encounters an error when parsing the | |
| // persisted json file, it will reset it using the static method | |
| // under the [Initializer] class and reset it to be an empty json object | |
| final minutesToSnooze = 10; | |
| final surveyToLoad = Survey( | |
| uniqueId: 'uniqueId', | |
| startDate: DateTime(2023, 1, 1), | |
| endDate: DateTime(2023, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: minutesToSnooze, | |
| samplingRate: 1.0, | |
| excludeDashToolList: [], | |
| conditionList: <Condition>[], | |
| buttonList: [ | |
| SurveyButton( | |
| buttonText: 'buttonText', | |
| action: 'accept', | |
| promptRemainsVisible: false, | |
| ), | |
| SurveyButton( | |
| buttonText: 'buttonText', | |
| action: 'dismiss', | |
| promptRemainsVisible: false, | |
| ), | |
| ], | |
| ); | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[surveyToLoad], | |
| ), | |
| ); | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 1); | |
| // Dismissing permanently will ensure that this survey is not | |
| // shown again | |
| final survey = fetchedSurveys.first; | |
| expect(survey.buttonList.length, 2); | |
| analytics.surveyInteracted( | |
| survey: survey, | |
| surveyButton: survey.buttonList.first, | |
| ); | |
| }); | |
| // Purposefully write invalid json into the persisted file | |
| dismissedSurveyFile.writeAsStringSync('{'); | |
| // Moving out a week | |
| await withClock(Clock.fixed(DateTime(2023, 3, 10, 12, 0)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[surveyToLoad], | |
| ), | |
| ); | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 1); | |
| }); | |
| }); | |
| test('persisted json file goes missing handled', () async { | |
| // If the persisted json file with the dismissed surveys is missing | |
| // there should be error handling to recreate the file again with | |
| // an empty json object | |
| final minutesToSnooze = 10; | |
| final surveyToLoad = Survey( | |
| uniqueId: 'uniqueId', | |
| startDate: DateTime(2023, 1, 1), | |
| endDate: DateTime(2023, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: minutesToSnooze, | |
| samplingRate: 1.0, | |
| excludeDashToolList: [], | |
| conditionList: <Condition>[], | |
| buttonList: [ | |
| SurveyButton( | |
| buttonText: 'buttonText', | |
| action: 'accept', | |
| promptRemainsVisible: false, | |
| ), | |
| SurveyButton( | |
| buttonText: 'buttonText', | |
| action: 'dismiss', | |
| promptRemainsVisible: false, | |
| ), | |
| ], | |
| ); | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[surveyToLoad], | |
| ), | |
| ); | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 1); | |
| // Dismissing permanently will ensure that this survey is not | |
| // shown again | |
| final survey = fetchedSurveys.first; | |
| expect(survey.buttonList.length, 2); | |
| analytics.surveyInteracted( | |
| survey: survey, | |
| surveyButton: survey.buttonList.first, | |
| ); | |
| }); | |
| // Moving out a week | |
| await withClock(Clock.fixed(DateTime(2023, 3, 10, 12, 0)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[surveyToLoad], | |
| ), | |
| ); | |
| // Purposefully delete the file | |
| dismissedSurveyFile.deleteSync(); | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 1); | |
| }); | |
| }); | |
| test('Filtering out with excludeDashTool array', () async { | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[ | |
| Survey( | |
| uniqueId: 'uniqueId', | |
| startDate: DateTime(2023, 1, 1), | |
| endDate: DateTime(2023, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: 10, | |
| samplingRate: 1.0, | |
| // This should be the same as the tool in the | |
| // Analytics constructor above | |
| excludeDashToolList: [DashTool.flutterTool], | |
| conditionList: [], | |
| buttonList: [ | |
| SurveyButton( | |
| buttonText: 'buttonText', | |
| action: 'accept', | |
| url: 'http://example.com', | |
| promptRemainsVisible: false, | |
| ), | |
| ], | |
| ), | |
| ], | |
| ), | |
| ); | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 0); | |
| }); | |
| }); | |
| test('Filter from excludeDashTool array does not ' | |
| 'apply for different tool', () async { | |
| await withClock(Clock.fixed(DateTime(2023, 3, 3)), () async { | |
| analytics = Analytics.fake( | |
| tool: DashTool.flutterTool, | |
| homeDirectory: homeDirectory, | |
| dartVersion: 'dartVersion', | |
| fs: fs, | |
| platform: DevicePlatform.macos, | |
| surveyHandler: FakeSurveyHandler.fromList( | |
| dismissedSurveyFile: dismissedSurveyFile, | |
| initializedSurveys: <Survey>[ | |
| Survey( | |
| uniqueId: 'uniqueId', | |
| startDate: DateTime(2023, 1, 1), | |
| endDate: DateTime(2023, 12, 31), | |
| description: 'description', | |
| snoozeForMinutes: 10, | |
| samplingRate: 1.0, | |
| // This should be different from the tool in the | |
| // Analytics constructor above | |
| excludeDashToolList: [DashTool.devtools], | |
| conditionList: [], | |
| buttonList: [ | |
| SurveyButton( | |
| buttonText: 'buttonText', | |
| action: 'accept', | |
| url: 'http://example.com', | |
| promptRemainsVisible: false, | |
| ), | |
| ], | |
| ), | |
| ], | |
| ), | |
| ); | |
| final fetchedSurveys = await analytics.fetchAvailableSurveys(); | |
| expect(fetchedSurveys.length, 1); | |
| final survey = fetchedSurveys.first; | |
| expect(survey.excludeDashToolList.length, 1); | |
| expect(survey.excludeDashToolList.contains(DashTool.devtools), true); | |
| }); | |
| }); | |
| }); | |
| } |