@react-three/postprocessing 3.0.5 → 3.1.1

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.
Files changed (106) hide show
  1. package/README.md +6 -7
  2. package/dist/EffectComposer.d.ts +9 -2
  3. package/dist/EffectGroup.d.ts +9 -0
  4. package/dist/createEffectComponent.d.ts +30 -0
  5. package/dist/effects/ASCII.d.ts +28 -7
  6. package/dist/effects/Autofocus.d.ts +1 -1
  7. package/dist/effects/Bloom.d.ts +9 -1
  8. package/dist/effects/BrightnessContrast.d.ts +14 -1
  9. package/dist/effects/ChromaticAberration.d.ts +26 -3
  10. package/dist/effects/ColorAverage.d.ts +7 -7
  11. package/dist/effects/ColorDepth.d.ts +7 -1
  12. package/dist/effects/Depth.d.ts +12 -1
  13. package/dist/effects/DotScreen.d.ts +14 -1
  14. package/dist/effects/FXAA.d.ts +10 -1
  15. package/dist/effects/Glitch.d.ts +16 -11
  16. package/dist/effects/GodRays.d.ts +1 -1
  17. package/dist/effects/Grid.d.ts +7 -6
  18. package/dist/effects/HueSaturation.d.ts +14 -1
  19. package/dist/effects/LUT.d.ts +1 -1
  20. package/dist/effects/LensFlare.d.ts +46 -5
  21. package/dist/effects/Noise.d.ts +7 -1
  22. package/dist/effects/Outline.d.ts +6 -3
  23. package/dist/effects/Pixelation.d.ts +1 -1
  24. package/dist/effects/Ramp.d.ts +58 -6
  25. package/dist/effects/SMAA.d.ts +9 -1
  26. package/dist/effects/SSAO.d.ts +1 -1
  27. package/dist/effects/ScanlineEffect.d.ts +12 -1
  28. package/dist/effects/SelectiveBloom.d.ts +1 -1
  29. package/dist/effects/Sepia.d.ts +12 -1
  30. package/dist/effects/ShockWave.d.ts +11 -1
  31. package/dist/effects/Texture.d.ts +4 -4
  32. package/dist/effects/TiltShift.d.ts +9 -1
  33. package/dist/effects/TiltShift2.d.ts +44 -4
  34. package/dist/effects/ToneMapping.d.ts +9 -3
  35. package/dist/effects/Vignette.d.ts +18 -1
  36. package/dist/effects/Water.d.ts +17 -1
  37. package/dist/index.d.ts +4 -1
  38. package/dist/index.js +2205 -713
  39. package/dist/index.js.map +1 -1
  40. package/dist/passes/DepthPicking.d.ts +11 -0
  41. package/dist/{effects → passes}/N8AO.d.ts +2 -1
  42. package/dist/util.d.ts +9 -15
  43. package/dist/wrapEffect.d.ts +3 -3
  44. package/package.json +6 -5
  45. package/src/EffectComposer.tsx +143 -42
  46. package/src/EffectGroup.tsx +74 -0
  47. package/src/createEffectComponent.tsx +71 -0
  48. package/src/effects/ASCII.tsx +75 -28
  49. package/src/effects/Autofocus.tsx +24 -77
  50. package/src/effects/Bloom.tsx +32 -4
  51. package/src/effects/BrightnessContrast.tsx +5 -2
  52. package/src/effects/ChromaticAberration.tsx +13 -21
  53. package/src/effects/ColorAverage.tsx +3 -16
  54. package/src/effects/ColorDepth.tsx +23 -2
  55. package/src/effects/Depth.tsx +4 -2
  56. package/src/effects/DepthOfField.tsx +48 -26
  57. package/src/effects/DotScreen.tsx +5 -2
  58. package/src/effects/FXAA.tsx +4 -2
  59. package/src/effects/Glitch.tsx +26 -31
  60. package/src/effects/GodRays.tsx +90 -6
  61. package/src/effects/Grid.tsx +17 -18
  62. package/src/effects/HueSaturation.tsx +5 -2
  63. package/src/effects/LUT.tsx +9 -11
  64. package/src/effects/LensFlare.tsx +209 -55
  65. package/src/effects/Noise.tsx +14 -2
  66. package/src/effects/Outline.tsx +55 -56
  67. package/src/effects/Pixelation.tsx +11 -8
  68. package/src/effects/Ramp.tsx +96 -7
  69. package/src/effects/SMAA.tsx +18 -2
  70. package/src/effects/SSAO.tsx +130 -22
  71. package/src/effects/ScanlineEffect.tsx +6 -6
  72. package/src/effects/SelectiveBloom.tsx +26 -44
  73. package/src/effects/Sepia.tsx +4 -2
  74. package/src/effects/ShockWave.tsx +30 -2
  75. package/src/effects/Texture.tsx +10 -9
  76. package/src/effects/TiltShift.tsx +30 -2
  77. package/src/effects/TiltShift2.tsx +72 -6
  78. package/src/effects/ToneMapping.tsx +16 -3
  79. package/src/effects/Vignette.tsx +5 -2
  80. package/src/effects/Water.tsx +21 -6
  81. package/src/index.ts +4 -2
  82. package/src/passes/DepthPicking.tsx +63 -0
  83. package/src/{effects → passes}/N8AO.tsx +28 -4
  84. package/src/tests/Autofocus.test.tsx +38 -0
  85. package/src/tests/Bloom.test.tsx +76 -0
  86. package/src/tests/ChromaticAberration.test.tsx +50 -0
  87. package/src/tests/ColorDepth.test.tsx +71 -0
  88. package/src/tests/DepthOfField.test.tsx +130 -0
  89. package/src/tests/DepthPicking.test.tsx +250 -0
  90. package/src/tests/EffectComposer.test.tsx +501 -8
  91. package/src/tests/EffectGroup.test.tsx +337 -0
  92. package/src/tests/Glitch.test.tsx +56 -0
  93. package/src/tests/GodRays.test.tsx +101 -0
  94. package/src/tests/Grid.test.tsx +34 -0
  95. package/src/tests/LUT.test.tsx +64 -0
  96. package/src/tests/N8AO.test.tsx +92 -0
  97. package/src/tests/Outline.test.tsx +122 -0
  98. package/src/tests/SSAO.test.tsx +140 -0
  99. package/src/tests/SelectiveBloom.test.tsx +48 -0
  100. package/src/tests/ShockWave.test.tsx +95 -0
  101. package/src/tests/TiltShift.test.tsx +76 -0
  102. package/src/tests/createEffectComponent.test.tsx +286 -0
  103. package/src/tests/effects.smoke.test.tsx +46 -42
  104. package/src/util.tsx +148 -23
  105. package/src/wrapEffect.tsx +3 -2
  106. package/dist/tests/test-utils.d.ts +0 -12
