test: cover router, forms, and reactive collections - #15

Merged
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections
Jun 15, 2026
Merged

test: cover router, forms, and reactive collections#15
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Summary

Follows up on the merged PR #14 (computed chains, composition, events) by adding test coverage for Router, form components, reactive collections, and component lifecycle/DOM-update patterns.

What changed

tests/router.test.ts (14 tests — new file)

  • Router.history returns configured mode ("memory")
  • Router.pathname at creation (root = "") and after go()
  • Router.go() — single and multi-hop navigation in memory mode
  • Router.go() resolves undefined (void return type)
  • Router.props — documents {} behaviour for routes without path params (known API nuance)
  • Redirect route (/old → /new) resolves to target after async microtask flush
  • Route title test — asserts typeof string only (full rendering pipeline limitation documented)
  • Router.create() throws on second call (singleton guard)
  • Router.reload() no-throw
  • Router.back() / Router.forward()console.warn in memory mode, never throw

tests/forms.test.ts (39 tests — new file)

  • TextField: DOM mounting, inputElement tag, placeholder, value, name, disabled get/set, type, label element presence, input/change event forwarding
  • CheckBox: DOM mounting, input[type=checkbox], checked default/prop/setter, value, name, label, disabled (Component level), variant radio/checkbox
  • CheckBoxGroup: DOM mounting, children, selected === null initially, onChange callback fires on change event
  • SwitchPanel: DOM mounting, input type, checked default/prop/setter, name, label, change forwarding, toggle cycle (false → true → false)

tests/reactive-collections.test.ts (57 tests — new file)

  • RefList: construction, id, valueOf(), value, Symbol.toStringTag; all mutation methods (push, pop, shift, unshift, splice, sort, reverse, fill, clear, insert, removeItem, remove, value setter, update()); reactivity (subscribe / listen / unsubscribe); RefList.syncComponentWithList (add, remove, reorder)
  • RefMap: construction, id, valueOf(); mutations (set, delete, clear, value setter, update(), toJSON()); reactivity (subscribe / listen / unsubscribe)
  • RefObject (via refObject()): creation, subscribe, listen, unsubscribe, update, toJSON
  • ref() factory: dispatches to RefList for arrays, RefMap for Maps

tests/lifecycle.test.ts (20 tests — new file)

  • DOM cleanup: remove(), parent removal cascades to children, re-append
  • Re-render: swap components in container, multiple swap cycle
  • Reactive DOM updates: ref → textContent, style, boolean visibility, multi-change propagation, unsubscribe stops DOM updates
  • RefList.syncComponentWithList driving DOM children: init, push settle, remove, clear
  • Multi-subscriber fan-out and selective unsubscribe
  • Component child management: append, innerHTML = '' clear, deep nesting, querySelectorAll

Validation

npm run test:run
 ✓ tests/forms.test.ts (39 tests) 50ms
✓ tests/router.test.ts (14 tests) 37ms
✓ tests/reactive-collections.test.ts (57 tests) 25ms
✓ tests/lifecycle.test.ts (20 tests) 29ms
✓ tests/events.test.ts (10 tests) 21ms
✓ tests/reactivity-computed.test.ts (9 tests) 11ms
✓ tests/composition.test.ts (8 tests) 20ms
✓ tests/component.test.ts (2 tests) 11ms
✓ tests/reactivity.test.ts (3 tests) 7ms
Test Files 9 passed (9)
Tests 162 passed (162)
npm run build

✅ Build passes (tsc + Sass + dist assembly).

Caveats / APIs skipped

AreaReason skipped
Router.props with queryParams in memory modeextras.queryParams are stored in #props then immediately overwritten by buildRoutePage(match) → match.params || {}. Documented as a known API nuance in the test.
document.title via full rendering pipelineApp.setPage(new Component()) may silently reject in happy-dom when custom element constructors reference unregistered elements; title test asserts typeof string only.
RefObject deep proxy reactive updatesSource has a console.log side-effect at RefObject.ts:296; not patched here per the "no production code changes" constraint.
ForEach, LazyLoad, VirtualScroll, DropDownAsync or layout-dependent APIs; deferred to a future PR.

No production files modified

This PR is tests only.

Roadmap alignment: Testing utilities and example tests.

cc @zico15

