Skip to content

Commit e07f738

Browse files
CopilotLayZeeDK
andcommitted
Enhance injectTestingRouterStore with InjectOptions and component injector support
Co-authored-by: LayZeeDK <6364586+LayZeeDK@users.noreply.github.com>
1 parent 9668fcf commit e07f738

4 files changed

Lines changed: 382 additions & 42 deletions

File tree

packages/router-component-store/README.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,9 @@ describe('HeroService', () => {
326326

327327
#### Injection helper
328328

329-
The `injectTestingRouterStore()` function provides a convenient way to inject the testing router store without manual casting:
329+
The `injectTestingRouterStore()` function provides a convenient way to inject the testing router store without manual casting. It supports injection options similar to Angular's `inject()` function and can also inject from specific component injectors for local router stores.
330+
331+
##### Basic usage
330332

331333
```typescript
332334
import { injectTestingRouterStore } from '@ngworker/router-component-store';
@@ -346,6 +348,46 @@ TestBed.runInInjectionContext(() => {
346348
});
347349
```
348350

351+
##### With injection options
352+
353+
```typescript
354+
TestBed.runInInjectionContext(() => {
355+
// With injection options (optional, skipSelf, self, host)
356+
const routerStore = injectTestingRouterStore({
357+
optional: true,
358+
host: true
359+
});
360+
routerStore?.setRouteParam('id', '123');
361+
});
362+
```
363+
364+
##### For local router stores
365+
366+
```typescript
367+
// When testing components with local router store providers
368+
@Component({
369+
template: '<p>Hero: {{ heroId$ | async }}</p>',
370+
providers: [provideTestingRouterStore()], // Local provider
371+
})
372+
class HeroComponent {
373+
private routerStore = inject(RouterStore);
374+
heroId$ = this.routerStore.selectRouteParam('id');
375+
}
376+
377+
// In your test
378+
const fixture = TestBed.createComponent(ParentComponent);
379+
380+
// Inject from the specific component's injector
381+
const routerStore = injectTestingRouterStore({
382+
component: HeroComponent,
383+
fixture,
384+
options: { host: true } // Optional injection options
385+
});
386+
387+
routerStore.setRouteParam('id', '123');
388+
fixture.detectChanges();
389+
```
390+
349391
**Note:** The `injectTestingRouterStore()` function should only be used when `provideTestingRouterStore()` is provided in the testing module. It must be called within an injection context (e.g., inside `TestBed.runInInjectionContext()` or within a component/service constructor).
350392

351393
#### Available testing methods

packages/router-component-store/src/lib/testing/inject-testing-router-store.example.spec.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,7 @@
1-
import { Component, inject } from '@angular/core';
21
import { TestBed } from '@angular/core/testing';
32
import { RouterStore } from '../router-store';
43
import { injectTestingRouterStore, provideTestingRouterStore, TestingRouterStore } from '../testing';
54

6-
// Example showing the improvement in developer experience
7-
@Component({
8-
standalone: true,
9-
selector: 'ngw-example-usage',
10-
template: '<p>Route param: {{ routeParam$ | async }}</p>',
11-
})
12-
class ExampleComponent {
13-
private routerStore = inject(RouterStore);
14-
routeParam$ = this.routerStore.selectRouteParam('id');
15-
}
16-
175
describe('injectTestingRouterStore - Usage Examples', () => {
186
beforeEach(() => {
197
TestBed.configureTestingModule({

packages/router-component-store/src/lib/testing/testing-router-store.spec.ts

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,4 +488,223 @@ describe('injectTestingRouterStore', () => {
488488
// the injection works by checking the component renders properly
489489
expect(fixture.nativeElement.querySelector('div')).toBeTruthy();
490490
});
491+
});
492+
493+
describe('injectTestingRouterStore - Enhanced Options', () => {
494+
beforeEach(() => {
495+
TestBed.configureTestingModule({
496+
providers: [provideTestingRouterStore()],
497+
});
498+
});
499+
500+
describe('injection options', () => {
501+
it('should support optional injection', () => {
502+
TestBed.runInInjectionContext(() => {
503+
// With optional: false (default behavior)
504+
const routerStore = injectTestingRouterStore();
505+
expect(routerStore).toBeInstanceOf(TestingRouterStore);
506+
507+
// With optional: true - should still work since TestingRouterStore is provided
508+
const optionalStore = injectTestingRouterStore({ optional: true });
509+
expect(optionalStore).toBeInstanceOf(TestingRouterStore);
510+
});
511+
});
512+
513+
it('should support host injection option', () => {
514+
TestBed.runInInjectionContext(() => {
515+
const routerStore = injectTestingRouterStore({ host: true });
516+
expect(routerStore).toBeInstanceOf(TestingRouterStore);
517+
expect(typeof routerStore.setRouteParam).toBe('function');
518+
});
519+
});
520+
521+
it('should support self injection option', () => {
522+
TestBed.runInInjectionContext(() => {
523+
const routerStore = injectTestingRouterStore({ self: true });
524+
expect(routerStore).toBeInstanceOf(TestingRouterStore);
525+
expect(typeof routerStore.setUrl).toBe('function');
526+
});
527+
});
528+
529+
it('should support skipSelf injection option', () => {
530+
TestBed.runInInjectionContext(() => {
531+
const routerStore = injectTestingRouterStore({ skipSelf: false });
532+
expect(routerStore).toBeInstanceOf(TestingRouterStore);
533+
expect(typeof routerStore.reset).toBe('function');
534+
});
535+
});
536+
537+
it('should support combined injection options', () => {
538+
TestBed.runInInjectionContext(() => {
539+
const routerStore = injectTestingRouterStore({
540+
optional: false,
541+
host: true,
542+
self: false,
543+
});
544+
expect(routerStore).toBeInstanceOf(TestingRouterStore);
545+
});
546+
});
547+
});
548+
549+
describe('component injector support', () => {
550+
@Component({
551+
standalone: true,
552+
selector: 'ngw-test-with-local',
553+
template: '<p>Test Component</p>',
554+
providers: [provideTestingRouterStore()], // Local provider
555+
})
556+
class TestComponentWithLocalStoreComponent {}
557+
558+
@Component({
559+
standalone: true,
560+
template: '<ngw-test-with-local></ngw-test-with-local>',
561+
imports: [TestComponentWithLocalStoreComponent],
562+
})
563+
class TestParentComponent {}
564+
565+
it('should inject from specific component injector', () => {
566+
TestBed.configureTestingModule({
567+
imports: [TestComponentWithLocalStoreComponent, TestParentComponent],
568+
});
569+
570+
const fixture = TestBed.createComponent(TestParentComponent);
571+
fixture.detectChanges();
572+
573+
const routerStore = injectTestingRouterStore({
574+
component: TestComponentWithLocalStoreComponent,
575+
fixture,
576+
});
577+
578+
expect(routerStore).toBeInstanceOf(TestingRouterStore);
579+
expect(typeof routerStore.setRouteParam).toBe('function');
580+
581+
// Test that we can use the injected store
582+
routerStore.setRouteParam('test', 'value');
583+
expect(routerStore).toBeDefined();
584+
});
585+
586+
it('should inject from component with options', () => {
587+
TestBed.configureTestingModule({
588+
imports: [TestComponentWithLocalStoreComponent, TestParentComponent],
589+
});
590+
591+
const fixture = TestBed.createComponent(TestParentComponent);
592+
fixture.detectChanges();
593+
594+
const routerStore = injectTestingRouterStore({
595+
component: TestComponentWithLocalStoreComponent,
596+
fixture,
597+
options: { host: true },
598+
});
599+
600+
expect(routerStore).toBeInstanceOf(TestingRouterStore);
601+
routerStore.setQueryParam('search', 'test');
602+
expect(routerStore).toBeDefined();
603+
});
604+
605+
it('should throw error if component not found in fixture', () => {
606+
@Component({
607+
standalone: true,
608+
template: '<p>Different Component</p>',
609+
})
610+
class DifferentComponent {}
611+
612+
TestBed.configureTestingModule({
613+
imports: [DifferentComponent],
614+
});
615+
616+
const fixture = TestBed.createComponent(DifferentComponent);
617+
618+
expect(() => {
619+
injectTestingRouterStore({
620+
component: TestComponentWithLocalStoreComponent, // This component is not in the fixture
621+
fixture,
622+
});
623+
}).toThrow('Component TestComponentWithLocalStoreComponent not found in fixture');
624+
});
625+
});
626+
});
627+
628+
describe('injectTestingRouterStore - Real-world Usage', () => {
629+
@Component({
630+
standalone: true,
631+
selector: 'ngw-hero-detail',
632+
template: `
633+
<div class="hero-detail">
634+
<h1>Hero: {{ heroId$ | async }}</h1>
635+
<p>Search: {{ search$ | async }}</p>
636+
</div>
637+
`,
638+
imports: [AsyncPipe],
639+
providers: [provideTestingRouterStore()], // Local testing store
640+
})
641+
class HeroDetailComponent {
642+
private routerStore = inject(RouterStore);
643+
heroId$ = this.routerStore.selectRouteParam('id');
644+
search$ = this.routerStore.selectQueryParam('search');
645+
}
646+
647+
@Component({
648+
standalone: true,
649+
template: '<ngw-hero-detail></ngw-hero-detail>',
650+
imports: [HeroDetailComponent],
651+
})
652+
class AppComponent {}
653+
654+
it('should work with local router store in real component', () => {
655+
TestBed.configureTestingModule({
656+
imports: [AppComponent],
657+
});
658+
659+
const fixture = TestBed.createComponent(AppComponent);
660+
fixture.detectChanges();
661+
662+
// Inject from the specific component's injector
663+
const routerStore = injectTestingRouterStore({
664+
component: HeroDetailComponent,
665+
fixture,
666+
});
667+
668+
// Set up test data
669+
routerStore.setRouteParam('id', 'superman');
670+
routerStore.setQueryParam('search', 'hero');
671+
672+
fixture.detectChanges();
673+
674+
const compiled = fixture.nativeElement;
675+
expect(compiled.textContent).toContain('Hero: superman');
676+
expect(compiled.textContent).toContain('Search: hero');
677+
});
678+
679+
it('should demonstrate different injection strategies', () => {
680+
TestBed.configureTestingModule({
681+
imports: [AppComponent],
682+
providers: [provideTestingRouterStore()], // Global testing store
683+
});
684+
685+
const fixture = TestBed.createComponent(AppComponent);
686+
fixture.detectChanges();
687+
688+
// Strategy 1: Use global testing store with injection context
689+
TestBed.runInInjectionContext(() => {
690+
const globalStore = injectTestingRouterStore();
691+
globalStore.setUrl('/global/test');
692+
expect(globalStore.url$).toBeDefined();
693+
});
694+
695+
// Strategy 2: Use local component store
696+
const localStore = injectTestingRouterStore({
697+
component: HeroDetailComponent,
698+
fixture,
699+
options: { host: true },
700+
});
701+
702+
localStore.setRouteParam('id', 'batman');
703+
localStore.setQueryParam('search', 'dark knight');
704+
705+
fixture.detectChanges();
706+
707+
expect(fixture.nativeElement.textContent).toContain('Hero: batman');
708+
expect(fixture.nativeElement.textContent).toContain('Search: dark knight');
709+
});
491710
});

0 commit comments

Comments
 (0)