Files
tasq/test/programmer_task_duration_test.dart
T
redz1029 627645056b Add Programmer Tasks module (work-log + time tracking)
Separate feature for the `programmer` role to record dev work by category,
distinct from the IT help-desk `tasks` feature. Modeled on the IT Service
Requests pattern (Brick offline-first model -> Riverpod stream/controller ->
list/detail screens); seed of a future Project Management workflow.

- programmer_tasks + programmer_task_activity_logs (category enum, RLS,
  realtime) via supabase migration
- single assignee, self-logged or lead-assigned; assign/reassign logged
- pause-on-switch: starting a task while another runs prompts to pause the
  running one, keeping one active task per user for accurate time attribution
- worked-duration math shared with help-desk tasks via a generic core in
  task_duration.dart (existing callers unchanged)
- list distinguishes running vs paused with a per-task Resume shortcut
- accessible dialog labels; route /programmer-tasks + nav gated to admin/programmer
- unit/widget tests: controller lifecycle, duration math, pause-switch dialog

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-09-27 01:13:33 +08:00

86 lines
2.7 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:tasq/utils/task_duration.dart';
DurationEvent _ev(DateTime at, String action) =>
(createdAt: at, actionType: action);
void main() {
final base = DateTime.utc(2026, 1, 1, 9, 0);
group('computeEffectiveDuration', () {
test('no pause events → full span from fallbackStartedAt to endAt', () {
final worked = computeEffectiveDuration(
fallbackStartedAt: base,
endAt: base.add(const Duration(hours: 2)),
events: const [],
);
expect(worked, const Duration(hours: 2));
});
test('resolves start from the "started" event over the fallback', () {
final started = base.add(const Duration(minutes: 30));
// events are newest-first (DESC)
final events = [_ev(started, 'started')];
final worked = computeEffectiveDuration(
fallbackStartedAt: base, // earlier fallback should be ignored
endAt: started.add(const Duration(hours: 1)),
events: events,
);
expect(worked, const Duration(hours: 1));
});
test('subtracts a completed paused interval', () {
final started = base;
final paused = base.add(const Duration(minutes: 20));
final resumed = base.add(const Duration(minutes: 50)); // 30m paused
final end = base.add(const Duration(hours: 2));
// DESC order
final events = [
_ev(resumed, 'resumed'),
_ev(paused, 'paused'),
_ev(started, 'started'),
];
final worked = computeEffectiveDuration(
fallbackStartedAt: started,
endAt: end,
events: events,
);
expect(worked, const Duration(hours: 2) - const Duration(minutes: 30));
});
test('counts an open pause interval still active at endAt', () {
final started = base;
final paused = base.add(const Duration(minutes: 40));
final end = base.add(const Duration(hours: 1)); // 20m still paused
final events = [
_ev(paused, 'paused'),
_ev(started, 'started'),
];
final worked = computeEffectiveDuration(
fallbackStartedAt: started,
endAt: end,
events: events,
);
expect(worked, const Duration(minutes: 40));
});
test('returns zero when there is no resolvable start', () {
final worked = computeEffectiveDuration(
fallbackStartedAt: null,
endAt: base,
events: const [],
);
expect(worked, Duration.zero);
});
test('returns zero when endAt is before start', () {
final worked = computeEffectiveDuration(
fallbackStartedAt: base,
endAt: base.subtract(const Duration(minutes: 5)),
events: const [],
);
expect(worked, Duration.zero);
});
});
}