@@ -86,4 +86,126 @@ describe('Outline', () => {
86
86
  await waitForComposer(composerRef)
87
87
  await expect(flush()).resolves.not.toThrow()
88
88
  })
89
+
90
+ it('applies visibleEdgeColor live, without reconstructing the effect (#143)', async () => {
91
+ const composerRef = React.createRef<EffectComposerImpl>()
92
+ const effectRef = React.createRef<OutlineEffect>()
93
+
94
+ const render = (color: number) =>
95
+ root.render(
96
+ <EffectComposer ref={composerRef}>
97
+ <Outline ref={effectRef} visibleEdgeColor={color} />
98
+ </EffectComposer>
99
+ )
100
+
101
+ await React.act(async () => render(0xff0000))
102
+ await waitForComposer(composerRef)
103
+ await flush()
104
+
105
+ const first = effectRef.current
106
+ expect(first!.visibleEdgeColor.getHex()).toBe(0xff0000)
107
+
108
+ await React.act(async () => render(0x00ff00))
109
+ await flush()
110
+
111
+ expect(effectRef.current).toBe(first)
112
+ expect(effectRef.current!.visibleEdgeColor.getHex()).toBe(0x00ff00)
113
+ })
114
+
115
+ it('accepts a CSS color string for visibleEdgeColor/hiddenEdgeColor, not just a numeric hex (#187, #182)', async () => {
116
+ const composerRef = React.createRef<EffectComposerImpl>()
117
+ const effectRef = React.createRef<OutlineEffect>()
118
+
119
+ await React.act(async () =>
120
+ root.render(
121
+ <EffectComposer ref={composerRef}>
122
+ <Outline ref={effectRef} visibleEdgeColor="red" hiddenEdgeColor="blue" />
123
+ </EffectComposer>
124
+ )
125
+ )
126
+ await waitForComposer(composerRef)
127
+ await flush()
128
+
129
+ expect(effectRef.current!.visibleEdgeColor.getHex()).toBe(0xff0000)
130
+ expect(effectRef.current!.hiddenEdgeColor.getHex()).toBe(0x0000ff)
131
+ })
132
+
133
+ it('resets edgeStrength to its constructor default when the prop is removed', async () => {
134
+ const composerRef = React.createRef<EffectComposerImpl>()
135
+ const effectRef = React.createRef<OutlineEffect>()
136
+
137
+ await React.act(async () =>
138
+ root.render(
139
+ <EffectComposer ref={composerRef}>
140
+ <Outline ref={effectRef} edgeStrength={100} />
141
+ </EffectComposer>
142
+ )
143
+ )
144
+ await waitForComposer(composerRef)
145
+ await flush()
146
+
147
+ expect(effectRef.current!.edgeStrength).toBe(100)
148
+
149
+ await React.act(async () =>
150
+ root.render(
151
+ <EffectComposer ref={composerRef}>
152
+ <Outline ref={effectRef} />
153
+ </EffectComposer>
154
+ )
155
+ )
156
+ await flush()
157
+
158
+ expect(effectRef.current!.edgeStrength).toBe(1)
159
+ })
160
+
161
+ it('still reconstructs when a construction-only prop (resolutionScale) changes', async () => {
162
+ const composerRef = React.createRef<EffectComposerImpl>()
163
+ const effectRef = React.createRef<OutlineEffect>()
164
+
165
+ const render = (resolutionScale: number) =>
166
+ root.render(
167
+ <EffectComposer ref={composerRef}>
168
+ <Outline ref={effectRef} resolutionScale={resolutionScale} />
169
+ </EffectComposer>
170
+ )
171
+
172
+ await React.act(async () => render(0.5))
173
+ await waitForComposer(composerRef)
174
+ await flush()
175
+ const first = effectRef.current
176
+
177
+ await React.act(async () => render(1))
178
+ await flush()
179
+
180
+ expect(effectRef.current).not.toBe(first)
181
+ })
182
+
183
+ it('does not dispose its render target on unrelated re-renders (multisampling has an unconditional dispose side effect)', async () => {
184
+ const composerRef = React.createRef<EffectComposerImpl>()
185
+ const effectRef = React.createRef<OutlineEffect>()
186
+
187
+ const render = (tick: number) =>
188
+ root.render(
189
+ <EffectComposer ref={composerRef}>
190
+ <Outline ref={effectRef} />
191
+ <group name={`tick-${tick}`} />
192
+ </EffectComposer>
193
+ )
194
+
195
+ await React.act(async () => render(0))
196
+ await waitForComposer(composerRef)
197
+ await flush()
198
+
199
+ // @ts-expect-error - `renderTargetMask` isn't part of the public OutlineEffect typing
200
+ const disposeSpy = vi.spyOn(effectRef.current!.renderTargetMask, 'dispose')
201
+
202
+ for (let t = 1; t <= 5; t++) {
203
+ await React.act(async () => render(t))
204
+ await flush()
205
+ }
206
+
207
+ expect(disposeSpy).not.toHaveBeenCalled()
208
+ disposeSpy.mockRestore()
209
+ })
210
+
89
211
  })
