summaryrefslogtreecommitdiffstats
path: root/dom/bindings/test/test_async_iterable.html
blob: 8f1f04aea79b74d124f175bc6093b8f1e27c0b49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
  <head>
    <title>Test Async Iterable Interface</title>
    <script src="/tests/SimpleTest/SimpleTest.js"></script>
    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
  </head>
  <body>
    <script class="testbody" type="application/javascript">

add_task(async function init() {
  await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]});
});

const singleValues = Array(10).fill(0).map((_, i) => i * 9 % 7);

async function check_single_result_values(values, multiplier = 1) {
  is(values.length, 10, `AsyncIterableSingle: should return 10 elements`);
  for (let i = 0; i < 10; i++) {
    let expected = singleValues[i] * multiplier;
    is(values[i], expected,
      `AsyncIterableSingle: should be ${expected}, get ${values[i]}`);
  }
}

async function check_single_result(itr, multiplier = 1) {
  let values = [];
  for await (let v of itr) {
    values.push(v);
  }
  check_single_result_values(values, multiplier);
}

async function test_data_single() {
  info(`AsyncIterableSingle: Testing simple iterable creation and functionality`);

  // eslint-disable-next-line no-undef
  let itr = new TestInterfaceAsyncIterableSingle({ failToInit: true });
  let initFailed = false;
  try {
    itr.values();
  } catch (e) {
    initFailed = true;
  }
  ok(initFailed,
     "AsyncIterableSingle: A failure in asynchronous iterator initialization " +
     "steps should propagate to the caller of the asynchronous iterator's " +
     "constructor.");

  // eslint-disable-next-line no-undef
  itr = new TestInterfaceAsyncIterableSingle();
  is(itr.values, itr[Symbol.asyncIterator],
    `AsyncIterableSingle: Should be using @@asyncIterator for 'values'`);

  await check_single_result(itr);
  await check_single_result(itr.values());

  // eslint-disable-next-line no-undef
  itr = new TestInterfaceAsyncIterableSingleWithArgs();
  is(itr.values, itr[Symbol.asyncIterator],
    `AsyncIterableSingleWithArgs: Should be using @@asyncIterator for 'values'`);

  await check_single_result(itr, 1);
  await check_single_result(itr.values({ multiplier: 2 }), 2);

  // eslint-disable-next-line no-undef
  itr = new TestInterfaceAsyncIterableSingle();
  let itrValues = itr.values();
  let values = [];
  for (let i = 0; i < 10; ++i) {
    values.push(itrValues.next());
  }
  check_single_result_values(await Promise.all(values).then(v => v.map(w => w.value)));

  // Test that there is only one ongoing promise at a time.
  // Async iterables return a promise that is then resolved with the iterator
  // value. We create an array of unresolved promises here, one promise for
  // every result that we expect from the iterator. We pass that array of
  // promises to the .value() method of the
  // TestInterfaceAsyncIterableSingleWithArgs, and it will chain the resolving
  // of each resulting iterator value on the corresponding promise from this
  // array. We then resolve the promises in the array one by one in reverse
  // order. This tries to make sure that the iterator always resolves the
  // promises in the order of iteration.
  let unblockers = [];
  let blockingPromises = [];
  for (let i = 0; i < 10; ++i) {
    let unblocker;
    let promise = new Promise((resolve, reject) => {
      unblocker = resolve;
    });
    unblockers.push(unblocker);
    blockingPromises.push(promise);
  }

  // eslint-disable-next-line no-undef
  itr = new TestInterfaceAsyncIterableSingleWithArgs();
  itrValues = itr.values({ blockingPromises });
  values = [];
  for (let i = 0; i < 10; ++i) {
    values.push(itrValues.next());
  }
  unblockers.reverse();
  for (let unblocker of unblockers) {
    unblocker();
  }

  check_single_result_values(await Promise.all(values).then(v => v.map(w => w.value)));

  // eslint-disable-next-line no-undef
  itr = new TestInterfaceAsyncIterableSingleWithArgs();

  let callCount = itr.returnCallCount;

  let i = 0;
  for await (let v of itr) {
    if (++i > 1) {
      break;
    }
    values.push(v);
  }

  is(itr.returnCallCount, callCount + 1,
     `AsyncIterableSingle: breaking out of for-await-of loop should call "return"`);
  is(itr.returnLastCalledWith, undefined,
     `AsyncIterableSingleWithArgs: the asynchronous iterator return algorithm should be called with the argument that was passed in.`);

  // eslint-disable-next-line no-undef
  itr = new TestInterfaceAsyncIterableSingleWithArgs();

  async function * yieldFromIterator () {
    yield * itr
  }

  let yieldingIterator = yieldFromIterator();

  let result = await yieldingIterator.next();
  is(result.value, singleValues[0],
     `AsyncIterableSingle: should be ${singleValues[0]}, get ${result.value}`);
  result = await yieldingIterator.next();
  is(result.value, singleValues[1],
     `AsyncIterableSingle: should be ${singleValues[1]}, get ${result.value}`);

  result = await yieldingIterator.return("abcd");
  is(typeof result, "object",
     `AsyncIterableSingleWithArgs: "return("abcd")" should return { done: true, value: "abcd" }`);
  is(result.done, true,
     `AsyncIterableSingleWithArgs: "return("abcd")" should return { done: true, value: "abcd" }`);
  is(result.value, "abcd",
     `AsyncIterableSingleWithArgs: "return("abcd")" should return { done: true, value: "abcd" }`);
  is(itr.returnLastCalledWith, "abcd",
     `AsyncIterableSingleWithArgs: the asynchronous iterator return algorithm should be called with the argument that was passed in.`);

  result = await yieldingIterator.return("efgh");
  is(typeof result, "object",
     `AsyncIterableSingleWithArgs: "return("efgh")" should return { done: true, value: "efgh" }`);
  is(result.done, true,
     `AsyncIterableSingleWithArgs: "return("efgh")" should return { done: true, value: "efgh" }`);
  is(result.value, "efgh",
     `AsyncIterableSingleWithArgs: "return("efgh")" should return { done: true, value: "efgh" }`);
  is(itr.returnLastCalledWith, "abcd",
     `AsyncIterableSingleWithArgs: the asynchronous iterator return algorithm shouldn't be called if the iterator's 'is finished' flag is true already.`);

  // eslint-disable-next-line no-undef
  itr = new TestInterfaceAsyncIterableSingleWithArgs();
  itrValues = itr.values({ failNextAfter: 1 });
  await itrValues.next().then(({ value, done }) => {
    is(value, singleValues[0], "First value is correct");
    ok(!done, "Expecting more values");
    return itrValues.next();
  }).then(() => {
    ok(false, "Second call to next() should convert failure to a rejected promise.");
    return itrValues.next();
  }).catch(() => {
    ok(true, "Second call to next() should convert failure to a rejected promise.");
    return itrValues.next();
  }).then(({ done }) => {
    ok(done, "An earlier failure in next() should set the async iterator's 'is finished' flag to true.");
  }).catch(() => {
    ok(false, "An earlier failure in next() shouldn't cause subsequent calls to return a rejected promise.");
  });

  // eslint-disable-next-line no-undef
  itr = new TestInterfaceAsyncIterableSingleWithArgs();
  itrValues = itr.values({ throwFromNext: true });
  await itrValues.next().then(() => {
    ok(false, "Should have rejected from the exception");
  }).catch(() => {
    ok(true, "Should have rejected from the exception");
  });

  // eslint-disable-next-line no-undef
  itr = new TestInterfaceAsyncIterableSingleWithArgs();
  itrValues = itr.values({ throwFromReturn: () => { throw new DOMException("Throw from return", "InvalidStateError"); } });
  await itrValues.return().then(() => {
    ok(false, "Should have rejected from the exception");
  }).catch(() => {
    ok(true, "Should have rejected from the exception");
  });
}

