Add provider tag suggestions and tag categories

This commit is contained in:
2026-05-23 15:41:23 +03:00
parent 1b763c4d62
commit b02c776628
30 changed files with 1119 additions and 77 deletions
+4
View File
@@ -65,6 +65,10 @@ void main() {
'gelbooru',
'rule34',
'safebooru',
'konachan',
'yandere',
'e621',
'e926',
]);
});
+1 -2
View File
@@ -155,8 +155,7 @@ void main() {
expect(result.data.map((provider) => provider.id), ['a', 'b']);
});
test('saved offline health does not block enabled provider retry',
() async {
test('saved offline health does not block enabled provider retry', () async {
final repository = FakeProviderRepository()
..configs['a'] = config('a', 0)
..configs['b'] = config('b', 1)
+90
View File
@@ -1,6 +1,8 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:gel_rule_app/backend/mappers/danbooru_mapper.dart';
import 'package:gel_rule_app/backend/mappers/e621_mapper.dart';
import 'package:gel_rule_app/backend/mappers/gelbooru_mapper.dart';
import 'package:gel_rule_app/backend/mappers/moebooru_mapper.dart';
import 'package:gel_rule_app/backend/mappers/rule34_mapper.dart';
void main() {
@@ -59,6 +61,24 @@ void main() {
expect(posts.single.fileType, 'video');
});
test('parses Rule34 gif with query and file_ext', () {
final posts = Rule34Mapper.postsFromResponse(
{
'posts': [
{
'id': '33',
'file_url': 'https://example.test/c.gif?download=1',
'file_ext': 'gif',
}
],
},
providerId: 'rule34',
providerName: 'Rule34',
);
expect(posts.single.fileType, 'gif');
});
test('parses Danbooru response', () {
final posts = DanbooruMapper.postsFromResponse(
[
@@ -81,6 +101,76 @@ void main() {
expect(posts.single.tags, ['blue', 'sky']);
});
test('parses Danbooru tag groups', () {
final posts = DanbooruMapper.postsFromResponse(
[
{
'id': 40,
'file_url': 'https://example.test/d.jpg',
'tag_string_general': 'blue sky',
'tag_string_artist': 'artist_name',
'tag_string_character': 'char_name',
}
],
providerId: 'danbooru',
providerName: 'Danbooru',
);
expect(posts.single.tagGroups['artist'], ['artist_name']);
expect(posts.single.tagGroups['character'], ['char_name']);
expect(posts.single.tagGroups['general'], ['blue', 'sky']);
});
test('parses Moebooru response through Danbooru-compatible mapper', () {
final posts = MoebooruMapper.postsFromResponse(
[
{
'id': 50,
'file_url': 'https://example.test/moe.png',
'tag_string': 'konachan_test',
}
],
providerId: 'konachan',
providerName: 'Konachan',
);
expect(posts.single.providerId, 'konachan');
expect(posts.single.tags, ['konachan_test']);
});
test('parses e621 response and tag groups', () {
final posts = E621Mapper.postsFromResponse(
{
'posts': [
{
'id': 60,
'file': {
'url': 'https://example.test/e.webm',
'width': 1280,
'height': 720,
'ext': 'webm',
},
'preview': {'url': 'https://example.test/e-preview.jpg'},
'sample': {'url': 'https://example.test/e-sample.jpg'},
'rating': 'e',
'score': {'total': 10},
'tags': {
'artist': ['artist_e'],
'species': ['wolf'],
'general': ['running'],
},
}
],
},
providerId: 'e621',
providerName: 'e621',
);
expect(posts.single.fileType, 'video');
expect(posts.single.rating, 'explicit');
expect(posts.single.tagGroups['species'], ['wolf']);
});
test('missing optional fields do not crash parser', () {
final posts = GelbooruMapper.postsFromResponse(
[
+3 -2
View File
@@ -52,12 +52,13 @@ void main() {
expect((result as Success<List<SearchHistory>>).data.first.query, 'new');
});
test('autocomplete uses history tags', () async {
test('autocomplete is provider-only and returns empty without manager',
() async {
await service.saveSearch('cat cute', 2);
await service.saveSearch('car', 1);
final result = await service.autocomplete('ca');
expect((result as Success<List<String>>).data, containsAll(['cat', 'car']));
expect((result as Success<List<String>>).data, isEmpty);
});
test('clear history removes all searches', () async {