Environment
main. Found by a review sweep over #452, which was itself the second instance of this in one file.
Description
#420 was the date column asserting Invalid Date. #450 was the status column asserting a default badge colour. Two in one file is not two accidents — it is a pattern, and a sweep of the snapshot corpus found fourteen more.
The shape is always the same: a case is added for a prop or slot, the fixture never satisfies its precondition, the snapshot records whatever the fallback renders, and the case now reads as coverage while asserting nothing. Nothing fails when it stops being true.
Tier 1 — worth fixing individually
1. test/components/Empty.spec.ts:32 — a prop the component does not have
['with avatar', { props: { avatar: { src: 'https://github.com/bitrix24.png' } } }],
EmptyProps (src/runtime/components/Empty.vue:11-52) has no avatar, and the string appears zero times in the component. It falls through to the root element and the snapshot pins it:
test/components/__snapshots__/Empty.spec.ts.snap:51 avatar="[object Object]"
Exactly #420's shape, and worse — there is no feature to fix the fixture against. Either Empty grows an avatar prop (it already has icon and a leading slot) or the case goes.
2. test/components/Countdown.spec.ts:22 — NaN pinned into an SVG attribute
The useCircle cases pass no seconds, so the default 0 applies and fullDashArray computes 0/0:
test/components/__snapshots__/Countdown.spec.ts.snap:42 stroke-dasharray="NaN 283"
It is the only dasharray value in either snapshot file — no snapshot renders a valid ring. ['with seconds', …] has the value but not useCircle; the two never meet. Fix is { useCircle: circle, seconds: 100 }, matching the unit test at Countdown.spec.ts:374-382 that already proves '283 283'.
3. The descriptionKey cluster — five specs setting a prop to its own default
| spec |
items used |
carry description? |
component default |
DropdownMenu.spec.ts:125 |
items |
no |
'description' |
InputMenu.spec.ts:52 |
items |
no |
'description' |
Listbox.spec.ts:48 |
items |
no |
'description' |
Select.spec.ts:53 |
items |
no |
'description' |
SelectMenu.spec.ts:52 |
items |
no |
'description' |
All five pass descriptionKey: 'description' — the value the prop already has — on items that lack the field. Doubly inert, and confirmed by snapshot identity: InputMenu > with descriptionKey is byte-identical to with items, with name, with selectedIcon and with size md.
In every one of the five, the right fixture (itemsWithDescription) is defined one line above and simply is not passed. Two siblings already do it correctly and are the model: CommandPalette.spec.ts:133 and ContextMenu.spec.ts:123. Both changes are needed — pass the with-description items and use a non-default key.
4. test/components/NavigationMenu.spec.ts:118 — a slot no item selects
The dynamic custom slot fires only for an item carrying slot: 'custom'. This is the only item-based spec whose fixture has no slot: key at all — every sibling has one. "Custom slot" appears nowhere in the snapshot, which is identical to four other cases.
Tier 2 — one-line fixes, better as a single sweep PR
Table.spec.ts:251 — the expanded slot renders only for an expanded row; the case passes no columns and no expansion, so the snapshot is byte-identical to with data.
Table.spec.ts:241, :244 — every virtualization case renders zero data rows under happy-dom, so with row pinning and virtualization is byte-identical to with virtualize and the pinning assertion is vacuous.
Table.spec.ts:144-153 — column.getIsSorted() is false in every snapshot; both sorted branches are unreachable. (And the asc branch resolves to the same icon as unsorted, so even adding a sort test could only distinguish desc.)
Table.spec.ts:187-222 — the whole actions items array is dead: every label appears 0 times across the snapshot file, because the menu is never opened.
- Six named-slot cases whose precondition is never set, each byte-identical to a sibling:
Alert.spec.ts:32 (close slot needs the close prop), CommandPalette.spec.ts:151 (empty needs no results), Progress.spec.ts:32 (status needs status/modelValue), Tabs.spec.ts:54 (the slot: 'custom' item is not the active tab), ContextMenu.spec.ts:137 (the item is inside a submenu that never opens), ChatMessages.spec.ts:41,:43.
DescriptionList.spec.ts:83 — with default slot passes no items, so it equals with empty items.
Stepper.spec.ts:28 — defaultValue: 'Address' picks index 0, already the active step. 'Shipping' would prove it. (Timeline.spec.ts:47 picks index 1 and is fine.)
Adjacent — a component defect, not a test one
src/runtime/components/Calendar.vue:267 omits nextMonth/nextYear/prevMonth/prevYear from omittedProps (which does list viewControl), so they are forwarded to the picker root and serialise as attributes:
test/components/__snapshots__/Calendar.spec.ts.snap:2518 nextmonth="[object Object]"
Unlike Empty's, these props work — this is a genuine leak, pinned as expected output. Worth splitting into its own issue against the component.
The scale problem underneath
Of the 35 renderEach snapshot entries in Table.spec.ts, only two render the custom cell functions at all — every other entry uses columns auto-derived from the data keys. The entire columns array's coverage rests on those two entries, which is why a dead branch there survives so easily.
That generalises: a renderEach matrix produces a lot of snapshots cheaply, and the cheapness is the problem — nothing tells you which of them are distinguishable from each other.
Suggested guard
A spec that groups snapshot entries by identical body and fails on collisions would have caught most of the above, since the symptom is nearly always "this case is byte-identical to a sibling". test/utils/ already holds cross-cutting invariant specs of this kind (docs-logical-properties.spec.ts, component-count-claims.spec.ts), so there is a pattern to follow.
It would need an allowlist for legitimately-identical pairs, which is itself useful: the allowlist becomes the record of which cases are known not to assert anything.
Environment
main. Found by a review sweep over #452, which was itself the second instance of this in one file.Description
#420 was the
datecolumn assertingInvalid Date. #450 was thestatuscolumn asserting a default badge colour. Two in one file is not two accidents — it is a pattern, and a sweep of the snapshot corpus found fourteen more.The shape is always the same: a case is added for a prop or slot, the fixture never satisfies its precondition, the snapshot records whatever the fallback renders, and the case now reads as coverage while asserting nothing. Nothing fails when it stops being true.
Tier 1 — worth fixing individually
1.
test/components/Empty.spec.ts:32— a prop the component does not haveEmptyProps(src/runtime/components/Empty.vue:11-52) has noavatar, and the string appears zero times in the component. It falls through to the root element and the snapshot pins it:Exactly #420's shape, and worse — there is no feature to fix the fixture against. Either
Emptygrows anavatarprop (it already hasiconand aleadingslot) or the case goes.2.
test/components/Countdown.spec.ts:22—NaNpinned into an SVG attributeThe
useCirclecases pass noseconds, so the default0applies andfullDashArraycomputes0/0:It is the only dasharray value in either snapshot file — no snapshot renders a valid ring.
['with seconds', …]has the value but notuseCircle; the two never meet. Fix is{ useCircle: circle, seconds: 100 }, matching the unit test atCountdown.spec.ts:374-382that already proves'283 283'.3. The
descriptionKeycluster — five specs setting a prop to its own defaultdescription?DropdownMenu.spec.ts:125items'description'InputMenu.spec.ts:52items'description'Listbox.spec.ts:48items'description'Select.spec.ts:53items'description'SelectMenu.spec.ts:52items'description'All five pass
descriptionKey: 'description'— the value the prop already has — on items that lack the field. Doubly inert, and confirmed by snapshot identity:InputMenu > with descriptionKeyis byte-identical towith items,with name,with selectedIconandwith size md.In every one of the five, the right fixture (
itemsWithDescription) is defined one line above and simply is not passed. Two siblings already do it correctly and are the model:CommandPalette.spec.ts:133andContextMenu.spec.ts:123. Both changes are needed — pass the with-description items and use a non-default key.4.
test/components/NavigationMenu.spec.ts:118— a slot no item selectsThe dynamic
customslot fires only for an item carryingslot: 'custom'. This is the only item-based spec whose fixture has noslot:key at all — every sibling has one. "Custom slot" appears nowhere in the snapshot, which is identical to four other cases.Tier 2 — one-line fixes, better as a single sweep PR
Table.spec.ts:251— theexpandedslot renders only for an expanded row; the case passes no columns and no expansion, so the snapshot is byte-identical towith data.Table.spec.ts:241,:244— every virtualization case renders zero data rows under happy-dom, sowith row pinning and virtualizationis byte-identical towith virtualizeand the pinning assertion is vacuous.Table.spec.ts:144-153—column.getIsSorted()isfalsein every snapshot; both sorted branches are unreachable. (And theascbranch resolves to the same icon as unsorted, so even adding a sort test could only distinguishdesc.)Table.spec.ts:187-222— the wholeactionsitemsarray is dead: every label appears 0 times across the snapshot file, because the menu is never opened.Alert.spec.ts:32(closeslot needs thecloseprop),CommandPalette.spec.ts:151(emptyneeds no results),Progress.spec.ts:32(statusneedsstatus/modelValue),Tabs.spec.ts:54(theslot: 'custom'item is not the active tab),ContextMenu.spec.ts:137(the item is inside a submenu that never opens),ChatMessages.spec.ts:41,:43.DescriptionList.spec.ts:83—with default slotpasses noitems, so it equalswith empty items.Stepper.spec.ts:28—defaultValue: 'Address'picks index 0, already the active step.'Shipping'would prove it. (Timeline.spec.ts:47picks index 1 and is fine.)Adjacent — a component defect, not a test one
src/runtime/components/Calendar.vue:267omitsnextMonth/nextYear/prevMonth/prevYearfromomittedProps(which does listviewControl), so they are forwarded to the picker root and serialise as attributes:Unlike Empty's, these props work — this is a genuine leak, pinned as expected output. Worth splitting into its own issue against the component.
The scale problem underneath
Of the 35
renderEachsnapshot entries inTable.spec.ts, only two render the custom cell functions at all — every other entry uses columns auto-derived from the data keys. The entirecolumnsarray's coverage rests on those two entries, which is why a dead branch there survives so easily.That generalises: a
renderEachmatrix produces a lot of snapshots cheaply, and the cheapness is the problem — nothing tells you which of them are distinguishable from each other.Suggested guard
A spec that groups snapshot entries by identical body and fails on collisions would have caught most of the above, since the symptom is nearly always "this case is byte-identical to a sibling".
test/utils/already holds cross-cutting invariant specs of this kind (docs-logical-properties.spec.ts,component-count-claims.spec.ts), so there is a pattern to follow.It would need an allowlist for legitimately-identical pairs, which is itself useful: the allowlist becomes the record of which cases are known not to assert anything.