@@ -559,6 +559,168 @@ describe("createAdapterStoreModule", () => {
559559 } ) ;
560560 } ) ;
561561
562+ describe ( "retrieveById" , ( ) => {
563+ it ( "should call httpService.getRequest with domainName and id" , async ( ) => {
564+ // Arrange
565+ const httpService : Pick < HttpService , "getRequest" > = { getRequest : vi . fn ( ) } ;
566+ const storageService : TestStorageService = { put : vi . fn ( ) , get : vi . fn ( ) . mockReturnValue ( { } ) } ;
567+ const loadingService : TestLoadingService = {
568+ ensureLoadingFinished : vi . fn ( ) . mockResolvedValue ( undefined ) ,
569+ } ;
570+ const config : AdapterStoreConfig < TestItem , TestAdapted , TestNewAdapted > = {
571+ domainName : "test-items" ,
572+ adapter : createTestAdapter ,
573+ httpService,
574+ storageService,
575+ loadingService,
576+ } ;
577+ vi . mocked ( httpService . getRequest ) . mockResolvedValue ( {
578+ data : {
579+ id : 7 ,
580+ name : "Item 7" ,
581+ createdAt : "2024-01-01T00:00:00Z" ,
582+ updatedAt : "2024-01-01T00:00:00Z" ,
583+ } satisfies TestItem ,
584+ } as AxiosResponse < TestItem > ) ;
585+ const store = createAdapterStoreModule ( config ) ;
586+
587+ // Act
588+ await store . retrieveById ( 7 ) ;
589+
590+ // Assert
591+ expect ( httpService . getRequest ) . toHaveBeenCalledWith ( "test-items/7" ) ;
592+ } ) ;
593+
594+ it ( "should insert the returned item into the store" , async ( ) => {
595+ // Arrange
596+ const httpService : Pick < HttpService , "getRequest" > = { getRequest : vi . fn ( ) } ;
597+ const storageService : TestStorageService = { put : vi . fn ( ) , get : vi . fn ( ) . mockReturnValue ( { } ) } ;
598+ const loadingService : TestLoadingService = {
599+ ensureLoadingFinished : vi . fn ( ) . mockResolvedValue ( undefined ) ,
600+ } ;
601+ const config : AdapterStoreConfig < TestItem , TestAdapted , TestNewAdapted > = {
602+ domainName : "test-items" ,
603+ adapter : createTestAdapter ,
604+ httpService,
605+ storageService,
606+ loadingService,
607+ } ;
608+ vi . mocked ( httpService . getRequest ) . mockResolvedValue ( {
609+ data : {
610+ id : 7 ,
611+ name : "Item 7" ,
612+ createdAt : "2024-01-01T00:00:00Z" ,
613+ updatedAt : "2024-01-01T00:00:00Z" ,
614+ } satisfies TestItem ,
615+ } as AxiosResponse < TestItem > ) ;
616+ const store = createAdapterStoreModule ( config ) ;
617+
618+ // Act
619+ await store . retrieveById ( 7 ) ;
620+
621+ // Assert
622+ expect ( store . getById ( 7 ) . value ?. testMethod ( ) ) . toBe ( "adapted-7" ) ;
623+ } ) ;
624+
625+ it ( "should refresh an existing item's adapted view after re-retrieval" , async ( ) => {
626+ // Arrange
627+ const httpService : Pick < HttpService , "getRequest" > = { getRequest : vi . fn ( ) } ;
628+ const storageService : TestStorageService = { put : vi . fn ( ) , get : vi . fn ( ) . mockReturnValue ( { } ) } ;
629+ const loadingService : TestLoadingService = {
630+ ensureLoadingFinished : vi . fn ( ) . mockResolvedValue ( undefined ) ,
631+ } ;
632+ const config : AdapterStoreConfig < TestItem , TestAdapted , TestNewAdapted > = {
633+ domainName : "test-items" ,
634+ adapter : createTestAdapter ,
635+ httpService,
636+ storageService,
637+ loadingService,
638+ } ;
639+ vi . mocked ( httpService . getRequest ) . mockResolvedValueOnce ( {
640+ data : {
641+ id : 1 ,
642+ name : "Original" ,
643+ createdAt : "2024-01-01T00:00:00Z" ,
644+ updatedAt : "2024-01-01T00:00:00Z" ,
645+ } satisfies TestItem ,
646+ } as AxiosResponse < TestItem > ) ;
647+ const store = createAdapterStoreModule ( config ) ;
648+ await store . retrieveById ( 1 ) ;
649+ expect ( store . getById ( 1 ) . value ?. name ) . toBe ( "Original" ) ;
650+ vi . mocked ( httpService . getRequest ) . mockResolvedValueOnce ( {
651+ data : {
652+ id : 1 ,
653+ name : "Updated" ,
654+ createdAt : "2024-01-01T00:00:00Z" ,
655+ updatedAt : "2024-01-02T00:00:00Z" ,
656+ } satisfies TestItem ,
657+ } as AxiosResponse < TestItem > ) ;
658+
659+ // Act
660+ await store . retrieveById ( 1 ) ;
661+
662+ // Assert
663+ expect ( store . getById ( 1 ) . value ?. name ) . toBe ( "Updated" ) ;
664+ } ) ;
665+
666+ it ( "should persist to storage service" , async ( ) => {
667+ // Arrange
668+ const httpService : Pick < HttpService , "getRequest" > = { getRequest : vi . fn ( ) } ;
669+ const storageService : TestStorageService = { put : vi . fn ( ) , get : vi . fn ( ) . mockReturnValue ( { } ) } ;
670+ const loadingService : TestLoadingService = {
671+ ensureLoadingFinished : vi . fn ( ) . mockResolvedValue ( undefined ) ,
672+ } ;
673+ const config : AdapterStoreConfig < TestItem , TestAdapted , TestNewAdapted > = {
674+ domainName : "test-items" ,
675+ adapter : createTestAdapter ,
676+ httpService,
677+ storageService,
678+ loadingService,
679+ } ;
680+ vi . mocked ( httpService . getRequest ) . mockResolvedValue ( {
681+ data : {
682+ id : 3 ,
683+ name : "Item 3" ,
684+ createdAt : "2024-01-01T00:00:00Z" ,
685+ updatedAt : "2024-01-01T00:00:00Z" ,
686+ } satisfies TestItem ,
687+ } as AxiosResponse < TestItem > ) ;
688+ const store = createAdapterStoreModule ( config ) ;
689+
690+ // Act
691+ await store . retrieveById ( 3 ) ;
692+
693+ // Assert
694+ expect ( storageService . put ) . toHaveBeenCalledWith (
695+ "test-items" ,
696+ expect . objectContaining ( { 3 : expect . any ( Object ) as unknown } ) ,
697+ ) ;
698+ } ) ;
699+
700+ it ( "should propagate http errors and leave state untouched" , async ( ) => {
701+ // Arrange
702+ const httpService : Pick < HttpService , "getRequest" > = { getRequest : vi . fn ( ) } ;
703+ const storageService : TestStorageService = { put : vi . fn ( ) , get : vi . fn ( ) . mockReturnValue ( { } ) } ;
704+ const loadingService : TestLoadingService = {
705+ ensureLoadingFinished : vi . fn ( ) . mockResolvedValue ( undefined ) ,
706+ } ;
707+ const config : AdapterStoreConfig < TestItem , TestAdapted , TestNewAdapted > = {
708+ domainName : "test-items" ,
709+ adapter : createTestAdapter ,
710+ httpService,
711+ storageService,
712+ loadingService,
713+ } ;
714+ vi . mocked ( httpService . getRequest ) . mockRejectedValue ( new Error ( "network down" ) ) ;
715+ const store = createAdapterStoreModule ( config ) ;
716+
717+ // Act & Assert
718+ await expect ( store . retrieveById ( 1 ) ) . rejects . toThrow ( "network down" ) ;
719+ expect ( store . getById ( 1 ) . value ) . toBeUndefined ( ) ;
720+ expect ( storageService . put ) . not . toHaveBeenCalled ( ) ;
721+ } ) ;
722+ } ) ;
723+
562724 describe ( "retrieveAll" , ( ) => {
563725 it ( "should call httpService.getRequest with domainName" , async ( ) => {
564726 // Arrange
@@ -1101,6 +1263,59 @@ describe("createAdapterStoreModule", () => {
11011263 expect ( refBefore ) . not . toBe ( refAfter ) ;
11021264 } ) ;
11031265
1266+ it ( "should reuse cached adapted entries for untouched ids when state changes for a different id" , async ( ) => {
1267+ // Arrange
1268+ const httpService : Pick < HttpService , "getRequest" > = { getRequest : vi . fn ( ) } ;
1269+ const storageService : TestStorageService = { put : vi . fn ( ) , get : vi . fn ( ) . mockReturnValue ( { } ) } ;
1270+ const loadingService : TestLoadingService = {
1271+ ensureLoadingFinished : vi . fn ( ) . mockResolvedValue ( undefined ) ,
1272+ } ;
1273+ const { adapter, getCapturedStoreModule } = createCapturingAdapter ( ) ;
1274+ const config : AdapterStoreConfig < TestItem , TestAdapted , TestNewAdapted > = {
1275+ domainName : "test-items" ,
1276+ adapter,
1277+ httpService,
1278+ storageService,
1279+ loadingService,
1280+ } ;
1281+ const items : TestItem [ ] = [
1282+ {
1283+ id : 1 ,
1284+ name : "Item 1" ,
1285+ createdAt : "2024-01-01T00:00:00Z" ,
1286+ updatedAt : "2024-01-01T00:00:00Z" ,
1287+ } ,
1288+ {
1289+ id : 2 ,
1290+ name : "Item 2" ,
1291+ createdAt : "2024-01-01T00:00:00Z" ,
1292+ updatedAt : "2024-01-01T00:00:00Z" ,
1293+ } ,
1294+ ] ;
1295+ vi . mocked ( httpService . getRequest ) . mockResolvedValue ( { data : items } as AxiosResponse <
1296+ TestItem [ ]
1297+ > ) ;
1298+ const store = createAdapterStoreModule ( config ) ;
1299+ await store . retrieveAll ( ) ;
1300+ const itemOneBefore = store . getById ( 1 ) . value ;
1301+ const itemTwoBefore = store . getById ( 2 ) . value ;
1302+ expect ( itemOneBefore ) . toBeDefined ( ) ;
1303+ expect ( itemTwoBefore ) . toBeDefined ( ) ;
1304+
1305+ // Act — setById for id 2 only. Clears adaptedCache for 2; id 1 remains cached.
1306+ const storeModule = getCapturedStoreModule ( ) as unknown as AdapterStoreModule < TestItem > ;
1307+ storeModule . setById ( {
1308+ id : 2 ,
1309+ name : "Item 2 Updated" ,
1310+ createdAt : "2024-01-01T00:00:00Z" ,
1311+ updatedAt : "2024-01-02T00:00:00Z" ,
1312+ } ) ;
1313+
1314+ // Assert — id 1 returns the same cached adapted reference; id 2 is freshly adapted.
1315+ expect ( store . getById ( 1 ) . value ) . toBe ( itemOneBefore ) ;
1316+ expect ( store . getById ( 2 ) . value ) . not . toBe ( itemTwoBefore ) ;
1317+ } ) ;
1318+
11041319 it ( "should return cached adapted object via getById when state has not changed" , async ( ) => {
11051320 // Arrange
11061321 const httpService : Pick < HttpService , "getRequest" > = { getRequest : vi . fn ( ) } ;
0 commit comments