Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOM: Add more Observable iterable tests #50338

Merged
merged 1 commit into from
Jan 28, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 128 additions & 58 deletions dom/observable/tentative/observable-from.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -1437,90 +1437,126 @@ promise_test(async t => {
test(() => {
const results = [];
const iterable = {
impl() {
return {
next() {
results.push('next() running');
return {done: true};
}
getter() {
results.push('GETTER called');
return () => {
results.push('Obtaining iterator');
return {
next() {
results.push('next() running');
return {done: true};
}
};
};
}
};

iterable[Symbol.iterator] = iterable.impl;
Object.defineProperty(iterable, Symbol.iterator, {
get: iterable.getter
});
{
const source = Observable.from(iterable);
assert_array_equals(results, ["GETTER called"]);
source.subscribe({}, {signal: AbortSignal.abort()});
assert_array_equals(results, []);
assert_array_equals(results, ["GETTER called"]);
}
iterable[Symbol.iterator] = undefined;
iterable[Symbol.asyncIterator] = iterable.impl;
Object.defineProperty(iterable, Symbol.asyncIterator, {
get: iterable.getter
});
{
const source = Observable.from(iterable);
assert_array_equals(results, ["GETTER called", "GETTER called"]);
source.subscribe({}, {signal: AbortSignal.abort()});
assert_array_equals(results, []);
assert_array_equals(results, ["GETTER called", "GETTER called"]);
}
}, "from(): Subscribing to an iterable Observable with an aborted signal " +
"does not call next()");

test(() => {
const results = [];
const ac = new AbortController();
let results = [];

const iterable = {
[Symbol.iterator]() {
ac.abort();
return {
val: 0,
next() {
results.push('next() called');
return {done: true};
},
return() {
results.push('return() called');
}
controller: null,
calledOnce: false,
getter() {
results.push('GETTER called');
if (!this.calledOnce) {
this.calledOnce = true;
return () => {
results.push('NOT CALLED');
// We don't need to return anything here. The only time this path is
// hit is during `Observable.from()` which doesn't actually obtain an
// iterator. It just samples the iterable protocol property to ensure
// that it's valid.
};
}

// This path is only called the second time the iterator protocol getter
// is run.
this.controller.abort();
return () => {
results.push('iterator obtained');
return {
val: 0,
next() {
results.push('next() called');
return {done: true};
},
return() {
results.push('return() called');
}
};
};
}
};
};

const source = Observable.from(iterable);
source.subscribe({
next: v => results.push(v),
complete: () => results.push('complete'),
}, {signal: ac.signal});
// Test for sync iterators.
{
const ac = new AbortController();
iterable.controller = ac;
Object.defineProperty(iterable, Symbol.iterator, {
get: iterable.getter,
});

assert_array_equals(results, []);
}, "from(): When iterable conversion aborts the subscription, next() is " +
"never called");
test(() => {
const results = [];
const ac = new AbortController();
const source = Observable.from(iterable);
assert_false(ac.signal.aborted, "[Sync iterator]: signal is not yet aborted after from() conversion");
assert_array_equals(results, ["GETTER called"]);

const iterable = {
[Symbol.asyncIterator]() {
ac.abort();
return {
val: 0,
next() {
results.push('next() called');
return {done: true};
},
return() {
results.push('return() called');
}
};
}
};
source.subscribe({
next: n => results.push(n),
complete: () => results.push('complete'),
}, {signal: ac.signal});
assert_true(ac.signal.aborted, "[Sync iterator]: signal is aborted during subscription");
assert_array_equals(results, ["GETTER called", "GETTER called", "iterator obtained"]);
}

const source = Observable.from(iterable);
source.subscribe({
next: v => results.push(v),
complete: () => results.push('complete'),
}, {signal: ac.signal});
results = [];

assert_array_equals(results, []);
}, "from(): When async iterable conversion aborts the subscription, next() " +
"is never called");
// Test for async iterators.
{
// Reset `iterable` so it can be reused.
const ac = new AbortController();
iterable.controller = ac;
iterable.calledOnce = false;
iterable[Symbol.iterator] = undefined;
Object.defineProperty(iterable, Symbol.asyncIterator, {
get: iterable.getter
});

const source = Observable.from(iterable);
assert_false(ac.signal.aborted, "[Async iterator]: signal is not yet aborted after from() conversion");
assert_array_equals(results, ["GETTER called"]);

source.subscribe({
next: n => results.push(n),
complete: () => results.push('complete'),
}, {signal: ac.signal});
assert_true(ac.signal.aborted, "[Async iterator]: signal is aborted during subscription");
assert_array_equals(results, ["GETTER called", "GETTER called", "iterator obtained"]);
}
}, "from(): When iterable conversion aborts the subscription, next() is " +
"never called");

// This test asserts some very subtle behavior with regard to async iterables
// and a mid-subscription signal abort. Specifically it detects that a signal
Expand Down Expand Up @@ -1659,3 +1695,37 @@ test(() => {
assert_not_equals(reportedError, null, "Protocol error is reported to the global");
assert_true(reportedError instanceof TypeError);
}, "Invalid async iterator protocol error is surfaced before Subscriber#signal is consulted");

test(() => {
const controller = new AbortController();
const iterable = {
calledOnce: false,
get[Symbol.iterator]() {
if (this.calledOnce) {
controller.abort();
return null;
} else {
this.calledOnce = true;
return this.validImplementation;
}
},
validImplementation() {
controller.abort();
return null;
}
};

let reportedError = null;
self.addEventListener("error", e => reportedError = e.error, {once: true});

let errorThrown = null;
const observable = Observable.from(iterable);
observable.subscribe({
error: e => errorThrown = e,
}, {signal: controller.signal});

assert_equals(errorThrown, null, "Protocol error is not surfaced to the Subscriber");

assert_not_equals(reportedError, null, "Protocol error is reported to the global");
assert_true(reportedError instanceof TypeError);
}, "Invalid iterator protocol error is surfaced before Subscriber#signal is consulted");