1// META: global=window,worker 2// META: script=resources/readable-stream-from-array.js 3// META: script=resources/readable-stream-to-array.js 4 5'use strict'; 6const inputString = 'I \u{1F499} streams'; 7const expectedOutputBytes = [0x49, 0x20, 0xf0, 0x9f, 0x92, 0x99, 0x20, 0x73, 8 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73]; 9// This is a character that must be represented in two code units in a string, 10// ie. it is not in the Basic Multilingual Plane. 11const astralCharacter = '\u{1F499}'; // BLUE HEART 12const astralCharacterEncoded = [0xf0, 0x9f, 0x92, 0x99]; 13const leading = astralCharacter[0]; 14const trailing = astralCharacter[1]; 15const replacementEncoded = [0xef, 0xbf, 0xbd]; 16 17// These tests assume that the implementation correctly classifies leading and 18// trailing surrogates and treats all the code units in each set equivalently. 19 20const testCases = [ 21 { 22 input: [inputString], 23 output: [expectedOutputBytes], 24 description: 'encoding one string of UTF-8 should give one complete chunk' 25 }, 26 { 27 input: [leading, trailing], 28 output: [astralCharacterEncoded], 29 description: 'a character split between chunks should be correctly encoded' 30 }, 31 { 32 input: [leading, trailing + astralCharacter], 33 output: [astralCharacterEncoded.concat(astralCharacterEncoded)], 34 description: 'a character following one split between chunks should be ' + 35 'correctly encoded' 36 }, 37 { 38 input: [leading, trailing + leading, trailing], 39 output: [astralCharacterEncoded, astralCharacterEncoded], 40 description: 'two consecutive astral characters each split down the ' + 41 'middle should be correctly reassembled' 42 }, 43 { 44 input: [leading, trailing + leading + leading, trailing], 45 output: [astralCharacterEncoded.concat(replacementEncoded), astralCharacterEncoded], 46 description: 'two consecutive astral characters each split down the ' + 47 'middle with an invalid surrogate in the middle should be correctly ' + 48 'encoded' 49 }, 50 { 51 input: [leading], 52 output: [replacementEncoded], 53 description: 'a stream ending in a leading surrogate should emit a ' + 54 'replacement character as a final chunk' 55 }, 56 { 57 input: [leading, astralCharacter], 58 output: [replacementEncoded.concat(astralCharacterEncoded)], 59 description: 'an unmatched surrogate at the end of a chunk followed by ' + 60 'an astral character in the next chunk should be replaced with ' + 61 'the replacement character at the start of the next output chunk' 62 }, 63 { 64 input: [leading, 'A'], 65 output: [replacementEncoded.concat([65])], 66 description: 'an unmatched surrogate at the end of a chunk followed by ' + 67 'an ascii character in the next chunk should be replaced with ' + 68 'the replacement character at the start of the next output chunk' 69 }, 70 { 71 input: [leading, leading, trailing], 72 output: [replacementEncoded, astralCharacterEncoded], 73 description: 'an unmatched surrogate at the end of a chunk followed by ' + 74 'a plane 1 character split into two chunks should result in ' + 75 'the encoded plane 1 character appearing in the last output chunk' 76 }, 77 { 78 input: [leading, leading], 79 output: [replacementEncoded, replacementEncoded], 80 description: 'two leading chunks should result in two replacement ' + 81 'characters' 82 }, 83 { 84 input: [leading + leading, trailing], 85 output: [replacementEncoded, astralCharacterEncoded], 86 description: 'a non-terminal unpaired leading surrogate should ' + 87 'immediately be replaced' 88 }, 89 { 90 input: [trailing, astralCharacter], 91 output: [replacementEncoded, astralCharacterEncoded], 92 description: 'a terminal unpaired trailing surrogate should ' + 93 'immediately be replaced' 94 }, 95 { 96 input: [leading, '', trailing], 97 output: [astralCharacterEncoded], 98 description: 'a leading surrogate chunk should be carried past empty chunks' 99 }, 100 { 101 input: [leading, ''], 102 output: [replacementEncoded], 103 description: 'a leading surrogate chunk should error when it is clear ' + 104 'it didn\'t form a pair' 105 }, 106 { 107 input: [''], 108 output: [], 109 description: 'an empty string should result in no output chunk' 110 }, 111 { 112 input: ['', inputString], 113 output: [expectedOutputBytes], 114 description: 'a leading empty chunk should be ignored' 115 }, 116 { 117 input: [inputString, ''], 118 output: [expectedOutputBytes], 119 description: 'a trailing empty chunk should be ignored' 120 }, 121 { 122 input: ['A'], 123 output: [[65]], 124 description: 'a plain ASCII chunk should be converted' 125 }, 126 { 127 input: ['\xff'], 128 output: [[195, 191]], 129 description: 'characters in the ISO-8859-1 range should be encoded correctly' 130 }, 131]; 132 133for (const {input, output, description} of testCases) { 134 promise_test(async () => { 135 const inputStream = readableStreamFromArray(input); 136 const outputStream = inputStream.pipeThrough(new TextEncoderStream()); 137 const chunkArray = await readableStreamToArray(outputStream); 138 assert_equals(chunkArray.length, output.length, 139 'number of chunks should match'); 140 for (let i = 0; i < output.length; ++i) { 141 assert_array_equals(chunkArray[i], output[i], `chunk ${i} should match`); 142 } 143 }, description); 144} 145