feat: color a site from its address, and offer a failed web page to the system browser
This commit is contained in:
parent
56db479df0
commit
9d9f09ebc7
24 changed files with 597 additions and 25 deletions
|
|
@ -133,6 +133,31 @@ void main() {
|
|||
expect(find.textContaining('Try again'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('a web page that will not load can open in a web browser',
|
||||
(tester) async {
|
||||
final stubDeps = AppDependencies(
|
||||
database: MewsDatabase.test(NativeDatabase.memory()),
|
||||
fetch: FakeFetch(const {}),
|
||||
strings: strings,
|
||||
);
|
||||
await tester.pumpWidget(MewsApp(deps: stubDeps, strings: strings));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// A web address that answers nothing: the error offers the way out,
|
||||
// since the web can be shown outside this reader.
|
||||
await stubDeps.browser.open('https://missing.example.org/');
|
||||
await tester.pumpAndSettle();
|
||||
expect(stubDeps.browser.error.value, isA<LoadFailed>());
|
||||
expect(find.text('Open in a web browser'), findsOneWidget);
|
||||
|
||||
// A protocol page has nowhere else to go, so only trying again is
|
||||
// offered there.
|
||||
await stubDeps.browser.open('gemini://missing.example.org/');
|
||||
await tester.pumpAndSettle();
|
||||
expect(stubDeps.browser.error.value, isA<LoadFailed>());
|
||||
expect(find.text('Open in a web browser'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('the hub opens the settings page, which applies a choice in place',
|
||||
(tester) async {
|
||||
await pumpApp(tester);
|
||||
|
|
|
|||
|
|
@ -77,6 +77,17 @@ void main() {
|
|||
expect(await db.allPins(), {'capsule.example:1965': presented});
|
||||
});
|
||||
|
||||
test('a failed load keeps the address the reader followed', () async {
|
||||
final controller = browser(FakeFetch(const {}));
|
||||
|
||||
// Neither the address nor its wap. subdomain answers; the wap. guess
|
||||
// is not left standing in for what the reader followed.
|
||||
await controller.open('https://capsule.example/post');
|
||||
|
||||
expect(controller.error.value, isA<LoadFailed>());
|
||||
expect(controller.currentAddress, 'https://capsule.example/post');
|
||||
});
|
||||
|
||||
test('a certificate request is answered by binding an identity', () async {
|
||||
final controller = browser(FakeFetch(
|
||||
{'gemini://capsule.example': '# Hello\n\nText.'},
|
||||
|
|
|
|||
85
app/test/site_palette_test.dart
Normal file
85
app/test/site_palette_test.dart
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mews/mews/document.dart';
|
||||
import 'package:mews/presentation/reader/oklch.dart';
|
||||
import 'package:mews/presentation/reader/palettes.dart';
|
||||
import 'package:mews/presentation/reader/site_palette.dart';
|
||||
|
||||
/// A wide sample of what the generator can produce: enough hosts to cover all
|
||||
/// 24 hues, 3 background tints and both foregrounds.
|
||||
Iterable<MewsPalette> _allPalettes(bool dark) => [
|
||||
for (var i = 0; i < 2000; i++) sitePalette('gemini://h$i.example/', dark: dark)!,
|
||||
];
|
||||
|
||||
void main() {
|
||||
group('seed', () {
|
||||
test('comes from the host', () {
|
||||
expect(siteSeed('gemini://example.org/page'), 'example.org');
|
||||
expect(siteSeed('gemini://Example.ORG/page'), 'example.org');
|
||||
});
|
||||
|
||||
test('names the user on a personal page', () {
|
||||
expect(siteSeed('gemini://example.org/~alice/'), 'alice@example.org');
|
||||
expect(siteSeed('gemini://example.org/users/alice/x.gmi'), 'alice@example.org');
|
||||
expect(siteSeed('gemini://example.org/Users/Alice'), 'alice@example.org');
|
||||
});
|
||||
|
||||
test('is empty without a host', () {
|
||||
expect(siteSeed('mews:settings'), '');
|
||||
expect(siteSeed('not a url'), '');
|
||||
});
|
||||
});
|
||||
|
||||
group('palette', () {
|
||||
test('is the same every time for one address', () {
|
||||
expect(sitePalette('gemini://example.org/a', dark: false),
|
||||
same(sitePalette('gemini://example.org/b', dark: false)));
|
||||
});
|
||||
|
||||
test('differs between two users on one host', () {
|
||||
final alice = sitePalette('gemini://example.org/~alice/', dark: false)!;
|
||||
final bob = sitePalette('gemini://example.org/~bob/', dark: false)!;
|
||||
expect(alice.link, isNot(bob.link));
|
||||
});
|
||||
|
||||
test('is null for an address with no host', () {
|
||||
expect(sitePalette('mews:settings', dark: false), isNull);
|
||||
});
|
||||
|
||||
test('falls back to plain when the reader asks for host colors', () {
|
||||
expect(
|
||||
paletteFor(
|
||||
pageVariant: MewsVariant.plain,
|
||||
readerVariant: 'host',
|
||||
dark: false,
|
||||
url: 'mews:settings'),
|
||||
paletteFor(pageVariant: MewsVariant.plain, readerVariant: 'plain', dark: false),
|
||||
);
|
||||
});
|
||||
|
||||
// Spec 5.1: every text and link color meets 7:1 against its background.
|
||||
for (final dark in [false, true]) {
|
||||
test('meets 7:1 contrast in ${dark ? 'dark' : 'light'} mode', () {
|
||||
for (final palette in _allPalettes(dark)) {
|
||||
expect(contrastRatio(palette.background, palette.foreground),
|
||||
greaterThanOrEqualTo(7.0));
|
||||
expect(contrastRatio(palette.background, palette.link),
|
||||
greaterThanOrEqualTo(7.0));
|
||||
expect(contrastRatio(palette.background, palette.visited),
|
||||
greaterThanOrEqualTo(7.0));
|
||||
}
|
||||
});
|
||||
|
||||
test('stays no louder than the fixed palettes in ${dark ? 'dark' : 'light'} mode', () {
|
||||
// Cool holds the most saturated link of the fixed palettes, so no
|
||||
// generated color may pass it.
|
||||
final cool = paletteFor(
|
||||
pageVariant: MewsVariant.cool, readerVariant: 'cool', dark: dark);
|
||||
final limit = chromaOf(cool.link);
|
||||
for (final palette in _allPalettes(dark)) {
|
||||
expect(chromaOf(palette.link), lessThanOrEqualTo(limit));
|
||||
expect(chromaOf(palette.background), lessThanOrEqualTo(0.03));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue