@@ -3,7 +3,7 @@ import { Component, Injectable, inject } from '@angular/core';
33import { TestBed } from '@angular/core/testing' ;
44import { BehaviorSubject , from } from 'rxjs' ;
55import { writable as svelteWritable } from 'svelte/store' ;
6- import { afterAll , afterEach , beforeAll , describe , expect , it , vi } from 'vitest' ;
6+ import { afterAll , afterEach , beforeAll , describe , expect , it , test , vi } from 'vitest' ;
77import type {
88 OnUseArgument ,
99 Readable ,
@@ -30,10 +30,10 @@ import {
3030 untrack ,
3131 writable ,
3232} from './index' ;
33- import { rawStoreSymbol } from './internal/exposeRawStores' ;
34- import { RawStoreFlags } from './internal/store' ;
33+ import { RawStoreFlags , rawStoreSymbol } from './internal/store' ;
3534import { flushUnused } from './internal/storeTrackingUsage' ;
3635import type { RawStoreWritable } from './internal/storeWritable' ;
36+ import { runWithConsumer , type Signal , watchSignal , type Watcher } from './interop' ;
3737
3838const expectCorrectlyCleanedUp = < T > ( store : StoreInput < T > ) => {
3939 const rawStore = ( store as any ) [ rawStoreSymbol ] as RawStoreWritable < T > ;
@@ -3565,4 +3565,257 @@ describe('stores', () => {
35653565 expectCorrectlyCleanedUp ( doubleDoubleDoubleStore ) ;
35663566 } ) ;
35673567 } ) ;
3568+
3569+ describe ( 'watch' , ( ) => {
3570+ it ( 'should work' , ( ) => {
3571+ const onUseCalls : { onUnused : number } [ ] = [ ] ;
3572+ const store = writable ( 0 , {
3573+ onUse : ( ) => {
3574+ const call = { onUnused : 0 } ;
3575+ onUseCalls . push ( call ) ;
3576+ return ( ) => {
3577+ call . onUnused ++ ;
3578+ } ;
3579+ } ,
3580+ } ) ;
3581+ const notify = vi . fn ( ) ;
3582+ const watcher = store [ watchSignal ] ( notify ) ;
3583+ expect ( watcher . isUpToDate ( ) ) . toBe ( false ) ;
3584+ expect ( onUseCalls . length ) . toBe ( 0 ) ;
3585+ expect ( watcher . update ( ) ) . toBe ( true ) ;
3586+ expect ( onUseCalls . length ) . toBe ( 1 ) ;
3587+ expect ( onUseCalls [ 0 ] . onUnused ) . toBe ( 0 ) ;
3588+ expect ( watcher . isUpToDate ( ) ) . toBe ( true ) ;
3589+ expect ( watcher . get ( ) ) . toBe ( 0 ) ;
3590+ expect ( notify ) . not . toHaveBeenCalled ( ) ;
3591+ store . set ( 1 ) ;
3592+ expect ( notify ) . toHaveBeenCalledOnce ( ) ;
3593+ notify . mockClear ( ) ;
3594+ expect ( watcher . isUpToDate ( ) ) . toBe ( false ) ;
3595+ store . set ( 2 ) ;
3596+ expect ( notify ) . not . toHaveBeenCalled ( ) ;
3597+ expect ( watcher . update ( ) ) . toBe ( true ) ;
3598+ expect ( watcher . isUpToDate ( ) ) . toBe ( true ) ;
3599+ expect ( watcher . get ( ) ) . toBe ( 2 ) ;
3600+ expect ( notify ) . not . toHaveBeenCalled ( ) ;
3601+ store . set ( 3 ) ;
3602+ expect ( notify ) . toHaveBeenCalledOnce ( ) ;
3603+ expect ( watcher . isUpToDate ( ) ) . toBe ( false ) ;
3604+ notify . mockClear ( ) ;
3605+ store . set ( 4 ) ;
3606+ store . set ( 2 ) ;
3607+ expect ( notify ) . not . toHaveBeenCalled ( ) ;
3608+ expect ( watcher . update ( ) ) . toBe ( false ) ;
3609+ expect ( watcher . isUpToDate ( ) ) . toBe ( true ) ;
3610+ expect ( watcher . update ( ) ) . toBe ( false ) ;
3611+ expect ( watcher . get ( ) ) . toBe ( 2 ) ;
3612+ watcher . destroy ( ) ;
3613+ expect ( ( ) => watcher . get ( ) ) . toThrowError ( 'invalid watcher state' ) ;
3614+ } ) ;
3615+ } ) ;
3616+
3617+ describe ( 'fromWatch' , ( ) => {
3618+ it ( 'should work' , ( ) => {
3619+ let notify : ( ) => void ;
3620+ let isUpdated = true ;
3621+ let value = 0 ;
3622+ const watcher = {
3623+ isUpToDate : vi . fn ( ( ) => {
3624+ throw new Error ( 'unexpected call to isUpToDate' ) ;
3625+ } ) ,
3626+ update : vi . fn ( ( ) => isUpdated ) ,
3627+ get : vi . fn ( ( ) => value ) ,
3628+ destroy : vi . fn ( ) ,
3629+ } satisfies Watcher < number > ;
3630+ const watchFn = vi . fn ( ( notifyFn : ( ) => void ) => {
3631+ notify = notifyFn ;
3632+ return watcher ;
3633+ } ) satisfies ( notify : ( ) => void ) => Watcher < number > ;
3634+ const clearMocks = ( ) => {
3635+ watchFn . mockClear ( ) ;
3636+ watcher . update . mockClear ( ) ;
3637+ watcher . get . mockClear ( ) ;
3638+ watcher . destroy . mockClear ( ) ;
3639+ } ;
3640+ const store = asReadable ( { [ watchSignal ] : watchFn } ) ;
3641+ expect ( watchFn ) . not . toHaveBeenCalled ( ) ;
3642+ expect ( store . get ( ) ) . toBe ( 0 ) ;
3643+ expect ( watchFn ) . toHaveBeenCalledOnce ( ) ;
3644+ expect ( watcher . update ) . toHaveBeenCalledOnce ( ) ;
3645+ expect ( watcher . get ) . toHaveBeenCalledOnce ( ) ;
3646+ expect ( watcher . destroy ) . toHaveBeenCalledOnce ( ) ;
3647+ clearMocks ( ) ;
3648+ const values : number [ ] = [ ] ;
3649+ const unsubscribe = store . subscribe ( ( value ) => {
3650+ values . push ( value ) ;
3651+ } ) ;
3652+ expect ( values ) . toEqual ( [ 0 ] ) ;
3653+ expect ( watchFn ) . toHaveBeenCalledOnce ( ) ;
3654+ expect ( watcher . update ) . toHaveBeenCalledOnce ( ) ;
3655+ expect ( watcher . get ) . toHaveBeenCalledOnce ( ) ;
3656+ expect ( watcher . destroy ) . not . toHaveBeenCalled ( ) ;
3657+ clearMocks ( ) ;
3658+ isUpdated = false ;
3659+ batch ( ( ) => {
3660+ notify ! ( ) ;
3661+ } ) ;
3662+ expect ( watcher . update ) . toHaveBeenCalledOnce ( ) ;
3663+ expect ( watchFn ) . not . toHaveBeenCalled ( ) ;
3664+ expect ( watcher . get ) . not . toHaveBeenCalled ( ) ;
3665+ expect ( watcher . destroy ) . not . toHaveBeenCalled ( ) ;
3666+ clearMocks ( ) ;
3667+ expect ( values ) . toEqual ( [ 0 ] ) ;
3668+ isUpdated = true ;
3669+ value = 2 ;
3670+ batch ( ( ) => {
3671+ notify ! ( ) ;
3672+ } ) ;
3673+ expect ( watcher . update ) . toHaveBeenCalledOnce ( ) ;
3674+ expect ( watcher . get ) . toHaveBeenCalledOnce ( ) ;
3675+ expect ( watcher . destroy ) . not . toHaveBeenCalled ( ) ;
3676+ expect ( watchFn ) . not . toHaveBeenCalled ( ) ;
3677+ clearMocks ( ) ;
3678+ expect ( values ) . toEqual ( [ 0 , 2 ] ) ;
3679+ isUpdated = true ;
3680+ watcher . get . mockImplementation ( ( ) => {
3681+ throw new Error ( 'myerror' ) ;
3682+ } ) ;
3683+ expect ( ( ) => {
3684+ batch ( ( ) => {
3685+ notify ! ( ) ;
3686+ } ) ;
3687+ } ) . toThrowError ( 'myerror' ) ;
3688+ expect ( watcher . update ) . toHaveBeenCalledOnce ( ) ;
3689+ expect ( watcher . get ) . toHaveBeenCalledOnce ( ) ;
3690+ expect ( watcher . destroy ) . not . toHaveBeenCalled ( ) ;
3691+ expect ( watchFn ) . not . toHaveBeenCalled ( ) ;
3692+ clearMocks ( ) ;
3693+ expect ( ( ) => {
3694+ store . get ( ) ;
3695+ } ) . toThrowError ( 'myerror' ) ;
3696+ expect ( values ) . toEqual ( [ 0 , 2 ] ) ;
3697+ expect ( watcher . update ) . not . toHaveBeenCalled ( ) ;
3698+ expect ( watcher . get ) . not . toHaveBeenCalled ( ) ;
3699+ expect ( watcher . destroy ) . not . toHaveBeenCalled ( ) ;
3700+ expect ( watchFn ) . not . toHaveBeenCalled ( ) ;
3701+ unsubscribe ( ) ;
3702+ expect ( watcher . destroy ) . toHaveBeenCalledOnce ( ) ;
3703+ expect ( watcher . update ) . not . toHaveBeenCalled ( ) ;
3704+ expect ( watcher . get ) . not . toHaveBeenCalled ( ) ;
3705+ expect ( watchFn ) . not . toHaveBeenCalled ( ) ;
3706+ expect ( watcher . isUpToDate ) . not . toHaveBeenCalled ( ) ;
3707+ } ) ;
3708+ } ) ;
3709+
3710+ describe ( 'watch / fromWatch' , ( ) => {
3711+ it ( 'should work to convert back and forth a basic writable' , ( ) => {
3712+ const store = writable ( 0 ) ;
3713+ const otherStore = asReadable ( { [ watchSignal ] : ( notify ) => store [ watchSignal ] ( notify ) } ) ;
3714+ expect ( otherStore ( ) ) . toBe ( 0 ) ;
3715+ store . set ( 1 ) ;
3716+ expect ( otherStore ( ) ) . toBe ( 1 ) ;
3717+ store . set ( 2 ) ;
3718+ expect ( otherStore ( ) ) . toBe ( 2 ) ;
3719+
3720+ const values : number [ ] = [ ] ;
3721+ const unsubscribe = otherStore . subscribe ( ( value ) => {
3722+ values . push ( value ) ;
3723+ } ) ;
3724+ expect ( values ) . toEqual ( [ 2 ] ) ;
3725+ store . set ( 3 ) ;
3726+ expect ( values ) . toEqual ( [ 2 , 3 ] ) ;
3727+ expect ( otherStore ( ) ) . toBe ( 3 ) ;
3728+ batch ( ( ) => {
3729+ store . set ( 4 ) ;
3730+ expect ( otherStore ( ) ) . toBe ( 4 ) ;
3731+ store . set ( 5 ) ;
3732+ store . set ( 3 ) ;
3733+ } ) ;
3734+ expect ( values ) . toEqual ( [ 2 , 3 ] ) ;
3735+ unsubscribe ( ) ;
3736+ } ) ;
3737+
3738+ it ( 'should work to convert back and forth a basic Store' , ( ) => {
3739+ class MyStore extends Store < number > {
3740+ increase ( ) {
3741+ this . update ( ( value ) => value + 1 ) ;
3742+ }
3743+ }
3744+ const store = new MyStore ( 0 ) ;
3745+
3746+ const otherStore = asReadable ( { [ watchSignal ] : ( notify ) => store [ watchSignal ] ( notify ) } ) ;
3747+ expect ( otherStore ( ) ) . toBe ( 0 ) ;
3748+ store . increase ( ) ;
3749+ expect ( otherStore ( ) ) . toBe ( 1 ) ;
3750+ store . increase ( ) ;
3751+ expect ( otherStore ( ) ) . toBe ( 2 ) ;
3752+
3753+ const values : number [ ] = [ ] ;
3754+ const unsubscribe = otherStore . subscribe ( ( value ) => {
3755+ values . push ( value ) ;
3756+ } ) ;
3757+ expect ( values ) . toEqual ( [ 2 ] ) ;
3758+ store . increase ( ) ;
3759+ expect ( values ) . toEqual ( [ 2 , 3 ] ) ;
3760+ expect ( otherStore ( ) ) . toBe ( 3 ) ;
3761+ batch ( ( ) => {
3762+ store . increase ( ) ;
3763+ expect ( otherStore ( ) ) . toBe ( 4 ) ;
3764+ store . increase ( ) ;
3765+ store . increase ( ) ;
3766+ } ) ;
3767+ expect ( values ) . toEqual ( [ 2 , 3 , 6 ] ) ;
3768+ unsubscribe ( ) ;
3769+ } ) ;
3770+
3771+ it ( 'should work to convert back and forth a computed' , ( ) => {
3772+ const store = writable ( 0 ) ;
3773+ const doubleStore = computed ( ( ) => store ( ) * 2 ) ;
3774+ const otherStore = asReadable ( {
3775+ [ watchSignal ] : ( notify ) => doubleStore [ watchSignal ] ( notify ) ,
3776+ } ) ;
3777+ expect ( otherStore ( ) ) . toBe ( 0 ) ;
3778+ store . set ( 1 ) ;
3779+ expect ( otherStore ( ) ) . toBe ( 2 ) ;
3780+ store . set ( 2 ) ;
3781+ expect ( otherStore ( ) ) . toBe ( 4 ) ;
3782+
3783+ const values : number [ ] = [ ] ;
3784+ const unsubscribe = otherStore . subscribe ( ( value ) => {
3785+ values . push ( value ) ;
3786+ } ) ;
3787+ expect ( values ) . toEqual ( [ 4 ] ) ;
3788+ store . set ( 3 ) ;
3789+ expect ( values ) . toEqual ( [ 4 , 6 ] ) ;
3790+ expect ( otherStore ( ) ) . toBe ( 6 ) ;
3791+ batch ( ( ) => {
3792+ store . set ( 4 ) ;
3793+ expect ( otherStore ( ) ) . toBe ( 8 ) ;
3794+ store . set ( 5 ) ;
3795+ store . set ( 3 ) ;
3796+ } ) ;
3797+ expect ( values ) . toEqual ( [ 4 , 6 ] ) ;
3798+ unsubscribe ( ) ;
3799+ } ) ;
3800+ } ) ;
3801+
3802+ describe ( 'interop computed' , ( ) => {
3803+ test ( 'should work with a computed from another library' , ( ) => {
3804+ const a = writable ( 0 ) ;
3805+ const notify = vi . fn ( ) ;
3806+ const watchers : Watcher < any > [ ] = [ ] ;
3807+ const consumer = vi . fn ( < T > ( signal : Signal < T > ) => {
3808+ const watcher = signal [ watchSignal ] ( notify ) ;
3809+ watchers . push ( watcher ) ;
3810+ watcher . update ( ) ;
3811+ } ) ;
3812+ const computeValue = ( ) => runWithConsumer ( ( ) => 2 * a ( ) , consumer ) ;
3813+ expect ( computeValue ( ) ) . toBe ( 0 ) ;
3814+ expect ( consumer ) . toHaveBeenCalledOnce ( ) ;
3815+ expect ( notify ) . not . toHaveBeenCalled ( ) ;
3816+ a . set ( 1 ) ;
3817+ expect ( notify ) . toHaveBeenCalledOnce ( ) ;
3818+ expect ( watchers ) . toHaveLength ( 1 ) ;
3819+ } ) ;
3820+ } ) ;
35683821} ) ;
0 commit comments