feat(apps): 重构 UI 架构为 presentation 层并新增 l10n 国际化支持

This commit is contained in:
qzl
2026-03-27 14:05:03 +08:00
parent b1f0eb8921
commit c592cc7854
178 changed files with 10748 additions and 5764 deletions
@@ -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);
},
);
}