| // META: global=window,worker | |
| // META: script=resources/readable-stream-from-array.js | |
| // META: script=resources/readable-stream-to-array.js | |
| 'use strict'; | |
| const inputString = 'I \u{1F499} streams'; | |
| const expectedOutputBytes = [0x49, 0x20, 0xf0, 0x9f, 0x92, 0x99, 0x20, 0x73, | |
| 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73]; | |
| // This is a character that must be represented in two code units in a string, | |
| // ie. it is not in the Basic Multilingual Plane. | |
| const astralCharacter = '\u{1F499}'; // BLUE HEART | |
| const astralCharacterEncoded = [0xf0, 0x9f, 0x92, 0x99]; | |
| const leading = astralCharacter[0]; | |
| const trailing = astralCharacter[1]; | |
| const replacementEncoded = [0xef, 0xbf, 0xbd]; | |
| // These tests assume that the implementation correctly classifies leading and | |
| // trailing surrogates and treats all the code units in each set equivalently. | |
| const testCases = [ | |
| { | |
| input: [inputString], | |
| output: [expectedOutputBytes], | |
| description: 'encoding one string of UTF-8 should give one complete chunk' | |
| }, | |
| { | |
| input: [leading, trailing], | |
| output: [astralCharacterEncoded], | |
| description: 'a character split between chunks should be correctly encoded' | |
| }, | |
| { | |
| input: [leading, trailing + astralCharacter], | |
| output: [astralCharacterEncoded.concat(astralCharacterEncoded)], | |
| description: 'a character following one split between chunks should be ' + | |
| 'correctly encoded' | |
| }, | |
| { | |
| input: [leading, trailing + leading, trailing], | |
| output: [astralCharacterEncoded, astralCharacterEncoded], | |
| description: 'two consecutive astral characters each split down the ' + | |
| 'middle should be correctly reassembled' | |
| }, | |
| { | |
| input: [leading, trailing + leading + leading, trailing], | |
| output: [astralCharacterEncoded.concat(replacementEncoded), astralCharacterEncoded], | |
| description: 'two consecutive astral characters each split down the ' + | |
| 'middle with an invalid surrogate in the middle should be correctly ' + | |
| 'encoded' | |
| }, | |
| { | |
| input: [leading], | |
| output: [replacementEncoded], | |
| description: 'a stream ending in a leading surrogate should emit a ' + | |
| 'replacement character as a final chunk' | |
| }, | |
| { | |
| input: [leading, astralCharacter], | |
| output: [replacementEncoded.concat(astralCharacterEncoded)], | |
| description: 'an unmatched surrogate at the end of a chunk followed by ' + | |
| 'an astral character in the next chunk should be replaced with ' + | |
| 'the replacement character at the start of the next output chunk' | |
| }, | |
| { | |
| input: [leading, 'A'], | |
| output: [replacementEncoded.concat([65])], | |
| description: 'an unmatched surrogate at the end of a chunk followed by ' + | |
| 'an ascii character in the next chunk should be replaced with ' + | |
| 'the replacement character at the start of the next output chunk' | |
| }, | |
| { | |
| input: [leading, leading, trailing], | |
| output: [replacementEncoded, astralCharacterEncoded], | |
| description: 'an unmatched surrogate at the end of a chunk followed by ' + | |
| 'a plane 1 character split into two chunks should result in ' + | |
| 'the encoded plane 1 character appearing in the last output chunk' | |
| }, | |
| { | |
| input: [leading, leading], | |
| output: [replacementEncoded, replacementEncoded], | |
| description: 'two leading chunks should result in two replacement ' + | |
| 'characters' | |
| }, | |
| { | |
| input: [leading + leading, trailing], | |
| output: [replacementEncoded, astralCharacterEncoded], | |
| description: 'a non-terminal unpaired leading surrogate should ' + | |
| 'immediately be replaced' | |
| }, | |
| { | |
| input: [trailing, astralCharacter], | |
| output: [replacementEncoded, astralCharacterEncoded], | |
| description: 'a terminal unpaired trailing surrogate should ' + | |
| 'immediately be replaced' | |
| }, | |
| { | |
| input: [leading, '', trailing], | |
| output: [astralCharacterEncoded], | |
| description: 'a leading surrogate chunk should be carried past empty chunks' | |
| }, | |
| { | |
| input: [leading, ''], | |
| output: [replacementEncoded], | |
| description: 'a leading surrogate chunk should error when it is clear ' + | |
| 'it didn\'t form a pair' | |
| }, | |
| { | |
| input: [''], | |
| output: [], | |
| description: 'an empty string should result in no output chunk' | |
| }, | |
| { | |
| input: ['', inputString], | |
| output: [expectedOutputBytes], | |
| description: 'a leading empty chunk should be ignored' | |
| }, | |
| { | |
| input: [inputString, ''], | |
| output: [expectedOutputBytes], | |
| description: 'a trailing empty chunk should be ignored' | |
| }, | |
| { | |
| input: ['A'], | |
| output: [[65]], | |
| description: 'a plain ASCII chunk should be converted' | |
| }, | |
| { | |
| input: ['\xff'], | |
| output: [[195, 191]], | |
| description: 'characters in the ISO-8859-1 range should be encoded correctly' | |
| }, | |
| ]; | |
| for (const {input, output, description} of testCases) { | |
| promise_test(async () => { | |
| const inputStream = readableStreamFromArray(input); | |
| const outputStream = inputStream.pipeThrough(new TextEncoderStream()); | |
| const chunkArray = await readableStreamToArray(outputStream); | |
| assert_equals(chunkArray.length, output.length, | |
| 'number of chunks should match'); | |
| for (let i = 0; i < output.length; ++i) { | |
| assert_array_equals(chunkArray[i], output[i], `chunk ${i} should match`); | |
| } | |
| }, description); | |
| } |