[dart-sdk] add Proposal class
TEST=
- fx set x64 --packages topaz/packages/buildbot
- fx run-host-tests fuchsia_modular_package_unittests
- fx shell runtests pkgfs/packages/fuchsia_modular_package_integration_tests/0/test
Change-Id: Ibebc38cbf080f49f9d09cffe8e2f3e4601ef1332
diff --git a/public/dart/fuchsia_modular/BUILD.gn b/public/dart/fuchsia_modular/BUILD.gn
index fc24702..8899b45 100644
--- a/public/dart/fuchsia_modular/BUILD.gn
+++ b/public/dart/fuchsia_modular/BUILD.gn
@@ -21,6 +21,7 @@
"lifecycle.dart",
"logger.dart",
"module.dart",
+ "proposal.dart",
"src/agent/agent.dart",
"src/agent/internal/_agent_impl.dart",
"src/entity/entity.dart",
@@ -38,6 +39,8 @@
"src/module/module.dart",
"src/module/module_state_exception.dart",
"src/module/noop_intent_handler.dart",
+ "src/proposal/proposal.dart",
+ "src/proposal/internal/proposal_listener_impl.dart",
]
deps = [
@@ -65,6 +68,8 @@
"module/internal/module_impl_test.dart",
"module/module_test.dart",
"module/noop_intent_handler_test.dart",
+ "proposal/proposal_test.dart",
+ "proposal/internal/proposal_listener_impl_test.dart"
]
deps = [
@@ -91,6 +96,7 @@
sources = [
"lifecycle/internal/lifecycle_impl_test.dart",
"module/internal/module_impl_integ_test.dart",
+ "proposal/proposal_integ_test.dart",
]
deps = [
diff --git a/public/dart/fuchsia_modular/lib/proposal.dart b/public/dart/fuchsia_modular/lib/proposal.dart
new file mode 100644
index 0000000..4113826
--- /dev/null
+++ b/public/dart/fuchsia_modular/lib/proposal.dart
@@ -0,0 +1,6 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// A collection of utitlities to simplify working with the intelligence system.
+export 'src/proposal/proposal.dart';
diff --git a/public/dart/fuchsia_modular/lib/src/proposal/internal/proposal_listener_impl.dart b/public/dart/fuchsia_modular/lib/src/proposal/internal/proposal_listener_impl.dart
new file mode 100644
index 0000000..8cfab8d
--- /dev/null
+++ b/public/dart/fuchsia_modular/lib/src/proposal/internal/proposal_listener_impl.dart
@@ -0,0 +1,21 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:fidl_fuchsia_modular/fidl_async.dart' as fuchsia_modular;
+
+/// A class which implements the [fuchsia_modular.ProposalListener] interface
+/// and calls a callback when the proposal is accepted.
+class ProposalListenerImpl implements fuchsia_modular.ProposalListener {
+ final void Function(String, String) _onProposalAccepted;
+
+ /// The default constructor
+ ProposalListenerImpl(this._onProposalAccepted);
+
+ @override
+ Future<void> onProposalAccepted(String proposalId, String storyId) async {
+ if (_onProposalAccepted != null) {
+ _onProposalAccepted(proposalId, storyId);
+ }
+ }
+}
diff --git a/public/dart/fuchsia_modular/lib/src/proposal/proposal.dart b/public/dart/fuchsia_modular/lib/src/proposal/proposal.dart
new file mode 100644
index 0000000..9c00098
--- /dev/null
+++ b/public/dart/fuchsia_modular/lib/src/proposal/proposal.dart
@@ -0,0 +1,87 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:fidl_fuchsia_modular/fidl_async.dart' as fuchsia_modular;
+import 'package:meta/meta.dart';
+
+import 'internal/proposal_listener_impl.dart';
+
+/// The [Proposal] class is an extension to the [fuchsia_modular.Proposal].
+///
+/// [Proposal]s are objects which can be submitted to the proposal publisher.
+/// The proposal publisher can then make recommendations to the user about
+/// actions that can be taken to enhance their user experience.
+class Proposal extends fuchsia_modular.Proposal {
+ /// Creates a [Proposal] object. The [id] and [headline] are both required and
+ /// must not be empty. If provided, the [onProposalAccepted] function will be
+ /// invoked when the proposal has been accepted by the user. Proposal
+ /// affinities and story commands can be added after the proposal is created
+ /// and before the proposal is submitted to the proposal publisher.
+ Proposal({
+ @required String id,
+ @required String headline,
+ String storyName,
+ double confidence = 0.0,
+ bool wantsRichSuggestion = false,
+ String subheadline,
+ String details,
+ List<fuchsia_modular.SuggestionDisplayImage> icons,
+ fuchsia_modular.SuggestionDisplayImage image,
+ fuchsia_modular.AnnoyanceType annoyance =
+ fuchsia_modular.AnnoyanceType.none,
+ int color = 0x000000,
+ void Function(String, String) onProposalAccepted,
+ }) : assert(id != null && id.isNotEmpty),
+ assert(headline != null && headline.isNotEmpty),
+ super(
+ id: id,
+ storyName: storyName,
+ affinity: [],
+ onSelected: [],
+ confidence: confidence,
+ wantsRichSuggestion: wantsRichSuggestion,
+ display: fuchsia_modular.SuggestionDisplay(
+ headline: headline,
+ subheadline: subheadline,
+ details: details,
+ color: color,
+ icons: icons,
+ image: image,
+ annoyance: annoyance,
+ ),
+ listener: onProposalAccepted != null
+ ? fuchsia_modular.ProposalListenerBinding()
+ .wrap(ProposalListenerImpl(onProposalAccepted))
+ : null,
+ );
+
+ /// Restricts the proposal to appear only when the module identified by
+ /// [moduleName] within the story identified by [storyName] is focused.
+ void addModuleAffinity(String moduleName, String storyName) => affinity.add(
+ fuchsia_modular.ProposalAffinity.withModuleAffinity(
+ fuchsia_modular.ModuleAffinity(
+ storyName: storyName,
+ moduleName: [moduleName],
+ ),
+ ),
+ );
+
+ /// Restricts the proposal to appear only when the story identified by
+ /// [storyName] is focused.
+ void addStoryAffinity(String storyName) => affinity.add(
+ fuchsia_modular.ProposalAffinity.withStoryAffinity(
+ fuchsia_modular.StoryAffinity(storyName: storyName),
+ ),
+ );
+
+ /// Adds a [fuchsia_modular.StoryCommand] to execute when the proposal is
+ /// selected.
+ void addStoryCommand(fuchsia_modular.StoryCommand command) =>
+ addStoryCommands([command]);
+
+ /// Adds a List of [fuchsia_modular.StoryCommand]s to execute when the
+ /// proposal is selected.
+ void addStoryCommands(List<fuchsia_modular.StoryCommand> commands) =>
+ onSelected.addAll(commands);
+}
diff --git a/public/dart/fuchsia_modular/test/lifecycle/internal/lifecycle_impl_test.dart b/public/dart/fuchsia_modular/test/lifecycle/internal/lifecycle_impl_test.dart
index 0577478..5059789 100644
--- a/public/dart/fuchsia_modular/test/lifecycle/internal/lifecycle_impl_test.dart
+++ b/public/dart/fuchsia_modular/test/lifecycle/internal/lifecycle_impl_test.dart
@@ -51,5 +51,7 @@
..addTerminateListener(expectAsync0(terminateListener1))
..addTerminateListener(expectAsync0(terminateListener2))
..terminate();
- });
+ },
+ skip:
+ 'this test will cause other tests to not run after it is invoked since it calls exit()');
}
diff --git a/public/dart/fuchsia_modular/test/proposal/internal/proposal_listener_impl_test.dart b/public/dart/fuchsia_modular/test/proposal/internal/proposal_listener_impl_test.dart
new file mode 100644
index 0000000..e564057
--- /dev/null
+++ b/public/dart/fuchsia_modular/test/proposal/internal/proposal_listener_impl_test.dart
@@ -0,0 +1,24 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: implementation_imports
+
+import 'package:test/test.dart';
+
+import 'package:fuchsia_modular/src/proposal/internal/proposal_listener_impl.dart';
+
+void main() {
+ test('calls the callback when proposal accepted', () {
+ String callbackPid;
+ String callbackSid;
+
+ ProposalListenerImpl((pid, sid) {
+ callbackPid = pid;
+ callbackSid = sid;
+ }).onProposalAccepted('proposal', 'story');
+
+ expect(callbackPid, 'proposal');
+ expect(callbackSid, 'story');
+ });
+}
diff --git a/public/dart/fuchsia_modular/test/proposal/proposal_integ_test.dart b/public/dart/fuchsia_modular/test/proposal/proposal_integ_test.dart
new file mode 100644
index 0000000..10f6a5f
--- /dev/null
+++ b/public/dart/fuchsia_modular/test/proposal/proposal_integ_test.dart
@@ -0,0 +1,46 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: implementation_imports
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+import 'package:fidl_fuchsia_modular/fidl_async.dart' as fidl;
+import 'package:fidl_fuchsia_modular/fidl_test.dart' as fidl_test;
+
+import 'package:fuchsia_modular/src/proposal/proposal.dart';
+
+void main() {
+ test('calls the set onAccept function', () async {
+ final completer = Completer();
+ final proposal = Proposal(
+ id: 'foo',
+ headline: 'headline',
+ onProposalAccepted: (i, s) {
+ completer.complete();
+ });
+
+ final publisher = _MockProposalPublisherImpl();
+ await Future.wait([
+ publisher.propose(proposal),
+ completer.future,
+ ]);
+ }, timeout: Timeout(Duration(milliseconds: 100)));
+}
+
+class _MockProposalPublisherImpl extends fidl_test.ProposalPublisher$TestBase {
+ @override
+ Future<void> propose(fidl.Proposal proposal) async {
+ final listenerHandle = proposal.listener;
+ if (listenerHandle == null) {
+ return;
+ }
+
+ final listenerProxy = fidl.ProposalListenerProxy();
+ listenerProxy.ctrl.bind(listenerHandle);
+
+ await listenerProxy.onProposalAccepted(proposal.id, 'foo-story-id');
+ }
+}
diff --git a/public/dart/fuchsia_modular/test/proposal/proposal_test.dart b/public/dart/fuchsia_modular/test/proposal/proposal_test.dart
new file mode 100644
index 0000000..1e493e2
--- /dev/null
+++ b/public/dart/fuchsia_modular/test/proposal/proposal_test.dart
@@ -0,0 +1,50 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: implementation_imports
+
+import 'package:test/test.dart';
+import 'package:fidl_fuchsia_modular/fidl_async.dart' as fidl;
+
+import 'package:fuchsia_modular/src/proposal/proposal.dart';
+
+void main() {
+ test('sets values on display', () {
+ final proposal = Proposal(
+ id: 'foo',
+ headline: 'headline',
+ subheadline: 'subheadline',
+ details: 'details',
+ color: 1,
+ annoyance: fidl.AnnoyanceType.blocking,
+ );
+
+ final display = proposal.display;
+ expect(display.headline, 'headline');
+ expect(display.subheadline, 'subheadline');
+ expect(display.details, 'details');
+ expect(display.color, 1);
+ expect(display.annoyance, fidl.AnnoyanceType.blocking);
+ });
+
+ test('addModuleAffinity', () {
+ final proposal = Proposal(
+ id: 'foo',
+ headline: 'h',
+ )..addModuleAffinity('mod', 'story');
+
+ final affinity = proposal.affinity.first;
+
+ expect(affinity.moduleAffinity.moduleName.first, 'mod');
+ expect(affinity.moduleAffinity.storyName, 'story');
+ });
+
+ test('addStoryAffinity', () {
+ final proposal = Proposal(id: 'foo', headline: 'h')
+ ..addStoryAffinity('story');
+
+ final affinity = proposal.affinity.first;
+ expect(affinity.storyAffinity.storyName, 'story');
+ });
+}