Fix bugs across component, layout, navigator and async content - #45
Merged
Conversation
Fixes found by auditing the library, each covered by a new test: - `UIComponent.onChildRendered` was notified with the parent instead of the rendered child. - `onEventKeyPress` without a `key:action` delimiter threw a `RangeError` that aborted the whole render. - `focusField` searched the text input in the component itself instead of in the resolved field component. - `getFieldsComponentsMap` let a plain element overwrite an `UIField` component with the same field name (`getFieldsExtended` had a sort with that intent, but it compared `MapEntry is UIComponent`). - `UIAsyncContent`: a multi-root HTML content returned the raw `String` instead of the built element (missing `return`). - `UILayout`: an element without an `id` built the invalid selector `#`, breaking any evaluated `uiLayout` command; fractional `px` values threw a `FormatException`; `'0 px'` is not a valid CSS value; the window `resize` listener was registered by every instance. - `UINavigator.navigateOnClick` threw on a `null` route; the "navigable not found" retry dropped `fromURL`, duplicating history entries. - `UIElementExtension.resolveUIComponent` threw when there was no `UIRoot`. - `$uiButtonLoader` emitted `button-style` only when `buttonClasses` was set, and built `loaded-text-classes` from `buttonClasses`. Adds 34 tests in 5 new test files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #45 +/- ##
===========================================
+ Coverage 40.16% 60.84% +20.67%
===========================================
Files 24 25 +1
Lines 3946 4587 +641
===========================================
+ Hits 1585 2791 +1206
+ Misses 2361 1796 -565
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Adds 162 tests in 6 new files, covering areas that had little or no coverage: - `bones_ui_extension.dart` (0% -> 87.8%): element value resolution for input/textarea/select/checkbox/radio, `field_value`, `UIField`/`UIFieldMap`. - `component/component_async.dart` (0% -> 91.8%): `UIComponentAsync` loading, error, property invalidation, `stop` and reuse. - `component/dialog.dart` (5.2% -> 77.1%): show/hide/cancel, `showAndWait`, `UIDialogAlert`/`UIDialogInput`/`UIDialogLoading`. - `component/loading.dart` (14.2% -> 84.9%): `UILoadingConfig` parse/round-trip. - `bones_ui_utils.dart` (52.4% -> 90.3%) and `getLanguageByExtension`. - `bones_ui_component.dart` (67.9% -> 77.5%): classes/style parsing, the attributes API and the fields/fields-group API. - `bones_ui_navigator.dart` (62.5% -> 79.1%): `UINavigableComponent` routes, wildcard routes and `UINavigableContent` head/body/foot. Bugs found and fixed while writing these tests: - `getUILoadingType`: the `dualRing` case was unreachable, since the value is lower-cased before the `switch`, so it silently fell back to `ring`. - `UILoadingConfig`: `textZoom` and `withProgress` were lost on a `toInlineProperties` -> `parse` round-trip (key mismatch and a missing entry). - `UIDialog.getAllDialogs`/`removeAllDialogs`: mapped `.ui-dialog` elements back to components through an `Expando`, which is keyed by identity; on `dart2wasm` an element re-read from the DOM is a new Dart wrapper, so both methods were no-ops. They now resolve through `UIRootComponent.getInstances`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The CI `build` job runs `dart analyze --fatal-infos --fatal-warnings .`, which was failing on `master` too: the workflow pins `sdk: latest` and newer SDKs added `unawaited_return_in_try_block` and widened `use_super_parameters`. - `BUIViewProvider.fromManifestContent`: the `return` of the `async` `fromManifestTree` was inside a `try` block, so its errors were never caught by the `catch` (`unawaited_return_in_try_block`). Moved out of the `try`, preserving the behavior and making the guarded scope explicit. - `use_super_parameters` in `UIDocument`, `BUIRender`, `UIButtonCapture`, `UIDialogInput`, `UIDialogAlert` and `UIDialogLoading`. Applied with `dart fix`; the only default that differs from the super constructor (`fullScreen = false` in the `UIDialog` subclasses, vs `true` in `UIDialogBase`) is kept explicit. Verified locally with every step of the CI `build` job: `dart format`, `dart analyze --fatal-infos --fatal-warnings .`, `dependency_validator` and `dart pub publish --dry-run`. Full suite: 296 tests on dart2js and dart2wasm. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Audit of the library for bugs, with a fix and a regression test for each finding.
Bugs fixed
UIComponent_callOnChildRenderedcalledonChildRendered(this), so every subscriber received the parent instead of the rendered child._parseOnEventKeyPressdidkeypress.split(':')[1]: anonEventKeyPressattribute without a:threw aRangeErrorthat propagated totoContentElements, which swallowed it and returned an empty list — silently losing the whole render.focusFieldsearched the text input in the component itself instead of in the resolved field component.getFieldsExtendedsorted witha is UIComponent, whereais aMapEntry(alwaysfalse). The actual conflict resolution happens ingetFieldsComponentsMap, which usedMap.fromEntries(last wins), letting a plain element overwrite anUIFieldcomponent with the same field name. The preference was moved there and the redundant sort removed.appendStyle: ensures the previous declarations are terminated before appending (hardening — Chrome's CSSOM already serializes with a trailing;).UIAsyncContent_ensureElementForDOMhad a danglingdiv;statement instead ofreturn div;, so an HTML content with multiple root nodes returned the rawStringinstead of the built element.UILayout_getElementIndexByIDbuilt the invalid CSS selector#for an element without anid, throwing aSyntaxError. This broke any evaluateduiLayoutcommand (e.g.x(10px)) on elements without anid. Found by the new tests._parseValuePxusedint.parse, throwing aFormatExceptionon fractional values (e.g.4.5px); it is now parsed as anumand ignored when not a plainpxvalue (e.g.calc(...))._valueXYreturned'0 px'(invalid CSS, note the space).resizelistener was registered by everyUILayoutinstance, because_registeredWindowwas notstatic._configure: removed a no-opsplit()over a regex passed as a literalString._getElementIndex/_getElementIndexByIDare now null-safe for detached elements.UINavigatornavigateOnClickdidsetAttribute(..., route!)and threw on anullroute; it now clears a previously registered route and returnsnull._navigateTo: the "navigable not found" retry dropped thefromURLflag, pushing a duplicated entry in the browser history for URL-driven navigations.Others
UIElementExtension.resolveUIComponentforce-unwrappedUIRoot.getInstance().UIEventHandler.trackAllRegisteredEventListenersusedisElementwhereisEmptywas meant.$uiButtonLoader:button-stylewas only emitted whenbuttonClasseswas provided (and emitted empty when onlybuttonClasseswas given);loaded-text-classeswas built frombuttonClassesinstead ofloadedTextClass;UIButtonLoader.generatornow also accepts theloaded-text-classesattribute that$uiButtonLoadergenerates.Tests
5 new test files with 34 tests:
bones_ui_component_test.dart,bones_ui_async_content_test.dart,bones_ui_layout_test.dart,bones_ui_navigator_test.dartandbones_ui_button_test.dart.Verified as real regression tests: stashing the
lib/changes makes 12 of them fail, one per distinct fix.dart2js+dart2wasm, plus the VM version test).dart analyze: no issues.dart format: no changes.Note
The version bump to
3.0.19(pubspec.yaml+BonesUI.version+CHANGELOG.md) is included in this branch; drop it if you prefer to release separately viabump.sh.🤖 Generated with Claude Code