Conversation
nilmerg
left a comment
There was a problem hiding this comment.
Didn't look at JS yet.
Also, add tests for Time and its child classes. Test formatting and rendering.
nilmerg
left a comment
There was a problem hiding this comment.
I stopped reading the tests once I saw more than one skip due to when the test is performed. Sorry, but this just non-sense. Tests must always succeed.
In addition, they make too heavy usage of contains and regex assertions. They test a HTML widget, so should assert the HTML structure: $obj actual, $html expected. Other tests exactly work like this and there is even a predefined assertion as part of ipl\Tests\Html\TestCase.
So please refactor your tests accordingly.
65cf226 to
7add9f3
Compare
BastianLedererIcinga
left a comment
There was a problem hiding this comment.
I see my icingadb-web PR is still not entirely compatible with your changes, since I see no proper way to include a data-ago-label in Time widgets, which is required for the switch from absolute to relative. Let's discuss a solution and this time document the agreed approach in this PR.
Create a general Time Widget which is also the base for TimeAgo, TimeSince & TimeUntil
For this change to work the following PR is required: Icinga/icingaweb2#5238
Allow the Time widget to accept a custom IntlDateFormatter instance, enabling locale-aware and customizable time formatting. Falls back to the default formatted datetime string when no formatter is set. Also declare the ext-intl PHP extension as an explicit dependency in composer.json.
PHPStan checks that the values in specified keys have the correct types. source: https://phpstan.org/writing-php-code/phpdoc-types#array-shapes
Remove the separate assemble() override in TimeAgo, TimeSince and TimeUntil. Attribute setup and text formatting are now handled entirely within format(), eliminating the duplicate diff() call and reducing the number of methods subclasses need to override.
…ocks Replace \Exception with the imported Exception class across Time and its subclasses, and add the missing Exception import and @throws annotation to Time::relative().
Previously, Time::diff() and Time::relative() always used new DateTime()
as the reference point, making deterministic unit tests impossible.
Introduce an optional $compareTime parameter throughout the widget family
so callers can inject an explicit reference time:
- Time::diff() accepts ?DateTime $compareTime instead of computing "now"
internally; Time::relative() forwards it to TimeAgo/TimeUntil.
- TimeAgo, TimeSince, and TimeUntil store the injected time in the new
Time::$compareTime property.
- Year/day comparisons using date('Y')/date('d') are replaced by the new
isSameByFormat() helper, removing the last implicit "now" dependencies.
- data-relative-time attributes are moved to $defaultAttributes, removing
duplicate code from format() methods.
- Fix inverted prefix logic in TimeUntil: only prepend '-' when invert
is 0 and the interval is non-zero.
- Move all test classes out of namespace blocks into proper ipl\Tests\Web\Widget namespace - Extend TestCase instead of PHPUnit\Framework\TestCase directly - Initialize StaticTranslator with NoopTranslator in setUp() to avoid translation side effects - Consolidate redundant constructor tests (int/float/DateTime variants) into fewer, clearer cases - Rename test methods to focus on observable behavior rather than input type (e.g. testConstructorAcceptsIntTimestamp → testConstructorAcceptsTimestamp) - Add $compareTime parameter usage to pin time in tests for deterministic results
7add9f3 to
18b4049
Compare
18b4049 to
a02cf6e
Compare
nilmerg
left a comment
There was a problem hiding this comment.
@BastianLedererIcinga Please take over now, as @Jan-Schuppik is already occupied for the coming weeks and I want to finish this soon.
Don't hesitate to let Claude refactor the tests 😉
Add a new test case and fix intentation of heredoc
Scan for new relative time elements on `rendered` and track existing ones. Use `setInterval` instead of the `icinga.timer`.
Since `Translation` is used in the `Time` class already it can be dropped from the subclasses. Tests are adjusted to match the new type of `$compareTime`.
| public function __construct(int|float|DateTime|null $time = null, ?DateTime $compareTime = null) | ||
| { | ||
| if ($compareTime !== null) { | ||
| $this->compareTime = $this->castToDateTime($compareTime); |
There was a problem hiding this comment.
It doesn't need to be cast anymore ;)
| public function __construct(int|float|DateTime|null $time = null, ?DateTime $compareTime = null) | ||
| { | ||
| if ($compareTime !== null) { | ||
| $this->compareTime = $this->castToDateTime($compareTime); |
| public function __construct(int|float|DateTime|null $time = null, ?DateTime $compareTime = null) | ||
| { | ||
| if ($compareTime !== null) { | ||
| $this->compareTime = $this->castToDateTime($compareTime); |
| * @private | ||
| */ | ||
| this._relativeTime = new RelativeTime(icinga.config.timezone); | ||
| this._relativeTime.scan(document); |
There was a problem hiding this comment.
I believe it shouldn't be necessary to scan upon construction. The render event is triggered upon page load by Icinga Web.
| } | ||
|
|
||
| onTimeRendered(event) { | ||
| event.data.self._relativeTime.scan(event.target); |
There was a problem hiding this comment.
Please only scan in case event.currentTarget === event.target. Otherwise this will scan containers multiple times while the event bubbles up to the ancestors.
For this to reliably work the listener registration in the constructor needs to use .container as condition.
| tick() { | ||
| this._trackedElements.forEach((ref) => { | ||
| const el = ref.deref(); | ||
| if (el) { |
There was a problem hiding this comment.
you only add to _trackedElements, drop dead refs from the set to avoid leaking memory.
| } | ||
|
|
||
| updateElement(element) { | ||
| const DYNAMIC_RELATIVE_TIME_THRESHOLD = 60 * 60; |
There was a problem hiding this comment.
please make this a class constant/property
|
|
||
| _getTimeDifferenceInSeconds(element, future = false) { | ||
|
|
||
| const fromDateTimeWithTimezone = (element, future = false) => { |
There was a problem hiding this comment.
Why is this a separate function?
| } | ||
| } | ||
|
|
||
| _getTimeDifferenceInSeconds(element, future = false) { |
There was a problem hiding this comment.
please remove underscores from all methods
| } | ||
|
|
||
| _getOffset() { | ||
| if (!this._offsetCache) { |
There was a problem hiding this comment.
a space after the negation operator please
- Make `DYNAMIC_RELATIVE_TIME_THRESHOLD` a class property - Remove underscores from method names - Simplify `getTimeDifferenceInSeconds` - Add missing space after negation
This PR removes the direct dependency of the time widgets on Icinga Web 2 (\Icinga\Date\DateFormatter::formatDateTime()) and introduces a native relative-time formatting behavior in IPL Web.
Resolve:
TimeAgo,TimeUntilandTimeSincefrom Icinga Web #341Depends on:
Icingatoipl/webbehaviors icingaweb2#5238(brings the ability to create js-behaviors outside of icingaweb2 )