diff --git a/packages/angular-query-experimental/package.json b/packages/angular-query-experimental/package.json index a1653e2286..98a45bb1de 100644 --- a/packages/angular-query-experimental/package.json +++ b/packages/angular-query-experimental/package.json @@ -73,6 +73,7 @@ "@angular/platform-browser": "^19.1.0-next.0", "@angular/platform-browser-dynamic": "^19.1.0-next.0", "@microsoft/api-extractor": "^7.48.1", + "@testing-library/angular": "^17.3.6", "eslint-plugin-jsdoc": "^50.5.0", "npm-run-all": "^4.1.5", "tsup": "8.0.2", diff --git a/packages/angular-query-experimental/src/__tests__/inject-queries.test-d.ts b/packages/angular-query-experimental/src/__tests__/inject-queries.test-d.ts new file mode 100644 index 0000000000..62547fd9e0 --- /dev/null +++ b/packages/angular-query-experimental/src/__tests__/inject-queries.test-d.ts @@ -0,0 +1,177 @@ +import { describe, expectTypeOf, it } from 'vitest' +import { skipToken } from '..' +import { injectQueries } from '../inject-queries' +import { queryOptions } from '../query-options' +import type { CreateQueryOptions, CreateQueryResult, OmitKeyof } from '..' +import type { Signal } from '@angular/core' + +describe('InjectQueries config object overload', () => { + it('TData should always be defined when initialData is provided as an object', () => { + const query1 = { + queryKey: ['key1'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: { + wow: false, + }, + } + + const query2 = { + queryKey: ['key2'], + queryFn: () => 'Query Data', + initialData: 'initial data', + } + + const query3 = { + queryKey: ['key2'], + queryFn: () => 'Query Data', + } + + const queryResults = injectQueries(() => ({ + queries: [query1, query2, query3], + })) + + const query1Data = queryResults()[0].data() + const query2Data = queryResults()[1].data() + const query3Data = queryResults()[2].data() + + expectTypeOf(query1Data).toEqualTypeOf<{ wow: boolean }>() + expectTypeOf(query2Data).toEqualTypeOf() + expectTypeOf(query3Data).toEqualTypeOf() + }) + + it('TData should be defined when passed through queryOptions', () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: { + wow: true, + }, + }) + const queryResults = injectQueries(() => ({ queries: [options] })) + + const data = queryResults()[0].data() + + expectTypeOf(data).toEqualTypeOf<{ wow: boolean }>() + }) + + it('should be possible to define a different TData than TQueryFnData using select with queryOptions spread into injectQuery', () => { + const query1 = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(1), + select: (data) => data > 1, + }) + + const query2 = { + queryKey: ['key'], + queryFn: () => Promise.resolve(1), + select: (data: number) => data > 1, + } + + const queryResults = injectQueries(() => ({ queries: [query1, query2] })) + const query1Data = queryResults()[0].data() + const query2Data = queryResults()[1].data() + + expectTypeOf(query1Data).toEqualTypeOf() + expectTypeOf(query2Data).toEqualTypeOf() + }) + + it('TData should have undefined in the union when initialData is provided as a function which can return undefined', () => { + const queryResults = injectQueries(() => ({ + queries: [ + { + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: () => undefined as { wow: boolean } | undefined, + }, + ], + })) + + const data = queryResults()[0].data() + + expectTypeOf(data).toEqualTypeOf<{ wow: boolean } | undefined>() + }) + + describe('custom injectable', () => { + it('should allow custom hooks using UseQueryOptions', () => { + type Data = string + + const injectCustomQueries = ( + options?: OmitKeyof, 'queryKey' | 'queryFn'>, + ) => { + return injectQueries(() => ({ + queries: [ + { + ...options, + queryKey: ['todos-key'], + queryFn: () => Promise.resolve('data'), + }, + ], + })) + } + + const queryResults = injectCustomQueries() + const data = queryResults()[0].data() + + expectTypeOf(data).toEqualTypeOf() + }) + }) + + it('TData should have correct type when conditional skipToken is passed', () => { + const queryResults = injectQueries(() => ({ + queries: [ + { + queryKey: ['withSkipToken'], + queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5), + }, + ], + })) + + const firstResult = queryResults()[0] + + expectTypeOf(firstResult).toEqualTypeOf>() + expectTypeOf(firstResult.data()).toEqualTypeOf() + }) + + it('should return correct data for dynamic queries with mixed result types', () => { + const Queries1 = { + get: () => + queryOptions({ + queryKey: ['key1'], + queryFn: () => Promise.resolve(1), + }), + } + const Queries2 = { + get: () => + queryOptions({ + queryKey: ['key2'], + queryFn: () => Promise.resolve(true), + }), + } + + const queries1List = [1, 2, 3].map(() => ({ ...Queries1.get() })) + const result = injectQueries(() => ({ + queries: [...queries1List, { ...Queries2.get() }], + })) + + expectTypeOf(result).branded.toEqualTypeOf< + Signal< + [ + ...Array>, + CreateQueryResult, + ] + > + >() + }) +}) diff --git a/packages/angular-query-experimental/src/__tests__/inject-queries.test.tsx b/packages/angular-query-experimental/src/__tests__/inject-queries.test.tsx new file mode 100644 index 0000000000..3a84e066b8 --- /dev/null +++ b/packages/angular-query-experimental/src/__tests__/inject-queries.test.tsx @@ -0,0 +1,82 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { render, waitFor } from '@testing-library/angular' +import { + Component, + effect, + provideExperimentalZonelessChangeDetection, +} from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { QueryClient, injectQueries, provideTanStackQuery } from '..' +import { evaluateSignals, queryKey } from './test-utils' + +let queryClient: QueryClient + +beforeEach(() => { + queryClient = new QueryClient() + // vi.useFakeTimers() + TestBed.configureTestingModule({ + providers: [ + provideExperimentalZonelessChangeDetection(), + provideTanStackQuery(queryClient), + ], + }) +}) + +afterEach(() => { + // vi.useRealTimers() +}) + +describe('useQueries', () => { + it('should return the correct states', async () => { + const key1 = queryKey() + const key2 = queryKey() + const results: Array>> = [] + + @Component({ + template: ` +
+
+ data1: {{ toString(result()[0].data() ?? 'null') }}, data2: + {{ toString(result()[1].data() ?? 'null') }} +
+
+ `, + }) + class Page { + toString(val: any) { + return String(val) + } + result = injectQueries(() => ({ + queries: [ + { + queryKey: key1, + queryFn: async () => { + await new Promise((r) => setTimeout(r, 10)) + return 1 + }, + }, + { + queryKey: key2, + queryFn: async () => { + await new Promise((r) => setTimeout(r, 100)) + return 2 + }, + }, + ], + })) + + _pushResults = effect(() => { + results.push(this.result().map(evaluateSignals)) + }) + } + + const rendered = await render(Page) + + await waitFor(() => rendered.getByText('data1: 1, data2: 2')) + + expect(results.length).toBe(3) + expect(results[0]).toMatchObject([{ data: undefined }, { data: undefined }]) + expect(results[1]).toMatchObject([{ data: 1 }, { data: undefined }]) + expect(results[2]).toMatchObject([{ data: 1 }, { data: 2 }]) + }) +}) diff --git a/packages/angular-query-experimental/src/__tests__/test-utils.ts b/packages/angular-query-experimental/src/__tests__/test-utils.ts index bb3f5bae83..3683d10431 100644 --- a/packages/angular-query-experimental/src/__tests__/test-utils.ts +++ b/packages/angular-query-experimental/src/__tests__/test-utils.ts @@ -64,7 +64,7 @@ export function errorMutator(_parameter?: unknown): Promise { } // Evaluate all signals on an object and return the result -function evaluateSignals>( +export function evaluateSignals>( obj: T, ): { [K in keyof T]: ReturnType } { const result: Partial<{ [K in keyof T]: ReturnType }> = {} diff --git a/packages/angular-query-experimental/src/inject-queries.ts b/packages/angular-query-experimental/src/inject-queries.ts index 3da49894cb..0d9dcf2653 100644 --- a/packages/angular-query-experimental/src/inject-queries.ts +++ b/packages/angular-query-experimental/src/inject-queries.ts @@ -5,14 +5,17 @@ import { } from '@tanstack/query-core' import { DestroyRef, + Injector, NgZone, computed, effect, inject, + runInInjectionContext, signal, + untracked, } from '@angular/core' import { assertInjector } from './util/assert-injector/assert-injector' -import type { Injector, Signal } from '@angular/core' +import { signalProxy } from './signal-proxy' import type { DefaultError, OmitKeyof, @@ -21,19 +24,24 @@ import type { QueryFunction, QueryKey, QueryObserverOptions, - QueryObserverResult, ThrowOnError, } from '@tanstack/query-core' +import type { + CreateQueryOptions, + CreateQueryResult, + DefinedCreateQueryResult, +} from './types' +import type { Signal } from '@angular/core' // This defines the `CreateQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`. -// `placeholderData` function does not have a parameter +// `placeholderData` function always gets undefined passed type QueryObserverOptionsForCreateQueries< TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, > = OmitKeyof< - QueryObserverOptions, + CreateQueryOptions, 'placeholderData' > & { placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction @@ -43,9 +51,9 @@ type QueryObserverOptionsForCreateQueries< type MAXIMUM_DEPTH = 20 // Widen the type of the symbol to enable type inference even if skipToken is not immutable. -type SkipTokenForUseQueries = symbol +type SkipTokenForCreateQueries = symbol -type GetOptions = +type GetCreateQueryOptionsForCreateQueries = // Part 1: responsible for applying explicit type parameter to function arguments, if object { queryFnData: TQueryFnData, error: TError, data: TData } T extends { queryFnData: infer TQueryFnData @@ -68,8 +76,8 @@ type GetOptions = T extends { queryFn?: | QueryFunction - | SkipTokenForUseQueries - select: (data: any) => infer TData + | SkipTokenForCreateQueries + select?: (data: any) => infer TData throwOnError?: ThrowOnError } ? QueryObserverOptionsForCreateQueries< @@ -81,54 +89,71 @@ type GetOptions = : // Fallback QueryObserverOptionsForCreateQueries -type GetResults = +// A defined initialData setting should return a DefinedCreateQueryResult rather than CreateQueryResult +type GetDefinedOrUndefinedQueryResult = T extends { + initialData?: infer TInitialData +} + ? unknown extends TInitialData + ? CreateQueryResult + : TInitialData extends TData + ? DefinedCreateQueryResult + : TInitialData extends () => infer TInitialDataResult + ? unknown extends TInitialDataResult + ? CreateQueryResult + : TInitialDataResult extends TData + ? DefinedCreateQueryResult + : CreateQueryResult + : CreateQueryResult + : CreateQueryResult + +type GetCreateQueryResult = // Part 1: responsible for mapping explicit type parameter to function result, if object T extends { queryFnData: any; error?: infer TError; data: infer TData } - ? QueryObserverResult + ? GetDefinedOrUndefinedQueryResult : T extends { queryFnData: infer TQueryFnData; error?: infer TError } - ? QueryObserverResult + ? GetDefinedOrUndefinedQueryResult : T extends { data: infer TData; error?: infer TError } - ? QueryObserverResult + ? GetDefinedOrUndefinedQueryResult : // Part 2: responsible for mapping explicit type parameter to function result, if tuple T extends [any, infer TError, infer TData] - ? QueryObserverResult + ? GetDefinedOrUndefinedQueryResult : T extends [infer TQueryFnData, infer TError] - ? QueryObserverResult + ? GetDefinedOrUndefinedQueryResult : T extends [infer TQueryFnData] - ? QueryObserverResult + ? GetDefinedOrUndefinedQueryResult : // Part 3: responsible for mapping inferred type to results, if no explicit parameter was provided T extends { queryFn?: | QueryFunction - | SkipTokenForUseQueries - select: (data: any) => infer TData + | SkipTokenForCreateQueries + select?: (data: any) => infer TData throwOnError?: ThrowOnError } - ? QueryObserverResult< + ? GetDefinedOrUndefinedQueryResult< + T, unknown extends TData ? TQueryFnData : TData, unknown extends TError ? DefaultError : TError > : // Fallback - QueryObserverResult + CreateQueryResult /** * QueriesOptions reducer recursively unwraps function arguments to infer/enforce type param - * @public */ export type QueriesOptions< T extends Array, - TResult extends Array = [], + TResults extends Array = [], TDepth extends ReadonlyArray = [], > = TDepth['length'] extends MAXIMUM_DEPTH ? Array : T extends [] ? [] : T extends [infer Head] - ? [...TResult, GetOptions] - : T extends [infer Head, ...infer Tail] + ? [...TResults, GetCreateQueryOptionsForCreateQueries] + : T extends [infer Head, ...infer Tails] ? QueriesOptions< - [...Tail], - [...TResult, GetOptions], + [...Tails], + [...TResults, GetCreateQueryOptionsForCreateQueries], [...TDepth, 1] > : ReadonlyArray extends T @@ -160,65 +185,64 @@ export type QueriesOptions< */ export type QueriesResults< T extends Array, - TResult extends Array = [], + TResults extends Array = [], TDepth extends ReadonlyArray = [], > = TDepth['length'] extends MAXIMUM_DEPTH - ? Array + ? Array : T extends [] ? [] : T extends [infer Head] - ? [...TResult, GetResults] - : T extends [infer Head, ...infer Tail] + ? [...TResults, GetCreateQueryResult] + : T extends [infer Head, ...infer Tails] ? QueriesResults< - [...Tail], - [...TResult, GetResults], + [...Tails], + [...TResults, GetCreateQueryResult], [...TDepth, 1] > - : T extends Array< - QueryObserverOptionsForCreateQueries< - infer TQueryFnData, - infer TError, - infer TData, - any - > - > - ? // Dynamic-size (homogenous) CreateQueryOptions array: map directly to array of results - Array< - QueryObserverResult< - unknown extends TData ? TQueryFnData : TData, - unknown extends TError ? DefaultError : TError - > - > - : // Fallback - Array + : { [K in keyof T]: GetCreateQueryResult } + +export interface InjectQueriesOptions< + T extends Array, + TCombinedResult = QueriesResults, +> { + queries: + | readonly [...QueriesOptions] + | readonly [ + ...{ [K in keyof T]: GetCreateQueryOptionsForCreateQueries }, + ] + combine?: (result: QueriesResults) => TCombinedResult +} /** - * @param root0 - * @param root0.queries - * @param root0.combine - * @param injector + * @param optionsFn - A function that returns queries' options. + * @param injector - The Angular injector to use. * @public */ export function injectQueries< T extends Array, TCombinedResult = QueriesResults, >( - { - queries, - ...options - }: { - queries: Signal<[...QueriesOptions]> - combine?: (result: QueriesResults) => TCombinedResult - }, + optionsFn: () => InjectQueriesOptions, injector?: Injector, ): Signal { return assertInjector(injectQueries, injector, () => { + const ngInjector = inject(Injector) const destroyRef = inject(DestroyRef) const ngZone = inject(NgZone) const queryClient = inject(QueryClient) + /** + * Signal that has the default options from query client applied + * computed() is used so signals can be inserted into the options + * making it reactive. Wrapping options in a function ensures embedded expressions + * are preserved and can keep being applied after signal changes + */ + const optionsSignal = computed(() => { + return runInInjectionContext(injector ?? ngInjector, () => optionsFn()) + }) + const defaultedQueries = computed(() => { - return queries().map((opts) => { + return optionsSignal().queries.map((opts) => { const defaultedOptions = queryClient.defaultQueryOptions(opts) // Make sure the results are already in fetching state before subscribing or updating options defaultedOptions._optimisticResults = 'optimistic' @@ -227,34 +251,69 @@ export function injectQueries< }) }) - const observer = new QueriesObserver( - queryClient, - defaultedQueries(), - options as QueriesObserverOptions, + const observerSignal = (() => { + let instance: QueriesObserver | null = null + + return computed(() => { + return (instance ||= new QueriesObserver( + queryClient, + defaultedQueries(), + optionsSignal() as QueriesObserverOptions, + )) + }) + })() + + const optimisticResultSignal = computed(() => + observerSignal().getOptimisticResult( + defaultedQueries(), + (optionsSignal() as QueriesObserverOptions).combine, + ), ) // Do not notify on updates because of changes in the options because // these changes should already be reflected in the optimistic result. effect(() => { - observer.setQueries( + observerSignal().setQueries( defaultedQueries(), - options as QueriesObserverOptions, + optionsSignal() as QueriesObserverOptions, { listeners: false }, ) }) - const [, getCombinedResult] = observer.getOptimisticResult( - defaultedQueries(), - (options as QueriesObserverOptions).combine, - ) + const optimisticCombinedResultSignal = computed(() => { + const [_optimisticResult, getCombinedResult, trackResult] = + optimisticResultSignal() + return getCombinedResult(trackResult()) + }) - const result = signal(getCombinedResult() as any) + const resultFromSubscriberSignal = signal(null) - const unsubscribe = ngZone.runOutsideAngular(() => - observer.subscribe(notifyManager.batchCalls(result.set)), - ) - destroyRef.onDestroy(unsubscribe) + effect(() => { + const observer = observerSignal() + const [_optimisticResult, getCombinedResult] = optimisticResultSignal() + + untracked(() => { + const unsubscribe = ngZone.runOutsideAngular(() => + observer.subscribe( + notifyManager.batchCalls((state) => { + resultFromSubscriberSignal.set(getCombinedResult(state)) + }), + ), + ) - return result - }) + destroyRef.onDestroy(unsubscribe) + }) + }) + + const resultSignal = computed(() => { + const subscriberResult = resultFromSubscriberSignal() + const optimisticResult = optimisticCombinedResultSignal() + return subscriberResult ?? optimisticResult + }) + + return computed(() => { + const result = resultSignal() + return result.map((query) => signalProxy(signal(query))) + }) + }) as unknown as Signal } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eabf66e820..d4d02e180d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1905,7 +1905,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: ^17.3.8 - version: 17.3.8(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.8)))(typescript@5.4.5))(@types/express@4.17.21)(@types/node@22.10.7)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.90.3(esbuild@0.20.1)))(lightningcss@1.27.0)(ng-packagr@17.3.0(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.8)))(typescript@5.4.5))(tailwindcss@3.4.7)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.7)(typescript@5.4.5) + version: 17.3.8(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.8)))(typescript@5.4.5))(@types/express@4.17.21)(@types/node@22.10.7)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.90.3(esbuild@0.24.0)))(lightningcss@1.27.0)(ng-packagr@17.3.0(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.8)))(typescript@5.4.5))(tailwindcss@3.4.7)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.7)(typescript@5.4.5) '@angular/cli': specifier: ^17.3.8 version: 17.3.8(chokidar@3.6.0) @@ -2169,7 +2169,7 @@ importers: dependencies: '@angular/common': specifier: '>=16.0.0' - version: 17.3.12(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1) + version: 19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1) '@tanstack/query-core': specifier: workspace:* version: link:../query-core @@ -2185,13 +2185,16 @@ importers: version: 19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0) '@angular/platform-browser': specifier: ^19.1.0-next.0 - version: 19.1.0-next.0(@angular/common@17.3.12(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0)) + version: 19.1.0-next.0(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0)) '@angular/platform-browser-dynamic': specifier: ^19.1.0-next.0 - version: 19.1.0-next.0(@angular/common@17.3.12(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/compiler@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0)))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(@angular/platform-browser@19.1.0-next.0(@angular/common@17.3.12(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))) + version: 19.1.0-next.0(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/compiler@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0)))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(@angular/platform-browser@19.1.0-next.0(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))) '@microsoft/api-extractor': specifier: ^7.48.1 version: 7.48.1(@types/node@22.10.7) + '@testing-library/angular': + specifier: ^17.3.6 + version: 17.3.6(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(@angular/platform-browser@19.1.0-next.0(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0)))(@angular/router@19.1.0-next.0(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(@angular/platform-browser@19.1.0-next.0(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0)))(rxjs@7.8.1))(@testing-library/dom@10.4.0) eslint-plugin-jsdoc: specifier: ^50.5.0 version: 50.5.0(eslint@9.15.0(jiti@2.4.0)) @@ -6695,6 +6698,15 @@ packages: react: '>=16' react-dom: '>=16' + '@testing-library/angular@17.3.6': + resolution: {integrity: sha512-8LxEfNtjhd6iguWus8TQngMwXfw8Y4qPMXeeCqtxbIf7gbhnPA52R8ofiqCMy+p9cD/kMOY9KZAxj18P+5vNHw==} + peerDependencies: + '@angular/common': '>= 17.0.0' + '@angular/core': '>= 17.0.0' + '@angular/platform-browser': '>= 17.0.0' + '@angular/router': '>= 17.0.0' + '@testing-library/dom': ^10.0.0 + '@testing-library/dom@10.4.0': resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} @@ -16102,11 +16114,11 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@17.3.8(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.8)))(typescript@5.4.5))(@types/express@4.17.21)(@types/node@22.10.7)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.90.3(esbuild@0.20.1)))(lightningcss@1.27.0)(ng-packagr@17.3.0(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.8)))(typescript@5.4.5))(tailwindcss@3.4.7)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.7)(typescript@5.4.5)': + '@angular-devkit/build-angular@17.3.8(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.8)))(typescript@5.4.5))(@types/express@4.17.21)(@types/node@22.10.7)(chokidar@3.6.0)(html-webpack-plugin@5.6.3(webpack@5.90.3(esbuild@0.24.0)))(lightningcss@1.27.0)(ng-packagr@17.3.0(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.8)))(typescript@5.4.5))(tailwindcss@3.4.7)(tslib@2.8.1)(typescript@5.4.5))(tailwindcss@3.4.7)(typescript@5.4.5)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1703.8(chokidar@3.6.0) - '@angular-devkit/build-webpack': 0.1703.8(chokidar@3.6.0)(webpack-dev-server@4.15.1(webpack@5.90.3(esbuild@0.20.1)))(webpack@5.90.3(esbuild@0.20.1)) + '@angular-devkit/build-webpack': 0.1703.8(chokidar@3.6.0)(webpack-dev-server@4.15.1(webpack@5.90.3(esbuild@0.24.0)))(webpack@5.90.3(esbuild@0.20.1)) '@angular-devkit/core': 17.3.8(chokidar@3.6.0) '@angular/compiler-cli': 17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.8)))(typescript@5.4.5) '@babel/core': 7.24.0 @@ -16164,11 +16176,11 @@ snapshots: undici: 6.11.1 vite: 5.1.7(@types/node@22.10.7)(less@4.2.0)(lightningcss@1.27.0)(sass@1.71.1)(terser@5.29.1) watchpack: 2.4.0 - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) webpack-dev-middleware: 6.1.2(webpack@5.90.3(esbuild@0.20.1)) - webpack-dev-server: 4.15.1(webpack@5.90.3(esbuild@0.20.1)) + webpack-dev-server: 4.15.1(webpack@5.90.3(esbuild@0.24.0)) webpack-merge: 5.10.0 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.90.3(esbuild@0.20.1)))(webpack@5.90.3(esbuild@0.20.1)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.90.3(esbuild@0.24.0)))(webpack@5.90.3(esbuild@0.20.1)) optionalDependencies: esbuild: 0.20.1 ng-packagr: 17.3.0(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.8)))(typescript@5.4.5))(tailwindcss@3.4.7)(tslib@2.8.1)(typescript@5.4.5) @@ -16192,12 +16204,12 @@ snapshots: - utf-8-validate - webpack-cli - '@angular-devkit/build-webpack@0.1703.8(chokidar@3.6.0)(webpack-dev-server@4.15.1(webpack@5.90.3(esbuild@0.20.1)))(webpack@5.90.3(esbuild@0.20.1))': + '@angular-devkit/build-webpack@0.1703.8(chokidar@3.6.0)(webpack-dev-server@4.15.1(webpack@5.90.3(esbuild@0.24.0)))(webpack@5.90.3(esbuild@0.20.1))': dependencies: '@angular-devkit/architect': 0.1703.8(chokidar@3.6.0) rxjs: 7.8.1 - webpack: 5.90.3(esbuild@0.24.0) - webpack-dev-server: 4.15.1(webpack@5.90.3(esbuild@0.20.1)) + webpack: 5.90.3(esbuild@0.20.1) + webpack-dev-server: 4.15.1(webpack@5.90.3(esbuild@0.24.0)) transitivePeerDependencies: - chokidar @@ -20169,7 +20181,7 @@ snapshots: dependencies: '@angular/compiler-cli': 17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.8)))(typescript@5.4.5) typescript: 5.4.5 - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) '@nodelib/fs.scandir@2.1.5': dependencies: @@ -21282,6 +21294,15 @@ snapshots: react: 19.0.0 react-dom: 19.0.0(react@19.0.0) + '@testing-library/angular@17.3.6(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(@angular/platform-browser@19.1.0-next.0(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0)))(@angular/router@19.1.0-next.0(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(@angular/platform-browser@19.1.0-next.0(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0)))(rxjs@7.8.1))(@testing-library/dom@10.4.0)': + dependencies: + '@angular/common': 19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1) + '@angular/core': 19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0) + '@angular/platform-browser': 19.1.0-next.0(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0)) + '@angular/router': 19.1.0-next.0(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(@angular/platform-browser@19.1.0-next.0(@angular/common@19.1.0-next.0(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0-next.0(rxjs@7.8.1)(zone.js@0.15.0)))(rxjs@7.8.1) + '@testing-library/dom': 10.4.0 + tslib: 2.8.1 + '@testing-library/dom@10.4.0': dependencies: '@babel/code-frame': 7.26.2 @@ -22768,7 +22789,7 @@ snapshots: '@babel/core': 7.24.0 find-cache-dir: 4.0.0 schema-utils: 4.3.0 - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) babel-loader@9.2.1(@babel/core@7.26.0)(webpack@5.96.1): dependencies: @@ -23726,7 +23747,7 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.3.0 serialize-javascript: 6.0.2 - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) core-js-compat@3.40.0: dependencies: @@ -23947,7 +23968,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.3 optionalDependencies: - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) css-select@4.3.0: dependencies: @@ -26035,7 +26056,7 @@ snapshots: util.promisify: 1.0.0 webpack: 4.44.2(webpack-cli@4.10.0) - html-webpack-plugin@5.6.3(webpack@5.90.3(esbuild@0.20.1)): + html-webpack-plugin@5.6.3(webpack@5.90.3(esbuild@0.24.0)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -26043,7 +26064,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) optional: true html-webpack-plugin@5.6.3(webpack@5.96.1): @@ -27021,7 +27042,7 @@ snapshots: dependencies: klona: 2.0.6 less: 4.2.0 - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) less@4.2.0: dependencies: @@ -27063,7 +27084,7 @@ snapshots: dependencies: webpack-sources: 3.2.3 optionalDependencies: - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) lie@3.1.1: dependencies: @@ -28037,7 +28058,7 @@ snapshots: dependencies: schema-utils: 4.3.0 tapable: 2.2.1 - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) minimalistic-assert@1.0.1: {} @@ -29426,7 +29447,7 @@ snapshots: postcss: 8.4.35 semver: 7.6.3 optionalDependencies: - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) transitivePeerDependencies: - typescript @@ -30442,7 +30463,7 @@ snapshots: neo-async: 2.6.2 optionalDependencies: sass: 1.71.1 - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) sass@1.71.1: dependencies: @@ -30924,7 +30945,7 @@ snapshots: dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) source-map-resolve@0.5.3: dependencies: @@ -31420,16 +31441,16 @@ snapshots: webpack-sources: 1.4.3 worker-farm: 1.7.0 - terser-webpack-plugin@5.3.11(esbuild@0.24.0)(webpack@5.90.3(esbuild@0.20.1)): + terser-webpack-plugin@5.3.11(esbuild@0.20.1)(webpack@5.90.3(esbuild@0.24.0)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.0 serialize-javascript: 6.0.2 terser: 5.31.6 - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) optionalDependencies: - esbuild: 0.24.0 + esbuild: 0.20.1 terser-webpack-plugin@5.3.11(esbuild@0.24.0)(webpack@5.96.1): dependencies: @@ -32571,14 +32592,14 @@ snapshots: webpack: 5.96.1(esbuild@0.24.0)(webpack-cli@5.1.4) webpack-merge: 5.10.0 - webpack-dev-middleware@5.3.4(webpack@5.90.3(esbuild@0.20.1)): + webpack-dev-middleware@5.3.4(webpack@5.90.3(esbuild@0.24.0)): dependencies: colorette: 2.0.20 memfs: 3.5.3 mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.3.0 - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) webpack-dev-middleware@6.1.2(webpack@5.90.3(esbuild@0.20.1)): dependencies: @@ -32588,9 +32609,9 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.0 optionalDependencies: - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) - webpack-dev-server@4.15.1(webpack@5.90.3(esbuild@0.20.1)): + webpack-dev-server@4.15.1(webpack@5.90.3(esbuild@0.24.0)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -32620,10 +32641,10 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 5.3.4(webpack@5.90.3(esbuild@0.20.1)) + webpack-dev-middleware: 5.3.4(webpack@5.90.3(esbuild@0.24.0)) ws: 8.18.0 optionalDependencies: - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) transitivePeerDependencies: - bufferutil - debug @@ -32643,12 +32664,12 @@ snapshots: webpack-sources@3.2.3: {} - webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.90.3(esbuild@0.20.1)))(webpack@5.90.3(esbuild@0.20.1)): + webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.90.3(esbuild@0.24.0)))(webpack@5.90.3(esbuild@0.20.1)): dependencies: typed-assert: 1.0.9 - webpack: 5.90.3(esbuild@0.24.0) + webpack: 5.90.3(esbuild@0.20.1) optionalDependencies: - html-webpack-plugin: 5.6.3(webpack@5.90.3(esbuild@0.20.1)) + html-webpack-plugin: 5.6.3(webpack@5.90.3(esbuild@0.24.0)) webpack-virtual-modules@0.6.2: {} @@ -32682,7 +32703,7 @@ snapshots: transitivePeerDependencies: - supports-color - webpack@5.90.3(esbuild@0.24.0): + webpack@5.90.3(esbuild@0.20.1): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.6 @@ -32705,7 +32726,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.11(esbuild@0.24.0)(webpack@5.90.3(esbuild@0.20.1)) + terser-webpack-plugin: 5.3.11(esbuild@0.20.1)(webpack@5.90.3(esbuild@0.24.0)) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: