Skip to content
This repository was archived by the owner on Mar 19, 2021. It is now read-only.

Commit 3c2d193

Browse files
rayankanschromium-wpt-export-bot
authored andcommitted
[Contacts] Add contacts wpt tests with a mock contacts implementation.
- Create a mocked contacts interface that requires user agents to set the contacts to return when queried. - Implement the mocked contacts interface for chromium. - Move chromium web_tests to the wpt folder. I've added one test and removed one test. Change-Id: I8fe518eef37eedf2908b4b0ea9bec9df9ea4938f Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1741915 Commit-Queue: Rayan Kanso <[email protected]> Reviewed-by: Peter Beverloo <[email protected]> Cr-Commit-Position: refs/heads/master@{#685182}
1 parent 5c61f24 commit 3c2d193

File tree

4 files changed

+285
-0
lines changed

4 files changed

+285
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// META: script=/resources/testdriver.js
2+
// META: script=/resources/testdriver-vendor.js
3+
// META: script=resources/helpers.js
4+
'use strict';
5+
6+
// Verifies that |func|, when invoked, throws a TypeError exception.
7+
async function expectTypeError(func) {
8+
try {
9+
await func();
10+
} catch (e) {
11+
assert_equals(e.name, 'TypeError');
12+
return;
13+
}
14+
15+
assert_unreached('expected a TypeError, but none was thrown');
16+
}
17+
18+
promise_test(async () => {
19+
try {
20+
await navigator.contacts.select(['name']);
21+
assert_unreached('expected a SecurityError, but none was thrown');
22+
} catch (e) {
23+
assert_equals(e.name, 'SecurityError');
24+
}
25+
}, 'The Contact API requires a user gesture')
26+
27+
contactsTestWithUserActivation(async (test, setSelectedContacts) => {
28+
// At least one property must be provided.
29+
await expectTypeError(() => navigator.contacts.select());
30+
await expectTypeError(() => navigator.contacts.select([]));
31+
32+
// Per WebIDL parsing, no invalid values may be provided.
33+
await expectTypeError(() =>
34+
navigator.contacts.select(['']));
35+
await expectTypeError(() =>
36+
navigator.contacts.select(['foo']));
37+
await expectTypeError(() =>
38+
navigator.contacts.select(['name', 'photo']));
39+
40+
}, 'The Contact API requires valid properties to be provided');
41+
42+
contactsTestWithUserActivation(async (test, setSelectedContacts) => {
43+
// Returns a NULL result, indicating that no results are available.
44+
setSelectedContacts(null);
45+
46+
await expectTypeError(() => navigator.contacts.select(['name']));
47+
48+
}, 'The Contact API can fail when the selector cannot be opened');
49+
50+
contactsTestWithUserActivation(async (test, setSelectedContacts) => {
51+
// Returns two contacts with all information available.
52+
setSelectedContacts([
53+
{ name: ['Dwight Schrute'], email: ['[email protected]'], tel: ['000-0000'] },
54+
{ name: ['Michael Scott', 'Prison Mike'], email: ['[email protected]'], tel: [] },
55+
]);
56+
57+
let results = await navigator.contacts.select(['name', 'email', 'tel'], { multiple: true });
58+
assert_equals(results.length, 2);
59+
results = results.sort((c1, c2) => JSON.stringify(c1) < JSON.stringify(c2) ? -1 : 1);
60+
61+
{
62+
const dwight = results[0];
63+
64+
assert_own_property(dwight, 'name');
65+
assert_own_property(dwight, 'email');
66+
assert_own_property(dwight, 'tel');
67+
68+
assert_array_equals(dwight.name, ['Dwight Schrute']);
69+
assert_array_equals(dwight.email, ['[email protected]']);
70+
assert_array_equals(dwight.tel, ['000-0000']);
71+
}
72+
73+
{
74+
const michael = results[1];
75+
76+
assert_own_property(michael, 'name');
77+
assert_own_property(michael, 'email');
78+
assert_own_property(michael, 'tel');
79+
80+
assert_array_equals(michael.name, ['Michael Scott', 'Prison Mike']);
81+
assert_array_equals(michael.email, ['[email protected]']);
82+
assert_array_equals(michael.tel, []);
83+
}
84+
}, 'The Contact API correctly returns ContactInfo entries');
85+
86+
contactsTestWithUserActivation(async (test, setSelectedContacts) => {
87+
// Returns two contacts with all information available.
88+
setSelectedContacts([
89+
{ name: ['Dwight Schrute'], email: ['[email protected]'], tel: ['000-0000'] },
90+
{ name: ['Michael Scott', 'Prison Mike'], email: ['[email protected]'], tel: [] },
91+
]);
92+
93+
const results = await navigator.contacts.select(['name', 'email', 'tel']);
94+
assert_equals(results.length, 1);
95+
96+
}, 'Only one contact is returned if `multiple` is not set.');
97+
98+
contactsTestWithUserActivation(async (test, setSelectedContacts) => {
99+
// Returns partial information since no e-mail addresses are requested.
100+
setSelectedContacts([{ name: ['Creed'], email: ['[email protected]'], tel: [] }]);
101+
102+
const results = await navigator.contacts.select(['name']);
103+
104+
assert_equals(results.length, 1);
105+
106+
{
107+
const creed = results[0];
108+
109+
assert_array_equals(creed.name, ['Creed']);
110+
assert_equals(creed.email, undefined);
111+
assert_equals(creed.tel, undefined);
112+
}
113+
}, 'The Contact API does not include fields that were not requested');
114+
115+
contactsTestWithUserActivation(async (test, setSelectedContacts) => {
116+
// Returns partial information since no e-mail addresses are requested.
117+
setSelectedContacts([{ name: ['Kelly'], email: [], tel: [] }]);
118+
119+
// First request should work.
120+
const promise1 = new Promise((resolve, reject) => {
121+
navigator.contacts.select(['name']).then(resolve)
122+
.catch(e => reject(e.message));
123+
});
124+
125+
// Second request should fail (since the first one didn't complete yet).
126+
const promise2 = new Promise((resolve, reject) => {
127+
navigator.contacts.select(['name']).then(contacts => reject('This was supposed to fail'))
128+
.catch(e => resolve(e.name));
129+
});
130+
131+
const results = await Promise.all([promise1, promise2]);
132+
const contacts = results[0];
133+
assert_equals(contacts.length, 1);
134+
const contact = contacts[0];
135+
assert_equals(contact.name[0], 'Kelly');
136+
assert_equals(results[1], 'InvalidStateError');
137+
138+
}, 'The Contact API cannot be used again until the first operation is complete.');
139+
140+
contactsTestWithUserActivation(async (test, setSelectedContacts) => {
141+
const iframe = document.createElement('iframe');
142+
document.body.appendChild(iframe);
143+
iframe.src = 'resources/non-main-frame-select.html';
144+
await new Promise(resolve => window.addEventListener('message', event => resolve(event.data)))
145+
.then(data => assert_equals(data.errorMsg, 'InvalidStateError'))
146+
.finally(() => iframe.remove())
147+
148+
}, 'Test contacts.select() throws an InvalidStateError in a sub-frame');