async function test_data_double() {
  info(`AsyncIterableDouble: Testing simple iterable creation and functionality`);

  // eslint-disable-next-line no-undef
  let itr = new TestInterfaceAsyncIterableDouble();
  is(itr.entries, itr[Symbol.asyncIterator],
    `AsyncIterableDouble: Should be using @@asyncIterator for 'entries'`);

  let elements = [["a", "b"], ["c", "d"], ["e", "f"]];
  let key_itr = itr.keys();
  let value_itr = itr.values();
  let entries_itr = itr.entries();
  let key = await key_itr.next();
  let value = await value_itr.next();
  let entry = await entries_itr.next();
  for (let i = 0; i < 3; ++i) {
    is(key.value, elements[i][0], `AsyncIterableDouble: Key.value should be ${elements[i][0]}, got ${key.value}`);
    is(key.done, false, `AsyncIterableDouble: Key.done should be false, got ${key.done}`);
    is(value.value, elements[i][1], `AsyncIterableDouble: Value.value should be ${elements[i][1]}, got ${value.value}`);
    is(value.done, false, `AsyncIterableDouble: Value.done should be false, got ${value.done}`);
    is(entry.value[0], elements[i][0], `AsyncIterableDouble: Entry.value[0] should be ${elements[i][0]}, got ${entry.value[0]}`);
    is(entry.value[1], elements[i][1], `AsyncIterableDouble: Entry.value[1] should be ${elements[i][1]}, got ${entry.value[1]}`);
    is(entry.done, false, `AsyncIterableDouble: Entry.done should be false, got ${entry.done}`);

    key = await key_itr.next();
    value = await value_itr.next();
    entry = await entries_itr.next();
  }
  is(key.value, undefined, `AsyncIterableDouble: Key.value should be ${undefined}, got ${key.value}`);
  is(key.done, true, `AsyncIterableDouble: Key.done should be true, got ${key.done}`);
  is(value.value, undefined, `AsyncIterableDouble: Value.value should be ${undefined}, got ${value.value}`);
  is(value.done, true, `AsyncIterableDouble: Value.done should be true, got ${value.done}`);
  is(entry.value, undefined, `AsyncIterableDouble: Entry.value should be ${undefined}, got ${entry.value}`);
  is(entry.done, true, `AsyncIterableDouble: Entry.done should be true, got ${entry.done}`);

  let idx = 0;
  for await (let [itrkey, itrvalue] of itr) {
    is(itrkey, elements[idx][0], `AsyncIterableDouble: Looping at ${idx} should have key ${elements[idx][0]}, got ${key}`);
    is(itrvalue, elements[idx][1], `AsyncIterableDouble: Looping at ${idx} should have value ${elements[idx][1]}, got ${value}`);
    ++idx;
  }
  is(idx, 3, `AsyncIterableDouble: Should have 3 loops of for-await-of, got ${idx}`);
}

