Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: "ff37bef603469fb030f2b72995ab929ccfc227f0"
channel: "stable"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
- platform: web
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0

# User provided section

# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
17 changes: 12 additions & 5 deletions lib/main_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,18 @@ class _MainState extends State<MainView> {
}

String? _validateNgayThangNam(String? value) {
if (value!.isEmpty) return 'Cần nhập ngày âm';
final nameExp = RegExp(
r'^(?:31([/\-.])(?:0?[13578]|1[02])\1|(?:29|30)(/)(?:0?[13-9]|1[0-2])\2)(?:1[6-9]|[2-9]\d)?\d{2}$|^29([/\-.])0?2\3(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:16|[2468][048]|[3579][26])00)$|^(?:0?[1-9]|1\d|2[0-8])([/\-.])(?:0?[1-9]|1[0-2])\4(?:1[6-9]|[2-9]\d)?\d{2}$',
);
if (!nameExp.hasMatch(value)) return 'Nhập theo Ngày/Tháng/Năm';
if (value == null || value.isEmpty) return 'Cần nhập ngày âm';
final parts = value.split('/');
if (parts.length != 3) return 'Nhập theo Ngày/Tháng/Năm';
final day = int.tryParse(parts[0]);
final month = int.tryParse(parts[1]);
final year = int.tryParse(parts[2]);
if (day == null || month == null || year == null) {
return 'Nhập theo Ngày/Tháng/Năm';
}
if (day < 1 || day > 30) return 'Ngày từ 1 đến 30';
if (month < 1 || month > 12) return 'Tháng từ 1 đến 12';
if (year < 1) return 'Năm không hợp lệ';
return null;
}

Expand Down
135 changes: 135 additions & 0 deletions test/main_bloc_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:licham/main_bloc.dart';
import 'package:licham/main_event.dart';
import 'package:licham/main_state.dart';

void main() {
Expand All @@ -25,4 +26,138 @@ void main() {
await mainBloc.close();
await future;
});

group('solar to lunar conversion', () {
Future<DateUpdate> convertDate(DateTime solar) async {
final bloc = MainBloc(solar)..add(const AppStarted());
final state = await bloc.stream.firstWhere((s) => s is DateUpdate);
await bloc.close();
return state as DateUpdate;
}

group('Tết Nguyên Đán (Lunar New Year = 1/1)', () {
test('2024-02-10 is Mùng 1 Tết Giáp Thìn', () async {
final state = await convertDate(DateTime(2024, 2, 10));
expect(state.lunar!.day, 1);
expect(state.lunar!.month, 1);
expect(state.lunar!.year, 2024);
expect(state.lunar!.canchiYear, 'Giáp Thìn');
});

test('2025-01-29 is Mùng 1 Tết Ất Tỵ', () async {
final state = await convertDate(DateTime(2025, 1, 29));
expect(state.lunar!.day, 1);
expect(state.lunar!.month, 1);
expect(state.lunar!.year, 2025);
expect(state.lunar!.canchiYear, 'Ất Tỵ');
});

test('2026-02-17 is Mùng 1 Tết Bính Ngọ', () async {
final state = await convertDate(DateTime(2026, 2, 17));
expect(state.lunar!.day, 1);
expect(state.lunar!.month, 1);
expect(state.lunar!.year, 2026);
expect(state.lunar!.canchiYear, 'Bính Ngọ');
});

test('2023-01-22 is Mùng 1 Tết Quý Mão', () async {
final state = await convertDate(DateTime(2023, 1, 22));
expect(state.lunar!.day, 1);
expect(state.lunar!.month, 1);
expect(state.lunar!.year, 2023);
expect(state.lunar!.canchiYear, 'Quý Mão');
});
});

group('Rằm Trung Thu (Mid-Autumn = 15/8)', () {
test('2024-09-17 is Rằm tháng Tám', () async {
final state = await convertDate(DateTime(2024, 9, 17));
expect(state.lunar!.day, 15);
expect(state.lunar!.month, 8);
});

test('2025-10-06 is Rằm tháng Tám', () async {
final state = await convertDate(DateTime(2025, 10, 6));
expect(state.lunar!.day, 15);
expect(state.lunar!.month, 8);
});
});

test('emits auspicious hours', () async {
final state = await convertDate(DateTime(2024, 2, 10));
expect(state.lunar!.hours, isNotNull);
expect(state.lunar!.hours, isNotEmpty);
});

test('emits Can Chi for day and month', () async {
final state = await convertDate(DateTime(2024, 2, 10));
expect(state.lunar!.canChiDay, isNotNull);
expect(state.lunar!.canChiMonth, isNotNull);
});
});

group('date navigation', () {
test('next day advances solar date', () async {
final bloc = MainBloc(DateTime(2024, 2, 10))..add(const AppStarted());

final states = <DateUpdate>[];
final sub = bloc.stream
.where((s) => s is DateUpdate)
.cast<DateUpdate>()
.listen(states.add);

await Future<void>.delayed(const Duration(milliseconds: 100));
bloc.add(const NextSelected());
await Future<void>.delayed(const Duration(milliseconds: 100));

expect(states.length, greaterThanOrEqualTo(2));
expect(states.last.solar!.day, 11);
expect(states.last.solar!.month, 2);

await sub.cancel();
await bloc.close();
});

test('previous day goes back', () async {
final bloc = MainBloc(DateTime(2024, 2, 10))..add(const AppStarted());

final states = <DateUpdate>[];
final sub = bloc.stream
.where((s) => s is DateUpdate)
.cast<DateUpdate>()
.listen(states.add);

await Future<void>.delayed(const Duration(milliseconds: 100));
bloc.add(const PreviousSelected());
await Future<void>.delayed(const Duration(milliseconds: 100));

expect(states.length, greaterThanOrEqualTo(2));
expect(states.last.solar!.day, 9);
expect(states.last.solar!.month, 2);

await sub.cancel();
await bloc.close();
});

test('solar date selection updates lunar', () async {
final bloc = MainBloc(DateTime(2024))..add(const AppStarted());

final states = <DateUpdate>[];
final sub = bloc.stream
.where((s) => s is DateUpdate)
.cast<DateUpdate>()
.listen(states.add);

await Future<void>.delayed(const Duration(milliseconds: 100));
bloc.add(SolarSelected(solar: DateTime(2024, 2, 10)));
await Future<void>.delayed(const Duration(milliseconds: 100));

expect(states.last.lunar!.day, 1);
expect(states.last.lunar!.month, 1);
expect(states.last.lunar!.year, 2024);

await sub.cancel();
await bloc.close();
});
});
}