feat(app)!: no anonymous accounts on the device (ADR 0020, D4, D25)
Every account has a way in, so the apps make no anonymous account any more. The start screen shows whenever nobody is signed in, a first start included, and offers SimpleLogin, a password and GitHub for those who have it, with no start without an account. An instance out of reach is said so, with Try again, and one offering no way to create an account too. An anonymous account's session left from before is deleted as the app starts (D25), falling back to signing out on the device. What only anonymous accounts needed goes: signing in anonymously, the signed-out mark, adding GitHub to an account and switching to a GitHub account from the Profile tab (with its Cancel, the only one), an anonymous device's own keys, the warning that signing out deletes the account, and the texts about keeping progress. Settings says on which instance the account is signed in. Learn's publishing, reports and suggestions and its learner count no longer ask whether the person has an identity: everyone signed in has one. The server still offers anonymous sign-in until the next commit, so older apps keep working until v0.20.0 raises the minimum build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
fad12b4411
commit
5b9df73756
11 changed files with 981 additions and 3339 deletions
|
|
@ -1,302 +0,0 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_app/halkyon_app.dart';
|
||||
import 'package:halkyon_app/testing.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/home/widget/home_screen.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_payload.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_content.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
import '../../../support/fake_studyset_gateway.dart';
|
||||
import '../../../support/pump_app.dart';
|
||||
import '../../../support/test_dependencies.dart';
|
||||
|
||||
void main() {
|
||||
late FakeAuthGateway auth;
|
||||
|
||||
setUp(() => auth = FakeAuthGateway());
|
||||
|
||||
/// Signs in anonymously and opens the profile tab; this device holds
|
||||
/// [studysets] studysets.
|
||||
Future<void> openProfile(WidgetTester tester, {int studysets = 0}) async {
|
||||
final gateway = FakeStudysetGateway();
|
||||
for (var i = 1; i <= studysets; i++) {
|
||||
gateway.receiveRemoteChange(
|
||||
'studyset-$i',
|
||||
encodeStudysetPayload(
|
||||
StudysetContent(
|
||||
title: 'Studyset $i',
|
||||
description: RichContent.empty(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
await pumpApp(
|
||||
tester,
|
||||
testDependencies(authGateway: auth, studysetGateway: gateway),
|
||||
);
|
||||
await openTab(tester, 'Profile');
|
||||
}
|
||||
|
||||
testWidgets('offers GitHub sign-in to anonymous people', (tester) async {
|
||||
await openProfile(tester);
|
||||
|
||||
expect(find.text('Sign in with GitHub'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('offers none where the instance has no GitHub sign-in', (
|
||||
tester,
|
||||
) async {
|
||||
auth.offersGitHub = false;
|
||||
|
||||
await openProfile(tester);
|
||||
|
||||
expect(find.text('Sign in with GitHub'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('signing in with GitHub keeps the progress with the account', (
|
||||
tester,
|
||||
) async {
|
||||
await openProfile(tester);
|
||||
|
||||
await tester.tap(find.text('Sign in with GitHub'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.text('Your progress is kept with your account.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('Sign in with GitHub'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('the account keeps its width once signed in', (tester) async {
|
||||
await openProfile(tester);
|
||||
Finder account() => find.ancestor(
|
||||
of: find.text('Sign in with GitHub'),
|
||||
matching: find.byType(ListView),
|
||||
);
|
||||
final anonymous = tester.getSize(account()).width;
|
||||
|
||||
await tester.tap(find.text('Sign in with GitHub'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
tester.getSize(find.byType(ListView).first).width,
|
||||
anonymous,
|
||||
);
|
||||
});
|
||||
|
||||
group("while GitHub's page is open in the browser", () {
|
||||
setUp(() => auth.gitHubPageOpens = true);
|
||||
|
||||
/// Opens the profile and starts signing in with GitHub.
|
||||
Future<void> startSignIn(WidgetTester tester) async {
|
||||
await openProfile(tester);
|
||||
await tester.tap(find.text('Sign in with GitHub'));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets('offers to cancel; cancelling changes nothing', (tester) async {
|
||||
auth.canCancelGitHub = true;
|
||||
await startSignIn(tester);
|
||||
expect(find.text('Continue in your browser.'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Cancel'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(auth.gitHubCancels, 1);
|
||||
expect(find.text('Sign in with GitHub'), findsOneWidget);
|
||||
expect(find.text("Couldn't sign in with GitHub."), findsNothing);
|
||||
expect(
|
||||
find.text('Progress is saved on this device. Sign in to keep it.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets("offers no cancel where the system's sign-in screen has its "
|
||||
'own', (tester) async {
|
||||
await startSignIn(tester);
|
||||
|
||||
expect(find.text('Continue in your browser.'), findsNothing);
|
||||
expect(find.text('Cancel'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('signs in once GitHub answers', (tester) async {
|
||||
auth.canCancelGitHub = true;
|
||||
await startSignIn(tester);
|
||||
|
||||
auth.answerGitHub();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.text('Your progress is kept with your account.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('Continue in your browser.'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('choosing another tab cancels it', (tester) async {
|
||||
auth.canCancelGitHub = true;
|
||||
await startSignIn(tester);
|
||||
|
||||
await openTab(tester, 'Home');
|
||||
expect(auth.gitHubCancels, 1);
|
||||
await openTab(tester, 'Profile');
|
||||
|
||||
expect(find.text('Sign in with GitHub'), findsOneWidget);
|
||||
expect(find.text("Couldn't sign in with GitHub."), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('settings, opened over the profile, keep it waiting', (
|
||||
tester,
|
||||
) async {
|
||||
auth.canCancelGitHub = true;
|
||||
await startSignIn(tester);
|
||||
|
||||
await tester.tap(find.byTooltip('Settings'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pageBack();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(auth.gitHubCancels, 0);
|
||||
expect(find.text('Continue in your browser.'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('a change of person cancels it', (tester) async {
|
||||
auth.canCancelGitHub = true;
|
||||
await startSignIn(tester);
|
||||
|
||||
auth.revokeSession();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(auth.gitHubCancels, 1);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets("asks before leaving this device's studysets behind; staying "
|
||||
'keeps them', (tester) async {
|
||||
auth.gitHubLinkOutcome = GitHubLinkOutcome.takenByAnotherPerson;
|
||||
await openProfile(tester, studysets: 1);
|
||||
|
||||
await tester.tap(find.text('Sign in with GitHub'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Switch to your GitHub account?'), findsOneWidget);
|
||||
await tester.tap(find.text('Stay anonymous'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(auth.gitHubSwitches, 0);
|
||||
expect(
|
||||
find.text('Progress is saved on this device. Sign in to keep it.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('switches once confirmed, and the app starts anew at home', (
|
||||
tester,
|
||||
) async {
|
||||
auth.gitHubLinkOutcome = GitHubLinkOutcome.takenByAnotherPerson;
|
||||
await openProfile(tester, studysets: 1);
|
||||
|
||||
await tester.tap(find.text('Sign in with GitHub'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Switch'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(auth.gitHubSwitches, 1);
|
||||
expect(find.text('Switch to your GitHub account?'), findsNothing);
|
||||
expect(find.byType(HomeScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
group('where people sign in with a password (ADR 0017)', () {
|
||||
setUp(() => auth.linksGitHub = false);
|
||||
|
||||
testWidgets('anonymous people may still sign in with the GitHub account '
|
||||
'they have; one nobody has is said so, with the password in its '
|
||||
'place', (tester) async {
|
||||
await openProfile(tester);
|
||||
expect(find.text('Sign in with GitHub'), findsOneWidget);
|
||||
|
||||
auth.gitHubError = const GitHubSignInFailed(
|
||||
GitHubSignInProblem.deprecated,
|
||||
);
|
||||
await tester.tap(find.text('Sign in with GitHub'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.text(
|
||||
'No account here signs in with this GitHub account yet. To keep '
|
||||
'your progress, set up a password instead.',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text("Couldn't sign in with GitHub."), findsNothing);
|
||||
expect(
|
||||
find.text('Progress is saved on this device. Sign in to keep it.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('a GitHub account that signs in with its password now is '
|
||||
'said so', (tester) async {
|
||||
auth
|
||||
..gitHubLinkOutcome = GitHubLinkOutcome.takenByAnotherPerson
|
||||
..gitHubSwitchError = const GitHubSignInFailed(
|
||||
GitHubSignInProblem.usePassword,
|
||||
);
|
||||
await openProfile(tester);
|
||||
|
||||
await tester.tap(find.text('Sign in with GitHub'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.text(
|
||||
'This account signs in with its email address and password now, '
|
||||
'not with GitHub.',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(auth.gitHubSwitches, 0);
|
||||
});
|
||||
});
|
||||
|
||||
group('someone identified', () {
|
||||
setUp(() {
|
||||
auth = FakeAuthGateway(
|
||||
status: const SignedIn(authUserId: 'ada', isIdentified: true),
|
||||
);
|
||||
auth.addressOwners['ada@example.org'] = 'ada';
|
||||
});
|
||||
|
||||
testWidgets('is offered to add GitHub where the instance adds it', (
|
||||
tester,
|
||||
) async {
|
||||
await openProfile(tester);
|
||||
|
||||
expect(find.text('Add GitHub'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('is offered no GitHub to add where people sign in with a '
|
||||
'password (ADR 0017): the instance would refuse it', (tester) async {
|
||||
auth.linksGitHub = false;
|
||||
|
||||
await openProfile(tester);
|
||||
|
||||
expect(find.text('Add GitHub'), findsNothing);
|
||||
expect(find.text('GitHub'), findsNothing);
|
||||
expect(find.text('Email address'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('who has GitHub there sees it signs them in, with nothing to '
|
||||
'add', (tester) async {
|
||||
auth
|
||||
..linksGitHub = false
|
||||
..gitHubPeople.add('ada');
|
||||
|
||||
await openProfile(tester);
|
||||
|
||||
expect(find.text('GitHub'), findsOneWidget);
|
||||
expect(find.text('Signs you in'), findsWidgets);
|
||||
expect(find.text('Add GitHub'), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -7,8 +7,8 @@ import 'package:material_ui/material_ui.dart';
|
|||
import '../../../support/pump_app.dart';
|
||||
import '../../../support/test_dependencies.dart';
|
||||
|
||||
/// The profile's way to keep an account with a password and an
|
||||
/// authenticator (ADR 0017), as halkyon_app's section shows it in Learn.
|
||||
/// The profile's way to set up a password and an authenticator (ADR 0017),
|
||||
/// as halkyon_app's section shows it in Learn.
|
||||
void main() {
|
||||
late FakeAuthGateway auth;
|
||||
late FakePasswordGateway passwords;
|
||||
|
|
@ -47,8 +47,8 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets('an anonymous person keeps their account with a password: the '
|
||||
'address, the phrase checked, the code, the authenticator', (
|
||||
testWidgets('an account without a password sets one up: the address, the '
|
||||
'phrase checked, the code, the authenticator', (
|
||||
tester,
|
||||
) async {
|
||||
await openProfile(tester);
|
||||
|
|
@ -87,12 +87,9 @@ void main() {
|
|||
await next(tester, 'Continue');
|
||||
await next(tester, "I've kept them");
|
||||
|
||||
expect(
|
||||
auth.status,
|
||||
const SignedIn(authUserId: 'anonymous-1', isIdentified: true),
|
||||
);
|
||||
expect(auth.status, FakeAuthGateway.person);
|
||||
expect(find.text('Your progress is kept with your account.'), findsOne);
|
||||
expect(keys.kept.keys, ['anonymous-1']);
|
||||
expect(keys.kept.keys, [FakeAuthGateway.person.authUserId]);
|
||||
expect(passwords.accounts['anna@example.org']!.finished, isTrue);
|
||||
await tester.dragUntilVisible(
|
||||
find.text('Change password'),
|
||||
|
|
@ -130,6 +127,5 @@ void main() {
|
|||
await openProfile(tester);
|
||||
|
||||
expect(find.text('Set up a password'), findsNothing);
|
||||
expect(find.text('Sign in with GitHub'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_app/testing.dart';
|
||||
|
||||
import '../../../support/pump_app.dart';
|
||||
import '../../../support/test_dependencies.dart';
|
||||
|
||||
// The Profile tab's account card: the ways the person signs in, SimpleLogin
|
||||
// to add (ADR 0020, D21) and nothing else to add, as GitHub is added to
|
||||
// nobody any more (D25).
|
||||
void main() {
|
||||
late FakeAuthGateway auth;
|
||||
late FakeSimpleLoginGateway simpleLogin;
|
||||
|
||||
setUp(() {
|
||||
auth = FakeAuthGateway();
|
||||
simpleLogin = FakeSimpleLoginGateway(auth)..isOffered = false;
|
||||
});
|
||||
|
||||
Future<void> openProfile(WidgetTester tester) async {
|
||||
await pumpApp(
|
||||
tester,
|
||||
testDependencies(authGateway: auth, simpleLoginGateway: simpleLogin),
|
||||
);
|
||||
await openTab(tester, 'Profile');
|
||||
}
|
||||
|
||||
testWidgets('says the progress is kept with the account', (tester) async {
|
||||
await openProfile(tester);
|
||||
|
||||
expect(
|
||||
find.text('Your progress is kept with your account.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('an account with GitHub sees it signs them in, with nothing to '
|
||||
'add', (tester) async {
|
||||
auth.gitHubPeople.add(FakeAuthGateway.person.authUserId);
|
||||
|
||||
await openProfile(tester);
|
||||
|
||||
expect(find.text('GitHub'), findsOneWidget);
|
||||
expect(find.text('Signs you in'), findsWidgets);
|
||||
expect(find.text('Add GitHub'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('an account without GitHub is offered none to add (D25)', (
|
||||
tester,
|
||||
) async {
|
||||
auth.addressOwners['ada@example.org'] = FakeAuthGateway.person.authUserId;
|
||||
|
||||
await openProfile(tester);
|
||||
|
||||
expect(find.text('GitHub'), findsNothing);
|
||||
expect(find.text('Add GitHub'), findsNothing);
|
||||
expect(find.text('Sign in with GitHub'), findsNothing);
|
||||
expect(find.text('Email address'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('SimpleLogin can be added where the instance offers it', (
|
||||
tester,
|
||||
) async {
|
||||
simpleLogin.isOffered = true;
|
||||
|
||||
await openProfile(tester);
|
||||
|
||||
expect(find.text('Add SimpleLogin'), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('and is not mentioned where it does not', (tester) async {
|
||||
await openProfile(tester);
|
||||
|
||||
expect(find.textContaining('SimpleLogin'), findsNothing);
|
||||
});
|
||||
}
|
||||
|
|
@ -44,112 +44,58 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets('shows the app once signed in anonymously', (tester) async {
|
||||
await showApp(tester);
|
||||
|
||||
expect(home, findsOneWidget);
|
||||
expect(gateway.signIns, 1);
|
||||
});
|
||||
|
||||
testWidgets('offers to try again when signing in fails', (tester) async {
|
||||
gateway.signInError = Exception('No connection');
|
||||
await showApp(tester);
|
||||
expect(find.text("Couldn't reach the server."), findsOneWidget);
|
||||
|
||||
gateway.signInError = null;
|
||||
await tester.tap(find.text('Try again'));
|
||||
/// Signs out from Settings, which asks how to start.
|
||||
Future<void> signOut(WidgetTester tester, {bool passwords = true}) async {
|
||||
await openSettings(tester, passwords: passwords);
|
||||
await tester.tap(find.text('Sign out'));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets('shows the app of whoever is signed in', (tester) async {
|
||||
await showApp(tester);
|
||||
|
||||
expect(home, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('the profile explains that progress lives on this device', (
|
||||
testWidgets('an anonymous session left from before is deleted, and the '
|
||||
'app asks how to start (ADR 0020, D25)', (tester) async {
|
||||
gateway = FakeAuthGateway(
|
||||
status: const SignedIn(authUserId: 'anonymous-1', isIdentified: false),
|
||||
);
|
||||
|
||||
await showApp(tester);
|
||||
|
||||
expect(gateway.accountDeletions, 1);
|
||||
expect(gateway.status, const SignedOut());
|
||||
expect(find.text('Start on learn.example.org:'), findsOneWidget);
|
||||
expect(home, findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Settings says on which instance the account is signed in', (
|
||||
tester,
|
||||
) async {
|
||||
await showApp(tester);
|
||||
|
||||
await openTab(tester, 'Profile');
|
||||
|
||||
expect(
|
||||
find.text('Progress is saved on this device. Sign in to keep it.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Settings says the account is anonymous, on which instance; '
|
||||
'one with GitHub, signed in', (tester) async {
|
||||
await openSettings(tester);
|
||||
expect(find.text('Anonymous on learn.example.org'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byTooltip('Back'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Sign in with GitHub'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byTooltip('Settings'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.scrollUntilVisible(
|
||||
find.text('Delete account'),
|
||||
200,
|
||||
scrollable: find.byType(Scrollable).first,
|
||||
);
|
||||
expect(find.text('Signed in on learn.example.org'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'signing out of an anonymous account asks first; Cancel keeps it',
|
||||
(tester) async {
|
||||
await openSettings(tester);
|
||||
testWidgets('signing out deletes nothing and asks how to start, offering '
|
||||
'no start without an account', (tester) async {
|
||||
await signOut(tester);
|
||||
|
||||
await tester.tap(find.text('Sign out'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Sign out?'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Cancel'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(gateway.signOuts, 0);
|
||||
expect(
|
||||
gateway.status,
|
||||
const SignedIn(authUserId: 'anonymous-1', isIdentified: false),
|
||||
);
|
||||
expect(find.text('Settings'), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('a confirmed sign-out asks how to start, making no account; '
|
||||
'starting without one starts anew at home', (tester) async {
|
||||
await openSettings(tester);
|
||||
await tester.tap(find.text('Sign out'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Sign out').last);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Signing out of an anonymous account deletes it.
|
||||
expect(gateway.accountDeletions, 1);
|
||||
expect(gateway.signOuts, 1);
|
||||
expect(gateway.accountDeletions, 0);
|
||||
expect(gateway.status, const SignedOut());
|
||||
expect(find.text('Signed out'), findsOneWidget);
|
||||
expect(find.text('Halkyon Learn'), findsOneWidget);
|
||||
expect(find.text('Start on learn.example.org:'), findsOneWidget);
|
||||
expect(find.text('Sign in with GitHub'), findsOneWidget);
|
||||
expect(find.text('Sign in'), findsOneWidget);
|
||||
expect(find.text('Create an account'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Start without an account'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
gateway.status,
|
||||
const SignedIn(authUserId: 'anonymous-2', isIdentified: false),
|
||||
);
|
||||
expect(home, findsOneWidget);
|
||||
expect(find.text('Sign in with GitHub'), findsOneWidget);
|
||||
expect(find.text('Start without an account'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('from there, GitHub signs in its person, making no account '
|
||||
'here', (tester) async {
|
||||
await openSettings(tester);
|
||||
await tester.tap(find.text('Sign out'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Sign out').last);
|
||||
await tester.pumpAndSettle();
|
||||
testWidgets('from there, GitHub signs in its person', (tester) async {
|
||||
await signOut(tester);
|
||||
|
||||
await tester.tap(find.text('Sign in with GitHub'));
|
||||
await tester.pumpAndSettle();
|
||||
|
|
@ -162,17 +108,12 @@ void main() {
|
|||
const SignedIn(authUserId: 'github-person', isIdentified: true),
|
||||
);
|
||||
expect(home, findsOneWidget);
|
||||
expect(gateway.signIns, 1);
|
||||
});
|
||||
|
||||
testWidgets('from there, a GitHub account nobody here signs in with is said '
|
||||
'so, with an account to create in its place where passwords are '
|
||||
'offered (ADR 0017)', (tester) async {
|
||||
await openSettings(tester);
|
||||
await tester.tap(find.text('Sign out'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Sign out').last);
|
||||
await tester.pumpAndSettle();
|
||||
await signOut(tester);
|
||||
|
||||
gateway.gitHubError = const GitHubSignInFailed(
|
||||
GitHubSignInProblem.unknownAccount,
|
||||
|
|
@ -182,7 +123,7 @@ void main() {
|
|||
|
||||
expect(
|
||||
find.text(
|
||||
'No account here signs in with this GitHub account yet. Create an '
|
||||
'No account here signs in with this GitHub account. Create an '
|
||||
'account with your email address and a password instead.',
|
||||
),
|
||||
findsOneWidget,
|
||||
|
|
@ -190,28 +131,15 @@ void main() {
|
|||
expect(gateway.status, const SignedOut());
|
||||
});
|
||||
|
||||
testWidgets('where no passwords are offered, with where Learn adds GitHub', (
|
||||
tester,
|
||||
) async {
|
||||
await openSettings(tester, passwords: false);
|
||||
await tester.tap(find.text('Sign out'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Sign out').last);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
gateway.gitHubError = const GitHubSignInFailed(
|
||||
GitHubSignInProblem.unknownAccount,
|
||||
);
|
||||
await tester.tap(find.text('Sign in with GitHub'));
|
||||
await tester.pumpAndSettle();
|
||||
testWidgets('where an instance offers no way to make an account, it says '
|
||||
'so', (tester) async {
|
||||
await signOut(tester, passwords: false);
|
||||
|
||||
expect(
|
||||
find.text(
|
||||
'No account here signs in with this GitHub account yet. Start '
|
||||
'without an account, then add GitHub in Profile.',
|
||||
),
|
||||
find.text('learn.example.org offers no way to create an account.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('Create an account'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('deleting the account asks first; Cancel keeps it', (
|
||||
|
|
@ -229,8 +157,7 @@ void main() {
|
|||
expect(find.text('Settings'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('a confirmed deletion asks how to start; starting without an '
|
||||
'account starts anew at home', (tester) async {
|
||||
testWidgets('a confirmed deletion asks how to start', (tester) async {
|
||||
await openSettings(tester);
|
||||
await tester.tap(find.text('Delete account'));
|
||||
await tester.pumpAndSettle();
|
||||
|
|
@ -239,13 +166,7 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
|
||||
expect(gateway.accountDeletions, 1);
|
||||
expect(find.text('Signed out'), findsOneWidget);
|
||||
await tester.tap(find.text('Start without an account'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
gateway.status,
|
||||
const SignedIn(authUserId: 'anonymous-2', isIdentified: false),
|
||||
);
|
||||
expect(home, findsOneWidget);
|
||||
expect(gateway.status, const SignedOut());
|
||||
expect(find.text('Start on learn.example.org:'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_app/testing.dart';
|
||||
|
||||
import '../../../support/pump_app.dart';
|
||||
import '../../../support/test_dependencies.dart';
|
||||
|
||||
// Where the instance offers SimpleLogin (ADR 0020, D21, D23): it alone keeps
|
||||
// an anonymous account.
|
||||
void main() {
|
||||
late FakeAuthGateway auth;
|
||||
late FakeSimpleLoginGateway simpleLogin;
|
||||
|
||||
setUp(() {
|
||||
auth = FakeAuthGateway();
|
||||
simpleLogin = FakeSimpleLoginGateway(auth);
|
||||
});
|
||||
|
||||
Future<void> start(WidgetTester tester) => pumpApp(
|
||||
tester,
|
||||
testDependencies(authGateway: auth, simpleLoginGateway: simpleLogin),
|
||||
);
|
||||
|
||||
testWidgets('an anonymous person is offered SimpleLogin alone, not GitHub '
|
||||
'(D23)', (tester) async {
|
||||
await start(tester);
|
||||
await openTab(tester, 'Profile');
|
||||
|
||||
expect(
|
||||
find.text(
|
||||
'Progress is saved on this device. Add SimpleLogin to keep it.',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('Sign in with GitHub'), findsNothing);
|
||||
expect(find.text('Add SimpleLogin'), findsWidgets);
|
||||
});
|
||||
|
||||
testWidgets('without SimpleLogin, GitHub keeps the account as before', (
|
||||
tester,
|
||||
) async {
|
||||
simpleLogin.isOffered = false;
|
||||
await start(tester);
|
||||
await openTab(tester, 'Profile');
|
||||
|
||||
expect(
|
||||
find.text('Progress is saved on this device. Sign in to keep it.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('Sign in with GitHub'), findsOneWidget);
|
||||
expect(find.textContaining('SimpleLogin'), findsNothing);
|
||||
});
|
||||
}
|
||||
|
|
@ -96,7 +96,6 @@ void main() {
|
|||
Future<void> showExplore(
|
||||
WidgetTester tester,
|
||||
FakeExploreGateway explore, {
|
||||
bool identified = true,
|
||||
FakeProfileGateway? profiles,
|
||||
FakeCardGateway? cards,
|
||||
}) async {
|
||||
|
|
@ -107,9 +106,9 @@ void main() {
|
|||
tester,
|
||||
testDependencies(
|
||||
authGateway: FakeAuthGateway(
|
||||
status: SignedIn(
|
||||
status: const SignedIn(
|
||||
authUserId: 'github-person',
|
||||
isIdentified: identified,
|
||||
isIdentified: true,
|
||||
),
|
||||
),
|
||||
studysetGateway: studysets,
|
||||
|
|
@ -157,16 +156,6 @@ void main() {
|
|||
expect(find.text('based on halkyon.eu/@tae/japanese'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('an anonymous person explores too', (tester) async {
|
||||
await showExplore(
|
||||
tester,
|
||||
FakeExploreGateway(published: [published('Japanese')]),
|
||||
identified: false,
|
||||
);
|
||||
|
||||
expect(find.text('Japanese'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('searches once typing pauses', (tester) async {
|
||||
final explore = FakeExploreGateway(
|
||||
published: [
|
||||
|
|
|
|||
|
|
@ -91,9 +91,8 @@ void main() {
|
|||
|
||||
Future<void> show(
|
||||
WidgetTester tester,
|
||||
String location, {
|
||||
bool identified = true,
|
||||
}) async {
|
||||
String location,
|
||||
) async {
|
||||
tester.view.physicalSize = const Size(1200, 1600);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
|
|
@ -101,9 +100,9 @@ void main() {
|
|||
tester,
|
||||
testDependencies(
|
||||
authGateway: FakeAuthGateway(
|
||||
status: SignedIn(
|
||||
status: const SignedIn(
|
||||
authUserId: 'github-person',
|
||||
isIdentified: identified,
|
||||
isIdentified: true,
|
||||
),
|
||||
),
|
||||
studysetGateway: studysets,
|
||||
|
|
@ -299,10 +298,4 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
expect(server.blocked, ['bob']);
|
||||
});
|
||||
|
||||
testWidgets('a person without an identity has no inbox', (tester) async {
|
||||
await show(tester, RoutePaths.profile, identified: false);
|
||||
|
||||
expect(find.text('Inbox'), findsNothing);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,12 +17,11 @@ import '../../support/pump_app.dart';
|
|||
import '../../support/test_dependencies.dart';
|
||||
|
||||
void main() {
|
||||
/// Opens the Profile tab of a person with an identity, whose profile the
|
||||
/// instance answers through [profiles].
|
||||
/// Opens the Profile tab of a person whose profile the instance answers
|
||||
/// through [profiles].
|
||||
Future<void> showProfile(
|
||||
WidgetTester tester,
|
||||
FakeProfileGateway profiles, {
|
||||
bool identified = true,
|
||||
FakeFilePicker? files,
|
||||
}) async {
|
||||
tester.view.physicalSize = const Size(1000, 1800);
|
||||
|
|
@ -32,9 +31,9 @@ void main() {
|
|||
tester,
|
||||
testDependencies(
|
||||
authGateway: FakeAuthGateway(
|
||||
status: SignedIn(
|
||||
status: const SignedIn(
|
||||
authUserId: 'github-person',
|
||||
isIdentified: identified,
|
||||
isIdentified: true,
|
||||
),
|
||||
),
|
||||
profileGateway: profiles,
|
||||
|
|
@ -267,16 +266,6 @@ void main() {
|
|||
expect(find.text('Choose picture'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('an anonymous person is told how to have one', (tester) async {
|
||||
await showProfile(tester, FakeProfileGateway(), identified: false);
|
||||
|
||||
expect(
|
||||
find.text('Sign in to have a public profile and publish.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.text('Public profile'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('without the instance, it says so and tries again', (
|
||||
tester,
|
||||
) async {
|
||||
|
|
|
|||
|
|
@ -58,13 +58,12 @@ void main() {
|
|||
parentId: parent,
|
||||
);
|
||||
|
||||
/// Opens the screen of the studyset [id] as a person with an identity
|
||||
/// ([identified]), whose profile is [profile], in a window [width] wide.
|
||||
/// Opens the screen of the studyset [id] as a person whose profile is
|
||||
/// [profile], in a window [width] wide.
|
||||
Future<void> showStudyset(
|
||||
WidgetTester tester,
|
||||
String id, {
|
||||
PublicProfile? profile = _profile,
|
||||
bool identified = true,
|
||||
double width = 1200,
|
||||
}) async {
|
||||
tester.view.physicalSize = Size(width, 1000);
|
||||
|
|
@ -74,9 +73,9 @@ void main() {
|
|||
tester,
|
||||
testDependencies(
|
||||
authGateway: FakeAuthGateway(
|
||||
status: SignedIn(
|
||||
status: const SignedIn(
|
||||
authUserId: 'github-person',
|
||||
isIdentified: identified,
|
||||
isIdentified: true,
|
||||
),
|
||||
),
|
||||
studysetGateway: studysets,
|
||||
|
|
@ -389,20 +388,6 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
testWidgets('an anonymous person is told how to publish, and the instance '
|
||||
"isn't asked", (tester) async {
|
||||
stored('japanese', 'Japanese', license: 'CC-BY-4.0');
|
||||
publishing.fail = Exception('must not be asked');
|
||||
await showStudyset(tester, 'japanese', identified: false);
|
||||
|
||||
await openSheet(tester);
|
||||
|
||||
expect(
|
||||
find.text('Sign in to have a public profile and publish.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('without the instance, it says so and tries again', (
|
||||
tester,
|
||||
) async {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ import 'dart:async';
|
|||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_app/halkyon_app.dart';
|
||||
import 'package:halkyon_app/testing.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/published_ref.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
|
||||
|
|
@ -41,13 +39,11 @@ void main() {
|
|||
/// How many syncs ran.
|
||||
var syncs = 0;
|
||||
|
||||
/// Shows the library in a window [width] wide; to a person with an
|
||||
/// identity ([identified]), whose studysets [publishing] publishes and
|
||||
/// [publishedStore] remembers as published.
|
||||
/// Shows the library in a window [width] wide, whose studysets
|
||||
/// [publishing] publishes and [publishedStore] remembers as published.
|
||||
Future<void> showLibrary(
|
||||
WidgetTester tester, {
|
||||
double width = 1200,
|
||||
bool identified = false,
|
||||
FakePublishGateway? publishing,
|
||||
MemoryPublishedStore? publishedStore,
|
||||
}) async {
|
||||
|
|
@ -58,14 +54,6 @@ void main() {
|
|||
await pumpApp(
|
||||
tester,
|
||||
testDependencies(
|
||||
authGateway: identified
|
||||
? FakeAuthGateway(
|
||||
status: const SignedIn(
|
||||
authUserId: 'github-person',
|
||||
isIdentified: true,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
studysetGateway: gateway,
|
||||
cardGateway: cardGateway,
|
||||
files: files,
|
||||
|
|
@ -810,7 +798,6 @@ void main() {
|
|||
|
||||
await showLibrary(
|
||||
tester,
|
||||
identified: true,
|
||||
publishing: FakePublishGateway(
|
||||
publications: {
|
||||
'japanese': publication(),
|
||||
|
|
@ -850,7 +837,6 @@ void main() {
|
|||
|
||||
await showLibrary(
|
||||
tester,
|
||||
identified: true,
|
||||
publishing: FakePublishGateway()..fail = Exception('Offline'),
|
||||
publishedStore: MemoryPublishedStore({'japanese'}),
|
||||
);
|
||||
|
|
@ -876,7 +862,7 @@ void main() {
|
|||
await tester.tap(find.text('Publish…'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.text('Sign in to have a public profile and publish.'),
|
||||
find.text('Not published · only you see it in Learn'),
|
||||
findsOneWidget,
|
||||
);
|
||||
await tester.tapAt(Offset.zero);
|
||||
|
|
@ -912,7 +898,6 @@ void main() {
|
|||
stored('japanese', 'Japanese');
|
||||
await showLibrary(
|
||||
tester,
|
||||
identified: true,
|
||||
publishing: FakePublishGateway(
|
||||
publications: {'japanese': publication()},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1870,9 +1870,12 @@ void main() {
|
|||
|
||||
expect(find.byType(StudysetScreen), findsNothing);
|
||||
expect(find.text('Japanese'), findsNothing);
|
||||
expect(find.text('Signed out'), findsOneWidget);
|
||||
expect(find.text('Start on learn.example.org:'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Start without an account'));
|
||||
await tester.tap(find.text('Sign in with GitHub'));
|
||||
await tester.pumpAndSettle();
|
||||
// A password is offered first (ADR 0017, D2); put off, the app.
|
||||
await tester.tap(find.text('Later'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Japanese'), findsNothing);
|
||||
|
|
|
|||
Loading…
Reference in a new issue