-
Notifications
You must be signed in to change notification settings - Fork 297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lectures
: Add hide/show functionality to PDF Preview Component
#9667
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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
♻️ Duplicate comments (1)
src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.ts (1)
125-136
:⚠️ Potential issueAvoid directly mutating the input signal “hiddenPages”
You are directly toggling the same set returned by “this.hiddenPages()!”, which might cause unexpected side effects. Instead, clone it, adjust, then emit. This was flagged in a previous review comment.
🧹 Nitpick comments (4)
src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (1)
Line range hint
153-219
: Improve error handling and state managementThe error handling could be more robust, and the state management during updates could be clearer.
Consider using a state machine pattern:
enum UpdateState { INITIAL, UPDATING_ATTACHMENT, UPDATING_HIDDEN_PAGES, COMPLETE, ERROR } private updateState = signal<UpdateState>(UpdateState.INITIAL); // Then use it in your update logic this.updateState.set(UpdateState.UPDATING_ATTACHMENT); // ... update logic this.updateState.set(UpdateState.COMPLETE);src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.ts (2)
38-38
: Output property name is clearThe naming “newHiddenPagesOutput” is self-explanatory, but consider matching naming conventions such as “hiddenPagesChange” if it is effectively an update event.
61-83
: Load PDF logic is well-structured but consider concurrencyThe loop approach to rendering is correct. However, you may have concurrency concerns if the user triggers multiple PDF loads in quick succession. Ensure that, if a new PDF is loading, you bail out or handle the older promise properly.
Also applies to: 92-92
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (1)
3-3
: Use consistent testing approach with fakeAsyncUsing fakeAsync is beneficial for controlling time-based or promise-based code. Ensure all asynchronous tests in the suite are consistent in their usage (fakeAsync vs. done callback).
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/main/java/de/tum/cit/aet/artemis/core/web/FileResource.java
(2 hunks)src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.ts
(5 hunks)src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts
(8 hunks)src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview-thumbnail-grid.component.spec.ts
(2 hunks)src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts
(8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/de/tum/cit/aet/artemis/core/web/FileResource.java
🧰 Additional context used
📓 Path-based instructions (4)
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview-thumbnail-grid.component.spec.ts (1)
Pattern src/test/javascript/spec/**/*.ts
: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (1)
Pattern src/test/javascript/spec/**/*.ts
: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}
src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.ts (1)
src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (1)
🔇 Additional comments (27)
src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (4)
106-113
: Add cleanup for effect
The effect should be cleaned up when the component is destroyed to prevent memory leaks.
141-148
: Avoid direct DOM manipulation
Using document.querySelectorAll
breaks Angular's encapsulation.
185-207
: Refactor nested subscriptions
The nested subscriptions make the code hard to maintain and reason about.
48-49
: 🛠️ Refactor suggestion
Consider cleanup for signal subscriptions and resources
While signals are properly initialized, ensure proper cleanup in ngOnDestroy
to prevent memory leaks.
Add cleanup logic to ngOnDestroy
:
ngOnDestroy() {
this.attachmentSub?.unsubscribe();
this.attachmentUnitSub?.unsubscribe();
+ // Cleanup signal resources
+ if (this.currentPdfUrl()) {
+ URL.revokeObjectURL(this.currentPdfUrl());
+ }
}
Likely invalid or redundant comment.
src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.ts (9)
1-1
: Organize imports properly if needed
Currently, you're importing both Angular core decorators and PDFJS from external libraries. Ensure that there is no duplication or unused imports, and that the imports are placed in a consistent order as per your style guidelines. Otherwise, this looks fine.
8-8
: Great choice of icons
Using FontAwesome icons for showing/hiding pages is consistent with the design approach. No issues spotted here.
17-17
: Confirm default values for signals
Check that providing default empty sets for “hiddenPages” and “isAttachmentUnit” does not create unexpected behavior if the parent component passes undefined. Setting them with strong defaults in the parent component (or handling in this component) can avoid potential runtime errors.
Also applies to: 23-24
43-44
: Icon usage is consistent
Exposing FontAwesome icons as protected class members is good. This approach keeps templates and code aligned.
46-52
: Use more robust logic to handle hiddenPages
changes
Right now, you reset “this.newHiddenPages” with “new Set(this.hiddenPages()!)” only if hiddenPages
changed. Verify that there's no scenario where the set must be reinitialized together with other inputs, e.g., if isAttachmentUnit also changes. Otherwise, this is good practice.
114-120
: Canvas creation method is straightforward
Your approach ensures each page is rendered in a dedicated canvas. This method is concise.
140-151
: Toggle selection logic is clear
Using a checkbox event for toggling selection is standard. The signal pattern is well applied, and the test coverage likely ensures correctness.
156-160
: Enlarged canvas references are consistent
You are storing the original canvas in a signal and flipping the “isEnlargedView” boolean. This is a neat approach.
28-32
: 🛠️ Refactor suggestion
Initialize signals inside ngOnInit for clarity
It’s correct that you initialize signals such as “totalPagesArray” and “loadedPages” early. However, referencing “this.hiddenPages()!” at construction time may cause issues if the parent hasn't set it yet. Consider deferring “new Set(this.hiddenPages()!)” until ngOnChanges or ngOnInit.
- newHiddenPages = signal(new Set<number>(this.hiddenPages()!));
+ newHiddenPages = signal<Set<number>>(new Set());
Then assign once in ngOnChanges or ngOnInit.
Likely invalid or redundant comment.
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview-thumbnail-grid.component.spec.ts (8)
9-9
: Ensure stable type usage for Angular tests
You rely on “ElementRef, InputSignal, Signal, SimpleChanges” from @angular/core. Confirm that your Angular version fully supports these extended features; otherwise, ensure the relevant TS config is set.
62-79
: Test coverage for input changes is good
The test verifies “hiddenPages” changes. Ensure you also include an edge case for an empty or undefined set, if relevant.
81-102
: Async test for currentPdfUrl
changes
Well done verifying loadPdf
is called with the new URL. Check for a scenario where the old PDF is still loading. Make sure the new load call does not conflict with the old one.
105-110
: Test for PDF rendering is straightforward
You mock out the essential calls and confirm “createCanvas” is invoked. The assertion on “component.totalPagesArray().size” is correct.
115-116
: Ensuring originalCanvas
state correctness
Assigning a mock canvas to the signal and verifying the toggling is valid. This test is succinct.
123-130
: Visibility toggle test
The test ensures page removal and addition to “hiddenPages”. Perfect for verifying negative and positive toggles.
132-137
: Selection test with events
This test confirms pages can be selected and deselected. Good approach using the event target’s checked property.
140-160
: Integration test for displayEnlargedCanvas
You properly mock up the container, return the matching canvas, and ensure “originalCanvas” is set. Nicely done.
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (6)
75-75
: Hidden slides test coverage
You now mock an observable returning [1, 2, 3]. Confirm edge cases for empty arrays or out-of-range page indexes.
128-136
: Load attachment unit file with fakeAsync
Great use of tick(). This ensures that the asynchronous call is fully resolved.
Line range hint 155-174
: Handle load errors with fakeAsync
Your approach for capturing and verifying error events is robust. The test is well-structured.
184-195
: Subscribing/unsubscribing from attachmentUnitSub
Using unsubscribes in ngOnDestroy is best practice. Test coverage confirms correctness.
210-220
: Accurate detection of hidden page IDs from DOM elements
The tests parse button IDs, extract numeric values, and confirm an empty array if none found. This is a clean approach and nicely ensures lines with invalid IDs are skipped.
Also applies to: 222-230, 232-241
426-483
: Creating a student version of an attachment
• Removing pages by zero-based index is correct.
• Wrapping the result in a File object is a good approach for naming.
• The error path test is robust.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (2)
src/main/webapp/app/overview/course-lectures/attachment-unit/attachment-unit.component.ts (1)
62-70
: Reduce code duplication with handleDownloadThe
handleOriginalVersion
method shares significant code withhandleDownload
. Consider extracting common functionality.Consider this refactor:
+ private downloadAttachment(link: string | undefined) { + if (!link || !this.lectureUnit()?.attachment?.name) { + console.error('Invalid attachment data'); + return; + } + this.fileService.downloadFileByAttachmentName(link, this.lectureUnit().attachment!.name!); + this.onCompletion.emit({ lectureUnit: this.lectureUnit(), completed: true }); + } + handleOriginalVersion() { this.logEvent(); - const link = this.lectureUnit().attachment!.link!; - if (link) { - this.fileService.downloadFileByAttachmentName(link, this.lectureUnit().attachment!.name!); - this.onCompletion.emit({ lectureUnit: this.lectureUnit(), completed: true }); - } + this.downloadAttachment(this.lectureUnit()?.attachment?.link); }src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.component.spec.ts (1)
96-103
: Verify arguments in handleOriginalVersion testThe test should verify the correct arguments are passed to the download method.
Add argument verification:
it('should handle original version', () => { const downloadFileSpy = jest.spyOn(fileService, 'downloadFileByAttachmentName'); const onCompletionEmitSpy = jest.spyOn(component.onCompletion, 'emit'); component.handleOriginalVersion(); expect(downloadFileSpy).toHaveBeenCalledOnce(); + expect(downloadFileSpy).toHaveBeenCalledWith( + attachmentUnit.attachment.link, + attachmentUnit.attachment.name + ); expect(onCompletionEmitSpy).toHaveBeenCalledOnce(); + expect(onCompletionEmitSpy).toHaveBeenCalledWith({ + lectureUnit: attachmentUnit, + completed: true + }); });
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/main/webapp/app/overview/course-lectures/attachment-unit/attachment-unit.component.html
(1 hunks)src/main/webapp/app/overview/course-lectures/attachment-unit/attachment-unit.component.ts
(1 hunks)src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.component.spec.ts
(1 hunks)src/test/javascript/spec/helpers/mocks/service/mock-file.service.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/webapp/app/overview/course-lectures/attachment-unit/attachment-unit.component.html
🧰 Additional context used
📓 Path-based instructions (3)
src/test/javascript/spec/helpers/mocks/service/mock-file.service.ts (1)
Pattern src/test/javascript/spec/**/*.ts
: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}
src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.component.spec.ts (1)
Pattern src/test/javascript/spec/**/*.ts
: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}
src/main/webapp/app/overview/course-lectures/attachment-unit/attachment-unit.component.ts (1)
src/main/webapp/app/overview/course-lectures/attachment-unit/attachment-unit.component.ts
Show resolved
Hide resolved
...est/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.component.spec.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (2)
src/test/java/de/tum/cit/aet/artemis/core/service/FilePathServiceTest.java (1)
35-37
: LGTM! Consider grouping related test cases.The new test case correctly validates the path conversion for student-specific attachment unit files, following the established patterns and maintaining consistency with existing assertions.
Consider grouping related test cases using nested test classes (e.g.,
@Nested
with descriptive class names) to improve organization, especially as more variants of attachment unit paths are added.Example refactor to improve organization:
@Test void testActualPathForPublicPath() { + @Nested + class AttachmentUnitPaths { + @Test + void testRegularAttachmentUnit() { actualPath = FilePathService.actualPathForPublicPath(URI.create("/api/files/attachments/attachment-unit/4/download.pdf")); assertThat(actualPath).isEqualTo(Path.of("uploads", "attachments", "attachment-unit", "4", "download.pdf")); + } + + @Test + void testStudentAttachmentUnit() { actualPath = FilePathService.actualPathForPublicPath(URI.create("/api/files/attachments/attachment-unit/4/student/download.pdf")); assertThat(actualPath).isEqualTo(Path.of("uploads", "attachments", "attachment-unit", "4", "student", "download.pdf")); + } + }src/main/java/de/tum/cit/aet/artemis/core/web/FileResource.java (1)
581-585
: Consider adding authorization check for student version access.The endpoint should verify if the requesting user has permission to access the student version of the attachment.
// check if hidden link is available in the attachment String studentVersion = attachment.getStudentVersion(); + // Check if user has permission to access student version + Course course = attachmentUnit.getLecture().getCourse(); + checkAttachmentAuthorizationOrThrow(course, attachment); + if (studentVersion == null) { return buildFileResponse(getActualPathFromPublicPathString(attachment.getLink()), false); }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/main/java/de/tum/cit/aet/artemis/core/service/FilePathService.java
(2 hunks)src/main/java/de/tum/cit/aet/artemis/core/web/FileResource.java
(2 hunks)src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.scss
(1 hunks)src/test/java/de/tum/cit/aet/artemis/core/service/FilePathServiceTest.java
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/de/tum/cit/aet/artemis/core/service/FilePathService.java
🧰 Additional context used
📓 Path-based instructions (2)
src/test/java/de/tum/cit/aet/artemis/core/service/FilePathServiceTest.java (1)
Pattern src/test/java/**/*.java
: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_use: enforce_package_rules; db_query_count_tests: track_performance; util_service_factory_pattern: true; avoid_db_access: true; mock_strategy: static_mocks; context_restart_minimize: true
src/main/java/de/tum/cit/aet/artemis/core/web/FileResource.java (1)
Pattern src/main/java/**/*.java
: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports
🔇 Additional comments (2)
src/main/java/de/tum/cit/aet/artemis/core/web/FileResource.java (1)
568-590
: 🛠️ Refactor suggestion
Improve error handling and path extraction logic.
The implementation needs several improvements:
- Add null checks for
attachmentUnit
andattachment
- Add error handling for path operations
- Extract path extraction logic to a helper method
@GetMapping("files/attachments/attachment-unit/{attachmentUnitId}/student/*")
@EnforceAtLeastStudent
public ResponseEntity<byte[]> getAttachmentUnitStudentVersion(@PathVariable Long attachmentUnitId) {
log.debug("REST request to get the student version of attachment Unit : {}", attachmentUnitId);
AttachmentUnit attachmentUnit = attachmentUnitRepository.findByIdElseThrow(attachmentUnitId);
+ if (attachmentUnit == null) {
+ throw new EntityNotFoundException("AttachmentUnit not found: " + attachmentUnitId);
+ }
+
Attachment attachment = attachmentUnit.getAttachment();
+ if (attachment == null) {
+ throw new EntityNotFoundException("Attachment not found for unit: " + attachmentUnitId);
+ }
// check if hidden link is available in the attachment
String studentVersion = attachment.getStudentVersion();
if (studentVersion == null) {
return buildFileResponse(getActualPathFromPublicPathString(attachment.getLink()), false);
}
- String fileName = studentVersion.substring(studentVersion.lastIndexOf("/") + 1);
+ String fileName = extractFileName(studentVersion);
return buildFileResponse(FilePathService.getAttachmentUnitFilePath().resolve(Path.of(attachmentUnit.getId().toString(), "student")), fileName, false);
}
+ /**
+ * Extracts filename from a path
+ * @param path The path containing the filename
+ * @return The extracted filename
+ */
+ private String extractFileName(String path) {
+ return path.substring(path.lastIndexOf("/") + 1);
+ }
Likely invalid or redundant comment.
src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.scss (1)
53-72
: Improve hover effects and transitions
The hover implementation has several issues:
- Use of
!important
should be avoided (as previously noted) - Transitions could be more specific to avoid performance issues
- Hidden icon transitions might conflict with overlay transitions
Consider these improvements:
.pdf-canvas-container:hover {
- transform: scale(1.02);
+ transform: scale(1.02);
+ transition: transform 0.3s ease-out;
button {
opacity: 1;
+ transition: opacity 0.2s ease-in;
}
.pdf-overlay {
opacity: 1;
}
.hidden-icon {
- transition: transform 0.3s;
+ transition: transform 0.3s, opacity 0.2s;
opacity: 0;
}
.hidden-overlay {
- opacity: 0 !important;
+ opacity: 0;
}
}
+/* Add this class with higher specificity */
+.pdf-container .pdf-canvas-container[data-state="hidden"]:hover .hidden-overlay {
+ opacity: 0;
+}
...app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.scss
Show resolved
Hide resolved
...app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.scss
Show resolved
Hide resolved
There was a problem hiding this 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
♻️ Duplicate comments (3)
src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (2)
140-147
: 🛠️ Refactor suggestionAvoid direct DOM manipulation.
The
getHiddenPages
method uses direct DOM queries which can lead to issues with Angular's change detection.Use Angular's
@ViewChildren
decorator instead:@ViewChildren('hideShowButton') hideShowButtons!: QueryList<ElementRef>; getHiddenPages(): number[] { return this.hideShowButtons .filter(btn => btn.nativeElement.classList.contains('btn-success')) .map(btn => { const match = btn.nativeElement.id.match(/hide-show-button-(\d+)/); return match ? parseInt(match[1], 10) : null; }) .filter((id): id is number => id !== null); }
276-291
: 🛠️ Refactor suggestionImprove type safety in updateHiddenPages.
The method has potential undefined values and unsafe type assertions.
private updateHiddenPages(pagesToDelete: number[]): void { const updatedHiddenPages = new Set<number>(); const currentHiddenPages = this.hiddenPages(); if (!currentHiddenPages) return; currentHiddenPages.forEach((hiddenPage) => { const adjustedPage = this.calculateAdjustedPage(hiddenPage, pagesToDelete); if (adjustedPage !== undefined) { updatedHiddenPages.add(adjustedPage); } }); this.hiddenPages.set(updatedHiddenPages); } private calculateAdjustedPage(hiddenPage: number, pagesToDelete: number[]): number | undefined { return pagesToDelete.reduce<number | undefined>((acc, pageIndex) => { if (acc === undefined || acc === pageIndex + 1) { return undefined; } return pageIndex < acc - 1 ? acc - 1 : acc; }, hiddenPage); }src/main/java/de/tum/cit/aet/artemis/lecture/web/AttachmentUnitResource.java (1)
344-354
: 🛠️ Refactor suggestionOptimize query execution order.
The endpoint makes separate queries for slides and attachment unit, which could be optimized.
@GetMapping("lectures/{lectureId}/attachment-units/{attachmentUnitId}/hidden-slides") @EnforceAtLeastEditor public ResponseEntity<List<Integer>> getAttachmentUnitHiddenSlides(@PathVariable Long attachmentUnitId, @PathVariable Long lectureId) { log.debug("REST request to get hidden slides of AttachmentUnit : {}", attachmentUnitId); - List<Integer> hiddenSlides = slideRepository.findHiddenSlideNumbersByAttachmentUnitId(attachmentUnitId); AttachmentUnit attachmentUnit = attachmentUnitRepository.findByIdElseThrow(attachmentUnitId); checkAttachmentUnitCourseAndLecture(attachmentUnit, lectureId); authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.EDITOR, attachmentUnit.getLecture().getCourse(), null); + List<Integer> hiddenSlides = slideRepository.findHiddenSlideNumbersByAttachmentUnitId(attachmentUnitId); return ResponseEntity.ok().body(hiddenSlides); }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/main/java/de/tum/cit/aet/artemis/lecture/service/AttachmentUnitService.java
(3 hunks)src/main/java/de/tum/cit/aet/artemis/lecture/web/AttachmentResource.java
(1 hunks)src/main/java/de/tum/cit/aet/artemis/lecture/web/AttachmentUnitResource.java
(6 hunks)src/main/webapp/app/lecture/attachment.service.ts
(1 hunks)src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts
(7 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/main/webapp/app/lecture/attachment.service.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/java/de/tum/cit/aet/artemis/lecture/web/AttachmentResource.java
- src/main/java/de/tum/cit/aet/artemis/lecture/service/AttachmentUnitService.java
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/de/tum/cit/aet/artemis/lecture/web/AttachmentUnitResource.java (1)
Pattern src/main/java/**/*.java
: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports
src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (1)
🔇 Additional comments (5)
src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (3)
47-48
: LGTM! Signal declarations are well-defined.
The signals for tracking hidden pages are properly initialized as empty sets.
105-112
:
Add cleanup for effect subscription.
The effect needs to be cleaned up when the component is destroyed to prevent memory leaks.
+private readonly cleanupFns: Array<() => void> = [];
constructor() {
- effect(
+ const disposeFn = effect(
() => {
this.hiddenPagesChanged();
},
{ allowSignalWrites: true },
);
+ this.cleanupFns.push(disposeFn);
}
+ngOnDestroy() {
+ this.cleanupFns.forEach(fn => fn());
+ // ... existing cleanup code
+}
Likely invalid or redundant comment.
299-315
:
Improve error handling and type safety.
The method uses unsafe null assertions and lacks proper error handling.
async createStudentVersionOfAttachment(hiddenPages: number[]) {
try {
- const fileName = this.attachmentUnit()!.attachment!.name;
+ const attachmentUnit = this.attachmentUnit();
+ if (!attachmentUnit?.attachment?.name) {
+ throw new Error('Missing attachment information');
+ }
+ const fileName = attachmentUnit.attachment.name;
const existingPdfBytes = await this.currentPdfBlob()!.arrayBuffer();
const hiddenPdfDoc = await PDFDocument.load(existingPdfBytes);
const pagesToDelete = hiddenPages.map((page) => page - 1).sort((a, b) => b - a);
pagesToDelete.forEach((pageIndex) => {
hiddenPdfDoc.removePage(pageIndex);
});
const pdfBytes = await hiddenPdfDoc.save();
return new File([pdfBytes], `${fileName}.pdf`, { type: 'application/pdf' });
} catch (error) {
this.alertService.error('artemisApp.attachment.pdfPreview.pageDeleteError', { error: error.message });
+ return undefined;
}
}
Likely invalid or redundant comment.
src/main/java/de/tum/cit/aet/artemis/lecture/web/AttachmentUnitResource.java (2)
Line range hint 79-93
: LGTM! Constructor injection is properly implemented.
The SlideRepository
dependency is correctly injected through constructor injection following Spring best practices.
122-133
: 🛠️ Refactor suggestion
Improve parameter validation and documentation.
The method parameters need better validation and documentation.
-@param hiddenPages the pages to be hidden in the attachment unit. No hidden pages will be sent if the parameter is not set
+@param hiddenPages comma-separated list of page numbers to be hidden (e.g., "1,2,3"). Must contain valid positive integers.
*/
@PutMapping(value = "lectures/{lectureId}/attachment-units/{attachmentUnitId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@EnforceAtLeastEditor
public ResponseEntity<AttachmentUnit> updateAttachmentUnit(@PathVariable Long lectureId, @PathVariable Long attachmentUnitId,
@RequestPart AttachmentUnit attachmentUnit, @RequestPart Attachment attachment,
@RequestPart(required = false) MultipartFile file,
@RequestPart(required = false) MultipartFile studentVersion,
@RequestParam(defaultValue = "false") boolean keepFilename,
@RequestParam(value = "notificationText", required = false) String notificationText,
- @RequestParam(value = "hiddenPages", required = false) String hiddenPages) {
+ @RequestParam(value = "hiddenPages", required = false) String hiddenPages) {
+ if (hiddenPages != null && !hiddenPages.matches("^\\d+(,\\d+)*$")) {
+ throw new BadRequestAlertException("Invalid format for hiddenPages", ENTITY_NAME, "invalidHiddenPages");
+ }
Likely invalid or redundant comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (2)
210-242
: Consider adding edge cases to strengthen test coverage.The test suite covers basic scenarios well, but could benefit from additional edge cases:
- Non-numeric page numbers
- Duplicate page numbers
- Very large page numbers
Add these test cases:
it('should handle non-numeric page numbers', () => { document.body.innerHTML = ` <button id="hide-show-button-abc" class="hide-show-btn btn-success"></button> `; const hiddenPages = component.getHiddenPages(); expect(hiddenPages).toEqual([]); }); it('should handle duplicate page numbers', () => { document.body.innerHTML = ` <button id="hide-show-button-1" class="hide-show-btn btn-success"></button> <button id="hide-show-button-1" class="hide-show-btn btn-success"></button> `; const hiddenPages = component.getHiddenPages(); expect(hiddenPages).toEqual([1]); });
475-532
: Add test case for empty hidden pages array.The test suite should verify that the student version creation works correctly when no pages are hidden.
Add this test case:
it('should return original PDF when no pages are hidden', async () => { const hiddenPages: number[] = []; const mockPdfBytes = new Uint8Array([1, 2, 3, 4]).buffer; const mockFileName = 'test-file'; const mockAttachmentUnit = { attachment: { name: mockFileName, }, }; component.attachmentUnit.set(mockAttachmentUnit as any); component.currentPdfBlob.set(new Blob([mockPdfBytes], { type: 'application/pdf' })); component.currentPdfBlob()!.arrayBuffer = jest.fn().mockResolvedValue(mockPdfBytes); const result = await component.createStudentVersionOfAttachment(hiddenPages); expect(result).toBeInstanceOf(File); expect(result!.name).toBe(`${mockFileName}.pdf`); expect(result!.type).toBe('application/pdf'); expect(PDFDocument.load).toHaveBeenCalledTimes(1); expect(PDFDocument.prototype.removePage).not.toHaveBeenCalled(); });
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts
(9 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (1)
Pattern src/test/javascript/spec/**/*.ts
: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts
Show resolved
Hide resolved
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts
Outdated
Show resolved
Hide resolved
… into feature/lectures/hide-pdf-pages
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts (1)
84-99
: Enhance test coverage for original version view.Good practices observed:
- Proper setup of inputs
- Button existence verification
- Consistent event handling
- Comprehensive spy assertions
Consider adding these test cases:
- Verify button is not shown when
showOriginalVersionButton
is false- Add specific assertions for emitted event data
it('should handle original version view', async () => { const handleOriginalVersionViewSpy = jest.spyOn(component, 'handleOriginalVersionView'); const onShowOriginalVersionEmitSpy = jest.spyOn(component.onShowOriginalVersion, 'emit'); + // Test when button should not be shown + fixture.componentRef.setInput('showOriginalVersionButton', false); + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('#view-original-version-button'))).toBeNull(); + + // Test when button should be shown fixture.componentRef.setInput('showOriginalVersionButton', true); fixture.detectChanges(); const event = new MouseEvent('click'); const button = fixture.debugElement.query(By.css('#view-original-version-button')); expect(button).not.toBeNull(); button.nativeElement.dispatchEvent(event); expect(handleOriginalVersionViewSpy).toHaveBeenCalledOnce(); - expect(onShowOriginalVersionEmitSpy).toHaveBeenCalledOnce(); + expect(onShowOriginalVersionEmitSpy).toHaveBeenCalledExactlyOnceWith(component.lectureUnit); });
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/main/resources/config/liquibase/master.xml
is excluded by!**/*.xml
📒 Files selected for processing (1)
src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts (1)
Pattern src/test/javascript/spec/**/*.ts
: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}
🔇 Additional comments (1)
src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts (1)
Line range hint 1-99
: Add test coverage for PDF Preview hide/show functionality.
Based on the PR objectives, the following test scenarios appear to be missing:
- Hide/show buttons visibility on page hover
- Page hiding/showing functionality
- Hidden page state persistence
- Visual state of hidden pages (grayed out, eye icon)
src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts
Outdated
Show resolved
Hide resolved
src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
🔭 Outside diff range comments (1)
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (1)
Line range hint
274-319
: Add test coverage for hidden pages in attachment unit update.The test suite covers the basic update functionality but misses testing the interaction with hidden pages.
Add this test case:
+it('should update attachment unit successfully with hidden pages', fakeAsync(() => { + const hiddenPages = [1, 3]; + jest.spyOn(component, 'getHiddenPages').mockReturnValue(hiddenPages); + jest.spyOn(FormData.prototype, 'append'); + + component.attachment.set(undefined); + component.attachmentUnit.set({ + id: 1, + lecture: { id: 1 }, + attachment: { id: 1, version: 1 }, + }); + attachmentUnitServiceMock.update.mockReturnValue(of({})); + + component.updateAttachmentWithFile(); + tick(); + + const formData = new FormData(); + expect(FormData.prototype.append).toHaveBeenCalledWith('hiddenSlides', JSON.stringify(hiddenPages)); + expect(attachmentUnitServiceMock.update).toHaveBeenCalledWith(1, 1, expect.any(FormData)); +}));
🧹 Nitpick comments (3)
src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.service.spec.ts (2)
228-234
: Improve subscription callback pattern.The assignment in the subscription callback can be refactored to follow a more idiomatic pattern.
-let actualResponse: number[] | undefined; -service - .getHiddenSlides(lectureId, attachmentUnitId) - .pipe(take(1)) - .subscribe((response) => (actualResponse = response)); +service + .getHiddenSlides(lectureId, attachmentUnitId) + .pipe(take(1)) + .subscribe({ + next: (response) => { + expect(response).toEqual(expectedHiddenSlides); + } + });🧰 Tools
🪛 Biome (1.9.4)
[error] 233-233: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.(lint/suspicious/noAssignInExpressions)
224-242
: Consider adding edge cases and error scenarios.The test covers the happy path but could benefit from additional test cases:
- Empty array response
- Error response handling
- Invalid lecture/attachment unit IDs
Example additional test case:
it('should handle empty hidden slides array', fakeAsync(() => { const lectureId = 1; const attachmentUnitId = 42; const expectedHiddenSlides: number[] = []; service .getHiddenSlides(lectureId, attachmentUnitId) .pipe(take(1)) .subscribe({ next: (response) => { expect(response).toEqual(expectedHiddenSlides); } }); const req = httpMock.expectOne({ url: `api/lectures/${lectureId}/attachment-units/${attachmentUnitId}/hidden-slides`, method: 'GET', }); req.flush(expectedHiddenSlides); }));🧰 Tools
🪛 Biome (1.9.4)
[error] 233-233: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.(lint/suspicious/noAssignInExpressions)
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (1)
128-136
: Enhance async test coverage with additional assertions.While the async test modifications correctly use
fakeAsync
andtick
, consider adding assertions for the component's loading state.Add these assertions to verify the loading state:
it('should load attachment unit file and verify service calls when attachment unit data is available', fakeAsync(() => { routeMock.data = of({ course: { id: 1, name: 'Example Course' }, attachmentUnit: { id: 1, name: 'Chapter 1', lecture: { id: 1 } }, }); + expect(component.isLoading()).toBeTrue(); component.ngOnInit(); tick(); expect(attachmentUnitServiceMock.getAttachmentFile).toHaveBeenCalledWith(1, 1); + expect(component.isLoading()).toBeFalse(); }));Also applies to: 155-174, 184-195
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/main/resources/config/liquibase/master.xml
is excluded by!**/*.xml
📒 Files selected for processing (5)
src/main/java/de/tum/cit/aet/artemis/core/service/FilePathService.java
(2 hunks)src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.service.spec.ts
(1 hunks)src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts
(1 hunks)src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts
(10 hunks)src/test/javascript/spec/component/shared/http/file.service.spec.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts
- src/main/java/de/tum/cit/aet/artemis/core/service/FilePathService.java
🧰 Additional context used
📓 Path-based instructions (3)
src/test/javascript/spec/component/shared/http/file.service.spec.ts (1)
Pattern src/test/javascript/spec/**/*.ts
: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}
src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.service.spec.ts (1)
Pattern src/test/javascript/spec/**/*.ts
: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (1)
Pattern src/test/javascript/spec/**/*.ts
: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}
🪛 Biome (1.9.4)
src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.service.spec.ts
[error] 233-233: The assignment should not be in an expression.
The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.
(lint/suspicious/noAssignInExpressions)
🔇 Additional comments (5)
src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.service.spec.ts (1)
235-239
: LGTM! HTTP mock configuration is correct.
The HTTP mock is properly configured:
- Correct URL pattern following REST conventions
- Appropriate HTTP method verification
- Proper request matching
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (3)
3-3
: LGTM: Mock setup is properly configured.
The addition of fakeAsync
and tick
imports, along with the getHiddenSlides
mock implementation, aligns well with the asynchronous nature of the component's functionality.
Also applies to: 75-75
210-242
: LGTM: Comprehensive test coverage for hidden pages functionality.
The test suite thoroughly covers various scenarios for the getHiddenPages
method:
- Valid hidden page numbers
- Empty result when no matching elements exist
- Proper handling of invalid IDs
474-531
: LGTM: Thorough test coverage for student version creation.
The test suite effectively covers:
- Successful creation of student version with hidden pages removed
- Error handling
- File naming and type verification
src/test/javascript/spec/component/shared/http/file.service.spec.ts (1)
235-243
: Verify implementation matches test expectations.
Let's verify that the implementation in file.service.ts
correctly handles all the test cases.
✅ Verification successful
Implementation matches test expectations
The implementation in src/main/webapp/app/shared/http/file.service.ts
correctly handles the test case by:
- Finding the last slash in the URL using
lastIndexOf('/')
- Inserting 'student' before the filename by concatenating the path up to the last slash with '/student' and the remaining filename
The implementation is also being used correctly in the codebase, as seen in attachment-unit.component.ts
where it's used as a fallback when a student version is not explicitly available.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check the implementation of createStudentLink method
# Find and display the implementation
echo "Searching for createStudentLink implementation..."
ast-grep --pattern 'createStudentLink($_) {
$$$
}'
# Check for URL handling utilities
echo "Checking for URL handling utilities..."
rg -A 5 "import.*URL" src/main/webapp/app/shared/http/file.service.ts
Length of output: 322
Script:
#!/bin/bash
# Let's try a different approach to find the implementation
# First, let's check if the file exists
echo "Checking if file.service.ts exists..."
fd "file.service.ts" src/main/webapp/app/shared/http/
# If it exists, let's look at its content
echo "Looking for createStudentLink in the implementation..."
rg -B 2 -A 5 "createStudentLink" src/main/webapp/app/shared/http/file.service.ts
# As a fallback, let's check the entire codebase for this method
echo "Searching entire codebase for createStudentLink..."
rg -B 2 -A 5 "createStudentLink" --type ts
Length of output: 6129
describe('createStudentLink', () => { | ||
it('should return the student version of the given link', () => { | ||
const link = 'http://example.com/course/attachment/file.pdf'; | ||
const expectedStudentLink = 'http://example.com/course/attachment/student/file.pdf'; | ||
|
||
const result = fileService.createStudentLink(link); | ||
expect(result).toBe(expectedStudentLink); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance test coverage for createStudentLink method.
While the basic test case is good, consider adding the following test cases to improve robustness:
describe('createStudentLink', () => {
it('should return the student version of the given link', () => {
const link = 'http://example.com/course/attachment/file.pdf';
const expectedStudentLink = 'http://example.com/course/attachment/student/file.pdf';
const result = fileService.createStudentLink(link);
expect(result).toBe(expectedStudentLink);
});
+
+ it('should handle URLs with query parameters', () => {
+ const link = 'http://example.com/course/attachment/file.pdf?version=1';
+ const expectedStudentLink = 'http://example.com/course/attachment/student/file.pdf?version=1';
+
+ const result = fileService.createStudentLink(link);
+ expect(result).toBe(expectedStudentLink);
+ });
+
+ it('should handle URLs with fragments', () => {
+ const link = 'http://example.com/course/attachment/file.pdf#page=1';
+ const expectedStudentLink = 'http://example.com/course/attachment/student/file.pdf#page=1';
+
+ const result = fileService.createStudentLink(link);
+ expect(result).toBe(expectedStudentLink);
+ });
+
+ it('should handle URLs that already contain student path', () => {
+ const link = 'http://example.com/course/attachment/student/file.pdf';
+
+ const result = fileService.createStudentLink(link);
+ expect(result).toBe(link);
+ });
+
+ it('should handle invalid URLs', () => {
+ const link = '';
+
+ expect(() => fileService.createStudentLink(link)).toThrow();
+ });
});
Committable suggestion skipped: line range outside the PR's diff.
Note: Please try to check for the PDF Preview view's functionalities again as Thumbnail Grid template is fixed from scratch :)
Checklist
General
Server
Client
authorities
to all new routes and checked the course groups for displaying navigation elements (links, buttons).Motivation and Context
This PR introduces a feature enabling the selective visibility of specific pages within a PDF document. Instructors often require the ability to retain certain pages in a document while temporarily withholding their visibility, for instance, until the conclusion of an exercise submission period. Currently, achieving this functionality necessitates dividing the content into multiple files, which can be cumbersome and inefficient. To address this limitation, a solution has been proposed to allow instructors to dynamically hide or reveal individual pages directly within the PDF Preview Component, enhancing the flexibility and usability of the system.
Description
A solution is implemented in PDF Preview Component, such as:
Steps for Testing
Prerequisites:
Testserver States
Note
These badges show the state of the test servers.
Green = Currently available, Red = Currently locked
Click on the badges to get to the test servers.
Review Progress
Performance Review
Code Review
Manual Tests
Test Coverage
Screenshots
1. Hide Button on Hover
2. Hidden Pages & Show Button on Hover
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests