gabriel / muse public

test_timeline_rational_time.py file-level

at sha256:6 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:6 chore(timeline): remove unused RationalRate import in entity.py · · Jul 10, 2026
1 """VID_02–VID_06 — RationalRate, RationalTime, TimeRange unit tests."""
2
3 from __future__ import annotations
4
5 import math
6
7 import pytest
8
9 from muse.plugins.timeline.rational_time import RationalRate, RationalTime, TimeRange
10
11
12 class TestVID02RationalRate:
13 def test_reduction(self) -> None:
14 r = RationalRate(48, 2)
15 assert (r.num, r.den) == (24, 1)
16
17 def test_rejects_non_positive(self) -> None:
18 with pytest.raises(ValueError):
19 RationalRate(0, 1)
20 with pytest.raises(ValueError):
21 RationalRate(1, 0)
22
23 def test_ntsc_table(self) -> None:
24 assert RationalRate.from_float(23.976) == RationalRate(24000, 1001)
25 assert RationalRate.from_float(29.97) == RationalRate(30000, 1001)
26
27 def test_rejects_undeclared_fractional(self) -> None:
28 with pytest.raises(ValueError):
29 RationalRate.from_float(23.500001)
30
31
32 class TestVID03RationalTimeEquality:
33 def test_equal_instant_different_rate(self) -> None:
34 a = RationalTime(24, RationalRate(24, 1))
35 b = RationalTime(48, RationalRate(48, 1))
36 assert a == b
37
38 def test_ordering(self) -> None:
39 a = RationalTime(10, RationalRate(24, 1))
40 b = RationalTime(20, RationalRate(24, 1))
41 assert a < b
42
43 def test_is_identical(self) -> None:
44 a = RationalTime(24, RationalRate(24, 1))
45 b = RationalTime(48, RationalRate(48, 1))
46 assert a == b
47 assert not a.is_identical(b)
48
49 def test_hash_equal_instant(self) -> None:
50 a = RationalTime(24, RationalRate(24, 1))
51 b = RationalTime(48, RationalRate(48, 1))
52 assert hash(a) == hash(b)
53
54 def test_no_float_in_arithmetic(self) -> None:
55 rt = RationalTime.from_frames(100, RationalRate(24, 1))
56 frac = rt.to_fraction_seconds()
57 assert isinstance(frac.numerator, int)
58 assert isinstance(frac.denominator, int)
59
60
61 class TestVID04TimecodeNonDrop:
62 @pytest.mark.parametrize(
63 ("fps", "tc", "frames"),
64 [
65 (24, "00:00:10:00", 240),
66 (25, "00:00:10:00", 250),
67 ],
68 )
69 def test_round_trip(self, fps: int, tc: str, frames: int) -> None:
70 rate = RationalRate(fps, 1)
71 rt = RationalTime.from_timecode(tc, rate, drop_frame=False)
72 assert rt.value == frames
73 assert rt.to_timecode(drop_frame=False) == tc
74
75
76 class TestVID05DropFrame:
77 def test_2997_drop_frame_separator(self) -> None:
78 rate = RationalRate.from_float(29.97)
79 rt = RationalTime.from_timecode("00:01:00;00", rate, drop_frame=True)
80 assert ";" in rt.to_timecode(drop_frame=True)
81
82 def test_2997_round_trip_anchor(self) -> None:
83 rate = RationalRate.from_float(29.97)
84 tc = "00:01:00;00"
85 rt = RationalTime.from_timecode(tc, rate, drop_frame=True)
86 assert rt.to_timecode(drop_frame=True) == tc
87
88
89 class TestVID06TimeRange:
90 def test_half_open_contains(self) -> None:
91 rate = RationalRate(24, 1)
92 tr = TimeRange(
93 start_time=RationalTime(0, rate),
94 duration=RationalTime(24, rate),
95 )
96 assert tr.contains(RationalTime(0, rate))
97 assert tr.contains(RationalTime(23, rate))
98 assert not tr.contains(RationalTime(24, rate))
99
100 def test_overlaps_boundary(self) -> None:
101 rate = RationalRate(24, 1)
102 a = TimeRange(RationalTime(0, rate), RationalTime(24, rate))
103 b = TimeRange(RationalTime(24, rate), RationalTime(24, rate))
104 assert not a.overlaps(b)
105
106 def test_end_time_exclusive(self) -> None:
107 rate = RationalRate(24, 1)
108 tr = TimeRange(RationalTime(10, rate), RationalTime(5, rate))
109 end = tr.end_time_exclusive
110 assert end == RationalTime(15, rate)
111
112 def test_duration_non_negative(self) -> None:
113 rate = RationalRate(24, 1)
114 with pytest.raises(ValueError):
115 TimeRange(RationalTime(0, rate), RationalTime(-1, rate))