Skip to content

Analytics#89

Open
TatevikGr wants to merge 5 commits into
devfrom
analytics
Open

Analytics#89
TatevikGr wants to merge 5 commits into
devfrom
analytics

Conversation

@TatevikGr

@TatevikGr TatevikGr commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added a new Analytics page with summary metrics, charts, and tables for campaign and domain performance.
    • Registered the Analytics page in the app navigation and server-side routing.
  • Bug Fixes

    • Improved admin settings dialogs so forms reset correctly when reopened.
    • Added support for loading the Analytics page through the backend frontend entry point.

Summary

Provide a general description of the code changes in your pull request …
were there any bugs you had fixed? If so, mention them. If these bugs have open
GitHub issues, be sure to tag them here as well, to keep the conversation
linked together.

Unit test

Are your changes covered with unit tests, and do they not break anything?

You can run the existing unit tests using this command:

vendor/bin/phpunit tests/

Code style

Have you checked that you code is well-documented and follows the PSR-2 coding
style?

You can check for this using this command:

vendor/bin/phpcs --standard=PSR2 src/ tests/

Other Information

If there's anything else that's important and relevant to your pull
request, mention that information here. This could include benchmarks,
or other information.

If you are updating any of the CHANGELOG files or are asked to update the
CHANGELOG files by reviewers, please add the CHANGELOG entry at the top of the file.

Thanks for contributing to phpList!

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@TatevikGr, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a755f49f-3888-4b50-ad3a-90c1fc12176d

📥 Commits

Reviewing files that changed from the base of the PR and between e955297 and bdd4cec.

📒 Files selected for processing (55)
  • .github/workflows/ci.yml
  • .husky/pre-commit
  • assets/vue/components/dashboard/KpiCard.vue
  • assets/vue/views/DashboardView.spec.js
  • tests/Unit/assets/vue/components/base/BaseBadge.spec.js
  • tests/Unit/assets/vue/components/base/BaseButton.spec.js
  • tests/Unit/assets/vue/components/base/BaseCard.spec.js
  • tests/Unit/assets/vue/components/base/BaseIcon.spec.js
  • tests/Unit/assets/vue/components/base/BaseProgressBar.spec.js
  • tests/Unit/assets/vue/components/base/CkEditorField.spec.js
  • tests/Unit/assets/vue/components/bounces/BounceOverview.spec.js
  • tests/Unit/assets/vue/components/bounces/BouncePer.spec.js
  • tests/Unit/assets/vue/components/bounces/BounceRules.spec.js
  • tests/Unit/assets/vue/components/bounces/BouncesActionsPanel.spec.js
  • tests/Unit/assets/vue/components/campaigns/CampaignDirectory.spec.js
  • tests/Unit/assets/vue/components/campaigns/ViewCampaignModal.spec.js
  • tests/Unit/assets/vue/components/dashboard/CampaignsTable.spec.js
  • tests/Unit/assets/vue/components/dashboard/KpiCard.spec.js
  • tests/Unit/assets/vue/components/dashboard/KpiGrid.spec.js
  • tests/Unit/assets/vue/components/dashboard/PerformanceChartCard.spec.js
  • tests/Unit/assets/vue/components/dashboard/QuickActionsCard.spec.js
  • tests/Unit/assets/vue/components/dashboard/RecentCampaignsCard.spec.js
  • tests/Unit/assets/vue/components/lists/AddSubscribersModal.spec.js
  • tests/Unit/assets/vue/components/lists/CreateListModal.spec.js
  • tests/Unit/assets/vue/components/lists/EditListModal.spec.js
  • tests/Unit/assets/vue/components/lists/ListDirectory.spec.js
  • tests/Unit/assets/vue/components/lists/ListSubscribersExportPanel.spec.js
  • tests/Unit/assets/vue/components/public-pages/PublicPageEditor.spec.js
  • tests/Unit/assets/vue/components/public-pages/PublicPagesDirectory.spec.js
  • tests/Unit/assets/vue/components/settings/CreateAdminAttributeModal.spec.js
  • tests/Unit/assets/vue/components/settings/CreateAdminModal.spec.js
  • tests/Unit/assets/vue/components/settings/CreateSubscriberAttributeModal.spec.js
  • tests/Unit/assets/vue/components/settings/EditAdminAttributeModal.spec.js
  • tests/Unit/assets/vue/components/settings/EditAdminModal.spec.js
  • tests/Unit/assets/vue/components/settings/EditSubscriberAttributeModal.spec.js
  • tests/Unit/assets/vue/components/settings/SettingsActionsPanel.spec.js
  • tests/Unit/assets/vue/components/settings/SettingsAdminAttributes.spec.js
  • tests/Unit/assets/vue/components/settings/SettingsAdmins.spec.js
  • tests/Unit/assets/vue/components/settings/SettingsConfigs.spec.js
  • tests/Unit/assets/vue/components/settings/SettingsSubscriberAttributes.spec.js
  • tests/Unit/assets/vue/components/sidebar/AppSidebar.spec.js
  • tests/Unit/assets/vue/components/sidebar/SidebarLogo.spec.js
  • tests/Unit/assets/vue/components/sidebar/SidebarNavItem.spec.js
  • tests/Unit/assets/vue/components/sidebar/SidebarNavSection.spec.js
  • tests/Unit/assets/vue/components/subscribers/ImportResult.spec.js
  • tests/Unit/assets/vue/components/subscribers/SubscriberDirectory.spec.js
  • tests/Unit/assets/vue/components/subscribers/SubscriberFilters.spec.js
  • tests/Unit/assets/vue/components/subscribers/SubscriberModal.spec.js
  • tests/Unit/assets/vue/components/subscribers/SubscriberTable.spec.js
  • tests/Unit/assets/vue/components/templates/TemplateLibrary.spec.js
  • tests/Unit/assets/vue/layouts/AdminLayout.spec.js
  • tests/Unit/assets/vue/views/AnalyticsView.spec.js
  • tests/Unit/assets/vue/views/BouncesView.spec.js
  • tests/Unit/assets/vue/views/DashboardView.spec.js
  • vitest.config.mjs
📝 Walkthrough

Walkthrough

This PR adds a new Analytics page: a Symfony AnalyticsController rendering the SPA shell at /analytics/, a Vue AnalyticsView.vue dashboard fetching and displaying campaign/domain statistics via ApexCharts, and router registration. It also fixes an EditAdminModal watcher, adds extensive new Vitest suites for settings modals/panels and BouncesView, relocates existing spec files' imports to the built asset paths, and updates CI/husky/vitest configuration for the new tests/Unit/assets test directory.

