index.test.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import dayjs from "../../src/utils/dayjs";
  2. import { getSolarTermDate, getSolarTerms, getSolarTermsInRange, type SolarTerm } from "../../src";
  3. import { SOLAR_TERMS_MONTH, SOLAR_TERMS, type SolarTermKey } from "../../src/solar_terms/constants";
  4. describe("Solar Terms", () => {
  5. describe("getSolarTermDate", () => {
  6. const testCases = [
  7. { year: 1998, month: 1, term: "lesser_cold" as SolarTermKey, expected: "1998-01-05", century: 20, desc: "Lesser Cold 1998 (20th century)" },
  8. { year: 2024, month: 1, term: "lesser_cold" as SolarTermKey, expected: "2024-01-06", century: 21, desc: "Lesser Cold 2024 (21st century)" },
  9. { year: 2026, month: 2, term: "rain_water" as SolarTermKey, expected: "2026-02-18", century: 21, desc: "Rain Water 2026 (with delta)" }, // SOLAR_TERMS_DELTA has 2026_rain_water: -1
  10. { year: 2024, month: 2, term: "the_beginning_of_spring" as SolarTermKey, expected: "2024-02-04", century: 21, desc: "Beginning of Spring 2024 (no delta)" },
  11. { year: 1900, month: 1, term: "greater_cold" as SolarTermKey, expected: "1900-01-21", century: 20, desc: "Greater Cold 1900 (Y-1)/4 logic for Jan term" },
  12. { year: 2000, month: 12, term: "the_winter_solstice" as SolarTermKey, expected: "2000-12-21", century: 20, desc: "Winter Solstice 2000 (20th cent.)" },
  13. { year: 2001, month: 3, term: "the_spring_equinox" as SolarTermKey, expected: "2001-03-20", century: 21, desc: "Spring Equinox 2001 (21st cent.)" },
  14. // Test a case where (Y-1)/4 vs Y/4 makes a difference for L value
  15. { year: 2001, month: 1, term: "lesser_cold" as SolarTermKey, expected: "2001-01-05", desc: "Lesser Cold 2001, Y=1, (Y-1)/4 = 0" }, // Y=1. (Y-1)/4=0. Y/4=0. No diff here.
  16. { year: 2004, month: 1, term: "lesser_cold" as SolarTermKey, expected: "2004-01-06", desc: "Lesser Cold 2004, Y=4, (Y-1)/4 = 0" }, // Y=4. (Y-1)/4=0. Y/4=1. L should be 0.
  17. { year: 2005, month: 1, term: "lesser_cold" as SolarTermKey, expected: "2005-01-05", desc: "Lesser Cold 2005, Y=5, (Y-1)/4 = 1" }, // Y=5. (Y-1)/4=1. Y/4=1. L should be 1.
  18. ];
  19. test.each(testCases)("should calculate $term for $year-$month ($desc) as $expected", ({ year, month, term, expected }) => {
  20. expect(getSolarTermDate(year, month, term)).toBe(expected);
  21. });
  22. });
  23. describe("getSolarTerms", () => {
  24. const baseExpected2024JanFeb: SolarTerm[] = [
  25. { date: "2024-01-06", term: "lesser_cold", name: "小寒", index: 1 },
  26. { date: "2024-01-20", term: "greater_cold", name: "大寒", index: 1 },
  27. { date: "2024-02-04", term: "the_beginning_of_spring", name: "立春", index: 1 },
  28. { date: "2024-02-19", term: "rain_water", name: "雨水", index: 1 },
  29. ];
  30. const testCases = [
  31. { start: "2024-01-01", end: "2024-02-29", expected: baseExpected2024JanFeb, desc: "Range covering Jan-Feb 2024" },
  32. {
  33. start: "2024-03-01", end: "2024-03-31", expected: [
  34. { date: "2024-03-05", name: "惊蛰", term: "the_waking_of_insects", index: 1 },
  35. { date: "2024-03-20", name: "春分", term: "the_spring_equinox", index: 1 },
  36. ], desc: "Range covering Mar 2024"
  37. },
  38. { start: "2024-01-06", end: "2024-01-06", expected: [{ date: "2024-01-06", term: "lesser_cold", name: "小寒", index: 1 }], desc: "Single day range on a solar term" },
  39. { start: "2024-01-06", end: undefined, expected: [{ date: "2024-01-06", term: "lesser_cold", name: "小寒", index: 1 }], desc: "Single day, end undefined" },
  40. { start: "2024-01-01", end: "2024-01-05", expected: [], desc: "Range before first solar term of year" },
  41. { start: "2024-02-20", end: "2024-03-04", expected: [], desc: "Range between two solar terms (Rain Water 19th, Waking Insect 5th)" },
  42. // Tests for line 84 conditions
  43. { start: "2024-02-19", end: "2024-03-05", expected: [ // Term on start, term on end
  44. { date: "2024-02-19", term: "rain_water", name: "雨水", index: 1 },
  45. { date: "2024-03-05", name: "惊蛰", term: "the_waking_of_insects", index: 1 },
  46. ], desc: "Term on start date, term on end date"
  47. },
  48. { start: "2024-01-05", end: "2024-01-06", expected: [{ date: "2024-01-06", term: "lesser_cold", name: "小寒", index: 1 }], desc: "Term on end date" },
  49. { start: "2024-01-06", end: "2024-01-07", expected: [{ date: "2024-01-06", term: "lesser_cold", name: "小寒", index: 1 }], desc: "Term on start date" },
  50. { start: "2024-12-20", end: "2025-01-07", expected: [ // Across year boundary
  51. { date: "2024-12-21", term: "the_winter_solstice", name: "冬至", index: 1 },
  52. { date: "2025-01-05", term: "lesser_cold", name: "小寒", index: 1 },
  53. ], desc: "Range across year boundary"
  54. },
  55. { start: "2024-01-10", end: "2024-01-15", expected: [], desc: "Range with no solar terms within it" },
  56. { start: "2024-02-01", end: "2024-02-03", expected: [], desc: "Short range, no terms" },
  57. ];
  58. test.each(testCases)("getSolarTerms($start, $end) ($desc)", ({ start, end, expected }) => {
  59. const terms = getSolarTerms(start, end);
  60. expect(terms).toEqual(expected);
  61. });
  62. test("should return empty array if start is after end", () => {
  63. expect(getSolarTerms("2024-02-01", "2024-01-01")).toEqual([]);
  64. });
  65. });
  66. describe('getSolarTermsInRange', () => {
  67. const defaultRangeTestCases = [
  68. {
  69. start: '2024-01-04', end: '2024-01-07', desc: "Jan 4 to Jan 7 2024",
  70. expected: [
  71. { date: '2024-01-04', term: 'the_winter_solstice', name: '冬至', index: 14 },
  72. { date: '2024-01-05', term: 'the_winter_solstice', name: '冬至', index: 15 },
  73. { date: '2024-01-06', term: 'lesser_cold', name: '小寒', index: 1 },
  74. { date: '2024-01-07', term: 'lesser_cold', name: '小寒', index: 2 }
  75. ]
  76. },
  77. {
  78. start: '2024-01-20', end: undefined, desc: "Single day Jan 20 2024 (end undefined)",
  79. expected: [{ date: '2024-01-20', term: 'greater_cold', name: '大寒', index: 1 }]
  80. },
  81. {
  82. start: '2024-12-30', end: '2025-01-02', desc: "Across year boundary Dec 30 2024 to Jan 2 2025",
  83. expected: [
  84. { date: '2024-12-30', term: 'the_winter_solstice', name: '冬至', index: 10 },
  85. { date: '2024-12-31', term: 'the_winter_solstice', name: '冬至', index: 11 },
  86. { date: '2025-01-01', term: 'the_winter_solstice', name: '冬至', index: 12 },
  87. { date: '2025-01-02', term: 'the_winter_solstice', name: '冬至', index: 13 }
  88. ]
  89. },
  90. {
  91. start: '2024-01-06', end: '2024-01-06', desc: "Single day on a solar term start",
  92. expected: [{ date: '2024-01-06', term: 'lesser_cold', name: '小寒', index: 1 }]
  93. },
  94. {
  95. start: '2024-01-07', end: '2024-01-07', desc: "Single day, 2nd day of a solar term",
  96. expected: [{ date: '2024-01-07', term: 'lesser_cold', name: '小寒', index: 2 }]
  97. },
  98. {
  99. start: '2024-01-01', end: '2024-01-03', desc: "Range with no solar term start, but within a term period",
  100. expected: [
  101. { date: '2024-01-01', term: 'the_winter_solstice', name: '冬至', index: 11 },
  102. { date: '2024-01-02', term: 'the_winter_solstice', name: '冬至', index: 12 },
  103. { date: '2024-01-03', term: 'the_winter_solstice', name: '冬至', index: 13 },
  104. ]
  105. }
  106. ];
  107. test.each(defaultRangeTestCases)("getSolarTermsInRange($start, $end) ($desc)", ({ start, end, expected }) => {
  108. const result = getSolarTermsInRange(start, end);
  109. expect(result).toEqual(expected);
  110. });
  111. test("should return empty array if start is after end for getSolarTermsInRange", () => {
  112. // Current logic of getSolarTermsInRange (subtracting 1 month from start, adding 1 to end)
  113. // might still produce results if the adjusted range is valid.
  114. // If start = 2024-02-01, end = 2024-01-01.
  115. // current becomes 2024-01-01. endDate becomes 2024-02-01.
  116. // allTerms will be populated for Jan.
  117. // deltaDays will be populated.
  118. // filter `trem.day.isBetween(start, end, 'day')` -> `isBetween('2024-02-01', '2024-01-01')` will be false.
  119. expect(getSolarTermsInRange("2024-02-01", "2024-01-01")).toEqual([]);
  120. });
  121. // Test to ensure all 24 solar terms are covered by getSolarTermsInRange throughout a year
  122. test('getSolarTermsInRange should cover all 24 solar terms in a year', () => {
  123. const allTermsFor2024 = getSolarTermsInRange('2024-01-01', '2024-12-31');
  124. const uniqueTerms = new Set(allTermsFor2024.map(t => t.term));
  125. expect(uniqueTerms.size).toBe(24);
  126. });
  127. });
  128. });