From 516510c2b2785cd1f2fa7e290d7a957386e0ef5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo=20Kondratiuk?= Date: Mon, 27 Jul 2026 15:30:58 -0300 Subject: [PATCH 1/3] Add AssumeNeverEmits, RaceWithSignalAndTimer, and FromEventBuffered Three additions motivated by puppeteer-sharp's WaitForTargetAsync/WaitForFrameAsync, promoting patterns that already existed as private, duplicated workarounds: - AssumeNeverEmits: promotes the NeverReached helper RetryAndRaceWithSignalAndTimer already had privately (widening an error-only Observable to any T, since C# has no bottom type for RaceWith to accept the way TypeScript's Observable does). RetryAndRaceWithSignalAndTimer now uses the public version instead of its own copy. - RaceWithSignalAndTimer: the non-retrying half of RetryAndRaceWithSignalAndTimer, for a single wait (e.g. "wait for the next matching event") that doesn't need retrying. RetryAndRaceWithSignalAndTimer is now built on top of it (Retry().RaceWithSignalAndTimer()). - FromEventBuffered: an eagerly-attaching, buffered variant of FromEvent. Ordinary FromEvent is cold - nothing attaches to the underlying event until Subscribe is called. That's fine in upstream rxjs, where single-threaded run-to-completion semantics mean nothing can fire between attaching a handler and checking some existing state for an already-matching item. .NET has no such guarantee (event delivery can run on another thread), so that gap is a real, provable race - FromEventBuffered attaches immediately and buffers into a ReplaySubject so nothing fired in that gap is lost. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01K92eapPm8e7mX7puT4cF4s --- src/RxSharp/Extras/AssumeNeverEmits.cs | 31 +++++ src/RxSharp/Extras/FromEventBuffered.cs | 96 +++++++++++++++ src/RxSharp/Extras/RaceWithSignalAndTimer.cs | 47 +++++++ .../Extras/RetryAndRaceWithSignalAndTimer.cs | 9 +- src/RxSharp/RxSharp.csproj | 6 +- .../Extras/AssumeNeverEmitsExtrasTests.cs | 64 ++++++++++ .../Extras/FromEventBufferedExtrasTests.cs | 116 ++++++++++++++++++ .../RaceWithSignalAndTimerTests.cs | 111 +++++++++++++++++ 8 files changed, 469 insertions(+), 11 deletions(-) create mode 100644 src/RxSharp/Extras/AssumeNeverEmits.cs create mode 100644 src/RxSharp/Extras/FromEventBuffered.cs create mode 100644 src/RxSharp/Extras/RaceWithSignalAndTimer.cs create mode 100644 test/RxSharp.Tests/Extras/AssumeNeverEmitsExtrasTests.cs create mode 100644 test/RxSharp.Tests/Extras/FromEventBufferedExtrasTests.cs create mode 100644 test/RxSharp.Tests/PuppeteerScenarios/RaceWithSignalAndTimerTests.cs diff --git a/src/RxSharp/Extras/AssumeNeverEmits.cs b/src/RxSharp/Extras/AssumeNeverEmits.cs new file mode 100644 index 0000000..f9812ac --- /dev/null +++ b/src/RxSharp/Extras/AssumeNeverEmits.cs @@ -0,0 +1,31 @@ +using RxSharp.Operators; + +namespace RxSharp.Extras; + +/// Extension methods widening an error-only to another element type. +public static class AssumeNeverEmitsExtras +{ + /// + /// Widens an that only ever calls (such as + /// or ) so it + /// type-checks anywhere an is expected - most commonly as one of the branches + /// passed to alongside a source that actually produces + /// values. Mirrors how rxjs relies on TypeScript structurally accepting + /// Observable<never> anywhere an Observable<T> is expected; C# has no bottom type, + /// so this exists to fill that gap explicitly. + /// + /// + /// This is only safe for a that is guaranteed, by contract, to never call + /// . If it ever does, the returned observable throws + /// from that point on, converting a real value into a crash rather + /// than propagating it - there is no compile-time check for this, unlike TypeScript's never. Do not + /// apply this to a source that might legitimately emit. + /// + /// The element type to widen to. + /// An observable that only ever errors, never emits. + /// An that mirrors 's errors and throws if it ever emits. + public static Observable AssumeNeverEmits(this Observable source) + => source.Map(_ => throw new InvalidOperationException( + "AssumeNeverEmits: the source observable emitted a value, but was assumed to only ever error. " + + "This is a contract violation in the caller, not in AssumeNeverEmits itself.")); +} diff --git a/src/RxSharp/Extras/FromEventBuffered.cs b/src/RxSharp/Extras/FromEventBuffered.cs new file mode 100644 index 0000000..170e21d --- /dev/null +++ b/src/RxSharp/Extras/FromEventBuffered.cs @@ -0,0 +1,96 @@ +using RxSharp.Subjects; + +namespace RxSharp.Extras; + +/// +/// A handle to an eagerly-attached, buffered .NET event source created by +/// . Disposing detaches the underlying event handler. +/// +/// The type of the event's payload. +public sealed class BufferedEventSource : IDisposable +{ + private readonly ReplaySubject _subject; + private readonly Action _detach; + private bool _disposed; + + internal BufferedEventSource(ReplaySubject subject, Action detach) + { + _subject = subject; + _detach = detach; + } + + /// + /// Exposes the buffered event payloads as an . Each subscriber first receives + /// any payloads buffered since this source was created, then live payloads as they arrive - exactly like + /// subscribing to a directly, because that is what this wraps. + /// + /// An observable over this source's buffered and live event payloads. + public Observable AsObservable() => _subject.AsObservable(); + + /// Detaches the underlying event handler and disposes the buffer. Safe to call more than once. + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + _detach(); + _subject.Dispose(); + } +} + +/// Extension methods providing an eagerly-attached, buffered variant of . +public static class FromEventBufferedExtras +{ + /// + /// Like , but attaches the underlying event handler + /// immediately - when this method is called - rather than lazily at subscribe time, buffering up to + /// payloads that arrive before any subscriber attaches and replaying them to + /// the first subscriber(s). + /// + /// + /// Ordinary is cold: nothing is attached to the + /// underlying event until Subscribe is called, matching every other RxSharp source. This is + /// deliberately not that. It exists for the narrow case where a handler must be attached before + /// synchronously checking some existing state that might already satisfy what the caller is waiting for + /// (e.g. checking a collection for an already-matching item) without losing an event that fires in the real + /// gap between attaching the raw handler and actually subscribing to the returned observable. That gap is + /// provably real on .NET, where event delivery can run on another thread - unlike single-threaded JS, where + /// rxjs's own fromEmitterEvent has no equivalent problem, since nothing can fire between two + /// synchronous statements. Most code should use the ordinary, cold + /// instead; reach for this only when that gap is a + /// real, provable race, not by default. + /// + /// The delegate type of the event handler. + /// The type of the event's payload. + /// Called immediately, with the handler to add to the event. + /// Called on , with the same handler, to remove it from the event. + /// Converts an callback into the event's actual delegate shape. + /// The maximum number of most-recent payloads kept for replay. Defaults to 1. + /// A handle exposing the buffered payloads as an observable, and detaching the handler on disposal. + public static BufferedEventSource FromEventBuffered( + Action addHandler, + Action removeHandler, + Func, TDelegate> conversion, + int bufferSize = 1) + { + var subject = new ReplaySubject(bufferSize); + var handler = conversion(subject.OnNext); + addHandler(handler); + return new BufferedEventSource(subject, () => removeHandler(handler)); + } + + /// The common case of for standard -shaped .NET events. + /// The type of the event's payload. + /// Called immediately, with the handler to add to the event. + /// Called on , with the same handler, to remove it from the event. + /// The maximum number of most-recent payloads kept for replay. Defaults to 1. + /// A handle exposing the buffered payloads as an observable, and detaching the handler on disposal. + public static BufferedEventSource FromEventBuffered( + Action> addHandler, + Action> removeHandler, + int bufferSize = 1) + => FromEventBuffered, TEventArgs>(addHandler, removeHandler, onNext => (_, args) => onNext(args), bufferSize); +} diff --git a/src/RxSharp/Extras/RaceWithSignalAndTimer.cs b/src/RxSharp/Extras/RaceWithSignalAndTimer.cs new file mode 100644 index 0000000..6a0c7eb --- /dev/null +++ b/src/RxSharp/Extras/RaceWithSignalAndTimer.cs @@ -0,0 +1,47 @@ +using RxSharp.Operators; + +namespace RxSharp.Extras; + +/// Extension methods racing a single subscription against cancellation and a timeout. +public static class RaceWithSignalAndTimerExtras +{ + /// + /// Races against cancellation and a timeout, whichever fires first. The + /// non-retrying half of + /// - use this directly for a single wait (e.g. "wait for the next matching event") that doesn't need + /// retrying, and reach for the retrying combinator when it does. + /// + /// The type of values produced by . + /// The source sequence to race. + /// The overall duration before giving up with a timeout error. A zero or negative value disables the timeout. + /// + /// Produces the exception used for both the cancellation and timeout branches. Defaults to + /// for cancellation and for the timeout, + /// via the defaults of and respectively. + /// Since one factory covers both branches, a caller needing to tell the two apart by exception type should + /// pass here (so each branch keeps its own distinct default type) and catch/rethrow + /// as needed at the call site. + /// + /// A token that, when cancelled, aborts the wait immediately. + /// An observable that mirrors unless the timeout or cancellation fires first. + public static Observable RaceWithSignalAndTimer( + this Observable source, + TimeSpan timeout, + Func? causeFactory, + CancellationToken cancellationToken) + => source.RaceWith( + CancellationExtras.FromCancellationToken(cancellationToken, causeFactory).AssumeNeverEmits(), + TimeoutExtras.Timeout(timeout, causeFactory).AssumeNeverEmits()); + + /// + /// Overload of + /// using the default cause factory (/). + /// + /// The type of values produced by . + /// The source sequence to race. + /// The overall duration before giving up with a timeout error. + /// A token that, when cancelled, aborts the wait immediately. + /// An observable that mirrors unless the timeout or cancellation fires first. + public static Observable RaceWithSignalAndTimer(this Observable source, TimeSpan timeout, CancellationToken cancellationToken) + => source.RaceWithSignalAndTimer(timeout, causeFactory: null, cancellationToken); +} diff --git a/src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs b/src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs index f90648b..6b7f9c5 100644 --- a/src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs +++ b/src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs @@ -36,9 +36,7 @@ public static Observable RetryAndRaceWithSignalAndTimer( CancellationToken cancellationToken) => source .Retry(delay: retryDelay ?? TimeSpan.FromMilliseconds(50)) - .RaceWith( - CancellationExtras.FromCancellationToken(cancellationToken, causeFactory).Map(NeverReached), - TimeoutExtras.Timeout(timeout, causeFactory).Map(NeverReached)); + .RaceWithSignalAndTimer(timeout, causeFactory, cancellationToken); /// /// Overload of @@ -51,9 +49,4 @@ public static Observable RetryAndRaceWithSignalAndTimer( /// An observable that retries until it succeeds, times out, or is cancelled. public static Observable RetryAndRaceWithSignalAndTimer(this Observable source, TimeSpan timeout, CancellationToken cancellationToken) => source.RetryAndRaceWithSignalAndTimer(timeout, causeFactory: null, retryDelay: null, cancellationToken); - - // FromCancellationToken/Timeout only ever call OnError, never OnNext — this projection exists purely so - // the two Observable notifiers type-check inside RaceWith(Observable), matching how rxjs relies - // on TypeScript accepting Observable anywhere an Observable is expected. C# has no bottom type. - private static TResult NeverReached(Unit value) => throw new InvalidOperationException("unreachable: this observable only ever errors"); } diff --git a/src/RxSharp/RxSharp.csproj b/src/RxSharp/RxSharp.csproj index 0aad034..21e79bc 100644 --- a/src/RxSharp/RxSharp.csproj +++ b/src/RxSharp/RxSharp.csproj @@ -16,9 +16,9 @@ https://github.com/hardkoded/ReactiveExtensions-Sharp https://github.com/hardkoded/ReactiveExtensions-Sharp README.md - 0.1.1 - 0.1.1.0 - 0.1.1.0 + 0.1.2 + 0.1.2.0 + 0.1.2.0 diff --git a/test/RxSharp.Tests/Extras/AssumeNeverEmitsExtrasTests.cs b/test/RxSharp.Tests/Extras/AssumeNeverEmitsExtrasTests.cs new file mode 100644 index 0000000..d84c309 --- /dev/null +++ b/test/RxSharp.Tests/Extras/AssumeNeverEmitsExtrasTests.cs @@ -0,0 +1,64 @@ +using RxSharp.Extras; + +namespace RxSharp.Tests.Extras; + +[TestFixture] +public class AssumeNeverEmitsExtrasTests +{ + [Test] + public void ShouldPropagateTheErrorFromAnErrorOnlySource() + { + var error = new InvalidOperationException("boom"); + using var signal = new ManualResetEventSlim(); + Exception? received = null; + + Observable.ThrowError(() => error) + .AssumeNeverEmits() + .Subscribe(onError: err => + { + received = err; + signal.Set(); + }); + + Assert.That(signal.Wait(TimeSpan.FromSeconds(2)), Is.True); + Assert.That(received, Is.SameAs(error)); + } + + [Test] + public void ShouldCompleteWithoutEmittingIfTheSourceCompletesWithoutErroring() + { + using var signal = new ManualResetEventSlim(); + var completed = false; + var received = new List(); + + Observable.Empty() + .AssumeNeverEmits() + .Subscribe(received.Add, onComplete: () => + { + completed = true; + signal.Set(); + }); + + Assert.That(signal.Wait(TimeSpan.FromSeconds(2)), Is.True); + Assert.That(completed, Is.True); + Assert.That(received, Is.Empty); + } + + [Test] + public void ShouldThrowIfTheSourceViolatesTheNeverEmitsContractByEmittingAValue() + { + using var signal = new ManualResetEventSlim(); + Exception? received = null; + + Observable.Of(Unit.Default) + .AssumeNeverEmits() + .Subscribe(onError: err => + { + received = err; + signal.Set(); + }); + + Assert.That(signal.Wait(TimeSpan.FromSeconds(2)), Is.True); + Assert.That(received, Is.InstanceOf()); + } +} diff --git a/test/RxSharp.Tests/Extras/FromEventBufferedExtrasTests.cs b/test/RxSharp.Tests/Extras/FromEventBufferedExtrasTests.cs new file mode 100644 index 0000000..7868bd5 --- /dev/null +++ b/test/RxSharp.Tests/Extras/FromEventBufferedExtrasTests.cs @@ -0,0 +1,116 @@ +using RxSharp.Extras; + +namespace RxSharp.Tests.Extras; + +[TestFixture] +public class FromEventBufferedExtrasTests +{ + private sealed class FakeTargetManager + { + public event EventHandler? TargetCreated; + + public void RaiseTargetCreated(string targetId) => TargetCreated?.Invoke(this, targetId); + } + + [Test] + public void ShouldAttachTheHandlerImmediatelyRatherThanAtSubscribeTime() + { + var manager = new FakeTargetManager(); + + using var source = FromEventBufferedExtras.FromEventBuffered( + h => manager.TargetCreated += h, + h => manager.TargetCreated -= h); + + // Raised before AsObservable() is ever subscribed to - this is the exact gap FromEventBuffered exists + // to close (a plain, cold FromEvent would lose this). + manager.RaiseTargetCreated("early-target"); + + var received = new List(); + source.AsObservable().Subscribe(received.Add); + + Assert.That(received, Is.EqualTo(new[] { "early-target" })); + } + + [Test] + public void ShouldReplayBufferedValuesToALateSubscriberThenDeliverLiveValues() + { + var manager = new FakeTargetManager(); + using var source = FromEventBufferedExtras.FromEventBuffered( + h => manager.TargetCreated += h, + h => manager.TargetCreated -= h); + + manager.RaiseTargetCreated("buffered-target"); + + var received = new List(); + source.AsObservable().Subscribe(received.Add); + manager.RaiseTargetCreated("live-target"); + + Assert.That(received, Is.EqualTo(new[] { "buffered-target", "live-target" })); + } + + [Test] + public void ShouldDeliverToEveryIndependentSubscriber() + { + var manager = new FakeTargetManager(); + using var source = FromEventBufferedExtras.FromEventBuffered( + h => manager.TargetCreated += h, + h => manager.TargetCreated -= h); + + var first = new List(); + var second = new List(); + source.AsObservable().Subscribe(first.Add); + source.AsObservable().Subscribe(second.Add); + + manager.RaiseTargetCreated("shared-target"); + + Assert.That(first, Is.EqualTo(new[] { "shared-target" })); + Assert.That(second, Is.EqualTo(new[] { "shared-target" })); + } + + [Test] + public void ShouldOnlyReplayUpToTheRequestedBufferSize() + { + var manager = new FakeTargetManager(); + using var source = FromEventBufferedExtras.FromEventBuffered( + h => manager.TargetCreated += h, + h => manager.TargetCreated -= h, + bufferSize: 2); + + manager.RaiseTargetCreated("one"); + manager.RaiseTargetCreated("two"); + manager.RaiseTargetCreated("three"); + + var received = new List(); + source.AsObservable().Subscribe(received.Add); + + Assert.That(received, Is.EqualTo(new[] { "two", "three" })); + } + + [Test] + public void ShouldDetachTheHandlerOnDispose() + { + var manager = new FakeTargetManager(); + var source = FromEventBufferedExtras.FromEventBuffered( + h => manager.TargetCreated += h, + h => manager.TargetCreated -= h); + + source.Dispose(); + manager.RaiseTargetCreated("after-dispose"); + + // If the handler weren't detached, this would throw ObjectDisposedException from the underlying + // subject instead of silently doing nothing. + Assert.DoesNotThrow(() => manager.RaiseTargetCreated("another-after-dispose")); + } + + [Test] + public void ShouldBeSafeToDisposeMoreThanOnce() + { + var manager = new FakeTargetManager(); + var source = FromEventBufferedExtras.FromEventBuffered( + h => manager.TargetCreated += h, + h => manager.TargetCreated -= h); + + source.Dispose(); + Assert.DoesNotThrow(() => source.Dispose()); + } +} diff --git a/test/RxSharp.Tests/PuppeteerScenarios/RaceWithSignalAndTimerTests.cs b/test/RxSharp.Tests/PuppeteerScenarios/RaceWithSignalAndTimerTests.cs new file mode 100644 index 0000000..1f0e42f --- /dev/null +++ b/test/RxSharp.Tests/PuppeteerScenarios/RaceWithSignalAndTimerTests.cs @@ -0,0 +1,111 @@ +using RxSharp.Extras; + +namespace RxSharp.Tests.PuppeteerScenarios; + +/// +/// Modeled on how Puppeteer's Browser.waitForTarget/Page.waitForRequest-shaped methods race a +/// single wait against cancellation and a timeout, without retrying: pipe(filterAsync(predicate), +/// raceWith(fromAbortSignal(...), timeout(...))). Like RetryAndRaceWithSignalAndTimer, no upstream +/// rxjs spec equivalent exists for this combinator. +/// +[TestFixture] +public class RaceWithSignalAndTimerTests +{ + [Test] + public void ShouldEmitTheSourceValueIfItArrivesBeforeTimeoutOrCancellation() + { + using var signal = new ManualResetEventSlim(); + var results = new List(); + var completed = false; + + Observable.Of("target-created") + .RaceWithSignalAndTimer(TimeSpan.FromSeconds(5), CancellationToken.None) + .Subscribe(results.Add, onComplete: () => + { + completed = true; + signal.Set(); + }); + + Assert.That(signal.Wait(TimeSpan.FromSeconds(2)), Is.True); + Assert.That(results, Is.EqualTo(new[] { "target-created" })); + Assert.That(completed, Is.True); + } + + [Test] + public void ShouldFailWithATimeoutErrorIfTheSourceNeverEmits() + { + using var signal = new ManualResetEventSlim(); + Exception? received = null; + + Observable.Never() + .RaceWithSignalAndTimer(TimeSpan.FromMilliseconds(30), CancellationToken.None) + .Subscribe(onError: err => + { + received = err; + signal.Set(); + }); + + Assert.That(signal.Wait(TimeSpan.FromSeconds(2)), Is.True); + Assert.That(received, Is.InstanceOf()); + } + + [Test] + public void ShouldFailWithCancellationIfTheCallerAborts() + { + using var cts = new CancellationTokenSource(); + using var signal = new ManualResetEventSlim(); + Exception? received = null; + + Observable.Never() + .RaceWithSignalAndTimer(TimeSpan.FromSeconds(5), cts.Token) + .Subscribe(onError: err => + { + received = err; + signal.Set(); + }); + + cts.Cancel(); + + Assert.That(signal.Wait(TimeSpan.FromSeconds(2)), Is.True); + Assert.That(received, Is.InstanceOf()); + } + + [Test] + public void ShouldUseASharedCauseForBothCancellationAndTimeout() + { + using var signal = new ManualResetEventSlim(); + Exception? received = null; + var cause = new TimeoutException("waiting for target timed out"); + + Observable.Never() + .RaceWithSignalAndTimer(TimeSpan.FromMilliseconds(30), () => cause, CancellationToken.None) + .Subscribe(onError: err => + { + received = err; + signal.Set(); + }); + + Assert.That(signal.Wait(TimeSpan.FromSeconds(2)), Is.True); + Assert.That(received, Is.SameAs(cause)); + } + + [Test] + public void ShouldDisableTheTimeoutForAZeroOrNegativeValue() + { + using var signal = new ManualResetEventSlim(); + var results = new List(); + + var subject = new RxSharp.Subjects.Subject(); + subject.AsObservable() + .RaceWithSignalAndTimer(TimeSpan.Zero, CancellationToken.None) + .Subscribe(results.Add, onComplete: signal.Set); + + // Prove the timeout branch is truly disabled, not just long, by outliving what would otherwise fire. + Thread.Sleep(50); + subject.OnNext("late-target"); + subject.OnCompleted(); + + Assert.That(signal.Wait(TimeSpan.FromSeconds(2)), Is.True); + Assert.That(results, Is.EqualTo(new[] { "late-target" })); + } +} From 15bf0d258e815ced28ff319a3f7e9870852497b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo=20Kondratiuk?= Date: Mon, 27 Jul 2026 17:16:06 -0300 Subject: [PATCH 2/3] Consolidate the Extras classes into one PuppeteerExtras type Every Extras method previously lived in its own single-method static class (CancellationExtras, TimeoutExtras, FilterAsyncExtras, ...), each named after its one method - fine for the extension methods (FilterAsync, RaceWithSignalAndTimer, etc.), which already show up via dot-completion on Observable with just `using RxSharp.Extras;`, but a real discoverability tax on the three factory-style methods (FromCancellationToken, Timeout, FromEventBuffered) that have no natural `this Observable` receiver to hang off of: a caller had no way to guess which class held which method. All seven are now one `public static partial class PuppeteerExtras`, split across the same per-concern files as before via `partial`. (Named PuppeteerExtras rather than plain Extras: CA1724 flags a type named the same as its containing namespace, and every file's own doc comments already frame these as puppeteer-sharp's real motivation.) Two colliding private `DefaultCause` helpers (one per branch's default exception factory) got distinct names now that they're members of the same type. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01K92eapPm8e7mX7puT4cF4s --- src/RxSharp/Extras/AssumeNeverEmits.cs | 4 ++-- src/RxSharp/Extras/FilterAsync.cs | 2 +- src/RxSharp/Extras/FromCancellationToken.cs | 6 +++--- src/RxSharp/Extras/FromEventBuffered.cs | 4 ++-- src/RxSharp/Extras/RaceWithSignalAndTimer.cs | 10 +++++----- src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs | 6 +++--- src/RxSharp/Extras/Timeout.cs | 6 +++--- test/RxSharp.Tests/Extras/CancellationExtrasTests.cs | 8 ++++---- .../Extras/FromEventBufferedExtrasTests.cs | 12 ++++++------ test/RxSharp.Tests/Extras/TimeoutExtrasTests.cs | 6 +++--- 10 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/RxSharp/Extras/AssumeNeverEmits.cs b/src/RxSharp/Extras/AssumeNeverEmits.cs index f9812ac..6cb07d0 100644 --- a/src/RxSharp/Extras/AssumeNeverEmits.cs +++ b/src/RxSharp/Extras/AssumeNeverEmits.cs @@ -3,11 +3,11 @@ namespace RxSharp.Extras; /// Extension methods widening an error-only to another element type. -public static class AssumeNeverEmitsExtras +public static partial class PuppeteerExtras { /// /// Widens an that only ever calls (such as - /// or ) so it + /// or ) so it /// type-checks anywhere an is expected - most commonly as one of the branches /// passed to alongside a source that actually produces /// values. Mirrors how rxjs relies on TypeScript structurally accepting diff --git a/src/RxSharp/Extras/FilterAsync.cs b/src/RxSharp/Extras/FilterAsync.cs index 0516192..cdd005d 100644 --- a/src/RxSharp/Extras/FilterAsync.cs +++ b/src/RxSharp/Extras/FilterAsync.cs @@ -3,7 +3,7 @@ namespace RxSharp.Extras; /// Extension methods providing an async-predicate flavor of Filter. -public static class FilterAsyncExtras +public static partial class PuppeteerExtras { /// /// An operator supporting an async predicate, mirroring Puppeteer's own filterAsync helper (implemented, diff --git a/src/RxSharp/Extras/FromCancellationToken.cs b/src/RxSharp/Extras/FromCancellationToken.cs index b424ad6..2858d4a 100644 --- a/src/RxSharp/Extras/FromCancellationToken.cs +++ b/src/RxSharp/Extras/FromCancellationToken.cs @@ -1,7 +1,7 @@ namespace RxSharp.Extras; /// Puppeteer-flavored combinators built on top of the core primitives — the C# analogues of the helpers Puppeteer itself layers on top of rxjs (see CLAUDE.md's "Puppeteer-essential surface"). -public static class CancellationExtras +public static partial class PuppeteerExtras { /// /// An observable that never emits and errors as soon as is cancelled. @@ -18,7 +18,7 @@ public static class CancellationExtras public static Observable FromCancellationToken(CancellationToken cancellationToken, Func? causeFactory = null) => new Observable(subscriber => { - var makeCause = causeFactory ?? DefaultCause; + var makeCause = causeFactory ?? DefaultCancellationCause; if (cancellationToken.IsCancellationRequested) { @@ -30,5 +30,5 @@ public static Observable FromCancellationToken(CancellationToken cancellat return new Subscription(() => registration.Dispose()); }); - private static Exception DefaultCause() => new OperationCanceledException(); + private static Exception DefaultCancellationCause() => new OperationCanceledException(); } diff --git a/src/RxSharp/Extras/FromEventBuffered.cs b/src/RxSharp/Extras/FromEventBuffered.cs index 170e21d..04a45e9 100644 --- a/src/RxSharp/Extras/FromEventBuffered.cs +++ b/src/RxSharp/Extras/FromEventBuffered.cs @@ -4,7 +4,7 @@ namespace RxSharp.Extras; /// /// A handle to an eagerly-attached, buffered .NET event source created by -/// . Disposing detaches the underlying event handler. +/// . Disposing detaches the underlying event handler. /// /// The type of the event's payload. public sealed class BufferedEventSource : IDisposable @@ -42,7 +42,7 @@ public void Dispose() } /// Extension methods providing an eagerly-attached, buffered variant of . -public static class FromEventBufferedExtras +public static partial class PuppeteerExtras { /// /// Like , but attaches the underlying event handler diff --git a/src/RxSharp/Extras/RaceWithSignalAndTimer.cs b/src/RxSharp/Extras/RaceWithSignalAndTimer.cs index 6a0c7eb..5e8c37f 100644 --- a/src/RxSharp/Extras/RaceWithSignalAndTimer.cs +++ b/src/RxSharp/Extras/RaceWithSignalAndTimer.cs @@ -3,11 +3,11 @@ namespace RxSharp.Extras; /// Extension methods racing a single subscription against cancellation and a timeout. -public static class RaceWithSignalAndTimerExtras +public static partial class PuppeteerExtras { /// /// Races against cancellation and a timeout, whichever fires first. The - /// non-retrying half of + /// non-retrying half of /// - use this directly for a single wait (e.g. "wait for the next matching event") that doesn't need /// retrying, and reach for the retrying combinator when it does. /// @@ -17,7 +17,7 @@ public static class RaceWithSignalAndTimerExtras /// /// Produces the exception used for both the cancellation and timeout branches. Defaults to /// for cancellation and for the timeout, - /// via the defaults of and respectively. + /// via the defaults of and respectively. /// Since one factory covers both branches, a caller needing to tell the two apart by exception type should /// pass here (so each branch keeps its own distinct default type) and catch/rethrow /// as needed at the call site. @@ -30,8 +30,8 @@ public static Observable RaceWithSignalAndTimer( Func? causeFactory, CancellationToken cancellationToken) => source.RaceWith( - CancellationExtras.FromCancellationToken(cancellationToken, causeFactory).AssumeNeverEmits(), - TimeoutExtras.Timeout(timeout, causeFactory).AssumeNeverEmits()); + PuppeteerExtras.FromCancellationToken(cancellationToken, causeFactory).AssumeNeverEmits(), + PuppeteerExtras.Timeout(timeout, causeFactory).AssumeNeverEmits()); /// /// Overload of diff --git a/src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs b/src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs index 6b7f9c5..c49e376 100644 --- a/src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs +++ b/src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs @@ -3,7 +3,7 @@ namespace RxSharp.Extras; /// Extension methods composing retry, cancellation, and timeout into the single combinator Puppeteer's Locator actions rely on. -public static class RetryAndRaceWithSignalAndTimerExtras +public static partial class PuppeteerExtras { /// /// The combinator behind Puppeteer's Locator actions (click/fill/hover/wait): retry the source @@ -13,7 +13,7 @@ public static class RetryAndRaceWithSignalAndTimerExtras /// that may not have rendered yet") while still giving up promptly, either because the caller cancelled it /// or because it took longer than — whichever happens first. Since the cancellation /// and timeout branches are sources that only ever error (see - /// and ), the only way + /// and ), the only way /// this combinator produces a value is if the retried itself emits one before either /// branch fires. /// @@ -23,7 +23,7 @@ public static class RetryAndRaceWithSignalAndTimerExtras /// /// Produces the exception used for both the cancellation and timeout branches. Defaults to /// for cancellation and for the timeout, - /// via the defaults of and respectively. + /// via the defaults of and respectively. /// /// The delay between retry attempts. Defaults to 50 milliseconds. /// A token that, when cancelled, aborts the whole operation immediately. diff --git a/src/RxSharp/Extras/Timeout.cs b/src/RxSharp/Extras/Timeout.cs index a315881..5e2cf9b 100644 --- a/src/RxSharp/Extras/Timeout.cs +++ b/src/RxSharp/Extras/Timeout.cs @@ -3,7 +3,7 @@ namespace RxSharp.Extras; /// Extension methods providing a standalone, -throwing timer observable. -public static class TimeoutExtras +public static partial class PuppeteerExtras { /// /// An observable that never emits and errors after elapses, or never errors at all if @@ -25,9 +25,9 @@ public static Observable Timeout(TimeSpan delay, Func? causeFac return Observable.Never(); } - var makeCause = causeFactory ?? DefaultCause; + var makeCause = causeFactory ?? DefaultTimeoutCause; return Observable.Timer(delay, scheduler).Map(_ => throw makeCause()); } - private static Exception DefaultCause() => new TimeoutException(); + private static Exception DefaultTimeoutCause() => new TimeoutException(); } diff --git a/test/RxSharp.Tests/Extras/CancellationExtrasTests.cs b/test/RxSharp.Tests/Extras/CancellationExtrasTests.cs index 219ddd5..27fd4f3 100644 --- a/test/RxSharp.Tests/Extras/CancellationExtrasTests.cs +++ b/test/RxSharp.Tests/Extras/CancellationExtrasTests.cs @@ -12,7 +12,7 @@ public void ShouldErrorImmediatelyIfTokenIsAlreadyCancelled() cts.Cancel(); Exception? received = null; - CancellationExtras.FromCancellationToken(cts.Token).Subscribe(onError: err => received = err); + PuppeteerExtras.FromCancellationToken(cts.Token).Subscribe(onError: err => received = err); Assert.That(received, Is.InstanceOf()); } @@ -22,7 +22,7 @@ public void ShouldErrorAsSoonAsTheTokenIsCancelled() { using var cts = new CancellationTokenSource(); Exception? received = null; - CancellationExtras.FromCancellationToken(cts.Token).Subscribe(onError: err => received = err); + PuppeteerExtras.FromCancellationToken(cts.Token).Subscribe(onError: err => received = err); Assert.That(received, Is.Null); @@ -36,7 +36,7 @@ public void ShouldNeverEmitAValue() { using var cts = new CancellationTokenSource(); var nextCalled = false; - var subscription = CancellationExtras.FromCancellationToken(cts.Token).Subscribe(_ => nextCalled = true); + var subscription = PuppeteerExtras.FromCancellationToken(cts.Token).Subscribe(_ => nextCalled = true); subscription.Dispose(); cts.Cancel(); @@ -52,7 +52,7 @@ public void ShouldUseTheCustomCauseFactory() var cause = new InvalidOperationException("custom cause"); Exception? received = null; - CancellationExtras.FromCancellationToken(cts.Token, () => cause).Subscribe(onError: err => received = err); + PuppeteerExtras.FromCancellationToken(cts.Token, () => cause).Subscribe(onError: err => received = err); Assert.That(received, Is.SameAs(cause)); } diff --git a/test/RxSharp.Tests/Extras/FromEventBufferedExtrasTests.cs b/test/RxSharp.Tests/Extras/FromEventBufferedExtrasTests.cs index 7868bd5..6f262ef 100644 --- a/test/RxSharp.Tests/Extras/FromEventBufferedExtrasTests.cs +++ b/test/RxSharp.Tests/Extras/FromEventBufferedExtrasTests.cs @@ -17,7 +17,7 @@ public void ShouldAttachTheHandlerImmediatelyRatherThanAtSubscribeTime() { var manager = new FakeTargetManager(); - using var source = FromEventBufferedExtras.FromEventBuffered( + using var source = PuppeteerExtras.FromEventBuffered( h => manager.TargetCreated += h, h => manager.TargetCreated -= h); @@ -35,7 +35,7 @@ public void ShouldAttachTheHandlerImmediatelyRatherThanAtSubscribeTime() public void ShouldReplayBufferedValuesToALateSubscriberThenDeliverLiveValues() { var manager = new FakeTargetManager(); - using var source = FromEventBufferedExtras.FromEventBuffered( + using var source = PuppeteerExtras.FromEventBuffered( h => manager.TargetCreated += h, h => manager.TargetCreated -= h); @@ -52,7 +52,7 @@ public void ShouldReplayBufferedValuesToALateSubscriberThenDeliverLiveValues() public void ShouldDeliverToEveryIndependentSubscriber() { var manager = new FakeTargetManager(); - using var source = FromEventBufferedExtras.FromEventBuffered( + using var source = PuppeteerExtras.FromEventBuffered( h => manager.TargetCreated += h, h => manager.TargetCreated -= h); @@ -71,7 +71,7 @@ public void ShouldDeliverToEveryIndependentSubscriber() public void ShouldOnlyReplayUpToTheRequestedBufferSize() { var manager = new FakeTargetManager(); - using var source = FromEventBufferedExtras.FromEventBuffered( + using var source = PuppeteerExtras.FromEventBuffered( h => manager.TargetCreated += h, h => manager.TargetCreated -= h, bufferSize: 2); @@ -90,7 +90,7 @@ public void ShouldOnlyReplayUpToTheRequestedBufferSize() public void ShouldDetachTheHandlerOnDispose() { var manager = new FakeTargetManager(); - var source = FromEventBufferedExtras.FromEventBuffered( + var source = PuppeteerExtras.FromEventBuffered( h => manager.TargetCreated += h, h => manager.TargetCreated -= h); @@ -106,7 +106,7 @@ public void ShouldDetachTheHandlerOnDispose() public void ShouldBeSafeToDisposeMoreThanOnce() { var manager = new FakeTargetManager(); - var source = FromEventBufferedExtras.FromEventBuffered( + var source = PuppeteerExtras.FromEventBuffered( h => manager.TargetCreated += h, h => manager.TargetCreated -= h); diff --git a/test/RxSharp.Tests/Extras/TimeoutExtrasTests.cs b/test/RxSharp.Tests/Extras/TimeoutExtrasTests.cs index a773b13..da79efa 100644 --- a/test/RxSharp.Tests/Extras/TimeoutExtrasTests.cs +++ b/test/RxSharp.Tests/Extras/TimeoutExtrasTests.cs @@ -11,7 +11,7 @@ public void ShouldErrorAfterTheDelay() using var signal = new ManualResetEventSlim(); Exception? received = null; - TimeoutExtras.Timeout(TimeSpan.FromMilliseconds(20)).Subscribe(onError: err => + PuppeteerExtras.Timeout(TimeSpan.FromMilliseconds(20)).Subscribe(onError: err => { received = err; signal.Set(); @@ -25,7 +25,7 @@ public void ShouldErrorAfterTheDelay() public void ShouldNeverErrorWhenDelayIsZero() { var errored = false; - TimeoutExtras.Timeout(TimeSpan.Zero).Subscribe(onError: _ => errored = true); + PuppeteerExtras.Timeout(TimeSpan.Zero).Subscribe(onError: _ => errored = true); Thread.Sleep(50); @@ -39,7 +39,7 @@ public void ShouldUseTheCustomCauseFactory() var cause = new InvalidOperationException("custom timeout cause"); Exception? received = null; - TimeoutExtras.Timeout(TimeSpan.FromMilliseconds(10), () => cause).Subscribe(onError: err => + PuppeteerExtras.Timeout(TimeSpan.FromMilliseconds(10), () => cause).Subscribe(onError: err => { received = err; signal.Set(); From d573f12e8ab7b79517d6184005914a9243ac9e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo=20Kondratiuk?= Date: Mon, 27 Jul 2026 17:50:45 -0300 Subject: [PATCH 3/3] Rename the consolidated Extras class to Extensions PuppeteerExtras tied the name too closely to one consumer for what's meant to be a general-purpose combinator surface. Extensions in the existing RxSharp.Extras namespace reads the same way as e.g. Observable in the RxSharp namespace. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01K92eapPm8e7mX7puT4cF4s --- src/RxSharp/Extras/AssumeNeverEmits.cs | 4 ++-- src/RxSharp/Extras/FilterAsync.cs | 2 +- src/RxSharp/Extras/FromCancellationToken.cs | 2 +- src/RxSharp/Extras/FromEventBuffered.cs | 4 ++-- src/RxSharp/Extras/RaceWithSignalAndTimer.cs | 10 +++++----- src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs | 6 +++--- src/RxSharp/Extras/Timeout.cs | 2 +- test/RxSharp.Tests/Extras/CancellationExtrasTests.cs | 8 ++++---- .../Extras/FromEventBufferedExtrasTests.cs | 12 ++++++------ test/RxSharp.Tests/Extras/TimeoutExtrasTests.cs | 6 +++--- 10 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/RxSharp/Extras/AssumeNeverEmits.cs b/src/RxSharp/Extras/AssumeNeverEmits.cs index 6cb07d0..6727380 100644 --- a/src/RxSharp/Extras/AssumeNeverEmits.cs +++ b/src/RxSharp/Extras/AssumeNeverEmits.cs @@ -3,11 +3,11 @@ namespace RxSharp.Extras; /// Extension methods widening an error-only to another element type. -public static partial class PuppeteerExtras +public static partial class Extensions { /// /// Widens an that only ever calls (such as - /// or ) so it + /// or ) so it /// type-checks anywhere an is expected - most commonly as one of the branches /// passed to alongside a source that actually produces /// values. Mirrors how rxjs relies on TypeScript structurally accepting diff --git a/src/RxSharp/Extras/FilterAsync.cs b/src/RxSharp/Extras/FilterAsync.cs index cdd005d..e789517 100644 --- a/src/RxSharp/Extras/FilterAsync.cs +++ b/src/RxSharp/Extras/FilterAsync.cs @@ -3,7 +3,7 @@ namespace RxSharp.Extras; /// Extension methods providing an async-predicate flavor of Filter. -public static partial class PuppeteerExtras +public static partial class Extensions { /// /// An operator supporting an async predicate, mirroring Puppeteer's own filterAsync helper (implemented, diff --git a/src/RxSharp/Extras/FromCancellationToken.cs b/src/RxSharp/Extras/FromCancellationToken.cs index 2858d4a..7c3a587 100644 --- a/src/RxSharp/Extras/FromCancellationToken.cs +++ b/src/RxSharp/Extras/FromCancellationToken.cs @@ -1,7 +1,7 @@ namespace RxSharp.Extras; /// Puppeteer-flavored combinators built on top of the core primitives — the C# analogues of the helpers Puppeteer itself layers on top of rxjs (see CLAUDE.md's "Puppeteer-essential surface"). -public static partial class PuppeteerExtras +public static partial class Extensions { /// /// An observable that never emits and errors as soon as is cancelled. diff --git a/src/RxSharp/Extras/FromEventBuffered.cs b/src/RxSharp/Extras/FromEventBuffered.cs index 04a45e9..7a7dad4 100644 --- a/src/RxSharp/Extras/FromEventBuffered.cs +++ b/src/RxSharp/Extras/FromEventBuffered.cs @@ -4,7 +4,7 @@ namespace RxSharp.Extras; /// /// A handle to an eagerly-attached, buffered .NET event source created by -/// . Disposing detaches the underlying event handler. +/// . Disposing detaches the underlying event handler. /// /// The type of the event's payload. public sealed class BufferedEventSource : IDisposable @@ -42,7 +42,7 @@ public void Dispose() } /// Extension methods providing an eagerly-attached, buffered variant of . -public static partial class PuppeteerExtras +public static partial class Extensions { /// /// Like , but attaches the underlying event handler diff --git a/src/RxSharp/Extras/RaceWithSignalAndTimer.cs b/src/RxSharp/Extras/RaceWithSignalAndTimer.cs index 5e8c37f..3478f77 100644 --- a/src/RxSharp/Extras/RaceWithSignalAndTimer.cs +++ b/src/RxSharp/Extras/RaceWithSignalAndTimer.cs @@ -3,11 +3,11 @@ namespace RxSharp.Extras; /// Extension methods racing a single subscription against cancellation and a timeout. -public static partial class PuppeteerExtras +public static partial class Extensions { /// /// Races against cancellation and a timeout, whichever fires first. The - /// non-retrying half of + /// non-retrying half of /// - use this directly for a single wait (e.g. "wait for the next matching event") that doesn't need /// retrying, and reach for the retrying combinator when it does. /// @@ -17,7 +17,7 @@ public static partial class PuppeteerExtras /// /// Produces the exception used for both the cancellation and timeout branches. Defaults to /// for cancellation and for the timeout, - /// via the defaults of and respectively. + /// via the defaults of and respectively. /// Since one factory covers both branches, a caller needing to tell the two apart by exception type should /// pass here (so each branch keeps its own distinct default type) and catch/rethrow /// as needed at the call site. @@ -30,8 +30,8 @@ public static Observable RaceWithSignalAndTimer( Func? causeFactory, CancellationToken cancellationToken) => source.RaceWith( - PuppeteerExtras.FromCancellationToken(cancellationToken, causeFactory).AssumeNeverEmits(), - PuppeteerExtras.Timeout(timeout, causeFactory).AssumeNeverEmits()); + Extensions.FromCancellationToken(cancellationToken, causeFactory).AssumeNeverEmits(), + Extensions.Timeout(timeout, causeFactory).AssumeNeverEmits()); /// /// Overload of diff --git a/src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs b/src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs index c49e376..79e95d1 100644 --- a/src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs +++ b/src/RxSharp/Extras/RetryAndRaceWithSignalAndTimer.cs @@ -3,7 +3,7 @@ namespace RxSharp.Extras; /// Extension methods composing retry, cancellation, and timeout into the single combinator Puppeteer's Locator actions rely on. -public static partial class PuppeteerExtras +public static partial class Extensions { /// /// The combinator behind Puppeteer's Locator actions (click/fill/hover/wait): retry the source @@ -13,7 +13,7 @@ public static partial class PuppeteerExtras /// that may not have rendered yet") while still giving up promptly, either because the caller cancelled it /// or because it took longer than — whichever happens first. Since the cancellation /// and timeout branches are sources that only ever error (see - /// and ), the only way + /// and ), the only way /// this combinator produces a value is if the retried itself emits one before either /// branch fires. /// @@ -23,7 +23,7 @@ public static partial class PuppeteerExtras /// /// Produces the exception used for both the cancellation and timeout branches. Defaults to /// for cancellation and for the timeout, - /// via the defaults of and respectively. + /// via the defaults of and respectively. /// /// The delay between retry attempts. Defaults to 50 milliseconds. /// A token that, when cancelled, aborts the whole operation immediately. diff --git a/src/RxSharp/Extras/Timeout.cs b/src/RxSharp/Extras/Timeout.cs index 5e2cf9b..d8dce67 100644 --- a/src/RxSharp/Extras/Timeout.cs +++ b/src/RxSharp/Extras/Timeout.cs @@ -3,7 +3,7 @@ namespace RxSharp.Extras; /// Extension methods providing a standalone, -throwing timer observable. -public static partial class PuppeteerExtras +public static partial class Extensions { /// /// An observable that never emits and errors after elapses, or never errors at all if diff --git a/test/RxSharp.Tests/Extras/CancellationExtrasTests.cs b/test/RxSharp.Tests/Extras/CancellationExtrasTests.cs index 27fd4f3..2611d4f 100644 --- a/test/RxSharp.Tests/Extras/CancellationExtrasTests.cs +++ b/test/RxSharp.Tests/Extras/CancellationExtrasTests.cs @@ -12,7 +12,7 @@ public void ShouldErrorImmediatelyIfTokenIsAlreadyCancelled() cts.Cancel(); Exception? received = null; - PuppeteerExtras.FromCancellationToken(cts.Token).Subscribe(onError: err => received = err); + Extensions.FromCancellationToken(cts.Token).Subscribe(onError: err => received = err); Assert.That(received, Is.InstanceOf()); } @@ -22,7 +22,7 @@ public void ShouldErrorAsSoonAsTheTokenIsCancelled() { using var cts = new CancellationTokenSource(); Exception? received = null; - PuppeteerExtras.FromCancellationToken(cts.Token).Subscribe(onError: err => received = err); + Extensions.FromCancellationToken(cts.Token).Subscribe(onError: err => received = err); Assert.That(received, Is.Null); @@ -36,7 +36,7 @@ public void ShouldNeverEmitAValue() { using var cts = new CancellationTokenSource(); var nextCalled = false; - var subscription = PuppeteerExtras.FromCancellationToken(cts.Token).Subscribe(_ => nextCalled = true); + var subscription = Extensions.FromCancellationToken(cts.Token).Subscribe(_ => nextCalled = true); subscription.Dispose(); cts.Cancel(); @@ -52,7 +52,7 @@ public void ShouldUseTheCustomCauseFactory() var cause = new InvalidOperationException("custom cause"); Exception? received = null; - PuppeteerExtras.FromCancellationToken(cts.Token, () => cause).Subscribe(onError: err => received = err); + Extensions.FromCancellationToken(cts.Token, () => cause).Subscribe(onError: err => received = err); Assert.That(received, Is.SameAs(cause)); } diff --git a/test/RxSharp.Tests/Extras/FromEventBufferedExtrasTests.cs b/test/RxSharp.Tests/Extras/FromEventBufferedExtrasTests.cs index 6f262ef..0a68745 100644 --- a/test/RxSharp.Tests/Extras/FromEventBufferedExtrasTests.cs +++ b/test/RxSharp.Tests/Extras/FromEventBufferedExtrasTests.cs @@ -17,7 +17,7 @@ public void ShouldAttachTheHandlerImmediatelyRatherThanAtSubscribeTime() { var manager = new FakeTargetManager(); - using var source = PuppeteerExtras.FromEventBuffered( + using var source = Extensions.FromEventBuffered( h => manager.TargetCreated += h, h => manager.TargetCreated -= h); @@ -35,7 +35,7 @@ public void ShouldAttachTheHandlerImmediatelyRatherThanAtSubscribeTime() public void ShouldReplayBufferedValuesToALateSubscriberThenDeliverLiveValues() { var manager = new FakeTargetManager(); - using var source = PuppeteerExtras.FromEventBuffered( + using var source = Extensions.FromEventBuffered( h => manager.TargetCreated += h, h => manager.TargetCreated -= h); @@ -52,7 +52,7 @@ public void ShouldReplayBufferedValuesToALateSubscriberThenDeliverLiveValues() public void ShouldDeliverToEveryIndependentSubscriber() { var manager = new FakeTargetManager(); - using var source = PuppeteerExtras.FromEventBuffered( + using var source = Extensions.FromEventBuffered( h => manager.TargetCreated += h, h => manager.TargetCreated -= h); @@ -71,7 +71,7 @@ public void ShouldDeliverToEveryIndependentSubscriber() public void ShouldOnlyReplayUpToTheRequestedBufferSize() { var manager = new FakeTargetManager(); - using var source = PuppeteerExtras.FromEventBuffered( + using var source = Extensions.FromEventBuffered( h => manager.TargetCreated += h, h => manager.TargetCreated -= h, bufferSize: 2); @@ -90,7 +90,7 @@ public void ShouldOnlyReplayUpToTheRequestedBufferSize() public void ShouldDetachTheHandlerOnDispose() { var manager = new FakeTargetManager(); - var source = PuppeteerExtras.FromEventBuffered( + var source = Extensions.FromEventBuffered( h => manager.TargetCreated += h, h => manager.TargetCreated -= h); @@ -106,7 +106,7 @@ public void ShouldDetachTheHandlerOnDispose() public void ShouldBeSafeToDisposeMoreThanOnce() { var manager = new FakeTargetManager(); - var source = PuppeteerExtras.FromEventBuffered( + var source = Extensions.FromEventBuffered( h => manager.TargetCreated += h, h => manager.TargetCreated -= h); diff --git a/test/RxSharp.Tests/Extras/TimeoutExtrasTests.cs b/test/RxSharp.Tests/Extras/TimeoutExtrasTests.cs index da79efa..625f46a 100644 --- a/test/RxSharp.Tests/Extras/TimeoutExtrasTests.cs +++ b/test/RxSharp.Tests/Extras/TimeoutExtrasTests.cs @@ -11,7 +11,7 @@ public void ShouldErrorAfterTheDelay() using var signal = new ManualResetEventSlim(); Exception? received = null; - PuppeteerExtras.Timeout(TimeSpan.FromMilliseconds(20)).Subscribe(onError: err => + Extensions.Timeout(TimeSpan.FromMilliseconds(20)).Subscribe(onError: err => { received = err; signal.Set(); @@ -25,7 +25,7 @@ public void ShouldErrorAfterTheDelay() public void ShouldNeverErrorWhenDelayIsZero() { var errored = false; - PuppeteerExtras.Timeout(TimeSpan.Zero).Subscribe(onError: _ => errored = true); + Extensions.Timeout(TimeSpan.Zero).Subscribe(onError: _ => errored = true); Thread.Sleep(50); @@ -39,7 +39,7 @@ public void ShouldUseTheCustomCauseFactory() var cause = new InvalidOperationException("custom timeout cause"); Exception? received = null; - PuppeteerExtras.Timeout(TimeSpan.FromMilliseconds(10), () => cause).Subscribe(onError: err => + Extensions.Timeout(TimeSpan.FromMilliseconds(10), () => cause).Subscribe(onError: err => { received = err; signal.Set();