Estimated code review effort: 3 (Moderate) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AnalyticsView
  participant statisticsClient
  participant AnalyticsController
  participant Session

  User->>AnalyticsController: GET /analytics/
  AnalyticsController->>Session: read auth_token
  AnalyticsController-->>User: render SPA shell (page, api_token, api_base_url)
  User->>AnalyticsView: mount (onMounted)
  AnalyticsView->>statisticsClient: fetch campaign statistics, view opens, top domains, domain confirmation, top local parts (Promise.all)
  statisticsClient-->>AnalyticsView: dataset responses
  AnalyticsView->>AnalyticsView: populate refs, compute metrics/chart data
  AnalyticsView-->>User: render metrics, charts, tables
Loading

Possibly related PRs

  • phpList/web-frontend#78: Both PRs modify assets/router/index.js to extend Vue Router's registered routes (this PR adds /analytics, the other adds dashboard/subscribers routes).
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too vague to describe the main change in this pull request. Use a concise, specific title like "Add analytics dashboard and route" so teammates can quickly understand the primary change.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch analytics

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@assets/vue/views/AnalyticsView.vue`:
- Around line 71-125: The empty-state sections in AnalyticsView.vue are
rendering before loadAnalytics() finishes, which causes a misleading “no data”
flash on first paint. Update the loading logic in AnalyticsView and the affected
empty-state blocks to stay hidden until the initial fetch completes, either by
introducing a hasLoaded flag or by guarding all “No ... found” branches with
!isLoading. Make sure the fix is applied consistently across the
domainConfirmation view and the other matching empty states so the template only
shows “no data” after the data request has actually finished.
- Around line 306-320: The formatDate helper in AnalyticsView.vue currently
passes date-only strings straight into new Date, which can shift the displayed
day in some time zones. Update formatDate to detect YYYY-MM-DD inputs and
normalize them before formatting, while keeping the existing Unknown date
fallback and Intl.DateTimeFormat output.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 137c0e28-face-46b0-ab9a-c3fa52df4efc

📥 Commits

Reviewing files that changed from the base of the PR and between 7a456a9 and 4bc4166.

📒 Files selected for processing (6)
  • assets/router/index.js
  • assets/vue/api.js
  • assets/vue/views/AnalyticsView.spec.js
  • assets/vue/views/AnalyticsView.vue
  • src/Controller/AnalyticsController.php
  • tests/Integration/Controller/AnalyticsControllerTest.php

Comment thread assets/vue/views/AnalyticsView.vue
Comment thread assets/vue/views/AnalyticsView.vue

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (1)
tests/Unit/assets/vue/views/AnalyticsView.spec.js (1)

19-21: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Mock the same api module the component imports. In tests/Unit/assets/vue/views/AnalyticsView.spec.js:19, vi.mock('../api', ...) resolves to tests/Unit/assets/vue/api, while assets/vue/views/AnalyticsView.vue imports ../api from the source tree. Point the mock at the component’s resolved path so the test doesn’t fall through to the real client.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Unit/assets/vue/views/AnalyticsView.spec.js` around lines 19 - 21, The
mock in AnalyticsView.spec.js is targeting the wrong module path, so
AnalyticsView.vue still resolves its own ../api import to the real client.
Update the vi.mock call to match the component’s actual resolved api module path
used by AnalyticsView.vue, and keep the statisticsClient stub there so the test
intercepts the same import the component loads.
♻️ Duplicate comments (2)
tests/Unit/assets/vue/components/settings/SettingsConfigs.spec.js (1)

12-18: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Same vi.mock relative-path mismatch as in the other settings modal/panel specs.

'../../api' resolves relative to this spec's own directory, landing on tests/Unit/assets/vue/api instead of the real assets/vue/api.js imported by SettingsConfigs.vue, so configClient calls likely bypass the mock.

