Skip to content

Comments

325 add unit tests for event viewer#326

Merged
brucetony merged 5 commits intodevelopfrom
325-add-unit-tests-for-event-viewer
Feb 19, 2026
Merged

325 add unit tests for event viewer#326
brucetony merged 5 commits intodevelopfrom
325-add-unit-tests-for-event-viewer

Conversation

@brucetony
Copy link
Collaborator

@brucetony brucetony commented Feb 19, 2026

Summary by CodeRabbit

  • New Features

    • Enhanced event filtering with improved log level counting and reactivity.
  • Bug Fixes

    • Improved filter logic for more accurate event display and count synchronization.
  • Updates

    • Added default query limit to event API calls for better performance.
  • Tests

    • Expanded test coverage for event viewing, filtering, and UI components.

@brucetony brucetony linked an issue Feb 19, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

Warning

Rate limit exceeded

@brucetony has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 7 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

This PR enhances event viewer components with unit tests, adds missing Vue component imports (Checkbox, ToggleSwitch), refactors event filtering and log-level counting logic in EventViewer.vue, sets a default query limit of 100 events, and updates mock API handlers to support event endpoint testing.

Changes

Cohort / File(s) Summary
Component Import Additions
components/events/DateFilterGraph.vue, components/events/TagFilterSidePanel.vue, components/header/AvatarButton.vue
Adds missing Vue imports: useNuxtApp from "#app" in DateFilterGraph, Checkbox from primevue/checkbox in TagFilterSidePanel, and ToggleSwitch from primevue/toggleswitch in AvatarButton.
Event Viewer Logic
components/events/EventViewer.vue
Reworks updateLogLevelCounts to accept an explicit event list and rebuild distributions reactively; simplifies tagsContainsAny filter logic with early exits; updates handleFilter to compute filtered counts safely; adjusts MeterGroup to derive max from filteredEventCount; enables Chip import for filter UI.
API Configuration
composables/useAPIFetch.ts
Adds default query parameter limit: 100 to getEvents API call.
Event Viewer Tests
test/components/events/DateFilterGraph.spec.ts, test/components/events/EventViewer.spec.ts, test/components/events/TagFilterSidePanel.spec.ts, test/components/events/constants.ts
Introduces new test suites for DateFilterGraph, EventViewer, and TagFilterSidePanel components with mocked API responses; adds reusable fakeEventResponse fixture for event log testing.
Header & Analysis Test Updates
test/components/header/AvatarButton.spec.ts, test/components/header/MenuHeader.spec.ts, test/components/analysis/AnalysisControlButtons.spec.ts
Converts AvatarButton beforeAll from async to sync; wraps MenuHeader in test component with Suspense; updates AnalysisControlButtons prop from nodeType to requireDatastore in test setup.
Mock API Handlers
test/mockapi/handlers.ts
Adds GET /node/settings and GET /events endpoints; updates Kong initialization type from kongBody to BodyKongInitializeKongInitializePost.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • Add unit tests for event viewer #325: Directly implements "Add unit tests for event viewer" by introducing comprehensive test suites for DateFilterGraph.spec.ts, EventViewer.spec.ts, and TagFilterSidePanel.spec.ts with mocked API responses.

Possibly related PRs

Poem

🐰 With tests now gleaming, events flow bright,
Mock APIs hopping through the night,
Components dance with Checkboxes new,
Log levels counted, filters true,
A hop towards coverage, pure delight! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary objective of the pull request: adding unit tests for the event viewer component, which represents the main change across multiple test files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 325-add-unit-tests-for-event-viewer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/events/EventViewer.vue (1)

178-182: ⚠️ Potential issue | 🟡 Minor

filteredEventCount is not reset when new events are loaded via the date filter.

After handleShowRequestEvents replaces events with a fresh dataset, filteredEventCount retains whatever value was set by the last DataTable @filter event. Downstream consumers — :max="filteredEventCount || eventCount" on MeterGroup and the :eventCount prop on DateFilterGraph — will reflect a stale filtered count rather than the new total.

