Skip to content

Fix bugs across component, layout, navigator and async content - #45

Merged
gmpassos merged 3 commits into
masterfrom
fix/bugs-and-tests
Aug 13, 2026
Merged

Fix bugs across component, layout, navigator and async content#45
gmpassos merged 3 commits into
masterfrom
fix/bugs-and-tests

Conversation

@gmpassos

Copy link
Copy Markdown
Contributor

Audit of the library for bugs, with a fix and a regression test for each finding.

Bugs fixed

UIComponent

  • _callOnChildRendered called onChildRendered(this), so every subscriber received the parent instead of the rendered child.
  • _parseOnEventKeyPress did keypress.split(':')[1]: an onEventKeyPress attribute without a : threw a RangeError that propagated to toContentElements, which swallowed it and returned an empty list — silently losing the whole render.
  • focusField searched the text input in the component itself instead of in the resolved field component.
  • getFieldsExtended sorted with a is UIComponent, where a is a MapEntry (always false). The actual conflict resolution happens in getFieldsComponentsMap, which used Map.fromEntries (last wins), letting a plain element overwrite an UIField component 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

  • _ensureElementForDOM had a dangling div; statement instead of return div;, so an HTML content with multiple root nodes returned the raw String instead of the built element.

UILayout

  • _getElementIndexByID built the invalid CSS selector # for an element without an id, throwing a SyntaxError. This broke any evaluated uiLayout command (e.g. x(10px)) on elements without an id. Found by the new tests.
  • _parseValuePx used int.parse, throwing a FormatException on fractional values (e.g. 4.5px); it is now parsed as a num and ignored when not a plain px value (e.g. calc(...)).
  • _valueXY returned '0 px' (invalid CSS, note the space).
  • The window resize listener was registered by every UILayout instance, because _registeredWindow was not static.
  • _configure: removed a no-op split() over a regex passed as a literal String.
  • _getElementIndex/_getElementIndexByID are now null-safe for detached elements.

UINavigator

  • navigateOnClick did setAttribute(..., route!) and threw on a null route; it now clears a previously registered route and returns null.
  • _navigateTo: the "navigable not found" retry dropped the fromURL flag, pushing a duplicated entry in the browser history for URL-driven navigations.

Others

  • UIElementExtension.resolveUIComponent force-unwrapped UIRoot.getInstance().
  • UIEventHandler.trackAllRegisteredEventListeners used isElement where isEmpty was meant.
  • $uiButtonLoader: button-style was only emitted when buttonClasses was provided (and emitted empty when only buttonClasses was given); loaded-text-classes was built from buttonClasses instead of loadedTextClass; UIButtonLoader.generator now also accepts the loaded-text-classes attribute that $uiButtonLoader generates.

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.dart and bones_ui_button_test.dart.

Verified as real regression tests: stashing the lib/ changes makes 12 of them fail, one per distinct fix.

  • Suite: 13 → 94 passing (47 tests × 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 via bump.sh.

🤖 Generated with Claude Code

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

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.03704% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.84%. Comparing base (8459262) to head (26e31e6).

Files with missing lines Patch % Lines
lib/src/bones_ui_layout.dart 70.00% 3 Missing ⚠️
lib/src/bones_ui_component.dart 88.88% 2 Missing ⚠️
lib/src/component/button.dart 50.00% 2 Missing ⚠️
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     
Flag Coverage Δ
unittests 60.84% <87.03%> (+20.67%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

gmpassos and others added 2 commits August 12, 2026 22:18
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>
@gmpassos
gmpassos merged commit 2269818 into master Aug 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant