diff --git a/modules/signals/spec/helpers.ts b/modules/signals/spec/helpers.ts index af47db90f6..2a039be049 100644 --- a/modules/signals/spec/helpers.ts +++ b/modules/signals/spec/helpers.ts @@ -1,5 +1,6 @@ import { Component, inject, Type } from '@angular/core'; import { TestBed } from '@angular/core/testing'; +import { SignalsDictionary } from '../src/signal-store-models'; export function createLocalService>( serviceToken: Service @@ -30,3 +31,26 @@ export function createLocalService>( destroy: () => fixture.destroy(), }; } + +/** + * This could be done by using `getState`, but + * 1. We don't want to depend on the implementation of `getState` in the test. + * 2. We want to be able to provide the state in its actual type (with slice signals). + */ +export function assertStateSource( + state: SignalsDictionary, + expected: SignalsDictionary +): void { + const stateKeys = Reflect.ownKeys(state); + const expectedKeys = Reflect.ownKeys(expected); + + const currentState = stateKeys.reduce((acc, key) => { + acc[key] = state[key](); + return acc; + }, {} as Record); + const expectedState = expectedKeys.reduce((acc, key) => { + acc[key] = expected[key](); + return acc; + }, {} as Record); + expect(currentState).toEqual(expectedState); +} diff --git a/modules/signals/spec/signal-state.spec.ts b/modules/signals/spec/signal-state.spec.ts index 72ac857efc..2506c355ee 100644 --- a/modules/signals/spec/signal-state.spec.ts +++ b/modules/signals/spec/signal-state.spec.ts @@ -1,7 +1,7 @@ -import { computed } from '@angular/core'; -import { effect, isSignal } from '@angular/core'; +import { computed, effect, isSignal } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { patchState, signalState } from '../src'; +import { SignalsDictionary } from '../src/signal-store-models'; import { STATE_SOURCE } from '../src/state-source'; vi.mock('@angular/core', { spy: true }); @@ -21,21 +21,30 @@ describe('signalState', () => { vi.clearAllMocks(); }); - it('has writable state source', () => { - const state = signalState({}); - const stateSource = state[STATE_SOURCE]; + it('creates its properites as Signals', () => { + const state = signalState({ foo: 'bar' }); + const stateSource: SignalsDictionary = state[STATE_SOURCE]; - expect(isSignal(stateSource)).toBe(true); - expect(typeof stateSource.update === 'function').toBe(true); + expect(isSignal(state)).toBe(true); + for (const key of Reflect.ownKeys(stateSource)) { + expect(isSignal(stateSource[key])).toBe(true); + expect(typeof stateSource[key].update === 'function').toBe(true); + } + }); + + it('does not keep the object reference of the initial state', () => { + const state = signalState(initialState); + expect(state()).not.toBe(initialState); + expect(state()).toEqual(initialState); }); it('creates signals for nested state slices', () => { const state = signalState(initialState); - expect(state()).toBe(initialState); + expect(state()).toEqual(initialState); expect(isSignal(state)).toBe(true); - expect(state.user()).toBe(initialState.user); + expect(state.user()).toEqual(initialState.user); expect(isSignal(state.user)).toBe(true); expect(state.user.firstName()).toBe(initialState.user.firstName); @@ -80,20 +89,11 @@ describe('signalState', () => { expect((state.user.firstName as any).y).toBe(undefined); }); - it('does not modify STATE_SOURCE', () => { - const state = signalState(initialState); - - expect((state[STATE_SOURCE] as any).user).toBe(undefined); - expect((state[STATE_SOURCE] as any).foo).toBe(undefined); - expect((state[STATE_SOURCE] as any).numbers).toBe(undefined); - expect((state[STATE_SOURCE] as any).ngrx).toBe(undefined); - }); - it('overrides Function properties if state keys have the same name', () => { const initialState = { name: { length: { length: 'ngrx' }, name: 20 } }; const state = signalState(initialState); - expect(state()).toBe(initialState); + expect(state()).toEqual(initialState); expect(state.name()).toBe(initialState.name); expect(isSignal(state.name)).toBe(true); @@ -190,12 +190,12 @@ describe('signalState', () => { patchState(state, {}); TestBed.flushEffects(); - expect(stateCounter).toBe(2); + expect(stateCounter).toBe(1); expect(userCounter).toBe(1); patchState(state, (state) => state); TestBed.flushEffects(); - expect(stateCounter).toBe(3); + expect(stateCounter).toBe(1); expect(userCounter).toBe(1); })); }); diff --git a/modules/signals/spec/signal-store-feature.spec.ts b/modules/signals/spec/signal-store-feature.spec.ts index b855d84733..7172f0d460 100644 --- a/modules/signals/spec/signal-store-feature.spec.ts +++ b/modules/signals/spec/signal-store-feature.spec.ts @@ -8,6 +8,7 @@ import { withState, } from '../src'; import { STATE_SOURCE } from '../src/state-source'; +import { assertStateSource } from './helpers'; describe('signalStoreFeature', () => { function withCustomFeature1() { @@ -50,7 +51,7 @@ describe('signalStoreFeature', () => { const store = new Store(); - expect(store[STATE_SOURCE]()).toEqual({ foo: 'foo' }); + assertStateSource(store[STATE_SOURCE], { foo: signal('foo') }); expect(store.foo()).toBe('foo'); expect(store.bar()).toBe('foo1'); expect(store.baz()).toBe('foofoo12'); @@ -65,7 +66,7 @@ describe('signalStoreFeature', () => { const store = new Store(); - expect(store[STATE_SOURCE]()).toEqual({ foo: 'foo' }); + assertStateSource(store[STATE_SOURCE], { foo: signal('foo') }); expect(store.foo()).toBe('foo'); expect(store.bar()).toBe('foo1'); expect(store.m()).toBe('foofoofoo123'); @@ -81,7 +82,11 @@ describe('signalStoreFeature', () => { const store = new Store(); - expect(store[STATE_SOURCE]()).toEqual({ foo: 'foo', foo1: 1, foo2: 2 }); + assertStateSource(store[STATE_SOURCE], { + foo: signal('foo'), + foo1: signal(1), + foo2: signal(2), + }); expect(store.foo()).toBe('foo'); expect(store.bar()).toBe('foo1'); expect(store.baz()).toBe('foofoo12'); diff --git a/modules/signals/spec/signal-store.spec.ts b/modules/signals/spec/signal-store.spec.ts index 837e76527a..db351f7e8a 100644 --- a/modules/signals/spec/signal-store.spec.ts +++ b/modules/signals/spec/signal-store.spec.ts @@ -16,7 +16,7 @@ import { withState, } from '../src'; import { STATE_SOURCE } from '../src/state-source'; -import { createLocalService } from './helpers'; +import { assertStateSource, createLocalService } from './helpers'; describe('signalStore', () => { describe('creation', () => { @@ -47,16 +47,20 @@ describe('signalStore', () => { expect(store1.foo()).toBe('bar'); }); - it('creates a store with readonly state source by default', () => { + it('creates a store with state source as Record holding slices as signals by default', () => { const Store = signalStore(withState({ foo: 'bar' })); const store = new Store(); const stateSource = store[STATE_SOURCE]; - expect(isSignal(stateSource)).toBe(true); - expect(stateSource()).toEqual({ foo: 'bar' }); + expect(isSignal(stateSource)).toBe(false); + expect(Object.keys(stateSource)).toEqual(['foo']); + expect(isSignal(stateSource.foo)).toBe(true); + assertStateSource(stateSource, { + foo: signal('bar'), + }); }); - it('creates a store with readonly state source when protectedState option is true', () => { + it('creates a store with state source as Record holding slices as signals when protectedState option is true', () => { const Store = signalStore( { protectedState: true }, withState({ foo: 'bar' }) @@ -64,11 +68,15 @@ describe('signalStore', () => { const store = new Store(); const stateSource = store[STATE_SOURCE]; - expect(isSignal(stateSource)).toBe(true); - expect(stateSource()).toEqual({ foo: 'bar' }); + expect(isSignal(stateSource)).toBe(false); + expect(Object.keys(stateSource)).toEqual(['foo']); + expect(isSignal(stateSource.foo)).toBe(true); + assertStateSource(stateSource, { + foo: signal('bar'), + }); }); - it('creates a store with writable state source when protectedState option is false', () => { + it('creates a store with state source as Record holding slices as writeable signals when protectedState option is false', () => { const Store = signalStore( { protectedState: false }, withState({ foo: 'bar' }) @@ -76,13 +84,19 @@ describe('signalStore', () => { const store = new Store(); const stateSource = store[STATE_SOURCE]; - expect(isSignal(stateSource)).toBe(true); - expect(stateSource()).toEqual({ foo: 'bar' }); - expect(typeof stateSource.update === 'function').toBe(true); + expect(isSignal(stateSource)).toBe(false); + expect(Object.keys(stateSource)).toEqual(['foo']); + expect(isSignal(stateSource.foo)).toBe(true); + assertStateSource(stateSource, { + foo: signal('bar'), + }); + expect(typeof stateSource.foo.update === 'function').toBe(true); patchState(store, { foo: 'baz' }); - expect(stateSource()).toEqual({ foo: 'baz' }); + assertStateSource(stateSource, { + foo: signal('baz'), + }); }); }); @@ -97,10 +111,11 @@ describe('signalStore', () => { const store = new Store(); - expect(store[STATE_SOURCE]()).toEqual({ - foo: 'foo', - x: { y: { z: 10 } }, + assertStateSource(store[STATE_SOURCE], { + foo: signal('foo'), + x: signal({ y: { z: 10 } }), }); + expect(store.foo()).toBe('foo'); expect(store.x()).toEqual({ y: { z: 10 } }); expect(store.x.y()).toEqual({ z: 10 }); @@ -178,7 +193,9 @@ describe('signalStore', () => { const store = new Store(); - expect(store[STATE_SOURCE]()).toEqual({ foo: 'foo' }); + assertStateSource(store[STATE_SOURCE], { + foo: signal('foo'), + }); expect(store.foo()).toBe('foo'); expect(store.bar()).toBe('bar'); expect(store.num).toBe(10); @@ -236,7 +253,9 @@ describe('signalStore', () => { const store = new Store(); - expect(store[STATE_SOURCE]()).toEqual({ foo: 'foo' }); + assertStateSource(store[STATE_SOURCE], { + foo: signal('foo'), + }); expect(store.foo()).toBe('foo'); expect(store.bar()).toBe('bar'); expect(store.num).toBe(10); @@ -279,7 +298,9 @@ describe('signalStore', () => { withMethods(() => ({ baz: () => 'baz' })), withProps(() => ({ num: 100 })), withMethods((store) => { - expect(store[STATE_SOURCE]()).toEqual({ foo: 'foo' }); + assertStateSource(store[STATE_SOURCE], { + foo: signal('foo'), + }); expect(store.foo()).toBe('foo'); expect(store.bar()).toBe('bar'); expect(store.baz()).toBe('baz'); @@ -291,7 +312,9 @@ describe('signalStore', () => { const store = new Store(); - expect(store[STATE_SOURCE]()).toEqual({ foo: 'foo' }); + assertStateSource(store[STATE_SOURCE], { + foo: signal('foo'), + }); expect(store.foo()).toBe('foo'); expect(store.bar()).toBe('bar'); expect(store.baz()).toBe('baz'); @@ -372,7 +395,9 @@ describe('signalStore', () => { withProps(() => ({ num: 10 })), withHooks({ onInit(store) { - expect(store[STATE_SOURCE]()).toEqual({ foo: 'foo' }); + assertStateSource(store[STATE_SOURCE], { + foo: signal('foo'), + }); expect(store.foo()).toBe('foo'); expect(store.bar()).toBe('bar'); expect(store.baz()).toBe('baz'); diff --git a/modules/signals/spec/state-source.spec.ts b/modules/signals/spec/state-source.spec.ts index 5f67f2a73d..d2a0ac8e5b 100644 --- a/modules/signals/spec/state-source.spec.ts +++ b/modules/signals/spec/state-source.spec.ts @@ -17,10 +17,9 @@ import { withHooks, withMethods, withState, - WritableStateSource, } from '../src'; import { STATE_SOURCE } from '../src/state-source'; -import { createLocalService } from './helpers'; +import { assertStateSource, createLocalService } from './helpers'; const SECRET = Symbol('SECRET'); @@ -38,16 +37,16 @@ describe('StateSource', () => { describe('isWritableStateSource', () => { it('returns true for a writable StateSource', () => { - const stateSource: StateSource = { - [STATE_SOURCE]: signal(initialState), + const stateSource: StateSource<{ value: typeof initialState }> = { + [STATE_SOURCE]: { value: signal(initialState) }, }; expect(isWritableStateSource(stateSource)).toBe(true); }); it('returns false for a readonly StateSource', () => { - const stateSource: StateSource = { - [STATE_SOURCE]: signal(initialState).asReadonly(), + const stateSource: StateSource<{ vaulue: typeof initialState }> = { + [STATE_SOURCE]: { value: signal(initialState).asReadonly() }, }; expect(isWritableStateSource(stateSource)).toBe(false); @@ -81,10 +80,12 @@ describe('StateSource', () => { foo: 'baz', }); - expect(state[STATE_SOURCE]()).toEqual({ - ...initialState, - user: { firstName: 'Johannes', lastName: 'Schmidt' }, - foo: 'baz', + assertStateSource(state[STATE_SOURCE], { + user: signal({ firstName: 'Johannes', lastName: 'Schmidt' }), + foo: signal('baz'), + numbers: signal([1, 2, 3]), + ngrx: signal('signals'), + [SECRET]: signal('secret'), }); }); @@ -96,10 +97,12 @@ describe('StateSource', () => { ngrx: 'rocks', })); - expect(state[STATE_SOURCE]()).toEqual({ - ...initialState, - numbers: [1, 2, 3, 4], - ngrx: 'rocks', + assertStateSource(state[STATE_SOURCE], { + user: signal({ firstName: 'John', lastName: 'Smith' }), + foo: signal('bar'), + numbers: signal([1, 2, 3, 4]), + ngrx: signal('rocks'), + [SECRET]: signal('secret'), }); }); @@ -121,11 +124,12 @@ describe('StateSource', () => { { foo: 'foo' } ); - expect(state[STATE_SOURCE]()).toEqual({ - ...initialState, - user: { firstName: 'Jovan', lastName: 'Schmidt' }, - foo: 'foo', - numbers: [1, 2, 3, 4], + assertStateSource(state[STATE_SOURCE], { + user: signal({ firstName: 'Jovan', lastName: 'Schmidt' }), + foo: signal('foo'), + numbers: signal([1, 2, 3, 4]), + ngrx: signal('signals'), + [SECRET]: signal('secret'), }); }); diff --git a/modules/signals/spec/with-state.spec.ts b/modules/signals/spec/with-state.spec.ts index b09ab24cfd..c1f9f60c6f 100644 --- a/modules/signals/spec/with-state.spec.ts +++ b/modules/signals/spec/with-state.spec.ts @@ -1,18 +1,18 @@ import { isSignal, signal } from '@angular/core'; import { withComputed, withMethods, withState } from '../src'; -import { STATE_SOURCE } from '../src/state-source'; import { getInitialInnerStore } from '../src/signal-store'; +import { getState } from '../src/state-source'; describe('withState', () => { it('patches state source and updates slices immutably', () => { const initialStore = getInitialInnerStore(); - const initialState = initialStore[STATE_SOURCE](); + const initialState = getState(initialStore); const store = withState({ foo: 'bar', x: { y: 'z' }, })(initialStore); - const state = store[STATE_SOURCE](); + const state = getState(store); expect(state).toEqual({ foo: 'bar', x: { y: 'z' } }); expect(initialState).toEqual({}); @@ -46,7 +46,7 @@ describe('withState', () => { foo: 'bar', x: { y: 'z' }, }))(initialStore); - const state = store[STATE_SOURCE](); + const state = getState(store); expect(state).toEqual({ foo: 'bar', x: { y: 'z' } }); expect(store.stateSignals.foo()).toBe('bar'); diff --git a/modules/signals/src/signal-state.ts b/modules/signals/src/signal-state.ts index d31521151e..cb16e38ef0 100644 --- a/modules/signals/src/signal-state.ts +++ b/modules/signals/src/signal-state.ts @@ -1,6 +1,7 @@ -import { signal } from '@angular/core'; -import { STATE_SOURCE, WritableStateSource } from './state-source'; +import { computed, signal } from '@angular/core'; import { DeepSignal, toDeepSignal } from './deep-signal'; +import { SignalsDictionary } from './signal-store-models'; +import { STATE_SOURCE, WritableStateSource } from './state-source'; export type SignalState = DeepSignal & WritableStateSource; @@ -8,11 +9,40 @@ export type SignalState = DeepSignal & export function signalState( initialState: State ): SignalState { - const stateSource = signal(initialState as State); - const signalState = toDeepSignal(stateSource.asReadonly()); + const stateKeys = Reflect.ownKeys(initialState); + const stateAsRecord = initialState as Record; + + // define STATE_SOURCE property + const stateSource = stateKeys.reduce( + (signalsDict, key) => ({ + ...signalsDict, + [key]: signal(stateAsRecord[key]), + }), + {} as SignalsDictionary + ); + + // define signalState as a computed signal of all STATE_SOURCE properties + const signalState = computed(() => + stateKeys.reduce( + (state, key) => ({ + ...state, + [key]: stateSource[key](), + }), + {} + ) + ); + + // append STATE_SOURCE property to the signalState Object.defineProperty(signalState, STATE_SOURCE, { value: stateSource, }); + // generate deep signals + for (const key of stateKeys) { + Object.defineProperty(signalState, key, { + value: toDeepSignal(stateSource[key]), + }); + } + return signalState as SignalState; } diff --git a/modules/signals/src/signal-store-feature.ts b/modules/signals/src/signal-store-feature.ts index cf3da5d0f5..4cf59bf88a 100644 --- a/modules/signals/src/signal-store-feature.ts +++ b/modules/signals/src/signal-store-feature.ts @@ -369,13 +369,13 @@ export function signalStoreFeature< >; export function signalStoreFeature( - featureOrInput: SignalStoreFeature | Partial, - ...restFeatures: SignalStoreFeature[] -): SignalStoreFeature { - const features = - typeof featureOrInput === 'function' - ? [featureOrInput, ...restFeatures] - : restFeatures; + ...args: + | [Partial, ...SignalStoreFeature[]] + | SignalStoreFeature[] +): SignalStoreFeature { + const features = ( + typeof args[0] === 'function' ? args : args.slice(1) + ) as SignalStoreFeature[]; return (inputStore) => features.reduce((store, feature) => feature(store), inputStore); diff --git a/modules/signals/src/signal-store-models.ts b/modules/signals/src/signal-store-models.ts index 451a03df47..0b9dc04df4 100644 --- a/modules/signals/src/signal-store-models.ts +++ b/modules/signals/src/signal-store-models.ts @@ -11,7 +11,7 @@ export type StateSignals = IsKnownRecord> extends true } : {}; -export type SignalsDictionary = Record>; +export type SignalsDictionary = Record>; export type MethodsDictionary = Record; diff --git a/modules/signals/src/signal-store.ts b/modules/signals/src/signal-store.ts index ce70ab8e46..692b14abb8 100644 --- a/modules/signals/src/signal-store.ts +++ b/modules/signals/src/signal-store.ts @@ -1384,7 +1384,7 @@ export function signalStore( export function getInitialInnerStore(): InnerSignalStore { return { - [STATE_SOURCE]: signal({}), + [STATE_SOURCE]: {}, stateSignals: {}, props: {}, methods: {}, diff --git a/modules/signals/src/state-source.ts b/modules/signals/src/state-source.ts index 98549e32a3..b0e687aed8 100644 --- a/modules/signals/src/state-source.ts +++ b/modules/signals/src/state-source.ts @@ -10,16 +10,18 @@ import { } from '@angular/core'; import { Prettify } from './ts-helpers'; -const STATE_WATCHERS = new WeakMap, Array>>(); +const STATE_WATCHERS = new WeakMap>>(); export const STATE_SOURCE = Symbol('STATE_SOURCE'); export type WritableStateSource = { - [STATE_SOURCE]: WritableSignal; + [STATE_SOURCE]: { + [Property in keyof State]: WritableSignal; + }; }; export type StateSource = { - [STATE_SOURCE]: Signal; + [STATE_SOURCE]: { [Property in keyof State]: Signal }; }; export type PartialStateUpdater = ( @@ -33,37 +35,62 @@ export type StateWatcher = ( export function isWritableStateSource( stateSource: StateSource ): stateSource is WritableStateSource { - return ( - 'set' in stateSource[STATE_SOURCE] && - 'update' in stateSource[STATE_SOURCE] && - typeof stateSource[STATE_SOURCE].set === 'function' && - typeof stateSource[STATE_SOURCE].update === 'function' - ); + const signals: Record = stateSource[STATE_SOURCE]; + return Reflect.ownKeys(stateSource[STATE_SOURCE]).every((key) => { + const stateSignal = signals[key]; + return ( + isSignal(stateSignal) && + 'set' in stateSignal && + 'update' in stateSignal && + typeof stateSignal.set === 'function' && + typeof stateSignal.update === 'function' + ); + }); } export function patchState( stateSource: WritableStateSource, ...updaters: Array< - Partial> | PartialStateUpdater> + | Partial>> + | PartialStateUpdater>> > ): void { - stateSource[STATE_SOURCE].update((currentState) => - updaters.reduce( - (nextState: State, updater) => ({ - ...nextState, - ...(typeof updater === 'function' ? updater(nextState) : updater), - }), - currentState - ) + const currentState = untracked(() => getState(stateSource)); + const newState = updaters.reduce( + (nextState: State, updater) => ({ + ...nextState, + ...(typeof updater === 'function' ? updater(nextState) : updater), + }), + currentState ); + const signals = stateSource[STATE_SOURCE]; + const stateKeys = Reflect.ownKeys(stateSource[STATE_SOURCE]); + for (const key of Reflect.ownKeys(newState)) { + if (!stateKeys.includes(key)) { + // TODO: Optional properties which don't exist in the initial state will not be added + continue; + } + const signalKey = key as keyof State; + signals[signalKey].set(newState[signalKey]); + } + notifyWatchers(stateSource); } export function getState( stateSource: StateSource ): State { - return stateSource[STATE_SOURCE](); + const signals: Record> = stateSource[ + STATE_SOURCE + ]; + return Reflect.ownKeys(stateSource[STATE_SOURCE]).reduce((state, key) => { + const value = signals[key](); + return { + ...state, + [key]: value, + }; + }, {} as State); } export function watchState( diff --git a/modules/signals/src/with-state.ts b/modules/signals/src/with-state.ts index e2ca7aede3..37b3be8025 100644 --- a/modules/signals/src/with-state.ts +++ b/modules/signals/src/with-state.ts @@ -1,7 +1,6 @@ -import { computed } from '@angular/core'; -import { assertUniqueStoreMembers } from './signal-store-assertions'; +import { Signal, signal } from '@angular/core'; import { toDeepSignal } from './deep-signal'; -import { STATE_SOURCE } from './state-source'; +import { assertUniqueStoreMembers } from './signal-store-assertions'; import { EmptyFeatureResult, InnerSignalStore, @@ -9,6 +8,7 @@ import { SignalStoreFeature, SignalStoreFeatureResult, } from './signal-store-models'; +import { STATE_SOURCE } from './state-source'; export function withState( stateFactory: () => State @@ -35,17 +35,17 @@ export function withState( assertUniqueStoreMembers(store, stateKeys); - store[STATE_SOURCE].update((currentState) => ({ - ...currentState, - ...state, - })); - - const stateSignals = stateKeys.reduce((acc, key) => { - const sliceSignal = computed( - () => (store[STATE_SOURCE]() as Record)[key] - ); - return { ...acc, [key]: toDeepSignal(sliceSignal) }; - }, {} as SignalsDictionary); + const stateAsRecord = state as Record; + const stateSource = store[STATE_SOURCE] as Record< + string | symbol, + Signal + >; + const stateSignals = {} as SignalsDictionary; + for (const key of stateKeys) { + const signalValue = stateAsRecord[key]; + stateSource[key] = signal(signalValue); + stateSignals[key] = toDeepSignal(stateSource[key]); + } return { ...store, diff --git a/modules/signals/testing/spec/types/uprotected.types.spec.ts b/modules/signals/testing/spec/types/uprotected.types.spec.ts index 57dea8f1f9..4106421637 100644 --- a/modules/signals/testing/spec/types/uprotected.types.spec.ts +++ b/modules/signals/testing/spec/types/uprotected.types.spec.ts @@ -29,7 +29,7 @@ describe('unprotected', () => { expectSnippet(snippet).toSucceed(); expectSnippet(snippet).toInfer( 'unprotectedStore', - '{ count: Signal; doubleCount: Signal; [STATE_SOURCE]: WritableSignal<{ count: number; }>; }' + '{ count: Signal; doubleCount: Signal; [STATE_SOURCE]: { count: WritableSignal; }; }' ); }); @@ -47,7 +47,7 @@ describe('unprotected', () => { expectSnippet(snippet).toSucceed(); expectSnippet(snippet).toInfer( 'unprotectedStore', - '{ count: Signal; [STATE_SOURCE]: WritableSignal<{ count: number; }>; }' + '{ count: Signal; [STATE_SOURCE]: { count: WritableSignal; }; }' ); }); }); diff --git a/modules/signals/testing/spec/unprotected.spec.ts b/modules/signals/testing/spec/unprotected.spec.ts index d289b270dd..750d7f0975 100644 --- a/modules/signals/testing/spec/unprotected.spec.ts +++ b/modules/signals/testing/spec/unprotected.spec.ts @@ -19,7 +19,7 @@ describe('unprotected', () => { it('throws error when provided state source is not writable', () => { const readonlySource: StateSource<{ count: number }> = { - [STATE_SOURCE]: signal({ count: 0 }).asReadonly(), + [STATE_SOURCE]: { count: signal(0).asReadonly() }, }; expect(() => unprotected(readonlySource)).toThrowError(