mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
merge: remote-tracking branch 'immich/main' into feat/integrity-checks-izzy
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/domain/services/remote_album.service.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
@@ -76,42 +77,42 @@ void main() {
|
||||
test('should sort correctly based on name', () async {
|
||||
final albums = [albumB, albumA];
|
||||
|
||||
final result = await sut.sortAlbums(albums, RemoteAlbumSortMode.title);
|
||||
final result = await sut.sortAlbums(albums, AlbumSortMode.title);
|
||||
expect(result, [albumA, albumB]);
|
||||
});
|
||||
|
||||
test('should sort correctly based on createdAt', () async {
|
||||
final albums = [albumB, albumA];
|
||||
|
||||
final result = await sut.sortAlbums(albums, RemoteAlbumSortMode.created);
|
||||
final result = await sut.sortAlbums(albums, AlbumSortMode.created);
|
||||
expect(result, [albumA, albumB]);
|
||||
});
|
||||
|
||||
test('should sort correctly based on updatedAt', () async {
|
||||
final albums = [albumB, albumA];
|
||||
|
||||
final result = await sut.sortAlbums(albums, RemoteAlbumSortMode.lastModified);
|
||||
final result = await sut.sortAlbums(albums, AlbumSortMode.lastModified);
|
||||
expect(result, [albumA, albumB]);
|
||||
});
|
||||
|
||||
test('should sort correctly based on assetCount', () async {
|
||||
final albums = [albumB, albumA];
|
||||
|
||||
final result = await sut.sortAlbums(albums, RemoteAlbumSortMode.assetCount);
|
||||
final result = await sut.sortAlbums(albums, AlbumSortMode.assetCount);
|
||||
expect(result, [albumA, albumB]);
|
||||
});
|
||||
|
||||
test('should sort correctly based on newestAssetTimestamp', () async {
|
||||
final albums = [albumB, albumA];
|
||||
|
||||
final result = await sut.sortAlbums(albums, RemoteAlbumSortMode.mostRecent);
|
||||
final result = await sut.sortAlbums(albums, AlbumSortMode.mostRecent);
|
||||
expect(result, [albumA, albumB]);
|
||||
});
|
||||
|
||||
test('should sort correctly based on oldestAssetTimestamp', () async {
|
||||
final albums = [albumB, albumA];
|
||||
|
||||
final result = await sut.sortAlbums(albums, RemoteAlbumSortMode.mostOldest);
|
||||
final result = await sut.sortAlbums(albums, AlbumSortMode.mostOldest);
|
||||
expect(result, [albumB, albumA]);
|
||||
});
|
||||
});
|
||||
|
||||
+4
-1
@@ -16,6 +16,7 @@ import 'schema_v10.dart' as v10;
|
||||
import 'schema_v11.dart' as v11;
|
||||
import 'schema_v12.dart' as v12;
|
||||
import 'schema_v13.dart' as v13;
|
||||
import 'schema_v14.dart' as v14;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
@@ -47,10 +48,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
return v12.DatabaseAtV12(db);
|
||||
case 13:
|
||||
return v13.DatabaseAtV13(db);
|
||||
case 14:
|
||||
return v14.DatabaseAtV14(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
}
|
||||
|
||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
|
||||
}
|
||||
|
||||
+7878
File diff suppressed because it is too large
Load Diff
@@ -77,15 +77,15 @@ void main() {
|
||||
overrides: overrides,
|
||||
);
|
||||
|
||||
expect(find.widgetWithIcon(IconButton, Icons.favorite_rounded), findsOneWidget);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.favorite_border_rounded), findsNothing);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.thumb_up), findsOneWidget);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.thumb_up_off_alt), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Bordered icon if likedId == null', (tester) async {
|
||||
await tester.pumpConsumerWidget(ActivityTextField(onSubmit: (_) {}), overrides: overrides);
|
||||
|
||||
expect(find.widgetWithIcon(IconButton, Icons.favorite_border_rounded), findsOneWidget);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.favorite_rounded), findsNothing);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.thumb_up_off_alt), findsOneWidget);
|
||||
expect(find.widgetWithIcon(IconButton, Icons.thumb_up), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Adds new like', (tester) async {
|
||||
|
||||
@@ -91,17 +91,17 @@ void main() {
|
||||
group('Like Activity', () {
|
||||
final activity = Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin);
|
||||
|
||||
testWidgets('Like contains filled heart as leading', (tester) async {
|
||||
testWidgets('Like contains filled thumbs-up as leading', (tester) async {
|
||||
await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides);
|
||||
|
||||
// Leading widget should not be null
|
||||
final listTile = tester.widget<ListTile>(find.byType(ListTile));
|
||||
expect(listTile.leading, isNotNull);
|
||||
|
||||
// And should have a favorite icon
|
||||
final favoIconFinder = find.widgetWithIcon(listTile.leading!.runtimeType, Icons.favorite_rounded);
|
||||
// And should have a thumb_up icon
|
||||
final thumbUpIconFinder = find.widgetWithIcon(listTile.leading!.runtimeType, Icons.thumb_up);
|
||||
|
||||
expect(favoIconFinder, findsOneWidget);
|
||||
expect(thumbUpIconFinder, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Like title is center aligned', (tester) async {
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/utils/timezone.dart';
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
|
||||
void main() {
|
||||
setUpAll(() {
|
||||
tz.initializeTimeZones();
|
||||
});
|
||||
|
||||
group('applyTimezoneOffset', () {
|
||||
group('with named timezone locations', () {
|
||||
test('should convert UTC to Asia/Hong_Kong (+08:00)', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'Asia/Hong_Kong',
|
||||
);
|
||||
|
||||
expect(adjustedTime.hour, 20); // 12:00 UTC + 8 hours = 20:00
|
||||
expect(offset, const Duration(hours: 8));
|
||||
});
|
||||
|
||||
test('should convert UTC to America/New_York (handles DST)', () {
|
||||
// Summer time (EDT = UTC-4)
|
||||
final summerUtc = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
final (summerTime, summerOffset) = applyTimezoneOffset(
|
||||
dateTime: summerUtc,
|
||||
timeZone: 'America/New_York',
|
||||
);
|
||||
|
||||
expect(summerTime.hour, 8); // 12:00 UTC - 4 hours = 08:00
|
||||
expect(summerOffset, const Duration(hours: -4));
|
||||
|
||||
// Winter time (EST = UTC-5)
|
||||
final winterUtc = DateTime.utc(2024, 1, 15, 12, 0, 0);
|
||||
final (winterTime, winterOffset) = applyTimezoneOffset(
|
||||
dateTime: winterUtc,
|
||||
timeZone: 'America/New_York',
|
||||
);
|
||||
|
||||
expect(winterTime.hour, 7); // 12:00 UTC - 5 hours = 07:00
|
||||
expect(winterOffset, const Duration(hours: -5));
|
||||
});
|
||||
|
||||
test('should convert UTC to Europe/London', () {
|
||||
// Winter (GMT = UTC+0)
|
||||
final winterUtc = DateTime.utc(2024, 1, 15, 12, 0, 0);
|
||||
final (winterTime, winterOffset) = applyTimezoneOffset(
|
||||
dateTime: winterUtc,
|
||||
timeZone: 'Europe/London',
|
||||
);
|
||||
|
||||
expect(winterTime.hour, 12);
|
||||
expect(winterOffset, Duration.zero);
|
||||
|
||||
// Summer (BST = UTC+1)
|
||||
final summerUtc = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
final (summerTime, summerOffset) = applyTimezoneOffset(
|
||||
dateTime: summerUtc,
|
||||
timeZone: 'Europe/London',
|
||||
);
|
||||
|
||||
expect(summerTime.hour, 13);
|
||||
expect(summerOffset, const Duration(hours: 1));
|
||||
});
|
||||
|
||||
test('should handle timezone with 30-minute offset (Asia/Kolkata)', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'Asia/Kolkata',
|
||||
);
|
||||
|
||||
expect(adjustedTime.hour, 17);
|
||||
expect(adjustedTime.minute, 30); // 12:00 UTC + 5:30 = 17:30
|
||||
expect(offset, const Duration(hours: 5, minutes: 30));
|
||||
});
|
||||
|
||||
test('should handle timezone with 45-minute offset (Asia/Kathmandu)', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'Asia/Kathmandu',
|
||||
);
|
||||
|
||||
expect(adjustedTime.hour, 17);
|
||||
expect(adjustedTime.minute, 45); // 12:00 UTC + 5:45 = 17:45
|
||||
expect(offset, const Duration(hours: 5, minutes: 45));
|
||||
});
|
||||
});
|
||||
|
||||
group('with UTC offset format', () {
|
||||
test('should handle UTC+08:00 format', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'UTC+08:00',
|
||||
);
|
||||
|
||||
expect(adjustedTime.hour, 20);
|
||||
expect(offset, const Duration(hours: 8));
|
||||
});
|
||||
|
||||
test('should handle UTC-05:00 format', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'UTC-05:00',
|
||||
);
|
||||
|
||||
expect(adjustedTime.hour, 7);
|
||||
expect(offset, const Duration(hours: -5));
|
||||
});
|
||||
|
||||
test('should handle UTC+8 format (without minutes)', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'UTC+8',
|
||||
);
|
||||
|
||||
expect(adjustedTime.hour, 20);
|
||||
expect(offset, const Duration(hours: 8));
|
||||
});
|
||||
|
||||
test('should handle UTC-5 format (without minutes)', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'UTC-5',
|
||||
);
|
||||
|
||||
expect(adjustedTime.hour, 7);
|
||||
expect(offset, const Duration(hours: -5));
|
||||
});
|
||||
|
||||
test('should handle plain UTC format', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'UTC',
|
||||
);
|
||||
|
||||
expect(adjustedTime.hour, 12);
|
||||
expect(offset, Duration.zero);
|
||||
});
|
||||
|
||||
test('should handle lowercase utc format', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'utc+08:00',
|
||||
);
|
||||
|
||||
expect(adjustedTime.hour, 20);
|
||||
expect(offset, const Duration(hours: 8));
|
||||
});
|
||||
|
||||
test('should handle UTC+05:30 format (with minutes)', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'UTC+05:30',
|
||||
);
|
||||
|
||||
expect(adjustedTime.hour, 17);
|
||||
expect(adjustedTime.minute, 30);
|
||||
expect(offset, const Duration(hours: 5, minutes: 30));
|
||||
});
|
||||
});
|
||||
|
||||
group('with null or invalid timezone', () {
|
||||
test('should return UTC time when timezone is null', () {
|
||||
final localTime = DateTime(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: localTime,
|
||||
timeZone: null,
|
||||
);
|
||||
|
||||
expect(adjustedTime.isUtc, true);
|
||||
expect(offset, adjustedTime.timeZoneOffset);
|
||||
});
|
||||
|
||||
test('should return UTC time when timezone is invalid', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'Invalid/Timezone',
|
||||
);
|
||||
|
||||
expect(adjustedTime.isUtc, true);
|
||||
expect(adjustedTime.hour, 12);
|
||||
expect(offset, adjustedTime.timeZoneOffset);
|
||||
});
|
||||
|
||||
test('should return UTC time when UTC offset format is malformed', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'UTC++08',
|
||||
);
|
||||
|
||||
expect(adjustedTime.isUtc, true);
|
||||
expect(adjustedTime.hour, 12);
|
||||
});
|
||||
});
|
||||
|
||||
group('edge cases', () {
|
||||
test('should handle date crossing midnight forward', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 20, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'Asia/Tokyo', // UTC+9
|
||||
);
|
||||
|
||||
expect(adjustedTime.day, 16); // Crosses to next day
|
||||
expect(adjustedTime.hour, 5); // 20:00 UTC + 9 = 05:00 next day
|
||||
expect(offset, const Duration(hours: 9));
|
||||
});
|
||||
|
||||
test('should handle date crossing midnight backward', () {
|
||||
final utcTime = DateTime.utc(2024, 6, 15, 3, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'America/Los_Angeles', // UTC-7 in summer
|
||||
);
|
||||
|
||||
expect(adjustedTime.day, 14); // Crosses to previous day
|
||||
expect(adjustedTime.hour, 20); // 03:00 UTC - 7 = 20:00 previous day
|
||||
expect(offset, const Duration(hours: -7));
|
||||
});
|
||||
|
||||
test('should handle year boundary crossing', () {
|
||||
final utcTime = DateTime.utc(2024, 1, 1, 2, 0, 0);
|
||||
|
||||
final (adjustedTime, offset) = applyTimezoneOffset(
|
||||
dateTime: utcTime,
|
||||
timeZone: 'America/New_York', // UTC-5 in winter
|
||||
);
|
||||
|
||||
expect(adjustedTime.year, 2023);
|
||||
expect(adjustedTime.month, 12);
|
||||
expect(adjustedTime.day, 31);
|
||||
expect(adjustedTime.hour, 21); // 02:00 UTC - 5 = 21:00 Dec 31
|
||||
});
|
||||
|
||||
test('should convert local time to UTC before applying timezone', () {
|
||||
// Create a local time (not UTC)
|
||||
final localTime = DateTime(2024, 6, 15, 12, 0, 0);
|
||||
|
||||
final (adjustedTime, _) = applyTimezoneOffset(
|
||||
dateTime: localTime,
|
||||
timeZone: 'Asia/Hong_Kong',
|
||||
);
|
||||
|
||||
// The function converts to UTC first, then applies timezone
|
||||
// So local 12:00 -> UTC (depends on local timezone) -> HK time
|
||||
// We can verify it's working by checking it's a TZDateTime
|
||||
expect(adjustedTime, isNotNull);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user