🔧 Proposed fix
-vi.mock('../../api', () => ({
+vi.mock('../../../../../../assets/vue/api', () => ({
     default: {},
     configClient: {
         getConfigs: getConfigsMock,
         update: updateMock,
     },
 }))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Unit/assets/vue/components/settings/SettingsConfigs.spec.js` around
lines 12 - 18, The SettingsConfigs spec is mocking the wrong relative module
path, so the `configClient` mock may not intercept the import used by
`SettingsConfigs.vue`. Update the `vi.mock` call in `SettingsConfigs.spec.js` to
target the same resolved `assets/vue/api` module path that the component
imports, matching the pattern used in the other settings modal/panel specs, so
`getConfigs` and `update` are properly mocked.
tests/Unit/assets/vue/components/settings/SettingsSubscriberAttributes.spec.js (1)

13-42: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Same vi.mock relative-path mismatch, affecting all 4 mocks in this file.

All of '../../api' (Line 13), '../base/BaseIcon.vue' (Line 20), './CreateSubscriberAttributeModal.vue' (Line 26), and './EditSubscriberAttributeModal.vue' (Line 35) are resolved relative to this spec file's directory, not relative to SettingsSubscriberAttributes.vue's actual location — so none of them likely intercept the real imports the component makes.

🔧 Proposed fix
-vi.mock('../../api', () => ({
+vi.mock('../../../../../../assets/vue/api', () => ({
     subscriberAttributesClient: {
         getAttributeDefinitions: getAttributeDefinitionsMock,
         deleteAttributeDefinition: deleteAttributeDefinitionMock,
     },
 }))

-vi.mock('../base/BaseIcon.vue', () => ({
+vi.mock('../../../../../../assets/vue/components/base/BaseIcon.vue', () => ({
     default: {
         template: '<span />',
     },
 }))

-vi.mock('./CreateSubscriberAttributeModal.vue', () => ({
+vi.mock('../../../../../../assets/vue/components/settings/CreateSubscriberAttributeModal.vue', () => ({
     default: defineComponent({
         name: 'CreateSubscriberAttributeModal',
         props: ['isOpen'],
         emits: ['close', 'created'],
         template: '<div />',
     }),
 }))

-vi.mock('./EditSubscriberAttributeModal.vue', () => ({
+vi.mock('../../../../../../assets/vue/components/settings/EditSubscriberAttributeModal.vue', () => ({
     default: defineComponent({
         name: 'EditSubscriberAttributeModal',
         props: ['isOpen', 'attribute'],
         emits: ['close', 'updated'],
         template: '<div />',
     }),
 }))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tests/Unit/assets/vue/components/settings/SettingsSubscriberAttributes.spec.js`
around lines 13 - 42, The four vi.mock calls in
SettingsSubscriberAttributes.spec.js are using paths relative to the spec file
instead of matching the imports used by SettingsSubscriberAttributes.vue, so
they won’t stub the real dependencies. Update the mocked module specifiers for
subscriberAttributesClient, BaseIcon, CreateSubscriberAttributeModal, and
EditSubscriberAttributeModal to the exact paths that
SettingsSubscriberAttributes.vue imports, using the existing mock names to
locate each entry.
🧹 Nitpick comments (3)
vitest.config.mjs (1)

9-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the stale assets/vue/**/*.spec.js glob. It matches nothing now, so tests/Unit/assets/vue/**/*.spec.js is enough.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vitest.config.mjs` at line 9, The Vitest include list still contains the
stale assets/vue/**/*.spec.js glob, which no longer matches any files. Update
the include configuration in vitest.config.mjs to keep only the valid
tests/Unit/assets/vue/**/*.spec.js pattern so the test discovery remains
accurate.
tests/Unit/assets/vue/components/settings/SettingsAdminAttributes.spec.js (1)

126-136: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Test asserts nothing.

reloads attributes after create only emits 'created' and never asserts getAttributeDefinitionsMock was called again — it will pass regardless of whether the reload actually happens, defeating the purpose of the test.

🔧 Suggested fix
     it('reloads attributes after create', async () => {
         const wrapper = mountComponent()

         await flushPromises()

         getAttributeDefinitionsMock.mockClear()

         const createModal = wrapper.findComponent(CreateStub)
         expect(createModal.exists()).toBe(true)
         await createModal.vm.$emit('created')
+        await flushPromises()
+
+        expect(getAttributeDefinitionsMock).toHaveBeenCalled()
     })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Unit/assets/vue/components/settings/SettingsAdminAttributes.spec.js`
around lines 126 - 136, The reload-after-create test is not verifying any
behavior because it only emits created and never checks that the refresh
happens. Update the SettingsAdminAttributes.spec.js test around reloads
attributes after create to assert the expected reload behavior by checking
getAttributeDefinitionsMock is called again after the CreateStub emits created,
so the test fails if the refresh does not occur.
tests/Unit/assets/vue/components/settings/CreateSubscriberAttributeModal.spec.js (1)

96-116: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fragile selector for the "remove" button.

find(button => !button.text().trim()) picks the first button with empty text, which is brittle if markup adds an icon-only close button elsewhere in the modal. Low priority given "Chill" review mode, but worth a data-testid down the line.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tests/Unit/assets/vue/components/settings/CreateSubscriberAttributeModal.spec.js`
around lines 96 - 116, The remove-button lookup in
CreateSubscriberAttributeModal.spec.js is brittle because it uses an empty-text
button search that can accidentally match other icon-only buttons. Update the
test around mountComponent and the “adds and removes options” case to target a
stable identifier instead, such as a data-testid or a dedicated selector on the
remove control, and use that for removeButton so the assertion only clicks the
intended button.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/Unit/assets/vue/components/bounces/BouncePer.spec.js`:
- Around line 3-4: The vi.mock path in BouncePer.spec.js is targeting the wrong
module, so it does not intercept the api import used by BouncePer.vue. Update
the mock to match the component’s actual relative import for bouncesClient from
assets/vue/api, and keep the test import of BouncePer aligned so the mocked
module is the one exercised by the component under test.

In `@tests/Unit/assets/vue/components/settings/CreateAdminAttributeModal.spec.js`:
- Around line 10-14: The vi.mock target in CreateAdminAttributeModal.spec.js is
pointing at the wrong api module, so the component’s imported
adminAttributeClient is never replaced. Update the mock path to match the same
assets/vue/api module that CreateAdminAttributeModal.vue imports, and keep the
createAttributeDefinition stub wired through adminAttributeClient so the spec
uses the mocked client.

In `@tests/Unit/assets/vue/components/settings/CreateAdminModal.spec.js`:
- Around line 24-34: The mocks in CreateAdminModal.spec.js are targeting paths
that resolve relative to the test file instead of the modules imported by
CreateAdminModal.vue, so they never intercept the real dependencies. Update the
vi.mock calls to use the same module IDs that CreateAdminModal.vue imports for
the API client and BaseIcon component, and verify the mock names still match the
references used in the test setup.

In
`@tests/Unit/assets/vue/components/settings/CreateSubscriberAttributeModal.spec.js`:
- Around line 10-20: Fix the Vitest mocks in
CreateSubscriberAttributeModal.spec.js so they intercept the same import paths
used by the component. Update the vi.mock calls for the API module and
BaseIcon.vue to match the component’s assets/vue/api.js and
assets/vue/components/base/BaseIcon.vue imports, ensuring
subscriberAttributesClient.createAttributeDefinition is actually mocked when the
modal tests run.

In `@tests/Unit/assets/vue/components/settings/EditAdminModal.spec.js`:
- Around line 24-34: The test mocks in EditAdminModal.spec.js are using module
ids that do not match the imports in EditAdminModal.vue, so the real adminClient
and BaseIcon dependencies are still being loaded. Update the vi.mock targets to
match the exact import paths used by EditAdminModal (for adminClient) and the
BaseIcon component so the update tests fully stub those dependencies.

In
`@tests/Unit/assets/vue/components/settings/EditSubscriberAttributeModal.spec.js`:
- Around line 10-20: The mocks in EditSubscriberAttributeModal.spec.js are
targeting the wrong module ids, so they do not intercept the imports used by
EditSubscriberAttributeModal.vue. Update the vi.mock calls for the api client
and BaseIcon.vue to point at the same source module paths that the component
imports, using the existing identifiers subscriberAttributesClient and
updateAttributeDefinitionMock so the component receives the intended stubs.

In `@tests/Unit/assets/vue/components/settings/SettingsAdminAttributes.spec.js`:
- Around line 13-24: The mocks in SettingsAdminAttributes.spec.js are resolving
from the test file location instead of the component’s import paths, so they do
not intercept the imports used by SettingsAdminAttributes.vue. Update the
vi.mock targets for the api client and BaseIcon.vue to match the component’s
actual relative imports (or switch both to a shared alias) so
adminAttributeClient and BaseIcon are properly mocked during the component test.

In `@tests/Unit/assets/vue/components/settings/SettingsAdmins.spec.js`:
- Around line 13-42: Update the mocks in SettingsAdmins.spec.js so they match
the import paths used by SettingsAdmins.vue rather than resolving from the spec
file; adjust the vi.mock targets for ../../api, ../base/BaseIcon.vue,
CreateAdminModal.vue, and EditAdminModal.vue to the component-relative asset
paths so the component under test actually receives the mocked modules.

In `@tests/Unit/assets/vue/components/subscribers/SubscriberModal.spec.js`:
- Around line 5-6: The test mock for subscribersClient is targeting a different
resolved module than the one SubscriberModal actually imports, so it will not
intercept the real dependency. Update the vi.mock setup in
SubscriberModal.spec.js to mock the same assets/vue/api module path used by
SubscriberModal, and keep the subscribersClient import aligned with that same
module resolution so the component under test receives the mocked client.

---

Outside diff comments:
In `@tests/Unit/assets/vue/views/AnalyticsView.spec.js`:
- Around line 19-21: The mock in AnalyticsView.spec.js is targeting the wrong
module path, so AnalyticsView.vue still resolves its own ../api import to the
real client. Update the vi.mock call to match the component’s actual resolved
api module path used by AnalyticsView.vue, and keep the statisticsClient stub
there so the test intercepts the same import the component loads.

---

Duplicate comments:
In `@tests/Unit/assets/vue/components/settings/SettingsConfigs.spec.js`:
- Around line 12-18: The SettingsConfigs spec is mocking the wrong relative
module path, so the `configClient` mock may not intercept the import used by
`SettingsConfigs.vue`. Update the `vi.mock` call in `SettingsConfigs.spec.js` to
target the same resolved `assets/vue/api` module path that the component
imports, matching the pattern used in the other settings modal/panel specs, so
`getConfigs` and `update` are properly mocked.

In
`@tests/Unit/assets/vue/components/settings/SettingsSubscriberAttributes.spec.js`:
- Around line 13-42: The four vi.mock calls in
SettingsSubscriberAttributes.spec.js are using paths relative to the spec file
instead of matching the imports used by SettingsSubscriberAttributes.vue, so
they won’t stub the real dependencies. Update the mocked module specifiers for
subscriberAttributesClient, BaseIcon, CreateSubscriberAttributeModal, and
EditSubscriberAttributeModal to the exact paths that
SettingsSubscriberAttributes.vue imports, using the existing mock names to
locate each entry.

---

Nitpick comments:
In
`@tests/Unit/assets/vue/components/settings/CreateSubscriberAttributeModal.spec.js`:
- Around line 96-116: The remove-button lookup in
CreateSubscriberAttributeModal.spec.js is brittle because it uses an empty-text
button search that can accidentally match other icon-only buttons. Update the
test around mountComponent and the “adds and removes options” case to target a
stable identifier instead, such as a data-testid or a dedicated selector on the
remove control, and use that for removeButton so the assertion only clicks the
intended button.

In `@tests/Unit/assets/vue/components/settings/SettingsAdminAttributes.spec.js`:
- Around line 126-136: The reload-after-create test is not verifying any
behavior because it only emits created and never checks that the refresh
happens. Update the SettingsAdminAttributes.spec.js test around reloads
attributes after create to assert the expected reload behavior by checking
getAttributeDefinitionsMock is called again after the CreateStub emits created,
so the test fails if the refresh does not occur.

In `@vitest.config.mjs`:
- Line 9: The Vitest include list still contains the stale
assets/vue/**/*.spec.js glob, which no longer matches any files. Update the
include configuration in vitest.config.mjs to keep only the valid
tests/Unit/assets/vue/**/*.spec.js pattern so the test discovery remains
accurate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 01cd48f1-4350-4439-a850-7a50220d3540

📥 Commits

Reviewing files that changed from the base of the PR and between 7f1932e and e955297.

📒 Files selected for processing (54)
  • .github/workflows/ci.yml
  • .husky/pre-commit
  • assets/vue/components/settings/EditAdminModal.vue
  • tests/Unit/assets/vue/components/base/BaseBadge.spec.js
  • tests/Unit/assets/vue/components/base/BaseButton.spec.js
  • tests/Unit/assets/vue/components/base/BaseCard.spec.js
  • tests/Unit/assets/vue/components/base/BaseIcon.spec.js
  • tests/Unit/assets/vue/components/base/BaseProgressBar.spec.js
  • tests/Unit/assets/vue/components/base/CkEditorField.spec.js
  • tests/Unit/assets/vue/components/bounces/BounceOverview.spec.js
  • tests/Unit/assets/vue/components/bounces/BouncePer.spec.js
  • tests/Unit/assets/vue/components/bounces/BounceRules.spec.js
  • tests/Unit/assets/vue/components/bounces/BouncesActionsPanel.spec.js
  • tests/Unit/assets/vue/components/campaigns/CampaignDirectory.spec.js
  • tests/Unit/assets/vue/components/campaigns/ViewCampaignModal.spec.js
  • tests/Unit/assets/vue/components/dashboard/CampaignsTable.spec.js
  • tests/Unit/assets/vue/components/dashboard/KpiCard.spec.js
  • tests/Unit/assets/vue/components/dashboard/KpiGrid.spec.js
  • tests/Unit/assets/vue/components/dashboard/PerformanceChartCard.spec.js
  • tests/Unit/assets/vue/components/dashboard/QuickActionsCard.spec.js
  • tests/Unit/assets/vue/components/dashboard/RecentCampaignsCard.spec.js
  • tests/Unit/assets/vue/components/lists/AddSubscribersModal.spec.js
  • tests/Unit/assets/vue/components/lists/CreateListModal.spec.js
  • tests/Unit/assets/vue/components/lists/EditListModal.spec.js
  • tests/Unit/assets/vue/components/lists/ListDirectory.spec.js
  • tests/Unit/assets/vue/components/lists/ListSubscribersExportPanel.spec.js
  • tests/Unit/assets/vue/components/public-pages/PublicPageEditor.spec.js
  • tests/Unit/assets/vue/components/public-pages/PublicPagesDirectory.spec.js
  • tests/Unit/assets/vue/components/settings/CreateAdminAttributeModal.spec.js
  • tests/Unit/assets/vue/components/settings/CreateAdminModal.spec.js
  • tests/Unit/assets/vue/components/settings/CreateSubscriberAttributeModal.spec.js
  • tests/Unit/assets/vue/components/settings/EditAdminAttributeModal.spec.js
  • tests/Unit/assets/vue/components/settings/EditAdminModal.spec.js
  • tests/Unit/assets/vue/components/settings/EditSubscriberAttributeModal.spec.js
  • tests/Unit/assets/vue/components/settings/SettingsActionsPanel.spec.js
  • tests/Unit/assets/vue/components/settings/SettingsAdminAttributes.spec.js
  • tests/Unit/assets/vue/components/settings/SettingsAdmins.spec.js
  • tests/Unit/assets/vue/components/settings/SettingsConfigs.spec.js
  • tests/Unit/assets/vue/components/settings/SettingsSubscriberAttributes.spec.js
  • tests/Unit/assets/vue/components/sidebar/AppSidebar.spec.js
  • tests/Unit/assets/vue/components/sidebar/SidebarLogo.spec.js
  • tests/Unit/assets/vue/components/sidebar/SidebarNavItem.spec.js
  • tests/Unit/assets/vue/components/sidebar/SidebarNavSection.spec.js
  • tests/Unit/assets/vue/components/subscribers/ImportResult.spec.js
  • tests/Unit/assets/vue/components/subscribers/SubscriberDirectory.spec.js
  • tests/Unit/assets/vue/components/subscribers/SubscriberFilters.spec.js
  • tests/Unit/assets/vue/components/subscribers/SubscriberModal.spec.js
  • tests/Unit/assets/vue/components/subscribers/SubscriberTable.spec.js
  • tests/Unit/assets/vue/components/templates/TemplateLibrary.spec.js
  • tests/Unit/assets/vue/layouts/AdminLayout.spec.js
  • tests/Unit/assets/vue/views/AnalyticsView.spec.js
  • tests/Unit/assets/vue/views/BouncesView.spec.js
  • tests/Unit/assets/vue/views/DashboardView.spec.js
  • vitest.config.mjs
✅ Files skipped from review due to trivial changes (21)
  • tests/Unit/assets/vue/components/base/BaseButton.spec.js
  • tests/Unit/assets/vue/components/base/BaseCard.spec.js
  • .husky/pre-commit
  • tests/Unit/assets/vue/views/BouncesView.spec.js
  • tests/Unit/assets/vue/components/public-pages/PublicPagesDirectory.spec.js
  • tests/Unit/assets/vue/components/base/BaseProgressBar.spec.js
  • tests/Unit/assets/vue/components/lists/EditListModal.spec.js
  • tests/Unit/assets/vue/components/campaigns/ViewCampaignModal.spec.js
  • tests/Unit/assets/vue/layouts/AdminLayout.spec.js
  • tests/Unit/assets/vue/components/subscribers/ImportResult.spec.js
  • tests/Unit/assets/vue/components/public-pages/PublicPageEditor.spec.js
  • tests/Unit/assets/vue/components/subscribers/SubscriberTable.spec.js
  • tests/Unit/assets/vue/components/base/BaseIcon.spec.js
  • tests/Unit/assets/vue/components/sidebar/AppSidebar.spec.js
  • tests/Unit/assets/vue/components/templates/TemplateLibrary.spec.js
  • tests/Unit/assets/vue/components/lists/ListDirectory.spec.js
  • tests/Unit/assets/vue/components/subscribers/SubscriberFilters.spec.js
  • tests/Unit/assets/vue/components/base/BaseBadge.spec.js
  • tests/Unit/assets/vue/components/settings/EditAdminAttributeModal.spec.js
  • tests/Unit/assets/vue/components/sidebar/SidebarNavItem.spec.js
  • tests/Unit/assets/vue/components/settings/SettingsActionsPanel.spec.js

Comment thread tests/Unit/assets/vue/components/bounces/BouncePer.spec.js
Comment thread tests/Unit/assets/vue/components/settings/CreateAdminAttributeModal.spec.js Outdated
Comment on lines +24 to +34
vi.mock('../../api', () => ({
adminClient: {
createAdministrator: createAdministratorMock,
},
}))

vi.mock('../base/BaseIcon.vue', () => ({
default: {
template: '<span />',
},
}))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Files ==\n'
git ls-files 'tests/Unit/assets/vue/components/settings/CreateAdminModal.spec.js' \
             'assets/vue/components/settings/CreateAdminModal.vue' \
             'assets/vue/api.*' \
             'assets/vue/components/base/BaseIcon.vue'

printf '\n== Outline spec ==\n'
ast-grep outline tests/Unit/assets/vue/components/settings/CreateAdminModal.spec.js --view expanded || true

printf '\n== Outline component ==\n'
ast-grep outline assets/vue/components/settings/CreateAdminModal.vue --view expanded || true

printf '\n== Spec lines ==\n'
cat -n tests/Unit/assets/vue/components/settings/CreateAdminModal.spec.js | sed -n '1,120p'

printf '\n== Component lines ==\n'
cat -n assets/vue/components/settings/CreateAdminModal.vue | sed -n '1,120p'

Repository: phpList/web-frontend

Length of output: 11090


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Search for vi.mock usage near these paths ==\n'
rg -n --hidden --glob '!node_modules' "vi\.mock\((['\"])\.\./.*BaseIcon|vi\.mock\((['\"])\.\./.*api|CreateAdminModal" tests/Unit assets/vue | sed -n '1,200p'

printf '\n== Search for explicit absolute mock patterns in nearby tests ==\n'
rg -n --hidden --glob '!node_modules' "vi\.mock\(.*assets/vue" tests/Unit | sed -n '1,200p'

printf '\n== Search for import in component ==\n'
rg -n --hidden --glob '!node_modules' "from ['\"].*api|from ['\"].*BaseIcon" assets/vue/components/settings/CreateAdminModal.vue assets/vue/components/base/BaseIcon.vue assets/vue/api.* | sed -n '1,200p'

Repository: phpList/web-frontend

Length of output: 4745


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== CreateAdminModal component imports ==\n'
rg -n --context 3 "from ['\"].*api|from ['\"].*BaseIcon|<script|defineProps|import " assets/vue/components/settings/CreateAdminModal.vue | sed -n '1,120p'

printf '\n== Similar settings tests/component imports ==\n'
for f in \
  tests/Unit/assets/vue/components/settings/CreateSubscriberAttributeModal.spec.js \
  assets/vue/components/settings/CreateSubscriberAttributeModal.vue \
  tests/Unit/assets/vue/components/settings/EditAdminModal.spec.js \
  assets/vue/components/settings/EditAdminModal.vue \
  tests/Unit/assets/vue/components/settings/SettingsAdmins.spec.js \
  assets/vue/components/settings/SettingsAdmins.vue
do
  printf '\n--- %s ---\n' "$f"
  if [ -f "$f" ]; then
    rg -n --context 2 "from ['\"].*api|from ['\"].*BaseIcon|from ['\"].*CreateAdminModal|<script|import " "$f" | sed -n '1,160p'
  fi
done

Repository: phpList/web-frontend

Length of output: 2423


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,80p' assets/vue/components/settings/CreateAdminModal.vue

Repository: phpList/web-frontend

Length of output: 3573


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

test = Path('tests/Unit/assets/vue/components/settings/CreateAdminModal.spec.js').read_text()
comp = Path('assets/vue/components/settings/CreateAdminModal.vue').read_text()

print('test mocks:')
for line in test.splitlines():
    if 'vi.mock(' in line:
        print(line)

print('\ncomponent imports:')
for line in comp.splitlines():
    if 'from ' in line or '<script' in line or 'import ' in line:
        print(line)
PY

Repository: phpList/web-frontend

Length of output: 505


🌐 Web query:

Vitest vi.mock relative path resolved relative to test file module path docs

💡 Result:

In Vitest, when you provide a relative path to vi.mock(), it is resolved relative to the test file where the vi.mock() call is located [1][2]. This behavior aligns with standard relative path resolution in modules, where paths are interpreted relative to the file containing the code [3][1]. Vitest treats the path provided to vi.mock() as a reference to the specific file you intend to mock, rather than a path relative to the project root or the current working directory [1]. Key points regarding vi.mock() path resolution: 1. Consistency: The path in vi.mock() should match the file path of the module you want to mock, resolved from the location of your test file [1]. 2. Hoisting: vi.mock() calls are hoisted to the top of the file [4][5]. Even though they are moved during transformation, the resolution of the relative path remains anchored to the original location of the test file [1]. 3. Aliases: You can also use configured Vite aliases within the vi.mock() path [5][6]. If you encounter resolution issues with aliases, it is often recommended to use explicit relative paths (e.g., ./path/to/module) or ensure aliases are correctly defined in your Vitest/Vite configuration [3][5][7]. 4. Best Practices: If your project structure is complex, ensure that the path provided to vi.mock() accurately points to the target module file relative to your test [1]. If you find vi.mock() is not working as expected, verify the path by checking how you would import the module in that same test file; the vi.mock() path should correspond to the same target [1].

Citations:


🌐 Web query:

site:vitest.dev vi.mock relative path test file resolved module path

💡 Result:

In Vitest, when you use a relative path in vi.mock(path, factory), the path is resolved relative to the file where the vi.mock call is located [1][2]. Vitest performs static analysis on your files to identify and hoist vi.mock calls to the top of the file, ensuring they execute before any imports [3][4]. Key aspects of path resolution and behavior include: 1. Path Aliases: You can use configured Vite aliases (e.g., '@/') within the path string [3][5]. If you are using TypeScript with paths aliases in tsconfig.json, ensure you have configured a tool like vite-tsconfig-paths or defined the equivalent aliases in your Vitest configuration, as Vite does not automatically respect tsconfig.json paths [1][2]. 2. Relative Path Gotchas: Be careful with relative aliases or paths that depend on the project root. Vite treats relative paths (e.g., './module') as relative to the file being imported, while aliases are resolved according to your Vite configuration [1][2]. If you encounter a "Cannot find module" error, verify the path spelling and ensure it correctly references the file relative to the test file's location [1][2]. 3. Hoisting: Because vi.mock is hoisted, its placement in your code does not affect resolution; it will always execute before your imports [3][5]. However, vi.mock must be imported directly from 'vitest' to be correctly detected by Vitest's static analyzer [5]. 4. Module Resolution: If you do not provide a factory function, Vitest looks for a corresponding file in a mocks folder relative to the mocked module [3][5]. If you are mocking a dependency in node_modules, it looks for a mocks folder in the project root by default [3][5]. You can customize where Vitest looks for these via the deps.moduleDirectories configuration option [6][7]. If you need more precise control or are having trouble with resolution, you can pass a dynamic import like vi.mock(import('./path/to/module.js'), factory) to provide better IDE support and explicit module reference [3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '120,220p' assets/vue/components/settings/CreateAdminModal.vue

Repository: phpList/web-frontend

Length of output: 3047


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n --hidden --glob '!node_modules' "adminClient\.createAdministrator|createAdministrator\(" assets/vue tests/Unit | sed -n '1,200p'

Repository: phpList/web-frontend

Length of output: 286


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n --hidden --glob '!node_modules' "resolve:|alias:|tests/Unit/assets/vue|assets/vue/api" vitest.config.* vite.config.* package.json tests src . | sed -n '1,240p'

Repository: phpList/web-frontend

Length of output: 4000


🏁 Script executed:

#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path

test_file = Path('tests/Unit/assets/vue/components/settings/CreateAdminModal.spec.js')
component_file = Path('assets/vue/components/settings/CreateAdminModal.vue')

test_mock_api = (test_file.parent / '../../api').resolve(strict=False)
test_mock_icon = (test_file.parent / '../base/BaseIcon.vue').resolve(strict=False)

component_api = (component_file.parent / '../../api').resolve(strict=False)
component_icon = (component_file.parent / '../base/BaseIcon.vue').resolve(strict=False)

print('test mock api     =', test_mock_api)
print('component api     =', component_api)
print('same api?         =', test_mock_api == component_api)
print('test mock icon    =', test_mock_icon)
print('component icon    =', component_icon)
print('same icon?        =', test_mock_icon == component_icon)
PY

Repository: phpList/web-frontend

Length of output: 498


Point the mocks at the modules CreateAdminModal.vue imports
vi.mock('../../api') and vi.mock('../base/BaseIcon.vue') resolve under tests/Unit/assets/vue/components/settings/, so they miss assets/vue/api.js and assets/vue/components/base/BaseIcon.vue. Use the same module IDs as the component import so the mocks actually apply.

Suggested fix
-vi.mock('../../api', () => ({
+vi.mock('../../../../../../assets/vue/api', () => ({
     adminClient: {
         createAdministrator: createAdministratorMock,
     },
 }))

-vi.mock('../base/BaseIcon.vue', () => ({
+vi.mock('../../../../../../assets/vue/components/base/BaseIcon.vue', () => ({
     default: {
         template: '<span />',
     },
 }))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
vi.mock('../../api', () => ({
adminClient: {
createAdministrator: createAdministratorMock,
},
}))
vi.mock('../base/BaseIcon.vue', () => ({
default: {
template: '<span />',
},
}))
vi.mock('../../../../../../assets/vue/api', () => ({
adminClient: {
createAdministrator: createAdministratorMock,
},
}))
vi.mock('../../../../../../assets/vue/components/base/BaseIcon.vue', () => ({
default: {
template: '<span />',
},
}))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Unit/assets/vue/components/settings/CreateAdminModal.spec.js` around
lines 24 - 34, The mocks in CreateAdminModal.spec.js are targeting paths that
resolve relative to the test file instead of the modules imported by
CreateAdminModal.vue, so they never intercept the real dependencies. Update the
vi.mock calls to use the same module IDs that CreateAdminModal.vue imports for
the API client and BaseIcon component, and verify the mock names still match the
references used in the test setup.

Comment on lines +10 to +20
vi.mock('../../api', () => ({
subscriberAttributesClient: {
createAttributeDefinition: createAttributeDefinitionMock,
},
}))

vi.mock('../base/BaseIcon.vue', () => ({
default: {
template: '<span />',
},
}))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n[File list]\n'
git ls-files 'tests/Unit/assets/vue/components/settings/CreateSubscriberAttributeModal.spec.js' \
             'assets/vue/components/settings/CreateSubscriberAttributeModal.vue' \
             'assets/vue/components/base/BaseIcon.vue' \
             'assets/vue/api*' \
             'tests/Unit/assets/vue/api*' \
             'tests/Unit/assets/vue/components/base/BaseIcon.vue' || true

printf '\n[Spec file excerpt]\n'
sed -n '1,120p' tests/Unit/assets/vue/components/settings/CreateSubscriberAttributeModal.spec.js

printf '\n[Component file excerpt]\n'
sed -n '1,120p' assets/vue/components/settings/CreateSubscriberAttributeModal.vue

printf '\n[Directory paths]\n'
python3 - <<'PY'
from pathlib import Path
spec = Path('tests/Unit/assets/vue/components/settings/CreateSubscriberAttributeModal.spec.js').resolve().parts
comp = Path('assets/vue/components/settings/CreateSubscriberAttributeModal.vue').resolve().parts
print('spec depth:', len(spec), spec)
print('comp depth:', len(comp), comp)
PY

Repository: phpList/web-frontend

Length of output: 7459


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "vi\.mock\(['\"](\.\./|\.\./\.\./)|vi\.mock\(['\"].*api|BaseIcon" tests/Unit/assets/vue/components/settings assets/vue/components/settings -g '*.spec.js' -g '*.spec.ts' -g '*.test.js' -g '*.test.ts'

Repository: phpList/web-frontend

Length of output: 2373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[Spec]"
sed -n '1,40p' tests/Unit/assets/vue/components/settings/CreateSubscriberAttributeModal.spec.js

echo
echo "[Component]"
sed -n '1,80p' assets/vue/components/settings/CreateSubscriberAttributeModal.vue

Repository: phpList/web-frontend

Length of output: 3311


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n[Component script]\n'
sed -n '120,260p' assets/vue/components/settings/CreateSubscriberAttributeModal.vue

printf '\n[API module]\n'
sed -n '1,220p' assets/vue/api.js

Repository: phpList/web-frontend

Length of output: 7547


Fix the mock paths in this spec.

vi.mock('../../api') and vi.mock('../base/BaseIcon.vue') resolve under tests/Unit/assets/vue/components/settings/, so they don’t match the component’s assets/vue/api.js and assets/vue/components/base/BaseIcon.vue imports. That leaves subscriberAttributesClient.createAttributeDefinition unmocked.

🔧 Suggested fix
-vi.mock('../../api', () => ({
+vi.mock('../../../../../../assets/vue/api', () => ({
     subscriberAttributesClient: {
         createAttributeDefinition: createAttributeDefinitionMock,
     },
 }))

-vi.mock('../base/BaseIcon.vue', () => ({
+vi.mock('../../../../../../assets/vue/components/base/BaseIcon.vue', () => ({
     default: {
         template: '<span />',
     },
 }))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
vi.mock('../../api', () => ({
subscriberAttributesClient: {
createAttributeDefinition: createAttributeDefinitionMock,
},
}))
vi.mock('../base/BaseIcon.vue', () => ({
default: {
template: '<span />',
},
}))
vi.mock('../../../../../../assets/vue/api', () => ({
subscriberAttributesClient: {
createAttributeDefinition: createAttributeDefinitionMock,
},
}))
vi.mock('../../../../../../assets/vue/components/base/BaseIcon.vue', () => ({
default: {
template: '<span />',
},
}))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tests/Unit/assets/vue/components/settings/CreateSubscriberAttributeModal.spec.js`
around lines 10 - 20, Fix the Vitest mocks in
CreateSubscriberAttributeModal.spec.js so they intercept the same import paths
used by the component. Update the vi.mock calls for the API module and
BaseIcon.vue to match the component’s assets/vue/api.js and
assets/vue/components/base/BaseIcon.vue imports, ensuring
subscriberAttributesClient.createAttributeDefinition is actually mocked when the
modal tests run.

Comment thread tests/Unit/assets/vue/components/settings/EditAdminModal.spec.js Outdated
Comment on lines +10 to +20
vi.mock('../../api', () => ({
subscriberAttributesClient: {
updateAttributeDefinition: updateAttributeDefinitionMock,
},
}))

vi.mock('../base/BaseIcon.vue', () => ({
default: {
template: '<span />',
},
}))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files ==\n'
git ls-files 'tests/Unit/assets/vue/components/settings/EditSubscriberAttributeModal.spec.js' \
             'assets/vue/components/settings/EditSubscriberAttributeModal.vue' \
             'tests/Unit/assets/vue/components/settings/*.spec.js' \
             'assets/vue/components/base/BaseIcon.vue' \
             'assets/vue/api.*' \
             'assets/vue/api/index.*' \
             'tests/Unit/assets/vue/api.*' \
             'tests/Unit/assets/vue/api/index.*'

printf '\n== spec outline ==\n'
ast-grep outline tests/Unit/assets/vue/components/settings/EditSubscriberAttributeModal.spec.js --view expanded || true

printf '\n== component outline ==\n'
ast-grep outline assets/vue/components/settings/EditSubscriberAttributeModal.vue --view expanded || true

printf '\n== spec excerpt ==\n'
sed -n '1,120p' tests/Unit/assets/vue/components/settings/EditSubscriberAttributeModal.spec.js

printf '\n== component excerpt ==\n'
sed -n '1,140p' assets/vue/components/settings/EditSubscriberAttributeModal.vue

Repository: phpList/web-frontend

Length of output: 8476


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== sibling specs using mocks ==\n'
rg -n "vi\.mock\\(" tests/Unit/assets/vue/components/settings -g '*.spec.js'

printf '\n== search component imports ==\n'
rg -n "from ['\"].*api|from ['\"].*BaseIcon|import .*api|import .*BaseIcon" assets/vue/components/settings/EditSubscriberAttributeModal.vue tests/Unit/assets/vue/components/settings/EditSubscriberAttributeModal.spec.js

Repository: phpList/web-frontend

Length of output: 3651


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== read spec =='
sed -n '1,80p' tests/Unit/assets/vue/components/settings/EditSubscriberAttributeModal.spec.js

echo '== read component =='
sed -n '1,120p' assets/vue/components/settings/EditSubscriberAttributeModal.vue

echo '== nearby sibling spec mocks =='
for f in tests/Unit/assets/vue/components/settings/*.spec.js; do
  echo "--- $f"
  rg -n "vi\.mock\(" "$f" || true
done

Repository: phpList/web-frontend

Length of output: 7851


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== api file ==\n'
sed -n '1,220p' assets/vue/api.js

printf '\n== edit subscriber spec full around update test ==\n'
sed -n '80,220p' tests/Unit/assets/vue/components/settings/EditSubscriberAttributeModal.spec.js

Repository: phpList/web-frontend

Length of output: 8084


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== api symbols ==\n'
rg -n "subscriberAttributesClient|updateAttributeDefinition" assets/vue/api.js assets/vue -g '*.js' -g '*.ts'

Repository: phpList/web-frontend

Length of output: 625


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,240p' assets/vue/api.js

Repository: phpList/web-frontend

Length of output: 4992


Mock the same module ids the component imports.
vi.mock('../../api') and vi.mock('../base/BaseIcon.vue') resolve to tests/Unit/assets/vue/api and tests/Unit/assets/vue/components/base/BaseIcon.vue from this spec, so they won’t intercept EditSubscriberAttributeModal.vue’s assets/vue/api and assets/vue/components/base/BaseIcon.vue imports. Point both mocks at the source modules.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tests/Unit/assets/vue/components/settings/EditSubscriberAttributeModal.spec.js`
around lines 10 - 20, The mocks in EditSubscriberAttributeModal.spec.js are
targeting the wrong module ids, so they do not intercept the imports used by
EditSubscriberAttributeModal.vue. Update the vi.mock calls for the api client
and BaseIcon.vue to point at the same source module paths that the component
imports, using the existing identifiers subscriberAttributesClient and
updateAttributeDefinitionMock so the component receives the intended stubs.

Comment on lines +13 to +24
vi.mock('../../api', () => ({
adminAttributeClient: {
getAttributeDefinitions: getAttributeDefinitionsMock,
deleteAttributeDefinition: deleteAttributeDefinitionMock,
},
}))

vi.mock('../base/BaseIcon.vue', () => ({
default: {
template: '<span />',
},
}))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files 'tests/Unit/assets/vue/components/settings/SettingsAdminAttributes.spec.js' 'assets/vue/components/settings/SettingsAdminAttributes.vue' 'tests/Unit/assets/vue/components/settings/*spec.js' | sed 's#^`#-` #'

printf '\n--- SPEC ---\n'
cat -n tests/Unit/assets/vue/components/settings/SettingsAdminAttributes.spec.js | sed -n '1,220p'

printf '\n--- COMPONENT ---\n'
cat -n assets/vue/components/settings/SettingsAdminAttributes.vue | sed -n '1,260p'

printf '\n--- SIBLING SPECS (mock usage) ---\n'
rg -n "vi\.mock\(" tests/Unit/assets/vue/components/settings/*spec.js

Repository: phpList/web-frontend

Length of output: 21197


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- VITE / VITEST CONFIG ---'
git ls-files | rg '(^|/)(vite|vitest|vite\.config|vitest\.config)\.(js|ts|mjs|cjs|mts|cts)$|(^|/)package\.json$|(^|/)tsconfig.*\.json$' || true

printf '\n--- SEARCH FOR vi.mock PATTERN WITH RELATIVE IMPORTS ---\n'
rg -n "vi\.mock\('(\.\./|\./|\.{2}/)" tests/Unit assets/vue -g '*spec.js' -g '*spec.ts'

printf '\n--- SETTINGS COMPONENT IMPORTS ---\n'
rg -n "from '../base/BaseIcon.vue'|from '../../api'" assets/vue/components/settings/SettingsAdminAttributes.vue assets/vue/components/settings/*.vue

printf '\n--- VITEST DOC HINTS IN REPO ---\n'
rg -n "vi\.mock|mock path|relative to" README.md docs tests/Unit package.json vite.config.* vitest.config.* 2>/dev/null || true

Repository: phpList/web-frontend

Length of output: 15761


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- vitest.config.mjs ---'
cat -n vitest.config.mjs | sed -n '1,220p'

printf '\n--- package.json (relevant bits) ---\n'
cat -n package.json | sed -n '1,220p'

Repository: phpList/web-frontend

Length of output: 1923


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'JS'
const path = require('path');

const testDir = path.posix.dirname('tests/Unit/assets/vue/components/settings/SettingsAdminAttributes.spec.js');
const apiFromTest = path.posix.normalize(path.posix.join(testDir, '../../api'));
const iconFromTest = path.posix.normalize(path.posix.join(testDir, '../base/BaseIcon.vue'));
const apiFromComponent = path.posix.normalize(path.posix.join('assets/vue/components/settings', '../../api'));
const iconFromComponent = path.posix.normalize(path.posix.join('assets/vue/components/settings', '../base/BaseIcon.vue'));

console.log({ apiFromTest, iconFromTest, apiFromComponent, iconFromComponent });
console.log({
  apiRelative: path.posix.relative(testDir, 'assets/vue/api'),
  iconRelative: path.posix.relative(testDir, 'assets/vue/components/base/BaseIcon.vue'),
});
JS

Repository: phpList/web-frontend

Length of output: 508


Point the mocks at the component imports. vi.mock('../../api') and vi.mock('../base/BaseIcon.vue') resolve from tests/Unit/assets/vue/components/settings/, so they miss the ../../api and ../base/BaseIcon.vue imports inside SettingsAdminAttributes.vue. Use the longer relative paths (or a shared alias) so the component’s client and icon are actually mocked.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Unit/assets/vue/components/settings/SettingsAdminAttributes.spec.js`
around lines 13 - 24, The mocks in SettingsAdminAttributes.spec.js are resolving
from the test file location instead of the component’s import paths, so they do
not intercept the imports used by SettingsAdminAttributes.vue. Update the
vi.mock targets for the api client and BaseIcon.vue to match the component’s
actual relative imports (or switch both to a shared alias) so
adminAttributeClient and BaseIcon are properly mocked during the component test.

Comment thread tests/Unit/assets/vue/components/settings/SettingsAdmins.spec.js Outdated
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.

2 participants