@octanejs/testing-library 0.1.48 → 0.1.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,10 +13,11 @@ pnpm add @octanejs/testing-library
13
13
  The split mirrors RTL's own architecture (and
14
14
  `docs/react-library-compat-plan.md` §2): **`@testing-library/dom` is
15
15
  framework-agnostic and reused verbatim** — every query, `screen`, `within`,
16
- `waitFor`/`waitForElementToBeRemoved`, `findBy*`, `fireEvent`, `prettyDOM`,
16
+ `waitFor`/`waitForElementToBeRemoved`, `findBy*`, `prettyDOM`,
17
17
  `configure` — while only react-testing-library's thin React layer is ported to
18
18
  octane: `render`, `cleanup`, `renderHook`, the `act` re-export, and the
19
- dom-testing-library config wiring (`eventWrapper`/`asyncWrapper`) that makes
19
+ focus/blur event helpers and dom-testing-library config wiring
20
+ (`eventWrapper`/`asyncWrapper`) that makes
20
21
  every dispatch/wait commit octane's scheduled work before your assertions run.
21
22
 
22
23
  ```ts
@@ -40,7 +41,8 @@ test('increments', () => {
40
41
  `RTL_SKIP_AUTO_CLEANUP=true` or import `@octanejs/testing-library/pure`)
41
42
  - `renderHook(callback, { initialProps, wrapper, ... }?)` → `{ result, rerender, unmount }`
42
43
  - `act` — octane's `act`, re-exported (always async; always `await` it)
43
- - `fireEvent`, `screen`, `waitFor`, `within`, … — dom-testing-library, re-exported
44
+ - `screen`, `waitFor`, `within`, … — dom-testing-library, re-exported
45
+ - `fireEvent` — DOM event helpers, with focus/blur pairs for delegated handlers
44
46
 
45
47
  ## Octane-specific surface
46
48
 
@@ -60,8 +62,8 @@ remounts — exactly RTL's rerender semantics.
60
62
  ## Differences from react-testing-library
61
63
 
62
64
  Octane dispatches **native, delegated DOM events — there is no synthetic event
63
- layer** — so `fireEvent` is dom-testing-library's, deliberately *without* RTL's
64
- React-specific remappings:
65
+ layer**. `fireEvent` uses DOM Testing Library with focus/blur pairs for
66
+ delegation. Other helpers keep native semantics:
65
67
 
66
68
  - **`fireEvent.change` fires a native `change`; `fireEvent.input` a native
67
69
  `input`.** In React, `onChange` handlers actually run off native `input`
@@ -85,18 +87,21 @@ React-specific remappings:
85
87
  sequence and automatic checked-state transition. `fireEvent.change(checkbox)`
86
88
  is only an explicit change dispatch; it does not model the click, activation,
87
89
  cancellation/rollback, or full event ordering.
88
- - **No enter/leave/focus double-dispatch.** RTL's `fireEvent.mouseEnter` also
89
- fires `mouseover` (and `focus` fires `focusin`, `select` fires `keyup`, …)
90
- purely to feed React's plugin system, which listens to different native
91
- events than the handler names suggest. Octane's `onMouseEnter` receives the
92
- real `mouseenter` (non-bubbling events are capture-delegated), so
93
- `fireEvent.mouseEnter` alone triggers it — no compensation needed or wanted.
94
- - **Commit timing is wired, not synthesized.** Octane already commits discrete
95
- events (`click`, `input`, `keydown`, …) synchronously; this package
96
- additionally wraps *every* `fireEvent` dispatch in `flushSync` + an effect
97
- drain via dom-testing-library's `eventWrapper`, so non-discrete/programmatic
98
- events also commit — with their `useEffect` cascades — before `fireEvent`
99
- returns (the equivalent of RTL's `act()` around each dispatch).
90
+ - **Focus helpers emit both native focus events.** `fireEvent.focus` dispatches
91
+ `focus` then `focusin`; `fireEvent.blur` dispatches `blur` then `focusout`,
92
+ matching browser order.
93
+ Octane's delegated `onFocus`/`onBlur` handlers receive `focusin`/`focusout`.
94
+ Both events preserve the supplied options, including `relatedTarget`, except
95
+ that the paired `focusin`/`focusout` always bubbles. The return value is false
96
+ when the native `focus`/`blur` event was canceled.
97
+ Use `user.tab()` or native `focus()`/`blur()` to move actual focus.
98
+ - **No enter/leave remapping.** `fireEvent.mouseEnter` dispatches a real
99
+ `mouseenter`, which Octane's capture delegation delivers directly. It does
100
+ not also dispatch `mouseover` to drive React's synthetic event plugins.
101
+ - **Every dispatch commits before returning.** The DOM library's
102
+ `eventWrapper` runs every `fireEvent` dispatch in `flushSync` and then drains
103
+ effects, so the event's state updates and `useEffect` cascades are visible
104
+ before the helper returns.
100
105
  - **Host elements at the root are `container.firstChild`, like RTL.**
101
106
  `render(createElement('div', …))` goes through octane's value-position
102
107
  renderer, which mounts a lone host element anchorless (the element
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/testing-library",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -41,14 +41,14 @@
41
41
  "@testing-library/dom": "^10.4.1"
42
42
  },
43
43
  "peerDependencies": {
44
- "octane": "0.1.51"
44
+ "octane": "^0.1.51 || ^0.2.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@testing-library/user-event": "^14.6.1",
48
48
  "react": "^19.2.7",
49
49
  "react-dom": "^19.2.7",
50
50
  "vitest": "^4.1.10",
51
- "octane": "0.1.51"
51
+ "octane": "0.2.3"
52
52
  },
53
53
  "scripts": {
54
54
  "test": "vitest run"
@@ -0,0 +1,45 @@
1
+ import * as dom from '@testing-library/dom';
2
+ import type { GetErrorFunction, QueryMethod, Variant, waitForOptions } from '@testing-library/dom';
3
+
4
+ // DOM Testing Library 10.4.1 exports these values but omits their declarations.
5
+ // Preserve the original functions and describe their pinned query contracts.
6
+ interface RuntimeQueryHelpers {
7
+ getMultipleElementsFoundError(message: string, container: Element): Error;
8
+ makeSingleQuery<Args extends unknown[], ElementType extends HTMLElement>(
9
+ query: QueryMethod<Args, ElementType[]>,
10
+ getMultipleError: GetErrorFunction<Args>,
11
+ ): QueryMethod<Args, ElementType | null>;
12
+ makeGetAllQuery<Args extends unknown[], ElementType extends HTMLElement>(
13
+ query: QueryMethod<Args, ElementType[]>,
14
+ getMissingError: GetErrorFunction<Args>,
15
+ ): QueryMethod<Args, ElementType[]>;
16
+ makeFindQuery<Text, Options, Result>(
17
+ query: (container: HTMLElement, text: Text, options?: Options) => Result,
18
+ ): (
19
+ container: HTMLElement,
20
+ text: Text,
21
+ options?: Options,
22
+ waitOptions?: waitForOptions,
23
+ ) => Promise<Awaited<Result>>;
24
+ wrapSingleQueryWithSuggestion<Args extends unknown[], Result extends HTMLElement | null>(
25
+ query: QueryMethod<Args, Result>,
26
+ queryName: string,
27
+ variant: Variant,
28
+ ): QueryMethod<Args, Result>;
29
+ wrapAllByQueryWithSuggestion<Args extends unknown[], ElementType extends HTMLElement>(
30
+ query: QueryMethod<Args, ElementType[]>,
31
+ queryName: string,
32
+ variant: Variant,
33
+ ): QueryMethod<Args, ElementType[]>;
34
+ }
35
+
36
+ export const {
37
+ getMultipleElementsFoundError,
38
+ makeSingleQuery,
39
+ makeGetAllQuery,
40
+ makeFindQuery,
41
+ wrapSingleQueryWithSuggestion,
42
+ wrapAllByQueryWithSuggestion,
43
+ } = dom as typeof dom & RuntimeQueryHelpers;
44
+
45
+ export const queryHelpers = dom.queryHelpers as typeof dom.queryHelpers & RuntimeQueryHelpers;
package/src/fire-event.ts CHANGED
@@ -1,22 +1,26 @@
1
- // `fireEvent` — dom-testing-library's, re-exported UNWRAPPED.
2
- //
3
- // react-testing-library layers remappings on top of dom-testing-library's
4
- // fireEvent because React's synthetic event system listens to DIFFERENT native
5
- // events than the handler names suggest (mouseEnter handlers run off native
6
- // mouseover, focus/blur off focusin/focusout, `select` is synthesized from key
7
- // events, `onChange` fires on native input, …), so RTL double-dispatches to
8
- // feed React's plugins.
9
- //
10
- // Octane has NO synthetic event layer — `onX` handlers receive the native `x`
11
- // event, delegated. `fireEvent.mouseEnter` therefore dispatches a real
12
- // `mouseenter` and octane's capture-phase delegation of non-bubbling events
13
- // delivers it; `fireEvent.change` fires a native `change` (NOT React's
14
- // input-as-change); no remapping is wanted. The commit wiring (flushSync +
15
- // effect drain around every dispatch) rides on dom-testing-library's
16
- // `eventWrapper` config hook — see pure.ts.
17
- //
18
- // In particular, `fireEvent.change(textbox)` means “dispatch an explicit native
19
- // commit event”; it does not simulate typing or blur. Likewise, change on a
20
- // checkbox does not reproduce click activation/toggling or click → input →
21
- // change ordering. Use user-event's type/tab/click flows for those sequences.
22
- export { fireEvent } from '@testing-library/dom';
1
+ import { fireEvent as domFireEvent } from '@testing-library/dom';
2
+
3
+ // Keep the DOM helpers independent: callers importing @testing-library/dom
4
+ // directly still get one native event per helper. Octane's onFocus/onBlur
5
+ // delegation listens to focusin/focusout, so those two convenience helpers
6
+ // emit native focus/blur followed by the corresponding bubbling event.
7
+ export const fireEvent: typeof domFireEvent = Object.assign(
8
+ (...args: Parameters<typeof domFireEvent>) => domFireEvent(...args),
9
+ domFireEvent,
10
+ );
11
+
12
+ fireEvent.focus = (element, options) => {
13
+ const result = domFireEvent.focus(element, options);
14
+ domFireEvent.focusIn(element, { ...options, bubbles: true });
15
+ return result;
16
+ };
17
+ fireEvent.blur = (element, options) => {
18
+ const result = domFireEvent.blur(element, options);
19
+ domFireEvent.focusOut(element, { ...options, bubbles: true });
20
+ return result;
21
+ };
22
+
23
+ // Other helpers retain DOM semantics: change dispatches an explicit native
24
+ // commit event; mouseEnter does not also dispatch mouseover. Use user-event
25
+ // for complete typing, click activation, and focus movement sequences.
26
+ // Commit/effect flushing uses the DOM library's eventWrapper (see pure.ts).
package/src/pure.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Strategy (docs/react-library-compat-plan.md §2): `@testing-library/dom` is
5
5
  * framework-agnostic, so it is depended on VERBATIM and re-exported wholesale
6
- * (queries, `screen`, `within`, `waitFor`, `fireEvent`, `prettyDOM`, …). Only
6
+ * (queries, `screen`, `within`, `waitFor`, `prettyDOM`, …). Only
7
7
  * react-testing-library's thin React layer is ported here, onto octane:
8
8
  *
9
9
  * - `render` / `cleanup` / `renderHook` mount through octane's `createRoot`
@@ -43,6 +43,7 @@ import {
43
43
  } from '@testing-library/dom';
44
44
  import type { BoundFunctions, Queries } from '@testing-library/dom';
45
45
  import { getIsOctaneActEnvironment, setOctaneActEnvironment } from './act-environment';
46
+ import { fireEvent as octaneFireEvent } from './fire-event';
46
47
 
47
48
  // ─────────────────────────────────────────────────────────────────────────────
48
49
  // dom-testing-library config wiring (RTL pure.js does the same three hooks).
@@ -87,13 +88,8 @@ configureDTL({
87
88
  setOctaneActEnvironment(previousActEnvironment);
88
89
  }
89
90
  },
90
- // Every `fireEvent` dispatch runs through here. Octane's DISCRETE delegated
91
- // events (click/input/keydown/…) already commit synchronously on their own
92
- // (maybeFlushDiscrete → flushSync), but fireEvent also dispatches event types
93
- // with no discrete path — non-delegated or programmatic events whose updates
94
- // would otherwise sit queued until the next microtask, i.e. after the test's
95
- // assertion. flushSync commits those too, and the passive drain mirrors RTL's
96
- // act(): effects scheduled by the event have run before fireEvent returns.
91
+ // Every fireEvent dispatch commits its updates and drains passive effects
92
+ // before returning, including programmatic and non-discrete events.
97
93
  eventWrapper: (cb) => {
98
94
  let result: unknown;
99
95
  flushSync(() => {
@@ -407,10 +403,93 @@ export function renderHook<Result, Props = undefined>(
407
403
 
408
404
  // ─────────────────────────────────────────────────────────────────────────────
409
405
  // Exports — everything dom-testing-library ships, plus the octane layer.
410
- // `fireEvent` is dom-testing-library's own (see fire-event.ts for why there is
411
- // deliberately NO react-testing-library-style event remapping on top).
406
+ // fireEvent adds native focus/blur pairs for delegated focus handlers.
407
+ // Other convenience helpers retain DOM Testing Library's event semantics.
412
408
  // ─────────────────────────────────────────────────────────────────────────────
413
409
 
414
- export * from '@testing-library/dom';
415
- export { fireEvent } from './fire-event';
410
+ export const fireEvent = octaneFireEvent;
411
+ // Explicit value exports keep older SSR module runners from replacing our
412
+ // focus-aware fireEvent with the DOM helper during wildcard re-export.
413
+ export {
414
+ buildQueries,
415
+ configure,
416
+ createEvent,
417
+ findAllByAltText,
418
+ findAllByDisplayValue,
419
+ findAllByLabelText,
420
+ findAllByPlaceholderText,
421
+ findAllByRole,
422
+ findAllByTestId,
423
+ findAllByText,
424
+ findAllByTitle,
425
+ findByAltText,
426
+ findByDisplayValue,
427
+ findByLabelText,
428
+ findByPlaceholderText,
429
+ findByRole,
430
+ findByTestId,
431
+ findByText,
432
+ findByTitle,
433
+ getAllByAltText,
434
+ getAllByDisplayValue,
435
+ getAllByLabelText,
436
+ getAllByPlaceholderText,
437
+ getAllByRole,
438
+ getAllByTestId,
439
+ getAllByText,
440
+ getAllByTitle,
441
+ getByAltText,
442
+ getByDisplayValue,
443
+ getByLabelText,
444
+ getByPlaceholderText,
445
+ getByRole,
446
+ getByTestId,
447
+ getByText,
448
+ getByTitle,
449
+ getConfig,
450
+ getDefaultNormalizer,
451
+ getElementError,
452
+ getNodeText,
453
+ getQueriesForElement,
454
+ getRoles,
455
+ getSuggestedQuery,
456
+ isInaccessible,
457
+ logDOM,
458
+ logRoles,
459
+ prettyDOM,
460
+ prettyFormat,
461
+ queries,
462
+ queryAllByAltText,
463
+ queryAllByAttribute,
464
+ queryAllByDisplayValue,
465
+ queryAllByLabelText,
466
+ queryAllByPlaceholderText,
467
+ queryAllByRole,
468
+ queryAllByTestId,
469
+ queryAllByText,
470
+ queryAllByTitle,
471
+ queryByAltText,
472
+ queryByAttribute,
473
+ queryByDisplayValue,
474
+ queryByLabelText,
475
+ queryByPlaceholderText,
476
+ queryByRole,
477
+ queryByTestId,
478
+ queryByText,
479
+ queryByTitle,
480
+ screen,
481
+ waitFor,
482
+ waitForElementToBeRemoved,
483
+ within,
484
+ } from '@testing-library/dom';
485
+ export {
486
+ getMultipleElementsFoundError,
487
+ makeFindQuery,
488
+ makeGetAllQuery,
489
+ makeSingleQuery,
490
+ queryHelpers,
491
+ wrapAllByQueryWithSuggestion,
492
+ wrapSingleQueryWithSuggestion,
493
+ } from './dom-query-helpers';
494
+ export type * from '@testing-library/dom';
416
495
  export { act } from 'octane';