summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/generic-sensor/generic-sensor-tests.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /testing/web-platform/tests/generic-sensor/generic-sensor-tests.js
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/generic-sensor/generic-sensor-tests.js')
-rw-r--r--testing/web-platform/tests/generic-sensor/generic-sensor-tests.js553
1 files changed, 553 insertions, 0 deletions
diff --git a/testing/web-platform/tests/generic-sensor/generic-sensor-tests.js b/testing/web-platform/tests/generic-sensor/generic-sensor-tests.js
new file mode 100644
index 0000000000..3c8f478c54
--- /dev/null
+++ b/testing/web-platform/tests/generic-sensor/generic-sensor-tests.js
@@ -0,0 +1,553 @@
+'use strict';
+
+// Run a set of tests for a given |sensorName|.
+// |readingData| is an object with 3 keys, all of which are arrays of arrays:
+// 1. "readings". Each value corresponds to one raw reading that will be
+// processed by a sensor.
+// 2. "expectedReadings". Each value corresponds to the processed value a
+// sensor will make available to users (i.e. a capped or rounded value).
+// Its length must match |readings|'.
+// 3. "expectedRemappedReadings" (optional). Similar to |expectedReadings|, but
+// used only by spatial sensors, whose reference frame can change the values
+// returned by a sensor.
+// Its length should match |readings|'.
+// |verificationFunction| is called to verify that a given reading matches a
+// value in |expectedReadings|.
+// |featurePolicies| represents |sensorName|'s associated sensor feature name.
+
+function runGenericSensorTests(sensorName,
+ readingData,
+ verificationFunction,
+ featurePolicies) {
+ const sensorType = self[sensorName];
+
+ function validateReadingFormat(data) {
+ return Array.isArray(data) && data.every(element => Array.isArray(element));
+ }
+
+ const { readings, expectedReadings, expectedRemappedReadings } = readingData;
+ if (!validateReadingFormat(readings)) {
+ throw new TypeError('readingData.readings must be an array of arrays.');
+ }
+ if (!validateReadingFormat(expectedReadings)) {
+ throw new TypeError('readingData.expectedReadings must be an array of ' +
+ 'arrays.');
+ }
+ if (readings.length < expectedReadings.length) {
+ throw new TypeError('readingData.readings\' length must be bigger than ' +
+ 'or equal to readingData.expectedReadings\' length.');
+ }
+ if (expectedRemappedReadings &&
+ !validateReadingFormat(expectedRemappedReadings)) {
+ throw new TypeError('readingData.expectedRemappedReadings must be an ' +
+ 'array of arrays.');
+ }
+ if (expectedRemappedReadings &&
+ expectedReadings.length != expectedRemappedReadings.length) {
+ throw new TypeError('readingData.expectedReadings and ' +
+ 'readingData.expectedRemappedReadings must have the same ' +
+ 'length.');
+ }
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ sensorProvider.setGetSensorShouldFail(sensorName, true);
+ const sensor = new sensorType;
+ const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
+ sensor.start();
+
+ const event = await sensorWatcher.wait_for("error");
+
+ assert_false(sensor.activated);
+ assert_equals(event.error.name, 'NotReadableError');
+ }, `${sensorName}: Test that onerror is sent when sensor is not supported.`);
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ sensorProvider.setPermissionsDenied(sensorName, true);
+ const sensor = new sensorType;
+ const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
+ sensor.start();
+
+ const event = await sensorWatcher.wait_for("error");
+
+ assert_false(sensor.activated);
+ assert_equals(event.error.name, 'NotAllowedError');
+ }, `${sensorName}: Test that onerror is sent when permissions are not\
+ granted.`);
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const sensor = new sensorType({frequency: 560});
+ const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
+ sensor.start();
+
+ const mockSensor = await sensorProvider.getCreatedSensor(sensorName);
+ mockSensor.setStartShouldFail(true);
+
+ const event = await sensorWatcher.wait_for("error");
+
+ assert_false(sensor.activated);
+ assert_equals(event.error.name, 'NotReadableError');
+ }, `${sensorName}: Test that onerror is send when start() call has failed.`);
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const sensor = new sensorType({frequency: 560});
+ const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]);
+ sensor.start();
+
+ const mockSensor = await sensorProvider.getCreatedSensor(sensorName);
+
+ await sensorWatcher.wait_for("activate");
+
+ assert_less_than_equal(mockSensor.getSamplingFrequency(), 60);
+ sensor.stop();
+ assert_false(sensor.activated);
+ }, `${sensorName}: Test that frequency is capped to allowed maximum.`);
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const maxSupportedFrequency = 5;
+ sensorProvider.setMaximumSupportedFrequency(maxSupportedFrequency);
+ const sensor = new sensorType({frequency: 50});
+ const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]);
+ sensor.start();
+
+ const mockSensor = await sensorProvider.getCreatedSensor(sensorName);
+
+ await sensorWatcher.wait_for("activate");
+
+ assert_equals(mockSensor.getSamplingFrequency(), maxSupportedFrequency);
+ sensor.stop();
+ assert_false(sensor.activated);
+ }, `${sensorName}: Test that frequency is capped to the maximum supported\
+ frequency.`);
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const minSupportedFrequency = 2;
+ sensorProvider.setMinimumSupportedFrequency(minSupportedFrequency);
+ const sensor = new sensorType({frequency: -1});
+ const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]);
+ sensor.start();
+
+ const mockSensor = await sensorProvider.getCreatedSensor(sensorName);
+
+ await sensorWatcher.wait_for("activate");
+
+ assert_equals(mockSensor.getSamplingFrequency(), minSupportedFrequency);
+ sensor.stop();
+ assert_false(sensor.activated);
+ }, `${sensorName}: Test that frequency is limited to the minimum supported\
+ frequency.`);
+
+ promise_test(async t => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const iframe = document.createElement('iframe');
+ iframe.allow = featurePolicies.join(' \'none\'; ') + ' \'none\';';
+ iframe.srcdoc = '<script>' +
+ ' window.onmessage = message => {' +
+ ' if (message.data === "LOADED") {' +
+ ' try {' +
+ ' new ' + sensorName + '();' +
+ ' parent.postMessage("FAIL", "*");' +
+ ' } catch (e) {' +
+ ' parent.postMessage("PASS", "*");' +
+ ' }' +
+ ' }' +
+ ' };' +
+ '<\/script>';
+ const iframeWatcher = new EventWatcher(t, iframe, "load");
+ document.body.appendChild(iframe);
+ await iframeWatcher.wait_for("load");
+ iframe.contentWindow.postMessage('LOADED', '*');
+
+ const windowWatcher = new EventWatcher(t, window, "message");
+ const message = await windowWatcher.wait_for("message");
+ assert_equals(message.data, 'PASS');
+ }, `${sensorName}: Test that sensor cannot be constructed within iframe\
+ disallowed to use feature policy.`);
+
+ promise_test(async t => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const iframe = document.createElement('iframe');
+ iframe.allow = featurePolicies.join(';') + ';';
+ iframe.srcdoc = '<script>' +
+ ' window.onmessage = message => {' +
+ ' if (message.data === "LOADED") {' +
+ ' try {' +
+ ' new ' + sensorName + '();' +
+ ' parent.postMessage("PASS", "*");' +
+ ' } catch (e) {' +
+ ' parent.postMessage("FAIL", "*");' +
+ ' }' +
+ ' }' +
+ ' };' +
+ '<\/script>';
+ const iframeWatcher = new EventWatcher(t, iframe, "load");
+ document.body.appendChild(iframe);
+ await iframeWatcher.wait_for("load");
+ iframe.contentWindow.postMessage('LOADED', '*');
+
+ const windowWatcher = new EventWatcher(t, window, "message");
+ const message = await windowWatcher.wait_for("message");
+ assert_equals(message.data, 'PASS');
+ }, `${sensorName}: Test that sensor can be constructed within an iframe\
+ allowed to use feature policy.`);
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const sensor = new sensorType();
+ const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
+ sensor.start();
+ assert_false(sensor.hasReading);
+
+ const mockSensor = await sensorProvider.getCreatedSensor(sensorName);
+ mockSensor.setSensorReading(readings);
+
+ await sensorWatcher.wait_for("reading");
+ const expected = new RingBuffer(expectedReadings).next().value;
+ assert_true(verificationFunction(expected, sensor));
+ assert_true(sensor.hasReading);
+
+ sensor.stop();
+ assert_true(verificationFunction(expected, sensor, /*isNull=*/true));
+ assert_false(sensor.hasReading);
+ }, `${sensorName}: Test that 'onreading' is called and sensor reading is\
+ valid.`);
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const sensor1 = new sensorType();
+ const sensorWatcher1 = new EventWatcher(t, sensor1, ["reading", "error"]);
+ sensor1.start();
+
+ const sensor2 = new sensorType();
+ const sensorWatcher2 = new EventWatcher(t, sensor2, ["reading", "error"]);
+ sensor2.start();
+
+ const mockSensor = await sensorProvider.getCreatedSensor(sensorName);
+ mockSensor.setSensorReading(readings);
+
+ await Promise.all([sensorWatcher1.wait_for("reading"),
+ sensorWatcher2.wait_for("reading")]);
+ const expected = new RingBuffer(expectedReadings).next().value;
+ // Reading values are correct for both sensors.
+ assert_true(verificationFunction(expected, sensor1));
+ assert_true(verificationFunction(expected, sensor2));
+
+ // After first sensor stops its reading values are null,
+ // reading values for the second sensor sensor remain.
+ sensor1.stop();
+ assert_true(verificationFunction(expected, sensor1, /*isNull=*/true));
+ assert_true(verificationFunction(expected, sensor2));
+
+ sensor2.stop();
+ assert_true(verificationFunction(expected, sensor2, /*isNull=*/true));
+ }, `${sensorName}: sensor reading is correct.`);
+
+ // Tests that readings maps to expectedReadings correctly. Due to threshold
+ // check and rounding some values might be discarded or changed.
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const sensor = new sensorType();
+ const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
+ sensor.start();
+
+ const mockSensor = await sensorProvider.getCreatedSensor(sensorName);
+ await mockSensor.setSensorReading(readings);
+
+ for (let expectedReading of expectedReadings) {
+ await sensorWatcher.wait_for("reading");
+ assert_true(sensor.hasReading, "hasReading");
+ assert_true(verificationFunction(expectedReading, sensor),
+ "verification");
+ }
+
+ sensor.stop();
+ }, `${sensorName}: Test that readings are all mapped to expectedReadings\
+ correctly.`);
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const sensor = new sensorType();
+ const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
+ sensor.start();
+
+ const mockSensor = await sensorProvider.getCreatedSensor(sensorName);
+ mockSensor.setSensorReading(readings);
+
+ await sensorWatcher.wait_for("reading");
+ const cachedTimeStamp1 = sensor.timestamp;
+
+ await sensorWatcher.wait_for("reading");
+ const cachedTimeStamp2 = sensor.timestamp;
+
+ assert_greater_than(cachedTimeStamp2, cachedTimeStamp1);
+ sensor.stop();
+ }, `${sensorName}: sensor timestamp is updated when time passes.`);
+
+ sensor_test(async t => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const sensor = new sensorType();
+ const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]);
+ assert_false(sensor.activated);
+ sensor.start();
+ assert_false(sensor.activated);
+
+ await sensorWatcher.wait_for("activate");
+ assert_true(sensor.activated);
+
+ sensor.stop();
+ assert_false(sensor.activated);
+ }, `${sensorName}: Test that sensor can be successfully created and its\
+ states are correct.`);
+
+ sensor_test(async t => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const sensor = new sensorType();
+ const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]);
+ sensor.start();
+ sensor.start();
+
+ await sensorWatcher.wait_for("activate");
+ assert_true(sensor.activated);
+ sensor.stop();
+ }, `${sensorName}: no exception is thrown when calling start() on already\
+ started sensor.`);
+
+ sensor_test(async t => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const sensor = new sensorType();
+ const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]);
+ sensor.start();
+
+ await sensorWatcher.wait_for("activate");
+ sensor.stop();
+ sensor.stop();
+ assert_false(sensor.activated);
+ }, `${sensorName}: no exception is thrown when calling stop() on already\
+ stopped sensor.`);
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const sensor = new sensorType();
+ const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]);
+ sensor.start();
+
+ const mockSensor = await sensorProvider.getCreatedSensor(sensorName);
+ mockSensor.setSensorReading(readings);
+
+ const expectedBuffer = new RingBuffer(expectedReadings);
+ await sensorWatcher.wait_for("reading");
+ const expected1 = expectedBuffer.next().value;
+ assert_true(sensor.hasReading);
+ assert_true(verificationFunction(expected1, sensor));
+ const timestamp = sensor.timestamp;
+ sensor.stop();
+ assert_false(sensor.hasReading);
+
+ sensor.start();
+ await sensorWatcher.wait_for("reading");
+ assert_true(sensor.hasReading);
+ // |readingData| may have a single reading/expectation value, and this
+ // is the second reading we are getting. For that case, make sure we
+ // also wrap around as if we had the same RingBuffer used in
+ // generic_sensor_mocks.js.
+ const expected2 = expectedBuffer.next().value;
+ assert_true(verificationFunction(expected2, sensor));
+ // Make sure that 'timestamp' is already initialized.
+ assert_greater_than(timestamp, 0);
+ // Check that the reading is updated.
+ assert_greater_than(sensor.timestamp, timestamp);
+ sensor.stop();
+ }, `${sensorName}: Test that fresh reading is fetched on start().`);
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const sensor = new sensorType();
+ t.add_cleanup(() => {
+ sensor.stop();
+ });
+ const sensorWatcher = new EventWatcher(t, sensor, ['reading', 'error']);
+ sensor.start();
+
+ const mockSensor = await sensorProvider.getCreatedSensor(sensorName);
+ mockSensor.setSensorReading(readings);
+
+ const expectedBuffer = new RingBuffer(expectedReadings);
+ await sensorWatcher.wait_for('reading');
+ const expected1 = expectedBuffer.next().value;
+ assert_true(verificationFunction(expected1, sensor));
+ assert_true(mockSensor.isReadingData());
+ const cachedTimestamp1 = sensor.timestamp;
+
+ const {minimize, restore} = window_state_context(t);
+
+ await minimize();
+ assert_true(document.hidden);
+ await t.step_wait(
+ () => !mockSensor.isReadingData(), 'readings must be suspended');
+ const cachedTimestamp2 = sensor.timestamp;
+ assert_equals(cachedTimestamp1, cachedTimestamp2);
+
+ await restore();
+ assert_false(document.hidden);
+ await t.step_wait(
+ () => mockSensor.isReadingData(), 'readings must be restored');
+ await sensorWatcher.wait_for('reading');
+ const expected2 = expectedBuffer.next().value;
+ assert_true(verificationFunction(expected2, sensor));
+ assert_greater_than(sensor.timestamp, cachedTimestamp2);
+ }, `${sensorName}: Losing visibility must cause readings to be suspended.`);
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+
+ const fastSensor = new sensorType({ frequency: 60 });
+ t.add_cleanup(() => { fastSensor.stop(); });
+ let eventWatcher = new EventWatcher(t, fastSensor, "activate");
+ fastSensor.start();
+
+ // Wait for |fastSensor| to be activated so that the call to
+ // getSamplingFrequency() below works.
+ await eventWatcher.wait_for("activate");
+
+ const mockSensor = await sensorProvider.getCreatedSensor(sensorName);
+ mockSensor.setSensorReading(readings);
+
+ // We need |fastSensorFrequency| because 60Hz might be higher than a sensor
+ // type's maximum allowed frequency.
+ const fastSensorFrequency = mockSensor.getSamplingFrequency();
+ const slowSensorFrequency = fastSensorFrequency * 0.25;
+
+ const slowSensor = new sensorType({ frequency: slowSensorFrequency });
+ t.add_cleanup(() => { slowSensor.stop(); });
+ eventWatcher = new EventWatcher(t, slowSensor, "activate");
+ slowSensor.start();
+
+ // Wait for |slowSensor| to be activated before we check if the mock
+ // platform sensor's sampling frequency has changed.
+ await eventWatcher.wait_for("activate");
+ assert_equals(mockSensor.getSamplingFrequency(), fastSensorFrequency);
+
+ // Now stop |fastSensor| and verify that the sampling frequency has dropped
+ // to the one |slowSensor| had requested.
+ fastSensor.stop();
+ return t.step_wait(() => {
+ return mockSensor.getSamplingFrequency() === slowSensorFrequency;
+ }, "Sampling frequency has dropped to slowSensor's requested frequency");
+ }, `${sensorName}: frequency hint works.`);
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+
+ const sensor1 = new sensorType();
+ const sensor2 = new sensorType();
+
+ return new Promise((resolve, reject) => {
+ sensor1.addEventListener('reading', () => {
+ sensor2.addEventListener('activate', () => {
+ try {
+ assert_true(sensor1.activated);
+ assert_true(sensor1.hasReading);
+ assert_false(verificationFunction(null, sensor1, /*isNull=*/true));
+ assert_not_equals(sensor1.timestamp, null);
+
+ assert_true(sensor2.activated);
+ assert_false(verificationFunction(null, sensor2, /*isNull=*/true));
+ assert_not_equals(sensor2.timestamp, null);
+ } catch (e) {
+ reject(e);
+ }
+ }, { once: true });
+ sensor2.addEventListener('reading', () => {
+ try {
+ assert_true(sensor2.activated);
+ assert_true(sensor2.hasReading);
+ assert_sensor_equals(sensor1, sensor2);
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ }, { once: true });
+ sensor2.start();
+ }, { once: true });
+ sensor1.start();
+ });
+ }, `${sensorName}: Readings delivered by shared platform sensor are\
+ immediately accessible to all sensors.`);
+
+// Re-enable after https://github.com/w3c/sensors/issues/361 is fixed.
+// test(() => {
+// assert_throws_dom("NotSupportedError",
+// () => { new sensorType({invalid: 1}) });
+// assert_throws_dom("NotSupportedError",
+// () => { new sensorType({frequency: 60, invalid: 1}) });
+// if (!expectedRemappedReadings) {
+// assert_throws_dom("NotSupportedError",
+// () => { new sensorType({referenceFrame: "screen"}) });
+// }
+// }, `${sensorName}: throw 'NotSupportedError' for an unsupported sensor\
+// option.`);
+
+ test(() => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const invalidFreqs = [
+ "invalid",
+ NaN,
+ Infinity,
+ -Infinity,
+ {}
+ ];
+ invalidFreqs.map(freq => {
+ assert_throws_js(TypeError,
+ () => { new sensorType({frequency: freq}) },
+ `when freq is ${freq}`);
+ });
+ }, `${sensorName}: throw 'TypeError' if frequency is invalid.`);
+
+ if (!expectedRemappedReadings) {
+ // The sensorType does not represent a spatial sensor.
+ return;
+ }
+
+ sensor_test(async (t, sensorProvider) => {
+ assert_implements(sensorName in self, `${sensorName} is not supported.`);
+ const sensor1 = new sensorType({frequency: 60});
+ const sensor2 = new sensorType({frequency: 60, referenceFrame: "screen"});
+ const sensorWatcher1 = new EventWatcher(t, sensor1, ["reading", "error"]);
+ const sensorWatcher2 = new EventWatcher(t, sensor1, ["reading", "error"]);
+
+ sensor1.start();
+ sensor2.start();
+
+ const mockSensor = await sensorProvider.getCreatedSensor(sensorName);
+ mockSensor.setSensorReading(readings);
+
+ await Promise.all([sensorWatcher1.wait_for("reading"),
+ sensorWatcher2.wait_for("reading")]);
+
+ const expected = new RingBuffer(expectedReadings).next().value;
+ const expectedRemapped =
+ new RingBuffer(expectedRemappedReadings).next().value;
+ assert_true(verificationFunction(expected, sensor1));
+ assert_true(verificationFunction(expectedRemapped, sensor2));
+
+ sensor1.stop();
+ assert_true(verificationFunction(expected, sensor1, /*isNull=*/true));
+ assert_true(verificationFunction(expectedRemapped, sensor2));
+
+ sensor2.stop();
+ assert_true(verificationFunction(expectedRemapped, sensor2,
+ /*isNull=*/true));
+ }, `${sensorName}: sensor reading is correct when options.referenceFrame\
+ is 'screen'.`);
+}
+
+function runGenericSensorInsecureContext(sensorName) {
+ test(() => {
+ assert_false(sensorName in window, `${sensorName} must not be exposed`);
+ }, `${sensorName} is not exposed in an insecure context.`);
+}