@@ -0,0 +1,140 @@
1
+ import { EffectComposer as EffectComposerImpl, SSAOEffect } from 'postprocessing'
2
+ import * as React from 'react'
3
+ import { Color } from 'three'
4
+ import { describe, expect, it } from 'vitest'
5
+ import { EffectComposer } from '../EffectComposer'
6
+ import { SSAO } from '../effects/SSAO'
7
+ import { flush, root, waitForComposer } from './test-utils'
8
+
9
+ describe('SSAO', () => {
10
+ it('resets color/fade/minRadiusScale to their constructor defaults when removed, not the first-mounted value', async () => {
11
+ const composerRef = React.createRef<EffectComposerImpl>()
12
+ const ref = React.createRef<SSAOEffect>()
13
+
14
+ const render = (withOverrides: boolean) =>
15
+ root.render(
16
+ <EffectComposer ref={composerRef} enableNormalPass>
17
+ <SSAO ref={ref} {...(withOverrides ? { color: new Color('red'), fade: 0.5, minRadiusScale: 0.9 } : {})} />
18
+ </EffectComposer>
19
+ )
20
+
21
+ await React.act(async () => render(true))
22
+ await waitForComposer(composerRef)
23
+ await flush()
24
+
25
+ expect(ref.current!.color!.getHexString()).toBe('ff0000')
26
+ expect(ref.current!.ssaoMaterial.fade).toBeCloseTo(0.5)
27
+ expect(ref.current!.ssaoMaterial.minRadiusScale).toBeCloseTo(0.9)
28
+
29
+ await React.act(async () => render(false))
30
+ await flush()
31
+
32
+ // SSAOEffect's own constructor defaults (null / 0.01 / 0.1), not the
33
+ // values from the first render this instance ever saw.
34
+ expect(ref.current!.color).toBeNull()
35
+ expect(ref.current!.ssaoMaterial.fade).toBeCloseTo(0.01)
36
+ expect(ref.current!.ssaoMaterial.minRadiusScale).toBeCloseTo(0.1)
37
+ })
38
+
39
+ it('applies intensity live, without reconstructing the effect', async () => {
40
+ const composerRef = React.createRef<EffectComposerImpl>()
41
+ const ref = React.createRef<SSAOEffect>()
42
+
43
+ const render = (intensity: number) =>
44
+ root.render(
45
+ <EffectComposer ref={composerRef} enableNormalPass>
46
+ <SSAO ref={ref} intensity={intensity} />
47
+ </EffectComposer>
48
+ )
49
+
50
+ await React.act(async () => render(1))
51
+ await waitForComposer(composerRef)
52
+ await flush()
53
+ const first = ref.current
54
+ expect(first!.intensity).toBe(1)
55
+
56
+ await React.act(async () => render(2))
57
+ await flush()
58
+
59
+ expect(ref.current).toBe(first)
60
+ expect(ref.current!.intensity).toBe(2)
61
+
62
+ await React.act(async () => root.render(null))
63
+ })
64
+
65
+ it('applies bias live via the nested ssaoMaterial, without reconstructing the effect', async () => {
66
+ const composerRef = React.createRef<EffectComposerImpl>()
67
+ const ref = React.createRef<SSAOEffect>()
68
+
69
+ const render = (bias: number) =>
70
+ root.render(
71
+ <EffectComposer ref={composerRef} enableNormalPass>
72
+ <SSAO ref={ref} bias={bias} />
73
+ </EffectComposer>
74
+ )
75
+
76
+ await React.act(async () => render(0.5))
77
+ await waitForComposer(composerRef)
78
+ await flush()
79
+ const first = ref.current
80
+ expect(first!.ssaoMaterial.bias).toBeCloseTo(0.5)
81
+
82
+ await React.act(async () => render(0.8))
83
+ await flush()
84
+
85
+ expect(ref.current).toBe(first)
86
+ expect(ref.current!.ssaoMaterial.bias).toBeCloseTo(0.8)
87
+
88
+ await React.act(async () => root.render(null))
89
+ })
90
+
91
+ it('applies worldProximityThreshold live via the nested ssaoMaterial, without reconstructing the effect', async () => {
92
+ const composerRef = React.createRef<EffectComposerImpl>()
93
+ const ref = React.createRef<SSAOEffect>()
94
+
95
+ const render = (worldProximityThreshold: number) =>
96
+ root.render(
97
+ <EffectComposer ref={composerRef} enableNormalPass>
98
+ <SSAO ref={ref} worldProximityThreshold={worldProximityThreshold} />
99
+ </EffectComposer>
100
+ )
101
+
102
+ await React.act(async () => render(0.5))
103
+ await waitForComposer(composerRef)
104
+ await flush()
105
+ const first = ref.current
106
+ expect(first!.ssaoMaterial.worldProximityThreshold).toBeCloseTo(0.5)
107
+
108
+ await React.act(async () => render(0.8))
109
+ await flush()
110
+
111
+ expect(ref.current).toBe(first)
112
+ expect(ref.current!.ssaoMaterial.worldProximityThreshold).toBeCloseTo(0.8)
113
+
114
+ await React.act(async () => root.render(null))
115
+ })
116
+
117
+ it('still reconstructs when resolutionScale (construction-only) changes', async () => {
118
+ const composerRef = React.createRef<EffectComposerImpl>()
119
+ const ref = React.createRef<SSAOEffect>()
120
+
121
+ const render = (resolutionScale: number) =>
122
+ root.render(
123
+ <EffectComposer ref={composerRef} enableNormalPass>
124
+ <SSAO ref={ref} resolutionScale={resolutionScale} />
125
+ </EffectComposer>
126
+ )
127
+
128
+ await React.act(async () => render(0.5))
129
+ await waitForComposer(composerRef)
130
+ await flush()
131
+ const first = ref.current
132
+
133
+ await React.act(async () => render(1))
134
+ await flush()
135
+
136
+ expect(ref.current).not.toBe(first)
137
+
138
+ await React.act(async () => root.render(null))
139
+ })
140
+ })
@@ -115,4 +115,52 @@ describe('SelectiveBloom', () => {
115
115
  await waitForComposer(composerRef)
116
116
  await expect(flush()).resolves.not.toThrow()
117
117
  })