async function test_data_double_union() {
  info(`AsyncIterableDoubleUnion: Testing simple iterable creation and functionality`);

  // eslint-disable-next-line no-undef
  let itr = new TestInterfaceAsyncIterableDoubleUnion();
  is(itr.entries, itr[Symbol.asyncIterator],
    `AsyncIterableDoubleUnion: Should be using @@asyncIterator for 'entries'`);

  let elements = [["long", 1], ["string", "a"]];
  let key_itr = itr.keys();
  let value_itr = itr.values();
  let entries_itr = itr.entries();
  let key = await key_itr.next();
  let value = await value_itr.next();
  let entry = await entries_itr.next();
  for (let i = 0; i < 2; ++i) {
    is(key.value, elements[i][0], `AsyncIterableDoubleUnion: Key.value should be ${elements[i][0]}, got ${key.value}`);
    is(key.done, false, `AsyncIterableDoubleUnion: Key.done should be false, got ${key.done}`);
    is(value.value, elements[i][1], `AsyncIterableDoubleUnion: Value.value should be ${elements[i][1]}, got ${value.value}`);
    is(value.done, false, `AsyncIterableDoubleUnion: Value.done should be false, got ${value.done}`);
    is(entry.value[0], elements[i][0], `AsyncIterableDoubleUnion: Entry.value[0] should be ${elements[i][0]}, got ${entry.value[0]}`);
    is(entry.value[1], elements[i][1], `AsyncIterableDoubleUnion: Entry.value[1] should be ${elements[i][1]}, got ${entry.value[1]}`);
    is(entry.done, false, `AsyncIterableDoubleUnion: Entry.done should be false, got ${entry.done}`);

    key = await key_itr.next();
    value = await value_itr.next();
    entry = await entries_itr.next();
  }
  is(key.value, undefined, `AsyncIterableDoubleUnion: Key.value should be ${undefined}, got ${key.value}`);
  is(key.done, true, `AsyncIterableDoubleUnion: Key.done should be true, got ${key.done}`);
  is(value.value, undefined, `AsyncIterableDoubleUnion: Value.value should be ${undefined}, got ${value.value}`);
  is(value.done, true, `AsyncIterableDoubleUnion: Value.done should be true, got ${value.done}`);
  is(entry.value, undefined, `AsyncIterableDoubleUnion: Entry.value should be ${undefined}, got ${entry.value}`);
  is(entry.done, true, `AsyncIterableDoubleUnion: Entry.done should be true, got ${entry.done}`);

  let idx = 0;
  for await (let [itrkey, itrvalue] of itr) {
    is(itrkey, elements[idx][0], `AsyncIterableDoubleUnion: Looping at ${idx} should have key ${elements[idx][0]}, got ${key}`);
    is(itrvalue, elements[idx][1], `AsyncIterableDoubleUnion: Looping at ${idx} should have value ${elements[idx][1]}, got ${value}`);
    ++idx;
  }
  is(idx, 2, `AsyncIterableDoubleUnion: Should have 2 loops of for-await-of, got ${idx}`);
}

add_task(async function do_tests() {
  await test_data_single();
  await test_data_double();
  await test_data_double_union();
});

    </script>
  </body>
</html>