implemented the Ticker module with a native ProseMirror document renderer and integrated API support for structured content, navigation trees, and proxied files
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:marianum_mobile/api/errors/ticker_content_unavailable_exception.dart';
|
||||
import 'package:marianum_mobile/api/marianumconnect/queries/get_ticker/get_ticker_response.dart';
|
||||
import 'package:marianum_mobile/api/marianumconnect/queries/get_ticker_nav/get_ticker_nav_response.dart';
|
||||
import 'package:marianum_mobile/api/marianumconnect/queries/get_ticker_page/get_ticker_page.dart';
|
||||
import 'package:marianum_mobile/api/marianumconnect/queries/get_ticker_page/get_ticker_page_response.dart';
|
||||
|
||||
void main() {
|
||||
group('TickerResponse.fromJson', () {
|
||||
test('available post carries the nested content object', () {
|
||||
final response = TickerResponse.fromJson({
|
||||
'schemaVersion': 1,
|
||||
'available': true,
|
||||
'hash': 'R123:uuid@2026-07-08T10:00:00',
|
||||
'publishedAt': '2026-07-08T10:00:00',
|
||||
'webUrl': '/ticker',
|
||||
'content': {
|
||||
'type': 'doc',
|
||||
'content': [
|
||||
{
|
||||
'type': 'paragraph',
|
||||
'content': [
|
||||
{'type': 'text', 'text': 'Hallo'},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.available, isTrue);
|
||||
expect(response.schemaVersion, 1);
|
||||
expect(response.webUrl, '/ticker');
|
||||
expect(response.content, isA<Map<String, dynamic>>());
|
||||
expect(response.content!['type'], 'doc');
|
||||
});
|
||||
|
||||
test('unavailable post keeps content null and defaults safely', () {
|
||||
final response = TickerResponse.fromJson({
|
||||
'schemaVersion': 1,
|
||||
'available': false,
|
||||
'webUrl': '/ticker',
|
||||
'content': null,
|
||||
});
|
||||
|
||||
expect(response.available, isFalse);
|
||||
expect(response.content, isNull);
|
||||
expect(response.hash, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('TickerNavResponse.fromJson', () {
|
||||
test('parses sections with per-kind fields', () {
|
||||
final nav = TickerNavResponse.fromJson({
|
||||
'schemaVersion': 1,
|
||||
'navHash': 'nav-abc',
|
||||
'sections': [
|
||||
{
|
||||
'title': 'Informationen',
|
||||
'pages': [
|
||||
{'title': 'Über uns', 'slug': 'ueber-uns', 'kind': 'CONTENT'},
|
||||
{
|
||||
'title': 'Webseite',
|
||||
'slug': 'web',
|
||||
'kind': 'REDIRECT',
|
||||
'externalUrl': 'https://marianum-fulda.de',
|
||||
'externalUrlNewTab': true,
|
||||
},
|
||||
{
|
||||
'title': 'Flyer',
|
||||
'slug': 'flyer',
|
||||
'kind': 'PROXIED_FILE',
|
||||
'fileUrl': 'ticker/pages/flyer/file',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(nav.navHash, 'nav-abc');
|
||||
expect(nav.sections, hasLength(1));
|
||||
final pages = nav.sections.single.pages;
|
||||
expect(pages.map((p) => p.kind), ['CONTENT', 'REDIRECT', 'PROXIED_FILE']);
|
||||
expect(pages[1].externalUrl, 'https://marianum-fulda.de');
|
||||
expect(pages[1].externalUrlNewTab, isTrue);
|
||||
expect(pages[2].fileUrl, 'ticker/pages/flyer/file');
|
||||
});
|
||||
|
||||
test('missing sections default to empty', () {
|
||||
final nav = TickerNavResponse.fromJson({
|
||||
'schemaVersion': 1,
|
||||
'navHash': '',
|
||||
});
|
||||
expect(nav.sections, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('TickerPageResponse.fromJson', () {
|
||||
test('CONTENT page exposes the nested document', () {
|
||||
final page = TickerPageResponse.fromJson({
|
||||
'schemaVersion': 1,
|
||||
'slug': 'ueber-uns',
|
||||
'title': 'Über uns',
|
||||
'kind': 'CONTENT',
|
||||
'content': {'type': 'doc', 'content': []},
|
||||
});
|
||||
expect(page.kind, TickerPageKind.content);
|
||||
expect(page.content, isNotNull);
|
||||
});
|
||||
|
||||
test('REDIRECT page exposes the external URL', () {
|
||||
final page = TickerPageResponse.fromJson({
|
||||
'schemaVersion': 1,
|
||||
'kind': 'REDIRECT',
|
||||
'externalUrl': 'https://marianum-fulda.de',
|
||||
});
|
||||
expect(page.kind, TickerPageKind.redirect);
|
||||
expect(page.externalUrl, 'https://marianum-fulda.de');
|
||||
});
|
||||
|
||||
test('PROXIED_FILE page exposes file metadata', () {
|
||||
final page = TickerPageResponse.fromJson({
|
||||
'schemaVersion': 1,
|
||||
'kind': 'PROXIED_FILE',
|
||||
'fileUrl': 'ticker/pages/flyer/file',
|
||||
'contentType': 'application/pdf',
|
||||
'filename': 'flyer.pdf',
|
||||
});
|
||||
expect(page.kind, TickerPageKind.proxiedFile);
|
||||
expect(page.contentType, 'application/pdf');
|
||||
expect(page.filename, 'flyer.pdf');
|
||||
});
|
||||
});
|
||||
|
||||
group('GetTickerPage error mapping', () {
|
||||
test('404 CONTENT_UNAVAILABLE maps to a typed exception with webUrl', () async {
|
||||
final requestOptions = RequestOptions(path: 'ticker/pages/foo');
|
||||
final dio = _ThrowingDio(
|
||||
DioException(
|
||||
requestOptions: requestOptions,
|
||||
type: DioExceptionType.badResponse,
|
||||
response: Response<dynamic>(
|
||||
requestOptions: requestOptions,
|
||||
statusCode: 404,
|
||||
data: {'error': 'CONTENT_UNAVAILABLE', 'webUrl': '/ticker/p/foo'},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
() => GetTickerPage('foo', dio: dio).run(),
|
||||
throwsA(
|
||||
isA<TickerContentUnavailableException>()
|
||||
.having((e) => e.webUrl, 'webUrl', '/ticker/p/foo')
|
||||
.having((e) => e.allowRetry, 'allowRetry', isFalse),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('other 404 (NOT_FOUND) does not become CONTENT_UNAVAILABLE', () async {
|
||||
final requestOptions = RequestOptions(path: 'ticker/pages/foo');
|
||||
final dio = _ThrowingDio(
|
||||
DioException(
|
||||
requestOptions: requestOptions,
|
||||
type: DioExceptionType.badResponse,
|
||||
response: Response<dynamic>(
|
||||
requestOptions: requestOptions,
|
||||
statusCode: 404,
|
||||
data: {'error': 'NOT_FOUND', 'webUrl': '/ticker/p/foo'},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
() => GetTickerPage('foo', dio: dio).run(),
|
||||
throwsA(isNot(isA<TickerContentUnavailableException>())),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Minimal fake Dio whose `get` always fails with the given exception; every
|
||||
/// other member is unused.
|
||||
class _ThrowingDio implements Dio {
|
||||
final DioException error;
|
||||
|
||||
_ThrowingDio(this.error);
|
||||
|
||||
@override
|
||||
Future<Response<T>> get<T>(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
CancelToken? cancelToken,
|
||||
ProgressCallback? onReceiveProgress,
|
||||
}) => Future<Response<T>>.error(error);
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) =>
|
||||
super.noSuchMethod(invocation);
|
||||
}
|
||||
Reference in New Issue
Block a user