From 4f373699e05fd23c574a7dfe0b627b7652d3bc0c Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 26 Apr 2026 14:21:24 +0900 Subject: [PATCH 01/10] test(angular-query-experimental/injectQuery): add test for 'select' option transforming 'queryFn' data (#10586) --- .../src/__tests__/inject-query.test.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/angular-query-experimental/src/__tests__/inject-query.test.ts b/packages/angular-query-experimental/src/__tests__/inject-query.test.ts index 82eb8f0f34..1254564a8b 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-query.test.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-query.test.ts @@ -356,6 +356,30 @@ describe('injectQuery', () => { expect(rendered.getByText('failureReason: Some error')).toBeInTheDocument() }) + it('should be able to select a part of the data with select', async () => { + const key = queryKey() + + @Component({ + template: `
data: {{ query.data() ?? 'none' }}
`, + }) + class Page { + readonly query = injectQuery<{ name: string }, Error, string>(() => ({ + queryKey: key, + queryFn: () => sleep(10).then(() => ({ name: 'test' })), + select: (data) => data.name, + })) + } + + const rendered = await render(Page) + + expect(rendered.getByText('data: none')).toBeInTheDocument() + + await vi.advanceTimersByTimeAsync(11) + rendered.fixture.detectChanges() + + expect(rendered.getByText('data: test')).toBeInTheDocument() + }) + it('should update query on options contained signal change', async () => { const key1 = queryKey() const key2 = queryKey() From 76161b7a6b403dad448b75bc15f06b86effb2283 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 26 Apr 2026 14:42:26 +0900 Subject: [PATCH 02/10] test(angular-query-experimental/injectQuery): add test for 'placeholderData' lifecycle (#10587) --- .../src/__tests__/inject-query.test.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/angular-query-experimental/src/__tests__/inject-query.test.ts b/packages/angular-query-experimental/src/__tests__/inject-query.test.ts index 1254564a8b..4ff82e2250 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-query.test.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-query.test.ts @@ -380,6 +380,38 @@ describe('injectQuery', () => { expect(rendered.getByText('data: test')).toBeInTheDocument() }) + it('should show placeholderData until queryFn resolves and then expose real data', async () => { + const key = queryKey() + + @Component({ + template: ` +
data: {{ query.data() }}
+
isPlaceholderData: {{ query.isPlaceholderData() }}
+
isSuccess: {{ query.isSuccess() }}
+ `, + }) + class Page { + readonly query = injectQuery(() => ({ + queryKey: key, + queryFn: () => sleep(10).then(() => 'real-data'), + placeholderData: 'placeholder', + })) + } + + const rendered = await render(Page) + + expect(rendered.getByText('data: placeholder')).toBeInTheDocument() + expect(rendered.getByText('isPlaceholderData: true')).toBeInTheDocument() + expect(rendered.getByText('isSuccess: true')).toBeInTheDocument() + + await vi.advanceTimersByTimeAsync(11) + rendered.fixture.detectChanges() + + expect(rendered.getByText('data: real-data')).toBeInTheDocument() + expect(rendered.getByText('isPlaceholderData: false')).toBeInTheDocument() + expect(rendered.getByText('isSuccess: true')).toBeInTheDocument() + }) + it('should update query on options contained signal change', async () => { const key1 = queryKey() const key2 = queryKey() From d78883bff0a0b950fa1385588def01db99397aaa Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 26 Apr 2026 14:53:07 +0900 Subject: [PATCH 03/10] test(angular-query-experimental/injectQuery): remove duplicate error test already covered by 'should reject and update signal' (#10588) --- .../src/__tests__/inject-query.test.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/angular-query-experimental/src/__tests__/inject-query.test.ts b/packages/angular-query-experimental/src/__tests__/inject-query.test.ts index 4ff82e2250..a8bff8527b 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-query.test.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-query.test.ts @@ -604,24 +604,6 @@ describe('injectQuery', () => { }) }) - it('should set state to error when queryFn returns reject promise', async () => { - const key = queryKey() - const query = TestBed.runInInjectionContext(() => { - return injectQuery(() => ({ - retry: false, - queryKey: key, - queryFn: () => - sleep(10).then(() => Promise.reject(new Error('Some error'))), - })) - }) - - expect(query.status()).toBe('pending') - - await vi.advanceTimersByTimeAsync(11) - - expect(query.status()).toBe('error') - }) - it('should render with required signal inputs', async () => { @Component({ selector: 'app-fake', From e69e5d2a07cc8ac661b1a2d926b8409b155a2e22 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 26 Apr 2026 15:03:29 +0900 Subject: [PATCH 04/10] test(angular-query-experimental/injectMutation): switch 'error' test to '@Component' + 'render' pattern (#10589) --- .../src/__tests__/inject-mutation.test.ts | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts b/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts index b9d3eea6e1..c1023a5391 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts @@ -72,25 +72,36 @@ describe('injectMutation', () => { }) it('should return error when request fails', async () => { - const mutation = TestBed.runInInjectionContext(() => { - return injectMutation(() => ({ + @Component({ + template: ` +
isIdle: {{ mutation.isIdle() }}
+
isPending: {{ mutation.isPending() }}
+
isError: {{ mutation.isError() }}
+
isSuccess: {{ mutation.isSuccess() }}
+
data: {{ mutation.data() ?? 'none' }}
+
error: {{ mutation.error()?.message ?? 'none' }}
+ `, + }) + class Page { + readonly mutation = injectMutation(() => ({ mutationFn: () => sleep(10).then(() => Promise.reject(new Error('Some error'))), })) - }) + } - mutation.mutate() + const rendered = await render(Page) + + rendered.fixture.componentInstance.mutation.mutate() await vi.advanceTimersByTimeAsync(11) + rendered.fixture.detectChanges() - expectSignals(mutation, { - isIdle: false, - isPending: false, - isError: true, - isSuccess: false, - data: undefined, - error: Error('Some error'), - }) + expect(rendered.getByText('isIdle: false')).toBeInTheDocument() + expect(rendered.getByText('isPending: false')).toBeInTheDocument() + expect(rendered.getByText('isError: true')).toBeInTheDocument() + expect(rendered.getByText('isSuccess: false')).toBeInTheDocument() + expect(rendered.getByText('data: none')).toBeInTheDocument() + expect(rendered.getByText('error: Some error')).toBeInTheDocument() }) it('should return data when request succeeds', async () => { From 72a15ae4d9f6fe262cb1bd72107f9862291c1e9d Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 26 Apr 2026 15:31:53 +0900 Subject: [PATCH 05/10] test(angular-query-experimental/injectMutation): switch 'pending' test to '@Component' + 'render' pattern (#10590) --- .../src/__tests__/inject-mutation.test.ts | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts b/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts index c1023a5391..fd32ee09b8 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts @@ -50,25 +50,34 @@ describe('injectMutation', () => { it('should change state after invoking mutate', async () => { const result = 'Mock data' - const mutation = TestBed.runInInjectionContext(() => { - return injectMutation(() => ({ + @Component({ + template: ` +
isIdle: {{ mutation.isIdle() }}
+
isPending: {{ mutation.isPending() }}
+
isError: {{ mutation.isError() }}
+
isSuccess: {{ mutation.isSuccess() }}
+
data: {{ mutation.data() ?? 'none' }}
+
error: {{ mutation.error()?.message ?? 'none' }}
+ `, + }) + class Page { + readonly mutation = injectMutation(() => ({ mutationFn: (params: string) => sleep(10).then(() => params), })) - }) + } - TestBed.tick() + const rendered = await render(Page) - mutation.mutate(result) + rendered.fixture.componentInstance.mutation.mutate(result) await vi.advanceTimersByTimeAsync(0) + rendered.fixture.detectChanges() - expectSignals(mutation, { - isIdle: false, - isPending: true, - isError: false, - isSuccess: false, - data: undefined, - error: null, - }) + expect(rendered.getByText('isIdle: false')).toBeInTheDocument() + expect(rendered.getByText('isPending: true')).toBeInTheDocument() + expect(rendered.getByText('isError: false')).toBeInTheDocument() + expect(rendered.getByText('isSuccess: false')).toBeInTheDocument() + expect(rendered.getByText('data: none')).toBeInTheDocument() + expect(rendered.getByText('error: none')).toBeInTheDocument() }) it('should return error when request fails', async () => { From 51ef23f46b23f9f7fd858386e42bfbb3adb5e49d Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 26 Apr 2026 17:28:05 +0900 Subject: [PATCH 06/10] test(angular-query-experimental/injectMutation): add test for 'mutateAsync' resolving with 'mutationFn' return value (#10591) --- .../src/__tests__/inject-mutation.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts b/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts index fd32ee09b8..f0c53602b1 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts @@ -518,6 +518,21 @@ describe('injectMutation', () => { await expect(() => mutateAsync()).rejects.toThrow(err) }) + it('should resolve mutateAsync with the value returned from mutationFn', async () => { + const key = queryKey() + const { mutateAsync } = TestBed.runInInjectionContext(() => { + return injectMutation(() => ({ + mutationKey: key, + mutationFn: (params: string) => sleep(10).then(() => params), + })) + }) + + const promise = mutateAsync('Mock data') + await vi.advanceTimersByTimeAsync(11) + + await expect(promise).resolves.toBe('Mock data') + }) + describe('injection context', () => { it('should throw NG0203 with descriptive error outside injection context', () => { const key = queryKey() From 7a5b53c4ea6e912a53ddc5cb0059155f5ff3f8be Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 26 Apr 2026 17:46:11 +0900 Subject: [PATCH 07/10] test(angular-query-experimental/injectIsFetching): add filter test by 'queryKey' (#10594) --- .../src/__tests__/inject-is-fetching.test.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/angular-query-experimental/src/__tests__/inject-is-fetching.test.ts b/packages/angular-query-experimental/src/__tests__/inject-is-fetching.test.ts index ad29c381bb..db1f7aeea1 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-is-fetching.test.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-is-fetching.test.ts @@ -60,6 +60,36 @@ describe('injectIsFetching', () => { expect(rendered.getByText('fetching: 0')).toBeInTheDocument() }) + it('should be able to filter by queryKey', async () => { + const key1 = queryKey() + const key2 = queryKey() + + @Component({ + template: `
fetching: {{ isFetching() }}
`, + }) + class Page { + readonly query1 = injectQuery(() => ({ + queryKey: key1, + queryFn: () => sleep(10).then(() => 'test1'), + })) + readonly query2 = injectQuery(() => ({ + queryKey: key2, + queryFn: () => sleep(100).then(() => 'test2'), + })) + readonly isFetching = injectIsFetching({ queryKey: key1 }) + } + + const rendered = await render(Page) + + await vi.advanceTimersByTimeAsync(0) + rendered.fixture.detectChanges() + expect(rendered.getByText('fetching: 1')).toBeInTheDocument() + + await vi.advanceTimersByTimeAsync(11) + rendered.fixture.detectChanges() + expect(rendered.getByText('fetching: 0')).toBeInTheDocument() + }) + describe('injection context', () => { it('should throw NG0203 with descriptive error outside injection context', () => { expect(() => { From c9755be3edc28bce37d10e2a8caaac12428e7646 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 26 Apr 2026 17:54:35 +0900 Subject: [PATCH 08/10] test(angular-query-experimental/injectIsMutating): add filter test by 'mutationKey' (#10595) --- .../src/__tests__/inject-is-mutating.test.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/angular-query-experimental/src/__tests__/inject-is-mutating.test.ts b/packages/angular-query-experimental/src/__tests__/inject-is-mutating.test.ts index 91d3e89afb..66fe678d03 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-is-mutating.test.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-is-mutating.test.ts @@ -62,6 +62,39 @@ describe('injectIsMutating', () => { expect(rendered.getByText('mutating: 0')).toBeInTheDocument() }) + it('should be able to filter by mutationKey', async () => { + const key1 = queryKey() + const key2 = queryKey() + + @Component({ + template: `
mutating: {{ isMutating() }}
`, + }) + class Page { + readonly mutation1 = injectMutation(() => ({ + mutationKey: key1, + mutationFn: () => sleep(10).then(() => 'data1'), + })) + readonly mutation2 = injectMutation(() => ({ + mutationKey: key2, + mutationFn: () => sleep(100).then(() => 'data2'), + })) + readonly isMutating = injectIsMutating({ mutationKey: key1 }) + } + + const rendered = await render(Page) + + rendered.fixture.componentInstance.mutation1.mutate() + rendered.fixture.componentInstance.mutation2.mutate() + + await vi.advanceTimersByTimeAsync(0) + rendered.fixture.detectChanges() + expect(rendered.getByText('mutating: 1')).toBeInTheDocument() + + await vi.advanceTimersByTimeAsync(11) + rendered.fixture.detectChanges() + expect(rendered.getByText('mutating: 0')).toBeInTheDocument() + }) + describe('injection context', () => { it('should throw NG0203 with descriptive error outside injection context', () => { expect(() => { From 3547f86e13419771cf7aee7d76a64c84fe10c53c Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 26 Apr 2026 18:09:51 +0900 Subject: [PATCH 09/10] test(angular-query-experimental/injectIsRestoring): add test for reactively reflecting changes to the provided signal (#10596) --- .../src/__tests__/inject-is-restoring.test.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/angular-query-experimental/src/__tests__/inject-is-restoring.test.ts b/packages/angular-query-experimental/src/__tests__/inject-is-restoring.test.ts index 0b74019b3a..73659d74a5 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-is-restoring.test.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-is-restoring.test.ts @@ -43,6 +43,26 @@ describe('injectIsRestoring', () => { expect(isRestoring()).toBe(true) }) + it('should reactively reflect changes to the provided signal', () => { + const restoringSignal = signal(true) + + TestBed.configureTestingModule({ + providers: [provideIsRestoring(restoringSignal.asReadonly())], + }) + + const isRestoring = TestBed.runInInjectionContext(() => { + return injectIsRestoring() + }) + + expect(isRestoring()).toBe(true) + + restoringSignal.set(false) + expect(isRestoring()).toBe(false) + + restoringSignal.set(true) + expect(isRestoring()).toBe(true) + }) + it('should be usable outside injection context when passing an injector', () => { const isRestoring = injectIsRestoring({ injector: TestBed.inject(Injector), From dd901aef9803226a791f3f858114171ddeae7d70 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Sun, 26 Apr 2026 18:18:41 +0900 Subject: [PATCH 10/10] test(angular-query-experimental/injectQueries): add test for error state when one of the queries rejects (#10597) --- .../src/__tests__/inject-queries.test.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/angular-query-experimental/src/__tests__/inject-queries.test.ts b/packages/angular-query-experimental/src/__tests__/inject-queries.test.ts index 2d3560b99b..523bda69f0 100644 --- a/packages/angular-query-experimental/src/__tests__/inject-queries.test.ts +++ b/packages/angular-query-experimental/src/__tests__/inject-queries.test.ts @@ -136,6 +136,50 @@ describe('injectQueries', () => { expect(results.length).toBeGreaterThanOrEqual(2) }) + it('should reflect error state when one of the queries rejects', async () => { + const key1 = queryKey() + const key2 = queryKey() + + @Component({ + template: ` +
+ status1: {{ result()[0].status() }}, error1: + {{ result()[0].error()?.message ?? 'none' }} +
+
+ status2: {{ result()[1].status() }}, data2: + {{ result()[1].data() ?? 'none' }} +
+ `, + }) + class Page { + readonly result = injectQueries(() => ({ + queries: [ + { + queryKey: key1, + queryFn: () => + sleep(10).then(() => Promise.reject(new Error('Some error'))), + retry: false, + }, + { + queryKey: key2, + queryFn: () => sleep(10).then(() => 2), + }, + ], + })) + } + + const rendered = await render(Page) + + await vi.advanceTimersByTimeAsync(11) + rendered.fixture.detectChanges() + + expect( + rendered.getByText('status1: error, error1: Some error'), + ).toBeInTheDocument() + expect(rendered.getByText('status2: success, data2: 2')).toBeInTheDocument() + }) + describe('isRestoring', () => { it('should not fetch for the duration of the restoring period when isRestoring is true', async () => { const key1 = queryKey()