Skip to content

feat: add icalendar file download support#2909

Merged
kasya merged 14 commits intoOWASP:mainfrom
Utkarsh-0304:feat/ics_download_support
Dec 24, 2025
Merged

feat: add icalendar file download support#2909
kasya merged 14 commits intoOWASP:mainfrom
Utkarsh-0304:feat/ics_download_support

Conversation

@Utkarsh-0304
Copy link
Contributor

Proposed change

Resolves #2841

This PR adds ICS file download support (using ics package). This rules out existing Google Calendar link functionality and unifies it for any type of calendar service. Additionally, unit and e2e tests are added.

Checklist

  • I read and followed the contributing guidelines
  • I ran make check-test locally and all tests passed
  • I used AI for code, documentation, or tests in this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 14, 2025

Summary by CodeRabbit

  • New Features

    • Calendar control now uses a button labeled "Add to Calendar" that generates and downloads an ICS file, disables while processing, and shows success/error toasts; accessibility labels updated.
    • Added a client-side utility to generate downloadable ICS file URLs.
  • Tests

    • Added unit and end-to-end tests for ICS generation, blob download flow, button interactions, accessibility, and error handling.
  • Chores

    • Added ICS library and test mocks, updated test setup, added spellcheck dictionary entry, and switched JSX runtime setting.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Replaces the Google Calendar link flow with client-side ICS export: adds getIcsFileUrl, switches CalendarButton from an anchor to a button that downloads an .ics via a blob URL, adds the ics dependency and Jest mock, and updates/adds unit and e2e tests and TypeScript JSX runtime setting.

Changes

