Skip to main content

Casos de prueba: Chat interno y mensajes programados

Specs de src/app/features/internal-chat/** (5 archivos) y src/app/features/scheduled-messages/** (3 archivos). Todos usan Karma + Jasmine. La mayoría instancia el componente directamente con new y dependencias simuladas; los de scheduled-messages filtro y tabla usan TestBed. No hay xit, fdescribe ni specs vacíos.

Chat interno​

src/app/features/internal-chat/components/conversation-thread/conversation-thread.component.spec.ts​

  • SUT: ConversationThreadComponent.
  • Mocking: instancia directa con new ConversationThreadComponent(...); la mayoría de dependencias son {} as any. En el primer bloque se usan un fakeTranslate (con instant que interpola {{param}}) y un fakeStateManager (getInitialState). En el segundo bloque, jasmine.createSpyObj para StarredMessageService (toggle, getIdsByScope, fetchMessageWindow) y ToastrNotificationService (showNotification), con of/throwError de RxJS.

Bloque ConversationThreadComponent — system messages > getSystemMessageText​

it()EscenarioResultado esperadoMocks/spies
renders a grouped member_added system message as one line naming both targetsDado un mensaje de sistema member_added con actor Ana y destinos Beto y Caro, cuando se llama getSystemMessageText, entonces se agrupan los destinos.Devuelve Ana added Beto and Caro.fakeTranslate.instant con diccionario de traducciones.
falls back to a generic message for an unrecognized systemEvent.type, not blankDado un systemEvent.type desconocido (renamed), cuando se llama getSystemMessageText, entonces se usa el texto genérico (camino de borde).Devuelve Ana made a change to the group, no vacío.fakeTranslate.instant.

Bloque ConversationThreadComponent — system messages > shouldRenderMessage​

it()EscenarioResultado esperadoMocks/spies
renders known content types (text, file, system)Dado un mensaje con contentType text, file o system, cuando se llama shouldRenderMessage, entonces se acepta.Los tres casos devuelven true.Ninguno (dependencias vacías).
skips an unrecognized contentType instead of crashing (legacy/forward compat)Dado un contentType desconocido (poll), cuando se llama shouldRenderMessage, entonces se omite sin lanzar error.Devuelve false.Ninguno.

Total del bloque: 4 tests.

Bloque ConversationThreadComponent — starred messages (WU4) > isFavorite / onToggleFavorite​

beforeEach fija component.activeConversationId = 'c1'.

it()EscenarioResultado esperadoMocks/spies
optimistically stars a message and calls toggle with messageType internalDado un mensaje no marcado y toggle que responde { starred: true }, cuando se llama onToggleFavorite, entonces se marca de forma optimista.isFavorite('m1') es true y toggle se llamó con ('m1', 'internal').starredMessageService.toggle devuelve of({ starred: true }).
optimistically unstars an already-starred messageDado m1 ya en favoriteMessageIds y toggle que responde { starred: false }, cuando se llama onToggleFavorite, entonces se desmarca.isFavorite('m1') es false.toggle devuelve of({ starred: false }).
rolls back and shows a toast when the toggle request failsDado que toggle falla, cuando se llama onToggleFavorite, entonces se revierte el cambio optimista y se notifica (camino de fallo).isFavorite('m1') es false y toastr.showNotification fue llamado.toggle devuelve throwError; espía toastr.showNotification.

Bloque ConversationThreadComponent — starred messages (WU4) > jumpToMessage​

it()EscenarioResultado esperadoMocks/spies
fetches a message window scoped to the active conversation when not already renderedDado que m2 no está renderizado y hay conversación activa c1, cuando se llama jumpToMessage('m2'), entonces se pide la ventana de mensajes.fetchMessageWindow llamado con ('m2', 'internal', 'c1').fetchMessageWindow devuelve of({ results, anchorId: 'm2' }).
is a no-op when there is no active conversationDado activeConversationId = null, cuando se llama jumpToMessage('m2'), entonces no hace nada.fetchMessageWindow no se llama.Espía fetchMessageWindow.
fails soft (no throw) when the window fetch errorsDado que fetchMessageWindow emite error, cuando se llama jumpToMessage('m2'), entonces el error se absorbe (camino de fallo).No lanza excepción.fetchMessageWindow devuelve throwError.

Total del bloque: 6 tests. Total del archivo: 10 tests.

src/app/features/internal-chat/components/starred-messages-panel/starred-messages-panel.component.spec.ts​

  • SUT: StarredMessagesPanelComponent.
  • Mocking: instancia directa con new; el primer argumento es un objeto con dismiss/close vacíos (modal activo), el segundo jasmine.createSpyObj('StarredMessageService', ['findByInternalConversation']) y el tercero un formateador de fechas con getFullDate. Helper listItem(overrides) construye StarredMessageListItem. conversationId = 'c1' en el beforeEach.

ngOnInit / loadStarred​

it()EscenarioResultado esperadoMocks/spies
loads the first page of starred messages for the conversationDado un servicio que devuelve una página con 1 resultado, cuando se llama ngOnInit, entonces se carga la primera página.Servicio llamado con ('c1', 1, 20), items.length es 1 e isLoading es false.findByInternalConversation devuelve of({ results, meta }).
resets to an empty list when the request failsDado que la petición falla, cuando se llama ngOnInit, entonces la lista queda vacía (camino de fallo).items es [] e isLoading es false.findByInternalConversation devuelve throwError.

loadMore​

it()EscenarioResultado esperadoMocks/spies
requests the next page and appends resultsDado items con star-1 y meta en página 1 de 2, cuando se llama loadMore, entonces se pide la página 2 y se anexa.Servicio llamado con ('c1', 2, 20); ids ['star-1', 'star-2'].findByInternalConversation devuelve of con star-2.
no-ops when already on the last pageDado meta en la última página (2 de 2), cuando se llama loadMore, entonces no hace nada (borde).El servicio no se llama.Espía findByInternalConversation.

getPreview​

it()EscenarioResultado esperadoMocks/spies
renders the hydrated file filename when a file is present (WU5)Dado un ítem con archivo audio.ogg, cuando se llama getPreview, entonces se muestra el nombre del archivo.Devuelve audio.ogg.Ninguno.
falls back to "[archivo]" when the file has no filenameDado un archivo sin filename, cuando se llama getPreview, entonces se usa el texto por defecto (borde).Devuelve [archivo].Ninguno.
renders the raw body when there is no attached fileDado un ítem sin archivo y con body: 'hola mundo', cuando se llama getPreview, entonces se muestra el cuerpo.Devuelve hola mundo.Ninguno.
returns an empty string for an orphaned item (message is null)Dado un ítem huérfano con message: null, cuando se llama getPreview, entonces no falla (borde).Devuelve ''.Ninguno.

getSenderName​

it()EscenarioResultado esperadoMocks/spies
returns firstname + lastName when both are presentDado un remitente con nombre y apellido, cuando se llama getSenderName, entonces se concatenan.Devuelve Grace Hopper.Ninguno.
falls back to username when firstname/lastName are missingDado un remitente solo con username, cuando se llama getSenderName, entonces se usa el usuario.Devuelve grace.Ninguno.
returns an empty string when sender is undefined (internal system message)Dado sender: undefined, cuando se llama getSenderName, entonces devuelve vacío (borde).Devuelve ''.Ninguno.

hasFile / isFileImage / getFileIconClass​

it()EscenarioResultado esperadoMocks/spies
reports no file when file is nullDado file: null, cuando se llama hasFile, entonces indica que no hay archivo.false.Ninguno.
reports a file and detects an image mimetypeDado un archivo image/jpeg, cuando se consultan hasFile, isFileImage y getFileIconClass, entonces se detecta imagen.true, true y fa-image.Ninguno.
resolves the microphone icon for an audio fileDado un archivo audio/ogg, cuando se consultan isFileImage y getFileIconClass, entonces no es imagen y el icono es de micrófono.false y fa-microphone.Ninguno.

onItemClick​

it()EscenarioResultado esperadoMocks/spies
emits jumpToMessage with the item messageIdDado un suscriptor a jumpToMessage, cuando se llama onItemClick con messageId: 'target-msg', entonces se emite el id.Emitido ['target-msg'].Suscripción manual al EventEmitter.

Total del archivo: 15 tests.

src/app/features/internal-chat/conversations/conversations-routing.module.spec.ts​

  • SUT: arreglo routes de conversations-routing.module.
  • Mocking: ninguno; inspecciona la configuración de rutas e importa permissionGuard.

ConversationsRoutingModule routes (permission wiring)​

it()EscenarioResultado esperadoMocks/spies
should gate the conversations page route on INTERNAL_CHAT_READ via permissionGuardDada la ruta con path === '', cuando se inspeccionan data y canActivate, entonces exige el permiso.data igual a { permission: 'INTERNAL_CHAT_READ' } y canActivate contiene permissionGuard.Ninguno.

Total del archivo: 1 test.

src/app/features/internal-chat/internal-chat-routing.module.spec.ts​

  • SUT: arreglo routes de internal-chat-routing.module.
  • Mocking: ninguno; inspección de configuración.

InternalChatRoutingModule routes (permission wiring)​

it()EscenarioResultado esperadoMocks/spies
should gate the lazy "conversations" child route on INTERNAL_CHAT_READ via permissionGuardDada la ruta hija con path === 'conversations', cuando se inspeccionan data y canActivate, entonces exige el permiso.data igual a { permission: 'INTERNAL_CHAT_READ' } y canActivate contiene permissionGuard.Ninguno.

Total del archivo: 1 test.

src/app/features/internal-chat/pages/internal-chat-page/internal-chat-page.component.spec.ts​

  • SUT: InternalChatPageComponent (enlace profundo highlightMessageId).
  • Mocking: instancia directa con new. store con select (devuelve of(null)) y dispatch como espías; route con queryParams: of(...); socket con connectionStatus$: of('authenticated') y connect; globalService con profile: of(null); el resto {} as any. Se inyecta _threadRef simulado mediante (component as any).
it()EscenarioResultado esperadoMocks/spies
dispatches setActiveConversation when a conversationId query param is presentDado el query param conversationId: 'c1', cuando se llama ngOnInit, entonces se activa la conversación.dispatch llamado con un objeto que contiene conversationId: 'c1'.store.dispatch espía.
delegates the jump to the mounted thread once its activeConversationId matchesDados conversationId y highlightMessageId y un hilo ya en c1, cuando se llama ngOnInit, entonces delega el salto.jumpToMessage llamado con 'm1'._threadRef simulado con espía jumpToMessage.
retries (bounded) until the thread has switched to the target conversationDado un hilo con activeConversationId: null, cuando se llama ngOnInit y luego el hilo pasa a c1, entonces se reintenta (reintento acotado, espera 150 ms con done).Al inicio jumpToMessage no se llama; tras 150 ms se llama con 'm1'._threadRef mutable, setTimeout, espía jumpToMessage.
does nothing extra when only conversationId is present (no highlightMessageId)Dado solo conversationId y un hilo en c1, cuando se llama ngOnInit, entonces no hay salto (borde).jumpToMessage no se llama._threadRef simulado con espía.

Total del archivo: 4 tests.

Mensajes programados​

src/app/features/scheduled-messages/components/scheduled-messages-filter/scheduled-messages-filter.component.spec.ts​

  • SUT: ScheduledMessagesFilterComponent (standalone).
  • Mocking: TestBed con imports: [ScheduledMessagesFilterComponent, TranslateModule.forRoot()], createComponent y detectChanges; espía jasmine.createSpy('filterChange') suscrito al output.

ScheduledMessagesFilterComponent​

it()EscenarioResultado esperadoMocks/spies
emits filterChange with the selected statusDado un suscriptor a filterChange, cuando se llama onStatusChange('pending'), entonces se emite el estado.Espía llamado con { status: 'pending' }.Espía filterChange.
emits filterChange with no status when "all" is selectedDado un suscriptor, cuando se llama onStatusChange('') (opción "todos"), entonces el estado queda indefinido (borde).Espía llamado con { status: undefined }.Espía filterChange.

Total del archivo: 2 tests.

src/app/features/scheduled-messages/components/scheduled-messages-table/scheduled-messages-table.component.spec.ts​

  • SUT: ScheduledMessagesTableComponent (standalone).
  • Mocking: TestBed con TranslateModule.forRoot(); helper makeItem(overrides) construye un ScheduledMessage con status: 'pending'; espía sobre el output cancel.

ScheduledMessagesTableComponent​

it()EscenarioResultado esperadoMocks/spies
exposes isCancellable as true only for pending itemsDados ítems con estado pending, sent y cancelled, cuando se llama isCancellable, entonces solo el pendiente es cancelable.true para pending; false para sent y cancelled.Ninguno.
emits cancel with the item when onCancelClick is calledDado un suscriptor a cancel, cuando se llama onCancelClick(item), entonces se emite el ítem.Espía llamado con item.Espía cancel.

Total del archivo: 2 tests.

src/app/features/scheduled-messages/pages/scheduled-messages-page/scheduled-messages-page.component.spec.ts​

  • SUT: ScheduledMessagesPageComponent.
  • Mocking: Store de NgRx simulado con jasmine.createSpyObj('Store', ['select', 'dispatch']), select devuelve of([makeItem()]); se registra como provider en TestBed, pero el componente se instancia directamente con new ScheduledMessagesPageComponent(store).

ScheduledMessagesPageComponent​

it()EscenarioResultado esperadoMocks/spies
dispatches loadMine on initDado el componente creado, cuando se llama ngOnInit, entonces se pide la carga propia.dispatch llamado con ScheduledMessageActions.loadMine({}).store.dispatch espía.
dispatches cancel with the id and item when onCancel is calledDado un ítem, cuando se llama onCancel(item), entonces se despacha la cancelación.dispatch llamado con ScheduledMessageActions.cancel({ id: item._id, item }).store.dispatch espía.
dispatches loadMine with the filter status when onFilterChange is calledDado un filtro { status: 'sent' }, cuando se llama onFilterChange, entonces se recarga con ese estado.dispatch llamado con ScheduledMessageActions.loadMine({ status: 'sent' }).store.dispatch espía.

Total del archivo: 3 tests.

Total de la página​

8 specs, 36 tests (it()): chat interno 31 (10 + 15 + 1 + 1 + 4) y mensajes programados 7 (2 + 2 + 3).