import { TestBed } from '@angular/core/testing'; import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; import { signal } from '@angular/core'; import { NavigationContextService } from './navigation-context.service'; import { TabService } from '@isa/core/tabs'; import { ReturnUrlContext } from './navigation-context.types'; import { NAVIGATION_CONTEXT_METADATA_KEY } from './navigation-context.constants'; describe('NavigationContextService', () => { let service: NavigationContextService; let tabServiceMock: { activatedTabId: ReturnType>; entityMap: ReturnType; patchTabMetadata: ReturnType; }; beforeEach(() => { // Create mock TabService with signals and methods tabServiceMock = { activatedTabId: signal(null), entityMap: vi.fn(), patchTabMetadata: vi.fn(), }; TestBed.configureTestingModule({ providers: [ NavigationContextService, { provide: TabService, useValue: tabServiceMock }, ], }); service = TestBed.inject(NavigationContextService); }); afterEach(() => { vi.clearAllMocks(); }); it('should be created', () => { expect(service).toBeTruthy(); }); describe('setContext', () => { it('should set context in tab metadata', async () => { // Arrange const tabId = 123; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: {}, }, }); const data: ReturnUrlContext = { returnUrl: '/test-page' }; // Act await service.setContext(data); // Assert expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith( tabId, expect.objectContaining({ [NAVIGATION_CONTEXT_METADATA_KEY]: expect.objectContaining({ default: expect.objectContaining({ data, createdAt: expect.any(Number), }), }), }), ); }); it('should set context with custom scope', async () => { // Arrange const tabId = 123; const customScope = 'customer-details'; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: {}, }, }); const data = { customerId: 42 }; // Act await service.setContext(data, customScope); // Assert expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith( tabId, expect.objectContaining({ [NAVIGATION_CONTEXT_METADATA_KEY]: expect.objectContaining({ [customScope]: expect.objectContaining({ data, createdAt: expect.any(Number), }), }), }), ); }); it('should throw error when no active tab', async () => { // Arrange tabServiceMock.activatedTabId.set(null); // Act & Assert await expect(service.setContext({ returnUrl: '/test' })).rejects.toThrow( 'No active tab - cannot set navigation context', ); }); it('should merge with existing contexts', async () => { // Arrange const tabId = 123; tabServiceMock.activatedTabId.set(tabId); const existingContexts = { 'existing-scope': { data: { existingData: 'value' }, createdAt: Date.now() - 1000, }, }; tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: { [NAVIGATION_CONTEXT_METADATA_KEY]: existingContexts, }, }, }); const newData = { returnUrl: '/new-page' }; // Act await service.setContext(newData); // Assert expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith( tabId, expect.objectContaining({ [NAVIGATION_CONTEXT_METADATA_KEY]: expect.objectContaining({ 'existing-scope': existingContexts['existing-scope'], default: expect.objectContaining({ data: newData, }), }), }), ); }); it('should accept TTL parameter for backward compatibility', async () => { // Arrange const tabId = 123; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: {}, }, }); // Act await service.setContext({ returnUrl: '/test' }, undefined, 60000); // Assert - TTL is ignored but method should still work expect(tabServiceMock.patchTabMetadata).toHaveBeenCalled(); }); }); describe('getContext', () => { it('should return null when no active tab', async () => { // Arrange tabServiceMock.activatedTabId.set(null); // Act const result = await service.getContext(); // Assert expect(result).toBeNull(); }); it('should return null when context does not exist', async () => { // Arrange const tabId = 123; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: {}, }, }); // Act const result = await service.getContext(); // Assert expect(result).toBeNull(); }); it('should retrieve context from default scope', async () => { // Arrange const tabId = 123; const data: ReturnUrlContext = { returnUrl: '/test-page' }; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: { [NAVIGATION_CONTEXT_METADATA_KEY]: { default: { data, createdAt: Date.now(), }, }, }, }, }); // Act const result = await service.getContext(); // Assert expect(result).toEqual(data); }); it('should retrieve context from custom scope', async () => { // Arrange const tabId = 123; const customScope = 'checkout-flow'; const data = { step: 2, productId: 456 }; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: { [NAVIGATION_CONTEXT_METADATA_KEY]: { [customScope]: { data, createdAt: Date.now(), }, }, }, }, }); // Act const result = await service.getContext(customScope); // Assert expect(result).toEqual(data); }); it('should return null when tab not found', async () => { // Arrange const tabId = 123; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({}); // Act const result = await service.getContext(); // Assert expect(result).toBeNull(); }); it('should handle invalid metadata gracefully', async () => { // Arrange const tabId = 123; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: { [NAVIGATION_CONTEXT_METADATA_KEY]: 'invalid', // Invalid type }, }, }); // Act const result = await service.getContext(); // Assert expect(result).toBeNull(); }); }); describe('getAndClearContext', () => { it('should return null when no active tab', async () => { // Arrange tabServiceMock.activatedTabId.set(null); // Act const result = await service.getAndClearContext(); // Assert expect(result).toBeNull(); }); it('should retrieve and remove context from default scope', async () => { // Arrange const tabId = 123; const data: ReturnUrlContext = { returnUrl: '/test-page' }; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: { [NAVIGATION_CONTEXT_METADATA_KEY]: { default: { data, createdAt: Date.now(), }, }, }, }, }); // Act const result = await service.getAndClearContext(); // Assert expect(result).toEqual(data); expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith(tabId, { [NAVIGATION_CONTEXT_METADATA_KEY]: {}, }); }); it('should retrieve and remove context from custom scope', async () => { // Arrange const tabId = 123; const customScope = 'wizard-flow'; const data = { currentStep: 3 }; const otherScopeData = { otherData: 'value' }; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: { [NAVIGATION_CONTEXT_METADATA_KEY]: { [customScope]: { data, createdAt: Date.now(), }, 'other-scope': { data: otherScopeData, createdAt: Date.now(), }, }, }, }, }); // Act const result = await service.getAndClearContext(customScope); // Assert expect(result).toEqual(data); expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith(tabId, { [NAVIGATION_CONTEXT_METADATA_KEY]: { 'other-scope': expect.objectContaining({ data: otherScopeData, }), }, }); }); it('should return null when context not found', async () => { // Arrange const tabId = 123; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: {}, }, }); // Act const result = await service.getAndClearContext(); // Assert expect(result).toBeNull(); expect(tabServiceMock.patchTabMetadata).not.toHaveBeenCalled(); }); }); describe('clearContext', () => { it('should return true when context exists and is cleared', async () => { // Arrange const tabId = 123; const data = { returnUrl: '/test' }; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: { [NAVIGATION_CONTEXT_METADATA_KEY]: { default: { data, createdAt: Date.now() }, }, }, }, }); // Act const result = await service.clearContext(); // Assert expect(result).toBe(true); }); it('should return false when context not found', async () => { // Arrange const tabId = 123; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: {}, }, }); // Act const result = await service.clearContext(); // Assert expect(result).toBe(false); }); }); describe('clearScope', () => { it('should clear all contexts for active tab', async () => { // Arrange const tabId = 123; const contexts = { default: { data: { url: '/test' }, createdAt: Date.now() }, 'scope-1': { data: { value: 1 }, createdAt: Date.now() }, 'scope-2': { data: { value: 2 }, createdAt: Date.now() }, }; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: { [NAVIGATION_CONTEXT_METADATA_KEY]: contexts, }, }, }); // Act const clearedCount = await service.clearScope(); // Assert expect(clearedCount).toBe(3); expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith(tabId, { [NAVIGATION_CONTEXT_METADATA_KEY]: {}, }); }); it('should return 0 when no contexts exist', async () => { // Arrange const tabId = 123; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: {}, }, }); // Act const clearedCount = await service.clearScope(); // Assert expect(clearedCount).toBe(0); expect(tabServiceMock.patchTabMetadata).not.toHaveBeenCalled(); }); it('should return 0 when no active tab', async () => { // Arrange tabServiceMock.activatedTabId.set(null); // Act const clearedCount = await service.clearScope(); // Assert expect(clearedCount).toBe(0); }); }); describe('clearAll', () => { it('should clear all contexts for active tab', async () => { // Arrange const tabId = 123; const contexts = { default: { data: { url: '/test' }, createdAt: Date.now() }, }; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: { [NAVIGATION_CONTEXT_METADATA_KEY]: contexts, }, }, }); // Act await service.clearAll(); // Assert expect(tabServiceMock.patchTabMetadata).toHaveBeenCalledWith(tabId, { [NAVIGATION_CONTEXT_METADATA_KEY]: {}, }); }); }); describe('hasContext', () => { it('should return true when context exists', async () => { // Arrange const tabId = 123; const data = { returnUrl: '/test' }; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: { [NAVIGATION_CONTEXT_METADATA_KEY]: { default: { data, createdAt: Date.now() }, }, }, }, }); // Act const result = await service.hasContext(); // Assert expect(result).toBe(true); }); it('should return false when context does not exist', async () => { // Arrange const tabId = 123; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: {}, }, }); // Act const result = await service.hasContext(); // Assert expect(result).toBe(false); }); it('should check custom scope', async () => { // Arrange const tabId = 123; const customScope = 'wizard'; const data = { step: 1 }; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: { [NAVIGATION_CONTEXT_METADATA_KEY]: { [customScope]: { data, createdAt: Date.now() }, }, }, }, }); // Act const result = await service.hasContext(customScope); // Assert expect(result).toBe(true); }); }); describe('getContextCount', () => { it('should return total number of contexts for active tab', async () => { // Arrange const tabId = 123; const contexts = { default: { data: { url: '/test' }, createdAt: Date.now() }, 'scope-1': { data: { value: 1 }, createdAt: Date.now() }, 'scope-2': { data: { value: 2 }, createdAt: Date.now() }, }; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: { [NAVIGATION_CONTEXT_METADATA_KEY]: contexts, }, }, }); // Act const count = await service.getContextCount(); // Assert expect(count).toBe(3); }); it('should return 0 when no contexts exist', async () => { // Arrange const tabId = 123; tabServiceMock.activatedTabId.set(tabId); tabServiceMock.entityMap.mockReturnValue({ [tabId]: { id: tabId, name: 'Test Tab', metadata: {}, }, }); // Act const count = await service.getContextCount(); // Assert expect(count).toBe(0); }); it('should return 0 when no active tab', async () => { // Arrange tabServiceMock.activatedTabId.set(null); // Act const count = await service.getContextCount(); // Assert expect(count).toBe(0); }); }); });