Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,6 @@ lib/generated/

# Mutation testing reports
mutation-test-report/

# Coverage output (regenerated by `flutter test --coverage`)
coverage/
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,66 @@ flutter format .
# Run tests
flutter test
flutter test integration_test/

# Run tests and produce a coverage report
flutter test --coverage
dart run tool/coverage_report.dart
```

## 🧪 Test Coverage

**Current line coverage: 33.00% (6,498 of 19,689 lines), across 952 tests.**

The figure comes from the LCOV records, that is, from every non-generated file
`flutter test --coverage` instrumented. Generated sources (`lib/generated/**`,
`*.g.dart`, `*.freezed.dart`, `*.mocks.dart`) are excluded because
`build_runner` re-creates them on every build; counting them would distort the
number.

### Checking coverage yourself

```bash
flutter pub get
dart run build_runner build -d # generates mocks and localization
flutter test --coverage # writes coverage/lcov.info
dart run tool/coverage_report.dart # prints the summary
```

`tool/coverage_report.dart` reads `coverage/lcov.info` and prints total line
coverage, the number of files measured, and any `lib/` file that no test ever
loaded. Those untouched files are listed as a warning so they are visible
instead of vanishing the way a plain `lcov` summary would hide them. They carry
no instrumented lines, so they are **not** part of the percentage: `--min` can
pass while they remain unmeasured. Import a file from a test to bring it into
the measured set.

Useful flags:

```bash
# List the files with the most uncovered lines
dart run tool/coverage_report.dart --top 20

# Fail with a non-zero exit code below a threshold (handy in CI)
dart run tool/coverage_report.dart --min 33
```

For an annotated HTML report, `lcov` works on the same file:

```bash
genhtml coverage/lcov.info -o coverage/html && open coverage/html/index.html
```

### What is and is not covered

- **Well covered**: protocol models and payloads, enums, the order state
machine (`MostroFSM`, `OrderState`), relay models, shared utilities, and most
presentational widgets plus the settings, logs and wallet screens.
- **Thin or uncovered**: `main.dart` and platform bootstrap, background and
push-notification services, Firebase glue, the restore manager, and the
long-lived Nostr/subscription notifiers. These need a live relay, a platform
channel, or a substantial mocking harness, so they are exercised by
`integration_test/` rather than by unit tests.

### Code Quality
This project maintains **zero Flutter analyze issues** and follows modern Flutter best practices:
- Updated to latest APIs (no deprecated warnings)
Expand Down
6 changes: 5 additions & 1 deletion lib/core/models/relay_list_event.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import 'package:dart_nostr/dart_nostr.dart';

/// Matches every trailing slash so `wss://relay.example//` normalizes the same
/// way as `wss://relay.example/`.
final RegExp _trailingSlashes = RegExp(r'/+$');

/// Represents a NIP-65 relay list event (kind 10002) from a Mostro instance.
/// These events contain the list of relays where the Mostro instance publishes its events.
class RelayListEvent {
Expand Down Expand Up @@ -49,7 +53,7 @@ class RelayListEvent {
return relays
.where((url) => url.startsWith('wss://') || url.startsWith('ws://'))
.map((url) => url.trim())
.map((url) => url.endsWith('/') ? url.substring(0, url.length - 1) : url)
.map((url) => url.replaceAll(_trailingSlashes, ''))
.toList();
}

Expand Down
9 changes: 3 additions & 6 deletions lib/features/relays/relays_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -860,12 +860,9 @@ class RelaysNotifier extends StateNotifier<List<Relay>> {

/// Normalize relay URL to prevent duplicates (removes trailing slash)
String _normalizeRelayUrl(String url) {
url = url.trim();
// Remove trailing slash if present
if (url.endsWith('/')) {
url = url.substring(0, url.length - 1);
}
return url;
// Remove every trailing slash so `wss://relay//` and `wss://relay/`
// normalize to the same key as `wss://relay`.
return url.trim().replaceAll(RegExp(r'/+$'), '');
}

@override
Expand Down
87 changes: 54 additions & 33 deletions lib/shared/widgets/order_filter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -524,22 +524,30 @@ class OrderFilterState extends ConsumerState<OrderFilter> {
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${S.of(context)!.discount}: ${premiumMin.toInt()}%",
style: const TextStyle(
color: AppTheme.sellColor,
fontSize: 12,
fontWeight: FontWeight.w500,
Flexible(
child: Text(
"${S.of(context)!.discount}: ${premiumMin.toInt()}%",
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppTheme.sellColor,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
const Spacer(),
Text(
"${S.of(context)!.premium}: ${premiumMax.toInt()}%",
style: const TextStyle(
color: AppTheme.buyColor,
fontSize: 12,
fontWeight: FontWeight.w500,
const SizedBox(width: 8),
Flexible(
child: Text(
"${S.of(context)!.premium}: ${premiumMax.toInt()}%",
textAlign: TextAlign.end,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppTheme.buyColor,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
],
Expand Down Expand Up @@ -596,22 +604,30 @@ class OrderFilterState extends ConsumerState<OrderFilter> {
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${S.of(context)!.min}: ${ratingMin.toInt()}",
style: const TextStyle(
color: AppTheme.sellColor,
fontSize: 12,
fontWeight: FontWeight.w500,
Flexible(
child: Text(
"${S.of(context)!.min}: ${ratingMin.toInt()}",
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppTheme.sellColor,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
const Spacer(),
Text(
"${S.of(context)!.max}: ${ratingMax.toInt()}",
style: const TextStyle(
color: AppTheme.buyColor,
fontSize: 12,
fontWeight: FontWeight.w500,
const SizedBox(width: 8),
Flexible(
child: Text(
"${S.of(context)!.max}: ${ratingMax.toInt()}",
textAlign: TextAlign.end,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppTheme.buyColor,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
],
Expand Down Expand Up @@ -668,16 +684,20 @@ class OrderFilterState extends ConsumerState<OrderFilter> {
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${S.of(context)!.days}: 0",
style: const TextStyle(
color: AppTheme.sellColor,
fontSize: 12,
fontWeight: FontWeight.w500,
Flexible(
child: Text(
"${S.of(context)!.days}: 0",
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppTheme.sellColor,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
const Spacer(),
const SizedBox(width: 8),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
SizedBox(
width: 72,
child: Text(
Expand Down Expand Up @@ -734,6 +754,7 @@ class OrderFilterState extends ConsumerState<OrderFilter> {
width: 72,
height: 32,
child: TextField(
key: const Key('minDaysField'),
controller: _daysController,
keyboardType: TextInputType.number,
inputFormatters: [
Expand Down
2 changes: 1 addition & 1 deletion pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1487,7 +1487,7 @@ packages:
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
dependency: "direct dev"
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
Expand Down
3 changes: 3 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ dev_dependencies:
sdk: flutter
flutter_intl: ^0.0.1
mockito: ^5.4.5
# In-memory SharedPreferencesAsync backend for widget tests
shared_preferences_platform_interface: ^2.4.1
Comment thread
coderabbitai[bot] marked this conversation as resolved.
build_runner: ^2.4.0
# Mutation testing for test quality assurance
mutation_test: ^1.8.0
Expand Down Expand Up @@ -176,3 +178,4 @@ flutter_launcher_icons:
adaptive_icon_foreground: "assets/images/launcher-icon.png"
adaptive_icon_background: "#2D2D2D"
min_sdk_android: 21

Loading
Loading