Skip to main content

blooming_test 0.2.4 copy "blooming_test: ^0.2.4" to clipboard
blooming_test: ^0.2.4 copied to clipboard

Comprehensive declarative testing harness and custom LPDAG matchers for the Bloom reactive framework.

example/blooming_test_example.dart

// Copyright 2023-2026 Youngjune Jeon All rights reserved.
// Use of this source code is governed by an Apache 2.0 license that can be
// found in the LICENSE file.

import 'package:blooming_test/blooming_test.dart';

// =============================================================================
// 1. Real-World Business Logic: Search & Filter Store
// =============================================================================

class SearchStore {
  SearchStore() {
    debouncedQuery = query.bloom.debounced(const Duration(milliseconds: 300));

    results = computed((via) {
      final term = via(debouncedQuery).trim();
      if (term.isEmpty) return const <String>[];
      return [
        'Result 1 for "$term"',
        'Result 2 for "$term"',
      ];
    });
  }

  /// Upstream user input
  final query = blooming('');

  /// Internal debounced pipeline node
  late final Bloom<String> debouncedQuery;

  /// Derived synchronous search results
  late final Bloom<List<String>> results;
}

// =============================================================================
// 2. Real-World Business Logic: Shopping Cart Store (Optimistic Mutations)
// =============================================================================

class CartStore {
  CartStore();

  final items = blooming<List<String>>([]);

  late final Bloom<int> itemCount = computed((via) => via(items.bloom).length);

  void addItem(String item) {
    items.data = [...items.data, item];
  }

  void removeItem(String item) {
    items.data = items.data.where((i) => i != item).toList();
  }

  void clear() {
    items.data = [];
  }
}

// =============================================================================
// 3. Complete Executable Test Suite with blooming_test
// =============================================================================

void main() {
  group('Shopping Cart Store Tests (Synchronous bloomTest)', () {
    bloomTest<CartStore>(
      'tracks cart item mutations and updates derived item count',
      build: () => CartStore(),
      target: (store) => store.itemCount,
      act: (store) {
        store.addItem('MacBook Pro');
        store.addItem('Magic Mouse');
        store.removeItem('Magic Mouse');
      },
      // Distinct states observed by itemCount: initial 0 -> 1 -> 2 -> 1
      expect: () => [0, 1, 2, 1],
      verify: (store) {
        expect(store.items.data, ['MacBook Pro']);
        expect(untracked(store.itemCount), 1);
      },
    );
  });

  group('Search Pipeline Tests (Virtual Time & Debounce)', () {
    bloomTest<SearchStore>(
      'debounces rapid typing and only queries API after 300ms of silence',
      build: () => SearchStore(),
      target: (store) => store.results,
      act: (store) {
        // User types rapidly: "d" -> "da" -> "dart"
        store.query.data = 'd';
        store.query.data = 'da';
        store.query.data = 'dart';
      },
      // Fast-forward 300ms virtual time using FakeAsync
      wait: const Duration(milliseconds: 300),
      // Intermediate "d" and "da" are suppressed by debounce
      expect: () => [
        const <String>[], // Initial empty list
        const ['Result 1 for "dart"', 'Result 2 for "dart"'],
      ],
      verify: (store) {
        expect(store.query.data, 'dart');
      },
    );
  });

  group('LPDAG Lifecycle & Invariant Assertions', () {
    test('verifies subscriber-driven dormancy invariant (zero memory leak)',
        () {
      final source = blooming(100);
      final derived = computed((via) => via(source.bloom) * 2);

      // 1. Unobserved nodes remain dormant (0 active subscribers)
      expect(derived, isDormant);
      expect(source, isDormant);

      // 2. Observing wakes up the node via effect runner
      final runner = derived.effect(() {}).start();
      expect(derived, isActive);
      expect(derived, hasSubscribers(1));

      // 3. Deactivating runner synchronously drops subscribers back to 0 (dormant)
      runner.stop();
      expect(derived, isDormant);
    });

    test('asserts 3-State FSM transitions with snapshot matchers', () {
      final user = blooming.late<String>(reason: 'Unauthenticated');

      // Check empty state directly on controller or node
      expect(user, isEmptyState('Unauthenticated'));

      // Transition to data
      user.data = 'Alice';
      expect(user, isData('Alice'));

      // Transition to error
      user.setError(Exception('Session expired'));
      expect(user, isErrorState());
    });
  });
}
0
likes
160
points
318
downloads

Documentation

API reference

Publisher

verified publisherpioneerjkf.dev

Weekly Downloads

Comprehensive declarative testing harness and custom LPDAG matchers for the Bloom reactive framework.

Homepage
Repository (GitHub)
View/report issues

Topics

#testing #reactive #state-management #lpdag

License

Apache-2.0 (license)

Dependencies

blooming, fake_async, meta, test

More

Packages that depend on blooming_test