blob: 1ba6fa48fb737f30ff620d13fec022bb63a5e666 [file] [log] [blame]
// 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:path/path.dart' as p;
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 = fs.file(p.join(
homeDirectory.path,
kDartToolDirectoryName,
kClientIdFileName,
));
clientIdFile.createSync(recursive: true);
clientIdFile.writeAsStringSync('string1');
// Assign the json file that will hold the persisted surveys
dismissedSurveyFile = fs.file(p.join(
homeDirectory.path,
kDartToolDirectoryName,
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);
});
});
});
}