Tane dialled its four default relays at launch, before anyone had asked for anything — an F-Droid reviewer spotted it, and they were right. The seed book needs no network at all, so the app should not have one until the person joins the sharing side. - SocialSettings gains a three-state `sharingEnabled`. `null` means "never asked", which is what lets `migrateSharingEnabled` keep an existing install exactly as it was: anyone past the intro was on a build that connected at launch, so they keep messaging, device sync and offer alerts. A fresh install starts fully offline. - bootstrap only starts the shared connection when sharing is on. The inbox/sync/plantaré/alert listeners are untouched: they react to a session, and none arrives. - SharingSwitch is the single place that moves the stored choice, the live connection and the flag the UI listens to, so they cannot drift. - Agreeing to the community rules is the opt-in — one consent surface, reached from the market or from the drawer's invitation. - SocialConnection.start is now idempotent and gains stop(), so turning sharing off goes offline immediately instead of at the next launch. - The social drawer entries stay visible but padlocked while sharing is off; tapping one explains what wakes up and offers to join. Hiding them would have kept the tool a secret. "Coming soon" is gone for good — everything it labelled is built. Covered by tests for the migration in both directions, start/stop lifecycle, the gate turning sharing on, the invitation, and the drawer in all three states (no social layer / off / on).
170 lines
5.8 KiB
Dart
170 lines
5.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:tane/app.dart';
|
|
import 'package:tane/i18n/strings.g.dart';
|
|
import 'package:tane/services/sharing_switch.dart';
|
|
|
|
import '../support/test_support.dart';
|
|
|
|
void main() {
|
|
/// The everyday case: the social layer exists and sharing is already on.
|
|
Widget app(db, {SharingSwitch? sharing}) => TranslationProvider(
|
|
child: TaneApp(
|
|
repository: newTestRepository(db),
|
|
species: newTestSpeciesRepository(db),
|
|
onboarding: newTestOnboardingStore(),
|
|
sharing: sharing ?? newTestSharingSwitch(),
|
|
),
|
|
);
|
|
|
|
testWidgets('home shows the menu and navigates to inventory', (tester) async {
|
|
LocaleSettings.setLocaleSync(AppLocale.en);
|
|
final db = newTestDatabase();
|
|
addTearDown(db.close);
|
|
|
|
await tester.pumpWidget(app(db));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Your inventory'), findsOneWidget);
|
|
expect(find.text('Market'), findsOneWidget);
|
|
// "Coming soon" is gone for good: everything on this screen is built.
|
|
expect(find.textContaining('COMING SOON'), findsNothing);
|
|
|
|
// Redesign copy: tagline + the two destination subtitles.
|
|
expect(find.text('Share and grow local seeds'), findsOneWidget);
|
|
expect(find.text('Manage your seeds'), findsOneWidget);
|
|
expect(find.text('Discover and share seeds nearby'), findsOneWidget);
|
|
|
|
await tester.tap(find.byKey(const Key('home.inventory')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('No seeds yet. Tap + to add your first.'), findsOneWidget);
|
|
await disposeTree(tester);
|
|
});
|
|
|
|
testWidgets('drawer opens and its Inventory item navigates', (tester) async {
|
|
LocaleSettings.setLocaleSync(AppLocale.en);
|
|
final db = newTestDatabase();
|
|
addTearDown(db.close);
|
|
|
|
await tester.pumpWidget(app(db));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byIcon(Icons.menu)); // hamburger
|
|
await tester.pumpAndSettle();
|
|
|
|
// With sharing on, the social destinations are live.
|
|
expect(find.text('Your profile'), findsOneWidget);
|
|
expect(find.byIcon(Icons.lock_outline), findsNothing);
|
|
|
|
await tester.tap(find.text('Inventory')); // active drawer item
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('No seeds yet. Tap + to add your first.'), findsOneWidget);
|
|
await disposeTree(tester);
|
|
});
|
|
|
|
testWidgets('inventory is a spoke: back arrow, no drawer, returns to home',
|
|
(tester) async {
|
|
LocaleSettings.setLocaleSync(AppLocale.en);
|
|
final db = newTestDatabase();
|
|
addTearDown(db.close);
|
|
|
|
await tester.pumpWidget(app(db));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Push into inventory off the home hub.
|
|
await tester.tap(find.byKey(const Key('home.inventory')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('No seeds yet. Tap + to add your first.'), findsOneWidget);
|
|
|
|
// The spoke shows a back arrow, not a hamburger — coherent with the market.
|
|
expect(find.byIcon(Icons.menu), findsNothing);
|
|
await tester.tap(find.byType(BackButton));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Back on the home menu.
|
|
expect(find.text('Your inventory'), findsOneWidget);
|
|
expect(find.text('Market'), findsOneWidget);
|
|
await disposeTree(tester);
|
|
});
|
|
|
|
testWidgets('sharing off: market stays open, the rest wears a padlock',
|
|
(tester) async {
|
|
LocaleSettings.setLocaleSync(AppLocale.en);
|
|
final db = newTestDatabase();
|
|
addTearDown(db.close);
|
|
|
|
await tester.pumpWidget(
|
|
app(db, sharing: newTestSharingSwitch(enabled: false)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// The market card is the door into sharing, so it must stay reachable.
|
|
expect(find.byKey(const Key('home.market')), findsOneWidget);
|
|
|
|
await tester.tap(find.byIcon(Icons.menu));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Market live; the social entries are visible but padlocked — and tapping
|
|
// one invites the person in rather than doing nothing.
|
|
expect(find.text('Your profile'), findsOneWidget);
|
|
expect(find.byIcon(Icons.lock_outline), findsWidgets);
|
|
|
|
await tester.tap(find.text('Your profile'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('This wakes up when you start sharing'), findsOneWidget);
|
|
await disposeTree(tester);
|
|
});
|
|
|
|
testWidgets('no social layer: the market and its friends are not drawn',
|
|
(tester) async {
|
|
LocaleSettings.setLocaleSync(AppLocale.en);
|
|
final db = newTestDatabase();
|
|
addTearDown(db.close);
|
|
|
|
await tester.pumpWidget(
|
|
TranslationProvider(
|
|
child: TaneApp(
|
|
repository: newTestRepository(db),
|
|
species: newTestSpeciesRepository(db),
|
|
onboarding: newTestOnboardingStore(),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Nothing here can be switched on, so it is hidden rather than teased.
|
|
expect(find.byKey(const Key('home.market')), findsNothing);
|
|
expect(find.text('Your inventory'), findsOneWidget);
|
|
|
|
await tester.tap(find.byIcon(Icons.menu));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Your profile'), findsNothing);
|
|
expect(find.byIcon(Icons.lock_outline), findsNothing);
|
|
await disposeTree(tester);
|
|
});
|
|
|
|
testWidgets('drawer Settings opens the settings screen', (tester) async {
|
|
|
|
|
|
LocaleSettings.setLocaleSync(AppLocale.en);
|
|
final db = newTestDatabase();
|
|
addTearDown(db.close);
|
|
|
|
await tester.pumpWidget(app(db));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byIcon(Icons.menu));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.text('Settings'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Locale-robust: assert the screen opened via the language selector's key
|
|
// and icon, not the current-language endonym (under en it's "English").
|
|
expect(find.byKey(const Key('settings.language')), findsOneWidget);
|
|
expect(find.byIcon(Icons.translate), findsOneWidget);
|
|
await disposeTree(tester);
|
|
}); }
|
|
|