v0.4.1
- scope options to select and datalist interfaces (805e374) (by Chris chris@stacksjs.com) (#1625)
- layout: take line-height and the baseline from the font (8555766) (by glennmichael123 gtorregosa@gmail.com)
Push over the same URL. A password will not work: create a token under access tokens and use it in place of one.
A very performant virtual DOM implementation.
19 releases, newest first.
This release gives very-happy-dom a layout pass. Boxes have positions now,
where getBoundingClientRect() previously reported a size at (0, 0) for
everything — so overlap, ordering, hit testing and is-this-above-the-fold are
answerable, and text is measured with real font metrics rather than a flat
estimate.
Everything below was checked against Chrome by serving the same markup to a real browser and diffing the rects: 122 measurements over eight pages, with the structural ones exact.
box-sizing,
margins, padding, borders, min/max sizes and percentages are all modelled, as
are position: relative and absolute/fixed against the nearest positioned
ancestor.wrap-reverse,
justify-content, align-items, align-content, align-self,
flex-grow/shrink/basis, gap, order, auto margins, and
align-items: baseline.fr, auto, minmax() and
repeat(auto-fill, ...), named areas, implicit tracks, grid-auto-flow
including dense, placement by line number or span, and the justify-*
and align-* family for both tracks and items.serif, sans-serif and monospace at both weights, which agree with
Chrome to within a tenth of a percent. Text was measured at a flat half the
font size per character before, which was out by up to 50% either way.font-size and line-height inherit, and em, rem, the viewport units,
calc(), min(), max() and clamp() all resolve.toBeInViewport, which could not be written while every box sat at the
origin.These are the reads whose values change. Each was wrong before rather than merely absent, so a test asserting the old value was asserting a placeholder.
| before | now | |
|---|---|---|
getBoundingClientRect().x / .y | always 0 | the box's real position |
offsetWidth on an unsized block | 0 | its containing block's width |
offsetTop / offsetLeft | always 0 | measured from offsetParent |
offsetParent | parentElement | nearest positioned ancestor |
elementFromPoint() | always body | the element at the point |
scrollWidth / scrollHeight | always 0 | the content's extent |
getClientRects() | always empty | one box per rendered element |
IntersectionObserver entries | a fixed 100x100 rect | measured geometry |
an inline-block's y | top of its line | on the text baseline |
| a text-driven width | length x font-size x 0.5 | real advances and kerning |
A declared size is still reported for an element outside a document, so a test that measures something it never appended keeps working.
A font outside the three generic families is measured as the family it falls
back to, so a page in Inter is measured as Arial, and a character outside ASCII
takes the family's average advance. A line's baseline comes from an estimated
ascent, within about a third of a pixel. There is no user-agent stylesheet, so
body starts at (0, 0) rather than a browser's 8px inset. An item spanning
several grid tracks does not contribute to those tracks' sizes, and named grid
lines are not resolved. Floats, transform, z-index ordering and intrinsic
image sizes are absent.
The compatibility guide has the full table.
Fixes a parser failure on framework markup. @click="tap()" was not a name the
attribute scanner accepted, so it stopped at the @, abandoned the whole tag, and
emitted @click="tap()">Go as text:
<button @click="tap()">Go</button>
textContent was '@click="tap()">Go' -- the <button> was goneA page of Alpine, Vue or stx markup came through as visible attribute soup with
its elements missing. Found by reading a real application's served HTML, where
innerText returned a wall of @click and x-class strings where the copy should
have been.
The spec allows almost any character in an attribute name, which is why these work
in a browser with no build step. @click, #slot, :aria-pressed, x-cloak and
v-on:click are all attributes now. A nameless position steps forward rather than
breaking out of the attribute loop, so a stray character cannot silently truncate a
tag's attributes either.
Two more fixes found by pointing the library at a real application's served HTML.
Neither needs anything exotic — a social meta tag and a lang attribute are on
essentially every page on the web.
meta[property="og:title"] threw Unsupported pseudo-class: ":title". The
pseudo-class scan read the raw selector including the inside of [...], so a
colon in a quoted attribute value was taken as the start of a pseudo-class. Every
og: meta selector failed, as did any value holding a URL or a time. Quoted
content is masked before the scan now, which leaves :not([property="og:x"])
working.
setContent silently dropped every attribute on <html>. An innerHTML
assignment cannot set them and nothing carried them across, so lang was lost —
a test asserting the page declares a language failed against a page that declares
one. A fragment that says nothing about <html> leaves the existing attributes
alone.
Fixes a crash on valid HTML. A < that cannot open a tag is now text, as it is in
a browser.
<p>a < b</p> threw `Invalid tag name` — a document containing a
comparison could not be parsed at all
<p>a <1 b</p> parsed, but reported "a " — the `<1` was read as a tag
and the rest of the line silently discardedFound by pointing the parser at a real application's served HTML and
delta-minimising the 634KB page that crashed it down to a fragment of a template
expression: rating() < 1 || submitting(). Markup like that is ordinary —
a comparison inside an attribute expression, a price, "<3" — and no fixture in the
test suite had ever contained one.
<! that is neither a comment nor a DOCTYPE, <?, and a nameless </> are also
text now, rather than the bogus comments a browser would make of them. That is a
deliberate narrowing: text can neither crash nor discard content, which are the
two failures that matter.
A second type-only fix. 0.3.0 and 0.3.1 both ship a declaration graph that does
not resolve: dist/screenshot/webview.d.ts was never written, while
capture.d.ts imports three types from it and the entry point re-exports
capture. So every consumer who typechecks fails, not only those touching
dialogs:
dist/screenshot/capture.d.ts(3,111): error TS2307: Cannot find module './webview'Runtime behaviour was never affected — dist/*.js has been correct throughout.
Upgrade from 0.3.0 or 0.3.1 if you use TypeScript. There is nothing else here.
0.3.1 added a guard that typechecks a consumer file against the built package. It filtered to TS1xxx syntax errors, and a module that was never written is a resolution error, so it passed while this was broken.
It now fails on any error naming a file under the package's own dist/, whatever
the code, and is verified against three shapes: the missing declaration, 0.3.0's
malformed Dialog comment verbatim, and a dangling type reference.
Five earlier versions of that filter each passed while something was broken, which
is the thing worth knowing: a guard that matches nothing looks exactly like a
guard that found nothing. skipLibCheck: true skips the very files being checked;
a lone .d.ts reports unresolved imports for every file; a file argument beside a
tsconfig raises TS5112 and exits 0 without checking; an absolute path match fails
because tsc reports relative; an unanchored dist/ match catches every
node_modules/x/dist in the tree.
A type-only fix. 0.3.0's dist/browser/Dialog.d.ts was not valid TypeScript, so
the package failed to typecheck for every consumer the moment they imported it:
readonly or an empty string. */
defaultValue?: string;
constructor(type: DialogType, message: string);A doc comment inside the constructor's parameter list made the declaration
emitter splice the comment's own text into the class body as a member, and drop
defaultValue from the emitted signature. Runtime behaviour was never affected —
dist/*.js was correct throughout — so anyone not typechecking saw nothing wrong.
Upgrade from 0.3.0 if you use TypeScript. There is nothing else in this release.
No test read the generated declarations. The suite typechecks src/, and the
packing guard added in #1598 checks the declared exports exist in the tarball.
Neither looks at what the emitter produced, and the only position that finds this
is a consumer's.
There is now a guard that takes that position: it writes a file importing the
built package, typechecks it against dist/ with its own tsconfig, and fails on
any TS1xxx syntax error anywhere in the declaration graph.
Closes the last of the Playwright-parity gaps. A minor rather than a patch: nine of these change what existing code observes, and most of them do so by reporting something that used to pass silently.
Each of these can turn a passing test red. In every case the old behaviour was the bug — an assertion that held whatever the page did.
isChecked, toBeChecked, check/uncheck and getByRole({ checked }) read
aria-checked. A design system's <div role="checkbox"> reported unchecked
whatever its state, so not.toBeChecked() passed unconditionally and check()
on an already-ticked control turned it off. (#1616)getBy* skip elements collapsed to a declared zero size. A closed accordion
or drawer built with height: 0 was fully reachable, so a role query matched a
button inside it and the click succeeded. (#1617)innerText collapses whitespace. It only trimmed the ends before, so
indentation between elements came through as literal runs of spaces. (#1614)@media rules apply. A dark-mode or responsive stylesheet had no effect,
while matchMedia reported the query matching — the two disagreed about the
same document. (#1611)waitForSelector throws TimeoutError instead of returning null. The
null turned "the selector never appeared" into null is not an object at the
call site. null now means only a deliberate detached/hidden wait. (#1605)keyboard.press('Control+a') dispatches key: 'a' with ctrlKey: true. It
dispatched the literal string 'Control+a' with no flags set, a value no real
KeyboardEvent can carry — so the handler never ran and the test asserted that
nothing happened, which was true. A key held with Control, Alt or Meta no longer
inserts text. (#1615)request and response fire for intercepted fetches, not only navigations.
route() enabled interception without ever reporting a request; using it
together with setRequestInterception(true) reported every request twice. (#1606)filter() throws on an unrecognised key. filter({ has: ... }) was dropped
silently and matched every candidate. (#1619)<html>
wrapper was assigned into documentElement, nesting an <html> inside the
<html>: elements existed but document.querySelector could not reach them.
Affected goto as much as setContent. (#1610)And one fix in the other direction: setRequestInterception(false) no longer
kills every route registered afterwards, which used to send the request to the
real network with nothing reporting a problem. (#1613)
page.on('dialog') with accept/dismiss, so the destructive
half of a confirm-gated flow is testable. Decisions must be synchronous; a
handler that awaits first is reported rather than silently defaulted. (#1612)page.setContent() (#1610), page.emulateMedia() (#1611)waitFor* family — waitForEvent, waitForResponse, waitForRequest,
waitForLoadState, waitForURL (#1606)filter({ has, hasNot }), or(), and(), .page
(#1619)selectOption, setInputFiles, press, clear,
dblclick, setChecked (#1608), evaluate, evaluateAll, allTextContents,
allInnerTexts (#1607), boundingBox, scrollIntoViewIfNeeded (#1609)toHaveRole, toHaveAccessibleName,
toHaveAccessibleDescription, toHaveCSS, toHaveId, toHaveJSProperty,
toHaveValues (#1618)keyboard.down() / up(), and event.code is populated (#1615)toBeInViewport and waitForLoadState('networkidle') are not provided, and
beforeunload is not in DialogType. With no layout pass and no in-flight
request tracking, each could only ever answer "yes" — a silent pass wearing an
assertion's clothes. A test records each decision so it is not added later from
Playwright's list.
1692 → 1965 tests.
Patch release with complete custom-element upgrade semantics, Shadow DOM slot assignment and slotchange behavior, plus deterministic WebSocket connection-failure handling. The attached package is the same npm-compatible artifact produced by the repository release build.