Test that the right events are sent to the Bluetooth chooser.

And that the chooser can control which device is returned from requestDevice().

Depends on https://codereview.chromium.org/1325953002 for testing functions, which are specified in https://webbluetoothcg.github.io/web-bluetooth/tests/.

BUG=500989

Review URL: https://codereview.chromium.org/1304353004

git-svn-id: svn://svn.chromium.org/blink/trunk@202647 bbb929c8-8fbe-4397-9dbb-9b2b20218538
diff --git a/LayoutTests/bluetooth/requestDevice.html b/LayoutTests/bluetooth/requestDevice.html
index 834bc70..efd10a4 100644
--- a/LayoutTests/bluetooth/requestDevice.html
+++ b/LayoutTests/bluetooth/requestDevice.html
@@ -21,14 +21,23 @@
                                 new TypeError());
 }, 'RequestDeviceOptions requires a |filters| member.');
 
-// TODO(jyasskin): Add a test that the chooser is informed of a failed discovery
-// session.
 promise_test(() => {
   testRunner.setBluetoothMockDataSet('FailStartDiscoveryAdapter');
-  return assert_promise_rejects_with_message(
-    requestDeviceWithKeyDown({filters: [{services: ['generic_access']}]}),
-    {name: 'NotFoundError', message: 'User cancelled the requestDevice() chooser.'},
-    'The adapter failed to start a discovery session.');
+  testRunner.setBluetoothManualChooser();
+  let requestDevicePromise =
+      requestDeviceWithKeyDown({filters: [{services: ['generic_access']}]});
+  return getBluetoothManualChooserEvents()
+    .then(events => {
+      assert_array_equals(events,
+                          ['chooser-opened(file:///)',
+                           'discovering',
+                           'discovery-failed-to-start']);
+      testRunner.sendBluetoothManualChooserEvent('cancelled', '');
+      return assert_promise_rejects_with_message(
+        requestDevicePromise,
+        {name: 'NotFoundError', message: 'User cancelled the requestDevice() chooser.'},
+        'The adapter failed to start a discovery session.');
+    });
 }, 'Discovery session fails to start.');
 
 promise_test(() => {
@@ -39,18 +48,24 @@
     'Bluetooth adapter is not present.');
 }, 'Reject with NotFoundError if the adapter is not present.');
 
-// TODO(jyasskin): Add a test that the chooser is informed of a disabled
-// Bluetooth adapter.
 promise_test(() => {
   testRunner.setBluetoothMockDataSet('NotPoweredAdapter');
-  return assert_promise_rejects_with_message(
-    requestDeviceWithKeyDown({filters: [{services: ['generic_access']}]}),
-    {name: 'NotFoundError', message: 'User cancelled the requestDevice() chooser.'},
-    'Bluetooth adapter is not powered.');
+  testRunner.setBluetoothManualChooser();
+  let requestDevicePromise =
+      requestDeviceWithKeyDown({filters: [{services: ['generic_access']}]});
+  return getBluetoothManualChooserEvents()
+    .then(events => {
+      assert_array_equals(events,
+                          ['chooser-opened(file:///)',
+                           'adapter-disabled']);
+      testRunner.sendBluetoothManualChooserEvent('cancelled', '');
+      return assert_promise_rejects_with_message(
+        requestDevicePromise,
+        {name: 'NotFoundError', message: 'User cancelled the requestDevice() chooser.'},
+        'Bluetooth adapter is not powered.');
+    });
 }, 'Reject with NotFoundError if the adapter is off.');
 
-// TODO(jyasskin): Add a test that the chooser gets a full list of found
-// devices.
 promise_test(() => {
   testRunner.setBluetoothMockDataSet('EmptyAdapter');
   return assert_promise_rejects_with_message(
@@ -123,6 +138,31 @@
 
 promise_test(() => {
   testRunner.setBluetoothMockDataSet('GlucoseHeartRateAdapter');
+  testRunner.setBluetoothManualChooser();
+  let requestDevicePromise = requestDeviceWithKeyDown({
+    filters: [{services: ['glucose']},
+              {services: ['heart_rate']}]
+  });
+  return getBluetoothManualChooserEvents(5)
+    .then(events => {
+      assert_equals(events.length, 5, events);
+      assert_equals(events[0], 'chooser-opened(file:///)', 'events[0]');
+      let idsByName = new AddDeviceEventSet();
+      for (let addedDevice of [events[1], events[2]]) {
+        idsByName.assert_add_device_event(addedDevice);
+      }
+      assert_true(idsByName.has('Heart Rate Device'));
+      assert_true(idsByName.has('Glucose Device'));
+      assert_equals(events[3], 'discovering');
+      assert_equals(events[4], 'discovery-idle');
+      testRunner.sendBluetoothManualChooserEvent('selected',
+                                                 idsByName.get('Glucose Device'));
+      return requestDevicePromise;
+    }).then(device => assert_equals(device.name, 'Glucose Device'));
+}, 'The chooser includes all devices.');
+
+promise_test(() => {
+  testRunner.setBluetoothMockDataSet('GlucoseHeartRateAdapter');
   return requestDeviceWithKeyDown({filters: [{services: ['glucose']}]})
     .then(device => assert_equals(device.name, 'Glucose Device'));
 }, 'Simple filter selects matching device.');
diff --git a/LayoutTests/bluetooth/resources/bluetooth-helpers.js b/LayoutTests/bluetooth/resources/bluetooth-helpers.js
index 0648804..af4654d 100644
--- a/LayoutTests/bluetooth/resources/bluetooth-helpers.js
+++ b/LayoutTests/bluetooth/resources/bluetooth-helpers.js
@@ -57,6 +57,26 @@
   return callWithKeyDown(() => navigator.bluetooth.requestDevice.apply(navigator.bluetooth, args));
 }
 
+// Calls testRunner.getBluetoothManualChooserEvents() until it's returned
+// |expected_count| events. Or just once if |expected_count| is undefined.
+function getBluetoothManualChooserEvents(expected_count) {
+  if (expected_count === undefined) {
+    expected_count = 0;
+  }
+  return new Promise((resolve, reject) => {
+    let events = [];
+    let accumulate_events = new_events => {
+      events.push(...new_events);
+      if (events.length >= expected_count) {
+        resolve(events);
+      } else {
+        testRunner.getBluetoothManualChooserEvents(accumulate_events);
+      }
+    };
+    testRunner.getBluetoothManualChooserEvents(accumulate_events);
+  });
+}
+
 // errorUUID(alias) returns a UUID with the top 32 bits of
 // '00000000-97e5-4cd7-b9f1-f5a427670c59' replaced with the bits of |alias|.
 // For example, errorUUID(0xDEADBEEF) returns
@@ -85,3 +105,24 @@
     }
   });
 }
+
+// Parses add-device(name)=id lines in
+// testRunner.getBluetoothManualChooserEvents() output, and exposes the name->id
+// mapping.
+class AddDeviceEventSet {
+  constructor() {
+    this._idsByName = new Map();
+    this._addDeviceRegex = /^add-device\(([^)]+)\)=(.+)$/;
+  }
+  assert_add_device_event(event, description) {
+    let match = this._addDeviceRegex.exec(event);
+    assert_true(!!match, event + "isn't an add-device event: " + description);
+    this._idsByName.set(match[1], match[2]);
+  }
+  has(name) {
+    return this._idsByName.has(name);
+  }
+  get(name) {
+    return this._idsByName.get(name);
+  }
+}