@wardic/test 0.0.0-stage → 1.0.0-rc.2
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/LICENSE +21 -0
- package/README.md +13 -2
- package/THIRD_PARTY_NOTICES.md +145 -0
- package/dist/clock.d.ts +55 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +119 -0
- package/dist/clock.js.map +1 -0
- package/dist/feeds.d.ts +25 -0
- package/dist/feeds.d.ts.map +1 -0
- package/dist/feeds.js +32 -0
- package/dist/feeds.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/input.d.ts +60 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +90 -0
- package/dist/input.js.map +1 -0
- package/dist/layer.d.ts +27 -0
- package/dist/layer.d.ts.map +1 -0
- package/dist/layer.js +43 -0
- package/dist/layer.js.map +1 -0
- package/dist/probe.d.ts +46 -0
- package/dist/probe.d.ts.map +1 -0
- package/dist/probe.js +60 -0
- package/dist/probe.js.map +1 -0
- package/dist/renderer.d.ts +32 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +43 -0
- package/dist/renderer.js.map +1 -0
- package/package.json +54 -4
- package/src/clock.ts +136 -0
- package/src/feeds.ts +46 -0
- package/src/index.ts +17 -0
- package/src/input.ts +104 -0
- package/src/layer.ts +78 -0
- package/src/probe.ts +87 -0
- package/src/renderer.ts +63 -0
package/src/input.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as InputSource from "@wardic/core/InputSource"
|
|
2
|
+
import * as Bool from "effect/Boolean"
|
|
3
|
+
import * as Effect from "effect/Effect"
|
|
4
|
+
import * as Predicate from "effect/Predicate"
|
|
5
|
+
import * as Layer from "effect/Layer"
|
|
6
|
+
import * as UndefinedOr from "effect/UndefinedOr"
|
|
7
|
+
import * as Option from "effect/Option"
|
|
8
|
+
import * as Feeds from "./feeds.ts"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* An InputSource that replays fixed scripts of events, as **Input feeds** that end after their
|
|
12
|
+
* script: a Stream over one ends too. `pointer(target)` replays the same `pointerEvents`,
|
|
13
|
+
* `focus(target)` the same `focusEvents` and `keyboard(target)` the same `keyEvents` to each
|
|
14
|
+
* subscriber (each ignores `target` — tests address a single surface). The first positional arg is
|
|
15
|
+
* pointer events for back-compat; focus and key events are optional, defaulting to empty so existing
|
|
16
|
+
* call sites emit nothing on them. Each script plays whole when its feed is subscribed, so scripts of
|
|
17
|
+
* two kinds don't interleave: a test that needs them to pushes them ({@link pushable}). Called with no
|
|
18
|
+
* args it yields an InputSource that emits nothing — for tests that don't exercise gestures. It adopts
|
|
19
|
+
* no pointer: `adoptPointer` delivers nothing.
|
|
20
|
+
*/
|
|
21
|
+
// Input feeds: #318. adoptPointer: #431.
|
|
22
|
+
export const layer = (
|
|
23
|
+
pointerEvents: ReadonlyArray<InputSource.PointerLikeEvent> = [],
|
|
24
|
+
focusEvents: ReadonlyArray<InputSource.FocusLikeEvent> = [],
|
|
25
|
+
keyEvents: ReadonlyArray<InputSource.KeyboardLikeEvent> = [],
|
|
26
|
+
): Layer.Layer<InputSource.InputSource> =>
|
|
27
|
+
Layer.succeed(InputSource.InputSource, {
|
|
28
|
+
pointer: () => Feeds.script(pointerEvents),
|
|
29
|
+
keyboard: () => Feeds.script(keyEvents),
|
|
30
|
+
focus: () => Feeds.script(focusEvents),
|
|
31
|
+
adoptPointer: () => Option.none(),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A push-based InputSource for tests that need to interleave pointer events with
|
|
36
|
+
* other effects (e.g. advancing the Clock between a release and a re-grab). Unlike
|
|
37
|
+
* {@link layer} — which replays a fixed script that drains immediately — this
|
|
38
|
+
* form lets the test `push` events one at a time, on its own schedule.
|
|
39
|
+
*
|
|
40
|
+
* Backed by a hub (`feeds.ts`) so that every subscription to `pointer(target)`
|
|
41
|
+
* hears every event: a consumer may subscribe to `input.pointer(target)` more than once
|
|
42
|
+
* (a binding's drag track reads the drag fold and the grabs). There is no replay buffer,
|
|
43
|
+
* so only events pushed *after* a subscription is live reach it.
|
|
44
|
+
*
|
|
45
|
+
* `push` doesn't yield: a subscriber handles the event inside the push, as an
|
|
46
|
+
* Environment's consumers handle a real event inside its dispatch, so a pipeline that hops
|
|
47
|
+
* through Effect's scheduler on its way to its sink hasn't got there when `push` returns,
|
|
48
|
+
* and its test sees it.
|
|
49
|
+
*
|
|
50
|
+
* Returns `{ layer, push }`. Resolve it inside an `Effect.gen`, provide `layer`,
|
|
51
|
+
* and drive the surface with `push`:
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const input = yield* TestInput.pushable()
|
|
55
|
+
* yield* Stream.runForEach(drag(undefined), …).pipe(Effect.fork…) // subscribe
|
|
56
|
+
* yield* input.push(ev("down", 0, 0, 0))
|
|
57
|
+
*
|
|
58
|
+
* Returns `pushFocus` and `pushKey` alongside `push` so focus/blur transitions and keys can be
|
|
59
|
+
* driven on the same schedule; `focus(target)` and `keyboard(target)` mirror `pointer(target)`, each
|
|
60
|
+
* with its own hub.
|
|
61
|
+
*
|
|
62
|
+
* `adoptPointer(target, raw)` takes a pointer event as its `raw`: a pressed one is pushed on
|
|
63
|
+
* the pointer hub as an adopted down, inside the call, as the DOM's is. The hub holds no pointer, so
|
|
64
|
+
* the test pushes that pointer's later events itself.
|
|
65
|
+
*
|
|
66
|
+
* `options.focusWithin` is the source's `focusWithin`, for a test of a **Keyboard press** that
|
|
67
|
+
* holds its keys only while focus is within its target. Without it the source says nothing of focus,
|
|
68
|
+
* and a press hears every key pushed.
|
|
69
|
+
*/
|
|
70
|
+
// The hub: #318. No yield: ADR-0032. Keys: #427. adoptPointer: #431. focusWithin: #460.
|
|
71
|
+
export const pushable = (options?: { readonly focusWithin?: ((target: unknown) => boolean) | undefined }): Effect.Effect<{
|
|
72
|
+
readonly layer: Layer.Layer<InputSource.InputSource>
|
|
73
|
+
readonly push: (event: InputSource.PointerLikeEvent) => Effect.Effect<void>
|
|
74
|
+
readonly pushFocus: (event: InputSource.FocusLikeEvent) => Effect.Effect<void>
|
|
75
|
+
readonly pushKey: (event: InputSource.KeyboardLikeEvent) => Effect.Effect<void>
|
|
76
|
+
}> =>
|
|
77
|
+
Effect.sync(() => {
|
|
78
|
+
const pointer = Feeds.hub<InputSource.PointerLikeEvent>()
|
|
79
|
+
const focus = Feeds.hub<InputSource.FocusLikeEvent>()
|
|
80
|
+
const keys = Feeds.hub<InputSource.KeyboardLikeEvent>()
|
|
81
|
+
return {
|
|
82
|
+
layer: Layer.succeed(InputSource.InputSource, {
|
|
83
|
+
pointer: () => pointer.feed,
|
|
84
|
+
keyboard: () => keys.feed,
|
|
85
|
+
focus: () => focus.feed,
|
|
86
|
+
focusWithin: UndefinedOr.map(options, (o) => o.focusWithin),
|
|
87
|
+
adoptPointer: (_target, raw) => {
|
|
88
|
+
if (isPressedPointer(raw)) {
|
|
89
|
+
const down: InputSource.PointerLikeEvent = { ...raw, type: "down", adopted: true }
|
|
90
|
+
Effect.runSync(pointer.push(down))
|
|
91
|
+
return Option.some(down)
|
|
92
|
+
}
|
|
93
|
+
return Option.none()
|
|
94
|
+
},
|
|
95
|
+
}),
|
|
96
|
+
push: pointer.push,
|
|
97
|
+
pushFocus: focus.push,
|
|
98
|
+
pushKey: keys.push,
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
/** Whether `raw` is a pointer event with a button pressed: one the pushable InputSource adopts (#431). */
|
|
103
|
+
const isPressedPointer = (raw: unknown): raw is InputSource.PointerLikeEvent =>
|
|
104
|
+
Bool.and(Predicate.hasProperty(raw, "pointerId"), Predicate.hasProperty(raw, "pressed")) && (raw as InputSource.PointerLikeEvent).pressed
|
package/src/layer.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as Announcer from "@wardic/core/Announcer"
|
|
2
|
+
import type * as Clock from "@wardic/core/Clock"
|
|
3
|
+
import * as DefaultTransition from "@wardic/core/DefaultTransition"
|
|
4
|
+
import * as DragActivity from "@wardic/core/DragActivity"
|
|
5
|
+
import * as FrameLoop from "@wardic/core/FrameLoop"
|
|
6
|
+
import type * as InputSource from "@wardic/core/InputSource"
|
|
7
|
+
import * as InterpolatorRegistry from "@wardic/core/InterpolatorRegistry"
|
|
8
|
+
import * as PressClaims from "@wardic/core/PressClaims"
|
|
9
|
+
import type * as Probe from "@wardic/core/Probe"
|
|
10
|
+
import * as ReducedMotion from "@wardic/core/ReducedMotion"
|
|
11
|
+
import * as RefreshFrameSource from "@wardic/core/RefreshFrameSource"
|
|
12
|
+
import type * as Renderer from "@wardic/core/Renderer"
|
|
13
|
+
import { constUndefined, constVoid } from "effect/Function"
|
|
14
|
+
import * as Layer from "effect/Layer"
|
|
15
|
+
import { AtomRegistry } from "effect/reactivity"
|
|
16
|
+
import * as TestClock from "./clock.ts"
|
|
17
|
+
import * as TestInput from "./input.ts"
|
|
18
|
+
import * as TestProbe from "./probe.ts"
|
|
19
|
+
import * as TestRenderer from "./renderer.ts"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The headless Environment's Refresh frame source: it never calls back, as its Clock never ticks
|
|
23
|
+
* on its own, so a flush gate built on it never flushes by itself. A test that fires frames
|
|
24
|
+
* provides `TestClock.pushable()`'s layer over it (ADR-0031, decision 6).
|
|
25
|
+
*/
|
|
26
|
+
const stillFrames: RefreshFrameSource.Shape = { requestFrame: constUndefined, cancelFrame: constVoid }
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The full headless environment: deterministic Clock (driven by `frames`), a Refresh frame
|
|
30
|
+
* source that never calls back, recording Renderer, static Probe, silent InputSource, core's real
|
|
31
|
+
* interpolator registry, a fresh AtomRegistry for reading Probe atoms
|
|
32
|
+
* (`viewport`, `prefersReducedMotion`), and a fresh Drag activity, so one test's
|
|
33
|
+
* drag never blocks another's hover or press, and core's Frame loop over that
|
|
34
|
+
* Clock. Provide this in place of `DomLive` to test animation code without a
|
|
35
|
+
* page. It draws nothing on one: an element rendered under it keeps its Starting style, so an
|
|
36
|
+
* app's components are tested with `DomLive` under the test runner's fake timers instead
|
|
37
|
+
* (https://wardic.dev/guides/testing).
|
|
38
|
+
*/
|
|
39
|
+
// The Drag activity: ADR-0019. The Frame loop: ADR-0027. The testing guide: #558.
|
|
40
|
+
export const testLayer = (
|
|
41
|
+
frames: ReadonlyArray<number> = [],
|
|
42
|
+
): Layer.Layer<
|
|
43
|
+
| Clock.Clock
|
|
44
|
+
| RefreshFrameSource.RefreshFrameSource
|
|
45
|
+
| FrameLoop.FrameLoop
|
|
46
|
+
| Renderer.Renderer
|
|
47
|
+
| Probe.Probe
|
|
48
|
+
| InputSource.InputSource
|
|
49
|
+
| InterpolatorRegistry.InterpolatorRegistry
|
|
50
|
+
| ReducedMotion.PositionalKeys
|
|
51
|
+
| DefaultTransition.DefaultTransition
|
|
52
|
+
| Announcer.Announcer
|
|
53
|
+
| AtomRegistry.AtomRegistry
|
|
54
|
+
> =>
|
|
55
|
+
FrameLoop.layer.pipe(
|
|
56
|
+
Layer.provideMerge(
|
|
57
|
+
Layer.mergeAll(
|
|
58
|
+
TestClock.layer(frames),
|
|
59
|
+
RefreshFrameSource.layer(stillFrames),
|
|
60
|
+
TestRenderer.layer,
|
|
61
|
+
TestProbe.layer(),
|
|
62
|
+
TestInput.layer(),
|
|
63
|
+
InterpolatorRegistry.layer,
|
|
64
|
+
// The transforms only: the Test Environment names no box keys (ADR-0036).
|
|
65
|
+
ReducedMotion.transformsOnly,
|
|
66
|
+
// No opinion on a key's Default transition: every key on one spring (ADR-0051).
|
|
67
|
+
DefaultTransition.layerOneSpring,
|
|
68
|
+
// No assistive technology to reach headless (ADR-0037).
|
|
69
|
+
Announcer.layerSilent,
|
|
70
|
+
AtomRegistry.layer,
|
|
71
|
+
Layer.sync(DragActivity.DragActivity, DragActivity.make),
|
|
72
|
+
Layer.sync(PressClaims.PressClaims, PressClaims.make),
|
|
73
|
+
),
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
/** `testLayer()` with an empty clock — for tests that drive their own frames. It never ticks on its own. */
|
|
78
|
+
export const TestLive = testLayer()
|
package/src/probe.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as Probe from "@wardic/core/Probe"
|
|
2
|
+
import * as Arr from "effect/Array"
|
|
3
|
+
import * as Effect from "effect/Effect"
|
|
4
|
+
import * as Layer from "effect/Layer"
|
|
5
|
+
import * as Stream from "effect/Stream"
|
|
6
|
+
import { Atom, AtomRegistry } from "effect/reactivity"
|
|
7
|
+
import * as Feeds from "./feeds.ts"
|
|
8
|
+
|
|
9
|
+
const ZERO_BOUNDS: Probe.Bounds = { x: 0, y: 0, width: 0, height: 0, top: 0, right: 0, bottom: 0, left: 0 }
|
|
10
|
+
const VIEWPORT = { width: 1024, height: 768 }
|
|
11
|
+
/** A page that doesn't scroll: no range, and progress 1, as Motion reads a container with none. */
|
|
12
|
+
const STILL_SCROLL: Probe.Scroll = { x: { current: 0, length: 0, progress: 1 }, y: { current: 0, length: 0, progress: 1 } }
|
|
13
|
+
|
|
14
|
+
/** A scripted in-view boolean as the Probe reports it, with no platform record. */
|
|
15
|
+
const toIntersection = (intersecting: boolean): Probe.Intersection => ({ intersecting })
|
|
16
|
+
|
|
17
|
+
/** Options for the static test Probe. */
|
|
18
|
+
// pageVisible: #443. scroll: #234. intersection, an Input feed: #318.
|
|
19
|
+
export interface Options {
|
|
20
|
+
readonly bounds?: Probe.Bounds
|
|
21
|
+
readonly viewport?: { readonly width: number; readonly height: number }
|
|
22
|
+
readonly prefersReducedMotion?: boolean
|
|
23
|
+
/** Whether the page is shown: `true` when left out. */
|
|
24
|
+
readonly pageVisible?: boolean
|
|
25
|
+
/** Every container's scroll, as `scroll` gives it: a page that doesn't scroll when left out. */
|
|
26
|
+
readonly scroll?: Probe.Scroll
|
|
27
|
+
/**
|
|
28
|
+
* Script of intersection (in-view) booleans replayed by `observeIntersection`, an **Input feed**
|
|
29
|
+
* that ends after it. Mirrors the InputSource test harness: omitted (or empty) means it
|
|
30
|
+
* emits nothing and ends.
|
|
31
|
+
*/
|
|
32
|
+
readonly intersection?: ReadonlyArray<boolean>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A static Probe: fixed bounds/viewport. `observeIntersection` replays the
|
|
37
|
+
* optional `intersection` script to each subscriber (empty by default, so it
|
|
38
|
+
* emits nothing); `observeResize` stays empty.
|
|
39
|
+
*/
|
|
40
|
+
export const layer = (options: Options = {}): Layer.Layer<Probe.Probe> => {
|
|
41
|
+
const { intersection = [], scroll = STILL_SCROLL } = options
|
|
42
|
+
return Layer.succeed(Probe.Probe, {
|
|
43
|
+
...fixed(options, Atom.make(scroll)),
|
|
44
|
+
observeIntersection: () => Feeds.script(Arr.map(intersection, toIntersection)),
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Both Probes' fixed part: the bounds, the viewport, the preference and the page's visibility, each defaulted, no resizes,
|
|
50
|
+
* and one scroll Atom for every container, target and offset (#234).
|
|
51
|
+
*/
|
|
52
|
+
const fixed = ({ bounds = ZERO_BOUNDS, viewport = VIEWPORT, prefersReducedMotion = false, pageVisible = true }: Options, scroll: Atom.Atom<Probe.Scroll>) => ({
|
|
53
|
+
measure: () => Effect.sync(() => bounds),
|
|
54
|
+
viewport: Atom.make(viewport),
|
|
55
|
+
prefersReducedMotion: Atom.make(prefersReducedMotion),
|
|
56
|
+
pageVisible: Atom.make(pageVisible),
|
|
57
|
+
scroll: () => Effect.succeed(scroll),
|
|
58
|
+
observeResize: () => Stream.empty,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A push-based Probe for tests that need to interleave intersection
|
|
63
|
+
* (in-view/out-of-view) transitions with other effects on their own schedule —
|
|
64
|
+
* mirroring {@link TestInput.pushable}. `observeIntersection(target)` subscribes
|
|
65
|
+
* to a hub, so every subscriber sees the same transitions and only
|
|
66
|
+
* events pushed *after* a subscription is live reach it. `pushIntersection`
|
|
67
|
+
* doesn't yield: a subscriber handles the transition inside the push,
|
|
68
|
+
* as `TestInput.pushable`'s do. `pushScroll` sets the scroll every container reads,
|
|
69
|
+
* through the Environment's registry, as a scroll event would.
|
|
70
|
+
* measure/viewport/prefersReducedMotion/observeResize behave as in {@link layer}.
|
|
71
|
+
*/
|
|
72
|
+
// The hub: #318. No yield: ADR-0032. Scroll: #234.
|
|
73
|
+
export const pushable = (options: Options = {}): Effect.Effect<{
|
|
74
|
+
readonly layer: Layer.Layer<Probe.Probe>
|
|
75
|
+
readonly pushIntersection: (inView: boolean) => Effect.Effect<void>
|
|
76
|
+
readonly pushScroll: (scroll: Probe.Scroll) => Effect.Effect<void, never, AtomRegistry.AtomRegistry>
|
|
77
|
+
}> =>
|
|
78
|
+
Effect.sync(() => {
|
|
79
|
+
const intersections = Feeds.hub<Probe.Intersection>()
|
|
80
|
+
const { scroll = STILL_SCROLL } = options
|
|
81
|
+
const scrollAtom = Atom.make(scroll)
|
|
82
|
+
return {
|
|
83
|
+
layer: Layer.succeed(Probe.Probe, { ...fixed(options, scrollAtom), observeIntersection: () => intersections.feed }),
|
|
84
|
+
pushIntersection: (inView: boolean) => intersections.push(toIntersection(inView)),
|
|
85
|
+
pushScroll: (next: Probe.Scroll) => AtomRegistry.AtomRegistry.use((registry) => Effect.sync(() => registry.set(scrollAtom, next))),
|
|
86
|
+
}
|
|
87
|
+
})
|
package/src/renderer.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as Renderer from "@wardic/core/Renderer"
|
|
2
|
+
import { constVoid } from "effect/Function"
|
|
3
|
+
import * as Layer from "effect/Layer"
|
|
4
|
+
|
|
5
|
+
/** A single recorded `project` call. */
|
|
6
|
+
export interface ProjectCall {
|
|
7
|
+
readonly values: Record<string, unknown>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** A single recorded `commit` call. */
|
|
11
|
+
export interface CommitCall {
|
|
12
|
+
readonly target: unknown
|
|
13
|
+
readonly patch: Renderer.Patch
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A Renderer that records every `project` and `commit` invocation into
|
|
18
|
+
* separate arrays instead of touching an environment — lets a test assert
|
|
19
|
+
* both the pure projection step and the imperative writes that would have
|
|
20
|
+
* been issued.
|
|
21
|
+
*
|
|
22
|
+
* Defaults:
|
|
23
|
+
* - `project` is identity (`(values) => values as Patch`).
|
|
24
|
+
* - `commit` pushes onto `commitCalls`.
|
|
25
|
+
*/
|
|
26
|
+
export const make = (): {
|
|
27
|
+
readonly service: {
|
|
28
|
+
readonly project: (values: Record<string, unknown>) => Renderer.Patch
|
|
29
|
+
readonly commit: (target: unknown, patch: Renderer.Patch) => void
|
|
30
|
+
}
|
|
31
|
+
readonly projectCalls: ReadonlyArray<ProjectCall>
|
|
32
|
+
readonly commitCalls: ReadonlyArray<CommitCall>
|
|
33
|
+
} => {
|
|
34
|
+
const projectCalls: Array<ProjectCall> = []
|
|
35
|
+
const commitCalls: Array<CommitCall> = []
|
|
36
|
+
return {
|
|
37
|
+
service: {
|
|
38
|
+
project: (values: Record<string, unknown>): Renderer.Patch => {
|
|
39
|
+
projectCalls.push({ values })
|
|
40
|
+
return values as Renderer.Patch
|
|
41
|
+
},
|
|
42
|
+
commit: (target: unknown, patch: Renderer.Patch): void => {
|
|
43
|
+
commitCalls.push({ target, patch })
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
projectCalls,
|
|
47
|
+
commitCalls,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A Renderer that draws nothing and keeps nothing: `project` hands its values back as the patch, and
|
|
53
|
+
* `commit` drops it. A test that reads the calls makes its own with {@link make}.
|
|
54
|
+
*/
|
|
55
|
+
// #618 (decision 7): this was one recording renderer made at module load, whose calls every test in a
|
|
56
|
+
// file added to and nothing read.
|
|
57
|
+
const DISCARDING: Renderer.Renderer["Service"] = {
|
|
58
|
+
project: (values) => values as Renderer.Patch,
|
|
59
|
+
commit: constVoid,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The {@link DISCARDING} Renderer's Layer. */
|
|
63
|
+
export const layer: Layer.Layer<Renderer.Renderer> = Layer.succeed(Renderer.Renderer, DISCARDING)
|