Cohort / File(s) Change Summary
Dictionary
cspell/custom-dict.txt
Added token ics to the custom spell-check dictionary.
Package & test setup
frontend/package.json, frontend/jest.setup.ts
Added dependency ics (^3.8.1) and a Jest module mock exporting createEvent as jest.fn().
ICS utility
frontend/src/utils/getIcsFileUrl.ts
New default-export getIcsFileUrl(event): Promise<string> — normalizes start/end (string
CalendarButton component
frontend/src/components/CalendarButton.tsx
Replaced anchor + Google Calendar URL with a <button> that calls getIcsFileUrl, programmatically triggers ICS download via a temporary anchor, manages isDownloading state, updates accessible labels/default text to “Add to Calendar”, and handles errors and cleanup (object URL revoke, element removal).
Unit tests
frontend/__tests__/unit/components/CalendarButton.test.tsx, frontend/__tests__/unit/utils/getIcsFileUrl.test.ts
Converted CalendarButton tests to button-based ICS download flow; added unit tests for getIcsFileUrl covering date conversion, end-date adjustment, attribute mapping, blob URL return, and error propagation; added DOM and URL mocks.
End-to-end tests
frontend/__tests__/e2e/pages/CalendarButton.spec.ts, frontend/__tests__/e2e/pages/Home.spec.ts
Added Playwright e2e test verifying ICS download filename and ICS content structure; tightened exact matching for Upcoming Events button in Home e2e test.
Config
frontend/tsconfig.json
Changed JSX runtime from \"preserve\" to \"react-jsx\".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • arkid15r

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly and concisely summarizes the main change: adding ICS file download support for calendar events.
Description check ✅ Passed The description is directly related to the changeset, explaining the addition of ICS file download support and the removal of Google Calendar links, plus comprehensive testing additions.
Linked Issues check ✅ Passed The pull request fully addresses issue #2841 objectives: replaces Google Calendar links with ICS download support, maintains modularity, and includes comprehensive unit and e2e tests.
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issue objectives. Minor configuration updates (tsconfig.json JSX handling) support the implementation without introducing unrelated functionality.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff7439 and 8a5cdfa.

📒 Files selected for processing (1)
  • frontend/__tests__/unit/components/CalendarButton.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/tests/unit/components/CalendarButton.test.tsx

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
Contributor

@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: 5

🧹 Nitpick comments (4)
frontend/jest.setup.ts (1)

125-129: Make the ics mock ESM-safe for named imports.
To reduce flakiness with Jest’s ESM resolution, return __esModule: true as well.

 jest.mock('ics', () => {
   return {
+    __esModule: true,
     createEvent: jest.fn(),
   }
 })
frontend/__tests__/unit/components/CalendarButton.test.tsx (1)

79-108: Add cleanup assertions once production code revokes the blob URL / removes the temp link.
Right now the test only verifies creation + click; after implementing cleanup in frontend/src/components/CalendarButton.tsx, please also assert removeChild (or link.remove()) and URL.revokeObjectURL(mockUrl) were called.

frontend/src/components/CalendarButton.tsx (1)

39-52: Set type="button" to avoid accidental form submits.

     <button
+      type="button"
       onClick={handleDownload}
       onMouseEnter={() => setIsHovered(true)}
       onMouseLeave={() => setIsHovered(false)}
       disabled={isDownloading}
       aria-label={ariaLabel}
       className={className}
     >
frontend/src/utils/getIcsFileUrl.ts (1)

27-50: Optional refactoring: Conditionally set optional EventAttributes fields for clarity.

The ics library gracefully handles undefined values in EventAttributes—omitting them from the output rather than causing errors. While the current code works correctly as written, you may consider using a cleaner pattern if the codebase prefers explicit field presence:

const eventAttributes: EventAttributes = {
  start: startArray,
  end: finalEndArray,
  title: event.title,
  status: 'CONFIRMED',
  busyStatus: 'BUSY',
  ...(event.description && { description: event.description }),
  ...(event.location && { location: event.location }),
  ...(event.url && { url: event.url }),
}

This pattern is purely stylistic—no functional change is required.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 670fdff and bc6a847.

⛔ Files ignored due to path filters (1)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • cspell/custom-dict.txt (1 hunks)
  • frontend/__tests__/e2e/pages/CalendarButton.spec.ts (1 hunks)
  • frontend/__tests__/unit/components/CalendarButton.test.tsx (9 hunks)
  • frontend/__tests__/unit/utils/getIcsFileUrl.test.ts (1 hunks)
  • frontend/jest.setup.ts (1 hunks)
  • frontend/package.json (1 hunks)
  • frontend/src/components/CalendarButton.tsx (1 hunks)
  • frontend/src/utils/getIcsFileUrl.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
frontend/__tests__/unit/components/CalendarButton.test.tsx (1)
frontend/src/components/CalendarButton.tsx (1)
  • CalendarButton (9-54)
frontend/src/components/CalendarButton.tsx (1)
frontend/src/types/calendar.ts (1)
  • CalendarButtonProps (12-19)
frontend/__tests__/unit/utils/getIcsFileUrl.test.ts (1)
frontend/src/utils/getIcsFileUrl.ts (1)
  • getIcsFileUrl (4-52)
🔇 Additional comments (2)
cspell/custom-dict.txt (1)

68-73: LGTM (dictionary update).

frontend/package.json (1)

49-52: No compatibility issues found with ics@^3.8.1. The package supports both ESM (import * as ics from 'ics') and CommonJS formats, and both status and busyStatus fields are available in the EventAttributes. This dependency is compatible with Jest 30 ESM setup and Next.js bundling.

Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

Please address coderabbit's comments first

Copy link
Contributor

@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: 0

🧹 Nitpick comments (3)
frontend/__tests__/unit/components/CalendarButton.test.tsx (3)

28-31: Consider moving URL mocks to beforeEach for test isolation.

Global mocks set in beforeAll persist across all tests without reset. While this works here since the mocks aren't directly exercised (generateIcsFileUrl is fully mocked), moving them to beforeEach would ensure cleaner test isolation and align with the pattern used for other mocks.

-  beforeAll(() => {
-    global.URL.createObjectURL = jest.fn(() => 'mock-url');
-    global.URL.revokeObjectURL = jest.fn();
-  });
-
   beforeEach(() => {
+    global.URL.createObjectURL = jest.fn(() => 'mock-url');
+    global.URL.revokeObjectURL = jest.fn();
     ;(generateIcsFileUrl as jest.Mock).mockResolvedValue(mockUrl)

85-113: Consider adding assertions for cleanup behavior.

The test validates the download flow but doesn't verify that cleanup occurs (URL revocation and anchor removal). Since the cleanup logic was specifically added to fix a memory leak, testing it would add confidence.

       await waitFor(() => {
         expect(button).not.toBeDisabled()
       })
+
+      // Verify cleanup
+      expect(global.URL.revokeObjectURL).toHaveBeenCalledWith(mockUrl)
+      expect(createdLink.parentNode).toBeNull() // anchor removed from DOM
     })

99-106: The link-finding logic is fragile; consider a simpler approach.

Searching through createSpy.mock.results by href works but is complex. A cleaner approach would be to capture the anchor directly when it's appended.

-      const createdLink = createSpy.mock.results.find(
-        (call) => call.value instanceof HTMLAnchorElement && call.value.href === mockUrl
-      )?.value
-
-      expect(createdLink).toBeDefined()
-      expect(createdLink.download).toBe('invite.ics')
-
-      expect(appendSpy).toHaveBeenCalledWith(createdLink)
+      // Get the anchor that was appended
+      const appendedElement = appendSpy.mock.calls[0]?.[0] as HTMLAnchorElement
+      expect(appendedElement).toBeInstanceOf(HTMLAnchorElement)
+      expect(appendedElement.href).toBe(mockUrl)
+      expect(appendedElement.download).toBe('invite.ics')
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc6a847 and 775426f.

📒 Files selected for processing (6)
  • frontend/__tests__/e2e/pages/CalendarButton.spec.ts (1 hunks)
  • frontend/__tests__/unit/components/CalendarButton.test.tsx (9 hunks)
  • frontend/__tests__/unit/utils/getIcsFileUrl.test.ts (1 hunks)
  • frontend/jest.setup.ts (1 hunks)
  • frontend/src/components/CalendarButton.tsx (1 hunks)
  • frontend/src/utils/getIcsFileUrl.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • frontend/tests/e2e/pages/CalendarButton.spec.ts
  • frontend/src/utils/getIcsFileUrl.ts
  • frontend/jest.setup.ts
  • frontend/tests/unit/utils/getIcsFileUrl.test.ts
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/__tests__/unit/components/CalendarButton.test.tsx (1)
frontend/src/components/CalendarButton.tsx (1)
  • CalendarButton (9-59)
frontend/src/components/CalendarButton.tsx (1)
frontend/src/types/calendar.ts (1)
  • CalendarButtonProps (12-19)
🔇 Additional comments (5)
frontend/src/components/CalendarButton.tsx (2)

23-41: Good implementation of cleanup logic.

The previous concern about memory leaks has been properly addressed. The anchor element is now removed and the blob URL is revoked in the finally block, ensuring cleanup happens regardless of success or failure.


44-57: LGTM!

The button implementation is well-structured with proper accessibility (aria-label), loading state management (disabled={isDownloading}), and hover interactions for the icon toggle.

frontend/__tests__/unit/components/CalendarButton.test.tsx (3)

48-82: LGTM!

The rendering tests comprehensively cover the component's various rendering scenarios including default state, custom icons, and custom JSX elements.


115-129: Good error handling test coverage.

The test properly verifies that errors are handled gracefully with an alert and that the button returns to an enabled state after failure.


132-144: LGTM!

Accessibility tests and label prop tests properly verify the updated labels ("Add to Calendar" instead of "Add to Google Calendar") and the aria-label behavior.

Also applies to: 206-234

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 15, 2025
@Utkarsh-0304 Utkarsh-0304 changed the title feat: added ICS file download support feat: add icalendar file download support Dec 15, 2025
Copy link
Contributor

@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: 0

🧹 Nitpick comments (3)
frontend/src/utils/getIcsFileUrl.ts (1)

6-9: Redundant window check after successful entry guard.

The check at line 6 already guards against server-side execution by rejecting early. The callback at lines 49-54 will only execute if we passed line 6, making the second check unreachable in practice.

     createEvent(eventAttributes, (error, value) => {
       if (error) {
         reject(error)
         return
       }
 
-      if (typeof globalThis.window !== 'undefined') {
-        const blob = new Blob([value], { type: 'text/calendar;charset=utf-8' })
-        resolve(globalThis.URL.createObjectURL(blob))
-      } else {
-        reject(new Error('Window not defined (server-side generation not supported)'))
-      }
+      const blob = new Blob([value], { type: 'text/calendar;charset=utf-8' })
+      resolve(globalThis.URL.createObjectURL(blob))
     })

Also applies to: 49-54

frontend/__tests__/unit/components/CalendarButton.test.tsx (2)

8-8: Consider consistent naming with the source module.

The import alias generateIcsFileUrl differs from the source function name getIcsFileUrl. While valid, using the original name improves traceability.

-import generateIcsFileUrl from 'utils/getIcsFileUrl'
+import getIcsFileUrl from 'utils/getIcsFileUrl'

Then update usages throughout the file (e.g., lines 11, 31, 91, 110).


28-43: Consider using jest.spyOn for URL methods to ensure proper cleanup.

Lines 29-30 directly assign to globalThis.URL methods, but jest.restoreAllMocks() only restores spies. Since beforeEach reassigns them for each test, this works within this file, but using spies would be more consistent with the pattern used for appendChild, createElement, etc.

   beforeEach(() => {
-    globalThis.URL.createObjectURL = jest.fn(() => 'mock-url');
-    globalThis.URL.revokeObjectURL = jest.fn();
+    jest.spyOn(globalThis.URL, 'createObjectURL').mockReturnValue('mock-url')
+    jest.spyOn(globalThis.URL, 'revokeObjectURL').mockImplementation(() => {})
     ;(generateIcsFileUrl as jest.Mock).mockResolvedValue(mockUrl)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 775426f and c58ea57.

📒 Files selected for processing (4)
  • frontend/__tests__/e2e/pages/CalendarButton.spec.ts (1 hunks)
  • frontend/__tests__/unit/components/CalendarButton.test.tsx (9 hunks)
  • frontend/__tests__/unit/utils/getIcsFileUrl.test.ts (1 hunks)
  • frontend/src/utils/getIcsFileUrl.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/tests/e2e/pages/CalendarButton.spec.ts
🔇 Additional comments (6)
frontend/__tests__/unit/utils/getIcsFileUrl.test.ts (2)

4-26: LGTM!

The test setup correctly captures and restores globalThis.URL.createObjectURL. The mock event provides comprehensive coverage of the expected input shape, and the afterEach cleanup ensures no mock state leaks between tests.


28-128: Solid test coverage for the ICS utility.

The tests comprehensively cover:

  • Successful blob URL generation
  • String and Date object formatting (including 0-indexed month correction)
  • Same-day event end date adjustment
  • Error propagation from createEvent
  • All event attribute passthrough
frontend/src/utils/getIcsFileUrl.ts (2)

10-26: LGTM on date handling logic.

formatDate correctly normalizes both string and Date inputs, handling the 0-indexed month from Date.getMonth(). The getEndDate function properly uses Date arithmetic to handle month/year boundary rollover when incrementing the day for same-day events.


32-55: Clean implementation of ICS generation.

The conditional spreading for optional fields (description, location, url) keeps the attributes object minimal. The blob MIME type text/calendar;charset=utf-8 is correct for ICS files.

frontend/__tests__/unit/components/CalendarButton.test.tsx (2)

81-122: Good coverage of the async download flow.

The tests properly verify:

  • Button disables during download
  • getIcsFileUrl is called with event data
  • Anchor element is created, appended, and clicked with correct attributes
  • Button re-enables after success or failure
  • Error path shows user-friendly alert

125-353: Comprehensive prop and scenario coverage.

The tests thoroughly validate accessibility attributes, styling props, label behavior, icon extensibility, and real-world usage patterns. The transition from anchor to button element is consistently applied throughout.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 15, 2025
Copy link
Contributor

@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: 0

🧹 Nitpick comments (2)
frontend/src/utils/getIcsFileUrl.ts (1)

10-17: Consider renaming local formatDate to avoid confusion with shared util

There is already a formatDate helper in frontend/src/utils/dateFormatter.ts with different behavior. Using the same name here for a very ICS‑specific transformer can be confusing.

Consider renaming this to something like toIcsDateArray (and possibly moving it to a shared ICS/date helper later) to make intent clearer and avoid mental clashes with the generic formatter.

frontend/__tests__/unit/components/CalendarButton.test.tsx (1)

28-31: Use jest.spyOn for URL methods instead of direct reassignment

beforeEach replaces globalThis.URL.createObjectURL and globalThis.URL.revokeObjectURL with jest.fn, but afterEach only calls jest.restoreAllMocks(), which won’t restore these direct assignments. This can leak mocked URL behavior into other tests.

Consider switching to spies so restoreAllMocks fully cleans up:

-  beforeEach(() => {
-    globalThis.URL.createObjectURL = jest.fn(() => 'mock-url')
-    globalThis.URL.revokeObjectURL = jest.fn()
+  beforeEach(() => {
+    jest.spyOn(globalThis.URL, 'createObjectURL').mockReturnValue('mock-url')
+    jest.spyOn(globalThis.URL, 'revokeObjectURL').mockImplementation(() => {})
     ;(getIcsFileUrl as jest.Mock).mockResolvedValue(mockUrl)
@@
-  afterEach(() => {
-    jest.restoreAllMocks()
-  })
+  afterEach(() => {
+    jest.restoreAllMocks()
+  })

This keeps the behavior the same for this suite while avoiding cross‑test pollution.

Also applies to: 41-43

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c58ea57 and c9e7e22.

📒 Files selected for processing (2)
  • frontend/__tests__/unit/components/CalendarButton.test.tsx (9 hunks)
  • frontend/src/utils/getIcsFileUrl.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/utils/getIcsFileUrl.ts (1)
frontend/src/utils/dateFormatter.ts (1)
  • formatDate (1-20)
🔇 Additional comments (2)
frontend/src/utils/getIcsFileUrl.ts (1)

19-30: Verify inclusive/exclusive semantics for multi‑day all‑day events

ICS all‑day events typically treat end as exclusive. Here, getEndDate only adds a day when start === end, but multi‑day events where endDate is stored as an inclusive date (e.g., 2–3 Dec) would be passed through unchanged and may appear one day shorter in calendar clients.

Please double‑check how CalendarEvent.endDate is defined upstream (inclusive vs exclusive) and adjust getEndDate or add tests accordingly if your domain model is inclusive for multi‑day all‑day events.

frontend/__tests__/unit/components/CalendarButton.test.tsx (1)

81-127: ICS download and UI behavior tests look comprehensive

The tests around the download flow, error handling, ARIA labels, className/icon behavior, and various layout scenarios are thorough and closely aligned with the new button‑based ICS UX. Assertions on disabled state, getIcsFileUrl calls, link creation, and accessibility attributes give good confidence in the component behavior.

Also applies to: 129-357

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 15, 2025
Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

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

@Utkarsh-0304 please resolve conflicts 👍🏼
Also, there's one new issue introduced with this PR found by Sonar - please address that too

Image

Copy link
Contributor

@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: 1

🧹 Nitpick comments (1)
frontend/__tests__/e2e/pages/CalendarButton.spec.ts (1)

36-38: Consider using non-null assertion for type safety.

After the truthy assertion, TypeScript may still consider path as potentially null. Using path! explicitly communicates the intent.

     const path = await download.path()
     expect(path, 'Expected Playwright to provide a download path').toBeTruthy()
-    const content = fs.readFileSync(path, 'utf-8')
+    const content = fs.readFileSync(path!, 'utf-8')
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c9e7e22 and 3eaf0d2.

⛔ Files ignored due to path filters (1)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • cspell/custom-dict.txt (1 hunks)
  • frontend/__tests__/e2e/pages/CalendarButton.spec.ts (1 hunks)
  • frontend/__tests__/e2e/pages/Home.spec.ts (1 hunks)
  • frontend/__tests__/unit/components/CalendarButton.test.tsx (9 hunks)
  • frontend/__tests__/unit/utils/getIcsFileUrl.test.ts (1 hunks)
  • frontend/jest.setup.ts (1 hunks)
  • frontend/package.json (1 hunks)
  • frontend/src/components/CalendarButton.tsx (1 hunks)
  • frontend/src/utils/getIcsFileUrl.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • frontend/jest.setup.ts
  • frontend/package.json
  • cspell/custom-dict.txt
  • frontend/src/utils/getIcsFileUrl.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/e2e/pages/Home.spec.ts
  • frontend/__tests__/e2e/pages/CalendarButton.spec.ts
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/e2e/pages/Home.spec.ts
🧬 Code graph analysis (2)
frontend/src/components/CalendarButton.tsx (2)
frontend/src/types/calendar.ts (1)
  • CalendarButtonProps (12-19)
frontend/src/utils/getIcsFileUrl.ts (1)
  • getIcsFileUrl (4-53)
frontend/__tests__/unit/components/CalendarButton.test.tsx (2)
frontend/src/utils/getIcsFileUrl.ts (1)
  • getIcsFileUrl (4-53)
frontend/src/components/CalendarButton.tsx (1)
  • CalendarButton (9-60)
🪛 Biome (2.1.2)
frontend/__tests__/unit/components/CalendarButton.test.tsx

[error] 33-44: Disallow duplicate setup and teardown hooks.

Disallow beforeEach duplicacy inside the describe function.

(lint/suspicious/noDuplicateTestHooks)

🔇 Additional comments (7)
frontend/src/components/CalendarButton.tsx (1)

1-59: Clean implementation of ICS download flow.

The component correctly:

  • Uses 'use client' directive for client-side functionality
  • Manages download state to prevent duplicate clicks
  • Cleans up the temporary anchor and revokes the blob URL in the finally block (addressing previous feedback)
  • Provides accessible button with appropriate aria-label
frontend/__tests__/e2e/pages/Home.spec.ts (1)

103-105: Test selectors correctly updated for button-based calendar control.

The change from link to button selector aligns with the CalendarButton component refactor. Using exact: true ensures precise element matching.

frontend/__tests__/e2e/pages/CalendarButton.spec.ts (1)

24-46: Solid e2e test for ICS download validation.

The test correctly:

  • Sets up download listener before triggering the click
  • Validates the suggested filename
  • Asserts download path availability before reading (addressing previous feedback)
  • Validates essential ICS structure elements
frontend/__tests__/unit/utils/getIcsFileUrl.test.ts (2)

4-26: Proper restoration of URL.createObjectURL (previous feedback addressed).

The setup now correctly captures and restores originalCreateObjectURL instead of the entire URL object, ensuring the mock is properly cleaned up.


28-128: Comprehensive test coverage for getIcsFileUrl.

The tests thoroughly validate:

  • Successful blob URL generation
  • String and Date object parsing with correct month handling
  • Single-day event end date adjustment
  • Error propagation
  • Complete attribute forwarding to createEvent
frontend/__tests__/unit/components/CalendarButton.test.tsx (2)

86-131: Well-structured download functionality tests.

The tests correctly validate:

  • Button disabled state during download
  • getIcsFileUrl called with event data
  • Temporary anchor creation with correct download attribute
  • Click triggered on the anchor
  • Button re-enabled after completion
  • Error handling with alert fallback

134-361: Thorough coverage of component props and edge cases.

Tests comprehensively cover accessibility attributes, className/iconClassName application, label behavior, icon extensibility, reusability scenarios, and long title handling. All selectors correctly use button role matching the component refactor.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (1)
frontend/src/components/CalendarButton.tsx (1)

30-30: Consider including event title in the download filename.

The hardcoded 'invite.ics' works, but a more descriptive filename would improve UX when users have multiple calendar files in their downloads folder.

-      link.setAttribute('download', 'invite.ics')
+      const sanitizedTitle = safeTitle.replace(/[^a-zA-Z0-9-_]/g, '-').substring(0, 50)
+      link.setAttribute('download', `${sanitizedTitle}.ics`)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between efddb53 and a9e9fc8.

📒 Files selected for processing (2)
  • frontend/__tests__/unit/components/CalendarButton.test.tsx (9 hunks)
  • frontend/src/components/CalendarButton.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
🧬 Code graph analysis (2)
frontend/src/components/CalendarButton.tsx (2)
frontend/src/types/calendar.ts (1)
  • CalendarButtonProps (12-19)
frontend/src/utils/getIcsFileUrl.ts (1)
  • getIcsFileUrl (4-53)
frontend/__tests__/unit/components/CalendarButton.test.tsx (2)
frontend/src/utils/getIcsFileUrl.ts (1)
  • getIcsFileUrl (4-53)
frontend/src/components/CalendarButton.tsx (1)
  • CalendarButton (8-62)
🔇 Additional comments (7)
frontend/__tests__/unit/components/CalendarButton.test.tsx (4)

1-42: Test setup looks good with proper mocking and cleanup.

The test setup correctly mocks the ICS generation flow with:

  • getIcsFileUrl mock returning a blob URL
  • DOM manipulation spies for appendChild, createElement, and click
  • alert mock for error handling tests
  • Proper cleanup via jest.restoreAllMocks() in afterEach

77-122: Comprehensive download flow and error handling tests.

The tests properly validate:

  1. Button disables during download (expect(button).toBeDisabled())
  2. getIcsFileUrl is called with the event
  3. Temporary anchor is created with correct download attribute
  4. Anchor is appended and clicked
  5. Button re-enables after completion
  6. Error path shows alert and keeps button enabled

125-137: Accessibility tests correctly updated for new labeling.

The aria-label format "Add {title} to Calendar" aligns with the component implementation. Tests verify both normal titles and fallback behavior.


199-227: Label tests correctly reflect the new default label.

Tests properly verify:

  • Default label "Add to Calendar" is hidden unless showLabel is true
  • Custom labels override the default
  • Labels are not rendered without showLabel prop
frontend/src/components/CalendarButton.tsx (3)

1-6: Client directive and imports are appropriate.

The 'use client' directive is correctly placed for this component that uses React hooks (useState) and browser APIs (document.createElement, URL.revokeObjectURL).


22-40: Download handler properly manages resources and state.

The implementation correctly:

  1. Tracks download state to disable the button
  2. Creates a temporary anchor for programmatic download
  3. Cleans up the anchor element via link.remove()
  4. Revokes the blob URL to prevent memory leaks
  5. Resets state in finally block ensuring cleanup on both success and failure

43-60: Button implementation is well-structured with proper accessibility.

The button element includes:

  • type="button" to prevent accidental form submission
  • disabled={isDownloading} to prevent duplicate downloads
  • Proper aria-label and title for screen readers and tooltips
  • Preserved hover state icon swap behavior

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 17, 2025
Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

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

@Utkarsh-0304 thanks for working on this!
Works great 👍🏼
Left some requests for UI\UX.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we change this to be a toast? I feel like that is a better UX.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have replaced 'alert' with 'heroui-toast' to enhance the user experience.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we use event name here instead of hardcoded invite.ics?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have updated the code to use the slugify utility function to parse the event title and display that in place of a hardcoded value.

Copy link
Collaborator

@kasya kasya Dec 18, 2025

Choose a reason for hiding this comment

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

Could you update the UI? After switching to a button the whole line is just jumbled there.
Here's current view on this branch:

Image

And here's current view in prod:

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I adjusted the className attribute in the button tag to address this UI issue.

Copy link
Contributor

@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: 1

🧹 Nitpick comments (2)
frontend/__tests__/unit/components/CalendarButton.test.tsx (1)

84-112: Add explicit verification of resource cleanup.

While the test verifies the button re-enables (indirectly confirming the finally block runs), it doesn't explicitly assert that URL.revokeObjectURL and link.remove() are called. These cleanup operations are critical for preventing memory leaks (as addressed in past reviews).

🔎 Add assertions to verify cleanup
      expect(clickSpy).toHaveBeenCalled()

      await waitFor(() => {
        expect(button).not.toBeDisabled()
      })
+
+      expect(globalThis.URL.revokeObjectURL).toHaveBeenCalledWith(mockUrl)
+      expect(createdLink.remove).toHaveBeenCalled()

Note: You'll need to spy on the remove method when creating the link:

      const createdLink = createSpy.mock.results.find(
        (call) => call.value instanceof HTMLAnchorElement && call.value.href === mockUrl
      )?.value

      expect(createdLink).toBeDefined()
+     const removeSpy = jest.spyOn(createdLink, 'remove')
      expect(createdLink.download).toBe(`${slugify(mockEvent.title)}.ics`)
frontend/src/components/CalendarButton.tsx (1)

36-43: Consider standard sentence capitalization for the toast description.

The description starts with a lowercase letter: "couldn't export your calendar...". While this may be intentional for visual hierarchy, standard sentence case would be "Couldn't export your calendar..."

🔎 Apply standard capitalization
       addToast({
-        description: "couldn't export your calendar. Please try again.",
+        description: "Couldn't export your calendar. Please try again.",
         title: 'Download Failed',
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9e9fc8 and f874d97.

📒 Files selected for processing (3)
  • frontend/__tests__/e2e/pages/CalendarButton.spec.ts (1 hunks)
  • frontend/__tests__/unit/components/CalendarButton.test.tsx (9 hunks)
  • frontend/src/components/CalendarButton.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/tests/e2e/pages/CalendarButton.spec.ts
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
🧬 Code graph analysis (2)
frontend/__tests__/unit/components/CalendarButton.test.tsx (2)
frontend/src/utils/getIcsFileUrl.ts (1)
  • getIcsFileUrl (4-53)
frontend/src/components/CalendarButton.tsx (1)
  • CalendarButton (10-71)
frontend/src/components/CalendarButton.tsx (2)
frontend/src/types/calendar.ts (1)
  • CalendarButtonProps (12-19)
frontend/src/utils/getIcsFileUrl.ts (1)
  • getIcsFileUrl (4-53)
🔇 Additional comments (6)
frontend/__tests__/unit/components/CalendarButton.test.tsx (3)

1-48: LGTM! Test setup is comprehensive and correct.

The JSDOM environment directive, mocks for getIcsFileUrl and addToast, URL API mocks, and spies are all properly configured. The duplicate beforeEach issue mentioned in past reviews has been resolved.


114-135: LGTM! Error handling test is comprehensive.

The test properly verifies the error path: toast notification with correct content, and button re-enabling. Good coverage of the failure scenario.


50-363: LGTM! Test suite is comprehensive and well-structured.

All tests have been properly updated to reflect the button-based implementation:

  • Rendering tests verify button element and icons
  • Accessibility tests ensure proper ARIA attributes
  • Styling tests confirm className application
  • Edge case tests cover long titles and flex container scenarios

The test structure follows project patterns and provides excellent coverage.

frontend/src/components/CalendarButton.tsx (3)

1-9: LGTM! Client directive and imports are appropriate.

The 'use client' directive is necessary for this component's client-side state and DOM manipulation. All imports support the new ICS download functionality.


10-23: LGTM! State and label updates are appropriate.

The isDownloading state provides good UX by preventing multiple simultaneous downloads. Label changes correctly reflect the generic calendar support (ICS format works with any calendar app, not just Google Calendar).


51-71: LGTM! Button implementation is correct and accessible.

The button properly:

  • Prevents form submission with type="button"
  • Disables during download for good UX
  • Provides accessibility with aria-label and title
  • Combines className props correctly
  • Maintains the hover-driven icon swap behavior

@Utkarsh-0304 Utkarsh-0304 requested a review from kasya December 18, 2025 17:48
Comment on lines 35 to 36
Copy link
Collaborator

Choose a reason for hiding this comment

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

@Utkarsh-0304 for this particular case you can add a logger and a comment for eslint to ignore this. I believe we have the same setup somewhere in the code already 👌🏼

Suggested change
} catch {
addToast({
} catch {
// eslint-disable-next-line no-console
console.error('Failed to download ICS file:', error)
addToast({

Copy link
Contributor

@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: 0

🧹 Nitpick comments (1)
frontend/src/components/CalendarButton.tsx (1)

32-32: Consider using safeTitle for download filename consistency.

The download attribute uses slugify(event.title) directly, while line 21 defines safeTitle as a fallback for empty titles. For consistency with the accessibility labels (line 22) and better handling of edge cases, consider using safeTitle here:

-      link.setAttribute('download', `${slugify(event.title)}.ics`)
+      link.setAttribute('download', `${slugify(safeTitle)}.ics`)

This ensures that if event.title is empty or undefined, the filename defaults to event.ics instead of just .ics.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f874d97 and 8b89933.

📒 Files selected for processing (2)
  • frontend/__tests__/unit/components/CalendarButton.test.tsx
  • frontend/src/components/CalendarButton.tsx
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-09-17T02:42:41.928Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2288
File: frontend/src/components/ActionButton.tsx:0-0
Timestamp: 2025-09-17T02:42:41.928Z
Learning: In frontend/src/components/ActionButton.tsx, the user Rajgupta36 intentionally changed text-blue-600 to text-[#1D7BD7] to align the text color with the border color (#1D7BD7) for visual consistency, prioritizing design alignment over theme tokens.

Applied to files:

  • frontend/src/components/CalendarButton.tsx
📚 Learning: 2025-06-18T20:00:23.899Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1634
File: frontend/src/app/api/auth/[...nextauth]/route.ts:30-55
Timestamp: 2025-06-18T20:00:23.899Z
Learning: The OWASP Nest application has logging disabled, so avoid suggesting console.log, console.error, or any other logging statements in code review suggestions.

Applied to files:

  • frontend/src/components/CalendarButton.tsx
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
🧬 Code graph analysis (1)
frontend/src/components/CalendarButton.tsx (2)
frontend/src/types/calendar.ts (1)
  • CalendarButtonProps (12-19)
frontend/src/utils/getIcsFileUrl.ts (1)
  • getIcsFileUrl (4-53)
🔇 Additional comments (5)
frontend/__tests__/unit/components/CalendarButton.test.tsx (3)

1-48: LGTM! Test setup is comprehensive.

The test setup properly mocks all dependencies (getIcsFileUrl, @heroui/toast, URL APIs) and creates spies for DOM interactions. The beforeEach and afterEach hooks correctly initialize and restore mocks for each test.


84-141: Excellent coverage of download flow and error handling.

The tests thoroughly verify:

  • The complete async download workflow with proper state management (disabled during download)
  • DOM manipulation (anchor creation, attributes, append, click)
  • Error handling with toast notification and console logging
  • Cleanup and state restoration in both success and failure paths

The use of waitFor appropriately handles the asynchronous nature of the download flow.


50-369: Well-structured tests with thorough coverage.

The test suite comprehensively covers:

  • Rendering variations (default icon, custom icons, JSX elements)
  • All prop combinations (className, iconClassName, showLabel, label)
  • Accessibility attributes and ARIA labels with calendar-agnostic wording
  • Reusability scenarios for different contexts (homepage, poster page)
  • Edge cases (long titles, flex container interactions)

All tests properly updated from anchor-based to button-based assertions with appropriate role queries and element checks.

frontend/src/components/CalendarButton.tsx (2)

24-51: Download handler is well-implemented with proper cleanup.

The handleDownload function correctly:

  • Manages download state with isDownloading
  • Creates and triggers a temporary anchor for download
  • Handles errors with toast notification and logging (with appropriate eslint disable)
  • Cleans up DOM elements and revokes object URLs in the finally block
  • Re-enables the button after completion or error

The resource management and error handling are solid.


53-72: Button implementation is accessible and properly configured.

The button element correctly:

  • Uses semantic type="button" to prevent form submission
  • Binds the handleDownload handler to onClick
  • Disables during download with the isDownloading state
  • Provides accessible labels with aria-label and title attributes
  • Maintains hover interaction for icon animation
  • Combines user-provided className with layout classes

The transition from anchor to button preserves all intended functionality while providing better semantics for a download action.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 22, 2025
@Utkarsh-0304 Utkarsh-0304 requested a review from kasya December 22, 2025 15:45
Comment on lines +46 to +49
} finally {
if (link) link.remove()
if (url) URL.revokeObjectURL(url)
setIsDownloading(false)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@Utkarsh-0304 this works great! One more suggestion - can we show a success toast on successful download of the file? It happens so quickly that user can easily miss anything was downloaded.

Suggested change
} finally {
if (link) link.remove()
if (url) URL.revokeObjectURL(url)
setIsDownloading(false)
} finally {
if (link) link.remove()
if (url) URL.revokeObjectURL(url)
setIsDownloading(false)
addToast({
description: 'Successfully downloaded ICS file',
title: `${event.title}`,
timeout: 3000,
shouldShowTimeoutProgress: true,
color: 'success',
variant: 'solid',
})

Copy link
Contributor

@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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b89933 and 1220e3f.

📒 Files selected for processing (3)
  • frontend/__tests__/unit/components/CalendarButton.test.tsx
  • frontend/src/components/CalendarButton.tsx
  • frontend/tsconfig.json
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-09-17T02:42:41.928Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2288
File: frontend/src/components/ActionButton.tsx:0-0
Timestamp: 2025-09-17T02:42:41.928Z
Learning: In frontend/src/components/ActionButton.tsx, the user Rajgupta36 intentionally changed text-blue-600 to text-[#1D7BD7] to align the text color with the border color (#1D7BD7) for visual consistency, prioritizing design alignment over theme tokens.

Applied to files:

  • frontend/src/components/CalendarButton.tsx
📚 Learning: 2025-06-18T20:00:23.899Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1634
File: frontend/src/app/api/auth/[...nextauth]/route.ts:30-55
Timestamp: 2025-06-18T20:00:23.899Z
Learning: The OWASP Nest application has logging disabled, so avoid suggesting console.log, console.error, or any other logging statements in code review suggestions.

Applied to files:

  • frontend/src/components/CalendarButton.tsx
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
🧬 Code graph analysis (1)
frontend/__tests__/unit/components/CalendarButton.test.tsx (2)
frontend/src/utils/getIcsFileUrl.ts (1)
  • getIcsFileUrl (4-53)
frontend/src/components/CalendarButton.tsx (1)
  • CalendarButton (10-81)
🔇 Additional comments (5)
frontend/__tests__/unit/components/CalendarButton.test.tsx (1)

1-49: LGTM! Test setup is well-structured.

The test environment configuration, mocks, and lifecycle hooks are properly configured. The duplicate beforeEach issue from the previous review has been addressed.

frontend/tsconfig.json (1)

14-14: LGTM! JSX configuration aligns with React 19.

The change to "react-jsx" enables the automatic JSX runtime, which is the recommended approach for React 19 and eliminates the need to import React in every component file.

frontend/src/components/CalendarButton.tsx (3)

1-23: LGTM! Component setup and state management.

The imports, state declarations, and accessibility labels are well-structured and align with the ICS download functionality.


35-45: LGTM! Error handling with user feedback.

The error handling correctly logs the error (as requested in past reviews) and displays an appropriate error toast to the user.


62-80: LGTM! Button implementation with accessibility.

The button is properly implemented with:

  • Correct type and click handling
  • Disabled state during download
  • Accessible labels and titles
  • Hover state interaction

Copy link
Contributor

@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: 0

♻️ Duplicate comments (1)
frontend/__tests__/unit/components/CalendarButton.test.tsx (1)

122-149: Test gap: Still missing assertion that success toast is NOT called on error.

This test verifies the error toast is shown but doesn't assert that the success toast is NOT called. This is the same gap flagged in the previous review (lines 122-149) and remains unaddressed. Without this assertion, a regression where the success toast appears in the finally block would pass this test.

🔎 Add assertion to verify only error toast called

After line 145, add:

         expect(consoleSpy).toHaveBeenCalledWith(
           expect.stringContaining('Failed to download ICS file'),
           errorMock
         )
+        // Verify only the error toast was called, not the success toast
+        expect(addToast).toHaveBeenCalledTimes(1)
       })

Alternatively, explicitly verify the success toast was not called:

       expect(button).not.toBeDisabled()
+      
+      // Verify success toast was NOT called
+      expect(addToast).not.toHaveBeenCalledWith(
+        expect.objectContaining({
+          color: 'success',
+          title: mockEvent.title,
+        })
+      )
       consoleSpy.mockRestore()
🧹 Nitpick comments (1)
frontend/src/components/CalendarButton.tsx (1)

46-53: Consider using event title in error toast for consistency.

The success toast uses the event title (line 37: title: ${event.title}), while the error toast uses a generic "Download Failed" title. For consistency and better user context, consider using the event title in the error toast as well.

🔎 Suggested improvement
       addToast({
         description: 'Failed to download ICS file',
-        title: 'Download Failed',
+        title: event.title,
         timeout: 3000,
         shouldShowTimeoutProgress: true,
         color: 'danger',
         variant: 'solid',
       })
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1220e3f and f65dc17.

📒 Files selected for processing (2)
  • frontend/__tests__/unit/components/CalendarButton.test.tsx
  • frontend/src/components/CalendarButton.tsx
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
📚 Learning: 2025-09-17T02:42:41.928Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 2288
File: frontend/src/components/ActionButton.tsx:0-0
Timestamp: 2025-09-17T02:42:41.928Z
Learning: In frontend/src/components/ActionButton.tsx, the user Rajgupta36 intentionally changed text-blue-600 to text-[#1D7BD7] to align the text color with the border color (#1D7BD7) for visual consistency, prioritizing design alignment over theme tokens.

Applied to files:

  • frontend/src/components/CalendarButton.tsx
📚 Learning: 2025-06-18T20:00:23.899Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1634
File: frontend/src/app/api/auth/[...nextauth]/route.ts:30-55
Timestamp: 2025-06-18T20:00:23.899Z
Learning: The OWASP Nest application has logging disabled, so avoid suggesting console.log, console.error, or any other logging statements in code review suggestions.

Applied to files:

  • frontend/src/components/CalendarButton.tsx
🧬 Code graph analysis (2)
frontend/__tests__/unit/components/CalendarButton.test.tsx (2)
frontend/src/utils/getIcsFileUrl.ts (1)
  • getIcsFileUrl (4-53)
frontend/src/components/CalendarButton.tsx (1)
  • CalendarButton (10-81)
frontend/src/components/CalendarButton.tsx (2)
frontend/src/types/calendar.ts (1)
  • CalendarButtonProps (12-19)
frontend/src/utils/getIcsFileUrl.ts (1)
  • getIcsFileUrl (4-53)
🔇 Additional comments (1)
frontend/src/components/CalendarButton.tsx (1)

35-42: Success toast correctly placed in try block.

The success toast is now properly positioned within the try block, ensuring it only fires after a successful download trigger. This correctly addresses the critical issue from the previous review where the toast was in the finally block and would show even on errors.

Copy link
Contributor

@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: 1

🧹 Nitpick comments (3)
frontend/__tests__/unit/components/CalendarButton.test.tsx (3)

43-43: Optional: Remove unused alert mock.

The globalThis.alert mock is defined but never asserted or used in any test. Consider removing it to keep the test setup clean.

🔎 Proposed cleanup
   clickSpy = jest.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
-
-  jest.spyOn(globalThis, 'alert').mockImplementation(() => {})
 })

84-120: Verify URL cleanup in download flow.

The test comprehensively covers the download flow but doesn't assert that URL.revokeObjectURL is called. The component should revoke the object URL after download to prevent memory leaks.

🔎 Add assertion for URL cleanup

After line 104 (after verifying the click), add:

     expect(clickSpy).toHaveBeenCalled()
+    expect(globalThis.URL.revokeObjectURL).toHaveBeenCalledWith(mockUrl)
 
     expect(addToast).toHaveBeenCalledWith({

154-154: Optional: Remove redundant mockRestore.

The explicit consoleSpy.mockRestore() is redundant since jest.restoreAllMocks() is already called in the afterEach block (line 47), which will restore all mocks including this spy.

🔎 Proposed cleanup
       )
-      consoleSpy.mockRestore()
     })
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f65dc17 and 9ff7439.

📒 Files selected for processing (1)
  • frontend/__tests__/unit/components/CalendarButton.test.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/components/CalendarButton.test.tsx
🧬 Code graph analysis (1)
frontend/__tests__/unit/components/CalendarButton.test.tsx (2)
frontend/src/utils/getIcsFileUrl.ts (1)
  • getIcsFileUrl (4-53)
frontend/src/components/CalendarButton.tsx (1)
  • CalendarButton (10-81)
🔇 Additional comments (1)
frontend/__tests__/unit/components/CalendarButton.test.tsx (1)

148-153: Excellent addition: Success toast assertion in error path.

This properly addresses the past review comment. The test now verifies that the success toast is NOT called when an error occurs, catching any bugs where the success toast might be shown in the finally block.

@sonarqubecloud
Copy link

@kasya kasya dismissed arkid15r’s stale review December 24, 2025 20:09

Already addressed

Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

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

@Utkarsh-0304 Thanks for working on this 👍🏼

@kasya kasya enabled auto-merge December 24, 2025 20:11
@kasya kasya added this pull request to the merge queue Dec 24, 2025
Merged via the queue into OWASP:main with commit ccde35f Dec 24, 2025
25 checks passed
Mr-Rahul-Paul pushed a commit to Mr-Rahul-Paul/Nest that referenced this pull request Jan 2, 2026
* fix:resolve conflicts & failing e2e tests

* fix: coderabbit suggestions

* fix:sonarqube_fix

* fix:coderabbit_nitpicks

* fix:formatting

* fix:make check issues

* fix:coderabbit suggestions and merge conflicts

* fix:ui changes and download file name updation

* fix:coderabbit suggestions

* feat: display toast for successful download

* fix: coderabbit suggestions

* Update tests

* Fix tests

---------

Co-authored-by: Kate <kate@kgthreads.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement ics file download support for upcoming events

3 participants

Comments