1// Flags: --expose-internals
2
3// This tests Node.js-specific behaviors of TextDecoder
4
5'use strict';
6
7const common = require('../common');
8
9const assert = require('assert');
10const { customInspectSymbol: inspect } = require('internal/util');
11const util = require('util');
12
13const buf = Buffer.from([0xef, 0xbb, 0xbf, 0x74, 0x65,
14                         0x73, 0x74, 0xe2, 0x82, 0xac]);
15
16// Make Sure TextDecoder exist
17assert(TextDecoder);
18
19// Test TextDecoder, UTF-8, fatal: false, ignoreBOM: false
20{
21  ['unicode-1-1-utf-8', 'utf8', 'utf-8'].forEach((i) => {
22    const dec = new TextDecoder(i);
23    assert.strictEqual(dec.encoding, 'utf-8');
24    const res = dec.decode(buf);
25    assert.strictEqual(res, 'test€');
26  });
27
28  ['unicode-1-1-utf-8', 'utf8', 'utf-8'].forEach((i) => {
29    const dec = new TextDecoder(i);
30    let res = '';
31    res += dec.decode(buf.slice(0, 8), { stream: true });
32    res += dec.decode(buf.slice(8));
33    assert.strictEqual(res, 'test€');
34  });
35}
36
37// Test TextDecoder, UTF-8, fatal: false, ignoreBOM: true
38{
39  ['unicode-1-1-utf-8', 'utf8', 'utf-8'].forEach((i) => {
40    const dec = new TextDecoder(i, { ignoreBOM: true });
41    const res = dec.decode(buf);
42    assert.strictEqual(res, '\ufefftest€');
43  });
44
45  ['unicode-1-1-utf-8', 'utf8', 'utf-8'].forEach((i) => {
46    const dec = new TextDecoder(i, { ignoreBOM: true });
47    let res = '';
48    res += dec.decode(buf.slice(0, 8), { stream: true });
49    res += dec.decode(buf.slice(8));
50    assert.strictEqual(res, '\ufefftest€');
51  });
52}
53
54// Invalid encoders
55{
56  ['meow', 'nonunicode', 'foo', 'bar'].forEach((fakeEncoding) => {
57    assert.throws(
58      () => { new TextDecoder(fakeEncoding); },
59      {
60        code: 'ERR_ENCODING_NOT_SUPPORTED',
61        name: 'RangeError'
62      }
63    );
64  });
65}
66
67// Test TextDecoder, UTF-8, fatal: true, ignoreBOM: false
68if (common.hasIntl) {
69  ['unicode-1-1-utf-8', 'utf8', 'utf-8'].forEach((i) => {
70    const dec = new TextDecoder(i, { fatal: true });
71    assert.throws(() => dec.decode(buf.slice(0, 8)),
72                  {
73                    code: 'ERR_ENCODING_INVALID_ENCODED_DATA',
74                    name: 'TypeError',
75                    message: 'The encoded data was not valid ' +
76                          'for encoding utf-8'
77                  });
78  });
79
80  ['unicode-1-1-utf-8', 'utf8', 'utf-8'].forEach((i) => {
81    const dec = new TextDecoder(i, { fatal: true });
82    dec.decode(buf.slice(0, 8), { stream: true });
83    dec.decode(buf.slice(8));
84  });
85} else {
86  assert.throws(
87    () => new TextDecoder('utf-8', { fatal: true }),
88    {
89      code: 'ERR_NO_ICU',
90      name: 'TypeError',
91      message: '"fatal" option is not supported on Node.js compiled without ICU'
92    });
93}
94
95// Test TextDecoder, label undefined, options null
96{
97  const dec = new TextDecoder(undefined, null);
98  assert.strictEqual(dec.encoding, 'utf-8');
99  assert.strictEqual(dec.fatal, false);
100  assert.strictEqual(dec.ignoreBOM, false);
101  assert.strictEqual(dec[Symbol.toStringTag], 'TextDecoder');
102}
103
104// Test TextDecoder, UTF-16le
105{
106  const dec = new TextDecoder('utf-16le');
107  const res = dec.decode(Buffer.from('test€', 'utf-16le'));
108  assert.strictEqual(res, 'test€');
109}
110
111// Test TextDecoder, UTF-16be
112if (common.hasIntl) {
113  const dec = new TextDecoder('utf-16be');
114  const res = dec.decode(Buffer.from('test€', 'utf-16le').swap16());
115  assert.strictEqual(res, 'test€');
116}
117
118// Test TextDecoder inspect with hidden fields
119{
120  const dec = new TextDecoder('utf-8', { ignoreBOM: true });
121  if (common.hasIntl) {
122    assert.strictEqual(
123      util.inspect(dec, { showHidden: true }),
124      'TextDecoder {\n' +
125      '  encoding: \'utf-8\',\n' +
126      '  fatal: false,\n' +
127      '  ignoreBOM: true,\n' +
128      '  [Symbol(flags)]: 4,\n' +
129      '  [Symbol(handle)]: undefined\n' +
130      '}'
131    );
132  } else {
133    assert.strictEqual(
134      util.inspect(dec, { showHidden: true }),
135      'TextDecoder {\n' +
136      "  encoding: 'utf-8',\n" +
137      '  fatal: false,\n' +
138      '  ignoreBOM: true,\n' +
139      '  [Symbol(flags)]: 4,\n' +
140      '  [Symbol(handle)]: StringDecoder {\n' +
141      "    encoding: 'utf8',\n" +
142      '    [Symbol(kNativeDecoder)]: <Buffer 00 00 00 00 00 00 01>\n' +
143      '  }\n' +
144      '}'
145    );
146  }
147}
148
149
150// Test TextDecoder inspect without hidden fields
151{
152  const dec = new TextDecoder('utf-8', { ignoreBOM: true });
153  assert.strictEqual(
154    util.inspect(dec, { showHidden: false }),
155    'TextDecoder { encoding: \'utf-8\', fatal: false, ignoreBOM: true }'
156  );
157}
158
159// Test TextDecoder inspect with negative depth
160{
161  const dec = new TextDecoder();
162  assert.strictEqual(util.inspect(dec, { depth: -1 }), '[TextDecoder]');
163}
164
165{
166  const inspectFn = TextDecoder.prototype[inspect];
167  const decodeFn = TextDecoder.prototype.decode;
168  const {
169    encoding: { get: encodingGetter },
170    fatal: { get: fatalGetter },
171    ignoreBOM: { get: ignoreBOMGetter },
172  } = Object.getOwnPropertyDescriptors(TextDecoder.prototype);
173
174  const instance = new TextDecoder();
175
176  const expectedError = {
177    code: 'ERR_INVALID_THIS',
178    name: 'TypeError',
179    message: 'Value of "this" must be of type TextDecoder'
180  };
181
182  inspectFn.call(instance, Infinity, {});
183  decodeFn.call(instance);
184  encodingGetter.call(instance);
185  fatalGetter.call(instance);
186  ignoreBOMGetter.call(instance);
187
188  const invalidThisArgs = [{}, [], true, 1, '', new TextEncoder()];
189  invalidThisArgs.forEach((i) => {
190    assert.throws(() => inspectFn.call(i, Infinity, {}), expectedError);
191    assert.throws(() => decodeFn.call(i), expectedError);
192    assert.throws(() => encodingGetter.call(i), expectedError);
193    assert.throws(() => fatalGetter.call(i), expectedError);
194    assert.throws(() => ignoreBOMGetter.call(i), expectedError);
195  });
196}
197
198{
199  assert.throws(
200    () => new TextDecoder('utf-8', 1),
201    {
202      code: 'ERR_INVALID_ARG_TYPE',
203      name: 'TypeError'
204    }
205  );
206}
207
208// Test TextDecoder for incomplete UTF-8 byte sequence.
209{
210  const decoder = new TextDecoder();
211  const chunk = new Uint8Array([0x66, 0x6f, 0x6f, 0xed]);
212  const str = decoder.decode(chunk);
213  assert.strictEqual(str, 'foo\ufffd');
214}
215
216if (common.hasIntl) {
217  try {
218    const decoder = new TextDecoder('Shift_JIS');
219    const chunk = new Uint8Array([-1]);
220    const str = decoder.decode(chunk);
221    assert.strictEqual(str, '\ufffd');
222  } catch (e) {
223    // Encoding may not be available, e.g. small-icu builds
224    assert.strictEqual(e.code, 'ERR_ENCODING_NOT_SUPPORTED');
225  }
226}
227
228{
229  const buffer = new ArrayBuffer(1);
230  new MessageChannel().port1.postMessage(buffer, [buffer]); // buffer is detached
231  const decoder = new TextDecoder();
232  assert.strictEqual(decoder.decode(buffer), '');
233}
234