feat(apps): 重构 UI 架构为 presentation 层并新增 l10n 国际化支持
This commit is contained in:
@@ -1,130 +0,0 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:social_app/features/settings/data/models/automation_job_model.dart';
|
||||
|
||||
void main() {
|
||||
group('AutomationJobConfigModel', () {
|
||||
test('fromJson parses schedule correctly', () {
|
||||
final json = {
|
||||
'input_template': 'Hello {{name}}',
|
||||
'enabled_tools': ['tool1', 'tool2'],
|
||||
'context': {
|
||||
'source': 'latest_chat',
|
||||
'window_mode': 'day',
|
||||
'window_count': 5,
|
||||
},
|
||||
'schedule': {
|
||||
'type': 'weekly',
|
||||
'run_at': {'hour': 9, 'minute': 30},
|
||||
'weekdays': [1, 3, 5],
|
||||
},
|
||||
};
|
||||
|
||||
final model = AutomationJobConfigModel.fromJson(json);
|
||||
|
||||
expect(model.schedule.type, 'weekly');
|
||||
expect(model.schedule.runAt.hour, 9);
|
||||
expect(model.schedule.runAt.minute, 30);
|
||||
expect(model.schedule.weekdays, [1, 3, 5]);
|
||||
});
|
||||
});
|
||||
|
||||
group('AutomationJobModel', () {
|
||||
test('fromJson parses all fields correctly', () {
|
||||
final json = {
|
||||
'id': 'job-123',
|
||||
'owner_id': 'user-456',
|
||||
'bootstrap_key': 'key-789',
|
||||
'title': 'Daily Report',
|
||||
'timezone': 'America/New_York',
|
||||
'status': 'ACTIVE',
|
||||
'is_system': false,
|
||||
'config': {
|
||||
'input_template': 'Hello',
|
||||
'enabled_tools': ['tool1'],
|
||||
'context': {
|
||||
'source': 'latest_chat',
|
||||
'window_mode': 'day',
|
||||
'window_count': 2,
|
||||
},
|
||||
'schedule': {
|
||||
'type': 'daily',
|
||||
'run_at': {'hour': 9, 'minute': 0},
|
||||
},
|
||||
},
|
||||
'next_run_at': '2024-01-15T09:00:00Z',
|
||||
'last_run_at': '2024-01-14T09:00:00Z',
|
||||
'created_at': '2024-01-01T00:00:00Z',
|
||||
'updated_at': '2024-01-14T12:00:00Z',
|
||||
};
|
||||
|
||||
final model = AutomationJobModel.fromJson(json);
|
||||
|
||||
expect(model.id, 'job-123');
|
||||
expect(model.ownerId, 'user-456');
|
||||
expect(model.title, 'Daily Report');
|
||||
expect(model.config.schedule.type, 'daily');
|
||||
expect(model.config.schedule.runAt.hour, 9);
|
||||
expect(model.timezone, 'America/New_York');
|
||||
expect(model.isDaily, isTrue);
|
||||
expect(model.isWeekly, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('AutomationJobCreateRequest', () {
|
||||
test('toJson serializes schedule under config', () {
|
||||
final request = AutomationJobCreateRequest(
|
||||
title: 'New Job',
|
||||
timezone: 'UTC',
|
||||
status: 'ACTIVE',
|
||||
config: AutomationJobConfigModel(
|
||||
inputTemplate: 'Hello',
|
||||
enabledTools: ['tool1'],
|
||||
context: MessageContextConfigModel(
|
||||
source: 'latest_chat',
|
||||
windowMode: 'day',
|
||||
windowCount: 2,
|
||||
),
|
||||
schedule: ScheduleConfigModel(
|
||||
type: 'daily',
|
||||
runAt: ScheduleRunAtModel(hour: 10, minute: 0),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final json = request.toJson();
|
||||
|
||||
expect(json['title'], 'New Job');
|
||||
expect(json['timezone'], 'UTC');
|
||||
expect(json['status'], 'ACTIVE');
|
||||
expect((json['config'] as Map<String, dynamic>)['schedule'], {
|
||||
'type': 'daily',
|
||||
'run_at': {'hour': 10, 'minute': 0},
|
||||
});
|
||||
expect(json.containsKey('run_at'), isFalse);
|
||||
expect(json.containsKey('schedule_type'), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('AutomationJobUpdateRequest', () {
|
||||
test('toJson includes schedule patch in config', () {
|
||||
final request = AutomationJobUpdateRequest(
|
||||
config: AutomationJobConfigPatchModel(
|
||||
schedule: ScheduleConfigModel(
|
||||
type: 'weekly',
|
||||
runAt: ScheduleRunAtModel(hour: 8, minute: 0),
|
||||
weekdays: [2, 4],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final json = request.toJson();
|
||||
final configJson = json['config'] as Map<String, dynamic>;
|
||||
|
||||
expect(configJson['schedule'], {
|
||||
'type': 'weekly',
|
||||
'run_at': {'hour': 8, 'minute': 0},
|
||||
'weekdays': [2, 4],
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:social_app/core/cache/cache_policy.dart';
|
||||
import 'package:social_app/core/cache/hybrid_cache_store.dart';
|
||||
import 'package:social_app/core/cache/memory_cache_store.dart';
|
||||
import 'package:social_app/core/cache/persistent_cache_store.dart';
|
||||
import 'package:social_app/features/settings/data/services/settings_user_cache.dart';
|
||||
import 'package:social_app/features/settings/data/services/user_profile_cache_repository.dart';
|
||||
import 'package:social_app/features/users/data/models/user_response.dart';
|
||||
|
||||
void main() {
|
||||
test('getProfile caches latest user in memory field', () async {
|
||||
var loadCalls = 0;
|
||||
final repository = UserProfileCacheRepository(
|
||||
store: HybridCacheStore(
|
||||
memory: MemoryCacheStore(),
|
||||
persistent: PersistentCacheStore(),
|
||||
),
|
||||
policy: const CachePolicy(
|
||||
softTtl: Duration(minutes: 2),
|
||||
hardTtl: Duration(minutes: 30),
|
||||
minRefreshInterval: Duration(minutes: 1),
|
||||
),
|
||||
remoteLoader: () async {
|
||||
loadCalls += 1;
|
||||
return const UserResponse(id: 'u1', username: 'first');
|
||||
},
|
||||
);
|
||||
final cache = SettingsUserCache(repository);
|
||||
|
||||
final first = await cache.getProfile();
|
||||
final second = await cache.getProfile();
|
||||
|
||||
expect(first.username, 'first');
|
||||
expect(second.username, 'first');
|
||||
expect(cache.cachedUser?.id, 'u1');
|
||||
expect(loadCalls, 1);
|
||||
});
|
||||
|
||||
test('invalidate clears memory cache', () {
|
||||
final repository = UserProfileCacheRepository(
|
||||
store: HybridCacheStore(
|
||||
memory: MemoryCacheStore(),
|
||||
persistent: PersistentCacheStore(),
|
||||
),
|
||||
remoteLoader: () async => const UserResponse(id: 'u1', username: 'first'),
|
||||
);
|
||||
final cache = SettingsUserCache(repository);
|
||||
|
||||
cache.set(const UserResponse(id: 'u1', username: 'first'));
|
||||
cache.invalidate();
|
||||
|
||||
expect(cache.cachedUser, isNull);
|
||||
});
|
||||
|
||||
test('set should update cached user immediately', () {
|
||||
final repository = UserProfileCacheRepository(
|
||||
store: HybridCacheStore(
|
||||
memory: MemoryCacheStore(),
|
||||
persistent: PersistentCacheStore(),
|
||||
),
|
||||
remoteLoader: () async => const UserResponse(id: 'u1', username: 'first'),
|
||||
);
|
||||
final cache = SettingsUserCache(repository);
|
||||
|
||||
cache.set(const UserResponse(id: 'u2', username: 'next'));
|
||||
|
||||
expect(cache.cachedUser?.id, 'u2');
|
||||
});
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:social_app/core/cache/cache_entry.dart';
|
||||
import 'package:social_app/core/cache/cache_policy.dart';
|
||||
import 'package:social_app/core/cache/hybrid_cache_store.dart';
|
||||
import 'package:social_app/core/cache/memory_cache_store.dart';
|
||||
import 'package:social_app/core/cache/persistent_cache_store.dart';
|
||||
import 'package:social_app/features/settings/data/services/user_profile_cache_repository.dart';
|
||||
import 'package:social_app/features/users/data/models/user_response.dart';
|
||||
|
||||
void main() {
|
||||
test(
|
||||
'repository should return persistent cache first then refresh in background',
|
||||
() async {
|
||||
final store = HybridCacheStore(
|
||||
memory: MemoryCacheStore(),
|
||||
persistent: PersistentCacheStore(),
|
||||
);
|
||||
const key = UserProfileCacheRepository.cacheKey;
|
||||
final stale = CacheEntry<UserResponse>(
|
||||
value: const UserResponse(id: 'u1', username: 'cached'),
|
||||
fetchedAt: DateTime(2026, 3, 20, 11, 0),
|
||||
);
|
||||
await store.persistent.write<CacheEntry<UserResponse>>(key, stale);
|
||||
|
||||
var refreshCalls = 0;
|
||||
final repository = UserProfileCacheRepository(
|
||||
store: store,
|
||||
now: () => DateTime(2026, 3, 20, 11, 5),
|
||||
policy: const CachePolicy(
|
||||
softTtl: Duration(minutes: 2),
|
||||
hardTtl: Duration(minutes: 30),
|
||||
minRefreshInterval: Duration(minutes: 1),
|
||||
),
|
||||
remoteLoader: () async {
|
||||
refreshCalls += 1;
|
||||
return const UserResponse(id: 'u1', username: 'remote');
|
||||
},
|
||||
);
|
||||
|
||||
final result = await repository.getProfile();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 10));
|
||||
|
||||
expect(result.username, 'cached');
|
||||
expect(refreshCalls, 1);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user