diff --git a/splitio_web/lib/splitio_web.dart b/splitio_web/lib/splitio_web.dart index b7bcdf2..764d5d3 100644 --- a/splitio_web/lib/splitio_web.dart +++ b/splitio_web/lib/splitio_web.dart @@ -411,6 +411,162 @@ class SplitioWeb extends SplitioPlatform { splitName.toJS, _convertMap(attributes, true), _convertEvaluationOptions(evaluationOptions)) as JSString; + return result.toDart; } + + @override + Future> getTreatments({ + required String matchingKey, + required String? bucketingKey, + required List splitNames, + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty(), + }) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatments.callAsFunction( + null, + splitNames.jsify(), + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + + return jsTreatmentsToMap(result); + } + + @override + Future getTreatmentWithConfig({ + required String matchingKey, + required String? bucketingKey, + required String splitName, + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty(), + }) async { + await this._initFuture; + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatmentWithConfig.callAsFunction( + null, + splitName.toJS, + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + + return jsTreatmentWithConfigToSplitResult(result); + } + + @override + Future> getTreatmentsWithConfig({ + required String matchingKey, + required String? bucketingKey, + required List splitNames, + Map attributes = const {}, + EvaluationOptions evaluationOptions = const EvaluationOptions.empty(), + }) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatmentsWithConfig.callAsFunction( + null, + splitNames.jsify(), + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + + return jsTreatmentsWithConfigToMap(result); + } + + @override + Future> getTreatmentsByFlagSet( + {required String matchingKey, + required String? bucketingKey, + required String flagSet, + Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatmentsByFlagSet.callAsFunction( + null, + flagSet.toJS, + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + + return jsTreatmentsToMap(result); + } + + @override + Future> getTreatmentsByFlagSets( + {required String matchingKey, + required String? bucketingKey, + required List flagSets, + Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatmentsByFlagSets.callAsFunction( + null, + flagSets.jsify(), + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + + return jsTreatmentsToMap(result); + } + + @override + Future> getTreatmentsWithConfigByFlagSet( + {required String matchingKey, + required String? bucketingKey, + required String flagSet, + Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatmentsWithConfigByFlagSet.callAsFunction( + null, + flagSet.toJS, + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + + return jsTreatmentsWithConfigToMap(result); + } + + @override + Future> getTreatmentsWithConfigByFlagSets( + {required String matchingKey, + required String? bucketingKey, + required List flagSets, + Map attributes = const {}, + EvaluationOptions evaluationOptions = + const EvaluationOptions.empty()}) async { + final client = await _getClient( + matchingKey: matchingKey, + bucketingKey: bucketingKey, + ); + + final result = client.getTreatmentsWithConfigByFlagSets.callAsFunction( + null, + flagSets.jsify(), + _convertMap(attributes, true), + _convertEvaluationOptions(evaluationOptions)) as JSObject; + + return jsTreatmentsWithConfigToMap(result); + } } diff --git a/splitio_web/lib/src/js_interop.dart b/splitio_web/lib/src/js_interop.dart index 94cfb58..6ec78e3 100644 --- a/splitio_web/lib/src/js_interop.dart +++ b/splitio_web/lib/src/js_interop.dart @@ -1,4 +1,5 @@ import 'dart:js_interop'; +import 'package:splitio_platform_interface/split_result.dart'; // JS SDK types @@ -15,6 +16,13 @@ extension type JS_ISettings._(JSObject _) implements JSObject { @JS() extension type JS_IBrowserClient._(JSObject _) implements JSObject { external JSFunction getTreatment; + external JSFunction getTreatments; + external JSFunction getTreatmentWithConfig; + external JSFunction getTreatmentsWithConfig; + external JSFunction getTreatmentsByFlagSet; + external JSFunction getTreatmentsByFlagSets; + external JSFunction getTreatmentsWithConfigByFlagSet; + external JSFunction getTreatmentsWithConfigByFlagSets; } @JS() @@ -68,3 +76,20 @@ dynamic jsAnyToDart(JSAny? value) { return value; // JS null and undefined are null in Dart } } + +// Conversion utils: JS SDK to Flutter SDK types + +Map jsTreatmentsToMap(JSObject obj) { + return jsObjectToMap(obj).map((k, v) => MapEntry(k, v as String)); +} + +Map jsTreatmentsWithConfigToMap(JSObject obj) { + return jsObjectToMap(obj).map((k, v) => MapEntry( + k, SplitResult(v['treatment'] as String, v['config'] as String?))); +} + +SplitResult jsTreatmentWithConfigToSplitResult(JSObject obj) { + final config = _reflectGet(obj, 'config'.toJS); + return SplitResult((_reflectGet(obj, 'treatment'.toJS) as JSString).toDart, + (config is JSString) ? config.toDart : null); +} diff --git a/splitio_web/test/splitio_web_test.dart b/splitio_web/test/splitio_web_test.dart index 60a124f..8af0a9e 100644 --- a/splitio_web/test/splitio_web_test.dart +++ b/splitio_web/test/splitio_web_test.dart @@ -8,6 +8,7 @@ import 'package:splitio_platform_interface/split_certificate_pinning_configurati import 'package:splitio_platform_interface/split_configuration.dart'; import 'package:splitio_platform_interface/split_evaluation_options.dart'; import 'package:splitio_platform_interface/split_sync_config.dart'; +import 'package:splitio_platform_interface/split_result.dart'; import 'package:splitio_platform_interface/split_rollout_cache_configuration.dart'; extension on web.Window { @@ -27,6 +28,106 @@ void main() { )); return 'on'.toJS; }.toJS; + mockClient['getTreatments'] = + (JSAny? flagNames, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatments', + methodArguments: [flagNames, attributes, evaluationOptions] + )); + if (flagNames is JSArray) { + return flagNames.toDart.fold(JSObject(), (previousValue, element) { + if (element is JSString) { + previousValue.setProperty(element, 'on'.toJS); + } + return previousValue; + }); + } + return JSObject(); + }.toJS; + mockClient['getTreatmentWithConfig'] = + (JSAny? flagName, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatmentWithConfig', + methodArguments: [flagName, attributes, evaluationOptions] + )); + final result = JSObject(); + result.setProperty('treatment'.toJS, 'on'.toJS); + result.setProperty('config'.toJS, 'some-config'.toJS); + return result; + }.toJS; + mockClient['getTreatmentsWithConfig'] = + (JSAny? flagNames, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatmentsWithConfig', + methodArguments: [flagNames, attributes, evaluationOptions] + )); + if (flagNames is JSArray) { + return flagNames.toDart.fold(JSObject(), (previousValue, element) { + if (element is JSString) { + final result = JSObject(); + result.setProperty('treatment'.toJS, 'on'.toJS); + result.setProperty('config'.toJS, 'some-config'.toJS); + previousValue.setProperty(element, result); + } + return previousValue; + }); + } + return JSObject(); + }.toJS; + mockClient['getTreatmentsByFlagSet'] = + (JSAny? flagSetName, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatmentsByFlagSet', + methodArguments: [flagSetName, attributes, evaluationOptions] + )); + final result = JSObject(); + result.setProperty('split1'.toJS, 'on'.toJS); + result.setProperty('split2'.toJS, 'on'.toJS); + return result; + }.toJS; + mockClient['getTreatmentsByFlagSets'] = + (JSAny? flagSetNames, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatmentsByFlagSets', + methodArguments: [flagSetNames, attributes, evaluationOptions] + )); + final result = JSObject(); + result.setProperty('split1'.toJS, 'on'.toJS); + result.setProperty('split2'.toJS, 'on'.toJS); + return result; + }.toJS; + mockClient['getTreatmentsWithConfigByFlagSet'] = + (JSAny? flagSetName, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatmentsWithConfigByFlagSet', + methodArguments: [flagSetName, attributes, evaluationOptions] + )); + + final treatmentWithConfig = JSObject(); + treatmentWithConfig.setProperty('treatment'.toJS, 'on'.toJS); + treatmentWithConfig.setProperty('config'.toJS, 'some-config'.toJS); + + final result = JSObject(); + result.setProperty('split1'.toJS, treatmentWithConfig); + result.setProperty('split2'.toJS, treatmentWithConfig); + return result; + }.toJS; + mockClient['getTreatmentsWithConfigByFlagSets'] = + (JSAny? flagSetNames, JSAny? attributes, JSAny? evaluationOptions) { + calls.add(( + methodName: 'getTreatmentsWithConfigByFlagSets', + methodArguments: [flagSetNames, attributes, evaluationOptions] + )); + + final treatmentWithConfig = JSObject(); + treatmentWithConfig.setProperty('treatment'.toJS, 'on'.toJS); + treatmentWithConfig.setProperty('config'.toJS, 'some-config'.toJS); + + final result = JSObject(); + result.setProperty('split1'.toJS, treatmentWithConfig); + result.setProperty('split2'.toJS, treatmentWithConfig); + return result; + }.toJS; final mockLog = JSObject(); mockLog['warn'] = (JSAny? arg1) { @@ -170,6 +271,246 @@ void main() { 'Invalid property value: [value1, 100, false], for key: propList, will be ignored')); }); + test('getTreatments without attributes', () async { + final result = await _platform.getTreatments( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitNames: ['split1', 'split2']); + + expect(result, {'split1': 'on', 'split2': 'on'}); + expect(calls.last.methodName, 'getTreatments'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['split1', 'split2'], + {}, + {} + ]); + }); + + test('getTreatments with attributes and evaluation properties', () async { + final result = await _platform.getTreatments( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitNames: ['split1', 'split2'], + attributes: {'attr1': true}); + + expect(result, {'split1': 'on', 'split2': 'on'}); + expect(calls.last.methodName, 'getTreatments'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['split1', 'split2'], + {'attr1': true}, + {} + ]); + }); + + test('getTreatmentWithConfig with attributes', () async { + final result = await _platform.getTreatmentWithConfig( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitName: 'split1', + attributes: {'attr1': true}); + + expect(result.toString(), SplitResult('on', 'some-config').toString()); + expect(calls.last.methodName, 'getTreatmentWithConfig'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + 'split1', + {'attr1': true}, + {} + ]); + }); + + test('getTreatmentWithConfig without attributes', () async { + final result = await _platform.getTreatmentWithConfig( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitName: 'split1'); + + expect(result.toString(), SplitResult('on', 'some-config').toString()); + expect(calls.last.methodName, 'getTreatmentWithConfig'); + expect(calls.last.methodArguments.map(jsAnyToDart), ['split1', {}, {}]); + }); + + test('getTreatmentsWithConfig without attributes', () async { + final result = await _platform.getTreatmentsWithConfig( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitNames: ['split1', 'split2']); + + expect(result, predicate>((result) { + return result.length == 2 && + result['split1'].toString() == + SplitResult('on', 'some-config').toString() && + result['split2'].toString() == + SplitResult('on', 'some-config').toString(); + })); + expect(calls.last.methodName, 'getTreatmentsWithConfig'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['split1', 'split2'], + {}, + {} + ]); + }); + + test('getTreatmentsWithConfig with attributes', () async { + final result = await _platform.getTreatmentsWithConfig( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + splitNames: ['split1', 'split2'], + attributes: {'attr1': true}); + + expect(result, predicate>((result) { + return result.length == 2 && + result['split1'].toString() == + SplitResult('on', 'some-config').toString() && + result['split2'].toString() == + SplitResult('on', 'some-config').toString(); + })); + expect(calls.last.methodName, 'getTreatmentsWithConfig'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['split1', 'split2'], + {'attr1': true}, + {} + ]); + }); + + test('getTreatmentsByFlagSet without attributes', () async { + final result = await _platform.getTreatmentsByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1'); + + expect(result, {'split1': 'on', 'split2': 'on'}); + expect(calls.last.methodName, 'getTreatmentsByFlagSet'); + expect(calls.last.methodArguments.map(jsAnyToDart), ['set_1', {}, {}]); + }); + + test('getTreatmentsByFlagSet with attributes', () async { + final result = await _platform.getTreatmentsByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1', + attributes: {'attr1': true}); + + expect(result, {'split1': 'on', 'split2': 'on'}); + expect(calls.last.methodName, 'getTreatmentsByFlagSet'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + 'set_1', + {'attr1': true}, + {} + ]); + }); + + test('getTreatmentsByFlagSets without attributes', () async { + final result = await _platform.getTreatmentsByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2']); + + expect(result, {'split1': 'on', 'split2': 'on'}); + expect(calls.last.methodName, 'getTreatmentsByFlagSets'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['set_1', 'set_2'], + {}, + {} + ]); + }); + + test('getTreatmentsByFlagSets with attributes', () async { + final result = await _platform.getTreatmentsByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2'], + attributes: {'attr1': true}); + + expect(result, {'split1': 'on', 'split2': 'on'}); + expect(calls.last.methodName, 'getTreatmentsByFlagSets'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['set_1', 'set_2'], + {'attr1': true}, + {} + ]); + }); + + test('getTreatmentsWithConfigByFlagSet without attributes', () async { + final result = await _platform.getTreatmentsWithConfigByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1'); + + expect(result, predicate>((result) { + return result.length == 2 && + result['split1'].toString() == + SplitResult('on', 'some-config').toString() && + result['split2'].toString() == + SplitResult('on', 'some-config').toString(); + })); + expect(calls.last.methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(calls.last.methodArguments.map(jsAnyToDart), ['set_1', {}, {}]); + }); + + test('getTreatmentsWithConfigByFlagSet with attributes', () async { + final result = await _platform.getTreatmentsWithConfigByFlagSet( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSet: 'set_1', + attributes: {'attr1': true}); + + expect(result, predicate>((result) { + return result.length == 2 && + result['split1'].toString() == + SplitResult('on', 'some-config').toString() && + result['split2'].toString() == + SplitResult('on', 'some-config').toString(); + })); + expect(calls.last.methodName, 'getTreatmentsWithConfigByFlagSet'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + 'set_1', + {'attr1': true}, + {} + ]); + }); + + test('getTreatmentsWithConfigByFlagSets without attributes', () async { + final result = await _platform.getTreatmentsWithConfigByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2']); + + expect(result, predicate>((result) { + return result.length == 2 && + result['split1'].toString() == + SplitResult('on', 'some-config').toString() && + result['split2'].toString() == + SplitResult('on', 'some-config').toString(); + })); + expect(calls.last.methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['set_1', 'set_2'], + {}, + {} + ]); + }); + + test('getTreatmentsWithConfigByFlagSets with attributes', () async { + final result = await _platform.getTreatmentsWithConfigByFlagSets( + matchingKey: 'matching-key', + bucketingKey: 'bucketing-key', + flagSets: ['set_1', 'set_2'], + attributes: {'attr1': true}); + + expect(result, predicate>((result) { + return result.length == 2 && + result['split1'].toString() == + SplitResult('on', 'some-config').toString() && + result['split2'].toString() == + SplitResult('on', 'some-config').toString(); + })); + expect(calls.last.methodName, 'getTreatmentsWithConfigByFlagSets'); + expect(calls.last.methodArguments.map(jsAnyToDart), [ + ['set_1', 'set_2'], + {'attr1': true}, + {} + ]); + }); }); group('initialization', () { @@ -429,5 +770,4 @@ void main() { ]); }); }); - }