@@ -22,7 +22,9 @@ the background if none is running.
2222` bin/test --spec="up.form" ` . The full suite runs in a real browser and takes
2323several minutes, so run it unfiltered only as a final gate — and note that
2424[ CI] ( commit-conventions.md#continuous-integration ) runs the full matrix once you open
25- a pull request.
25+ a pull request. Some modules are used by most others, so a change to ` up.fragment ` ,
26+ ` up.network ` , ` up.script ` , ` up.layer ` , ` up.util ` or ` up.element ` can break specs anywhere
27+ and deserves the full suite.
2628
2729A run with one failing spec looks like this:
2830
@@ -50,7 +52,7 @@ Stack traces are mapped back to the original source, and the `Debug in browser:`
5052opens that single spec in a browser you can put DevTools on.
5153
5254** Debug by instrumenting, not stepping.** Anything you ` console.log ` in a spec or in
53- ` src/unpoly ` appears in the browser log that ` --verbose ` prints, so adding a log line
55+ ` src/unpoly ` appears in the browser log that ` --verbose ` prints (below) , so adding a log line
5456and re-running is usually the quickest way to inspect state — and the only way if you
5557can't open a browser. When you genuinely need DevTools, open the ` Debug in browser: `
5658URL from a failing run rather than trying to step-debug the headless browser.
@@ -60,32 +62,16 @@ failure with the flag keeps everything above and adds the browser log plus an ou
6062the DOM as it stood when the spec failed:
6163
6264```
63- F1) state demo → shows the HTML state on failure
64-
65- Failure/error:
66- Expected 'foo@example.com' to equal 'bar@example.com'.
67-
68- Stacktrace:
69- <jasmine internals>
70- spec/unpoly/tmp_state_demo_spec.js:15 in anonymous function
71- <jasmine internals>
72-
7365 Browser log:
7466 log: field value is "foo@example.com"
7567
7668 HTML state:
7769 body
78- default-fallback
7970 #fixtures
8071 form#signup.signup-form[method="post"][action="/action"][up-submit]
8172 input[type="text"][name="email"][value="foo@example.com"]
8273 button[type="submit"]
8374 Sign up
84- #outside-fixtures
85- attached outside #fixtures
86-
87- Debug in browser:
88- http://localhost:4000/specs?spec=state%20demo%20shows%20the%20HTML%20state%20on%20failure
8975```
9076
9177The HTML state is a tree of CSS selectors rather than raw markup, which keeps a large
@@ -95,17 +81,9 @@ enough to identify the problem without opening a browser — a fixture that was
9581compiled, an overlay still on the stack, a fragment inserted in the wrong place, or an
9682attribute you expected Unpoly to have set.
9783
98- It is rooted at ` body ` , so it includes elements attached outside ` #fixtures ` and any
99- state Unpoly wrote onto ` body ` itself. Jasmine's own reporter is excluded, and
100- ` default-fallback ` is a container the spec harness adds to every layer so that specs
101- always have a main target. Very large trees are clipped at 600 nodes, with a line saying
102- so — if you hit that, narrow the fixture rather than trusting what's shown.
103-
104- ` --verbose ` also changes the stack. The compact form shows just two lines — the topmost
105- frame in a spec file (which spec failed) and the topmost frame in ` src/ ` (where in Unpoly
106- it originated) — while the verbose form keeps every frame. The ` <jasmine internals> `
107- markers are Jasmine's own work: it collapses its internal frames before the runner ever
108- sees the stack.
84+ It is rooted at ` body ` , so it includes elements attached outside ` #fixtures ` , and a very
85+ large tree is clipped with a line saying so. ` --verbose ` also keeps every stack frame,
86+ where the default shows only the topmost frame in a spec file and in ` src/ ` .
10987
11088** Options:**
11189
@@ -116,7 +94,7 @@ sees the stack.
11694| ` --browser=firefox ` | Run in Firefox instead of Chrome |
11795| ` --headless=false ` | Show the browser window while running |
11896| ` --stop-on-failure=false ` | Keep running a spec after its first failed expectation (default: stop) |
119- | ` --csp=… ` · ` --es6 ` · ` --migrate ` | Serve the specs under a CSP / ES6 / unpoly-migrate variant |
97+ | ` --csp=none\|nonce-only\|strict-dynamic ` · ` --es6 ` · ` --migrate ` | Serve the specs under a CSP / ES6 / unpoly-migrate variant |
12098
12199` --minify ` runs the specs against the bundle we actually ship. That matters because the
122100minifier renames every ` _ ` -prefixed member, so a spec that reaches one by name passes
@@ -129,34 +107,14 @@ A failed spec exits with a non-zero exit code, so `bin/test` composes with other
129107tooling. The spec runner itself — its exit codes, architecture and self-tests — is
130108documented in [ ` runner/README.md ` ] ( ../../runner/README.md ) .
131109
132- ### Which specs to run
133-
134- The full suite takes several minutes, so don't run it on every edit. While you work, run
135- the module you are changing:
136-
137- ```
138- bin/test --spec="up.form"
139- ```
140-
141- Some modules are used by most others, and a change there can break specs anywhere:
142- ` up.fragment ` , ` up.network ` , ` up.script ` , ` up.layer ` , ` up.util ` and ` up.element ` . Treat a
143- change to those as needing the full suite.
144-
145- Run the full suite once before you hand the change over. CI runs it on every pull request
146- across several CSP and build variants, so it is a check, not your only safety net.
147-
148110### If you are an agent
149111
150112** Give the command a generous timeout.** The full suite takes minutes, and nothing caps
151113its total runtime. A tool timeout shorter than that kills the run part-way and tells you
152114nothing about your change.
153115
154- You don't need to guard against a hang yourself. The runner watches for * silence* , not
155- duration: if it sees no spec event for 30 seconds — a frozen browser session, say — it
156- reports ` no progress from the spec runner for 30s ` and exits with code ` 2 ` . A single spec
157- that never settles is caught earlier still, by Jasmine's 5-second per-spec timeout. So
158- set your timeout from how long the suite actually takes, not from either of those
159- numbers.
116+ You don't need to guard against a hang yourself: a run that stops making progress detects
117+ itself and exits non-zero. Set your timeout from how long the suite actually takes.
160118
161119** Don't pipe the output through ` tail ` or ` head ` .** The part you need from a red run is
162120the failure block, and truncating it means running the whole suite again to see what
@@ -245,7 +203,11 @@ alongside it — `form_switch_spec.js` for `[up-switch]`, `layer_open_spec.js` f
245203
246204```
247205$ ls spec/unpoly | grep ^form_
248- form_spec.js form_submit_attr_spec.js form_submit_fn_spec.js form_switch_spec.js …
206+ form_spec.js
207+ form_submit_attr_spec.js
208+ form_submit_fn_spec.js
209+ form_switch_spec.js
210+ …
249211```
250212
251213Opening the module file also tells you what it gave away, and where that used to sit:
@@ -262,7 +224,8 @@ spec code that happens to call the feature:
262224$ bin/find-spec "kept element"
263225fragment_keep_spec.js:47 up.fragment / unobtrusive behavior / [up-keep] / does not run destructors within kept elements
264226fragment_keep_spec.js:143 up.fragment / unobtrusive behavior / [up-keep] / omits a kept element from the returned up.RenderResult
265- radio_poll_spec.js:769 up.radio / unobtrusive behavior / [up-poll] / keeps polling if an [up-keep] ancestor is kept
227+ fragment_keep_spec.js:189 up.fragment / unobtrusive behavior / [up-keep] / keeps the scroll position of an [up-viewport] within a kept element
228+ …
266229```
267230
268231Each path is the spec's full name, so you can hand any part of it back to ` --spec ` . Passing
@@ -310,23 +273,11 @@ extendDescribe('up.fragment', function() {
310273` ` `
311274
312275So the split is invisible from the outside: one suite per module, unchanged full names,
313- and ` beforeEach` hooks from the module file still apply. An extracted file can extract
314- further, as ` up .render ()` does — it is the only feature large enough to need it.
276+ and ` beforeEach` hooks from the module file still apply.
315277
316- Two things to know if you ever add such a file:
317-
318- - It must **not** be listed in ` spec/ specs .js ` , which requires every other spec file by
319- hand. It is only ever loaded by its one ` require ()` . ` bin/ self - test` enforces both
320- halves: it fails on a spec file that nothing loads, and on an extracted file that
321- ` specs .js ` would load a second time.
322- - It is its own module, so aliases like ` const u = up .util ` at the top of the module file
323- are not in scope. Redeclare the ones you use — ` bin/ lint` names them.
324-
325- The feature part of the name is the feature's own name without ` up.` or ` up- ` :
326- ` form_switch_spec .js ` for ` [up- switch ]`, `layer_open_spec.js` for `up.layer.open()`. When
327- a module has both a function and a selector of that name, both files say which:
328- `form_validate_fn_spec.js` and `form_validate_attr_spec.js`. A file may also hold several
329- related features under a topic name, as `util_lists_spec.js` does.
278+ If you ever add such a file, the tooling keeps you honest: ` bin/ self - test` fails if it is
279+ listed in ` spec/ specs .js ` (or if nothing loads it), and ` bin/ lint` names any alias you need
280+ to redeclare, since an extracted file is its own module.
330281
331282
332283## Anatomy of a spec
@@ -391,34 +342,14 @@ When a spec needs the same markup more than once with variations — typically a
391342page and the server's "new" response — build it with a function:
392343
393344` ` ` js
394- it(' runs compilers after hungry fragments have been swapped' , async function () {
395- let compileSpy = jasmine .createSpy (' compile spy' )
396-
397- let html = (prefix ) => `
398- <div id="fragment1">
399- ${ prefix} fragment1
400- </div>
401- <div id="fragment2" up-hungry>
402- ${ prefix} fragment2
403- </div>
404- `
405-
406- up .compiler (' #fragment1' , (element ) => {
407- compileSpy (
408- document .querySelector (' #fragment1' ).textContent .trim (),
409- document .querySelector (' #fragment2' ).textContent .trim (),
410- )
411- })
345+ let html = (prefix ) => `
346+ <div id="fragment1">${ prefix} fragment1</div>
347+ <div id="fragment2" up-hungry>${ prefix} fragment2</div>
348+ `
412349
413- htmlFixtureList (html (' old' ))
350+ htmlFixtureList (html (' old' ))
414351
415- await up .render ({ target: ' #fragment1' , document : html (' new' ) })
416-
417- expect (compileSpy).toHaveBeenCalledWith (
418- ' new fragment1' ,
419- ' new fragment2' ,
420- )
421- })
352+ await up .render ({ target: ' #fragment1' , document : html (' new' ) })
422353` ` `
423354
424355This keeps the two versions visibly identical apart from the one thing that differs.
@@ -434,10 +365,15 @@ const [form, field] = htmlFixtureList(`...`)
434365up .hello (form)
435366` ` `
436367
437- Keeping this explicit is deliberate . It ' s often not needed — behavior wired to a
438- delegated event listener works on an uncompiled fixture — and when it *is* needed, some
439- specs care about exactly when compilers run. An implicit `up.hello()` would take that
440- observation away.
368+ Keeping this explicit is deliberate: some specs care about exactly when compilers run.
369+
370+ ### State resets between specs
371+
372+ You never have to undo what a spec did. Fixtures are removed, and Unpoly itself is reset
373+ after every spec — compilers you registered, ` up.* .config ` you changed, open overlays, the
374+ cache. So a spec can call ` up .compiler ()` or set config freely, and the next one starts
375+ clean.
376+
441377
442378### Overlays and styles
443379
@@ -549,7 +485,7 @@ making a mocked request time out. `jasmine.lastRequest().responseTimeout()` come
549485jasmine-ajax and only works with the clock installed. The handful of specs that do this
550486say so in a comment.
551487
552- Older specs use ` up .util .timer (ms, callback)` . Use ` jasmine . waitTime (ms)` instead.
488+ Older specs use ` up .util .timer (ms, callback)` . Use ` await wait (ms)` instead.
553489
554490### Asserting on promises
555491
579515## Matchers
580516
581517Custom matchers live one per file in ` spec/ helpers/ ` , named after the matcher
582- (` to_have_text .js ` → ` toHaveText ()` ). There are around ninety . Check for an existing one
518+ (` to_have_text .js ` → ` toHaveText ()` ). There are around seventy . Check for an existing one
583519before asserting by hand — most things you'd want to say about an element, a request or
584520a cache entry already have a matcher, and using it gives a far better failure message
585521than a hand-rolled boolean.
@@ -608,7 +544,7 @@ The ones you'll use constantly:
608544| ` toHaveSelector (selector)` | A descendant matches the selector |
609545| ` toMatchSelector (selector)` | The element itself matches |
610546| ` toBeAttached ()` · ` toBeDetached ()` | Whether the element is in the DOM tree |
611- | ` toBeMissing ()` | A *value* is ` null ` , ` undefined ` or blank — not a DOM check |
547+ | ` toBeMissing ()` | A *value* is ` null ` or ` undefined ` — not a DOM check |
612548| ` toBeVisible ()` · ` toBeHidden ()` | Rendered visibility |
613549| ` toBeFocused ()` | The element has focus |
614550| ` toHaveAttribute (name, value)` | An attribute and (optionally) its value |
0 commit comments