118
+
119
+ it('applies intensity live, without reconstructing the effect', async () => {
120
+ const composerRef = React.createRef<EffectComposerImpl>()
121
+ const effectRef = React.createRef<SelectiveBloomEffect>()
122
+ const light = new PointLight()
123
+
124
+ const render = (intensity: number) =>
125
+ root.render(
126
+ <EffectComposer ref={composerRef}>
127
+ <SelectiveBloom ref={effectRef} lights={[light]} intensity={intensity} />
128
+ </EffectComposer>
129
+ )
130
+
131
+ await React.act(async () => render(1))
132
+ await waitForComposer(composerRef)
133
+ await flush()
134
+ const first = effectRef.current
135
+ expect(first!.intensity).toBe(1)
136
+
137
+ await React.act(async () => render(3))
138
+ await flush()
139
+
140
+ expect(effectRef.current).toBe(first)
141
+ expect(effectRef.current!.intensity).toBe(3)
142
+ })
143
+
144
+ it('still reconstructs when luminanceThreshold changes (no live setter in postprocessing)', async () => {
145
+ const composerRef = React.createRef<EffectComposerImpl>()
146
+ const effectRef = React.createRef<SelectiveBloomEffect>()
147
+ const light = new PointLight()
148
+
149
+ const render = (luminanceThreshold: number) =>
150
+ root.render(
151
+ <EffectComposer ref={composerRef}>
152
+ <SelectiveBloom ref={effectRef} lights={[light]} luminanceThreshold={luminanceThreshold} />
153
+ </EffectComposer>
154
+ )
155
+
156
+ await React.act(async () => render(0.5))
157
+ await waitForComposer(composerRef)
158
+ await flush()
159
+ const first = effectRef.current
160
+
161
+ await React.act(async () => render(0.8))
162
+ await flush()
163
+
164
+ expect(effectRef.current).not.toBe(first)
165
+ })
118
166
  })
@@ -0,0 +1,95 @@
1
+ import { EffectComposer as EffectComposerImpl, ShockWaveEffect } from 'postprocessing'
2
+ import * as React from 'react'
3
+ import { Vector3 } from 'three'
4
+ import { describe, expect, it } from 'vitest'
5
+ import { EffectComposer } from '../EffectComposer'
6
+ import { ShockWave } from '../effects/ShockWave'
7
+ import { flush, root } from './test-utils'
8
+
9
+ describe('ShockWave', () => {
10
+ it('applies speed and position, which createEffectComponent cannot (ShockWaveEffect takes them as a 3rd ctor arg)', async () => {
11
+ const ref = React.createRef<ShockWaveEffect>()
12
+ const composerRef = React.createRef<EffectComposerImpl>()
13
+ const position = new Vector3(1, 2, 3)
14
+
15
+ await React.act(async () =>
16
+ root.render(
17
+ <EffectComposer ref={composerRef}>
18
+ <ShockWave ref={ref} speed={5} position={position} />
19
+ </EffectComposer>
20
+ )
21
+ )
22
+
23
+ await flush()
24
+
25
+ expect(ref.current!.speed).toBe(5)
26
+ expect(ref.current!.position).toBe(position)
27
+
28
+ await React.act(async () => root.render(null))
29
+ })
30
+
31
+ it('updates speed/position live, without reconstructing the instance', async () => {
32
+ const ref = React.createRef<ShockWaveEffect>()
33
+ const composerRef = React.createRef<EffectComposerImpl>()
34
+
35
+ await React.act(async () =>
36
+ root.render(
37
+ <EffectComposer ref={composerRef}>
38
+ <ShockWave ref={ref} speed={1} />
39
+ </EffectComposer>
40
+ )
41
+ )
42
+
43
+ await flush()
44
+
45
+ const firstInstance = ref.current
46
+
47
+ await React.act(async () =>
48
+ root.render(
49
+ <EffectComposer ref={composerRef}>
50
+ <ShockWave ref={ref} speed={2} waveSize={0.5} />
51
+ </EffectComposer>
52
+ )
53
+ )
54
+
55
+ await flush()
56
+
57
+ expect(ref.current).toBe(firstInstance)
58
+ expect(ref.current!.speed).toBe(2)
59
+ expect(ref.current!.waveSize).toBe(0.5)
60
+
61
+ await React.act(async () => root.render(null))
62
+ })
63
+
64
+ it('resets speed to its constructor default when the prop is removed', async () => {
65
+ const ref = React.createRef<ShockWaveEffect>()
66
+ const composerRef = React.createRef<EffectComposerImpl>()
67
+
68
+ await React.act(async () =>
69
+ root.render(
70
+ <EffectComposer ref={composerRef}>
71
+ <ShockWave ref={ref} speed={5} />
72
+ </EffectComposer>
73
+ )
74
+ )
75
+
76
+ await flush()
77
+
78
+ expect(ref.current!.speed).toBe(5)
79
+ const defaultSpeed = 2
80
+
81
+ await React.act(async () =>
82
+ root.render(
83
+ <EffectComposer ref={composerRef}>
84
+ <ShockWave ref={ref} />
85
+ </EffectComposer>
86
+ )
87
+ )
88
+
89
+ await flush()
90
+
91
+ expect(ref.current!.speed).toBe(defaultSpeed)
92
+
93
+ await React.act(async () => root.render(null))
94
+ })
95
+ })
@@ -0,0 +1,76 @@
1
+ import { EffectComposer as EffectComposerImpl, TiltShiftEffect } from 'postprocessing'
2
+ import * as React from 'react'
3
+ import { describe, expect, it } from 'vitest'
4
+ import { EffectComposer } from '../EffectComposer'
5
+ import { TiltShift } from '../effects/TiltShift'
6
+ import { flush, root } from './test-utils'
7
+
8
+ describe('TiltShift', () => {
9
+ it('applies resolutionScale at construction (previously never reached the effect at all)', async () => {
10
+ const composerRef = React.createRef<EffectComposerImpl>()
11
+ const ref = React.createRef<TiltShiftEffect>()
12
+
13
+ await React.act(async () =>
14
+ root.render(
15
+ <EffectComposer ref={composerRef}>
16
+ <TiltShift ref={ref} resolutionScale={0.25} />
17
+ </EffectComposer>
18
+ )
19
+ )
20
+
21
+ await flush()
22
+
23
+ expect(ref.current!.blurPass.resolution.scale).toBeCloseTo(0.25)
24
+
25
+ await React.act(async () => root.render(null))
26
+ })
27
+
28
+ it('reconstructs when resolutionScale (construction-only) changes', async () => {
29
+ const composerRef = React.createRef<EffectComposerImpl>()
30
+ const ref = React.createRef<TiltShiftEffect>()
31
+
32
+ const render = (resolutionScale: number) =>
33
+ root.render(
34
+ <EffectComposer ref={composerRef}>
35
+ <TiltShift ref={ref} resolutionScale={resolutionScale} />
36
+ </EffectComposer>
37
+ )
38
+
39
+ await React.act(async () => render(0.25))
40
+ await flush()
41
+ const first = ref.current
42
+
43
+ await React.act(async () => render(0.5))
44
+ await flush()
45
+
46
+ expect(ref.current).not.toBe(first)
47
+ expect(ref.current!.blurPass.resolution.scale).toBeCloseTo(0.5)
48
+
49
+ await React.act(async () => root.render(null))
50
+ })
51
+
52
+ it('applies offset live, without reconstructing the effect', async () => {
53
+ const composerRef = React.createRef<EffectComposerImpl>()
54
+ const ref = React.createRef<TiltShiftEffect>()
55
+
56
+ const render = (offset: number) =>
57
+ root.render(
58
+ <EffectComposer ref={composerRef}>
59
+ <TiltShift ref={ref} offset={offset} />
60
+ </EffectComposer>
61
+ )
62
+
63
+ await React.act(async () => render(0.1))
64
+ await flush()
65
+ const first = ref.current
66
+ expect(first!.offset).toBeCloseTo(0.1)
67
+
68
+ await React.act(async () => render(0.2))
69
+ await flush()
70
+
71
+ expect(ref.current).toBe(first)
72
+ expect(ref.current!.offset).toBeCloseTo(0.2)
73
+
74
+ await React.act(async () => root.render(null))
75
+ })
76
+ })