contacts/resources/helpers.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'use strict';
2+
3+
// These tests rely on the User Agent providing an implementation of
4+
// platform contacts backends.
5+
//
6+
// In Chromium-based browsers this implementation is provided by a polyfill
7+
// in order to reduce the amount of test-only code shipped to users. To enable
8+
// these tests the browser must be run with these options:
9+
//
10+
// --enable-blink-features=MojoJS,MojoJSTest
11+
const loadChromiumResources = async () => {
12+
if (!window.MojoInterfaceInterceptor) {
13+
// Do nothing on non-Chromium-based browsers or when the Mojo bindings are
14+
// not present in the global namespace.
15+
return;
16+
}
17+
18+
const resources = [
19+
'/gen/layout_test_data/mojo/public/js/mojo_bindings.js',
20+
'/gen/third_party/blink/public/mojom/contacts/contacts_manager.mojom.js',
21+
'/resources/chromium/contacts_manager_mock.js',
22+
];
23+
24+
await Promise.all(resources.map(path => {
25+
const script = document.createElement('script');
26+
script.src = path;
27+
script.async = false;
28+
const promise = new Promise((resolve, reject) => {
29+
script.onload = resolve;
30+
script.onerror = reject;
31+
});
32+
document.head.appendChild(script);
33+
return promise;
34+
}));
35+
};
36+
37+
// User Agents must provide their own implementation of `WebContacts`,
38+
// which must contain the following this interface:
39+
// class WebContactsTest {
40+
// /** @param {?Array<!ContactInfo>} contacts */
41+
// setSelectedContacts(contacts);
42+
// }
43+
async function createWebContactsTest() {
44+
if (typeof WebContactsTest === 'undefined') {
45+
await loadChromiumResources();
46+
}
47+
assert_true(
48+
typeof WebContactsTest !== 'undefined',
49+
'Mojo testing interface is not available.'
50+
);
51+
return new WebContactsTest();
52+
}
53+
54+
// Creates a Promise test for |func| given the |description|. The |func| will
55+
// be executed with `setSelectedContacts` which will allow tests to mock out
56+
// the result of calling navigator.contacts.select. `setSelectedContacts`
57+
// accepts a nullable Array of ContactInfos.
58+
function contactsTestWithUserActivation(func, description) {
59+
promise_test(async test => {
60+
const webContactsTest = await createWebContactsTest();
61+
await window.test_driver.bless('request contacts');
62+
return func(test, contacts => webContactsTest.setSelectedContacts(contacts));
63+
}, description);
64+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
'use strict';
3+
4+
window.onload = function() {
5+
navigator.contacts.select(['name', 'email'], { multiple: true })
6+
.then(results => parent.postMessage({ errorMsg: '' }, '*'))
7+
.catch(exception => parent.postMessage({ errorMsg: exception.name }, '*'));
8+
}
9+
</script>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2018 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
'use strict';
6+
7+
const WebContactsTest = (() => {
8+
class MockContacts {
9+
constructor() {
10+
this.bindingSet_ = new mojo.BindingSet(blink.mojom.ContactsManager);
11+
12+
this.interceptor_ = new MojoInterfaceInterceptor(
13+
blink.mojom.ContactsManager.name);
14+
this.interceptor_.oninterfacerequest =
15+
e => this.bindingSet_.addBinding(this, e.handle);
16+
this.interceptor_.start();
17+
18+
this.selectedContacts_ = [];
19+
}
20+
21+
async select(multiple, includeNames, includeEmails, includeTel) {
22+
if (this.selectedContacts_ === null)
23+
return {contacts: null};
24+
25+
const contactInfos = this.selectedContacts_.map(contact => {
26+
const contactInfo = new blink.mojom.ContactInfo();
27+
if (includeNames)
28+
contactInfo.name = contact.name;
29+
if (includeEmails)
30+
contactInfo.email = contact.email;
31+
if (includeTel)
32+
contactInfo.tel = contact.tel;
33+
return contactInfo;
34+
});
35+
36+
if (!contactInfos.length) return {contacts: []};
37+
if (!multiple) return {contacts: [contactInfos[0]]};
38+
return {contacts: contactInfos};
39+
}
40+
41+
setSelectedContacts(contacts) {
42+
this.selectedContacts_ = contacts;
43+
}
44+
45+
reset() {
46+
this.bindingSet_.closeAllBindings();
47+
this.interceptor_.stop();
48+
}
49+
}
50+
51+
const mockContacts = new MockContacts();
52+
53+
class ContactsTestChromium {
54+
constructor() {
55+
Object.freeze(this); // Make it immutable.
56+
}
57+
58+
setSelectedContacts(contacts) {
59+
mockContacts.setSelectedContacts(contacts);
60+
}
61+
}
62+
63+
return ContactsTestChromium;
64+
})();

0 commit comments

Comments
 (0)