Two ways the event rules reported handled events as unhandled (error severity, high confidence): 1. A handler on the event base or an intermediate group. `on<SyncEvent>` with a switch inside handles every subclass, but handlers were matched to dispatches by exact name, so every dispatched subclass was reported by event-without-handler and the base handler itself by dead-handler. Variants now record the supertype they were enumerated through, and coverage follows it: an event is handled when it or an ancestor is registered, and a handler is live when anything at or below it is dispatched or constructed. A group handler still does not cover a sibling outside the group (UploadAbandoned stays reported). 2. Blocs sharing an event type. Only the first owner of an event type was kept, so a second bloc's handlers were ignored: an event only it handles was reported unhandled, and its dead handlers were never examined. There is now one event union per owning bloc. A dispatch does not record which bloc it targets, so an event any sharing bloc handles counts as handled, and an unhandled event is reported once per shared union rather than once per bloc. Dead handlers are still judged per bloc. Reproduced first on base_event.dart: 4 false findings before (SyncStarted, UploadRetryNow unhandled; SyncEvent, UploadRetry dead) and the shared case (ShareDismissed falsely unhandled, ShareBetaBloc's dead ShareExpired missed). Each is now a contract control. No change on nuwayze (102 findings) or tick (2): byte-identical before and after, since neither uses these patterns.
127 lines
3.1 KiB
Dart
127 lines
3.1 KiB
Dart
import 'bloc.dart';
|
|
|
|
// --- a handler registered on the event BASE --------------------------------
|
|
//
|
|
// `on<SyncEvent>` with a switch inside handles every subclass. Matching handlers
|
|
// to dispatches by exact name would report every dispatched subclass as
|
|
// unhandled (error severity) and the base handler itself as dead. This is the
|
|
// regression guard for handler coverage following the event hierarchy.
|
|
|
|
sealed class SyncEvent {
|
|
const SyncEvent();
|
|
}
|
|
|
|
class SyncStarted extends SyncEvent {
|
|
const SyncStarted();
|
|
}
|
|
|
|
class SyncCancelled extends SyncEvent {
|
|
const SyncCancelled();
|
|
}
|
|
|
|
/// An intermediate group: `on<RetryEvent>` covers [RetryNow] only.
|
|
abstract class RetryEvent extends SyncEvent {
|
|
const RetryEvent();
|
|
}
|
|
|
|
class RetryNow extends RetryEvent {
|
|
const RetryNow();
|
|
}
|
|
|
|
class SyncState {
|
|
const SyncState();
|
|
}
|
|
|
|
class SyncBloc extends Bloc<SyncEvent, SyncState> {
|
|
SyncBloc() : super(const SyncState());
|
|
|
|
void register() {
|
|
on<SyncEvent>((event, emit) {
|
|
switch (event) {
|
|
case SyncStarted():
|
|
case SyncCancelled():
|
|
case RetryEvent():
|
|
emit(const SyncState());
|
|
}
|
|
});
|
|
}
|
|
|
|
void start() => add(const SyncStarted());
|
|
}
|
|
|
|
// A second bloc whose only handler is on an intermediate group, so the rule must
|
|
// credit dispatches of the group's subclasses to it, and still report a sibling
|
|
// outside the group.
|
|
|
|
sealed class UploadEvent {
|
|
const UploadEvent();
|
|
}
|
|
|
|
abstract class UploadRetry extends UploadEvent {
|
|
const UploadRetry();
|
|
}
|
|
|
|
class UploadRetryNow extends UploadRetry {
|
|
const UploadRetryNow();
|
|
}
|
|
|
|
/// Dispatched, and outside the handled `UploadRetry` group: truly unhandled.
|
|
class UploadAbandoned extends UploadEvent {
|
|
const UploadAbandoned();
|
|
}
|
|
|
|
class UploadBloc extends Bloc<UploadEvent, SyncState> {
|
|
UploadBloc() : super(const SyncState());
|
|
|
|
void register() {
|
|
on<UploadRetry>((event, emit) => emit(const SyncState()));
|
|
}
|
|
|
|
void retry() => add(const UploadRetryNow());
|
|
|
|
void abandon() => add(const UploadAbandoned());
|
|
}
|
|
|
|
// --- two blocs sharing one event type ---------------------------------------
|
|
//
|
|
// Keeping only the first owner of an event type ignored the second bloc's
|
|
// handlers: an event only the second bloc handles was reported unhandled (error
|
|
// severity), and a dead handler in the second bloc was never seen.
|
|
|
|
sealed class ShareEvent {
|
|
const ShareEvent();
|
|
}
|
|
|
|
class SharePressed extends ShareEvent {
|
|
const SharePressed();
|
|
}
|
|
|
|
class ShareDismissed extends ShareEvent {
|
|
const ShareDismissed();
|
|
}
|
|
|
|
/// Handled only by [ShareBetaBloc], and never dispatched: dead there.
|
|
class ShareExpired extends ShareEvent {
|
|
const ShareExpired();
|
|
}
|
|
|
|
class ShareAlphaBloc extends Bloc<ShareEvent, SyncState> {
|
|
ShareAlphaBloc() : super(const SyncState());
|
|
|
|
void register() {
|
|
on<SharePressed>((event, emit) => emit(const SyncState()));
|
|
}
|
|
|
|
void press() => add(const SharePressed());
|
|
}
|
|
|
|
class ShareBetaBloc extends Bloc<ShareEvent, SyncState> {
|
|
ShareBetaBloc() : super(const SyncState());
|
|
|
|
void register() {
|
|
on<ShareDismissed>((event, emit) => emit(const SyncState()));
|
|
on<ShareExpired>((event, emit) => emit(const SyncState()));
|
|
}
|
|
|
|
void dismiss() => add(const ShareDismissed());
|
|
}
|