lucas-spinand others added 5 commits June 15, 2026 14:57
Adds 130 new tests across 4 new test files, bringing the suite total
from 32 tests (5 files) to 162 tests (9 files).
tests/router.test.ts (14 tests)
- Router.history, Router.pathname accessors
- Router.go() in memory mode — single and multi-hop navigation
- Router.go() void return type
- Router.props documents {} behaviour for routes without path params
- Redirect route resolves to target after async microtask flush
- Route title assertion scoped to a string (full pipeline caveat documented)
- Router.create() throws on second call
- Router.reload() no-throw
- Router.back() / Router.forward() — console.warn in memory mode
tests/forms.test.ts (39 tests)
- TextField: DOM mounting, inputElement tag, placeholder, value, name,
disabled, type, label, input/change event forwarding
- CheckBox: DOM mounting, input type, checked default/prop/setter, value,
name, label, disabled, variant radio/checkbox
- CheckBoxGroup: DOM mounting, children, selected null, onChange callback
- SwitchPanel: DOM mounting, input type, checked default/prop/setter, name,
label, change forwarding, toggle cycle
tests/reactive-collections.test.ts (57 tests)
- RefList: construction, id, valueOf, value, Symbol.toStringTag
- RefList mutations: push, pop, shift, unshift, splice, sort, reverse,
fill, clear, insert, removeItem, remove, value setter, update()
- RefList reactivity: subscribe (emit), listen (no-emit), unsubscribe
- RefList.syncComponentWithList: add, remove, reorder
- RefMap: construction, id, valueOf; mutations: set, delete, clear,
value setter, update(), toJSON; reactivity: subscribe, listen, unsubscribe
- RefObject: creation, subscribe, listen, unsubscribe, update, toJSON
- ref() factory: dispatches to RefList for arrays, RefMap for Maps
tests/lifecycle.test.ts (20 tests)
- DOM cleanup: remove, parent removal, re-append
- Re-render: swap components in container
- Reactive DOM updates: ref → textContent, style, boolean visibility,
multi-change propagation, unsubscribe stops updates
- RefList.syncComponentWithList: init, push, remove, clear flows
- Multi-subscriber fan-out and selective unsubscribe
- Component child management: append, innerHTML clear, deep nesting,
querySelectorAll
Caveats / APIs skipped:
- Router queryParams in memory mode: extras.queryParams are overwritten by
buildRoutePage(); documented as {} behaviour in the props test.
- Router document.title via full rendering pipeline: App.setPage() +
Component constructor may silently reject in happy-dom; title test
asserts typeof string only.
- RefObject deep-proxy reactive updates: console.log side-effect present
in the current source (RefObject.ts:296); not patched here.
- ForEach / LazyLoad / VirtualScroll / DropDown: async or layout-dependent
APIs; deferred to a future PR.
No production files were modified.
Roadmap alignment: Testing utilities and example tests.
@joaodibba
joaodibba merged commit b5a1377 into TypeComposer:mainJun 15, 2026
2 checks passed
joaodibba added a commit that referenced this pull request Jun 15, 2026
## Summary
Removes a debug `console.log("RefObject: emit", ...)` that was
accidentally left in the `createObservador()` Proxy `set` trap inside
`src/core/ref/RefObject.ts`.
The log fired on **every nested object property mutation**, leaking
internal reactive state (`{ key, value, target, receiver }`) to
production consoles.
**File changed:** `src/core/ref/RefObject.ts`
```diff
- console.log("RefObject: emit", {
- key, value, target, receiver
- });
container.emitAll('value', this.value);
```
---
## Regression tests added
Three new tests in `tests/reactive-collections.test.ts` (describe:
*RefObject – no console.log in production*):
| Test | Asserts |
|---|---|
| `nested property mutation does NOT call console.log` | proxy setter
fires without calling `console.log` |
| `update() does NOT call console.log` | `obj.update({...})` fires
without calling `console.log` |
| `subscriber is still notified after property mutation without
console.log` | reactivity still works (listen callback fires), no log |
---
## Validation
```
npm run test:run → 9 test files, 165 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 1** from the PR #15 review. Small, focused, no
architectural change.
Reviewer: @zico15
joaodibba pushed a commit that referenced this pull request Jun 15, 2026
Replaces four weak toBeTruthy / typeof-only assertions from the
RefObject (refObject) describe block (introduced in PR #15) with
exact behavioral assertions:
- 'creates a reactive object wrapper': now verifies id is a non-empty
string and that subscribe/update are functions (not just truthy).
- 'value exposes the wrapped plain object': now asserts the actual key
value (x === 1) rather than just that the value is truthy.
- 'update with new plain object replaces content': now counts subscriber
call invocations to confirm notification fires, and checks lastValue
is a defined object.
- 'toJSON serializes the object value': now checks the serialized shape
contains the original key/value pairs (key === 'value', count === 42).
All 162 tests pass. Addresses audit item 8.
joaodibba added a commit that referenced this pull request Jun 15, 2026
…#23)
## Summary
Follow-up to PR #15 — replaces four weak `toBeTruthy()` / `typeof`-only
assertions in the `RefObject (refObject)` describe block with exact
behavioral assertions that would catch real regressions.
**File changed:** `tests/reactive-collections.test.ts` (test-only
change, no production code modified)
| Old assertion (weak) | New assertion (exact) |
|---|---|
| `expect(obj).toBeTruthy()` | `expect(typeof obj.id).toBe('string')` +
`id.length > 0` + `subscribe`/`update` are functions |
| `expect(obj.value).toBeTruthy()` | `expect((obj.value as
any).x).toBe(1)` — checks actual wrapped value |
| `expect(lastValue).toBeTruthy()` after update | counts subscriber
calls (> baseCalls) and checks `lastValue` is a defined object |
| `expect(typeof json).toBe('object')` | checks `json.key === 'value'`
and `json.count === 42` — verifies serialized shape |
---
## Validation
```
npm run test:run → 9 test files, 162 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 8** (strengthen weak RefObject test assertions).
No production code changed.
Reviewer: @zico15
joaodibba added a commit to TypeComposer/docs that referenced this pull request Jun 15, 2026
## Summary
Marks the **Component Testing Framework** roadmap item as `completed`
(was `in-progress`).
### Why
The testing milestone is fully done across three merged PRs in
`TypeComposer/typecomposer`:
| PR | What it added |
|---|---|
| [#13](TypeComposer/typecomposer#13) | Testing
utilities (`tests/utils.ts` render helper) + CI integration (GitHub
Actions workflow) |
| [#14](TypeComposer/typecomposer#14) | Example
tests: computed properties, composition patterns, event handling |
| [#15](TypeComposer/typecomposer#15) | Expanded
tests: router, forms, reactive-collections, lifecycle hooks |
All three roadmap sub-items are now satisfied:
- ✅ Add testing utilities
- ✅ Create example tests
- ✅ Integrate with CI
### Change
**File:** `src/assets/roadmap.json` **Diff:** 1-line change — `"status": "in-progress"` → `"status":
"completed"` on the `Component Testing Framework` card (October 2025
milestone).
### Validation
```
npm run build → ✓ 3487 modules transformed, built in 16.82s (no errors)
```
cc @zico15
joaodibba added a commit that referenced this pull request Jun 15, 2026
…ualScroll tests (#24)
## Summary
Closes two deferred roadmap items from PR #15:
1. **`tests/README.md`** — contributor documentation for the test suite.
2. **`tests/async-components.test.ts`** — stable tests for `ForEach`,
`LazyLoad`, and `VirtualScroll`.
---
## What's in each file
### `tests/README.md`
| Section | What it covers |
|---|---|
| Running tests | `npm run test` (watch) vs `npm run test:run` (CI) |
| Test structure | Every file in `tests/`, what each one tests |
| `tests/utils.ts` render helper | `render()` signature, auto-cleanup,
example |
| `tests/setup.ts` | `MemoryStorage` polyfill, `beforeEach` storage
clear |
| Assertion patterns | DOM structure, styles, reactivity, async flush |
| happy-dom layout limitations | Which APIs return 0 and how to avoid
brittle tests |
| `vitest.config.ts` reference | Environment, globals, setupFiles |
| Adding new tests | Step-by-step checklist |
### `tests/async-components.test.ts`
**ForEach (11 tests):** custom element tag, empty innerHTML, `item`
getter default, `of` setter/getAttribute round-trip, `apply()` no-throw
and innerHTML-clear contract.
**LazyLoad (11 tests):** custom element tag, `loader`/`loaderProps`
stored correctly, `Component.lazyLoad` spy asserts it is called with
correct `fallback`/`props` on `onConnected`, not called when no loader
provided.
**VirtualScroll (13 tests, 1 skipped):** custom element tag, inner
container presence and `position: relative`, total height =
`items.length × itemHeight`, `overflow-y: auto`, initial render slice
under happy-dom (`scrollTop=0` → `buffer` items), custom `buffer`, empty
items, absolute positioning + px `top` + `100% width`, `renderItem`
factory call count, `top = index × itemHeight`, scroll event no-throw.
Scroll-position windowing skipped with a note pointing to E2E.
---
## Validation
```
npm run test:run
npx tsc --noEmit
```
```
Test Files 10 passed (10)
Tests 207 passed | 1 skipped (208)
Duration ~9s
tsc --noEmit: no errors
```
All pre-existing tests continue to pass.
---
## Reviewers
@zico15
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

test: cover router, forms, and reactive collections - #15

Merged
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections
Jun 15, 2026
Merged

test: cover router, forms, and reactive collections#15
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Summary

Follows up on the merged PR #14 (computed chains, composition, events) by adding test coverage for Router, form components, reactive collections, and component lifecycle/DOM-update patterns.

What changed

tests/router.test.ts (14 tests — new file)

  • Router.history returns configured mode ("memory")
  • Router.pathname at creation (root = "") and after go()
  • Router.go() — single and multi-hop navigation in memory mode
  • Router.go() resolves undefined (void return type)
  • Router.props — documents {} behaviour for routes without path params (known API nuance)
  • Redirect route (/old → /new) resolves to target after async microtask flush
  • Route title test — asserts typeof string only (full rendering pipeline limitation documented)
  • Router.create() throws on second call (singleton guard)
  • Router.reload() no-throw
  • Router.back() / Router.forward()console.warn in memory mode, never throw

tests/forms.test.ts (39 tests — new file)

  • TextField: DOM mounting, inputElement tag, placeholder, value, name, disabled get/set, type, label element presence, input/change event forwarding
  • CheckBox: DOM mounting, input[type=checkbox], checked default/prop/setter, value, name, label, disabled (Component level), variant radio/checkbox
  • CheckBoxGroup: DOM mounting, children, selected === null initially, onChange callback fires on change event
  • SwitchPanel: DOM mounting, input type, checked default/prop/setter, name, label, change forwarding, toggle cycle (false → true → false)

tests/reactive-collections.test.ts (57 tests — new file)

  • RefList: construction, id, valueOf(), value, Symbol.toStringTag; all mutation methods (push, pop, shift, unshift, splice, sort, reverse, fill, clear, insert, removeItem, remove, value setter, update()); reactivity (subscribe / listen / unsubscribe); RefList.syncComponentWithList (add, remove, reorder)
  • RefMap: construction, id, valueOf(); mutations (set, delete, clear, value setter, update(), toJSON()); reactivity (subscribe / listen / unsubscribe)
  • RefObject (via refObject()): creation, subscribe, listen, unsubscribe, update, toJSON
  • ref() factory: dispatches to RefList for arrays, RefMap for Maps

tests/lifecycle.test.ts (20 tests — new file)

  • DOM cleanup: remove(), parent removal cascades to children, re-append
  • Re-render: swap components in container, multiple swap cycle
  • Reactive DOM updates: ref → textContent, style, boolean visibility, multi-change propagation, unsubscribe stops DOM updates
  • RefList.syncComponentWithList driving DOM children: init, push settle, remove, clear
  • Multi-subscriber fan-out and selective unsubscribe
  • Component child management: append, innerHTML = '' clear, deep nesting, querySelectorAll

Validation

npm run test:run
 ✓ tests/forms.test.ts (39 tests) 50ms
✓ tests/router.test.ts (14 tests) 37ms
✓ tests/reactive-collections.test.ts (57 tests) 25ms
✓ tests/lifecycle.test.ts (20 tests) 29ms
✓ tests/events.test.ts (10 tests) 21ms
✓ tests/reactivity-computed.test.ts (9 tests) 11ms
✓ tests/composition.test.ts (8 tests) 20ms
✓ tests/component.test.ts (2 tests) 11ms
✓ tests/reactivity.test.ts (3 tests) 7ms
Test Files 9 passed (9)
Tests 162 passed (162)
npm run build

✅ Build passes (tsc + Sass + dist assembly).

Caveats / APIs skipped

AreaReason skipped
Router.props with queryParams in memory modeextras.queryParams are stored in #props then immediately overwritten by buildRoutePage(match) → match.params || {}. Documented as a known API nuance in the test.
document.title via full rendering pipelineApp.setPage(new Component()) may silently reject in happy-dom when custom element constructors reference unregistered elements; title test asserts typeof string only.
RefObject deep proxy reactive updatesSource has a console.log side-effect at RefObject.ts:296; not patched here per the "no production code changes" constraint.
ForEach, LazyLoad, VirtualScroll, DropDownAsync or layout-dependent APIs; deferred to a future PR.

No production files modified

This PR is tests only.

Roadmap alignment: Testing utilities and example tests.

cc @zico15

lucas-spinand others added 5 commits June 15, 2026 14:57
Adds 130 new tests across 4 new test files, bringing the suite total
from 32 tests (5 files) to 162 tests (9 files).
tests/router.test.ts (14 tests)
- Router.history, Router.pathname accessors
- Router.go() in memory mode — single and multi-hop navigation
- Router.go() void return type
- Router.props documents {} behaviour for routes without path params
- Redirect route resolves to target after async microtask flush
- Route title assertion scoped to a string (full pipeline caveat documented)
- Router.create() throws on second call
- Router.reload() no-throw
- Router.back() / Router.forward() — console.warn in memory mode
tests/forms.test.ts (39 tests)
- TextField: DOM mounting, inputElement tag, placeholder, value, name,
disabled, type, label, input/change event forwarding
- CheckBox: DOM mounting, input type, checked default/prop/setter, value,
name, label, disabled, variant radio/checkbox
- CheckBoxGroup: DOM mounting, children, selected null, onChange callback
- SwitchPanel: DOM mounting, input type, checked default/prop/setter, name,
label, change forwarding, toggle cycle
tests/reactive-collections.test.ts (57 tests)
- RefList: construction, id, valueOf, value, Symbol.toStringTag
- RefList mutations: push, pop, shift, unshift, splice, sort, reverse,
fill, clear, insert, removeItem, remove, value setter, update()
- RefList reactivity: subscribe (emit), listen (no-emit), unsubscribe
- RefList.syncComponentWithList: add, remove, reorder
- RefMap: construction, id, valueOf; mutations: set, delete, clear,
value setter, update(), toJSON; reactivity: subscribe, listen, unsubscribe
- RefObject: creation, subscribe, listen, unsubscribe, update, toJSON
- ref() factory: dispatches to RefList for arrays, RefMap for Maps
tests/lifecycle.test.ts (20 tests)
- DOM cleanup: remove, parent removal, re-append
- Re-render: swap components in container
- Reactive DOM updates: ref → textContent, style, boolean visibility,
multi-change propagation, unsubscribe stops updates
- RefList.syncComponentWithList: init, push, remove, clear flows
- Multi-subscriber fan-out and selective unsubscribe
- Component child management: append, innerHTML clear, deep nesting,
querySelectorAll
Caveats / APIs skipped:
- Router queryParams in memory mode: extras.queryParams are overwritten by
buildRoutePage(); documented as {} behaviour in the props test.
- Router document.title via full rendering pipeline: App.setPage() +
Component constructor may silently reject in happy-dom; title test
asserts typeof string only.
- RefObject deep-proxy reactive updates: console.log side-effect present
in the current source (RefObject.ts:296); not patched here.
- ForEach / LazyLoad / VirtualScroll / DropDown: async or layout-dependent
APIs; deferred to a future PR.
No production files were modified.
Roadmap alignment: Testing utilities and example tests.
@joaodibba
joaodibba merged commit b5a1377 into TypeComposer:mainJun 15, 2026
2 checks passed
joaodibba added a commit that referenced this pull request Jun 15, 2026
## Summary
Removes a debug `console.log("RefObject: emit", ...)` that was
accidentally left in the `createObservador()` Proxy `set` trap inside
`src/core/ref/RefObject.ts`.
The log fired on **every nested object property mutation**, leaking
internal reactive state (`{ key, value, target, receiver }`) to
production consoles.
**File changed:** `src/core/ref/RefObject.ts`
```diff
- console.log("RefObject: emit", {
- key, value, target, receiver
- });
container.emitAll('value', this.value);
```
---
## Regression tests added
Three new tests in `tests/reactive-collections.test.ts` (describe:
*RefObject – no console.log in production*):
| Test | Asserts |
|---|---|
| `nested property mutation does NOT call console.log` | proxy setter
fires without calling `console.log` |
| `update() does NOT call console.log` | `obj.update({...})` fires
without calling `console.log` |
| `subscriber is still notified after property mutation without
console.log` | reactivity still works (listen callback fires), no log |
---
## Validation
```
npm run test:run → 9 test files, 165 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 1** from the PR #15 review. Small, focused, no
architectural change.
Reviewer: @zico15
joaodibba pushed a commit that referenced this pull request Jun 15, 2026
Replaces four weak toBeTruthy / typeof-only assertions from the
RefObject (refObject) describe block (introduced in PR #15) with
exact behavioral assertions:
- 'creates a reactive object wrapper': now verifies id is a non-empty
string and that subscribe/update are functions (not just truthy).
- 'value exposes the wrapped plain object': now asserts the actual key
value (x === 1) rather than just that the value is truthy.
- 'update with new plain object replaces content': now counts subscriber
call invocations to confirm notification fires, and checks lastValue
is a defined object.
- 'toJSON serializes the object value': now checks the serialized shape
contains the original key/value pairs (key === 'value', count === 42).
All 162 tests pass. Addresses audit item 8.
joaodibba added a commit that referenced this pull request Jun 15, 2026
…#23)
## Summary
Follow-up to PR #15 — replaces four weak `toBeTruthy()` / `typeof`-only
assertions in the `RefObject (refObject)` describe block with exact
behavioral assertions that would catch real regressions.
**File changed:** `tests/reactive-collections.test.ts` (test-only
change, no production code modified)
| Old assertion (weak) | New assertion (exact) |
|---|---|
| `expect(obj).toBeTruthy()` | `expect(typeof obj.id).toBe('string')` +
`id.length > 0` + `subscribe`/`update` are functions |
| `expect(obj.value).toBeTruthy()` | `expect((obj.value as
any).x).toBe(1)` — checks actual wrapped value |
| `expect(lastValue).toBeTruthy()` after update | counts subscriber
calls (> baseCalls) and checks `lastValue` is a defined object |
| `expect(typeof json).toBe('object')` | checks `json.key === 'value'`
and `json.count === 42` — verifies serialized shape |
---
## Validation
```
npm run test:run → 9 test files, 162 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 8** (strengthen weak RefObject test assertions).
No production code changed.
Reviewer: @zico15
joaodibba added a commit to TypeComposer/docs that referenced this pull request Jun 15, 2026
## Summary
Marks the **Component Testing Framework** roadmap item as `completed`
(was `in-progress`).
### Why
The testing milestone is fully done across three merged PRs in
`TypeComposer/typecomposer`:
| PR | What it added |
|---|---|
| [#13](TypeComposer/typecomposer#13) | Testing
utilities (`tests/utils.ts` render helper) + CI integration (GitHub
Actions workflow) |
| [#14](TypeComposer/typecomposer#14) | Example
tests: computed properties, composition patterns, event handling |
| [#15](TypeComposer/typecomposer#15) | Expanded
tests: router, forms, reactive-collections, lifecycle hooks |
All three roadmap sub-items are now satisfied:
- ✅ Add testing utilities
- ✅ Create example tests
- ✅ Integrate with CI
### Change
**File:** `src/assets/roadmap.json` **Diff:** 1-line change — `"status": "in-progress"` → `"status":
"completed"` on the `Component Testing Framework` card (October 2025
milestone).
### Validation
```
npm run build → ✓ 3487 modules transformed, built in 16.82s (no errors)
```
cc @zico15
joaodibba added a commit that referenced this pull request Jun 15, 2026
…ualScroll tests (#24)
## Summary
Closes two deferred roadmap items from PR #15:
1. **`tests/README.md`** — contributor documentation for the test suite.
2. **`tests/async-components.test.ts`** — stable tests for `ForEach`,
`LazyLoad`, and `VirtualScroll`.
---
## What's in each file
### `tests/README.md`
| Section | What it covers |
|---|---|
| Running tests | `npm run test` (watch) vs `npm run test:run` (CI) |
| Test structure | Every file in `tests/`, what each one tests |
| `tests/utils.ts` render helper | `render()` signature, auto-cleanup,
example |
| `tests/setup.ts` | `MemoryStorage` polyfill, `beforeEach` storage
clear |
| Assertion patterns | DOM structure, styles, reactivity, async flush |
| happy-dom layout limitations | Which APIs return 0 and how to avoid
brittle tests |
| `vitest.config.ts` reference | Environment, globals, setupFiles |
| Adding new tests | Step-by-step checklist |
### `tests/async-components.test.ts`
**ForEach (11 tests):** custom element tag, empty innerHTML, `item`
getter default, `of` setter/getAttribute round-trip, `apply()` no-throw
and innerHTML-clear contract.
**LazyLoad (11 tests):** custom element tag, `loader`/`loaderProps`
stored correctly, `Component.lazyLoad` spy asserts it is called with
correct `fallback`/`props` on `onConnected`, not called when no loader
provided.
**VirtualScroll (13 tests, 1 skipped):** custom element tag, inner
container presence and `position: relative`, total height =
`items.length × itemHeight`, `overflow-y: auto`, initial render slice
under happy-dom (`scrollTop=0` → `buffer` items), custom `buffer`, empty
items, absolute positioning + px `top` + `100% width`, `renderItem`
factory call count, `top = index × itemHeight`, scroll event no-throw.
Scroll-position windowing skipped with a note pointing to E2E.
---
## Validation
```
npm run test:run
npx tsc --noEmit
```
```
Test Files 10 passed (10)
Tests 207 passed | 1 skipped (208)
Duration ~9s
tsc --noEmit: no errors
```
All pre-existing tests continue to pass.
---
## Reviewers
@zico15
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

test: cover router, forms, and reactive collections - #15

Merged
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections
Jun 15, 2026
Merged

test: cover router, forms, and reactive collections#15
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Summary

Follows up on the merged PR #14 (computed chains, composition, events) by adding test coverage for Router, form components, reactive collections, and component lifecycle/DOM-update patterns.

What changed

tests/router.test.ts (14 tests — new file)

  • Router.history returns configured mode ("memory")
  • Router.pathname at creation (root = "") and after go()
  • Router.go() — single and multi-hop navigation in memory mode
  • Router.go() resolves undefined (void return type)
  • Router.props — documents {} behaviour for routes without path params (known API nuance)
  • Redirect route (/old → /new) resolves to target after async microtask flush
  • Route title test — asserts typeof string only (full rendering pipeline limitation documented)
  • Router.create() throws on second call (singleton guard)
  • Router.reload() no-throw
  • Router.back() / Router.forward()console.warn in memory mode, never throw

tests/forms.test.ts (39 tests — new file)

  • TextField: DOM mounting, inputElement tag, placeholder, value, name, disabled get/set, type, label element presence, input/change event forwarding
  • CheckBox: DOM mounting, input[type=checkbox], checked default/prop/setter, value, name, label, disabled (Component level), variant radio/checkbox
  • CheckBoxGroup: DOM mounting, children, selected === null initially, onChange callback fires on change event
  • SwitchPanel: DOM mounting, input type, checked default/prop/setter, name, label, change forwarding, toggle cycle (false → true → false)

tests/reactive-collections.test.ts (57 tests — new file)

  • RefList: construction, id, valueOf(), value, Symbol.toStringTag; all mutation methods (push, pop, shift, unshift, splice, sort, reverse, fill, clear, insert, removeItem, remove, value setter, update()); reactivity (subscribe / listen / unsubscribe); RefList.syncComponentWithList (add, remove, reorder)
  • RefMap: construction, id, valueOf(); mutations (set, delete, clear, value setter, update(), toJSON()); reactivity (subscribe / listen / unsubscribe)
  • RefObject (via refObject()): creation, subscribe, listen, unsubscribe, update, toJSON
  • ref() factory: dispatches to RefList for arrays, RefMap for Maps

tests/lifecycle.test.ts (20 tests — new file)

  • DOM cleanup: remove(), parent removal cascades to children, re-append
  • Re-render: swap components in container, multiple swap cycle
  • Reactive DOM updates: ref → textContent, style, boolean visibility, multi-change propagation, unsubscribe stops DOM updates
  • RefList.syncComponentWithList driving DOM children: init, push settle, remove, clear
  • Multi-subscriber fan-out and selective unsubscribe
  • Component child management: append, innerHTML = '' clear, deep nesting, querySelectorAll

Validation

npm run test:run
 ✓ tests/forms.test.ts (39 tests) 50ms
✓ tests/router.test.ts (14 tests) 37ms
✓ tests/reactive-collections.test.ts (57 tests) 25ms
✓ tests/lifecycle.test.ts (20 tests) 29ms
✓ tests/events.test.ts (10 tests) 21ms
✓ tests/reactivity-computed.test.ts (9 tests) 11ms
✓ tests/composition.test.ts (8 tests) 20ms
✓ tests/component.test.ts (2 tests) 11ms
✓ tests/reactivity.test.ts (3 tests) 7ms
Test Files 9 passed (9)
Tests 162 passed (162)
npm run build

✅ Build passes (tsc + Sass + dist assembly).

Caveats / APIs skipped

AreaReason skipped
Router.props with queryParams in memory modeextras.queryParams are stored in #props then immediately overwritten by buildRoutePage(match) → match.params || {}. Documented as a known API nuance in the test.
document.title via full rendering pipelineApp.setPage(new Component()) may silently reject in happy-dom when custom element constructors reference unregistered elements; title test asserts typeof string only.
RefObject deep proxy reactive updatesSource has a console.log side-effect at RefObject.ts:296; not patched here per the "no production code changes" constraint.
ForEach, LazyLoad, VirtualScroll, DropDownAsync or layout-dependent APIs; deferred to a future PR.

No production files modified

This PR is tests only.

Roadmap alignment: Testing utilities and example tests.

cc @zico15

lucas-spinand others added 5 commits June 15, 2026 14:57
Adds 130 new tests across 4 new test files, bringing the suite total
from 32 tests (5 files) to 162 tests (9 files).
tests/router.test.ts (14 tests)
- Router.history, Router.pathname accessors
- Router.go() in memory mode — single and multi-hop navigation
- Router.go() void return type
- Router.props documents {} behaviour for routes without path params
- Redirect route resolves to target after async microtask flush
- Route title assertion scoped to a string (full pipeline caveat documented)
- Router.create() throws on second call
- Router.reload() no-throw
- Router.back() / Router.forward() — console.warn in memory mode
tests/forms.test.ts (39 tests)
- TextField: DOM mounting, inputElement tag, placeholder, value, name,
disabled, type, label, input/change event forwarding
- CheckBox: DOM mounting, input type, checked default/prop/setter, value,
name, label, disabled, variant radio/checkbox
- CheckBoxGroup: DOM mounting, children, selected null, onChange callback
- SwitchPanel: DOM mounting, input type, checked default/prop/setter, name,
label, change forwarding, toggle cycle
tests/reactive-collections.test.ts (57 tests)
- RefList: construction, id, valueOf, value, Symbol.toStringTag
- RefList mutations: push, pop, shift, unshift, splice, sort, reverse,
fill, clear, insert, removeItem, remove, value setter, update()
- RefList reactivity: subscribe (emit), listen (no-emit), unsubscribe
- RefList.syncComponentWithList: add, remove, reorder
- RefMap: construction, id, valueOf; mutations: set, delete, clear,
value setter, update(), toJSON; reactivity: subscribe, listen, unsubscribe
- RefObject: creation, subscribe, listen, unsubscribe, update, toJSON
- ref() factory: dispatches to RefList for arrays, RefMap for Maps
tests/lifecycle.test.ts (20 tests)
- DOM cleanup: remove, parent removal, re-append
- Re-render: swap components in container
- Reactive DOM updates: ref → textContent, style, boolean visibility,
multi-change propagation, unsubscribe stops updates
- RefList.syncComponentWithList: init, push, remove, clear flows
- Multi-subscriber fan-out and selective unsubscribe
- Component child management: append, innerHTML clear, deep nesting,
querySelectorAll
Caveats / APIs skipped:
- Router queryParams in memory mode: extras.queryParams are overwritten by
buildRoutePage(); documented as {} behaviour in the props test.
- Router document.title via full rendering pipeline: App.setPage() +
Component constructor may silently reject in happy-dom; title test
asserts typeof string only.
- RefObject deep-proxy reactive updates: console.log side-effect present
in the current source (RefObject.ts:296); not patched here.
- ForEach / LazyLoad / VirtualScroll / DropDown: async or layout-dependent
APIs; deferred to a future PR.
No production files were modified.
Roadmap alignment: Testing utilities and example tests.
@joaodibba
joaodibba merged commit b5a1377 into TypeComposer:mainJun 15, 2026
2 checks passed
joaodibba added a commit that referenced this pull request Jun 15, 2026
## Summary
Removes a debug `console.log("RefObject: emit", ...)` that was
accidentally left in the `createObservador()` Proxy `set` trap inside
`src/core/ref/RefObject.ts`.
The log fired on **every nested object property mutation**, leaking
internal reactive state (`{ key, value, target, receiver }`) to
production consoles.
**File changed:** `src/core/ref/RefObject.ts`
```diff
- console.log("RefObject: emit", {
- key, value, target, receiver
- });
container.emitAll('value', this.value);
```
---
## Regression tests added
Three new tests in `tests/reactive-collections.test.ts` (describe:
*RefObject – no console.log in production*):
| Test | Asserts |
|---|---|
| `nested property mutation does NOT call console.log` | proxy setter
fires without calling `console.log` |
| `update() does NOT call console.log` | `obj.update({...})` fires
without calling `console.log` |
| `subscriber is still notified after property mutation without
console.log` | reactivity still works (listen callback fires), no log |
---
## Validation
```
npm run test:run → 9 test files, 165 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 1** from the PR #15 review. Small, focused, no
architectural change.
Reviewer: @zico15
joaodibba pushed a commit that referenced this pull request Jun 15, 2026
Replaces four weak toBeTruthy / typeof-only assertions from the
RefObject (refObject) describe block (introduced in PR #15) with
exact behavioral assertions:
- 'creates a reactive object wrapper': now verifies id is a non-empty
string and that subscribe/update are functions (not just truthy).
- 'value exposes the wrapped plain object': now asserts the actual key
value (x === 1) rather than just that the value is truthy.
- 'update with new plain object replaces content': now counts subscriber
call invocations to confirm notification fires, and checks lastValue
is a defined object.
- 'toJSON serializes the object value': now checks the serialized shape
contains the original key/value pairs (key === 'value', count === 42).
All 162 tests pass. Addresses audit item 8.
joaodibba added a commit that referenced this pull request Jun 15, 2026
…#23)
## Summary
Follow-up to PR #15 — replaces four weak `toBeTruthy()` / `typeof`-only
assertions in the `RefObject (refObject)` describe block with exact
behavioral assertions that would catch real regressions.
**File changed:** `tests/reactive-collections.test.ts` (test-only
change, no production code modified)
| Old assertion (weak) | New assertion (exact) |
|---|---|
| `expect(obj).toBeTruthy()` | `expect(typeof obj.id).toBe('string')` +
`id.length > 0` + `subscribe`/`update` are functions |
| `expect(obj.value).toBeTruthy()` | `expect((obj.value as
any).x).toBe(1)` — checks actual wrapped value |
| `expect(lastValue).toBeTruthy()` after update | counts subscriber
calls (> baseCalls) and checks `lastValue` is a defined object |
| `expect(typeof json).toBe('object')` | checks `json.key === 'value'`
and `json.count === 42` — verifies serialized shape |
---
## Validation
```
npm run test:run → 9 test files, 162 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 8** (strengthen weak RefObject test assertions).
No production code changed.
Reviewer: @zico15
joaodibba added a commit to TypeComposer/docs that referenced this pull request Jun 15, 2026
## Summary
Marks the **Component Testing Framework** roadmap item as `completed`
(was `in-progress`).
### Why
The testing milestone is fully done across three merged PRs in
`TypeComposer/typecomposer`:
| PR | What it added |
|---|---|
| [#13](TypeComposer/typecomposer#13) | Testing
utilities (`tests/utils.ts` render helper) + CI integration (GitHub
Actions workflow) |
| [#14](TypeComposer/typecomposer#14) | Example
tests: computed properties, composition patterns, event handling |
| [#15](TypeComposer/typecomposer#15) | Expanded
tests: router, forms, reactive-collections, lifecycle hooks |
All three roadmap sub-items are now satisfied:
- ✅ Add testing utilities
- ✅ Create example tests
- ✅ Integrate with CI
### Change
**File:** `src/assets/roadmap.json` **Diff:** 1-line change — `"status": "in-progress"` → `"status":
"completed"` on the `Component Testing Framework` card (October 2025
milestone).
### Validation
```
npm run build → ✓ 3487 modules transformed, built in 16.82s (no errors)
```
cc @zico15
joaodibba added a commit that referenced this pull request Jun 15, 2026
…ualScroll tests (#24)
## Summary
Closes two deferred roadmap items from PR #15:
1. **`tests/README.md`** — contributor documentation for the test suite.
2. **`tests/async-components.test.ts`** — stable tests for `ForEach`,
`LazyLoad`, and `VirtualScroll`.
---
## What's in each file
### `tests/README.md`
| Section | What it covers |
|---|---|
| Running tests | `npm run test` (watch) vs `npm run test:run` (CI) |
| Test structure | Every file in `tests/`, what each one tests |
| `tests/utils.ts` render helper | `render()` signature, auto-cleanup,
example |
| `tests/setup.ts` | `MemoryStorage` polyfill, `beforeEach` storage
clear |
| Assertion patterns | DOM structure, styles, reactivity, async flush |
| happy-dom layout limitations | Which APIs return 0 and how to avoid
brittle tests |
| `vitest.config.ts` reference | Environment, globals, setupFiles |
| Adding new tests | Step-by-step checklist |
### `tests/async-components.test.ts`
**ForEach (11 tests):** custom element tag, empty innerHTML, `item`
getter default, `of` setter/getAttribute round-trip, `apply()` no-throw
and innerHTML-clear contract.
**LazyLoad (11 tests):** custom element tag, `loader`/`loaderProps`
stored correctly, `Component.lazyLoad` spy asserts it is called with
correct `fallback`/`props` on `onConnected`, not called when no loader
provided.
**VirtualScroll (13 tests, 1 skipped):** custom element tag, inner
container presence and `position: relative`, total height =
`items.length × itemHeight`, `overflow-y: auto`, initial render slice
under happy-dom (`scrollTop=0` → `buffer` items), custom `buffer`, empty
items, absolute positioning + px `top` + `100% width`, `renderItem`
factory call count, `top = index × itemHeight`, scroll event no-throw.
Scroll-position windowing skipped with a note pointing to E2E.
---
## Validation
```
npm run test:run
npx tsc --noEmit
```
```
Test Files 10 passed (10)
Tests 207 passed | 1 skipped (208)
Duration ~9s
tsc --noEmit: no errors
```
All pre-existing tests continue to pass.
---
## Reviewers
@zico15
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

test: cover router, forms, and reactive collections - #15

Merged
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections
Jun 15, 2026
Merged

test: cover router, forms, and reactive collections#15
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Summary

Follows up on the merged PR #14 (computed chains, composition, events) by adding test coverage for Router, form components, reactive collections, and component lifecycle/DOM-update patterns.

What changed

tests/router.test.ts (14 tests — new file)

  • Router.history returns configured mode ("memory")
  • Router.pathname at creation (root = "") and after go()
  • Router.go() — single and multi-hop navigation in memory mode
  • Router.go() resolves undefined (void return type)
  • Router.props — documents {} behaviour for routes without path params (known API nuance)
  • Redirect route (/old → /new) resolves to target after async microtask flush
  • Route title test — asserts typeof string only (full rendering pipeline limitation documented)
  • Router.create() throws on second call (singleton guard)
  • Router.reload() no-throw
  • Router.back() / Router.forward()console.warn in memory mode, never throw

tests/forms.test.ts (39 tests — new file)

  • TextField: DOM mounting, inputElement tag, placeholder, value, name, disabled get/set, type, label element presence, input/change event forwarding
  • CheckBox: DOM mounting, input[type=checkbox], checked default/prop/setter, value, name, label, disabled (Component level), variant radio/checkbox
  • CheckBoxGroup: DOM mounting, children, selected === null initially, onChange callback fires on change event
  • SwitchPanel: DOM mounting, input type, checked default/prop/setter, name, label, change forwarding, toggle cycle (false → true → false)

tests/reactive-collections.test.ts (57 tests — new file)

  • RefList: construction, id, valueOf(), value, Symbol.toStringTag; all mutation methods (push, pop, shift, unshift, splice, sort, reverse, fill, clear, insert, removeItem, remove, value setter, update()); reactivity (subscribe / listen / unsubscribe); RefList.syncComponentWithList (add, remove, reorder)
  • RefMap: construction, id, valueOf(); mutations (set, delete, clear, value setter, update(), toJSON()); reactivity (subscribe / listen / unsubscribe)
  • RefObject (via refObject()): creation, subscribe, listen, unsubscribe, update, toJSON
  • ref() factory: dispatches to RefList for arrays, RefMap for Maps

tests/lifecycle.test.ts (20 tests — new file)

  • DOM cleanup: remove(), parent removal cascades to children, re-append
  • Re-render: swap components in container, multiple swap cycle
  • Reactive DOM updates: ref → textContent, style, boolean visibility, multi-change propagation, unsubscribe stops DOM updates
  • RefList.syncComponentWithList driving DOM children: init, push settle, remove, clear
  • Multi-subscriber fan-out and selective unsubscribe
  • Component child management: append, innerHTML = '' clear, deep nesting, querySelectorAll

Validation

npm run test:run
 ✓ tests/forms.test.ts (39 tests) 50ms
✓ tests/router.test.ts (14 tests) 37ms
✓ tests/reactive-collections.test.ts (57 tests) 25ms
✓ tests/lifecycle.test.ts (20 tests) 29ms
✓ tests/events.test.ts (10 tests) 21ms
✓ tests/reactivity-computed.test.ts (9 tests) 11ms
✓ tests/composition.test.ts (8 tests) 20ms
✓ tests/component.test.ts (2 tests) 11ms
✓ tests/reactivity.test.ts (3 tests) 7ms
Test Files 9 passed (9)
Tests 162 passed (162)
npm run build

✅ Build passes (tsc + Sass + dist assembly).

Caveats / APIs skipped

AreaReason skipped
Router.props with queryParams in memory modeextras.queryParams are stored in #props then immediately overwritten by buildRoutePage(match) → match.params || {}. Documented as a known API nuance in the test.
document.title via full rendering pipelineApp.setPage(new Component()) may silently reject in happy-dom when custom element constructors reference unregistered elements; title test asserts typeof string only.
RefObject deep proxy reactive updatesSource has a console.log side-effect at RefObject.ts:296; not patched here per the "no production code changes" constraint.
ForEach, LazyLoad, VirtualScroll, DropDownAsync or layout-dependent APIs; deferred to a future PR.

No production files modified

This PR is tests only.

Roadmap alignment: Testing utilities and example tests.

cc @zico15

lucas-spinand others added 5 commits June 15, 2026 14:57
Adds 130 new tests across 4 new test files, bringing the suite total
from 32 tests (5 files) to 162 tests (9 files).
tests/router.test.ts (14 tests)
- Router.history, Router.pathname accessors
- Router.go() in memory mode — single and multi-hop navigation
- Router.go() void return type
- Router.props documents {} behaviour for routes without path params
- Redirect route resolves to target after async microtask flush
- Route title assertion scoped to a string (full pipeline caveat documented)
- Router.create() throws on second call
- Router.reload() no-throw
- Router.back() / Router.forward() — console.warn in memory mode
tests/forms.test.ts (39 tests)
- TextField: DOM mounting, inputElement tag, placeholder, value, name,
disabled, type, label, input/change event forwarding
- CheckBox: DOM mounting, input type, checked default/prop/setter, value,
name, label, disabled, variant radio/checkbox
- CheckBoxGroup: DOM mounting, children, selected null, onChange callback
- SwitchPanel: DOM mounting, input type, checked default/prop/setter, name,
label, change forwarding, toggle cycle
tests/reactive-collections.test.ts (57 tests)
- RefList: construction, id, valueOf, value, Symbol.toStringTag
- RefList mutations: push, pop, shift, unshift, splice, sort, reverse,
fill, clear, insert, removeItem, remove, value setter, update()
- RefList reactivity: subscribe (emit), listen (no-emit), unsubscribe
- RefList.syncComponentWithList: add, remove, reorder
- RefMap: construction, id, valueOf; mutations: set, delete, clear,
value setter, update(), toJSON; reactivity: subscribe, listen, unsubscribe
- RefObject: creation, subscribe, listen, unsubscribe, update, toJSON
- ref() factory: dispatches to RefList for arrays, RefMap for Maps
tests/lifecycle.test.ts (20 tests)
- DOM cleanup: remove, parent removal, re-append
- Re-render: swap components in container
- Reactive DOM updates: ref → textContent, style, boolean visibility,
multi-change propagation, unsubscribe stops updates
- RefList.syncComponentWithList: init, push, remove, clear flows
- Multi-subscriber fan-out and selective unsubscribe
- Component child management: append, innerHTML clear, deep nesting,
querySelectorAll
Caveats / APIs skipped:
- Router queryParams in memory mode: extras.queryParams are overwritten by
buildRoutePage(); documented as {} behaviour in the props test.
- Router document.title via full rendering pipeline: App.setPage() +
Component constructor may silently reject in happy-dom; title test
asserts typeof string only.
- RefObject deep-proxy reactive updates: console.log side-effect present
in the current source (RefObject.ts:296); not patched here.
- ForEach / LazyLoad / VirtualScroll / DropDown: async or layout-dependent
APIs; deferred to a future PR.
No production files were modified.
Roadmap alignment: Testing utilities and example tests.
@joaodibba
joaodibba merged commit b5a1377 into TypeComposer:mainJun 15, 2026
2 checks passed
joaodibba added a commit that referenced this pull request Jun 15, 2026
## Summary
Removes a debug `console.log("RefObject: emit", ...)` that was
accidentally left in the `createObservador()` Proxy `set` trap inside
`src/core/ref/RefObject.ts`.
The log fired on **every nested object property mutation**, leaking
internal reactive state (`{ key, value, target, receiver }`) to
production consoles.
**File changed:** `src/core/ref/RefObject.ts`
```diff
- console.log("RefObject: emit", {
- key, value, target, receiver
- });
container.emitAll('value', this.value);
```
---
## Regression tests added
Three new tests in `tests/reactive-collections.test.ts` (describe:
*RefObject – no console.log in production*):
| Test | Asserts |
|---|---|
| `nested property mutation does NOT call console.log` | proxy setter
fires without calling `console.log` |
| `update() does NOT call console.log` | `obj.update({...})` fires
without calling `console.log` |
| `subscriber is still notified after property mutation without
console.log` | reactivity still works (listen callback fires), no log |
---
## Validation
```
npm run test:run → 9 test files, 165 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 1** from the PR #15 review. Small, focused, no
architectural change.
Reviewer: @zico15
joaodibba pushed a commit that referenced this pull request Jun 15, 2026
Replaces four weak toBeTruthy / typeof-only assertions from the
RefObject (refObject) describe block (introduced in PR #15) with
exact behavioral assertions:
- 'creates a reactive object wrapper': now verifies id is a non-empty
string and that subscribe/update are functions (not just truthy).
- 'value exposes the wrapped plain object': now asserts the actual key
value (x === 1) rather than just that the value is truthy.
- 'update with new plain object replaces content': now counts subscriber
call invocations to confirm notification fires, and checks lastValue
is a defined object.
- 'toJSON serializes the object value': now checks the serialized shape
contains the original key/value pairs (key === 'value', count === 42).
All 162 tests pass. Addresses audit item 8.
joaodibba added a commit that referenced this pull request Jun 15, 2026
…#23)
## Summary
Follow-up to PR #15 — replaces four weak `toBeTruthy()` / `typeof`-only
assertions in the `RefObject (refObject)` describe block with exact
behavioral assertions that would catch real regressions.
**File changed:** `tests/reactive-collections.test.ts` (test-only
change, no production code modified)
| Old assertion (weak) | New assertion (exact) |
|---|---|
| `expect(obj).toBeTruthy()` | `expect(typeof obj.id).toBe('string')` +
`id.length > 0` + `subscribe`/`update` are functions |
| `expect(obj.value).toBeTruthy()` | `expect((obj.value as
any).x).toBe(1)` — checks actual wrapped value |
| `expect(lastValue).toBeTruthy()` after update | counts subscriber
calls (> baseCalls) and checks `lastValue` is a defined object |
| `expect(typeof json).toBe('object')` | checks `json.key === 'value'`
and `json.count === 42` — verifies serialized shape |
---
## Validation
```
npm run test:run → 9 test files, 162 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 8** (strengthen weak RefObject test assertions).
No production code changed.
Reviewer: @zico15
joaodibba added a commit to TypeComposer/docs that referenced this pull request Jun 15, 2026
## Summary
Marks the **Component Testing Framework** roadmap item as `completed`
(was `in-progress`).
### Why
The testing milestone is fully done across three merged PRs in
`TypeComposer/typecomposer`:
| PR | What it added |
|---|---|
| [#13](TypeComposer/typecomposer#13) | Testing
utilities (`tests/utils.ts` render helper) + CI integration (GitHub
Actions workflow) |
| [#14](TypeComposer/typecomposer#14) | Example
tests: computed properties, composition patterns, event handling |
| [#15](TypeComposer/typecomposer#15) | Expanded
tests: router, forms, reactive-collections, lifecycle hooks |
All three roadmap sub-items are now satisfied:
- ✅ Add testing utilities
- ✅ Create example tests
- ✅ Integrate with CI
### Change
**File:** `src/assets/roadmap.json` **Diff:** 1-line change — `"status": "in-progress"` → `"status":
"completed"` on the `Component Testing Framework` card (October 2025
milestone).
### Validation
```
npm run build → ✓ 3487 modules transformed, built in 16.82s (no errors)
```
cc @zico15
joaodibba added a commit that referenced this pull request Jun 15, 2026
…ualScroll tests (#24)
## Summary
Closes two deferred roadmap items from PR #15:
1. **`tests/README.md`** — contributor documentation for the test suite.
2. **`tests/async-components.test.ts`** — stable tests for `ForEach`,
`LazyLoad`, and `VirtualScroll`.
---
## What's in each file
### `tests/README.md`
| Section | What it covers |
|---|---|
| Running tests | `npm run test` (watch) vs `npm run test:run` (CI) |
| Test structure | Every file in `tests/`, what each one tests |
| `tests/utils.ts` render helper | `render()` signature, auto-cleanup,
example |
| `tests/setup.ts` | `MemoryStorage` polyfill, `beforeEach` storage
clear |
| Assertion patterns | DOM structure, styles, reactivity, async flush |
| happy-dom layout limitations | Which APIs return 0 and how to avoid
brittle tests |
| `vitest.config.ts` reference | Environment, globals, setupFiles |
| Adding new tests | Step-by-step checklist |
### `tests/async-components.test.ts`
**ForEach (11 tests):** custom element tag, empty innerHTML, `item`
getter default, `of` setter/getAttribute round-trip, `apply()` no-throw
and innerHTML-clear contract.
**LazyLoad (11 tests):** custom element tag, `loader`/`loaderProps`
stored correctly, `Component.lazyLoad` spy asserts it is called with
correct `fallback`/`props` on `onConnected`, not called when no loader
provided.
**VirtualScroll (13 tests, 1 skipped):** custom element tag, inner
container presence and `position: relative`, total height =
`items.length × itemHeight`, `overflow-y: auto`, initial render slice
under happy-dom (`scrollTop=0` → `buffer` items), custom `buffer`, empty
items, absolute positioning + px `top` + `100% width`, `renderItem`
factory call count, `top = index × itemHeight`, scroll event no-throw.
Scroll-position windowing skipped with a note pointing to E2E.
---
## Validation
```
npm run test:run
npx tsc --noEmit
```
```
Test Files 10 passed (10)
Tests 207 passed | 1 skipped (208)
Duration ~9s
tsc --noEmit: no errors
```
All pre-existing tests continue to pass.
---
## Reviewers
@zico15
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

test: cover router, forms, and reactive collections - #15

Merged
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections
Jun 15, 2026
Merged

test: cover router, forms, and reactive collections#15
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Summary

Follows up on the merged PR #14 (computed chains, composition, events) by adding test coverage for Router, form components, reactive collections, and component lifecycle/DOM-update patterns.

What changed

tests/router.test.ts (14 tests — new file)

  • Router.history returns configured mode ("memory")
  • Router.pathname at creation (root = "") and after go()
  • Router.go() — single and multi-hop navigation in memory mode
  • Router.go() resolves undefined (void return type)
  • Router.props — documents {} behaviour for routes without path params (known API nuance)
  • Redirect route (/old → /new) resolves to target after async microtask flush
  • Route title test — asserts typeof string only (full rendering pipeline limitation documented)
  • Router.create() throws on second call (singleton guard)
  • Router.reload() no-throw
  • Router.back() / Router.forward()console.warn in memory mode, never throw

tests/forms.test.ts (39 tests — new file)

  • TextField: DOM mounting, inputElement tag, placeholder, value, name, disabled get/set, type, label element presence, input/change event forwarding
  • CheckBox: DOM mounting, input[type=checkbox], checked default/prop/setter, value, name, label, disabled (Component level), variant radio/checkbox
  • CheckBoxGroup: DOM mounting, children, selected === null initially, onChange callback fires on change event
  • SwitchPanel: DOM mounting, input type, checked default/prop/setter, name, label, change forwarding, toggle cycle (false → true → false)

tests/reactive-collections.test.ts (57 tests — new file)

  • RefList: construction, id, valueOf(), value, Symbol.toStringTag; all mutation methods (push, pop, shift, unshift, splice, sort, reverse, fill, clear, insert, removeItem, remove, value setter, update()); reactivity (subscribe / listen / unsubscribe); RefList.syncComponentWithList (add, remove, reorder)
  • RefMap: construction, id, valueOf(); mutations (set, delete, clear, value setter, update(), toJSON()); reactivity (subscribe / listen / unsubscribe)
  • RefObject (via refObject()): creation, subscribe, listen, unsubscribe, update, toJSON
  • ref() factory: dispatches to RefList for arrays, RefMap for Maps

tests/lifecycle.test.ts (20 tests — new file)

  • DOM cleanup: remove(), parent removal cascades to children, re-append
  • Re-render: swap components in container, multiple swap cycle
  • Reactive DOM updates: ref → textContent, style, boolean visibility, multi-change propagation, unsubscribe stops DOM updates
  • RefList.syncComponentWithList driving DOM children: init, push settle, remove, clear
  • Multi-subscriber fan-out and selective unsubscribe
  • Component child management: append, innerHTML = '' clear, deep nesting, querySelectorAll

Validation

npm run test:run
 ✓ tests/forms.test.ts (39 tests) 50ms
✓ tests/router.test.ts (14 tests) 37ms
✓ tests/reactive-collections.test.ts (57 tests) 25ms
✓ tests/lifecycle.test.ts (20 tests) 29ms
✓ tests/events.test.ts (10 tests) 21ms
✓ tests/reactivity-computed.test.ts (9 tests) 11ms
✓ tests/composition.test.ts (8 tests) 20ms
✓ tests/component.test.ts (2 tests) 11ms
✓ tests/reactivity.test.ts (3 tests) 7ms
Test Files 9 passed (9)
Tests 162 passed (162)
npm run build

✅ Build passes (tsc + Sass + dist assembly).

Caveats / APIs skipped

AreaReason skipped
Router.props with queryParams in memory modeextras.queryParams are stored in #props then immediately overwritten by buildRoutePage(match) → match.params || {}. Documented as a known API nuance in the test.
document.title via full rendering pipelineApp.setPage(new Component()) may silently reject in happy-dom when custom element constructors reference unregistered elements; title test asserts typeof string only.
RefObject deep proxy reactive updatesSource has a console.log side-effect at RefObject.ts:296; not patched here per the "no production code changes" constraint.
ForEach, LazyLoad, VirtualScroll, DropDownAsync or layout-dependent APIs; deferred to a future PR.

No production files modified

This PR is tests only.

Roadmap alignment: Testing utilities and example tests.

cc @zico15

lucas-spinand others added 5 commits June 15, 2026 14:57
Adds 130 new tests across 4 new test files, bringing the suite total
from 32 tests (5 files) to 162 tests (9 files).
tests/router.test.ts (14 tests)
- Router.history, Router.pathname accessors
- Router.go() in memory mode — single and multi-hop navigation
- Router.go() void return type
- Router.props documents {} behaviour for routes without path params
- Redirect route resolves to target after async microtask flush
- Route title assertion scoped to a string (full pipeline caveat documented)
- Router.create() throws on second call
- Router.reload() no-throw
- Router.back() / Router.forward() — console.warn in memory mode
tests/forms.test.ts (39 tests)
- TextField: DOM mounting, inputElement tag, placeholder, value, name,
disabled, type, label, input/change event forwarding
- CheckBox: DOM mounting, input type, checked default/prop/setter, value,
name, label, disabled, variant radio/checkbox
- CheckBoxGroup: DOM mounting, children, selected null, onChange callback
- SwitchPanel: DOM mounting, input type, checked default/prop/setter, name,
label, change forwarding, toggle cycle
tests/reactive-collections.test.ts (57 tests)
- RefList: construction, id, valueOf, value, Symbol.toStringTag
- RefList mutations: push, pop, shift, unshift, splice, sort, reverse,
fill, clear, insert, removeItem, remove, value setter, update()
- RefList reactivity: subscribe (emit), listen (no-emit), unsubscribe
- RefList.syncComponentWithList: add, remove, reorder
- RefMap: construction, id, valueOf; mutations: set, delete, clear,
value setter, update(), toJSON; reactivity: subscribe, listen, unsubscribe
- RefObject: creation, subscribe, listen, unsubscribe, update, toJSON
- ref() factory: dispatches to RefList for arrays, RefMap for Maps
tests/lifecycle.test.ts (20 tests)
- DOM cleanup: remove, parent removal, re-append
- Re-render: swap components in container
- Reactive DOM updates: ref → textContent, style, boolean visibility,
multi-change propagation, unsubscribe stops updates
- RefList.syncComponentWithList: init, push, remove, clear flows
- Multi-subscriber fan-out and selective unsubscribe
- Component child management: append, innerHTML clear, deep nesting,
querySelectorAll
Caveats / APIs skipped:
- Router queryParams in memory mode: extras.queryParams are overwritten by
buildRoutePage(); documented as {} behaviour in the props test.
- Router document.title via full rendering pipeline: App.setPage() +
Component constructor may silently reject in happy-dom; title test
asserts typeof string only.
- RefObject deep-proxy reactive updates: console.log side-effect present
in the current source (RefObject.ts:296); not patched here.
- ForEach / LazyLoad / VirtualScroll / DropDown: async or layout-dependent
APIs; deferred to a future PR.
No production files were modified.
Roadmap alignment: Testing utilities and example tests.
@joaodibba
joaodibba merged commit b5a1377 into TypeComposer:mainJun 15, 2026
2 checks passed
joaodibba added a commit that referenced this pull request Jun 15, 2026
## Summary
Removes a debug `console.log("RefObject: emit", ...)` that was
accidentally left in the `createObservador()` Proxy `set` trap inside
`src/core/ref/RefObject.ts`.
The log fired on **every nested object property mutation**, leaking
internal reactive state (`{ key, value, target, receiver }`) to
production consoles.
**File changed:** `src/core/ref/RefObject.ts`
```diff
- console.log("RefObject: emit", {
- key, value, target, receiver
- });
container.emitAll('value', this.value);
```
---
## Regression tests added
Three new tests in `tests/reactive-collections.test.ts` (describe:
*RefObject – no console.log in production*):
| Test | Asserts |
|---|---|
| `nested property mutation does NOT call console.log` | proxy setter
fires without calling `console.log` |
| `update() does NOT call console.log` | `obj.update({...})` fires
without calling `console.log` |
| `subscriber is still notified after property mutation without
console.log` | reactivity still works (listen callback fires), no log |
---
## Validation
```
npm run test:run → 9 test files, 165 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 1** from the PR #15 review. Small, focused, no
architectural change.
Reviewer: @zico15
joaodibba pushed a commit that referenced this pull request Jun 15, 2026
Replaces four weak toBeTruthy / typeof-only assertions from the
RefObject (refObject) describe block (introduced in PR #15) with
exact behavioral assertions:
- 'creates a reactive object wrapper': now verifies id is a non-empty
string and that subscribe/update are functions (not just truthy).
- 'value exposes the wrapped plain object': now asserts the actual key
value (x === 1) rather than just that the value is truthy.
- 'update with new plain object replaces content': now counts subscriber
call invocations to confirm notification fires, and checks lastValue
is a defined object.
- 'toJSON serializes the object value': now checks the serialized shape
contains the original key/value pairs (key === 'value', count === 42).
All 162 tests pass. Addresses audit item 8.
joaodibba added a commit that referenced this pull request Jun 15, 2026
…#23)
## Summary
Follow-up to PR #15 — replaces four weak `toBeTruthy()` / `typeof`-only
assertions in the `RefObject (refObject)` describe block with exact
behavioral assertions that would catch real regressions.
**File changed:** `tests/reactive-collections.test.ts` (test-only
change, no production code modified)
| Old assertion (weak) | New assertion (exact) |
|---|---|
| `expect(obj).toBeTruthy()` | `expect(typeof obj.id).toBe('string')` +
`id.length > 0` + `subscribe`/`update` are functions |
| `expect(obj.value).toBeTruthy()` | `expect((obj.value as
any).x).toBe(1)` — checks actual wrapped value |
| `expect(lastValue).toBeTruthy()` after update | counts subscriber
calls (> baseCalls) and checks `lastValue` is a defined object |
| `expect(typeof json).toBe('object')` | checks `json.key === 'value'`
and `json.count === 42` — verifies serialized shape |
---
## Validation
```
npm run test:run → 9 test files, 162 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 8** (strengthen weak RefObject test assertions).
No production code changed.
Reviewer: @zico15
joaodibba added a commit to TypeComposer/docs that referenced this pull request Jun 15, 2026
## Summary
Marks the **Component Testing Framework** roadmap item as `completed`
(was `in-progress`).
### Why
The testing milestone is fully done across three merged PRs in
`TypeComposer/typecomposer`:
| PR | What it added |
|---|---|
| [#13](TypeComposer/typecomposer#13) | Testing
utilities (`tests/utils.ts` render helper) + CI integration (GitHub
Actions workflow) |
| [#14](TypeComposer/typecomposer#14) | Example
tests: computed properties, composition patterns, event handling |
| [#15](TypeComposer/typecomposer#15) | Expanded
tests: router, forms, reactive-collections, lifecycle hooks |
All three roadmap sub-items are now satisfied:
- ✅ Add testing utilities
- ✅ Create example tests
- ✅ Integrate with CI
### Change
**File:** `src/assets/roadmap.json` **Diff:** 1-line change — `"status": "in-progress"` → `"status":
"completed"` on the `Component Testing Framework` card (October 2025
milestone).
### Validation
```
npm run build → ✓ 3487 modules transformed, built in 16.82s (no errors)
```
cc @zico15
joaodibba added a commit that referenced this pull request Jun 15, 2026
…ualScroll tests (#24)
## Summary
Closes two deferred roadmap items from PR #15:
1. **`tests/README.md`** — contributor documentation for the test suite.
2. **`tests/async-components.test.ts`** — stable tests for `ForEach`,
`LazyLoad`, and `VirtualScroll`.
---
## What's in each file
### `tests/README.md`
| Section | What it covers |
|---|---|
| Running tests | `npm run test` (watch) vs `npm run test:run` (CI) |
| Test structure | Every file in `tests/`, what each one tests |
| `tests/utils.ts` render helper | `render()` signature, auto-cleanup,
example |
| `tests/setup.ts` | `MemoryStorage` polyfill, `beforeEach` storage
clear |
| Assertion patterns | DOM structure, styles, reactivity, async flush |
| happy-dom layout limitations | Which APIs return 0 and how to avoid
brittle tests |
| `vitest.config.ts` reference | Environment, globals, setupFiles |
| Adding new tests | Step-by-step checklist |
### `tests/async-components.test.ts`
**ForEach (11 tests):** custom element tag, empty innerHTML, `item`
getter default, `of` setter/getAttribute round-trip, `apply()` no-throw
and innerHTML-clear contract.
**LazyLoad (11 tests):** custom element tag, `loader`/`loaderProps`
stored correctly, `Component.lazyLoad` spy asserts it is called with
correct `fallback`/`props` on `onConnected`, not called when no loader
provided.
**VirtualScroll (13 tests, 1 skipped):** custom element tag, inner
container presence and `position: relative`, total height =
`items.length × itemHeight`, `overflow-y: auto`, initial render slice
under happy-dom (`scrollTop=0` → `buffer` items), custom `buffer`, empty
items, absolute positioning + px `top` + `100% width`, `renderItem`
factory call count, `top = index × itemHeight`, scroll event no-throw.
Scroll-position windowing skipped with a note pointing to E2E.
---
## Validation
```
npm run test:run
npx tsc --noEmit
```
```
Test Files 10 passed (10)
Tests 207 passed | 1 skipped (208)
Duration ~9s
tsc --noEmit: no errors
```
All pre-existing tests continue to pass.
---
## Reviewers
@zico15
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

test: cover router, forms, and reactive collections - #15

Merged
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections
Jun 15, 2026
Merged

test: cover router, forms, and reactive collections#15
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Summary

Follows up on the merged PR #14 (computed chains, composition, events) by adding test coverage for Router, form components, reactive collections, and component lifecycle/DOM-update patterns.

What changed

tests/router.test.ts (14 tests — new file)

  • Router.history returns configured mode ("memory")
  • Router.pathname at creation (root = "") and after go()
  • Router.go() — single and multi-hop navigation in memory mode
  • Router.go() resolves undefined (void return type)
  • Router.props — documents {} behaviour for routes without path params (known API nuance)
  • Redirect route (/old → /new) resolves to target after async microtask flush
  • Route title test — asserts typeof string only (full rendering pipeline limitation documented)
  • Router.create() throws on second call (singleton guard)
  • Router.reload() no-throw
  • Router.back() / Router.forward()console.warn in memory mode, never throw

tests/forms.test.ts (39 tests — new file)

  • TextField: DOM mounting, inputElement tag, placeholder, value, name, disabled get/set, type, label element presence, input/change event forwarding
  • CheckBox: DOM mounting, input[type=checkbox], checked default/prop/setter, value, name, label, disabled (Component level), variant radio/checkbox
  • CheckBoxGroup: DOM mounting, children, selected === null initially, onChange callback fires on change event
  • SwitchPanel: DOM mounting, input type, checked default/prop/setter, name, label, change forwarding, toggle cycle (false → true → false)

tests/reactive-collections.test.ts (57 tests — new file)

  • RefList: construction, id, valueOf(), value, Symbol.toStringTag; all mutation methods (push, pop, shift, unshift, splice, sort, reverse, fill, clear, insert, removeItem, remove, value setter, update()); reactivity (subscribe / listen / unsubscribe); RefList.syncComponentWithList (add, remove, reorder)
  • RefMap: construction, id, valueOf(); mutations (set, delete, clear, value setter, update(), toJSON()); reactivity (subscribe / listen / unsubscribe)
  • RefObject (via refObject()): creation, subscribe, listen, unsubscribe, update, toJSON
  • ref() factory: dispatches to RefList for arrays, RefMap for Maps

tests/lifecycle.test.ts (20 tests — new file)

  • DOM cleanup: remove(), parent removal cascades to children, re-append
  • Re-render: swap components in container, multiple swap cycle
  • Reactive DOM updates: ref → textContent, style, boolean visibility, multi-change propagation, unsubscribe stops DOM updates
  • RefList.syncComponentWithList driving DOM children: init, push settle, remove, clear
  • Multi-subscriber fan-out and selective unsubscribe
  • Component child management: append, innerHTML = '' clear, deep nesting, querySelectorAll

Validation

npm run test:run
 ✓ tests/forms.test.ts (39 tests) 50ms
✓ tests/router.test.ts (14 tests) 37ms
✓ tests/reactive-collections.test.ts (57 tests) 25ms
✓ tests/lifecycle.test.ts (20 tests) 29ms
✓ tests/events.test.ts (10 tests) 21ms
✓ tests/reactivity-computed.test.ts (9 tests) 11ms
✓ tests/composition.test.ts (8 tests) 20ms
✓ tests/component.test.ts (2 tests) 11ms
✓ tests/reactivity.test.ts (3 tests) 7ms
Test Files 9 passed (9)
Tests 162 passed (162)
npm run build

✅ Build passes (tsc + Sass + dist assembly).

Caveats / APIs skipped

AreaReason skipped
Router.props with queryParams in memory modeextras.queryParams are stored in #props then immediately overwritten by buildRoutePage(match) → match.params || {}. Documented as a known API nuance in the test.
document.title via full rendering pipelineApp.setPage(new Component()) may silently reject in happy-dom when custom element constructors reference unregistered elements; title test asserts typeof string only.
RefObject deep proxy reactive updatesSource has a console.log side-effect at RefObject.ts:296; not patched here per the "no production code changes" constraint.
ForEach, LazyLoad, VirtualScroll, DropDownAsync or layout-dependent APIs; deferred to a future PR.

No production files modified

This PR is tests only.

Roadmap alignment: Testing utilities and example tests.

cc @zico15

lucas-spinand others added 5 commits June 15, 2026 14:57
Adds 130 new tests across 4 new test files, bringing the suite total
from 32 tests (5 files) to 162 tests (9 files).
tests/router.test.ts (14 tests)
- Router.history, Router.pathname accessors
- Router.go() in memory mode — single and multi-hop navigation
- Router.go() void return type
- Router.props documents {} behaviour for routes without path params
- Redirect route resolves to target after async microtask flush
- Route title assertion scoped to a string (full pipeline caveat documented)
- Router.create() throws on second call
- Router.reload() no-throw
- Router.back() / Router.forward() — console.warn in memory mode
tests/forms.test.ts (39 tests)
- TextField: DOM mounting, inputElement tag, placeholder, value, name,
disabled, type, label, input/change event forwarding
- CheckBox: DOM mounting, input type, checked default/prop/setter, value,
name, label, disabled, variant radio/checkbox
- CheckBoxGroup: DOM mounting, children, selected null, onChange callback
- SwitchPanel: DOM mounting, input type, checked default/prop/setter, name,
label, change forwarding, toggle cycle
tests/reactive-collections.test.ts (57 tests)
- RefList: construction, id, valueOf, value, Symbol.toStringTag
- RefList mutations: push, pop, shift, unshift, splice, sort, reverse,
fill, clear, insert, removeItem, remove, value setter, update()
- RefList reactivity: subscribe (emit), listen (no-emit), unsubscribe
- RefList.syncComponentWithList: add, remove, reorder
- RefMap: construction, id, valueOf; mutations: set, delete, clear,
value setter, update(), toJSON; reactivity: subscribe, listen, unsubscribe
- RefObject: creation, subscribe, listen, unsubscribe, update, toJSON
- ref() factory: dispatches to RefList for arrays, RefMap for Maps
tests/lifecycle.test.ts (20 tests)
- DOM cleanup: remove, parent removal, re-append
- Re-render: swap components in container
- Reactive DOM updates: ref → textContent, style, boolean visibility,
multi-change propagation, unsubscribe stops updates
- RefList.syncComponentWithList: init, push, remove, clear flows
- Multi-subscriber fan-out and selective unsubscribe
- Component child management: append, innerHTML clear, deep nesting,
querySelectorAll
Caveats / APIs skipped:
- Router queryParams in memory mode: extras.queryParams are overwritten by
buildRoutePage(); documented as {} behaviour in the props test.
- Router document.title via full rendering pipeline: App.setPage() +
Component constructor may silently reject in happy-dom; title test
asserts typeof string only.
- RefObject deep-proxy reactive updates: console.log side-effect present
in the current source (RefObject.ts:296); not patched here.
- ForEach / LazyLoad / VirtualScroll / DropDown: async or layout-dependent
APIs; deferred to a future PR.
No production files were modified.
Roadmap alignment: Testing utilities and example tests.
@joaodibba
joaodibba merged commit b5a1377 into TypeComposer:mainJun 15, 2026
2 checks passed
joaodibba added a commit that referenced this pull request Jun 15, 2026
## Summary
Removes a debug `console.log("RefObject: emit", ...)` that was
accidentally left in the `createObservador()` Proxy `set` trap inside
`src/core/ref/RefObject.ts`.
The log fired on **every nested object property mutation**, leaking
internal reactive state (`{ key, value, target, receiver }`) to
production consoles.
**File changed:** `src/core/ref/RefObject.ts`
```diff
- console.log("RefObject: emit", {
- key, value, target, receiver
- });
container.emitAll('value', this.value);
```
---
## Regression tests added
Three new tests in `tests/reactive-collections.test.ts` (describe:
*RefObject – no console.log in production*):
| Test | Asserts |
|---|---|
| `nested property mutation does NOT call console.log` | proxy setter
fires without calling `console.log` |
| `update() does NOT call console.log` | `obj.update({...})` fires
without calling `console.log` |
| `subscriber is still notified after property mutation without
console.log` | reactivity still works (listen callback fires), no log |
---
## Validation
```
npm run test:run → 9 test files, 165 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 1** from the PR #15 review. Small, focused, no
architectural change.
Reviewer: @zico15
joaodibba pushed a commit that referenced this pull request Jun 15, 2026
Replaces four weak toBeTruthy / typeof-only assertions from the
RefObject (refObject) describe block (introduced in PR #15) with
exact behavioral assertions:
- 'creates a reactive object wrapper': now verifies id is a non-empty
string and that subscribe/update are functions (not just truthy).
- 'value exposes the wrapped plain object': now asserts the actual key
value (x === 1) rather than just that the value is truthy.
- 'update with new plain object replaces content': now counts subscriber
call invocations to confirm notification fires, and checks lastValue
is a defined object.
- 'toJSON serializes the object value': now checks the serialized shape
contains the original key/value pairs (key === 'value', count === 42).
All 162 tests pass. Addresses audit item 8.
joaodibba added a commit that referenced this pull request Jun 15, 2026
…#23)
## Summary
Follow-up to PR #15 — replaces four weak `toBeTruthy()` / `typeof`-only
assertions in the `RefObject (refObject)` describe block with exact
behavioral assertions that would catch real regressions.
**File changed:** `tests/reactive-collections.test.ts` (test-only
change, no production code modified)
| Old assertion (weak) | New assertion (exact) |
|---|---|
| `expect(obj).toBeTruthy()` | `expect(typeof obj.id).toBe('string')` +
`id.length > 0` + `subscribe`/`update` are functions |
| `expect(obj.value).toBeTruthy()` | `expect((obj.value as
any).x).toBe(1)` — checks actual wrapped value |
| `expect(lastValue).toBeTruthy()` after update | counts subscriber
calls (> baseCalls) and checks `lastValue` is a defined object |
| `expect(typeof json).toBe('object')` | checks `json.key === 'value'`
and `json.count === 42` — verifies serialized shape |
---
## Validation
```
npm run test:run → 9 test files, 162 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 8** (strengthen weak RefObject test assertions).
No production code changed.
Reviewer: @zico15
joaodibba added a commit to TypeComposer/docs that referenced this pull request Jun 15, 2026
## Summary
Marks the **Component Testing Framework** roadmap item as `completed`
(was `in-progress`).
### Why
The testing milestone is fully done across three merged PRs in
`TypeComposer/typecomposer`:
| PR | What it added |
|---|---|
| [#13](TypeComposer/typecomposer#13) | Testing
utilities (`tests/utils.ts` render helper) + CI integration (GitHub
Actions workflow) |
| [#14](TypeComposer/typecomposer#14) | Example
tests: computed properties, composition patterns, event handling |
| [#15](TypeComposer/typecomposer#15) | Expanded
tests: router, forms, reactive-collections, lifecycle hooks |
All three roadmap sub-items are now satisfied:
- ✅ Add testing utilities
- ✅ Create example tests
- ✅ Integrate with CI
### Change
**File:** `src/assets/roadmap.json` **Diff:** 1-line change — `"status": "in-progress"` → `"status":
"completed"` on the `Component Testing Framework` card (October 2025
milestone).
### Validation
```
npm run build → ✓ 3487 modules transformed, built in 16.82s (no errors)
```
cc @zico15
joaodibba added a commit that referenced this pull request Jun 15, 2026
…ualScroll tests (#24)
## Summary
Closes two deferred roadmap items from PR #15:
1. **`tests/README.md`** — contributor documentation for the test suite.
2. **`tests/async-components.test.ts`** — stable tests for `ForEach`,
`LazyLoad`, and `VirtualScroll`.
---
## What's in each file
### `tests/README.md`
| Section | What it covers |
|---|---|
| Running tests | `npm run test` (watch) vs `npm run test:run` (CI) |
| Test structure | Every file in `tests/`, what each one tests |
| `tests/utils.ts` render helper | `render()` signature, auto-cleanup,
example |
| `tests/setup.ts` | `MemoryStorage` polyfill, `beforeEach` storage
clear |
| Assertion patterns | DOM structure, styles, reactivity, async flush |
| happy-dom layout limitations | Which APIs return 0 and how to avoid
brittle tests |
| `vitest.config.ts` reference | Environment, globals, setupFiles |
| Adding new tests | Step-by-step checklist |
### `tests/async-components.test.ts`
**ForEach (11 tests):** custom element tag, empty innerHTML, `item`
getter default, `of` setter/getAttribute round-trip, `apply()` no-throw
and innerHTML-clear contract.
**LazyLoad (11 tests):** custom element tag, `loader`/`loaderProps`
stored correctly, `Component.lazyLoad` spy asserts it is called with
correct `fallback`/`props` on `onConnected`, not called when no loader
provided.
**VirtualScroll (13 tests, 1 skipped):** custom element tag, inner
container presence and `position: relative`, total height =
`items.length × itemHeight`, `overflow-y: auto`, initial render slice
under happy-dom (`scrollTop=0` → `buffer` items), custom `buffer`, empty
items, absolute positioning + px `top` + `100% width`, `renderItem`
factory call count, `top = index × itemHeight`, scroll event no-throw.
Scroll-position windowing skipped with a note pointing to E2E.
---
## Validation
```
npm run test:run
npx tsc --noEmit
```
```
Test Files 10 passed (10)
Tests 207 passed | 1 skipped (208)
Duration ~9s
tsc --noEmit: no errors
```
All pre-existing tests continue to pass.
---
## Reviewers
@zico15
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

test: cover router, forms, and reactive collections - #15

Merged
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections
Jun 15, 2026
Merged

test: cover router, forms, and reactive collections#15
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Summary

Follows up on the merged PR #14 (computed chains, composition, events) by adding test coverage for Router, form components, reactive collections, and component lifecycle/DOM-update patterns.

What changed

tests/router.test.ts (14 tests — new file)

  • Router.history returns configured mode ("memory")
  • Router.pathname at creation (root = "") and after go()
  • Router.go() — single and multi-hop navigation in memory mode
  • Router.go() resolves undefined (void return type)
  • Router.props — documents {} behaviour for routes without path params (known API nuance)
  • Redirect route (/old → /new) resolves to target after async microtask flush
  • Route title test — asserts typeof string only (full rendering pipeline limitation documented)
  • Router.create() throws on second call (singleton guard)
  • Router.reload() no-throw
  • Router.back() / Router.forward()console.warn in memory mode, never throw

tests/forms.test.ts (39 tests — new file)

  • TextField: DOM mounting, inputElement tag, placeholder, value, name, disabled get/set, type, label element presence, input/change event forwarding
  • CheckBox: DOM mounting, input[type=checkbox], checked default/prop/setter, value, name, label, disabled (Component level), variant radio/checkbox
  • CheckBoxGroup: DOM mounting, children, selected === null initially, onChange callback fires on change event
  • SwitchPanel: DOM mounting, input type, checked default/prop/setter, name, label, change forwarding, toggle cycle (false → true → false)

tests/reactive-collections.test.ts (57 tests — new file)

  • RefList: construction, id, valueOf(), value, Symbol.toStringTag; all mutation methods (push, pop, shift, unshift, splice, sort, reverse, fill, clear, insert, removeItem, remove, value setter, update()); reactivity (subscribe / listen / unsubscribe); RefList.syncComponentWithList (add, remove, reorder)
  • RefMap: construction, id, valueOf(); mutations (set, delete, clear, value setter, update(), toJSON()); reactivity (subscribe / listen / unsubscribe)
  • RefObject (via refObject()): creation, subscribe, listen, unsubscribe, update, toJSON
  • ref() factory: dispatches to RefList for arrays, RefMap for Maps

tests/lifecycle.test.ts (20 tests — new file)

  • DOM cleanup: remove(), parent removal cascades to children, re-append
  • Re-render: swap components in container, multiple swap cycle
  • Reactive DOM updates: ref → textContent, style, boolean visibility, multi-change propagation, unsubscribe stops DOM updates
  • RefList.syncComponentWithList driving DOM children: init, push settle, remove, clear
  • Multi-subscriber fan-out and selective unsubscribe
  • Component child management: append, innerHTML = '' clear, deep nesting, querySelectorAll

Validation

npm run test:run
 ✓ tests/forms.test.ts (39 tests) 50ms
✓ tests/router.test.ts (14 tests) 37ms
✓ tests/reactive-collections.test.ts (57 tests) 25ms
✓ tests/lifecycle.test.ts (20 tests) 29ms
✓ tests/events.test.ts (10 tests) 21ms
✓ tests/reactivity-computed.test.ts (9 tests) 11ms
✓ tests/composition.test.ts (8 tests) 20ms
✓ tests/component.test.ts (2 tests) 11ms
✓ tests/reactivity.test.ts (3 tests) 7ms
Test Files 9 passed (9)
Tests 162 passed (162)
npm run build

✅ Build passes (tsc + Sass + dist assembly).

Caveats / APIs skipped

AreaReason skipped
Router.props with queryParams in memory modeextras.queryParams are stored in #props then immediately overwritten by buildRoutePage(match) → match.params || {}. Documented as a known API nuance in the test.
document.title via full rendering pipelineApp.setPage(new Component()) may silently reject in happy-dom when custom element constructors reference unregistered elements; title test asserts typeof string only.
RefObject deep proxy reactive updatesSource has a console.log side-effect at RefObject.ts:296; not patched here per the "no production code changes" constraint.
ForEach, LazyLoad, VirtualScroll, DropDownAsync or layout-dependent APIs; deferred to a future PR.

No production files modified

This PR is tests only.

Roadmap alignment: Testing utilities and example tests.

cc @zico15

lucas-spinand others added 5 commits June 15, 2026 14:57
Adds 130 new tests across 4 new test files, bringing the suite total
from 32 tests (5 files) to 162 tests (9 files).
tests/router.test.ts (14 tests)
- Router.history, Router.pathname accessors
- Router.go() in memory mode — single and multi-hop navigation
- Router.go() void return type
- Router.props documents {} behaviour for routes without path params
- Redirect route resolves to target after async microtask flush
- Route title assertion scoped to a string (full pipeline caveat documented)
- Router.create() throws on second call
- Router.reload() no-throw
- Router.back() / Router.forward() — console.warn in memory mode
tests/forms.test.ts (39 tests)
- TextField: DOM mounting, inputElement tag, placeholder, value, name,
disabled, type, label, input/change event forwarding
- CheckBox: DOM mounting, input type, checked default/prop/setter, value,
name, label, disabled, variant radio/checkbox
- CheckBoxGroup: DOM mounting, children, selected null, onChange callback
- SwitchPanel: DOM mounting, input type, checked default/prop/setter, name,
label, change forwarding, toggle cycle
tests/reactive-collections.test.ts (57 tests)
- RefList: construction, id, valueOf, value, Symbol.toStringTag
- RefList mutations: push, pop, shift, unshift, splice, sort, reverse,
fill, clear, insert, removeItem, remove, value setter, update()
- RefList reactivity: subscribe (emit), listen (no-emit), unsubscribe
- RefList.syncComponentWithList: add, remove, reorder
- RefMap: construction, id, valueOf; mutations: set, delete, clear,
value setter, update(), toJSON; reactivity: subscribe, listen, unsubscribe
- RefObject: creation, subscribe, listen, unsubscribe, update, toJSON
- ref() factory: dispatches to RefList for arrays, RefMap for Maps
tests/lifecycle.test.ts (20 tests)
- DOM cleanup: remove, parent removal, re-append
- Re-render: swap components in container
- Reactive DOM updates: ref → textContent, style, boolean visibility,
multi-change propagation, unsubscribe stops updates
- RefList.syncComponentWithList: init, push, remove, clear flows
- Multi-subscriber fan-out and selective unsubscribe
- Component child management: append, innerHTML clear, deep nesting,
querySelectorAll
Caveats / APIs skipped:
- Router queryParams in memory mode: extras.queryParams are overwritten by
buildRoutePage(); documented as {} behaviour in the props test.
- Router document.title via full rendering pipeline: App.setPage() +
Component constructor may silently reject in happy-dom; title test
asserts typeof string only.
- RefObject deep-proxy reactive updates: console.log side-effect present
in the current source (RefObject.ts:296); not patched here.
- ForEach / LazyLoad / VirtualScroll / DropDown: async or layout-dependent
APIs; deferred to a future PR.
No production files were modified.
Roadmap alignment: Testing utilities and example tests.
@joaodibba
joaodibba merged commit b5a1377 into TypeComposer:mainJun 15, 2026
2 checks passed
joaodibba added a commit that referenced this pull request Jun 15, 2026
## Summary
Removes a debug `console.log("RefObject: emit", ...)` that was
accidentally left in the `createObservador()` Proxy `set` trap inside
`src/core/ref/RefObject.ts`.
The log fired on **every nested object property mutation**, leaking
internal reactive state (`{ key, value, target, receiver }`) to
production consoles.
**File changed:** `src/core/ref/RefObject.ts`
```diff
- console.log("RefObject: emit", {
- key, value, target, receiver
- });
container.emitAll('value', this.value);
```
---
## Regression tests added
Three new tests in `tests/reactive-collections.test.ts` (describe:
*RefObject – no console.log in production*):
| Test | Asserts |
|---|---|
| `nested property mutation does NOT call console.log` | proxy setter
fires without calling `console.log` |
| `update() does NOT call console.log` | `obj.update({...})` fires
without calling `console.log` |
| `subscriber is still notified after property mutation without
console.log` | reactivity still works (listen callback fires), no log |
---
## Validation
```
npm run test:run → 9 test files, 165 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 1** from the PR #15 review. Small, focused, no
architectural change.
Reviewer: @zico15
joaodibba pushed a commit that referenced this pull request Jun 15, 2026
Replaces four weak toBeTruthy / typeof-only assertions from the
RefObject (refObject) describe block (introduced in PR #15) with
exact behavioral assertions:
- 'creates a reactive object wrapper': now verifies id is a non-empty
string and that subscribe/update are functions (not just truthy).
- 'value exposes the wrapped plain object': now asserts the actual key
value (x === 1) rather than just that the value is truthy.
- 'update with new plain object replaces content': now counts subscriber
call invocations to confirm notification fires, and checks lastValue
is a defined object.
- 'toJSON serializes the object value': now checks the serialized shape
contains the original key/value pairs (key === 'value', count === 42).
All 162 tests pass. Addresses audit item 8.
joaodibba added a commit that referenced this pull request Jun 15, 2026
…#23)
## Summary
Follow-up to PR #15 — replaces four weak `toBeTruthy()` / `typeof`-only
assertions in the `RefObject (refObject)` describe block with exact
behavioral assertions that would catch real regressions.
**File changed:** `tests/reactive-collections.test.ts` (test-only
change, no production code modified)
| Old assertion (weak) | New assertion (exact) |
|---|---|
| `expect(obj).toBeTruthy()` | `expect(typeof obj.id).toBe('string')` +
`id.length > 0` + `subscribe`/`update` are functions |
| `expect(obj.value).toBeTruthy()` | `expect((obj.value as
any).x).toBe(1)` — checks actual wrapped value |
| `expect(lastValue).toBeTruthy()` after update | counts subscriber
calls (> baseCalls) and checks `lastValue` is a defined object |
| `expect(typeof json).toBe('object')` | checks `json.key === 'value'`
and `json.count === 42` — verifies serialized shape |
---
## Validation
```
npm run test:run → 9 test files, 162 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 8** (strengthen weak RefObject test assertions).
No production code changed.
Reviewer: @zico15
joaodibba added a commit to TypeComposer/docs that referenced this pull request Jun 15, 2026
## Summary
Marks the **Component Testing Framework** roadmap item as `completed`
(was `in-progress`).
### Why
The testing milestone is fully done across three merged PRs in
`TypeComposer/typecomposer`:
| PR | What it added |
|---|---|
| [#13](TypeComposer/typecomposer#13) | Testing
utilities (`tests/utils.ts` render helper) + CI integration (GitHub
Actions workflow) |
| [#14](TypeComposer/typecomposer#14) | Example
tests: computed properties, composition patterns, event handling |
| [#15](TypeComposer/typecomposer#15) | Expanded
tests: router, forms, reactive-collections, lifecycle hooks |
All three roadmap sub-items are now satisfied:
- ✅ Add testing utilities
- ✅ Create example tests
- ✅ Integrate with CI
### Change
**File:** `src/assets/roadmap.json` **Diff:** 1-line change — `"status": "in-progress"` → `"status":
"completed"` on the `Component Testing Framework` card (October 2025
milestone).
### Validation
```
npm run build → ✓ 3487 modules transformed, built in 16.82s (no errors)
```
cc @zico15
joaodibba added a commit that referenced this pull request Jun 15, 2026
…ualScroll tests (#24)
## Summary
Closes two deferred roadmap items from PR #15:
1. **`tests/README.md`** — contributor documentation for the test suite.
2. **`tests/async-components.test.ts`** — stable tests for `ForEach`,
`LazyLoad`, and `VirtualScroll`.
---
## What's in each file
### `tests/README.md`
| Section | What it covers |
|---|---|
| Running tests | `npm run test` (watch) vs `npm run test:run` (CI) |
| Test structure | Every file in `tests/`, what each one tests |
| `tests/utils.ts` render helper | `render()` signature, auto-cleanup,
example |
| `tests/setup.ts` | `MemoryStorage` polyfill, `beforeEach` storage
clear |
| Assertion patterns | DOM structure, styles, reactivity, async flush |
| happy-dom layout limitations | Which APIs return 0 and how to avoid
brittle tests |
| `vitest.config.ts` reference | Environment, globals, setupFiles |
| Adding new tests | Step-by-step checklist |
### `tests/async-components.test.ts`
**ForEach (11 tests):** custom element tag, empty innerHTML, `item`
getter default, `of` setter/getAttribute round-trip, `apply()` no-throw
and innerHTML-clear contract.
**LazyLoad (11 tests):** custom element tag, `loader`/`loaderProps`
stored correctly, `Component.lazyLoad` spy asserts it is called with
correct `fallback`/`props` on `onConnected`, not called when no loader
provided.
**VirtualScroll (13 tests, 1 skipped):** custom element tag, inner
container presence and `position: relative`, total height =
`items.length × itemHeight`, `overflow-y: auto`, initial render slice
under happy-dom (`scrollTop=0` → `buffer` items), custom `buffer`, empty
items, absolute positioning + px `top` + `100% width`, `renderItem`
factory call count, `top = index × itemHeight`, scroll event no-throw.
Scroll-position windowing skipped with a note pointing to E2E.
---
## Validation
```
npm run test:run
npx tsc --noEmit
```
```
Test Files 10 passed (10)
Tests 207 passed | 1 skipped (208)
Duration ~9s
tsc --noEmit: no errors
```
All pre-existing tests continue to pass.
---
## Reviewers
@zico15
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

test: cover router, forms, and reactive collections - #15

Merged
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections
Jun 15, 2026
Merged

test: cover router, forms, and reactive collections#15
joaodibba merged 5 commits into
TypeComposer:mainfrom
lucas-spin:test/router-forms-reactive-collections

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Summary

Follows up on the merged PR #14 (computed chains, composition, events) by adding test coverage for Router, form components, reactive collections, and component lifecycle/DOM-update patterns.

What changed

tests/router.test.ts (14 tests — new file)

  • Router.history returns configured mode ("memory")
  • Router.pathname at creation (root = "") and after go()
  • Router.go() — single and multi-hop navigation in memory mode
  • Router.go() resolves undefined (void return type)
  • Router.props — documents {} behaviour for routes without path params (known API nuance)
  • Redirect route (/old → /new) resolves to target after async microtask flush
  • Route title test — asserts typeof string only (full rendering pipeline limitation documented)
  • Router.create() throws on second call (singleton guard)
  • Router.reload() no-throw
  • Router.back() / Router.forward()console.warn in memory mode, never throw

tests/forms.test.ts (39 tests — new file)

  • TextField: DOM mounting, inputElement tag, placeholder, value, name, disabled get/set, type, label element presence, input/change event forwarding
  • CheckBox: DOM mounting, input[type=checkbox], checked default/prop/setter, value, name, label, disabled (Component level), variant radio/checkbox
  • CheckBoxGroup: DOM mounting, children, selected === null initially, onChange callback fires on change event
  • SwitchPanel: DOM mounting, input type, checked default/prop/setter, name, label, change forwarding, toggle cycle (false → true → false)

tests/reactive-collections.test.ts (57 tests — new file)

  • RefList: construction, id, valueOf(), value, Symbol.toStringTag; all mutation methods (push, pop, shift, unshift, splice, sort, reverse, fill, clear, insert, removeItem, remove, value setter, update()); reactivity (subscribe / listen / unsubscribe); RefList.syncComponentWithList (add, remove, reorder)
  • RefMap: construction, id, valueOf(); mutations (set, delete, clear, value setter, update(), toJSON()); reactivity (subscribe / listen / unsubscribe)
  • RefObject (via refObject()): creation, subscribe, listen, unsubscribe, update, toJSON
  • ref() factory: dispatches to RefList for arrays, RefMap for Maps

tests/lifecycle.test.ts (20 tests — new file)

  • DOM cleanup: remove(), parent removal cascades to children, re-append
  • Re-render: swap components in container, multiple swap cycle
  • Reactive DOM updates: ref → textContent, style, boolean visibility, multi-change propagation, unsubscribe stops DOM updates
  • RefList.syncComponentWithList driving DOM children: init, push settle, remove, clear
  • Multi-subscriber fan-out and selective unsubscribe
  • Component child management: append, innerHTML = '' clear, deep nesting, querySelectorAll

Validation

npm run test:run
 ✓ tests/forms.test.ts (39 tests) 50ms
✓ tests/router.test.ts (14 tests) 37ms
✓ tests/reactive-collections.test.ts (57 tests) 25ms
✓ tests/lifecycle.test.ts (20 tests) 29ms
✓ tests/events.test.ts (10 tests) 21ms
✓ tests/reactivity-computed.test.ts (9 tests) 11ms
✓ tests/composition.test.ts (8 tests) 20ms
✓ tests/component.test.ts (2 tests) 11ms
✓ tests/reactivity.test.ts (3 tests) 7ms
Test Files 9 passed (9)
Tests 162 passed (162)
npm run build

✅ Build passes (tsc + Sass + dist assembly).

Caveats / APIs skipped

AreaReason skipped
Router.props with queryParams in memory modeextras.queryParams are stored in #props then immediately overwritten by buildRoutePage(match) → match.params || {}. Documented as a known API nuance in the test.
document.title via full rendering pipelineApp.setPage(new Component()) may silently reject in happy-dom when custom element constructors reference unregistered elements; title test asserts typeof string only.
RefObject deep proxy reactive updatesSource has a console.log side-effect at RefObject.ts:296; not patched here per the "no production code changes" constraint.
ForEach, LazyLoad, VirtualScroll, DropDownAsync or layout-dependent APIs; deferred to a future PR.

No production files modified

This PR is tests only.

Roadmap alignment: Testing utilities and example tests.

cc @zico15

lucas-spinand others added 5 commits June 15, 2026 14:57
Adds 130 new tests across 4 new test files, bringing the suite total
from 32 tests (5 files) to 162 tests (9 files).
tests/router.test.ts (14 tests)
- Router.history, Router.pathname accessors
- Router.go() in memory mode — single and multi-hop navigation
- Router.go() void return type
- Router.props documents {} behaviour for routes without path params
- Redirect route resolves to target after async microtask flush
- Route title assertion scoped to a string (full pipeline caveat documented)
- Router.create() throws on second call
- Router.reload() no-throw
- Router.back() / Router.forward() — console.warn in memory mode
tests/forms.test.ts (39 tests)
- TextField: DOM mounting, inputElement tag, placeholder, value, name,
disabled, type, label, input/change event forwarding
- CheckBox: DOM mounting, input type, checked default/prop/setter, value,
name, label, disabled, variant radio/checkbox
- CheckBoxGroup: DOM mounting, children, selected null, onChange callback
- SwitchPanel: DOM mounting, input type, checked default/prop/setter, name,
label, change forwarding, toggle cycle
tests/reactive-collections.test.ts (57 tests)
- RefList: construction, id, valueOf, value, Symbol.toStringTag
- RefList mutations: push, pop, shift, unshift, splice, sort, reverse,
fill, clear, insert, removeItem, remove, value setter, update()
- RefList reactivity: subscribe (emit), listen (no-emit), unsubscribe
- RefList.syncComponentWithList: add, remove, reorder
- RefMap: construction, id, valueOf; mutations: set, delete, clear,
value setter, update(), toJSON; reactivity: subscribe, listen, unsubscribe
- RefObject: creation, subscribe, listen, unsubscribe, update, toJSON
- ref() factory: dispatches to RefList for arrays, RefMap for Maps
tests/lifecycle.test.ts (20 tests)
- DOM cleanup: remove, parent removal, re-append
- Re-render: swap components in container
- Reactive DOM updates: ref → textContent, style, boolean visibility,
multi-change propagation, unsubscribe stops updates
- RefList.syncComponentWithList: init, push, remove, clear flows
- Multi-subscriber fan-out and selective unsubscribe
- Component child management: append, innerHTML clear, deep nesting,
querySelectorAll
Caveats / APIs skipped:
- Router queryParams in memory mode: extras.queryParams are overwritten by
buildRoutePage(); documented as {} behaviour in the props test.
- Router document.title via full rendering pipeline: App.setPage() +
Component constructor may silently reject in happy-dom; title test
asserts typeof string only.
- RefObject deep-proxy reactive updates: console.log side-effect present
in the current source (RefObject.ts:296); not patched here.
- ForEach / LazyLoad / VirtualScroll / DropDown: async or layout-dependent
APIs; deferred to a future PR.
No production files were modified.
Roadmap alignment: Testing utilities and example tests.
@joaodibba
joaodibba merged commit b5a1377 into TypeComposer:mainJun 15, 2026
2 checks passed
joaodibba added a commit that referenced this pull request Jun 15, 2026
## Summary
Removes a debug `console.log("RefObject: emit", ...)` that was
accidentally left in the `createObservador()` Proxy `set` trap inside
`src/core/ref/RefObject.ts`.
The log fired on **every nested object property mutation**, leaking
internal reactive state (`{ key, value, target, receiver }`) to
production consoles.
**File changed:** `src/core/ref/RefObject.ts`
```diff
- console.log("RefObject: emit", {
- key, value, target, receiver
- });
container.emitAll('value', this.value);
```
---
## Regression tests added
Three new tests in `tests/reactive-collections.test.ts` (describe:
*RefObject – no console.log in production*):
| Test | Asserts |
|---|---|
| `nested property mutation does NOT call console.log` | proxy setter
fires without calling `console.log` |
| `update() does NOT call console.log` | `obj.update({...})` fires
without calling `console.log` |
| `subscriber is still notified after property mutation without
console.log` | reactivity still works (listen callback fires), no log |
---
## Validation
```
npm run test:run → 9 test files, 165 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 1** from the PR #15 review. Small, focused, no
architectural change.
Reviewer: @zico15
joaodibba pushed a commit that referenced this pull request Jun 15, 2026
Replaces four weak toBeTruthy / typeof-only assertions from the
RefObject (refObject) describe block (introduced in PR #15) with
exact behavioral assertions:
- 'creates a reactive object wrapper': now verifies id is a non-empty
string and that subscribe/update are functions (not just truthy).
- 'value exposes the wrapped plain object': now asserts the actual key
value (x === 1) rather than just that the value is truthy.
- 'update with new plain object replaces content': now counts subscriber
call invocations to confirm notification fires, and checks lastValue
is a defined object.
- 'toJSON serializes the object value': now checks the serialized shape
contains the original key/value pairs (key === 'value', count === 42).
All 162 tests pass. Addresses audit item 8.
joaodibba added a commit that referenced this pull request Jun 15, 2026
…#23)
## Summary
Follow-up to PR #15 — replaces four weak `toBeTruthy()` / `typeof`-only
assertions in the `RefObject (refObject)` describe block with exact
behavioral assertions that would catch real regressions.
**File changed:** `tests/reactive-collections.test.ts` (test-only
change, no production code modified)
| Old assertion (weak) | New assertion (exact) |
|---|---|
| `expect(obj).toBeTruthy()` | `expect(typeof obj.id).toBe('string')` +
`id.length > 0` + `subscribe`/`update` are functions |
| `expect(obj.value).toBeTruthy()` | `expect((obj.value as
any).x).toBe(1)` — checks actual wrapped value |
| `expect(lastValue).toBeTruthy()` after update | counts subscriber
calls (> baseCalls) and checks `lastValue` is a defined object |
| `expect(typeof json).toBe('object')` | checks `json.key === 'value'`
and `json.count === 42` — verifies serialized shape |
---
## Validation
```
npm run test:run → 9 test files, 162 tests passed ✓
npx tsc --noEmit → no errors ✓
```
---
## Roadmap alignment
Addresses **audit item 8** (strengthen weak RefObject test assertions).
No production code changed.
Reviewer: @zico15
joaodibba added a commit to TypeComposer/docs that referenced this pull request Jun 15, 2026
## Summary
Marks the **Component Testing Framework** roadmap item as `completed`
(was `in-progress`).
### Why
The testing milestone is fully done across three merged PRs in
`TypeComposer/typecomposer`:
| PR | What it added |
|---|---|
| [#13](TypeComposer/typecomposer#13) | Testing
utilities (`tests/utils.ts` render helper) + CI integration (GitHub
Actions workflow) |
| [#14](TypeComposer/typecomposer#14) | Example
tests: computed properties, composition patterns, event handling |
| [#15](TypeComposer/typecomposer#15) | Expanded
tests: router, forms, reactive-collections, lifecycle hooks |
All three roadmap sub-items are now satisfied:
- ✅ Add testing utilities
- ✅ Create example tests
- ✅ Integrate with CI
### Change
**File:** `src/assets/roadmap.json` **Diff:** 1-line change — `"status": "in-progress"` → `"status":
"completed"` on the `Component Testing Framework` card (October 2025
milestone).
### Validation
```
npm run build → ✓ 3487 modules transformed, built in 16.82s (no errors)
```
cc @zico15
joaodibba added a commit that referenced this pull request Jun 15, 2026
…ualScroll tests (#24)
## Summary
Closes two deferred roadmap items from PR #15:
1. **`tests/README.md`** — contributor documentation for the test suite.
2. **`tests/async-components.test.ts`** — stable tests for `ForEach`,
`LazyLoad`, and `VirtualScroll`.
---
## What's in each file
### `tests/README.md`
| Section | What it covers |
|---|---|
| Running tests | `npm run test` (watch) vs `npm run test:run` (CI) |
| Test structure | Every file in `tests/`, what each one tests |
| `tests/utils.ts` render helper | `render()` signature, auto-cleanup,
example |
| `tests/setup.ts` | `MemoryStorage` polyfill, `beforeEach` storage
clear |
| Assertion patterns | DOM structure, styles, reactivity, async flush |
| happy-dom layout limitations | Which APIs return 0 and how to avoid
brittle tests |
| `vitest.config.ts` reference | Environment, globals, setupFiles |
| Adding new tests | Step-by-step checklist |
### `tests/async-components.test.ts`
**ForEach (11 tests):** custom element tag, empty innerHTML, `item`
getter default, `of` setter/getAttribute round-trip, `apply()` no-throw
and innerHTML-clear contract.
**LazyLoad (11 tests):** custom element tag, `loader`/`loaderProps`
stored correctly, `Component.lazyLoad` spy asserts it is called with
correct `fallback`/`props` on `onConnected`, not called when no loader
provided.
**VirtualScroll (13 tests, 1 skipped):** custom element tag, inner
container presence and `position: relative`, total height =
`items.length × itemHeight`, `overflow-y: auto`, initial render slice
under happy-dom (`scrollTop=0` → `buffer` items), custom `buffer`, empty
items, absolute positioning + px `top` + `100% width`, `renderItem`
factory call count, `top = index × itemHeight`, scroll event no-throw.
Scroll-position windowing skipped with a note pointing to E2E.
---
## Validation
```
npm run test:run
npx tsc --noEmit
```
```
Test Files 10 passed (10)
Tests 207 passed | 1 skipped (208)
Duration ~9s
tsc --noEmit: no errors
```
All pre-existing tests continue to pass.
---
## Reviewers
@zico15
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba