88
99import {
1010 ApplicationRef ,
11+ computed ,
1112 createEnvironmentInjector ,
13+ effect ,
1214 EnvironmentInjector ,
1315 Injector ,
1416 resource ,
@@ -140,16 +142,18 @@ describe('resource', () => {
140142 } ) ;
141143
142144 TestBed . tick ( ) ;
143- await backend . reject ( requestParam , 'Something went wrong....' ) ;
145+ await backend . reject ( requestParam , new Error ( 'Something went wrong....' ) ) ;
144146
145147 expect ( echoResource . status ( ) ) . toBe ( 'error' ) ;
146148 expect ( echoResource . isLoading ( ) ) . toBeFalse ( ) ;
147149 expect ( echoResource . hasValue ( ) ) . toBeFalse ( ) ;
148- expect ( echoResource . value ( ) ) . toEqual ( undefined ) ;
149- expect ( echoResource . error ( ) ) . toEqual (
150- jasmine . objectContaining ( { cause : 'Something went wrong....' } ) ,
151- ) ,
152- expect ( echoResource . error ( ) ! . message ) . toContain ( 'Resource' ) ;
150+
151+ const err = extractError ( ( ) => echoResource . value ( ) ) ! ;
152+ expect ( err ) . not . toBeUndefined ( ) ;
153+ expect ( err instanceof Error ) . toBeTrue ( ) ;
154+ expect ( err ! . message ) . toContain ( 'Resource' ) ;
155+ expect ( err . cause ) . toEqual ( new Error ( 'Something went wrong....' ) ) ;
156+ expect ( echoResource . error ( ) ) . toEqual ( new Error ( 'Something went wrong....' ) ) ;
153157 } ) ;
154158
155159 it ( 'should expose errors on reload' , async ( ) => {
@@ -183,8 +187,109 @@ describe('resource', () => {
183187 expect ( echoResource . status ( ) ) . toBe ( 'error' ) ;
184188 expect ( echoResource . isLoading ( ) ) . toBeFalse ( ) ;
185189 expect ( echoResource . hasValue ( ) ) . toBeFalse ( ) ;
186- expect ( echoResource . value ( ) ) . toEqual ( undefined ) ;
190+ const err = extractError ( ( ) => echoResource . value ( ) ) ! ;
191+ expect ( err ) . not . toBeUndefined ( ) ;
192+ expect ( err . message ) . toContain ( 'Resource' ) ;
193+ expect ( err . message ) . toContain ( 'KO' ) ;
194+ expect ( err . cause ) . toEqual ( new Error ( 'KO' ) ) ;
195+ expect ( echoResource . error ( ) ) . toEqual ( Error ( 'KO' ) ) ;
196+
197+ counter . update ( ( value ) => value + 1 ) ;
198+ TestBed . tick ( ) ;
199+ await backend . flush ( ) ;
200+
201+ expect ( echoResource . status ( ) ) . toBe ( 'resolved' ) ;
202+ expect ( echoResource . isLoading ( ) ) . toBeFalse ( ) ;
203+ expect ( echoResource . hasValue ( ) ) . toBeTrue ( ) ;
204+ expect ( echoResource . value ( ) ) . toEqual ( 'ok' ) ;
205+ expect ( echoResource . error ( ) ) . toBe ( undefined ) ;
206+ } ) ;
207+
208+ it ( 'should not trigger consumers on every status change via hasValue()' , async ( ) => {
209+ const params = signal < number | undefined > ( undefined ) ;
210+ const testResource = resource ( {
211+ params,
212+ // Hanging promise, never resolves
213+ loader : ( ) => new Promise ( ( ) => { } ) ,
214+ injector : TestBed . inject ( Injector ) ,
215+ } ) ;
216+
217+ let effectRuns = 0 ;
218+ effect (
219+ ( ) => {
220+ testResource . hasValue ( ) ;
221+ effectRuns ++ ;
222+ } ,
223+ { injector : TestBed . inject ( Injector ) } ,
224+ ) ;
225+
226+ TestBed . tick ( ) ;
227+ // Params are undefined, status should be idle.
228+ expect ( testResource . status ( ) ) . toBe ( 'idle' ) ;
229+ // Effect should run the first time.
230+ expect ( effectRuns ) . toBe ( 1 ) ;
231+
232+ // Set params, causing the stauts to become 'loading'.
233+ params . set ( 0 ) ;
234+ expect ( testResource . status ( ) ) . toBe ( 'loading' ) ;
235+ TestBed . tick ( ) ;
236+ // The effect should not rerun.
237+ expect ( effectRuns ) . toBe ( 1 ) ;
238+ } ) ;
239+
240+ it ( 'should update computed signals' , async ( ) => {
241+ const backend = new MockEchoBackend ( ) ;
242+ const counter = signal ( 0 ) ;
243+ const echoResource = resource ( {
244+ params : ( ) => ( { counter : counter ( ) } ) ,
245+ loader : ( params ) => {
246+ if ( params . params . counter % 2 === 0 ) {
247+ return Promise . resolve ( params . params . counter ) ;
248+ } else {
249+ throw new Error ( 'KO' ) ;
250+ }
251+ } ,
252+ injector : TestBed . inject ( Injector ) ,
253+ } ) ;
254+ const computedValue = computed ( ( ) => {
255+ if ( ! echoResource . hasValue ( ) ) {
256+ return - 1 ;
257+ }
258+ return echoResource . value ( ) ;
259+ } ) ;
260+
261+ TestBed . tick ( ) ;
262+ await backend . flush ( ) ;
263+
264+ expect ( echoResource . status ( ) ) . toBe ( 'resolved' ) ;
265+ expect ( echoResource . hasValue ( ) ) . toBeTrue ( ) ;
266+ expect ( echoResource . value ( ) ) . toEqual ( 0 ) ;
267+ expect ( computedValue ( ) ) . toEqual ( 0 ) ;
268+ expect ( echoResource . error ( ) ) . toBe ( undefined ) ;
269+
270+ counter . update ( ( value ) => value + 1 ) ;
271+ TestBed . tick ( ) ;
272+ await backend . flush ( ) ;
273+
274+ expect ( echoResource . status ( ) ) . toBe ( 'error' ) ;
275+ expect ( echoResource . hasValue ( ) ) . toBeFalse ( ) ;
276+ const err = extractError ( ( ) => echoResource . value ( ) ) ! ;
277+ expect ( err ) . not . toBeUndefined ( ) ;
278+ expect ( err . message ) . toContain ( 'Resource' ) ;
279+ expect ( err . message ) . toContain ( 'KO' ) ;
280+ expect ( err . cause ) . toEqual ( new Error ( 'KO' ) ) ;
281+ expect ( computedValue ( ) ) . toEqual ( - 1 ) ;
187282 expect ( echoResource . error ( ) ) . toEqual ( Error ( 'KO' ) ) ;
283+
284+ counter . update ( ( value ) => value + 1 ) ;
285+ TestBed . tick ( ) ;
286+ await backend . flush ( ) ;
287+
288+ expect ( echoResource . status ( ) ) . toBe ( 'resolved' ) ;
289+ expect ( echoResource . hasValue ( ) ) . toBeTrue ( ) ;
290+ expect ( echoResource . value ( ) ) . toEqual ( 2 ) ;
291+ expect ( computedValue ( ) ) . toEqual ( 2 ) ;
292+ expect ( echoResource . error ( ) ) . toBe ( undefined ) ;
188293 } ) ;
189294
190295 it ( 'should respond to a request that changes while loading' , async ( ) => {
@@ -231,7 +336,7 @@ describe('resource', () => {
231336 expect ( res . value ( ) ) . toBe ( 1 ) ;
232337 } ) ;
233338
234- it ( 'should return a default value if provided' , async ( ) => {
339+ it ( 'should throw an error when getting a value even when provided with a default value ' , async ( ) => {
235340 const DEFAULT : string [ ] = [ ] ;
236341 const request = signal ( 0 ) ;
237342 const res = resource ( {
@@ -256,7 +361,11 @@ describe('resource', () => {
256361 request . set ( 2 ) ;
257362 await TestBed . inject ( ApplicationRef ) . whenStable ( ) ;
258363 expect ( res . error ( ) ) . not . toBeUndefined ( ) ;
259- expect ( res . value ( ) ) . toBe ( DEFAULT ) ;
364+ const err = extractError ( ( ) => res . value ( ) ) ! ;
365+ expect ( err ) . not . toBeUndefined ( ) ;
366+ expect ( err . message ) . toContain ( 'Resource' ) ;
367+ expect ( err . message ) . toContain ( 'err' ) ;
368+ expect ( err . cause ) . toEqual ( new Error ( 'err' ) ) ;
260369 } ) ;
261370
262371 it ( 'should _not_ load if the request resolves to undefined' , ( ) => {
@@ -731,3 +840,12 @@ describe('resource', () => {
731840function flushMicrotasks ( ) : Promise < void > {
732841 return new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
733842}
843+
844+ function extractError ( fn : ( ) => unknown ) : Error | undefined {
845+ try {
846+ fn ( ) ;
847+ return undefined ;
848+ } catch ( err : unknown ) {
849+ return err as Error ;
850+ }
851+ }
0 commit comments