| <!DOCTYPE html> |
| <meta charset=utf-8> |
| <title>Web NFC: NDEFWriter.write Tests</title> |
| <link rel="author" title="Intel" href="http://www.intel.com"/> |
| <link rel="help" href="https://w3c.github.io/web-nfc/"/> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="resources/nfc-helpers.js"></script> |
| <script> |
| |
| "use strict"; |
| |
| const invalid_type_messages = |
| [ |
| // Invalid NDEFMessageSource type |
| undefined, |
| |
| // NDEFMessage.records: should have at least 1 valid record. |
| // https://w3c.github.io/web-nfc/#the-write-method - Step 8. |
| createMessage([{}]), |
| |
| // NDEFMessageSource: not NDEF-formatable. |
| // https://w3c.github.io/web-nfc/#the-write-method - Step 8. |
| createMessage([]), |
| |
| // https://w3c.github.io/web-nfc/#dfn-map-text-to-ndef |
| // NDEFRecord must have data. |
| createMessage([createTextRecord()]), |
| |
| // NDEFRecord.data for 'text' record must be either a string, |
| // an arrayBuffer, or an arrayBufferView. |
| createMessage([createTextRecord(test_json_data)]), |
| |
| // NDEFRecord.encoding for 'text' record must be either "utf-8", |
| // "utf-16", "utf-16le" or "utf-16be". |
| createMessage([createTextRecord(test_buffer_data, "chinese")]), |
| |
| // https://w3c.github.io/web-nfc/#dfn-map-a-url-to-ndef |
| // NDEFRecord must have data. |
| createMessage([createUrlRecord()]), |
| |
| // https://w3c.github.io/web-nfc/#dfn-map-a-url-to-ndef |
| // NDEFRecord must have data. |
| createMessage([createUrlRecord(undefined, true)]), |
| |
| // NDEFRecord.data for 'url' record must be string. |
| createMessage([createUrlRecord(test_buffer_data)]), |
| createMessage([createUrlRecord(test_json_data)]), |
| |
| // NDEFRecord.data for 'absolute-url' record must be string. |
| createMessage([createUrlRecord(test_buffer_data, true)]), |
| createMessage([createUrlRecord(test_json_data, true)]), |
| |
| // https://w3c.github.io/web-nfc/#dfn-map-binary-data-to-ndef |
| // NDEFRecord must have data. |
| createMessage([createMimeRecord()]), |
| |
| // NDEFRecord.data for 'mime' record must be BufferSource. |
| createMessage([createMimeRecord(test_text_data)]), |
| createMessage([createMimeRecord(test_number_data)]), |
| createMessage([createMimeRecord(test_json_data)]), |
| |
| // NDEFRecord must have data. |
| createMessage([createUnknownRecord()]), |
| |
| // NDEFRecord.data for 'unknown' record must be BufferSource. |
| createMessage([createUnknownRecord(test_text_data)]), |
| createMessage([createUnknownRecord(test_number_data)]), |
| createMessage([createUnknownRecord(test_json_data)]), |
| |
| // https://w3c.github.io/web-nfc/#dfn-map-external-data-to-ndef |
| // NDEFRecord must have data. |
| createMessage([createRecord('w3.org:xyz')]), |
| |
| // NDEFRecord.data for external record must be a BufferSource or NDEFMessageInit. |
| createMessage([createRecord('w3.org:xyz', test_text_data)]), |
| createMessage([createRecord('w3.org:xyz', test_number_data)]), |
| createMessage([createRecord('w3.org:xyz', test_json_data)]), |
| |
| // https://w3c.github.io/web-nfc/#dfn-map-local-type-to-ndef |
| // NDEFRecord must have data. |
| createMessage([createRecord(':xyz')]), |
| |
| // NDEFRecord.data for local type record must be a BufferSource or NDEFMessageInit. |
| createMessage([createRecord(':xyz', test_text_data)]), |
| createMessage([createRecord(':xyz', test_number_data)]), |
| createMessage([createRecord(':xyz', test_json_data)]), |
| |
| // https://w3c.github.io/web-nfc/#dfn-map-smart-poster-to-ndef |
| // NDEFRecord must have data. |
| createMessage([createRecord('smart-poster')]), |
| |
| // NDEFRecord.data for smart-poster record must be a NDEFMessageInit. |
| createMessage([createRecord('smart-poster', test_text_data)]), |
| createMessage([createRecord('smart-poster', test_number_data)]), |
| createMessage([createRecord('smart-poster', test_json_data)]), |
| createMessage([createRecord('smart-poster', test_buffer_data)]), |
| |
| // https://w3c.github.io/web-nfc/#ndef-record-types |
| // The record type is neither a known type ('text', 'mime' etc.) nor a |
| // valid external/local type. |
| createMessage([createRecord('unmatched_type', test_buffer_data)]) |
| ]; |
| |
| const invalid_syntax_messages = |
| [ |
| // Data for 'url' or 'absolute-url' record, must be a valid URL. |
| createMessage([createUrlRecord('Invalid URL:// Data')]), |
| createMessage([createUrlRecord('Invalid URL:// Data', true)]), |
| |
| // NDEFRecord.lang length for 'text' record must be lower than 64. |
| createMessage([createTextRecord(test_text_data, undefined /* encoding */, |
| [...Array(64)].map(_ => 'a'))]), |
| ]; |
| |
| const invalid_signals = [ |
| "string", |
| 123, |
| {}, |
| true, |
| Symbol(), |
| () => {}, |
| self |
| ]; |
| |
| promise_test(async t => { |
| const writer = new NDEFWriter(); |
| const promises = []; |
| invalid_type_messages.forEach(message => { |
| promises.push( |
| promise_rejects_js(t, TypeError, writer.write(message))); |
| }); |
| await Promise.all(promises); |
| }, "Test that promise is rejected with TypeError if NDEFMessageSource is invalid."); |
| |
| promise_test(async t => { |
| const writer = new NDEFWriter(); |
| const promises = []; |
| invalid_syntax_messages.forEach(message => { |
| promises.push( |
| promise_rejects_dom(t, 'SyntaxError', writer.write(message))); |
| }); |
| await Promise.all(promises); |
| }, "Test that promise is rejected with SyntaxError if NDEFMessageSource contains\ |
| invalid records."); |
| |
| promise_test(async t => { |
| if (window.testRunner) { |
| // Deny nfc permissions for Chromium testrunner. |
| window.testRunner.setPermission('nfc', 'denied', |
| location.origin, location.origin); |
| } |
| const writer = new NDEFWriter(); |
| await promise_rejects_dom(t, 'NotAllowedError', writer.write(test_text_data)); |
| }, 'NDEFWriter.write should fail if user permission is not granted.'); |
| |
| // We do not provide NFC mock here to simulate that there has no available |
| // implementation for NFC Mojo interface. |
| promise_test(async t => { |
| if (window.testRunner) { |
| // Deny nfc permissions for Chromium testrunner. |
| window.testRunner.setPermission('nfc', 'granted', |
| location.origin, location.origin); |
| } |
| const writer = new NDEFWriter(); |
| await promise_rejects_dom(t, 'NotSupportedError', writer.write(test_text_data)); |
| }, 'NDEFWriter.write should fail if no implementation for NFC Mojo interface is available.'); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| const controller = new AbortController(); |
| |
| //Make sure push is pending |
| mockNFC.setPendingPushCompleted(false); |
| const p = writer.write(test_text_data, { signal: controller.signal }); |
| const rejected = promise_rejects_dom(t, 'AbortError', p); |
| let callback_called = false; |
| await new Promise(resolve => { |
| t.step_timeout(() => { |
| callback_called = true; |
| controller.abort(); |
| resolve(); |
| }, 10); |
| }); |
| await rejected; |
| assert_true(callback_called, 'timeout should have caused the abort'); |
| }, "NDEFWriter.write should fail if abort write request before write happends."); |
| |
| promise_test(async t => { |
| const writer = new NDEFWriter(); |
| const controller = new AbortController(); |
| assert_false(controller.signal.aborted); |
| controller.abort(); |
| assert_true(controller.signal.aborted); |
| await promise_rejects_dom(t, 'AbortError', |
| writer.write(test_text_data, { signal: controller.signal })); |
| }, "NDEFWriter.write should fail if signal's aborted flag is set."); |
| |
| promise_test(async t => { |
| const writer = new NDEFWriter(); |
| const promises = []; |
| invalid_signals.forEach(invalid_signal => { |
| promises.push(promise_rejects_js(t, TypeError, |
| writer.write(test_text_data, { signal: invalid_signal }))); |
| }); |
| await Promise.all(promises); |
| }, "NDEFWriter.write should fail if signal is not an AbortSignal."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer1 = new NDEFWriter(); |
| const writer2 = new NDEFWriter(); |
| const controller = new AbortController(); |
| const p1 = writer1.write(test_text_data, { signal: controller.signal }); |
| |
| // Even though write request is grantable, |
| // this abort should be processed synchronously. |
| controller.abort(); |
| await promise_rejects_dom(t, 'AbortError', p1); |
| |
| await writer2.write(test_text_data); |
| assertNDEFMessagesEqual(test_text_data, mockNFC.pushedMessage()); |
| }, "Synchronously signaled abort."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| mockNFC.setHWStatus(NFCHWStatus.DISABLED); |
| await promise_rejects_dom(t, 'NotReadableError', writer.write(test_text_data)); |
| }, "NDEFWriter.write should fail when NFC HW is disabled."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| mockNFC.setHWStatus(NFCHWStatus.NOT_SUPPORTED); |
| await promise_rejects_dom(t, 'NotSupportedError', writer.write(test_text_data)); |
| }, "NDEFWriter.write should fail when NFC HW is not supported."); |
| |
| promise_test(async () => { |
| await new Promise((resolve,reject) => { |
| const iframe = document.createElement('iframe'); |
| iframe.srcdoc = `<script> |
| if (window.testRunner) { |
| // Grant nfc permissions for Chromium testrunner. |
| window.testRunner.setPermission('nfc', 'granted', |
| location.origin, location.origin); |
| } |
| window.onmessage = message => { |
| if (message.data === "Ready") { |
| const onSuccess = () => { |
| parent.postMessage("Failure", "*"); |
| }; |
| const onError = error => { |
| if (error.name == "NotAllowedError") { |
| parent.postMessage("Success", "*"); |
| } else { |
| parent.postMessage("Failure", "*"); |
| } |
| }; |
| try { |
| const writer = new NDEFWriter(); |
| writer.write("Test").then(onSuccess, onError); |
| } catch(e) { |
| parent.postMessage("Failure", "*"); |
| } |
| } |
| }; |
| <\/script>`; |
| iframe.onload = () => iframe.contentWindow.postMessage('Ready', '*'); |
| document.body.appendChild(iframe); |
| window.onmessage = message => { |
| if (message.data == 'Success') { |
| resolve(); |
| } else if (message.data == 'Failure') { |
| reject(); |
| } |
| } |
| }); |
| }, 'Test that WebNFC API is not accessible from iframe context.'); |
| |
| nfc_test(async () => { |
| const writer = new NDEFWriter(); |
| await writer.write(test_text_data); |
| }, 'NDEFWriter.write should succeed when NFC HW is enabled'); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| const message = createMessage([createTextRecord(test_text_data), |
| createMimeRecordFromJson(test_json_data), |
| createMimeRecord(test_buffer_data), |
| createUnknownRecord(test_buffer_data), |
| createUrlRecord(test_url_data), |
| createUrlRecord(test_url_data, true), |
| createRecord('w3.org:xyz', test_buffer_data)], |
| test_message_origin); |
| await writer.write(message); |
| assertNDEFMessagesEqual(message, mockNFC.pushedMessage()); |
| }, "NDEFWriter.write NDEFMessage containing text, mime, unknown, url, absolute-url \ |
| and external records with default NDEFWriteOptions."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const messageContainText = createMessage([createTextRecord(test_text_data)]); |
| |
| // Prepare a local type record that uses |messageContainText| as its payload. |
| const messageContainLocal = createMessage([createRecord(':containsTextRecord', messageContainText)]); |
| |
| // Prepare an external type record that uses |messageContainLocal| as its payload. |
| const message = createMessage([createRecord('example.com:containsLocalRecord', messageContainLocal)]); |
| |
| const writer = new NDEFWriter(); |
| await writer.write(message); |
| const pushed_message = mockNFC.pushedMessage(); |
| |
| // The mojom message received by mock nfc contains only the external type record. |
| assert_equals(pushed_message.data.length, 1); |
| assert_equals(pushed_message.data[0].recordType, 'example.com:containsLocalRecord', 'recordType'); |
| |
| // The external type record's payload is from the original |messageContainLocal|, |
| // containing only the local type record. |
| assert_array_equals(pushed_message.data[0].data, new Uint8Array(0), |
| 'payloadMessage is used instead'); |
| assert_equals(pushed_message.data[0].payloadMessage.data.length, 1); |
| assert_equals(pushed_message.data[0].payloadMessage.data[0].recordType, ':containsTextRecord', 'recordType'); |
| |
| // The local type record's payload is from the original |messageContainText|, |
| // containing only the text record. |
| assert_array_equals(pushed_message.data[0].payloadMessage.data[0].data, new Uint8Array(0), |
| 'payloadMessage is used instead'); |
| assertNDEFMessagesEqual(messageContainText, pushed_message.data[0].payloadMessage.data[0].payloadMessage); |
| }, "NDEFWriter.write NDEFMessage containing embedded records."); |
| |
| nfc_test(async (t, mockNFC) => { |
| // A smart-poster record contains a uri record, text record. |
| const uri_record = createUrlRecord(test_url_data); |
| const text_record = createTextRecord(test_text_data); |
| const payload_message = createMessage([uri_record, text_record]); |
| const message = createMessage([createRecord( |
| 'smart-poster', payload_message, "dummy_record_id")]); |
| |
| const writer = new NDEFWriter(); |
| await writer.write(message); |
| const pushed_message = mockNFC.pushedMessage(); |
| |
| // The mojom message received by mock nfc contains only the smart-poster record. |
| assert_equals(pushed_message.data.length, 1); |
| assert_equals(pushed_message.data[0].recordType, 'smart-poster', 'recordType'); |
| assert_equals(pushed_message.data[0].mediaType, null, 'mediaType'); |
| assert_equals(pushed_message.data[0].id, 'dummy_record_id', 'id'); |
| |
| const embedded_records = pushed_message.data[0].payloadMessage.data; |
| assert_equals(embedded_records.length, 2); |
| |
| let embedded_record_types = []; |
| for(let record of embedded_records) { |
| embedded_record_types.push(record.recordType); |
| switch(record.recordType) { |
| case 'url': |
| compareNDEFRecords(uri_record, record); |
| break; |
| case 'text': |
| compareNDEFRecords(text_record, record); |
| break; |
| default: |
| assert_unreached("Unknown recordType"); |
| } |
| } |
| assert_array_equals(embedded_record_types.sort(), ['text', 'url'], |
| 'smart-poster record\'s contained record types'); |
| }, "NDEFWriter.write NDEFMessage containing smart-poster record."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| await writer.write(test_text_data); |
| assertNDEFMessagesEqual(test_text_data, mockNFC.pushedMessage()); |
| }, "Test that NDEFWriter.write succeeds when message is DOMString."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| await writer.write(test_buffer_data); |
| assertNDEFMessagesEqual(test_buffer_data, mockNFC.pushedMessage()); |
| }, "Test that NDEFWriter.write succeeds when message is ArrayBuffer."); |
| |
| nfc_test(async (t, mockNFC) => { |
| let buffer_view = new Uint8Array(test_buffer_data, 2, 5); |
| const writer = new NDEFWriter(); |
| await writer.write(buffer_view); |
| assertNDEFMessagesEqual(buffer_view, mockNFC.pushedMessage()); |
| }, "Test that NDEFWriter.write succeeds when message is ArrayBufferView."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| await writer.write(createMessage([createRecord('empty')])); |
| const receivedMessage = mockNFC.pushedMessage(); |
| assert_equals(receivedMessage.data.length, 1); |
| assert_equals(receivedMessage.data[0].recordType, 'empty', 'recordType'); |
| }, "NDEFWriter.write with 'empty' record should succeed."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| await writer.write(test_text_data); |
| assertNDEFWriteOptionsEqual(createNDEFWriteOptions(true), |
| mockNFC.writeOptions()); |
| }, "Check that default NDEFWriteOptions values are correctly set."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| let ndefWriteOptions = createNDEFWriteOptions(false); |
| await writer.write(test_text_data, ndefWriteOptions); |
| assertNDEFWriteOptionsEqual(ndefWriteOptions, mockNFC.writeOptions()); |
| }, "Check that provided NDEFWriteOptions values are correctly converted."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const reader = new NDEFReader(); |
| const message = createMessage([createTextRecord(test_text_data)]); |
| const controller = new AbortController(); |
| const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]); |
| await reader.scan({ signal: controller.signal }); |
| |
| const writer = new NDEFWriter(); |
| await writer.write(test_text_data, { ignoreRead: false }); |
| assertNDEFMessagesEqual(test_text_data, mockNFC.pushedMessage()); |
| |
| mockNFC.setReadingMessage(message); |
| await readerWatcher.wait_for("reading").then(event => { |
| controller.abort(); |
| assertWebNDEFMessagesEqual(event.message, new NDEFMessage(message)); |
| }); |
| |
| }, "NDEFWriter.write should read data when ignoreRead is false."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const reader = new NDEFReader(); |
| const message = createMessage([createTextRecord(test_text_data)]); |
| // Ignore reading if NDEFWriteOptions.ignoreRead is true |
| reader.onreading = t.unreached_func("reading event should not be fired."); |
| await reader.scan(); |
| |
| const writer = new NDEFWriter(); |
| await writer.write(test_text_data, { ignoreRead: true }); |
| mockNFC.setReadingMessage(message); |
| assertNDEFMessagesEqual(test_text_data, mockNFC.pushedMessage()); |
| }, "NDEFWriter.write should ignore reading data when ignoreRead is true."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer1 = new NDEFWriter(); |
| const writer2 = new NDEFWriter(); |
| |
| const ndefWriteOptions1 = createNDEFWriteOptions( false); |
| const ndefWriteOptions2 = createNDEFWriteOptions(true); |
| const p1 = writer1.write(test_text_data, ndefWriteOptions1); |
| const p2 = writer2.write(test_url_data, ndefWriteOptions2); |
| |
| await new Promise((resolve, reject) => { |
| // Make first push pending |
| mockNFC.setPendingPushCompleted(false); |
| let err = ""; |
| p1.then(() => { |
| reject("pending push should not be fulfilled"); |
| }).catch(e => { |
| err = e.name; |
| }); |
| p2.then(() => { |
| assertNDEFMessagesEqual(test_url_data, mockNFC.pushedMessage()); |
| assertNDEFWriteOptionsEqual(ndefWriteOptions2, mockNFC.writeOptions()); |
| assert_equals(err, "AbortError", "the pending push should be aborted"); |
| resolve(); |
| }); |
| }); |
| }, "NDEFWriter.write should replace all previously configured write operations."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| await writer.write({ records: [{ recordType: "mime", data: test_buffer_data }] }); |
| assertNDEFMessagesEqual(test_buffer_data, mockNFC.pushedMessage()); |
| }, "Test that mediaType should be set to 'application/octet-stream' if \ |
| NDEFRecordInit.record's recordType is 'mime' and NDEFRecordInit.record's \ |
| mediaType is undefined."); |
| |
| nfc_test(async (t, mockNFC) => { |
| // Make sure the push will be pending in the mock. |
| mockNFC.setPendingPushCompleted(false); |
| |
| const writer = new NDEFWriter(); |
| const promise = writer.write(test_text_data); |
| |
| // Just to make sure the write() request has already reached to the mock. |
| const reader = new NDEFReader(); |
| await reader.scan(); |
| |
| mockNFC.simulateNonNDEFTagDiscovered(); |
| await promise_rejects_dom(t, 'NotSupportedError', promise); |
| }, "NDEFWriter.write should fail when the NFC device coming up does not expose \ |
| NDEF technology."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| await writer.write(test_text_data, { overwrite: false }); |
| assertNDEFMessagesEqual(test_text_data, mockNFC.pushedMessage()); |
| }, "NDEFWriter.write should succeed to write data to an unformatted NFC device \ |
| when the NDEFWriteOptions.overwrite is false."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| await writer.write(test_buffer_data); |
| assertNDEFMessagesEqual(test_buffer_data, mockNFC.pushedMessage()); |
| await writer.write(test_text_data, { overwrite: true }); |
| assertNDEFMessagesEqual(test_text_data, mockNFC.pushedMessage()); |
| }, "NDEFWriter.write should succeed to overwrite the existing data \ |
| when the NDEFWriteOptions.overwrite is true."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| const p = writer.write(test_text_data, { overwrite: false }); |
| mockNFC.setIsFormattedTag(true); |
| await promise_rejects_dom(t, 'NotAllowedError', p); |
| }, "NDEFWriter.write should fail when there are NDEF records on the NFC device \ |
| and NDEFWriteOptions.overwrite is false."); |
| |
| nfc_test(async (t, mockNFC) => { |
| const writer = new NDEFWriter(); |
| mockNFC.simulateDataTransferFails(); |
| await promise_rejects_dom(t, 'NetworkError', writer.write(test_text_data)); |
| }, "NDEFWriter.write should fail with NetworkError when NFC data transfer fails."); |
| |
| </script> |