-
Notifications
You must be signed in to change notification settings - Fork 0
feat(docs): add the angular testing documentation #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,171 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: testing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: Testing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| As there is currently no simple way to await a signal to reach a specific value we will use polling to wait in our test (instead of transforming our signals in observable and use RxJS features to filter the values). If you want to do like us for the polling you can use the angular testing library. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Install this by running: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```sh | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ng add @testing-library/angular | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Otherwise we recommend to use the toObservable feature from Angular. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## What to test | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Because the recommendation is to use services that provide the Query options through function this is what we are going to do. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## A simple test | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //tasks.service.ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { HttpClient } from '@angular/common/http' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Injectable, inject } from '@angular/core' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QueryClient, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mutationOptions, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryOptions, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '@tanstack/angular-query-experimental' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { lastValueFrom } from 'rxjs' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Injectable({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| providedIn: 'root', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class TasksService { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #queryClient = inject(QueryClient) // Manages query state and caching | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #http = inject(HttpClient) // Handles HTTP requests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Fetches all tasks from the API. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Returns an observable containing an array of task strings. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| allTasks = () => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryOptions({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryKey: ['tasks'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryFn: () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return lastValueFrom(this.#http.get<Array<string>>('/api/tasks')); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // tasks.service.spec.ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { TestBed } from "@angular/core/testing"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { provideHttpClient, withFetch, withInterceptors } from "@angular/common/http"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { QueryClient, injectQuery, provideTanStackQuery } from "@tanstack/angular-query-experimental"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Injector, inject, runInInjectionContext } from "@angular/core"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { waitFor } from '@testing-library/angular'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { mockInterceptor } from "../interceptor/mock-api.interceptor"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { TasksService } from "./tasks.service"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { CreateQueryResult} from "@tanstack/angular-query-experimental"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe('Test suite: TaskService', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let service!: TasksService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let injector!: Injector; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // https://angular.dev/guide/http/testing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| beforeEach(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TestBed.configureTestingModule({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| providers: [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| provideHttpClient(withFetch(), withInterceptors([mockInterceptor])), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TasksService, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // It is recommended to cancel the retries in the tests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| provideTanStackQuery(new QueryClient({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defaultOptions: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queries: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| retry: false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| service = TestBed.inject(TasksService); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| injector = TestBed.inject(Injector); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should get all the Tasks', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let allTasks: any; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runInInjectionContext(injector, () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| allTasks = injectQuery(() => service.allTasks()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(allTasks.status()).toEqual('pending'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(allTasks.isFetching()).toEqual(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(allTasks.data()).toEqual(undefined); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // We await the first result from the query | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await waitFor(() => expect(allTasks.isFetching()).toBe(false), {timeout: 10000}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(allTasks.status()).toEqual('success'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(allTasks.data()).toEqual([]); // Considering that the inteceptor is returning [] at the first query request. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a typo in the comment. "inteceptor" should be "interceptor".
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // To have a more complete example have a look at "unit testing / jest" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+90
to
+103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make the test async to allow You’re using - it('should get all the Tasks', () => {
+ it('should get all the tasks', async () => {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // mock-api.interceptor.ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * MockApiInterceptor is used to simulate API responses for `/api/tasks` endpoints. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * It handles the following operations: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * - GET: Fetches all tasks from sessionStorage. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * - POST: Adds a new task to sessionStorage. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Simulated responses include a delay to mimic network latency. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { HttpResponse } from '@angular/common/http' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { delay, of, throwError } from 'rxjs' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HttpEvent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HttpHandlerFn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HttpInterceptorFn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HttpRequest, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '@angular/common/http' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { Observable } from 'rxjs' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const mockInterceptor: HttpInterceptorFn = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| req: HttpRequest<unknown>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| next: HttpHandlerFn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Observable<HttpEvent<any>> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const respondWith = (status: number, body: any) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| of(new HttpResponse({ status, body })).pipe(delay(1000)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (req.url === '/api/tasks') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch (req.method) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 'GET': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return respondWith( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 200, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JSON.parse( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sessionStorage.getItem('unit-testing-tasks') || '[]', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 'POST': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const tasks = JSON.parse( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sessionStorage.getItem('unit-testing-tasks') || '[]', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tasks.push(req.body) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sessionStorage.setItem( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'unit-testing-tasks', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JSON.stringify(tasks), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return respondWith(201, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: 'success', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| task: req.body, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (req.url === '/api/tasks-wrong-url') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return throwError(() => new Error('error')).pipe(delay(1000)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return next(req) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Turn off retries | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| The library defaults to three retries with exponential backoff, which means that your tests are likely to timeout if you want to test an erroneous query. The easiest way to turn retries off is via the provideTanStackQuery during the TestBed setup as shown in the above example. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Testing Network Calls | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Instead of targetting a server for the data you should mock the requests. There are multiple way of handling the mocking, we recommend to use the Interceptor from Angular, see [here](https://angular.dev/guide/http/interceptors) for more details. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| You can see the the Interceptor setup in the "Unit testing / Jest" examples. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -38,7 +38,7 @@ export class TasksService { | |||||||||||
| lastValueFrom(this.#http.post('/api/tasks', task)), | ||||||||||||
| mutationKey: ['tasks'], | ||||||||||||
| onSuccess: () => { | ||||||||||||
| this.#queryClient.invalidateQueries({ queryKey: ['tasks'] }) | ||||||||||||
| return this.#queryClient.invalidateQueries({ queryKey: ['tasks'] }) | ||||||||||||
| }, | ||||||||||||
| }) | ||||||||||||
| } | ||||||||||||
|
|
@@ -52,7 +52,7 @@ export class TasksService { | |||||||||||
| mutationFn: () => lastValueFrom(this.#http.delete('/api/tasks')), | ||||||||||||
| mutationKey: ['clearTasks'], | ||||||||||||
| onSuccess: () => { | ||||||||||||
| this.#queryClient.invalidateQueries({ queryKey: ['tasks'] }) | ||||||||||||
| return this.#queryClient.invalidateQueries({ queryKey: ['tasks'] }) | ||||||||||||
| }, | ||||||||||||
|
Comment on lines
+55
to
56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Same here: await invalidation for clear-all. Aligns semantics with addTask and ensures tests observe a refetched cache. - onSuccess: () => {
- return this.#queryClient.invalidateQueries({ queryKey: ['tasks'] })
- },
+ onSuccess: async () => {
+ await this.#queryClient.invalidateQueries({ queryKey: ['tasks'] })
+ },📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
| }) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,7 +6,7 @@ | |||||||||
| * Simulated responses include a delay to mimic network latency. | ||||||||||
| */ | ||||||||||
| import { HttpResponse } from '@angular/common/http' | ||||||||||
| import { delay, of } from 'rxjs' | ||||||||||
| import { delay, of, throwError } from 'rxjs' | ||||||||||
|
Comment on lines
8
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainAdd missing HttpErrorResponse import (and keep style consistent). To support the change above, import HttpErrorResponse alongside HttpResponse. Also consider removing the lone semicolon at Line 49 to match the file’s no-semi style. -import { HttpResponse } from '@angular/common/http'
+import { HttpResponse, HttpErrorResponse } from '@angular/common/http'Search for code/tests expecting HttpErrorResponse shape (status, message) to confirm this aligns with consumers: 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Scanning for patterns that assume HttpErrorResponse shape..."
rg -n --glob 'examples/angular/**' \
'HttpErrorResponse|error\.status|instanceof\s+HttpErrorResponse|catchError\(' -A 3Length of output: 270 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate and display the mock API interceptor file around the relevant import and line 49
file=$(fd --type f mock-api.interceptor.ts | head -n1)
echo "File: $file"
echo "----- Lines 1-60 -----"
sed -n '1,60p' "$file"
echo "----- Lines 40-80 -----"
sed -n '40,80p' "$file"Length of output: 2219 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Display the import statements and around line 49 for the optimistic-updates example
file="examples/angular/optimistic-updates/src/app/interceptor/mock-api.interceptor.ts"
echo "File: $file"
echo "----- Lines 1-20 (imports) -----"
sed -n '1,20p' "$file"
echo "----- Lines 40-60 -----"
sed -n '40,60p' "$file"Length of output: 1543 Remove the unnecessary import suggestion and fix the trailing semicolon The file doesn’t reference HttpErrorResponse anywhere, so you can skip adding that import. However, there’s a lone semicolon on the • File: examples/angular/optimistic-updates/src/app/interceptor/mock-api.interceptor.ts -return throwError(() => new Error('error')).pipe(delay(1000));
+return throwError(() => new Error('error')).pipe(delay(1000))📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| import type { | ||||||||||
| HttpEvent, | ||||||||||
| HttpHandlerFn, | ||||||||||
|
|
@@ -46,9 +46,7 @@ export const mockInterceptor: HttpInterceptorFn = ( | |||||||||
| } | ||||||||||
| } | ||||||||||
| if (req.url === '/api/tasks-wrong-url') { | ||||||||||
| return respondWith(500, { | ||||||||||
| status: 'error', | ||||||||||
| }) | ||||||||||
| return throwError(() => new Error('error')).pipe(delay(1000)); | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Emit HttpErrorResponse instead of a generic Error for realistic HttpClient errors and stronger typing. Returning a plain Error loses status/URL/context that many handlers and tests depend on (e.g., checking - return throwError(() => new Error('error')).pipe(delay(1000));
+ return throwError(() => new HttpErrorResponse({
+ status: 500,
+ statusText: 'Server Error',
+ url: req.url,
+ error: { status: 'error' },
+ })).pipe(delay(1000))Note: This change requires importing HttpErrorResponse (see next comment).
🤖 Prompt for AI Agents |
||||||||||
| } | ||||||||||
|
|
||||||||||
| return next(req) | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "name": "Node.js", | ||
| "image": "mcr.microsoft.com/devcontainers/javascript-node:22" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| // @ts-check | ||
|
|
||
| /** @type {import('eslint').Linter.Config} */ | ||
| const config = {} | ||
|
|
||
| module.exports = config |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,7 @@ | ||||||||||||||
| # TanStack Query Angular unit-testing example | ||||||||||||||
|
|
||||||||||||||
| To run this example: | ||||||||||||||
|
|
||||||||||||||
| - `npm install` or `yarn` or `pnpm i` or `bun i` | ||||||||||||||
| - `npm run start` or `yarn start` or `pnpm start` or `bun start` | ||||||||||||||
| - `npm run test` to run the tests | ||||||||||||||
|
Comment on lines
+5
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainInclude equivalent test commands for all package managers. Right now only the npm test command is documented. Add yarn/pnpm/bun equivalents for consistency with the install/start bullets. - - `npm run test` to run the tests
+ - `npm run test` or `yarn test` or `pnpm test` or `bun test` to run the testsQuick check to ensure 🏁 Script executed: #!/bin/bash
set -euo pipefail
PKG="examples/angular/unit-testing/package.json"
if [[ ! -f "$PKG" ]]; then
echo "Not found: $PKG"
exit 1
fi
echo "Scripts in $PKG:"
jq -r '.scripts' "$PKG"
echo
echo "Expecting both \"start\" and \"test\" script entries..."
jq -er '.scripts.start' "$PKG" >/dev/null && echo "start: OK" || { echo "start: MISSING"; exit 1; }
jq -er '.scripts.test' "$PKG" >/dev/null && echo "test: OK" || { echo "test: MISSING"; exit 1; }Length of output: 758 Add missing test commands for Yarn, pnpm, and Bun in README The • File: - - `npm run test` to run the tests
+ - `npm run test` or `yarn test` or `pnpm test` or `bun test` to run the tests📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Angular "Unit Testing / Jest" example — looks good; verify example page and code exist
Great addition. Let’s ensure both the docs page and the example project are present.
🏁 Script executed:
Length of output: 374
🏁 Script executed:
Length of output: 716
Missing Angular “Unit Testing / Jest” docs page
Our verification shows:
examples/angular/unit-testing/examples/angular/unit-testing/src/app/services/tasks.service.spec.tsdocs/framework/angular/examples/unit-testing.*Please add the corresponding docs page (e.g.
docs/framework/angular/examples/unit-testing.mdor.mdx) so that theconfig.jsonentry under"framework/angular/examples/unit-testing"points to a real file.🤖 Prompt for AI Agents