feat: 实现 Auth 全局状态机与 401 统一处理机制
- 新增 AuthSessionInvalidated 事件处理 token 失效场景 - ApiInterceptor 新增 authFailureCallback 单飞机制 - AuthBloc 区分 manual logout 与 auto expiry 语义 - 新增 startup recovery fallback 防止启动卡死 feat: 重构 Calendar DayWeek 视图事件布局引擎 - 新增 DayEventLayoutEngine 解耦事件计算与渲染 - 新增 DayTimelineMetrics 统一时间轴常量 - 新增 DayViewScale 支持捏合缩放 feat: 新增 Settings 页面共享 UI 组件 - 新增 BackTitlePageHeader 统一页面 header - 新增 DetailHeaderActionMenu 统一操作菜单 - 新增 DestructiveActionSheet 统一删除确认 - 新增 AppToggleSwitch 统一开关组件 feat: Chat UI Schema 支持导航操作 - 支持 navigation 类型 action 触发内部路由跳转 - 新增路径验证与参数处理 chore: 更新相关测试覆盖 auth 失效路径
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:social_app/features/calendar/data/models/schedule_item_model.dart';
|
||||
import 'package:social_app/features/calendar/ui/dayweek/day_event_layout_engine.dart';
|
||||
import 'package:social_app/features/calendar/ui/dayweek/day_timeline_metrics.dart';
|
||||
import 'package:social_app/features/calendar/ui/dayweek/day_view_scale.dart';
|
||||
|
||||
void main() {
|
||||
group('DayEventLayoutEngine', () {
|
||||
const engine = DayEventLayoutEngine();
|
||||
const scale = DayViewScale(hourHeight: 60);
|
||||
|
||||
test('maps event top and height by exact minutes', () {
|
||||
final event = _event(
|
||||
id: 'a',
|
||||
start: DateTime(2026, 3, 18, 10, 15),
|
||||
end: DateTime(2026, 3, 18, 11, 45),
|
||||
);
|
||||
|
||||
final layouts = engine.layout(
|
||||
events: [event],
|
||||
scale: scale,
|
||||
eventAreaLeft: DayTimelineMetrics.eventAreaLeft(),
|
||||
eventAreaWidth: 200,
|
||||
);
|
||||
|
||||
expect(layouts, hasLength(1));
|
||||
expect(layouts.first.startMinutes, 615);
|
||||
expect(layouts.first.endMinutes, 705);
|
||||
expect(layouts.first.top, 615);
|
||||
expect(layouts.first.geometryHeight, 90);
|
||||
expect(layouts.first.visualHeight, 90);
|
||||
});
|
||||
|
||||
test('splits overlapped events into columns', () {
|
||||
final e1 = _event(
|
||||
id: 'a',
|
||||
start: DateTime(2026, 3, 18, 9, 0),
|
||||
end: DateTime(2026, 3, 18, 10, 0),
|
||||
);
|
||||
final e2 = _event(
|
||||
id: 'b',
|
||||
start: DateTime(2026, 3, 18, 9, 30),
|
||||
end: DateTime(2026, 3, 18, 10, 30),
|
||||
);
|
||||
|
||||
final layouts = engine.layout(
|
||||
events: [e1, e2],
|
||||
scale: scale,
|
||||
eventAreaLeft: DayTimelineMetrics.eventAreaLeft(),
|
||||
eventAreaWidth: 200,
|
||||
);
|
||||
|
||||
expect(layouts, hasLength(2));
|
||||
expect(layouts[0].columnCount, 2);
|
||||
expect(layouts[1].columnCount, 2);
|
||||
expect(layouts[0].width, closeTo(98, 0.001));
|
||||
expect(layouts[1].width, closeTo(98, 0.001));
|
||||
expect(layouts[0].left, DayTimelineMetrics.eventAreaLeft());
|
||||
expect(
|
||||
layouts[1].left,
|
||||
closeTo(DayTimelineMetrics.eventAreaLeft() + 102, 0.001),
|
||||
);
|
||||
});
|
||||
|
||||
test('uses 1 pixel minimum visual height but preserves geometry', () {
|
||||
final event = _event(
|
||||
id: 'a',
|
||||
start: DateTime(2026, 3, 18, 9, 0),
|
||||
end: DateTime(2026, 3, 18, 9, 1),
|
||||
);
|
||||
|
||||
final tinyScale = const DayViewScale(
|
||||
hourHeight: DayViewScale.minHourHeight,
|
||||
);
|
||||
final layouts = engine.layout(
|
||||
events: [event],
|
||||
scale: tinyScale,
|
||||
eventAreaLeft: DayTimelineMetrics.eventAreaLeft(),
|
||||
eventAreaWidth: 200,
|
||||
);
|
||||
|
||||
expect(layouts, hasLength(1));
|
||||
expect(
|
||||
layouts.first.geometryHeight,
|
||||
closeTo(tinyScale.pixelsForMinutes(1), 0.001),
|
||||
);
|
||||
expect(layouts.first.visualHeight, greaterThanOrEqualTo(1));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ScheduleItemModel _event({
|
||||
required String id,
|
||||
required DateTime start,
|
||||
required DateTime end,
|
||||
}) {
|
||||
return ScheduleItemModel(
|
||||
id: id,
|
||||
ownerId: 'owner',
|
||||
title: 'event-$id',
|
||||
startAt: start,
|
||||
endAt: end,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:social_app/features/calendar/ui/dayweek/day_view_scale.dart';
|
||||
|
||||
void main() {
|
||||
group('DayViewScale', () {
|
||||
test('maps minutes to pixels and back', () {
|
||||
const scale = DayViewScale(hourHeight: 60);
|
||||
|
||||
expect(scale.pixelsForMinutes(30), 30);
|
||||
expect(scale.pixelsForMinutes(75), 75);
|
||||
expect(scale.minutesForPixels(90), 90);
|
||||
});
|
||||
|
||||
test('clamps zoom height at boundaries', () {
|
||||
const scale = DayViewScale(hourHeight: 34);
|
||||
|
||||
final zoomIn = scale.zoomByFactor(20);
|
||||
final zoomOut = scale.zoomByFactor(0.01);
|
||||
|
||||
expect(zoomIn.hourHeight, DayViewScale.maxHourHeight);
|
||||
expect(zoomOut.hourHeight, DayViewScale.minHourHeight);
|
||||
});
|
||||
|
||||
test('ignores invalid zoom factor', () {
|
||||
const scale = DayViewScale(hourHeight: 34);
|
||||
|
||||
expect(scale.zoomByFactor(0).hourHeight, 34);
|
||||
expect(scale.zoomByFactor(-1).hourHeight, 34);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user