🐛 Proposed fix
 function handleShowRequestEvents(requestedEvents: EventLogResponse) {
   events.value = requestedEvents.data;
   eventCount.value = requestedEvents.meta.count;
+  filteredEventCount.value = 0;
   updateLogLevelCounts();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/events/EventViewer.vue` around lines 178 - 182, When replacing the
events dataset in handleShowRequestEvents, reset the DataTable filter state so
downstream consumers use the new total; inside handleShowRequestEvents (which
currently sets events.value, eventCount.value and calls updateLogLevelCounts),
also clear filteredEventCount (e.g. set filteredEventCount.value = 0 or
null/undefined) so :max="filteredEventCount || eventCount" on MeterGroup and
:eventCount on DateFilterGraph reflect the fresh eventCount rather than a stale
filter value.
🧹 Nitpick comments (4)
test/components/header/MenuHeader.spec.ts (1)

10-18: Incorrect type annotation and unnecessary async on beforeAll.

Two issues on adjacent lines:

  1. Line 10DefineComponent<typeof defineComponent> passes the function-type of defineComponent itself as the props-type generic argument, which is incorrect. The equivalent in AvatarButton.spec.ts uses typeof AvatarButton. Use the same pattern here for correctness and consistency.

  2. Line 13beforeAll is declared async but the body contains only a synchronous defineComponent({...}) assignment (no await). This is the exact pattern that was corrected for beforeAll in AvatarButton.spec.ts in this same PR.

♻️ Proposed fix
-  let MenuHeaderTestComponent: DefineComponent<typeof defineComponent>;
+  let MenuHeaderTestComponent: typeof MenuHeader;

   // Render the component with the fake params
-  beforeAll(async () => {
+  beforeAll(() => {
     MenuHeaderTestComponent = defineComponent({
       components: { MenuHeader },
       template: "<Suspense><MenuHeader/></Suspense>",
     });
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/components/header/MenuHeader.spec.ts` around lines 10 - 18, The variable
MenuHeaderTestComponent is incorrectly typed and beforeAll is unnecessarily
async: change the generic on MenuHeaderTestComponent from DefineComponent<typeof
defineComponent> to DefineComponent<typeof MenuHeader> (matching
AvatarButton.spec.ts) and remove the async modifier from the beforeAll
declaration since the body only performs a synchronous defineComponent({...})
assignment for the MenuHeader component.
composables/useAPIFetch.ts (1)

29-37: opts.query is silently overridden — merge query params instead.

Because ...opts is spread before query: { limit: 100 }, any query property inside opts is completely replaced. While the current only call site (EventViewer.vue) doesn't pass custom query params, the function signature accepts opts without restriction, making it a latent issue for future callers. The same pattern affects multiple functions across this file (getProjectNodes, getProjects, getAnalyses, etc.).

♻️ Proposed fix — merge caller query with the default
 export function getEvents(opts?) {
   return useAPIFetch<EventLogResponse>("/events", {
     ...opts,
     method: "GET",
     query: {
       limit: 100,
+      ...opts?.query,
     },
   });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composables/useAPIFetch.ts` around lines 29 - 37, The spread of ...opts
before setting query in getEvents causes any caller-provided opts.query to be
overwritten; change the call to merge default and caller query without mutating
opts, e.g. keep method:"GET" and set query: { limit: 100, ...(opts?.query || {})
} so callers can override or add params; apply the same merge pattern to the
other affected functions (getProjectNodes, getProjects, getAnalyses, etc.) to
ensure caller query params are preserved.
components/events/EventViewer.vue (1)

57-79: Redundant if (eventList) guard can be removed.

eventList has type EventLog[] with a default of events.value (initialized as ref<EventLog[]>([])), so it is always an array and never falsy. The guard on line 66 is dead code and slightly misleads readers into thinking undefined is possible. Note that if the guard were removed, the outer updatedCounts map (with zero counts) would be unconditionally assigned to logLevelDistributions.value, which is the correct reset behavior.

♻️ Proposed simplification
 function updateLogLevelCounts(eventList: EventLog[] = events.value) {
   const updatedCounts = new Map(
     logLevelDistributions.value.map((dist) => [
       dist.label,
       { ...dist, value: 0 },
     ]),
   );

-  if (eventList) {
-    // Count occurrences
-    eventList.forEach((event) => {
-      const tags = event.attributes?.tags || [];
-      updatedCounts.forEach((dist) => {
-        if (tags.includes(dist.label)) {
-          dist.value++;
-        }
-      });
-    });
-    logLevelDistributions.value = Array.from(updatedCounts.values());
-  }
+  // Count occurrences
+  eventList.forEach((event) => {
+    const tags = event.attributes?.tags || [];
+    updatedCounts.forEach((dist) => {
+      if (tags.includes(dist.label)) {
+        dist.value++;
+      }
+    });
+  });
+  logLevelDistributions.value = Array.from(updatedCounts.values());
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/events/EventViewer.vue` around lines 57 - 79, The guard `if
(eventList)` in updateLogLevelCounts is redundant because eventList has type
EventLog[] with a default of events.value (events is initialized as
ref<EventLog[]>([])), so remove the `if` block and its matching braces, run the
counting loop unconditionally, and always assign the reset updatedCounts to
logLevelDistributions.value so the meter group is reliably reset; refer to
updateLogLevelCounts, eventList, events.value, and logLevelDistributions in your
changes.
test/components/events/TagFilterSidePanel.spec.ts (1)

13-14: wrapper.find("button") targets the Panel toggle button instead of the clear button.

The Panel component with toggleable attribute (line 40) renders a toggle button before the explicit clear button (lines 53-61). This causes wrapper.find("button") to match the wrong element and silently test the Panel toggle instead of the clear filter functionality. Use a data-testid attribute to explicitly target the clear button.

In TagFilterSidePanel.vue, add a test identifier to the clear button:

             <Button
               icon="pi pi-filter-slash"
+              data-testid="clear-tag-filter"
               `@click`="clearFilters()"
               size="small"
               variant="text"
               severity="contrast"
               v-tooltip.top="'Clear the tag filters'"
               rounded
             />

Then in the test:

-    const button = wrapper.find("button");
+    const button = wrapper.find("[data-testid='clear-tag-filter']");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/components/events/TagFilterSidePanel.spec.ts` around lines 13 - 14, The
test is clicking the wrong button because wrapper.find("button") matches the
Panel toggle; add a data-testid to the clear button in the TagFilterSidePanel
component (e.g., data-testid="clear-filters-btn" on the Clear button element),
then update the test in TagFilterSidePanel.spec.ts to target that specific
element (use wrapper.find('[data-testid="clear-filters-btn"]') and
trigger("click")) so the clearFilter handler/path is exercised instead of the
Panel toggle; reference the Clear button markup in TagFilterSidePanel.vue and
the test that currently uses wrapper.find("button").
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/components/events/DateFilterGraph.spec.ts`:
- Around line 30-38: The test for DateFilterGraph may assert the
"showRequestedEvents" emit before the mocked $hubApi promise resolves; after
triggering the click (submit.trigger("click")) add an await flushPromises() to
drain pending microtasks before checking wrapper.emitted("showRequestedEvents"),
and ensure flushPromises is imported from '@vue/test-utils' at the top of the
spec so the emit assertion runs after the mocked $hubApi resolves.

In `@test/components/events/EventViewer.spec.ts`:
- Line 56: The test's exact datetime assertion is timezone- and locale-dependent
because formatTimestamp uses Intl.DateTimeFormat; replace the brittle equality
check in EventViewer.spec.ts (the expect against rowCells[0].text()) with a
stable assertion—either assert that the rendered string contains a
locale-agnostic fragment such as the time portion or date digits using
toContain, or explicitly set the test TZ by assigning process.env.TZ = 'UTC' at
the top of the spec and update the expected string; target the expectation
around the output of formatTimestamp to make the assertion deterministic.

In `@test/mockapi/handlers.ts`:
- Around line 320-326: The MSW handler in test/mockapi/handlers.ts returns an
object wrapper { status: 200, data: fakeEventResponse } which breaks getEvents()
(which uses useAPIFetch<EventLogResponse>() and expects the raw
EventLogResponse); update the `/events` handler so it returns the
EventLogResponse payload directly by calling HttpResponse.json with
fakeEventResponse (i.e., remove the outer { status, data } wrapper) so
response.value.data and response.value.meta are present as expected.

---

Outside diff comments:
In `@components/events/EventViewer.vue`:
- Around line 178-182: When replacing the events dataset in
handleShowRequestEvents, reset the DataTable filter state so downstream
consumers use the new total; inside handleShowRequestEvents (which currently
sets events.value, eventCount.value and calls updateLogLevelCounts), also clear
filteredEventCount (e.g. set filteredEventCount.value = 0 or null/undefined) so
:max="filteredEventCount || eventCount" on MeterGroup and :eventCount on
DateFilterGraph reflect the fresh eventCount rather than a stale filter value.

---

Nitpick comments:
In `@components/events/EventViewer.vue`:
- Around line 57-79: The guard `if (eventList)` in updateLogLevelCounts is
redundant because eventList has type EventLog[] with a default of events.value
(events is initialized as ref<EventLog[]>([])), so remove the `if` block and its
matching braces, run the counting loop unconditionally, and always assign the
reset updatedCounts to logLevelDistributions.value so the meter group is
reliably reset; refer to updateLogLevelCounts, eventList, events.value, and
logLevelDistributions in your changes.

In `@composables/useAPIFetch.ts`:
- Around line 29-37: The spread of ...opts before setting query in getEvents
causes any caller-provided opts.query to be overwritten; change the call to
merge default and caller query without mutating opts, e.g. keep method:"GET" and
set query: { limit: 100, ...(opts?.query || {}) } so callers can override or add
params; apply the same merge pattern to the other affected functions
(getProjectNodes, getProjects, getAnalyses, etc.) to ensure caller query params
are preserved.

In `@test/components/events/TagFilterSidePanel.spec.ts`:
- Around line 13-14: The test is clicking the wrong button because
wrapper.find("button") matches the Panel toggle; add a data-testid to the clear
button in the TagFilterSidePanel component (e.g.,
data-testid="clear-filters-btn" on the Clear button element), then update the
test in TagFilterSidePanel.spec.ts to target that specific element (use
wrapper.find('[data-testid="clear-filters-btn"]') and trigger("click")) so the
clearFilter handler/path is exercised instead of the Panel toggle; reference the
Clear button markup in TagFilterSidePanel.vue and the test that currently uses
wrapper.find("button").

In `@test/components/header/MenuHeader.spec.ts`:
- Around line 10-18: The variable MenuHeaderTestComponent is incorrectly typed
and beforeAll is unnecessarily async: change the generic on
MenuHeaderTestComponent from DefineComponent<typeof defineComponent> to
DefineComponent<typeof MenuHeader> (matching AvatarButton.spec.ts) and remove
the async modifier from the beforeAll declaration since the body only performs a
synchronous defineComponent({...}) assignment for the MenuHeader component.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (8)
test/components/header/AvatarButton.spec.ts (2)

2-2: Remove stale development comment.

// add flushPromises reads as a TODO/note-to-self and should be dropped from the committed test file.

🧹 Proposed cleanup
-import { flushPromises, mount } from "@vue/test-utils"; // add flushPromises
+import { flushPromises, mount } from "@vue/test-utils";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/components/header/AvatarButton.spec.ts` at line 2, Remove the stale
inline development comment from the import statement in AvatarButton.spec.ts:
locate the import line that reads "import { flushPromises, mount } from
\"@vue/test-utils\"; // add flushPromises" and delete the trailing "// add
flushPromises" comment so the import is clean and contains no TODO/note-to-self
text.

16-16: Remove dead code: vi.mocked(useRuntimeConfig) with no effect.

useRuntimeConfig is already globally mocked in test/mockapi/setup.ts (registered via vitest.config.ts setupFiles). The standalone vi.mocked(useRuntimeConfig) call on line 16 does nothing—vi.mocked() is just a TypeScript type helper, and when called without chaining a method like .mockReturnValue(), it produces no side effects and the return value is discarded. This line is dead code and should be removed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/components/header/AvatarButton.spec.ts` at line 16, Remove the dead call
vi.mocked(useRuntimeConfig) from AvatarButton.spec.ts: useRuntimeConfig is
already globally mocked in test/mockapi/setup.ts via vitest setupFiles, and
vi.mocked(...) here is only a TypeScript helper with no effect when not chained,
so delete that standalone line and run tests to confirm nothing else relies on a
local mock.
test/components/header/MenuHeader.spec.ts (1)

13-18: beforeAll callback is async without any await

defineComponent(...) is synchronous, so the async keyword is unnecessary here. Dropping it removes a false signal that async work is performed.

🧹 Proposed fix
-  beforeAll(async () => {
+  beforeAll(() => {
     MenuHeaderTestComponent = defineComponent({
       components: { MenuHeader },
       template: "<Suspense><MenuHeader/></Suspense>",
     });
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/components/header/MenuHeader.spec.ts` around lines 13 - 18, The
beforeAll callback is marked async but contains no awaits; remove the
unnecessary async modifier from the beforeAll used to initialize
MenuHeaderTestComponent (the defineComponent call that registers MenuHeader and
sets template "<Suspense><MenuHeader/></Suspense>), so change the beforeAll to a
synchronous function to avoid implying asynchronous work.
test/components/events/EventViewer.spec.ts (1)

16-18: Prefer vi.resetAllMocks() over vi.restoreAllMocks() for module mocks

vi.restoreAllMocks() is semantically intended for spies created via vi.spyOn() (restoring the original implementation). For vi.fn() module mocks created via vi.mock(), vi.resetAllMocks() is the idiomatic choice — it clears the mockResolvedValue set in the previous test, which is the actual goal here.

♻️ Proposed fix
   beforeEach(() => {
-    vi.restoreAllMocks();
+    vi.resetAllMocks();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/components/events/EventViewer.spec.ts` around lines 16 - 18, The
beforeEach currently calls vi.restoreAllMocks(); change this to
vi.resetAllMocks() to correctly clear module mocks created with
vi.mock()/vi.fn() between tests; update the beforeEach block in
EventViewer.spec.ts so that vi.resetAllMocks() is invoked instead of
vi.restoreAllMocks() to ensure mockResolvedValue and similar mock state is
cleared between tests.
test/components/events/TagFilterSidePanel.spec.ts (1)

13-14: Use a specific selector for the clear-filter button

wrapper.find("button") relies on DOM order to find the clearFilters button rather than PrimeVue's internal Panel toggle button. If PrimeVue reorders its rendered header elements across versions, this will silently click the wrong button.

♻️ Proposed fix
-    const button = wrapper.find("button");
+    const button = wrapper.find(".event-viewer-filter-panel-header-clear-btn button");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/components/events/TagFilterSidePanel.spec.ts` around lines 13 - 14, The
test currently uses wrapper.find("button") which can target the wrong element;
change the click target to a stable, specific selector for the clear-filters
control (e.g., add and use a data-testid or an aria-label on the clearFilters
button in the TagFilterSidePanel component and replace wrapper.find("button")
with wrapper.find('[data-testid="clear-filters"]') or
wrapper.find('[aria-label="Clear filters"]'), or use wrapper.findComponent to
locate the specific PrimeVue Button instance); update the test to
trigger("click") on that specific selector instead of wrapper.find("button") so
it no longer relies on DOM ordering.
composables/useAPIFetch.ts (1)

29-36: opts.query is silently overwritten — merge it instead

Because query: { limit: 100 } is placed after ...opts, any caller that passes opts = { query: { start_date: '...' } } will have that key completely replaced. DateFilterGraph.vue works around this by calling $hubApi directly, but getEvents itself presents a broken passthrough contract.

♻️ Proposed fix
 export function getEvents(opts?) {
   return useAPIFetch<EventLogResponse>("/events", {
     ...opts,
     method: "GET",
     query: {
+      ...(opts?.query ?? {}),
       limit: 100,
     },
   });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composables/useAPIFetch.ts` around lines 29 - 36, getEvents currently
clobbers any caller-provided opts.query because query: { limit: 100 } is placed
after ...opts; change it to merge the caller's query with the default instead.
In getEvents, build the query as something like query: { limit: 100,
...(opts?.query || {}) } (or if you want to keep 100 enforced, reverse the
spread to { ...(opts?.query || {}), limit: 100 }) and ensure opts is handled
when undefined; update the call in getEvents so other query keys from opts
aren't lost.
components/events/EventViewer.vue (2)

127-147: FilterService.register is a global side effect — move it out of the component.

FilterService.register runs on every mount of EventViewer, registering a PrimeVue-global filter each time. While idempotent in production, this pattern complicates unit testing (the filter must be registered before mounting the component) and is semantically wrong — a global registry entry should be set up once at app initialisation.

Consider moving the registration to a Nuxt plugin or the composable layer:

// plugins/primevue-filters.ts
import { FilterService } from "@primevue/core/api";
import { EventServiceTag, EventLogLevelTag } from "~/types/eventTag";
import type { EventTag } from "~/types/eventTag";

export default defineNuxtPlugin(() => {
  FilterService.register("tagsContainsAny", (value, filter) => {
    if (!filter || filter.length === 0) return true;
    if (!value || value.length === 0) return false;
    const selectedServices = filter.filter((f: EventTag) =>
      Object.values(EventServiceTag).includes(f),
    );
    const selectedLogLevels = filter.filter((f: EventTag) =>
      Object.values(EventLogLevelTag).includes(f),
    );
    const matchesService =
      selectedServices.length === 0 ||
      selectedServices.some((f: EventTag) => value.includes(f));
    const matchesLogLevel =
      selectedLogLevels.length === 0 ||
      selectedLogLevels.some((f: EventTag) => value.includes(f));
    return matchesService && matchesLogLevel;
  });
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/events/EventViewer.vue` around lines 127 - 147, Move the PrimeVue
global filter registration out of the EventViewer component: remove the
FilterService.register("tagsContainsAny", ...) call from EventViewer and instead
register it once at app initialization (e.g., a Nuxt plugin or a composable
setup). Keep the same predicate logic and imports for EventServiceTag,
EventLogLevelTag and EventTag, and ensure the global registration happens before
components mount so tests and mounts don’t re-register the filter; reference the
existing FilterService.register, the "tagsContainsAny" name, and the
EventServiceTag/EventLogLevelTag enums when relocating the code.

57-79: Redundant if (eventList) guard — always truthy.

eventList is typed as EventLog[] and defaults to events.value (initialized to []), so it can never be null/undefined under TypeScript. The guard implies a nullish path that doesn't exist, and the placement leaves updatedCounts (already reset to zeros) silently unused if the branch is somehow skipped.

♻️ Proposed cleanup
 function updateLogLevelCounts(eventList: EventLog[] = events.value) {
   const updatedCounts = new Map(
     logLevelDistributions.value.map((dist) => [
       dist.label,
       { ...dist, value: 0 },
     ]),
   );

-  if (eventList) {
-    // Count occurrences
-    eventList.forEach((event) => {
-      const tags = event.attributes?.tags || [];
-
-      updatedCounts.forEach((dist) => {
-        if (tags.includes(dist.label)) {
-          dist.value++;
-        }
-      });
-    });
-    logLevelDistributions.value = Array.from(updatedCounts.values());
-  }
+  // Count occurrences
+  eventList.forEach((event) => {
+    const tags = event.attributes?.tags || [];
+    updatedCounts.forEach((dist) => {
+      if (tags.includes(dist.label)) {
+        dist.value++;
+      }
+    });
+  });
+  logLevelDistributions.value = Array.from(updatedCounts.values());
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/events/EventViewer.vue` around lines 57 - 79, The guard inside
updateLogLevelCounts is redundant because eventList is typed EventLog[] and
defaults to events.value (initialized to []); remove the if (eventList) branch,
unindent its body so updatedCounts is always used to count tags and then assign
logLevelDistributions.value = Array.from(updatedCounts.values()); keep the
initial reset of updatedCounts and the forEach over eventList (using
event.attributes?.tags || []) and preserve the existing updatedCounts.forEach
increment logic so counts are always updated even when eventList is empty.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/components/header/MenuHeader.spec.ts`:
- Line 10: Replace the incorrect generic usage by changing the type of
MenuHeaderTestComponent from DefineComponent<typeof defineComponent> to
ReturnType<typeof defineComponent> (or remove the explicit annotation) so the
variable reflects the component instance type correctly; also remove the
unnecessary async modifier from the beforeAll block since it contains only
synchronous code. Ensure you update references to MenuHeaderTestComponent,
DefineComponent, defineComponent, and beforeAll accordingly.

In `@test/mockapi/handlers.ts`:
- Around line 35-40: The mock handler for the GET route defined with
http.get("/node/settings", ...) is returning a JSON body that includes a status
field; remove the status: 200 property so the handler returns only {
data_required: false } to match the NodeSettings interface and the real backend
shape, i.e. update the HttpResponse.json(...) call inside the /node/settings
handler to return only the data_required property (leave status codes to HTTP
headers/status, not the JSON body).

---

Duplicate comments:
In `@test/components/events/DateFilterGraph.spec.ts`:
- Around line 30-38: The test for DateFilterGraph triggers a click that calls
the async requestEvents which awaits $hubApi; to ensure the mocked promise
settles before asserting the emit, add an await flushPromises() (imported from
"flush-promises") immediately after await submit.trigger("click") and before
expect(wrapper.emitted("showRequestedEvents")).toHaveLength(1); this guarantees
the microtask from mockResolvedValue is drained and the component’s emit
(showRequestedEvents) is present; reference DateFilterGraph, requestEvents,
submit.trigger("click") and showRequestedEvents when locating where to insert
the flushPromises call.

In `@test/mockapi/handlers.ts`:
- Around line 320-326: The MSW handler http.get(`/events`) is returning an
object wrapper ({ status: 200, data: fakeEventResponse }) instead of the
expected EventLogResponse shape, which breaks consumers that expect
response.value.data to be EventLog[] and response.value.meta.count to exist;
update the handler in handlers.ts (the http.get('/events') callback) to return
the fakeEventResponse directly via HttpResponse.json so the response shape
matches the real EventLogResponse (use HttpResponse.json with the
EventLogResponse payload rather than wrapping it).

---

Nitpick comments:
In `@components/events/EventViewer.vue`:
- Around line 127-147: Move the PrimeVue global filter registration out of the
EventViewer component: remove the FilterService.register("tagsContainsAny", ...)
call from EventViewer and instead register it once at app initialization (e.g.,
a Nuxt plugin or a composable setup). Keep the same predicate logic and imports
for EventServiceTag, EventLogLevelTag and EventTag, and ensure the global
registration happens before components mount so tests and mounts don’t
re-register the filter; reference the existing FilterService.register, the
"tagsContainsAny" name, and the EventServiceTag/EventLogLevelTag enums when
relocating the code.
- Around line 57-79: The guard inside updateLogLevelCounts is redundant because
eventList is typed EventLog[] and defaults to events.value (initialized to []);
remove the if (eventList) branch, unindent its body so updatedCounts is always
used to count tags and then assign logLevelDistributions.value =
Array.from(updatedCounts.values()); keep the initial reset of updatedCounts and
the forEach over eventList (using event.attributes?.tags || []) and preserve the
existing updatedCounts.forEach increment logic so counts are always updated even
when eventList is empty.

In `@composables/useAPIFetch.ts`:
- Around line 29-36: getEvents currently clobbers any caller-provided opts.query
because query: { limit: 100 } is placed after ...opts; change it to merge the
caller's query with the default instead. In getEvents, build the query as
something like query: { limit: 100, ...(opts?.query || {}) } (or if you want to
keep 100 enforced, reverse the spread to { ...(opts?.query || {}), limit: 100 })
and ensure opts is handled when undefined; update the call in getEvents so other
query keys from opts aren't lost.

In `@test/components/events/EventViewer.spec.ts`:
- Around line 16-18: The beforeEach currently calls vi.restoreAllMocks(); change
this to vi.resetAllMocks() to correctly clear module mocks created with
vi.mock()/vi.fn() between tests; update the beforeEach block in
EventViewer.spec.ts so that vi.resetAllMocks() is invoked instead of
vi.restoreAllMocks() to ensure mockResolvedValue and similar mock state is
cleared between tests.

In `@test/components/events/TagFilterSidePanel.spec.ts`:
- Around line 13-14: The test currently uses wrapper.find("button") which can
target the wrong element; change the click target to a stable, specific selector
for the clear-filters control (e.g., add and use a data-testid or an aria-label
on the clearFilters button in the TagFilterSidePanel component and replace
wrapper.find("button") with wrapper.find('[data-testid="clear-filters"]') or
wrapper.find('[aria-label="Clear filters"]'), or use wrapper.findComponent to
locate the specific PrimeVue Button instance); update the test to
trigger("click") on that specific selector instead of wrapper.find("button") so
it no longer relies on DOM ordering.

In `@test/components/header/AvatarButton.spec.ts`:
- Line 2: Remove the stale inline development comment from the import statement
in AvatarButton.spec.ts: locate the import line that reads "import {
flushPromises, mount } from \"@vue/test-utils\"; // add flushPromises" and
delete the trailing "// add flushPromises" comment so the import is clean and
contains no TODO/note-to-self text.
- Line 16: Remove the dead call vi.mocked(useRuntimeConfig) from
AvatarButton.spec.ts: useRuntimeConfig is already globally mocked in
test/mockapi/setup.ts via vitest setupFiles, and vi.mocked(...) here is only a
TypeScript helper with no effect when not chained, so delete that standalone
line and run tests to confirm nothing else relies on a local mock.

In `@test/components/header/MenuHeader.spec.ts`:
- Around line 13-18: The beforeAll callback is marked async but contains no
awaits; remove the unnecessary async modifier from the beforeAll used to
initialize MenuHeaderTestComponent (the defineComponent call that registers
MenuHeader and sets template "<Suspense><MenuHeader/></Suspense>), so change the
beforeAll to a synchronous function to avoid implying asynchronous work.

@brucetony brucetony merged commit 1c39e33 into develop Feb 19, 2026
5 checks passed
@brucetony brucetony deleted the 325-add-unit-tests-for-event-viewer branch February 19, 2026 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add unit tests for event viewer

1 participant