Servicios y helpers del core
Casos de prueba de src/app/core/services/** y src/app/core/helpers/**: 13 specs y 90 tests (it()), sin xit, fdescribe ni specs vacíos.
Los servicios HTTP se prueban con TestBed + HttpTestingController (HttpClientTestingModule o provideHttpClient() + provideHttpClientTesting()) y httpMock.verify() en afterEach. Los helpers y constantes se prueban como funciones/valores puros. Los títulos de it() se mantienen verbatim (algunos en inglés).
Helpers
src/app/core/helpers/global/presence.helper.spec.ts
- SUT:
isPresenceFreshy la constantePRESENCE_STALE_MS(presence.helper.ts). - Mocking: ninguno; función pura con objetos
TicketContactarmados en el test ylastSeencalculado conDate.now().
isPresenceFresh
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
is fresh at 0ms elapsed (just connected) | Dado un contacto isOnline: true con lastSeen de hace 0 s, cuando se evalúa la frescura, entonces es fresco. | true | Ninguno |
is fresh at 89s elapsed (within the 90s threshold) | Dado isOnline: true y lastSeen de hace 89 s (dentro del umbral de 90 s). | true | Ninguno |
is stale at 91s elapsed (past the 90s threshold) | Dado isOnline: true y lastSeen de hace 91 s (pasado el umbral): un isOnline viejo se degrada a offline. | false | Ninguno |
is stale exactly at the PRESENCE_STALE_MS boundary (strict less-than) | Dado lastSeen exactamente PRESENCE_STALE_MS atrás (borde, comparación estricta). | false | Ninguno |
is offline when lastSeen is missing, even if isOnline is true | Dado isOnline: true sin lastSeen. | false | Ninguno |
is offline when isOnline is explicitly false, regardless of a fresh lastSeen | Dado isOnline: false con lastSeen de hace 1 s. | false | Ninguno |
is offline when isOnline is undefined | Dado un contacto sin isOnline y con lastSeen reciente. | false | Ninguno |
is offline when the contact itself is null/undefined | Cuando se pasa null y luego undefined en lugar del contacto. | Ambas llamadas devuelven false | Ninguno |
PRESENCE_STALE_MS is 90 seconds (>= 3x backend PRESENCE_HEARTBEAT_MS=30_000, per design D4) | Cuando se lee la constante. | Es 90_000 | Ninguno |
Tests en este archivo: 9.
src/app/core/helpers/ui/nav.constant.spec.ts
- SUT: constante
MENU_ITEMS(nav.constant.ts), el árbol de menú lateral. - Mocking: ninguno; se buscan ítems por
id/titley se inspeccionanpermission,children,path,typeyhideForUserTypes.
MENU_ITEMS — permission acronyms (sidebar acronym-gap fix)
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
should NOT use static menu slugs as permission values on gated leaf items | Dado el ítem contacts, el permiso no debe ser el slug del menú. | permission distinto de 'contacts' y igual a 'CONTACT_READ' | Ninguno |
should map each top-level leaf item to its real READ-tier acronym | Dados los ítems hoja connections, users, labels, quick-responses, departments y settings. | Cada uno usa su acrónimo READ: CONNECTION_READ, USER_READ, LABEL_READ, QUICK_RESPONSE_READ, DEPARTMENT_READ, SETTINGS_READ | Ninguno |
should leave the dashboard item ungated (undefined permission) | Dado el ítem dashboard. | permission es undefined | Ninguno |
should gate the access-control container on ACCESS_CONTROL_READ | Dado el contenedor access-control: el gate del padre no se deduce de los hijos; sin él quedaría un submenú vacío. | permission es 'ACCESS_CONTROL_READ' | Ninguno |
should leave the chats container ungated (has children without gate) | Dado el contenedor chats, que tiene hijos sin gate. | permission es undefined | Ninguno |
should gate each access-control child on ACCESS_CONTROL_READ | Dados los hijos ac-roles, ac-permissions, ac-role-permissions, ac-modules y ac-permissions-builder. | Todos tienen permission 'ACCESS_CONTROL_READ' | Ninguno |
should gate the ticket-chat child on TICKET_READ and leave exempt chat children ungated | Dados los hijos de chats: el de título tickets.chats.title, internal-chats y starred-messages. | Ticket chat con 'TICKET_READ'; internal-chats y starred-messages sin permiso (undefined) | Ninguno |
MENU_ITEMS — settings entry
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
should include a settings nav item pointing to PAGE_URLS.SETTINGS | Dado el ítem settings. | Definido, path 'settings' y type 'link' | Ninguno |
should gate the settings item by permission instead of user type | Dado el ítem settings. | permission 'SETTINGS_READ' y hideForUserTypes undefined | Ninguno |
should NOT declare hideForUserTypes on unrelated items (e.g. departments) | Dado el ítem departments. | hideForUserTypes es undefined | Ninguno |
Tests en este archivo: 10.
Servicios API
src/app/core/services/api/connection.service.spec.ts
- SUT:
ConnectionService(endpoints de sesión de WhatsApp). - Mocking:
TestBedconHttpClientTestingModule;ToastrNotificationServicereemplazado porjasmine.createSpyObj('ToastrNotificationService', ['showNotification']).
ConnectionService — whatsapp session endpoints
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
startWhatsappSession should POST /connections/whatsapp/:id/start and unwrap data | Cuando se inicia la sesión de conn-1 y el servidor responde { message, data } con estado qr. | POST a /connections/whatsapp/conn-1/start; el observable emite solo data | HttpTestingController, spy de ToastrNotificationService |
logoutWhatsappSession should DELETE /connections/whatsapp/:id/logout | Cuando se cierra la sesión y el servidor responde data: null. | DELETE a /connections/whatsapp/conn-1/logout; emite undefined | HttpTestingController, spy de ToastrNotificationService |
getWhatsappSessionStatus should GET /connections/whatsapp/:id/status and unwrap data | Cuando se consulta el estado y el servidor responde connected con phoneJid. | GET a /connections/whatsapp/conn-1/status; emite data | HttpTestingController, spy de ToastrNotificationService |
Tests en este archivo: 3.
src/app/core/services/api/message.service.spec.ts
- SUT:
MessageService.getContactNotes()(el resto de métodos del servicio no tiene cobertura, según el comentario del spec). - Mocking:
TestBedconHttpClientTestingModule; matcher por función enexpectOnepara validar URL y params.
MessageService > getContactNotes
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
issues GET /messages/:contactId with messageType=note, page and limit params | Cuando se piden las notas de contact-1 con página 1 y límite 10. | GET a /messages/contact-1 con messageType=note, page=1, limit=10 | HttpTestingController |
defaults page to 1 and limit to 10 when not provided | Cuando se invoca solo con el contactId. | Se envían page=1, limit=10 y messageType=note | HttpTestingController |
reverses the server-returned ascending page into newest-first order | Dado que el backend devuelve la página ascendente por createdAt (n-1, n-2). | El resultado queda newest-first: ['n-2', 'n-1'] | HttpTestingController |
returns an empty results array and preserves meta when data.results is undefined | Dado que la respuesta trae meta pero no data.results. | results es [] y meta se conserva | HttpTestingController |
does not throw and returns empty results/undefined meta when data itself is missing | Dado que la respuesta no trae data. | No lanza error; results es [] y meta es undefined | HttpTestingController |
Tests en este archivo: 5.
src/app/core/services/api/scheduled-message.service.spec.ts
- SUT:
ScheduledMessageService. - Mocking:
TestBedconprovideHttpClient()+provideHttpClientTesting(); fixtureScheduledMessagede ejemplo.
ScheduledMessageService > create
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
POSTs create with the payload | Cuando se crea un mensaje programado con ticketId, body, scheduledAt y messageType. | POST a /scheduled-messages/create con el payload íntegro como body | HttpTestingController |
ScheduledMessageService > getMine
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
GETs mine with status/page/limit query params | Cuando se piden los propios con status: 'pending', página 1, límite 20. | GET a /scheduled-messages/mine con params status, page=1 y limit=20 | HttpTestingController |
ScheduledMessageService > getByTicket
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
GETs by-ticket/:ticketId | Cuando se consultan los programados del ticket tk1. | GET a /scheduled-messages/by-ticket/tk1 | HttpTestingController |
ScheduledMessageService > update
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
PATCHes update/:id with body/scheduledAt | Cuando se actualiza sm1 solo con { body: 'edited' }. | PATCH a /scheduled-messages/update/sm1 con body { body: 'edited' } | HttpTestingController |
ScheduledMessageService > cancel
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
PATCHes cancel/:id with an empty body | Cuando se cancela sm1. | PATCH a /scheduled-messages/cancel/sm1 con body {} | HttpTestingController |
Tests en este archivo: 5.
src/app/core/services/api/settings.service.spec.ts
- SUT:
SettingsService. - Mocking:
TestBedconHttpClientTestingModule; fixtureTenantSettingscompleto. La respuesta GET simula el reshape delResponseInterceptorglobal (data.results).
SettingsService
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
getSettings should GET /settings/get-all and unwrap data.results[0] | Cuando el servidor responde data: { results: [tenantSettings], meta: {} }. | GET a /settings/get-all; emite el primer elemento de results | HttpTestingController |
updateGeneral should PATCH /settings/general with the payload | Cuando se envía { language: 'en', timezone: 'UTC' }. | PATCH a /settings/general con ese body; emite los settings devueltos | HttpTestingController |
updateSchedule should PATCH /settings/attention/schedule with the payload | Cuando se envían los businessHours. | PATCH a /settings/attention/schedule con el payload | HttpTestingController |
updateAutoResolution should PATCH /settings/attention/auto-resolution with the payload | Cuando se envía la configuración de autoResolution. | PATCH a /settings/attention/auto-resolution con el payload | HttpTestingController |
updateCsat should PATCH /settings/attention/csat with the payload | Cuando se envía la configuración de csat. | PATCH a /settings/attention/csat con el payload | HttpTestingController |
Tests en este archivo: 5.
src/app/core/services/api/starred-message.service.spec.ts
- SUT:
StarredMessageService(incluye el streamchanged$). - Mocking:
TestBedconHttpClientTestingModule; se suscribe achanged$para observar las señales emitidas.
StarredMessageService
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
toggle should POST /starred-messages/toggle/:messageId with { messageType } and unwrap data | Cuando se hace toggle de msg-1 tipo contact. | POST a /starred-messages/toggle/msg-1 con body { messageType: 'contact' }; emite { starred: true } | HttpTestingController |
toggle should announce the change on changed$ once the server confirms it | Dada una suscripción a changed$, cuando el servidor confirma el toggle (las vistas de destacados no participan del toggle y necesitan la señal). | changed$ emite { messageId: 'msg-1', messageType: 'contact', starred: true } | HttpTestingController |
toggle should carry starred:false through when the message was un-starred | Cuando el servidor responde starred: false para msg-2 tipo internal. | El cambio emitido en changed$ trae starred en false | HttpTestingController |
toggle should stay silent when the request fails, so nothing refreshes on a rejected change | Cuando la petición falla con 500. | changed$ no emite nada ([]); el error se absorbe en el test | HttpTestingController |
getIdsByScope should GET /starred-messages/ids with contactId param and unwrap data.ids | Cuando se pide con { contactId: 'contact-1' }. | GET con param contactId; emite ['msg-1', 'msg-2'] | HttpTestingController |
getIdsByScope should default to no scope params when called without arguments | Cuando se invoca sin argumentos. | No se envían params de query; emite [] | HttpTestingController |
findByContact should GET /starred-messages/by-contact/:contactId with page/limit and unwrap data | Cuando se pide contact-1 página 1 límite 20. | GET a /starred-messages/by-contact/contact-1 con page=1 y limit=20; emite data | HttpTestingController |
findByInternalConversation should GET /starred-messages/by-internal-conversation/:conversationId | Cuando se pide la conversación conv-1. | GET a la ruta de by-internal-conversation; emite data | HttpTestingController |
findGlobal should GET /starred-messages with optional messageType filter | Cuando se pide página 1, límite 20, tipo internal. | GET a /starred-messages con param messageType=internal; emite data | HttpTestingController |
fetchMessageWindow should GET /messages/:id/window for contact messages | Cuando se pide la ventana de msg-1 tipo contact. | GET a /messages/msg-1/window; emite el resultado | HttpTestingController |
fetchMessageWindow should include radius when provided | Cuando se pasa radius 30. | La petición lleva radius=30; emite { results: [], anchorId: 'msg-1' } | HttpTestingController |
fetchMessageWindow should GET the nested internal-conversations route for internal messages | Cuando se pide msg-2 tipo internal con conv-1. | GET a /internal-conversations/conv-1/messages/msg-2/window | HttpTestingController |
fetchMessageWindow should error out synchronously when messageType is internal without a conversationId | Caso de fallo: tipo internal sin conversationId. | El observable emite error sin next; no se hace ninguna petición HTTP (expectNone) | HttpTestingController, done |
Tests en este archivo: 13.
src/app/core/services/api/system-module.service.spec.ts
- SUT:
SystemModuleService. - Mocking:
TestBedconHttpClientTestingModule;ToastrNotificationServicecomojasmine.createSpyObj.
SystemModuleService
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
should be created | Dada la inyección con sus dependencias mockeadas. | El servicio existe (toBeTruthy) | HttpTestingController, spy de ToastrNotificationService |
Tests en este archivo: 1.
src/app/core/services/api/ticket.service.spec.ts
- SUT:
TicketService(pospuesto y bloqueo de tickets). - Mocking:
TestBedconprovideHttpClient()+provideHttpClientTesting().
TicketService > postponeTicket
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
PATCHes postpone/:id with postponedUntil in the body | Cuando se pospone t1 hasta 2026-07-06T18:30:00.000Z. | PATCH a /tickets/postpone/t1 con body { postponedUntil } | HttpTestingController |
TicketService > cancelPostponeTicket
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
PATCHes cancel-postpone/:id with an empty body | Cuando se cancela el pospuesto de t1. | PATCH a /tickets/cancel-postpone/t1 con body {} | HttpTestingController |
TicketService > lockTicket
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
PATCHes lock/:id with an empty body | Cuando se bloquea t1. | PATCH a /tickets/lock/t1 con body {} | HttpTestingController |
TicketService > unlockTicket
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
PATCHes unlock/:id with an empty body | Cuando se desbloquea t1. | PATCH a /tickets/unlock/t1 con body {} | HttpTestingController |
TicketService > forceUnlockTicket
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
PATCHes force-unlock/:id with an empty body | Cuando se fuerza el desbloqueo de t1. | PATCH a /tickets/force-unlock/t1 con body {} | HttpTestingController |
TicketService > getLockedTickets
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
GETs get-all with lockedOnly=true merged into the filter | Cuando se pide con filtro { page: 1, limit: 10 }. | GET a /tickets/get-all con lockedOnly=true, page=1 y limit=10 | HttpTestingController |
Tests en este archivo: 6.
src/app/core/services/api/user.service.spec.ts
- SUT:
UserService.updateMyAvatar. - Mocking:
TestBedconprovideHttpClient()+provideHttpClientTesting();ToastrNotificationServicecomojasmine.createSpyObj.
UserService > updateMyAvatar
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
PATCHes me/avatar with a FormData body containing the file under "file" | Cuando se sube un File photo.jpg (image/jpeg). | PATCH a /users/me/avatar; el body es FormData y get('file') devuelve el mismo archivo | HttpTestingController, spy de ToastrNotificationService |
Tests en este archivo: 1.
Servicios UI
src/app/core/services/ui/chat-socket.service.spec.ts
- SUT:
ChatSocketService(listeners de socket.io). - Mocking:
TestBedconChatSocketServiceyStorereemplazado porjasmine.createSpyObj('Store', ['dispatch', 'select'])(selectdevuelveof('es')). El método privadocreateSocketse sustituye conspyOn<any>(service, 'createSocket')por un socket falso que registra cadaon(event, handler)en un mapahandlers, para invocar los handlers como lo haría el servidor. Luego se llamaservice.connect('user-1'). Cadadescriberepite este setup.
ChatSocketService — ticket_deleted
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
dispatches ticketDeletedFromSocket and emits ticketDeleted$ when the server sends ticket_deleted | Cuando el handler de TICKET_DELETED recibe { ticketId: 't1' }. | Se despacha TicketActions.ticketDeletedFromSocket y ticketDeleted$ emite { ticketId: 't1' } | Socket falso, spy de Store |
carries the ticketId through for a different id (triangulation, not a hardcoded echo) | Cuando llega otro id (ticket-xyz-42). | Acción y emisión llevan ese id, no un valor fijo | Socket falso, spy de Store |
ChatSocketService — contact_presence_changed
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
dispatches contactPresenceChangedFromSocket when the server sends contact_presence_changed | Cuando llega presencia online con lastSeen y disconnectReason: null. | Se despacha contactPresenceChangedFromSocket con el mismo payload | Socket falso, spy de Store |
carries a disconnect classification through on an offline transition | Cuando llega isOnline: false con disconnectReason: 'clean'. | La acción despachada conserva disconnectReason: 'clean' | Socket falso, spy de Store |
ChatSocketService — ticket_lock_changed
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
dispatches ticketLockChangedFromSocket when the server sends ticket_lock_changed (lock) | Cuando llega un bloqueo con lockedBy y lockedByName. | Se despacha ticketLockChangedFromSocket con esos datos | Socket falso, spy de Store |
dispatches ticketLockChangedFromSocket with a null lockedBy on unlock | Caso de desbloqueo: lockedBy y lockedByName en null. | Se despacha la acción con ambos en null | Socket falso, spy de Store |
ChatSocketService — scheduled message events
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
dispatches sentFromSocket on scheduled_message_sent | Cuando llega SCHEDULED_MESSAGE_SENT con id, ticketId, messageId. | Se despacha ScheduledMessageActions.sentFromSocket | Socket falso, spy de Store |
dispatches cancelledFromSocket on scheduled_message_cancelled | Cuando llega con reason: 'ticket_resolved'. | Se despacha cancelledFromSocket con id y reason | Socket falso, spy de Store |
dispatches failedFromSocket on scheduled_message_failed | Cuando llega con error: 'boom'. | Se despacha failedFromSocket con id y error | Socket falso, spy de Store |
dispatches createdFromSocket on scheduled_message_created | Cuando llega { item }. | Se despacha createdFromSocket({ item }) | Socket falso, spy de Store |
dispatches updatedFromSocket on scheduled_message_updated | Cuando llega { item }. | Se despacha updatedFromSocket({ item }) | Socket falso, spy de Store |
ChatSocketService — whatsapp_qr / whatsapp_session_status
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
emits whatsappQr$ with the server-rendered data URL when the server sends whatsapp_qr | Cuando llega WHATSAPP_QR con connectionId y qr (data URL). | whatsappQr$ emite el payload y store.dispatch no se llama | Socket falso, spy de Store |
emits whatsappSessionStatus$ when the server sends whatsapp_session_status | Cuando llega WHATSAPP_SESSION_STATUS con estado connected y phoneJid. | whatsappSessionStatus$ emite el payload | Socket falso, spy de Store |
ChatSocketService — reconnection
Usa jasmine.clock().install() en beforeEach y uninstall() en afterEach; el socket falso añade connect.
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
reconnects itself when the server closes the connection (io server disconnect) | Cuando llega DISCONNECT con 'io server disconnect'. | connect no se llama de inmediato; tras 1000 ms sí | Socket falso, jasmine.clock() |
reports "connecting" while the server-initiated retry is pending | Cuando el servidor cierra la conexión y se observa connectionStatus$. | El último estado emitido es 'connecting' | Socket falso, spy de Store |
does not retry a transport-level drop: socket.io-client already reconnects those | Cuando DISCONNECT llega con 'ping timeout' y pasan 10 s. | connect nunca se llama | Socket falso, jasmine.clock() |
gives up after the retry budget instead of hammering the server forever | Cuando el servidor desconecta 5 veces seguidas (espera intento * 1000 ms) y luego una sexta vez, con 60 s de espera. | connect se llamó 5 veces y sigue en 5 tras la sexta | Socket falso, jasmine.clock() |
resets the retry budget once a reconnection authenticates | Tras agotar 5 reintentos, llega AUTHENTICATED y luego otra desconexión del servidor. | El presupuesto se reinicia: connect suma 6 llamadas | Socket falso, jasmine.clock() |
emits reconnected$ only on re-authentications, never on the first one | Cuando llega AUTHENTICATED dos veces. | reconnected$ emite 0 veces tras la primera y 1 tras la segunda | Socket falso |
reuses the existing socket instead of building a second one with duplicate listeners | Dado un socket existente con connected = false, cuando se llama connect('user-1') otra vez. | createSocket se llamó solo 1 vez y fakeSocket.connect se invoca | Socket falso, spy de createSocket |
cancels a pending server-disconnect retry when the caller disconnects | Dada una desconexión del servidor con reintento pendiente, cuando se llama service.disconnect() y pasan 5 s. | connect nunca se llama | Socket falso, jasmine.clock() |
surfaces a failed handshake as "connecting" rather than staying silent | Caso de fallo: CONNECT_ERROR con Error('xhr poll error'). | El último estado de connectionStatus$ es 'connecting' | Socket falso |
Tests en este archivo: 22.
src/app/core/services/ui/my-permissions.service.spec.ts
- SUT:
MyPermissionsService(hasPermission,hasAnyPermission, caché con TTL). - Mocking:
TestBed;GlobalServicereemplazado por{ profile: ReplaySubject<User> }yUserServiceporjasmine.createSpyObj('UserService', ['getMeUser']). Helpers localesemitProfileymeResponse.
MyPermissionsService
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
should resolve true when the acronym is present in the profile-provided set | Dado un perfil con CONTACT_READ y CONTACT_UPDATE, cuando se consulta CONTACT_READ. | Emite true | ReplaySubject de perfil, spy de UserService |
should resolve false when the acronym is missing from the resolved set | Dado un perfil solo con CONTACT_READ, cuando se consulta CONTACT_DELETE. | Emite false | ReplaySubject de perfil, spy de UserService |
should resolve false when the profile has no permissions yet | Dado un perfil con permissions undefined. | Emite false | ReplaySubject de perfil, spy de UserService |
should NOT re-fetch getMe when the cache is fresh (within TTL) | Dado un caché vigente. | getMeUser no se llama | ReplaySubject de perfil, spy de UserService |
should re-fetch getMe and resolve against the refreshed set once the cache is stale | Dado que el reloj avanza 10 001 ms (pasado el TTL) y getMeUser devuelve CONTACT_READ y CONTACT_DELETE. | getMeUser se llama 1 vez y CONTACT_DELETE resuelve true | spyOn(Date, 'now'), spy de UserService |
should resolve any-of membership via hasAnyPermission | Dado un perfil con CONTACT_READ, cuando se consulta ['CONTACT_DELETE', 'CONTACT_READ']. | Emite true | ReplaySubject de perfil, spy de UserService |
Tests en este archivo: 6.
src/app/core/services/ui/user-avatars.service.spec.ts
- SUT:
UserAvatarsService.getAvatarUrl. - Mocking: sin
TestBed; instancia directa connew UserAvatarsService()y usuarios simulados conas User.
UserAvatarsService > getAvatarUrl
| it() | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
returns the avatarUrl when the user has one set | Dado un usuario con avatarUrl. | Devuelve esa URL | Ninguno |
returns undefined when avatarUrl is absent (caller falls back to initials) | Dado un usuario sin avatarUrl. | undefined (el llamador usa iniciales) | Ninguno |
returns undefined when avatarUrl is an empty string | Dado avatarUrl: ''. | undefined | Ninguno |
returns undefined when user itself is null/undefined | Cuando se pasa null y luego undefined. | Ambas llamadas devuelven undefined | Ninguno |
Tests en este archivo: 4.
Total
13 specs, 90 tests en core/services y core/helpers (helpers: 19; servicios API: 39; servicios UI: 32).