Skip to content
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

Development: Update theme switcher to use Angular 18 practices #9250

Merged
merged 40 commits into from
Nov 16, 2024

Conversation

FelixTJDietrich
Copy link
Contributor

@FelixTJDietrich FelixTJDietrich commented Aug 26, 2024

Checklist

General

Client

  • Important: I implemented the changes with a very good performance, prevented too many (unnecessary) REST calls and made sure the UI is responsive, even with large data (e.g. using paging).
  • I strictly followed the client coding and design guidelines.

Motivation and Context

We want to migrate to the new way of Angular 18 and potentially use this PR as example PR for the documentation.

Description

This migrates the theme switcher component and the components it affects to the latest Angular development standards with signals.

Steps for Testing

Prerequisites:

  • 1 Instructor
  1. Log in to Artemis
  2. Play around with the theme switcher (dark, light, store user preference)
  3. Check if behaviour is as expected after refreshes
  4. Go to any course, instructor view
  5. Press "create programming exercise"
  6. Check if the Monaco editor (problem statement) responds to theme switches
  7. In problem statement preview, check if the PlantUML diagram also responds to theme switches
  8. Cancel, and go to communication
  9. On any message, check if the emoji picker responds to theme switches

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

  • I (as a reviewer) confirm that the client changes (in particular related to REST calls and UI responsiveness) are implemented with a very good performance even for very large courses with more than 2000 students.

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

  • Test 1
  • Test 2

Performance Tests

  • Test 1
  • Test 2

Test Coverage

Client

Class/File Line Coverage Confirmation (assert/expect)
theme-switch.component.ts 89.65%
theme.service.ts 90.66%
example-modeling-submission.component.ts 92.56%
programming-exercise-instruction.component.ts 85.39%
programming-exercise-plant-uml.service.ts 80.76%
lti13-exercise-launch.component.ts 90%
progress-bar.component.ts 100%
emoji-picker.component.ts 100%
emoji.component.ts 100%
monaco-editor.service.ts 100%

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced client theming guidelines to promote theme-aware color usage.
    • Introduction of the ThemeSwitchComponent for improved theme management.
  • Bug Fixes

    • Updated theme application methods to ensure consistency across components.
  • Documentation

    • Expanded guidelines on color usage and theme management, including structured approaches for developers.
  • Tests

    • Revised test cases to align with new theme management methods and improved reactivity.

@github-actions github-actions bot added the client Pull requests that update TypeScript code. (Added Automatically!) label Aug 26, 2024
@github-actions github-actions bot added the tests label Aug 27, 2024
…tion-example

# Conflicts:
#	src/main/webapp/app/shared/markdown-editor/ace-editor/ace-editor.component.ts
@github-actions github-actions bot added the stale label Sep 7, 2024
@github-actions github-actions bot removed the stale label Sep 10, 2024
@github-actions github-actions bot added stale and removed stale labels Sep 25, 2024
@github-actions github-actions bot added the stale label Oct 5, 2024
@github-actions github-actions bot added lock:artemis-test9 and removed deploy:artemis-test9 Testserver for Project Theia labels Nov 13, 2024
Copy link

coderabbitai bot commented Nov 13, 2024

Walkthrough

The pull request includes significant modifications to client theming guidelines, emphasizing the use of global default colors and Bootstrap classes instead of hard-coded color values. It also updates the ThemeService to utilize Angular's reactive signal and computed features for better theme management. Various components and services have been refactored to adopt a more reactive approach, enhancing clarity and maintainability. Additionally, the test suites have been updated to reflect these changes, ensuring that the new theming logic is properly validated.

Changes

File Change Summary
docs/dev/guidelines/client-design.rst Updated client theming guidelines, emphasizing avoidance of hard-coded colors, expanded custom color definitions, and clarified theme-specific global styles. Changed ThemeService method from applyThemeExplicitly to applyThemePreference.
src/main/webapp/app/app.module.ts Removed ThemeModule import and added ThemeSwitchComponent import; updated @NgModule imports accordingly.
src/main/webapp/app/core/theme/theme-switch.component.html Updated HTML structure and bindings, changed translation mechanism and synchronization state handling, modified animation and class bindings.
src/main/webapp/app/core/theme/theme-switch.component.ts Refactored component to standalone, updated properties and methods for computed values, removed constructor, and streamlined dependency management.
src/main/webapp/app/core/theme/theme.module.ts Deleted ThemeModule file.
src/main/webapp/app/core/theme/theme.service.ts Transitioned to reactive signal and computed for theme management, updated method names and logic for applying themes.
src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.html Changed function calls for highlightedElements and highlightColor from property access to function invocation.
src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.ts Updated properties to use reactive signal and computed, streamlined state management.
src/main/webapp/app/exercises/programming/shared/instructions-render/programming-exercise-instruction.component.ts Simplified theme change handling, updated subscription method to use toObservable.
src/main/webapp/app/exercises/programming/shared/instructions-render/service/programming-exercise-plant-uml.service.ts Updated theme handling logic to use reactive programming with signals.
src/main/webapp/app/exercises/programming/shared/instructions-render/step-wizard/programming-exercise-instruction-step-wizard.component.html Modified tracking mechanism in @for loop syntax.
src/main/webapp/app/lti/lti13-exercise-launch.component.ts Updated theme application method to applyThemePreference.
src/main/webapp/app/shared/dashboards/tutor-participation-graph/progress-bar/progress-bar.component.ts Removed OnInit, updated theme observable subscription method.
src/main/webapp/app/shared/metis/emoji/emoji-picker.component.html Changed attributes of <emoji-mart> component to use function calls.
src/main/webapp/app/shared/metis/emoji/emoji-picker.component.ts Refactored to use reactive properties, removed constructor and subscription logic.
src/main/webapp/app/shared/metis/emoji/emoji.component.html Updated conditional rendering logic to evaluate theme state dynamically.
src/main/webapp/app/shared/metis/emoji/emoji.component.ts Removed constructor and OnDestroy, updated dark theme handling to use computed properties.
src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts Simplified access to currentTheme property.
src/test/javascript/spec/component/emoji/emoji-picker.component.spec.ts Updated tests to reflect changes in theme handling methods.
src/test/javascript/spec/component/emoji/emoji.component.spec.ts Deleted test file for EmojiComponent.
src/test/javascript/spec/component/programming-exercise/programming-exercise-instruction.component.spec.ts Enhanced test cases for participation changes and markdown updates.
src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts Updated import paths and theme handling logic in tests.
src/test/javascript/spec/component/shared/navbar.component.spec.ts Added GuidedTourService and updated breadcrumb expectations.
src/test/javascript/spec/component/shared/progress-bar.component.spec.ts Updated to use MockThemeService and changed method calls for theme application.
src/test/javascript/spec/component/theme/theme-switch.component.spec.ts Modified test setup to use asynchronous beforeEach and updated theme handling assertions.
src/test/javascript/spec/helpers/mocks/service/mock-theme.service.ts Transitioned to signals for state management, updated methods for theme application.
src/test/javascript/spec/integration/code-editor/code-editor-instructor.integration.spec.ts Removed unnecessary ChangeDetectorRef import.
src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts Updated to include ThemeSwitchComponent directly in tests.
src/test/javascript/spec/service/theme.service.spec.ts Updated method calls in tests to reflect changes in ThemeService.

Possibly related PRs

Suggested labels

ready for review, priority:high

Suggested reviewers

  • SimonEntholzer
  • florian-glombik
  • krusche
  • BBesrour
  • edkaya

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Outside diff range and nitpick comments (33)
src/main/webapp/app/shared/metis/emoji/emoji.component.html (1)

2-5: Consider extracting common emoji properties to reduce duplication.

While the implementation is correct, the emoji properties like class, size, and backgroundImageFn are duplicated between themes. Consider refactoring to improve maintainability:

+ <!-- Common emoji configuration -->
+ <ng-template #commonEmoji>
+   <ngx-emoji
+     [backgroundImageFn]="utils.EMOJI_SHEET_URL"
+     class="emoji"
+     [emoji]="emoji"
+     [size]="16"
+   />
+ </ng-template>
+
  <!-- Using two instances to 'rerender' if the theme changes -->
  @if (!dark()) {
-   <ngx-emoji [backgroundImageFn]="utils.EMOJI_SHEET_URL" class="emoji" [emoji]="emoji" [size]="16" />
+   <ng-container [ngTemplateOutlet]="commonEmoji" />
  } @else {
-   <ngx-emoji [imageUrlFn]="utils.singleDarkModeEmojiUrlFn" [backgroundImageFn]="utils.EMOJI_SHEET_URL" class="emoji" [emoji]="emoji" [size]="16" />
+   <ngx-emoji
+     [ngTemplateOutletContext]="commonEmoji"
+     [imageUrlFn]="utils.singleDarkModeEmojiUrlFn"
+   />
  }
src/main/webapp/app/shared/metis/emoji/emoji-picker.component.html (1)

10-11: LGTM! Consider performance optimization.

The changes correctly implement Angular's reactive approach using computed properties for theme management. This aligns well with Angular 18 practices and the PR objectives.

Consider memoizing the computed properties if they're used frequently to prevent unnecessary recalculations:

// In the component class
dark = computed(() => this.themeService.currentTheme() === 'dark');
singleImageFunction = memoized(() => this.dark() ? darkEmojiUrlFn : lightEmojiUrlFn);
src/test/javascript/spec/helpers/mocks/service/mock-theme.service.ts (2)

8-9: Consider adding type validation for Theme enum values.

While the implementation is good, consider adding runtime validation to ensure only valid Theme enum values are used.

-    private _userPreference = signal<Theme | undefined>(undefined);
+    private _userPreference = signal<Theme | undefined>(undefined, {
+        equal: (a, b) => a === b,
+        validate: (value) => value === undefined || Object.values(Theme).includes(value)
+    });

15-15: Document the purpose of the empty print method.

The empty print method lacks documentation explaining its purpose. If it's needed for specific test scenarios, please add a comment explaining why.

-    public print() {}
+    /** Used for ... [please explain the purpose] */
+    public print() {}
src/main/webapp/app/exercises/programming/shared/instructions-render/step-wizard/programming-exercise-instruction-step-wizard.component.html (1)

5-5: Consider using object identity for tracking instead of index.

While tracking by index works, it might not be optimal for performance if the steps array undergoes frequent modifications (reordering, filtering, etc.). Consider tracking by a unique identifier if available.

-@for (step of steps; let i = $index; track i) {
+@for (step of steps; track step.id) {
src/test/javascript/spec/component/emoji/emoji-picker.component.spec.ts (1)

32-34: Consider adding edge cases to initial state verification.

The initial state verification looks good and follows the coding guidelines by using toBeFalse(). However, consider adding tests for edge cases such as:

  • Component initialization with dark theme already active
  • Multiple rapid theme changes
it('should handle edge cases in theme changes', () => {
    // Test initialization with dark theme
    mockThemeService.applyThemePreference(Theme.DARK);
    fixture = TestBed.createComponent(EmojiPickerComponent);
    comp = fixture.componentInstance;
    expect(comp.dark()).toBeTrue();

    // Test rapid theme changes
    mockThemeService.applyThemePreference(Theme.LIGHT);
    mockThemeService.applyThemePreference(Theme.DARK);
    mockThemeService.applyThemePreference(Theme.LIGHT);
    expect(comp.dark()).toBeFalse();
});
src/main/webapp/app/core/theme/theme-switch.component.html (1)

23-23: Great performance optimization using [class.dark]!

The change from [ngClass] to [class.dark] is a performance improvement, as it's more efficient for single class toggles. The isDarkTheme() method aligns well with the reactive approach.

Consider adding aria-label or aria-pressed attributes to improve accessibility:

-        <div class="theme-toggle" [class.dark]="isDarkTheme()" id="theme-toggle">
+        <div class="theme-toggle" [class.dark]="isDarkTheme()" id="theme-toggle"
+             role="button"
+             [attr.aria-pressed]="isDarkTheme()"
+             [attr.aria-label]="'artemisApp.theme.toggleTheme' | translate">
src/main/webapp/app/shared/dashboards/tutor-participation-graph/progress-bar/progress-bar.component.ts (2)

Line range hint 25-30: Consider optimizing theme subscription performance

The subscription triggers change detection on every theme change, which might impact performance. Consider using:

  1. OnPush change detection strategy
  2. async pipe in template instead of manual subscription
+ @Component({
+   selector: 'jhi-progress-bar',
+   templateUrl: './progress-bar.component.html',
+   changeDetection: ChangeDetectionStrategy.OnPush
+ })

Line range hint 43-74: Consider extracting magic numbers into named constants

The percentage thresholds (50, 100) should be extracted into named constants for better maintainability and reusability.

+ private readonly DANGER_THRESHOLD = 50;
+ private readonly SUCCESS_THRESHOLD = 100;

  calculateProgressBarClass(): void {
-   if (this.percentage < 50) {
+   if (this.percentage < this.DANGER_THRESHOLD) {
      this.backgroundColorClass = 'bg-danger';
-   } else if (this.percentage < 100) {
+   } else if (this.percentage < this.SUCCESS_THRESHOLD) {
      this.backgroundColorClass = 'bg-warning';
    } else {
      this.backgroundColorClass = 'bg-success';
    }
  }
src/test/javascript/spec/component/shared/progress-bar.component.spec.ts (2)

59-62: Enhance test specificity and assertions

While the test logic is correct, we can improve it according to our test guidelines:

  1. Use more specific assertions
  2. Consider testing the intermediate state

Consider refactoring the test like this:

-        themeService.applyThemePreference(Theme.DARK);
-        fixture.detectChanges();
+        const themeService = TestBed.inject(ThemeService);
+        
+        // Verify initial state
+        expect(component.isDarkTheme).toBeFalse();
+        
+        // Apply theme change
+        themeService.applyThemePreference(Theme.DARK);
+        fixture.detectChanges();
+        
+        // Verify theme state and component response
+        expect(component.isDarkTheme).toBeTrue();
         expect(component.foregroundColorClass).toBe('text-white');

Line range hint 36-43: Consider expanding test coverage

The parameterized test for background colors could be enhanced with additional edge cases:

Consider adding these test cases:

 it.each([
     { percentage: 49, clazz: 'bg-danger' },
+    { percentage: 50, clazz: 'bg-warning' },
     { percentage: 99, clazz: 'bg-warning' },
     { percentage: 100, clazz: 'bg-success' },
+    { percentage: 0, clazz: 'bg-danger' },
+    { percentage: -1, clazz: 'bg-danger' },  // Edge case
+    { percentage: 101, clazz: 'bg-success' }, // Edge case
 ])('uses correct background', ({ percentage, clazz }) => {
src/test/javascript/spec/component/theme/theme-switch.component.spec.ts (2)

19-41: Consider optimizing the test module setup.

While the setup is generally good, consider these improvements:

  1. Move the spy setup into a separate setup function for better organization
  2. Consider using MockProvider from ng-mocks for cleaner service mocking

Here's a suggested refactor:

- beforeEach(async () => {
-     await TestBed.configureTestingModule({
-         imports: [ArtemisTestModule, ThemeSwitchComponent, MockDirective(NgbPopover)],
-         declarations: [],
-         providers: [
-             { provide: LocalStorageService, useClass: MockLocalStorageService },
-             {
-                 provide: ThemeService,
-                 useClass: MockThemeService,
-             },
-         ],
-     }).compileComponents();
+ const setupSpies = () => {
+     openSpy = jest.spyOn(component.popover(), 'open');
+     closeSpy = jest.spyOn(component.popover(), 'close');
+ };
+
+ beforeEach(async () => {
+     await TestBed.configureTestingModule({
+         imports: [ArtemisTestModule, ThemeSwitchComponent, MockDirective(NgbPopover)],
+         providers: [
+             MockProvider(LocalStorageService, MockLocalStorageService),
+             MockProvider(ThemeService, MockThemeService),
+         ],
+     }).compileComponents();

-     themeService = TestBed.inject(ThemeService);
-     fixture = TestBed.createComponent(ThemeSwitchComponent);
-     component = fixture.componentInstance;
-
-     openSpy = jest.spyOn(component.popover(), 'open');
-     closeSpy = jest.spyOn(component.popover(), 'close');
-
-     fixture.componentRef.setInput('popoverPlacement', ['bottom']);
+     themeService = TestBed.inject(ThemeService);
+     fixture = TestBed.createComponent(ThemeSwitchComponent);
+     component = fixture.componentInstance;
+     setupSpies();
+     fixture.componentRef.setInput('popoverPlacement', ['bottom']);
});

57-67: Consider adding edge cases to OS sync test.

While the test covers basic functionality, consider adding tests for:

  1. System theme changes while synced
  2. Error scenarios in theme application

Example addition:

it('handles system theme changes when synced', fakeAsync(() => {
    const applyThemePreferenceSpy = jest.spyOn(themeService, 'applyThemePreference');
    component.toggleSynced();
    // Simulate system theme change
    window.matchMedia('(prefers-color-scheme: dark)').matches = true;
    tick(100);
    expect(applyThemePreferenceSpy).toHaveBeenCalledWith(undefined);
}));
src/main/webapp/app/app.module.ts (1)

45-45: Consider impact on initial bundle size

While the migration to a standalone component is good, including it directly in the root module means it's eagerly loaded. Consider if theme switching is essential for initial app load or if it could be lazy loaded.

You could move it to a micro-frontend setup using Angular's built-in lazy loading:

// In app-routing.module.ts
{
  path: '',
  component: JhiMainComponent,
  children: [{
    path: '',
    loadComponent: () => import('./core/theme/theme-switch.component')
      .then(m => m.ThemeSwitchComponent)
  }]
}
src/main/webapp/app/exercises/programming/shared/instructions-render/service/programming-exercise-plant-uml.service.ts (1)

24-28: Enhance effect implementation clarity and robustness.

While the implementation works, consider these improvements:

  1. The comment should better explain why we're notifying via subject
  2. Consider wrapping the subject.next() in try-catch for error handling
 effect(() => {
-    // Apply the theme as soon as the currentTheme changes
+    // Notify cached PlantUML diagrams to re-render when theme changes
     this.themeService.currentTheme();
-    themeChangedSubject.next();
+    try {
+        themeChangedSubject.next();
+    } catch (error) {
+        console.error('Failed to notify theme change:', error);
+    }
 });
src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts (2)

46-54: Consider adding explicit test for initial theme state.

While the theme switching tests are good, consider adding explicit assertions for the initial theme state before any switches occur. This would improve test coverage and documentation of expected behavior.

// Add at the start of the test:
expect(themeService.theme()).toBe(Theme.LIGHT);

58-65: Consider adding type assertions for editor instances.

While the tests are well-structured, consider adding specific type assertions for the editor instances to improve type safety and documentation.

// For the standalone editor case:
expect(editor instanceof monaco.editor.StandaloneCodeEditor).toBeTrue();
// For the diff editor case:
expect(editor instanceof monaco.editor.StandaloneDiffEditor).toBeTrue();
src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts (1)

Line range hint 63-93: Consider enhancing editor accessibility and responsiveness

While the editor configuration is comprehensive, consider the following improvements:

  1. Add ARIA labels and roles for better accessibility
  2. Ensure responsive behavior on different screen sizes

Consider updating the editor configuration:

 createStandaloneDiffEditor(domElement: HTMLElement): monaco.editor.IStandaloneDiffEditor {
     return monaco.editor.createDiffEditor(domElement, {
         automaticLayout: true,
+        ariaLabel: 'Code diff editor',
         glyphMargin: true,
         minimap: { enabled: false },
         readOnly: true,
-        renderSideBySide: true,
+        renderSideBySide: window.innerWidth > 768, // Switch to inline diff on mobile
         scrollBeyondLastLine: false,
         lineHeight: 16,
         stickyScroll: {
             enabled: false,
         },
src/test/javascript/spec/service/theme.service.spec.ts (3)

Line range hint 64-99: Enhance signal testing coverage.

While the test correctly verifies the theme changes, consider these improvements for better signal testing:

  1. Add verification of signal subscription cleanup in the teardown
  2. Add explicit state transition tests using toHaveBeenSet() matcher
  3. Consider tracking signal emission count to prevent unnecessary updates
 it('applies theme changes correctly', () => {
     TestBed.flushEffects();
     expect(documentGetElementMock).toHaveBeenCalledOnce();
+    const themeSpy = jest.fn();
+    const subscription = service.currentTheme.subscribe(themeSpy);
 
     service.applyThemePreference(Theme.DARK);
     TestBed.flushEffects();
 
+    expect(themeSpy).toHaveBeenCalledTimes(1);
     expect(documentGetElementMock).toHaveBeenCalledTimes(2);
     // ... existing expectations ...
 
+    subscription.unsubscribe();
 });

Line range hint 112-146: Add tests for OS preference change events.

The tests verify initial OS preferences but miss dynamic preference changes. Consider adding:

  1. Tests for preference change listener registration
  2. Tests for handling preference change events
  3. Tests for cleanup of preference change listeners
+    it('responds to OS preference changes', () => {
+        let preferenceChangeCallback: ((e: MediaQueryListEvent) => void) | undefined;
+        const addListenerSpy = jest.fn().mockImplementation((type: string, callback: (e: MediaQueryListEvent) => void) => {
+            preferenceChangeCallback = callback;
+        });
+
+        windowMatchMediaSpy = jest.spyOn(window, 'matchMedia').mockImplementation((query) => ({
+            media: query,
+            matches: false,
+            addEventListener: addListenerSpy,
+            removeEventListener: jest.fn(),
+        } as any as MediaQueryList));
+
+        service.initialize();
+        TestBed.flushEffects();
+
+        expect(addListenerSpy).toHaveBeenCalledWith('change', expect.any(Function));
+
+        // Simulate preference change
+        preferenceChangeCallback?.({ matches: true } as MediaQueryListEvent);
+        TestBed.flushEffects();
+
+        expect(service.currentTheme()).toBe(Theme.DARK);
+    });

Line range hint 148-167: Enhance print testing with error cases and theme restoration.

While the test covers the basic print flow, consider adding:

  1. Verification of theme restoration after print errors
  2. Tests for missing elements
  3. Tests for print cancellation
+    it('handles print errors gracefully', fakeAsync(() => {
+        const winSpy = jest.spyOn(window, 'print').mockImplementation(() => {
+            throw new Error('Print failed');
+        });
+        const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
+        const returnedElement = { rel: 'stylesheet', style: { display: 'block' } };
+        const docSpy = jest.spyOn(document, 'getElementById').mockReturnValue(returnedElement as any as HTMLElement);
+
+        service.print();
+        tick(250);
+
+        expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Print failed'));
+        expect(returnedElement.rel).toBe('stylesheet'); // Should restore original state
+        expect(returnedElement.style.display).toBe('block');
+    }));
+
+    it('handles missing elements gracefully', fakeAsync(() => {
+        const winSpy = jest.spyOn(window, 'print').mockImplementation();
+        const docSpy = jest.spyOn(document, 'getElementById').mockReturnValue(null);
+
+        service.print();
+        tick(250);
+
+        expect(winSpy).toHaveBeenCalledOnce(); // Should still attempt to print
+    }));
docs/dev/guidelines/client-design.rst (1)

Line range hint 182-207: Update code example to use Angular 18 signals.

The code example showing theme subscription uses RxJS observables and manual subscription management. Since this PR is migrating to Angular 18 practices, we should update this example to use signals for better reactivity and reduced boilerplate.

Here's the updated example using signals:

 @Component({
     selector: 'jhi-my-component',
     templateUrl: './my-component.component.html',
     styleUrls: ['my-component.component.scss'],
 })
-export class MyComponent implements OnInit, OnDestroy {
-    isDark = false;
-    themeSubscription: Subscription;
+export class MyComponent {
+    isDark = computed(() => this.themeService.currentTheme() === Theme.DARK);

-    constructor(private themeService: ThemeService) {}
+    constructor(private themeService: ThemeService) { }

-    ngOnInit() {
-        this.themeSubscription = this.themeService.getCurrentThemeObservable().subscribe((theme) => {
-            this.isDark = theme === Theme.DARK;
-        });
-    }
-
-    ngOnDestroy() {
-       this.themeSubscription.unsubscribe();
-    }
}

Benefits of this approach:

  • Automatic cleanup (no need for manual subscription management)
  • More concise and reactive code
  • Better alignment with Angular 18 practices
  • Improved performance through fine-grained updates
src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts (1)

90-91: Enhance the comment to better explain the workaround

While the comment references the ng-mocks issue, it would be helpful to:

  1. Explain the impact of using the real component
  2. Document any additional setup required
  3. Add a TODO to revisit once the ng-mocks issue is resolved
-// Component can not be mocked at the moment https://github.com/help-me-mom/ng-mocks/issues/8634
+// TODO: Replace with MockComponent once ng-mocks issue #8634 is resolved
+// Using real ThemeSwitchComponent as a workaround due to ng-mocks limitation
+// Note: This may require additional setup for ThemeSwitchComponent's dependencies
src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.html (1)

132-137: Consider memoizing frequently accessed signals

While the transition to signals is good, the template makes multiple calls to highlightedElements(). Consider memoizing this value to prevent unnecessary recomputations, especially if the highlighting logic is complex.

Example optimization in the component:

// Instead of directly using the signal in the template
private readonly highlightedElements = signal<Set<string>>(new Set());

// Add a memoized computed that only updates when the underlying signal changes
protected readonly highlightedElementsValue = computed(() => this.highlightedElements());

Then update the template bindings to use the memoized value:

-@if (highlightedElements() && highlightedElements().size > 0) {
+@if (highlightedElementsValue() && highlightedElementsValue().size > 0) {
-[highlightedElements]="highlightedElements()"
+[highlightedElements]="highlightedElementsValue()"

Also applies to: 163-163

src/main/webapp/app/exercises/programming/shared/instructions-render/programming-exercise-instruction.component.ts (2)

92-96: Consider adding type safety to the theme subscription

The theme subscription implementation is correct and follows reactive patterns. However, we could enhance type safety.

Consider this improvement:

-    private themeChangeSubscription = toObservable(this.themeService.currentTheme).subscribe(() => {
+    private themeChangeSubscription = toObservable(this.themeService.currentTheme).pipe(
+        filter((theme): theme is NonNullable<typeof theme> => theme !== null)
+    ).subscribe((theme) => {
         if (!this.isInitial) {
             this.updateMarkdown();
         }
     });

48-50: Consider splitting the component for better maintainability

The component handles multiple responsibilities (markdown rendering, task injection, theme management). Consider breaking it down into smaller, more focused components following the Single Responsibility Principle.

Suggested improvements:

  1. Extract markdown rendering logic into a separate service
  2. Create a dedicated TaskInjectorComponent for task-related functionality
  3. Consider using a facade service to coordinate between these components

This would improve:

  • Testability
  • Maintainability
  • Code reuse
  • Separation of concerns
src/test/javascript/spec/integration/code-editor/code-editor-instructor.integration.spec.ts (3)

Line range hint 66-200: Consider extracting common setup logic into helper functions.

The beforeEach block contains a significant amount of setup code. Consider extracting the spy setup and subject initialization into separate helper functions to improve readability and maintainability.

+ const initializeSpies = (services) => {
+   const spies = {
+     checkIfRepositoryIsClean: jest.spyOn(services.codeEditorRepository, 'getStatus'),
+     getRepositoryContent: jest.spyOn(services.codeEditorRepositoryFile, 'getRepositoryContent'),
+     // ... other spy initializations
+   };
+   return spies;
+ };
+
+ const initializeSubjects = () => {
+   return {
+     checkIfRepositoryIsClean: new Subject<{ isClean: boolean }>(),
+     getRepositoryContent: new Subject<{ [fileName: string]: FileType }>(),
+     // ... other subject initializations
+   };
+ };

4-4: Remove commented-out TypeScript ignore directive.

The @ts-ignore comment suggests potential type-safety issues that should be addressed properly rather than ignored.

-// @ts-ignore

Line range hint 33-38: Consider using test data builders for exercise objects.

Creating test data directly in the test cases makes the tests harder to maintain. Consider implementing a test data builder pattern for ProgrammingExercise and related objects.

class ProgrammingExerciseBuilder {
    private exercise: Partial<ProgrammingExercise> = {
        id: 1,
        problemStatement: '',
        course: { id: 1 }
    };

    withTemplateParticipation(id: number): this {
        this.exercise.templateParticipation = { id, repositoryUri: `test${id}` };
        return this;
    }

    // ... other builder methods

    build(): ProgrammingExercise {
        return this.exercise as ProgrammingExercise;
    }
}
src/test/javascript/spec/component/shared/navbar.component.spec.ts (2)

127-132: Consider enhancing the GuidedTourService mock implementation.

The current mock only implements getGuidedTourAvailabilityStream. Consider adding other commonly used methods from GuidedTourService to make the mock more comprehensive for future test cases.

 {
     provide: GuidedTourService,
     useValue: {
         getGuidedTourAvailabilityStream: () => of(false),
+        startTour: () => {},
+        endTour: () => {},
+        isActive: () => false,
     },
 },

Line range hint 776-885: Consider enhancing breakpoint test documentation and maintainability.

While the breakpoint tests are comprehensive, consider these improvements:

  1. Add comments explaining the breakpoint logic for each user role
  2. Extract magic numbers into named constants for better maintainability
+// Breakpoint constants for different user roles
+const BREAKPOINTS = {
+    ADMIN: {
+        COLLAPSE: 1100,
+        ICONS_TO_MENU: 600,
+        VERTICAL_NAV: 550,
+    },
+    INSTRUCTOR: {
+        COLLAPSE: 850,
+        ICONS_TO_MENU: 600,
+        VERTICAL_NAV: 470,
+    },
+    USER: {
+        COLLAPSE: 650,
+        ICONS_TO_MENU: 600,
+        VERTICAL_NAV: 470,
+    },
+    ANONYMOUS: {
+        COLLAPSE: 500,
+        VERTICAL_NAV: 450,
+        ICONS_TO_MENU: 400,
+    },
+};

 it.each([
     {
-        width: 1200,
+        width: BREAKPOINTS.ADMIN.COLLAPSE + 100, // Above collapse breakpoint
         account: { login: 'test' },
         roles: [Authority.ADMIN],
         expected: { isCollapsed: false, isNavbarNavVertical: false, iconsMovedToMenu: false },
     },
     // ... rest of the test cases using constants
src/main/webapp/app/core/theme/theme-switch.component.ts (1)

67-68: Confirm the necessity of the setTimeout when opening the popover

The setTimeout in toggleTheme() delays the popover opening by 250 milliseconds after toggling the theme. Verify if this delay is required for the desired user experience or if it can be refactored for clarity and responsiveness.

src/main/webapp/app/core/theme/theme.service.ts (1)

Line range hint 171-195: Consider using Renderer2 for DOM manipulations

The applyTheme method performs direct DOM manipulations using native methods like document.getElementById and document.createElement. To adhere to Angular best practices and enhance compatibility with server-side rendering and web workers, consider using Angular's Renderer2 service for DOM manipulations.

Apply this diff to refactor the DOM manipulations using Renderer2:

+import { Renderer2, RendererFactory2 } from '@angular/core';

@Injectable({
    providedIn: 'root',
})
export class ThemeService {
+    private renderer: Renderer2;

+    constructor() {
+        const rendererFactory = inject(RendererFactory2);
+        this.renderer = rendererFactory.createRenderer(null, null);
         effect(() => {
             // Apply the theme as soon as the currentTheme changes
             const currentTheme = this.currentTheme();
             untracked(() => this.applyTheme(currentTheme));
         });
     }

     private applyTheme(theme: Theme) {
-        const overrideTag = document.getElementById(THEME_OVERRIDE_ID);
+        const overrideTag = this.renderer.selectRootElement(`#${THEME_OVERRIDE_ID}`, true);

         if (theme.isDefault) {
             overrideTag?.remove();
         } else {
             // Select the head element
-            const head = document.getElementsByTagName('head')[0];
+            const head = this.renderer.selectRootElement('head', true);

             // Create new override tag from the current theme
-            const newTag = document.createElement('link');
-            newTag.id = THEME_OVERRIDE_ID;
-            newTag.rel = 'stylesheet';
-            newTag.href = theme.fileName! + '?_=' + new Date().setMinutes(0, 0, 0);
+            const newTag = this.renderer.createElement('link');
+            this.renderer.setAttribute(newTag, 'id', THEME_OVERRIDE_ID);
+            this.renderer.setAttribute(newTag, 'rel', 'stylesheet');
+            this.renderer.setAttribute(newTag, 'href', theme.fileName! + '?_=' + new Date().setMinutes(0, 0, 0));

             // As soon as the new style sheet loads, remove the old override (if present)
             newTag.onload = () => {
                 overrideTag?.remove();
             };

             // Append the new stylesheet link tag to the head
-            const existingLinkTags = head.getElementsByTagName('link');
-            const lastLinkTag = existingLinkTags[existingLinkTags.length - 1];
-            head.insertBefore(newTag, lastLinkTag?.nextSibling);
+            this.renderer.appendChild(head, newTag);
         }
     }
}

This change enhances compatibility and maintains Angular's platform independence.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 70ccfc3 and a2abbdd.

📒 Files selected for processing (29)
  • docs/dev/guidelines/client-design.rst (1 hunks)
  • src/main/webapp/app/app.module.ts (2 hunks)
  • src/main/webapp/app/core/theme/theme-switch.component.html (2 hunks)
  • src/main/webapp/app/core/theme/theme-switch.component.ts (4 hunks)
  • src/main/webapp/app/core/theme/theme.module.ts (0 hunks)
  • src/main/webapp/app/core/theme/theme.service.ts (4 hunks)
  • src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.html (2 hunks)
  • src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.ts (5 hunks)
  • src/main/webapp/app/exercises/programming/shared/instructions-render/programming-exercise-instruction.component.ts (3 hunks)
  • src/main/webapp/app/exercises/programming/shared/instructions-render/service/programming-exercise-plant-uml.service.ts (4 hunks)
  • src/main/webapp/app/exercises/programming/shared/instructions-render/step-wizard/programming-exercise-instruction-step-wizard.component.html (1 hunks)
  • src/main/webapp/app/lti/lti13-exercise-launch.component.ts (1 hunks)
  • src/main/webapp/app/shared/dashboards/tutor-participation-graph/progress-bar/progress-bar.component.ts (3 hunks)
  • src/main/webapp/app/shared/metis/emoji/emoji-picker.component.html (1 hunks)
  • src/main/webapp/app/shared/metis/emoji/emoji-picker.component.ts (1 hunks)
  • src/main/webapp/app/shared/metis/emoji/emoji.component.html (1 hunks)
  • src/main/webapp/app/shared/metis/emoji/emoji.component.ts (1 hunks)
  • src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts (1 hunks)
  • src/test/javascript/spec/component/emoji/emoji-picker.component.spec.ts (1 hunks)
  • src/test/javascript/spec/component/emoji/emoji.component.spec.ts (0 hunks)
  • src/test/javascript/spec/component/programming-exercise/programming-exercise-instruction.component.spec.ts (2 hunks)
  • src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts (4 hunks)
  • src/test/javascript/spec/component/shared/navbar.component.spec.ts (18 hunks)
  • src/test/javascript/spec/component/shared/progress-bar.component.spec.ts (3 hunks)
  • src/test/javascript/spec/component/theme/theme-switch.component.spec.ts (1 hunks)
  • src/test/javascript/spec/helpers/mocks/service/mock-theme.service.ts (1 hunks)
  • src/test/javascript/spec/integration/code-editor/code-editor-instructor.integration.spec.ts (1 hunks)
  • src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts (1 hunks)
  • src/test/javascript/spec/service/theme.service.spec.ts (6 hunks)
💤 Files with no reviewable changes (2)
  • src/main/webapp/app/core/theme/theme.module.ts
  • src/test/javascript/spec/component/emoji/emoji.component.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • src/main/webapp/app/lti/lti13-exercise-launch.component.ts
🧰 Additional context used
📓 Path-based instructions (25)
src/main/webapp/app/app.module.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/core/theme/theme-switch.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/core/theme/theme-switch.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/core/theme/theme.service.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/exercises/programming/shared/instructions-render/programming-exercise-instruction.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/exercises/programming/shared/instructions-render/service/programming-exercise-plant-uml.service.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/exercises/programming/shared/instructions-render/step-wizard/programming-exercise-instruction-step-wizard.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/shared/dashboards/tutor-participation-graph/progress-bar/progress-bar.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/metis/emoji/emoji-picker.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/shared/metis/emoji/emoji-picker.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/metis/emoji/emoji.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/shared/metis/emoji/emoji.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/test/javascript/spec/component/emoji/emoji-picker.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/programming-exercise/programming-exercise-instruction.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/shared/monaco-editor/monaco-editor.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/shared/navbar.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/shared/progress-bar.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/theme/theme-switch.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/helpers/mocks/service/mock-theme.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/integration/code-editor/code-editor-instructor.integration.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/integration/guided-tour/guided-tour.integration.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/service/theme.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}}

🔇 Additional comments (55)
src/main/webapp/app/shared/metis/emoji/emoji.component.html (1)

2-4: Excellent use of new Angular control flow syntax!

The migration from *ngIf to @if/@else syntax aligns perfectly with Angular 18 practices. The function call dark() indicates proper reactive state management.

src/main/webapp/app/shared/metis/emoji/emoji.component.ts (4)

1-1: LGTM! Modern Angular imports added correctly

The addition of computed and inject imports aligns with Angular's modern reactive programming practices.


11-11: LGTM! Modern dependency injection pattern

Using inject() function aligns with Angular's recommended approach for dependency injection.


16-16: Consider extracting theme computation to a shared location

The dark theme computation could potentially be reused across components. Consider moving it to the ThemeService as a shared computed signal if it's used in multiple places.

// In theme.service.ts
+readonly isDarkTheme = computed(() => this.currentTheme() === Theme.DARK);

// In this component
-dark = computed(() => this.themeService.currentTheme() === Theme.DARK);
+dark = this.themeService.isDarkTheme;
#!/bin/bash
# Description: Check if similar theme computations exist in other components

# Search for similar dark theme computations
rg -A 2 "Theme\.DARK|isDark|darkTheme" "src/main/webapp/app"

# Search for components using ThemeService
ast-grep --pattern 'ThemeService'

10-11: Verify removal of OnDestroy implementation

The removal of OnDestroy is appropriate since we're using computed signals instead of subscriptions. However, let's verify there are no other subscriptions that might need cleanup.

✅ Verification successful

Removal of OnDestroy confirmed as appropriate
No remaining subscriptions found in EmojiComponent that require cleanup.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining subscriptions in the component
# that might require cleanup

# Search for subscription patterns in the component
rg -A 5 "subscribe\(|Subscription" "src/main/webapp/app/shared/metis/emoji/emoji.component"

# Search for any event listeners or observables that might need cleanup
ast-grep --pattern 'this.$_ = $_.$$$subscribe($_)'

Length of output: 12348

src/test/javascript/spec/helpers/mocks/service/mock-theme.service.ts (2)

1-4: LGTM! Clean imports and proper mock naming.

The imports are minimal and specific, and the mock service follows proper naming conventions.


11-13: Verify behavioral consistency with ThemeService.

The implementation looks good, but we should verify that it mirrors the behavior of the actual ThemeService.

#!/bin/bash
# Description: Compare mock behavior with actual ThemeService
# Find the actual ThemeService implementation
rg -A 5 "applyThemePreference.*Theme.*undefined" "src/main/webapp"

# Look for test cases that verify the consistency
rg -l "expect.*applyThemePreference.*Theme\.LIGHT" "src/test/javascript/spec"
src/main/webapp/app/exercises/programming/shared/instructions-render/step-wizard/programming-exercise-instruction-step-wizard.component.html (1)

5-5: LGTM! Correctly using new Angular 18 syntax.

The template has been properly updated to use the new @for syntax instead of *ngFor, which aligns with Angular 18 best practices.

src/test/javascript/spec/component/emoji/emoji-picker.component.spec.ts (1)

36-39: LGTM! Theme change verification is well implemented.

The test correctly verifies:

  • Theme change using the new applyThemePreference method
  • Dark mode state using recommended toBeTrue() assertion
  • Emoji image path generation for the updated theme
src/main/webapp/app/core/theme/theme-switch.component.html (2)

5-5: LGTM! Clean implementation of the sync toggle.

The changes appropriately use the jhiTranslate directive and implement a reactive approach with the isSyncedWithOS() method.

Also applies to: 7-7


20-21: Verify the hardcoded animation value.

While simplifying the animation property to a hardcoded value can improve code clarity, please verify that this doesn't affect any dynamic animation requirements or accessibility features.

Run the following script to check for any dynamic animation configurations in the component or its tests:

✅ Verification successful

Hardcoded animation value is acceptable and consistent with existing configurations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for animation-related configurations
# Look for animation configurations in the component and test files
rg -A 3 "animation.*=|animations:" --type ts

Length of output: 3216

src/main/webapp/app/shared/dashboards/tutor-participation-graph/progress-bar/progress-bar.component.ts (3)

1-6: LGTM! Clean import updates for signal integration

The changes appropriately remove OnInit and add toObservable for signal conversion, aligning with Angular 18 practices.


Line range hint 11-19: LGTM! Clean class structure

The component properly implements OnChanges and OnDestroy, with well-defined Input properties following Angular guidelines.


Line range hint 31-42: LGTM! Proper lifecycle management

The component correctly handles changes and cleanup, preventing memory leaks.

src/test/javascript/spec/component/shared/progress-bar.component.spec.ts (1)

8-8: LGTM: Proper mock service import

The import of MockThemeService aligns with our mocking guidelines and supports the transition to signals-based theme management.

src/test/javascript/spec/component/theme/theme-switch.component.spec.ts (3)

3-9: LGTM! Imports follow best practices.

The imports are well-organized and follow the guidelines by avoiding full module imports and properly importing test utilities.


45-54: LGTM! Theme toggle test follows best practices.

The test properly validates theme changes and follows the coding guidelines for expectations using toBeTrue() and toHaveBeenCalledWith().


70-84: LGTM! Popover behavior tests are comprehensive.

The tests properly validate:

  • Open/close functionality
  • Timing of operations
  • Mouse leave behavior
    And follow the coding guidelines for spy expectations using toHaveBeenCalledOnce() and not.toHaveBeenCalled().
src/main/webapp/app/app.module.ts (1)

24-24: Verify standalone component configuration

The import of ThemeSwitchComponent suggests it's now a standalone component. Let's verify its configuration to ensure it follows Angular 18 best practices.

✅ Verification successful

Further Verification Required


Further Verification Required


Review Comment Clarification

The ThemeSwitchComponent is correctly configured as a standalone component and is only utilized within the AppModule. There are no additional usages that impact lazy loading or initial load performance.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify ThemeSwitchComponent is properly configured as standalone
# and uses signals for state management

# Check component decorator and configuration
ast-grep --pattern 'Component({
  $$$
  standalone: true,
  $$$
})
class ThemeSwitchComponent'

# Check for signal usage
rg -A 5 'signal<|computed\(' app/core/theme/theme-switch.component.ts

Length of output: 373


Script:

#!/bin/bash
# Description: Locate ThemeSwitchComponent and verify its standalone configuration and signal usage

# Locate all ThemeSwitchComponent files
fd 'theme-switch\.component\.ts$' > theme_switch_files.txt

if [ -s theme_switch_files.txt ]; then
    while read -r file; do
        echo "Verifying $file"

        # Verify standalone configuration
        ast-grep --pattern 'Component({
          $$$
          standalone: true,
          $$$
        })' "$file"

        # Check for signal usage
        rg -A 5 'signal<|computed\(' "$file"
    done < theme_switch_files.txt
else
    echo "ThemeSwitchComponent file not found."
fi

Length of output: 1593


Script:

#!/bin/bash
# Description: Locate all usages of ThemeSwitchComponent to assess lazy loading implications

# Search for ThemeSwitchComponent usage in TypeScript and HTML files, excluding app.module.ts
rg -l 'ThemeSwitchComponent' --glob 'src/main/webapp/app/**/*.ts' --glob 'src/main/webapp/app/**/*.html' | grep -v 'app.module.ts' > theme_switch_usages.txt

if [ -s theme_switch_usages.txt ]; then
    echo "ThemeSwitchComponent is used in the following files:"
    cat theme_switch_usages.txt
else
    echo "No usages of ThemeSwitchComponent found outside app.module.ts."
fi

Length of output: 367

src/main/webapp/app/exercises/programming/shared/instructions-render/service/programming-exercise-plant-uml.service.ts (2)

1-1: LGTM! Modern dependency injection implementation.

The transition to using inject() function and readonly properties aligns well with Angular 18's best practices, improving code maintainability and type safety.

Also applies to: 15-16


Line range hint 38-43: Verify cache invalidation performance under frequent theme changes.

The cache configuration looks good, but we should verify that frequent theme changes don't cause performance issues due to cache invalidation.

✅ Verification successful

Cache Invalidation Performance Verified

The cache invalidation setup with themeChangedSubject is localized to programming-exercise-plant-uml.service.ts. No other components are affected, ensuring that frequent theme changes do not introduce unintended performance issues.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other uses of themeChangedSubject to understand the impact
rg -l "themeChangedSubject" src/main/webapp/

# Look for other cached components that might be affected by theme changes
ast-grep --pattern '@Cacheable({
  $$$
  cacheBusterObserver: $_,
  $$$
})'

Length of output: 4227

src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts (3)

4-4: LGTM! Import path update is appropriate.

The change to use absolute imports from 'app/' improves maintainability and follows Angular best practices.


15-16: LGTM! Variable declaration follows testing best practices.

Moving the themeService declaration to class level and removing BehaviorSubject aligns with the migration to signals in Angular 18.


28-28: LGTM! Proper TestBed injection setup.

The ThemeService injection in beforeEach follows Angular testing best practices.

src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts (2)

17-17: LGTM! Proper implementation of Angular signals

The initialization of currentTheme directly from ThemeService's signal aligns with Angular 18's best practices for reactive state management.


Line range hint 26-28: Verify theme application timing and cleanup

While the effect implementation looks correct, let's verify:

  1. Theme application timing during editor initialization
  2. Proper cleanup to prevent memory leaks
src/test/javascript/spec/service/theme.service.spec.ts (1)

13-13: LGTM! Clean setup with proper mocking practices.

The mock variable renaming improves naming consistency, and the setup follows jest best practices with proper spy implementations.

Also applies to: 39-39

docs/dev/guidelines/client-design.rst (1)

240-240: LGTM! Method name updated correctly.

The change from applyThemeExplicitly to applyThemePreference aligns with the PR objectives and maintains consistency with the updated ThemeService implementation.

src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts (1)

90-91: Verify test isolation with real ThemeSwitchComponent

Using the real ThemeSwitchComponent instead of a mock could affect test isolation and performance. Please verify:

  1. That all required dependencies are properly provided
  2. That the component's initialization doesn't interfere with the guided tour tests
  3. That test execution time remains reasonable
src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.html (2)

Line range hint 1-1: LGTM: Proper usage of new Angular control flow syntax

The template correctly uses the new Angular control flow syntax (@if and @for) instead of the older *ngIf and *ngFor directives, which aligns with the coding guidelines.

Also applies to: 7-7, 8-8, 13-13, 15-15, 20-20, 38-38, 41-41, 63-63, 66-66, 71-71, 74-74, 82-82, 85-85, 89-89, 92-92, 132-132, 138-138, 141-141, 147-147, 150-150, 170-170, 179-179, 182-182, 185-185, 188-188, 191-191


Line range hint 141-146: LGTM: Clean component integration

The template demonstrates proper component integration with clear input/output bindings and event handling. The conditional rendering of either jhi-modeling-editor or jhi-modeling-assessment based on the assessment mode is well-structured.

Also applies to: 150-164

src/main/webapp/app/exercises/programming/shared/instructions-render/programming-exercise-instruction.component.ts (1)

14-14: LGTM: Modern dependency injection pattern applied correctly

The changes follow Angular 18's best practices by:

  • Using the new inject() function for dependency injection
  • Importing toObservable for signal-to-observable conversion
  • Moving ThemeService injection out of the constructor

Also applies to: 43-43, 51-52

src/test/javascript/spec/component/programming-exercise/programming-exercise-instruction.component.spec.ts (1)

213-213: LGTM: Fixture change detection is correctly placed

The addition of fixture.detectChanges() ensures that Angular's change detection is triggered after the component changes, which is a good practice for component testing.

src/test/javascript/spec/integration/code-editor/code-editor-instructor.integration.spec.ts (3)

Line range hint 1-65: LGTM! Well-organized imports and comprehensive mock setup.

The test file demonstrates good practices with:

  • Clear separation of imports
  • Proper use of NgMocks for component mocking
  • Comprehensive provider setup

Line range hint 251-500: LGTM! Comprehensive test coverage for repository navigation.

The test suite thoroughly covers:

  • Repository switching scenarios
  • Navigation between different repository types
  • Error states
  • Component state management

Line range hint 201-250: Consider adding test coverage for error scenarios in repository navigation.

The initContainer helper function is well implemented, but the test suite could benefit from additional test cases covering error scenarios during repository navigation.

Add test cases for:

  • Network errors during repository navigation
  • Invalid repository states
  • Permission-related errors
src/test/javascript/spec/component/shared/navbar.component.spec.ts (1)

231-246: LGTM! Improved readability of breadcrumb expectations.

The reformatting of breadcrumb expectations follows a consistent pattern and improves code readability. The structure makes it easier to understand and maintain the test cases.

Also applies to: 257-266, 279-288, 299-303, 405-409, 434-438, 479-488, 552-556, 592-596, 627-636, 660-664, 692-701, 737-746, 767-770

src/main/webapp/app/shared/metis/emoji/emoji-picker.component.ts (3)

1-1: Imports Updated Appropriately

The import statement correctly includes computed and inject for utilizing Angular's reactive features.


11-11: Proper Use of 'inject' for Dependency Injection

Using inject(ThemeService) simplifies dependency management and aligns with Angular 18 best practices.


19-20: Verify Fallback Function in 'singleImageFunction'

The fallback function () => '' returns an empty string when the theme is not dark. Please confirm that this behavior is intended and does not affect the emoji display in light mode.

src/main/webapp/app/core/theme/theme-switch.component.ts (3)

21-23: Excellent use of standalone components and OnPush change detection

Declaring the component as standalone and utilizing ChangeDetectionStrategy.OnPush enhances performance and modularity. This aligns well with the Angular style guidelines and promotes efficient change detection.


28-28: Verify compatibility of inject(), input.required, and viewChild.required with your Angular version

While the use of inject(), input.required, and viewChild.required reflects modern Angular practices, please ensure that these features are supported in your project's current Angular version and align with your team's coding standards.

Also applies to: 30-31


33-34: Effective use of computed for reactive state management

Utilizing computed() for isDarkTheme and isSyncedWithOS provides reactive and efficient state handling, enhancing the maintainability of the component.

src/main/webapp/app/core/theme/theme.service.ts (9)

46-49: Proper use of Angular signals for user preference

The implementation correctly initializes _userPreference as a writable signal and exposes userPreference as a readonly signal. This adheres to best practices for encapsulating mutable state and providing a read-only interface where appropriate.


58-60: Initialization of systemPreference with default theme

Setting systemPreference as a signal with a default value of Theme.LIGHT ensures that there's a fallback theme if the system preference cannot be determined.


63-66: Effective use of computed signal for currentTheme

The currentTheme computed signal correctly derives its value based on userPreference and systemPreference. This reactive approach ensures that theme changes are automatically reflected in the application when either preference changes.


67-68: Dependency injection using inject function

Using the inject function to obtain an instance of LocalStorageService aligns with Angular 18 practices and simplifies the constructor.


72-76: Proper use of effect and untracked for theme application

The effect watches for changes in currentTheme and applies the theme accordingly. Wrapping applyTheme with untracked ensures that calling this method doesn't create additional dependencies or unintended side effects within the reactive context.


87-93: Handling system theme preference changes efficiently

Adding an event listener to darkSchemeMediaQuery allows the application to react to changes in the user's system theme preference. Updating the systemPreference signal within this listener ensures that currentTheme reflects the latest preference.


102-103: Initial setting of systemPreference and userPreference

Setting the initial values of systemPreference and _userPreference ensures that the theme is correctly applied when the application starts, based on the system settings and any stored user preferences.


109-115: Robust retrieval of stored theme preference

The getStoredThemePreference method safely retrieves the stored theme preference from local storage and validates it against the available themes. Clearing unknown or invalid stored values prevents potential errors due to mismatched theme identifiers.


155-162: Efficiently applying user theme preference and managing local storage

The applyThemePreference method accurately stores or clears the theme preference in local storage based on user input and updates the _userPreference signal. This ensures that user preferences are persisted and reflected in the application's theme.

src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.ts (3)

43-44: Injecting ThemeService using Angular's inject function is appropriate

The use of inject(ThemeService) for dependency injection aligns with Angular 18's recommended practices and enhances code readability by reducing constructor parameters.


98-100: Utilization of Angular Signals and Computed Properties

Implementing highlightedElements as a signal and highlightColor as a computed property effectively leverages Angular's reactive programming features, ensuring efficient updates and state management.


118-130: Efficiently Updating Highlighted Elements on Theme Change

The effect hook correctly updates highlightedElements whenever the theme changes, ensuring that UI elements reflect the current theme. This approach is consistent with Angular's reactive patterns and promotes maintainability.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🧹 Outside diff range and nitpick comments (30)
src/main/webapp/app/shared/metis/emoji/emoji.component.ts (1)

13-16: Consider adding JSDoc documentation

Adding JSDoc comments would improve maintainability by documenting:

  • The purpose of the emoji input
  • The computed dark property's usage
  • The component's overall responsibility

Example improvement:

+/** Utility instance for emoji-related operations */
 utils = EmojiUtils;
+/** The emoji character to be rendered */
 @Input() emoji: string;

+/** Computed signal that determines if dark theme is active */
 dark = computed(() => this.themeService.currentTheme() === Theme.DARK);
src/main/webapp/app/shared/metis/emoji/emoji-picker.component.html (1)

10-11: Good use of Angular 18 signals pattern!

The changes from property bindings to function calls (dark() and singleImageFunction()) align well with Angular 18's signals pattern for reactive updates. This approach ensures that theme changes are properly propagated through the component.

Consider documenting these patterns in your team's guidelines as they serve as a good example for:

  • Using signals for reactive theme management
  • Proper integration with third-party components (emoji-mart)
  • Clean template syntax that clearly indicates reactive bindings
src/test/javascript/spec/helpers/mocks/service/mock-theme.service.ts (1)

5-6: Consider adding JSDoc comments for better documentation

The implementation correctly uses signals and follows proper encapsulation patterns. Consider adding JSDoc comments to document the purpose of these signals and their readonly nature.

+/** Private signal for managing the current theme state */
 private _currentTheme = signal(Theme.LIGHT);
+/** Public readonly access to the current theme */
 public readonly currentTheme = this._currentTheme.asReadonly();
src/test/javascript/spec/component/emoji/emoji-picker.component.spec.ts (1)

33-34: Improve assertion specificity as per coding guidelines

The test follows the coding guidelines for boolean assertions (toBeTrue/toBeFalse), but the string comparison could be more specific.

Consider using more specific assertions:

-expect(comp.singleImageFunction()({ unified: '1F519' } as EmojiData)).toBe('');
+expect(comp.singleImageFunction()({ unified: '1F519' } as EmojiData)).toStrictEqual('');

-expect(comp.singleImageFunction()({ unified: '1F519' } as EmojiData)).toBe('public/emoji/1f519.png');
+expect(comp.singleImageFunction()({ unified: '1F519' } as EmojiData)).toMatch(/^public\/emoji\/[a-f0-9]+\.png$/);

Also applies to: 38-39

src/main/webapp/app/shared/dashboards/tutor-participation-graph/progress-bar/progress-bar.component.ts (1)

Line range hint 62-74: Consider enhancing theme handling with a more type-safe approach

While the theme handling works, it could benefit from a more robust implementation:

Consider using a type-safe mapping object:

-chooseProgressBarTextColor() {
-    switch (this.themeService.currentTheme()) {
-        case Theme.DARK:
-            this.foregroundColorClass = 'text-white';
-            break;
-        case Theme.LIGHT:
-        default:
-            if (this.percentage < 100) {
-                this.foregroundColorClass = 'text-dark';
-            } else {
-                this.foregroundColorClass = 'text-white';
-            }
-    }
-}
+private readonly themeColorMap = {
+    [Theme.DARK]: 'text-white',
+    [Theme.LIGHT]: (percentage: number) => 
+        percentage < 100 ? 'text-dark' : 'text-white',
+} as const;
+
+chooseProgressBarTextColor() {
+    const theme = this.themeService.currentTheme();
+    const colorMapper = this.themeColorMap[theme] ?? this.themeColorMap[Theme.LIGHT];
+    this.foregroundColorClass = typeof colorMapper === 'function' 
+        ? colorMapper(this.percentage)
+        : colorMapper;
+}

This approach provides better type safety and makes it easier to add new themes in the future.

src/test/javascript/spec/component/shared/progress-bar.component.spec.ts (1)

59-62: Consider optimizing change detection calls

The explicit fixture.detectChanges() call after applyThemePreference might be redundant since the theme service should trigger change detection automatically through signals.

Consider removing the explicit change detection call:

 themeService.applyThemePreference(Theme.DARK);
-
-fixture.detectChanges();
src/test/javascript/spec/component/theme/theme-switch.component.spec.ts (3)

19-41: Consider optimizing test module imports and adding type safety.

The setup is well-structured but could be enhanced:

  1. Consider creating a minimal test module instead of importing the full ArtemisTestModule
  2. Add type safety to the spy declarations
- let openSpy: jest.SpyInstance;
- let closeSpy: jest.SpyInstance;
+ let openSpy: jest.SpyInstance<void, []>;
+ let closeSpy: jest.SpyInstance<void, []>;

45-54: Add negative test cases for theme toggle.

While the happy path is well tested, consider adding test cases for:

  • Error scenarios
  • Edge cases when theme service fails
  • Multiple rapid toggles

69-84: Consider adding timing variation tests.

The timing-dependent tests are well structured but could be more robust:

  1. Test different timing scenarios (e.g., rapid mouse enter/leave)
  2. Verify behavior when timing is just under/over threshold

Example additional test:

it('handles rapid mouse enter/leave correctly', fakeAsync(() => {
  component.openPopover();
  component.mouseLeave();
  tick(100);
  component.openPopover();
  component.mouseLeave();
  tick(100);
  expect(closeSpy).not.toHaveBeenCalled();
  tick(150);
  expect(closeSpy).toHaveBeenCalledOnce();
}));
src/main/webapp/app/app.module.ts (1)

Line range hint 31-48: Consider documenting theme configuration in module comments

The module structure correctly maintains separation of concerns and follows lazy loading principles. Since theme management is now component-based, consider adding a comment explaining the theme configuration approach for better maintainability.

Add a comment before the imports array:

 @NgModule({
     imports: [
+        // Theme management is handled via standalone ThemeSwitchComponent
+        // following Angular 18 practices for better modularity
         BrowserModule,
src/main/webapp/app/exercises/programming/shared/instructions-render/service/programming-exercise-plant-uml.service.ts (1)

47-47: Improve code reuse and type safety in PlantUML methods

The theme check logic and caching configuration are duplicated. Consider extracting common functionality to improve maintainability.

Consider these improvements:

+private readonly cacheConfig = {
+    maxCacheCount: 100,
+    maxAge: 3600000,
+    slidingExpiration: true,
+    cacheBusterObserver: themeChangedSubject,
+};

+private getThemeParams(plantUml: string): HttpParams {
+    return new HttpParams({ encoder: this.encoder })
+        .set('plantuml', plantUml)
+        .set('useDarkTheme', String(this.themeService.currentTheme() === Theme.DARK));
+}

 @Cacheable({
-    maxCacheCount: 100,
-    maxAge: 3600000,
-    slidingExpiration: true,
-    cacheBusterObserver: themeChangedSubject,
+    ...this.cacheConfig
 })
 getPlantUmlImage(plantUml: string) {
     return this.http
         .get(`${this.resourceUrl}/png`, {
-            params: new HttpParams({ encoder: this.encoder })
-                .set('plantuml', plantUml)
-                .set('useDarkTheme', this.themeService.currentTheme() === Theme.DARK),
+            params: this.getThemeParams(plantUml),
             responseType: 'arraybuffer',
         })
         .pipe(map((res) => this.convertPlantUmlResponseToBase64(res)));
 }

Also applies to: 68-68

src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts (1)

46-54: Consider adding edge case tests for theme changes

While the basic theme switching tests are good, consider adding tests for:

  • Rapid theme switches
  • Invalid theme values
  • Theme switch during editor initialization

Example test case:

it('should handle rapid theme switches correctly', () => {
    themeService.applyThemePreference(Theme.DARK);
    themeService.applyThemePreference(Theme.LIGHT);
    themeService.applyThemePreference(Theme.DARK);
    TestBed.flushEffects();
    expect(setThemeSpy).toHaveBeenCalledTimes(4);
    expect(setThemeSpy).toHaveBeenLastCalledWith(MONACO_DARK_THEME_DEFINITION.id);
});
src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts (1)

Line range hint 24-28: Consider adding cleanup for effect

The effect is created in the constructor but there's no cleanup mechanism. To prevent potential memory leaks, consider using the cleanup callback or moving the effect to a component that handles its lifecycle.

 constructor() {
     this.registerCustomThemes();
     this.registerCustomMarkdownLanguage();
 
-    effect(() => {
-        this.applyTheme(this.currentTheme());
-    });
+    // Store effect reference for potential cleanup
+    const themeEffect = effect(() => {
+        this.applyTheme(this.currentTheme());
+        // Return cleanup function if needed
+        return () => {
+            // Cleanup theme-related resources
+        };
+    });
 }
src/test/javascript/spec/service/theme.service.spec.ts (2)

Line range hint 64-99: Improve test structure with better organization

While the test logic is correct, consider restructuring it for better readability and maintainability.

Consider this structure:

 it('applies theme changes correctly', () => {
     TestBed.flushEffects();
-    expect(documentGetElementMock).toHaveBeenCalledOnce();
+    // Initial state verification
+    expect(documentGetElementMock).toHaveBeenCalledOnce();
+    
+    // Apply dark theme
     service.applyThemePreference(Theme.DARK);
     TestBed.flushEffects();
 
-    expect(documentGetElementMock).toHaveBeenCalledTimes(2);
+    // Verify dark theme application
+    expect(documentGetElementMock).toHaveBeenCalledTimes(2);
     expect(documentGetElementMock).toHaveBeenCalledWith(THEME_OVERRIDE_ID);
     expect(documentGetElementsByTagNameMock).toHaveBeenCalledOnce();
     expect(documentGetElementsByTagNameMock).toHaveBeenCalledWith('head');
+    
+    // Verify DOM updates
     expect(documentCreateElementMock).toHaveBeenCalledOnce();
     expect(documentCreateElementMock).toHaveBeenCalledWith('link');
     expect(newElement.id).toBe(THEME_OVERRIDE_ID);
     expect(newElement.rel).toBe('stylesheet');
     expect(newElement.href).toStartWith('theme-dark.css');
     expect(newElement.onload).toEqual(expect.any(Function));
+    
+    // Verify theme state
     expect(service.currentTheme()).toBe(Theme.DARK);
     expect(storeSpy).toHaveBeenCalledWith(THEME_LOCAL_STORAGE_KEY, 'DARK');
     expect(linkElement.remove).toHaveBeenCalledOnce();
 
+    // Apply light theme
     service.applyThemePreference(Theme.LIGHT);
     TestBed.flushEffects();
 
+    // Verify light theme application
     expect(documentGetElementMock).toHaveBeenCalledTimes(3);
     expect(documentGetElementMock).toHaveBeenNthCalledWith(3, THEME_OVERRIDE_ID);
     expect(linkElement.remove).toHaveBeenCalledTimes(2);
     expect(service.currentTheme()).toBe(Theme.LIGHT);
 });

126-133: Consider adding edge cases for OS preference tests

While the current tests cover the basic scenarios, consider adding tests for:

  • OS preference changes after initial load
  • Handling of invalid OS preference values
  • Race conditions between OS preference changes and explicit theme changes

Example test case:

it('handles OS preference changes after initial load', fakeAsync(() => {
    const retrieveSpy = jest.spyOn(localStorageService, 'retrieve').mockReturnValue(undefined);
    let darkModeListener: ((e: MediaQueryListEvent) => void) | undefined;
    
    // Mock matchMedia to capture the listener
    windowMatchMediaSpy.mockImplementation((query) => ({
        media: query,
        matches: false,
        addEventListener: (_, listener) => { darkModeListener = listener; }
    }));

    service.initialize();
    TestBed.flushEffects();
    expect(service.currentTheme()).toBe(Theme.LIGHT);

    // Simulate OS preference change
    if (darkModeListener) {
        darkModeListener({ matches: true } as MediaQueryListEvent);
        TestBed.flushEffects();
        expect(service.currentTheme()).toBe(Theme.DARK);
    }
}));

Also applies to: 140-145

src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts (2)

90-91: Consider alternative mocking strategies for ThemeSwitchComponent.

The comment indicates an issue with ng-mocks preventing the mocking of ThemeSwitchComponent. While using the actual component works, it might introduce unnecessary dependencies and slow down tests.

Consider these alternatives:

  1. Create a minimal mock implementation of ThemeSwitchComponent
  2. Use jest.mock() with a custom factory
  3. Track the ng-mocks issue and update once fixed

Example minimal mock:

@Component({
  selector: 'jhi-theme-switch',
  template: ''
})
class MockThemeSwitchComponent {}

Line range hint 156-196: Test assertions follow best practices but could be more specific.

The test assertions follow good practices by:

  • Using specific matchers (toBeTrue, toBeFalse, not.toBeNull)
  • Checking component state after each step
  • Verifying element existence

However, consider enhancing the assertions:

// Instead of just checking element existence
expect(selectedElement).not.toBeNull();

// Also verify the element's properties and state
expect(selectedElement.nativeElement.classList.contains('highlighted')).toBeTrue();
expect(selectedElement).toMatchObject({
  properties: {
    // Add expected properties
  }
});
src/main/webapp/app/exercises/programming/shared/instructions-render/programming-exercise-instruction.component.ts (1)

92-96: Consider optimizing theme change subscription

While the implementation works, consider these improvements for better performance and reliability:

  1. Move the subscription initialization to ngOnInit to avoid potential race conditions
  2. Add debouncing to prevent rapid consecutive theme updates
  3. Add distinctUntilChanged to prevent unnecessary updates when theme hasn't changed
-private themeChangeSubscription = toObservable(this.themeService.currentTheme).subscribe(() => {
-    if (!this.isInitial) {
-        this.updateMarkdown();
-    }
-});
+private themeChangeSubscription: Subscription;
+
+ngOnInit() {
+    this.themeChangeSubscription = toObservable(this.themeService.currentTheme).pipe(
+        filter(() => !this.isInitial),
+        distinctUntilChanged(),
+        debounceTime(100)
+    ).subscribe(() => {
+        this.updateMarkdown();
+    });
+}
src/test/javascript/spec/component/programming-exercise/programming-exercise-instruction.component.spec.ts (2)

213-213: Consider consolidating detectChanges() calls.

Multiple detectChanges() calls can mask timing issues in tests. Consider consolidating this with the earlier detectChanges() call or document why multiple updates are necessary.


Line range hint 1-469: Consider improving test organization and structure.

The test suite could benefit from better organization:

  1. Group related tests using describe blocks (e.g., "Theme Changes", "Problem Statement Updates", "Task Icons").
  2. Consider extracting common setup code into shared helper functions.
  3. Add test cases for error scenarios in theme changes.

This would improve maintainability and make the test suite easier to understand and extend.

src/test/javascript/spec/integration/code-editor/code-editor-instructor.integration.spec.ts (4)

Line range hint 65-65: Consider extracting mock setup to a separate helper file

The test setup involves numerous mock services and components. Consider extracting this setup into a dedicated test helper file to improve maintainability and reusability across other test files.


Line range hint 66-200: Refactor test initialization for better maintainability

The beforeEach block is handling too many responsibilities. Consider breaking it down into smaller, focused setup functions:

  1. Component setup
  2. Service injection
  3. Mock setup
  4. Spy setup

This would improve readability and make the test setup more maintainable.

Example refactor:

const setupComponent = () => {
    containerFixture = TestBed.createComponent(CodeEditorInstructorAndEditorContainerComponent);
    comp = containerFixture.componentInstance;
    containerDebugElement = containerFixture.debugElement;
};

const setupServices = () => {
    const services = {
        codeEditorRepository: containerDebugElement.injector.get(CodeEditorRepositoryService),
        // ... other services
    };
    return services;
};

const setupMocks = () => {
    checkIfRepositoryIsCleanSubject = new Subject<{ isClean: boolean }>();
    // ... other subjects
};

const setupSpies = (services: any) => {
    checkIfRepositoryIsCleanStub = jest.spyOn(services.codeEditorRepository, 'getStatus');
    // ... other spies
};

Line range hint 201-450: Improve type safety in test cases

Several test cases use @ts-ignore to bypass TypeScript checks. Consider creating proper type definitions or partial mocks instead:

type PartialProgrammingExercise = Partial<ProgrammingExercise> & {
    id: number;
    course: { id: number };
};

const createMockExercise = (overrides?: Partial<PartialProgrammingExercise>): PartialProgrammingExercise => ({
    id: 1,
    course: { id: 1 },
    ...overrides
});

This would make the tests more type-safe and maintainable.


Line range hint 315-339: Make expectations more specific

The test expectations for component existence could be more specific. Instead of just checking if they're defined, consider checking for specific properties or states:

expect(comp.codeEditorContainer.buildOutput.participation).toBeDefined();

could be:

expect(comp.codeEditorContainer.buildOutput.participation).toEqual(
    expect.objectContaining({
        id: exercise.studentParticipations[0].id,
        // other relevant properties
    })
);
src/test/javascript/spec/component/shared/navbar.component.spec.ts (2)

41-42: Consider enhancing the GuidedTourService mock implementation.

The current mock implementation only covers getGuidedTourAvailabilityStream. Consider implementing other commonly used methods to make the tests more robust.

Also, the need to override the component to remove ThemeSwitchComponent from imports might indicate a circular dependency. Consider reviewing the component's architecture.

Also applies to: 113-113, 127-132, 139-143


Line range hint 776-885: Consider improving breakpoint test maintainability.

While the breakpoint tests are comprehensive, consider these improvements:

  1. Add comments explaining the breakpoint logic for each role
  2. Extract magic numbers (widths) into named constants
  3. Group test cases by role for better readability

Example improvement:

+ // Breakpoint constants
+ const BREAKPOINTS = {
+   ADMIN: { COLLAPSE: 1100, ICONS_TO_MENU: 600, VERTICAL_NAV: 550 },
+   INSTRUCTOR: { COLLAPSE: 850, ICONS_TO_MENU: 600, VERTICAL_NAV: 470 },
+   USER: { COLLAPSE: 650, ICONS_TO_MENU: 600, VERTICAL_NAV: 470 },
+   ANONYMOUS: { COLLAPSE: 500, ICONS_TO_MENU: 400, VERTICAL_NAV: 450 }
+ };

  it.each([
+   // Admin breakpoints
    {
-     width: 1200,
+     width: BREAKPOINTS.ADMIN.COLLAPSE + 100,
      account: { login: 'test' },
      roles: [Authority.ADMIN],
      expected: { isCollapsed: false, isNavbarNavVertical: false, iconsMovedToMenu: false },
    },
    // ... rest of the test cases
  ])('should calculate correct breakpoints', ...
src/main/webapp/app/core/theme/theme-switch.component.ts (1)

36-36: Specify a more precise type instead of 'any' for 'closeTimeout'

Using any defeats the purpose of TypeScript's type checking. Since setTimeout returns a number in the browser environment, it's better to specify the type as number to enhance type safety.

Apply this change:

- closeTimeout: any;
+ closeTimeout: number;
src/main/webapp/app/core/theme/theme.service.ts (2)

Line range hint 171-194: Avoid direct DOM manipulation; use Renderer2 for DOM operations

Direct manipulation of the DOM using native APIs like document.getElementById and document.createElement is discouraged in Angular applications. To ensure compatibility across different platforms and enhance testability, consider using Angular's Renderer2 service for DOM manipulations in the applyTheme method.


87-93: Ensure proper cleanup of event listeners to prevent memory leaks

The service adds event listeners to darkSchemeMediaQuery and the global storage event. While the service is provided in the root and likely persists for the application's lifetime, it's good practice to remove event listeners in a ngOnDestroy method. This prevents potential memory leaks and prepares the code for future changes where the service's lifecycle might be shorter.

Also applies to: 98-99

src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.ts (1)

98-100: Converting properties to signals and computed values for reactivity

  • highlightedElements is now a signal<Map<string, string>>, improving reactive state management.
  • highlightColor is now a computed property that dynamically updates based on the theme.

Consider the following suggestion:

Avoid hardcoding color values; use theme constants instead

Instead of hardcoding 'darkblue' and 'lightblue', consider using predefined theme variables or constants to ensure consistency and ease of maintenance across the application.

Example:

- highlightColor = computed(() => (this.themeService.userPreference() === Theme.DARK ? 'darkblue' : 'lightblue'));
+ highlightColor = computed(() => (this.themeService.userPreference() === Theme.DARK ? DARK_THEME_HIGHLIGHT_COLOR : LIGHT_THEME_HIGHLIGHT_COLOR));

Ensure that DARK_THEME_HIGHLIGHT_COLOR and LIGHT_THEME_HIGHLIGHT_COLOR are defined in a centralized theme configuration.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 70ccfc3 and a2abbdd.

📒 Files selected for processing (29)
  • docs/dev/guidelines/client-design.rst (1 hunks)
  • src/main/webapp/app/app.module.ts (2 hunks)
  • src/main/webapp/app/core/theme/theme-switch.component.html (2 hunks)
  • src/main/webapp/app/core/theme/theme-switch.component.ts (4 hunks)
  • src/main/webapp/app/core/theme/theme.module.ts (0 hunks)
  • src/main/webapp/app/core/theme/theme.service.ts (4 hunks)
  • src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.html (2 hunks)
  • src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.ts (5 hunks)
  • src/main/webapp/app/exercises/programming/shared/instructions-render/programming-exercise-instruction.component.ts (3 hunks)
  • src/main/webapp/app/exercises/programming/shared/instructions-render/service/programming-exercise-plant-uml.service.ts (4 hunks)
  • src/main/webapp/app/exercises/programming/shared/instructions-render/step-wizard/programming-exercise-instruction-step-wizard.component.html (1 hunks)
  • src/main/webapp/app/lti/lti13-exercise-launch.component.ts (1 hunks)
  • src/main/webapp/app/shared/dashboards/tutor-participation-graph/progress-bar/progress-bar.component.ts (3 hunks)
  • src/main/webapp/app/shared/metis/emoji/emoji-picker.component.html (1 hunks)
  • src/main/webapp/app/shared/metis/emoji/emoji-picker.component.ts (1 hunks)
  • src/main/webapp/app/shared/metis/emoji/emoji.component.html (1 hunks)
  • src/main/webapp/app/shared/metis/emoji/emoji.component.ts (1 hunks)
  • src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts (1 hunks)
  • src/test/javascript/spec/component/emoji/emoji-picker.component.spec.ts (1 hunks)
  • src/test/javascript/spec/component/emoji/emoji.component.spec.ts (0 hunks)
  • src/test/javascript/spec/component/programming-exercise/programming-exercise-instruction.component.spec.ts (2 hunks)
  • src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts (4 hunks)
  • src/test/javascript/spec/component/shared/navbar.component.spec.ts (18 hunks)
  • src/test/javascript/spec/component/shared/progress-bar.component.spec.ts (3 hunks)
  • src/test/javascript/spec/component/theme/theme-switch.component.spec.ts (1 hunks)
  • src/test/javascript/spec/helpers/mocks/service/mock-theme.service.ts (1 hunks)
  • src/test/javascript/spec/integration/code-editor/code-editor-instructor.integration.spec.ts (1 hunks)
  • src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts (1 hunks)
  • src/test/javascript/spec/service/theme.service.spec.ts (6 hunks)
💤 Files with no reviewable changes (2)
  • src/main/webapp/app/core/theme/theme.module.ts
  • src/test/javascript/spec/component/emoji/emoji.component.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • src/main/webapp/app/lti/lti13-exercise-launch.component.ts
🧰 Additional context used
📓 Path-based instructions (25)
src/main/webapp/app/app.module.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/core/theme/theme-switch.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/core/theme/theme-switch.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/core/theme/theme.service.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/exercises/programming/shared/instructions-render/programming-exercise-instruction.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/exercises/programming/shared/instructions-render/service/programming-exercise-plant-uml.service.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/exercises/programming/shared/instructions-render/step-wizard/programming-exercise-instruction-step-wizard.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/shared/dashboards/tutor-participation-graph/progress-bar/progress-bar.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/metis/emoji/emoji-picker.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/shared/metis/emoji/emoji-picker.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/metis/emoji/emoji.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/shared/metis/emoji/emoji.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/test/javascript/spec/component/emoji/emoji-picker.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/programming-exercise/programming-exercise-instruction.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/shared/monaco-editor/monaco-editor.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/shared/navbar.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/shared/progress-bar.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/theme/theme-switch.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/helpers/mocks/service/mock-theme.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/integration/code-editor/code-editor-instructor.integration.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/integration/guided-tour/guided-tour.integration.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/service/theme.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}}

🔇 Additional comments (46)
src/main/webapp/app/shared/metis/emoji/emoji.component.html (3)

2-4: Great use of new Angular control flow syntax!

The template correctly uses the new @if/@else syntax instead of the older *ngIf, which aligns with Angular 18 best practices.


3-5: LGTM: Consistent emoji rendering configuration

The component maintains consistent base properties (size, backgroundImageFn) while properly handling theme-specific configuration through imageUrlFn for dark mode.


2-2: Verify dark() signal implementation

The change from a boolean property to dark() suggests it's now a signal or computed value. Let's verify its implementation in the component.

✅ Verification successful

dark() is correctly implemented as a computed value.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the dark() signal implementation in the component
# Expected: dark should be defined as a signal or computed value

# Search for the dark signal/computed definition in the component
ast-grep --pattern 'class EmojiComponent {
  $$$
  dark = $_;
  $$$
}'

# Also check for potential computed implementation
ast-grep --pattern 'class EmojiComponent {
  $$$
  dark = computed(() => $$$);
  $$$
}'

Length of output: 1808

src/main/webapp/app/shared/metis/emoji/emoji.component.ts (3)

1-3: LGTM! Imports follow Angular best practices

The imports are well-organized and correctly utilize modern Angular features (computed, inject).


5-9: LGTM! Component configuration follows Angular guidelines

The component decorator maintains proper configuration with appropriate selector prefix and separation of concerns (template/styles).


10-12: LGTM! Excellent migration to modern Angular patterns

The refactoring effectively:

  • Eliminates manual subscription management
  • Uses modern dependency injection with inject()
  • Implements reactive patterns with signals
src/test/javascript/spec/helpers/mocks/service/mock-theme.service.ts (2)

1-4: LGTM! Clean and focused imports

The imports are minimal and the mock service follows proper naming conventions.


8-9: LGTM! Consistent signal pattern implementation

The user preference management follows the same clean pattern as the theme state management, with proper handling of undefined states.

src/main/webapp/app/shared/metis/emoji/emoji-picker.component.ts (3)

1-9: LGTM! Proper adoption of Angular 18 features

The imports and component setup correctly incorporate Angular 18's computed and inject features while adhering to the project's coding guidelines.


19-20: ⚠️ Potential issue

Review the light theme emoji URL function implementation

The singleImageFunction returns an empty string function for light theme, which seems incorrect. Consider using EmojiUtils.singleLightModeEmojiUrlFn (if available) or the appropriate light theme URL function.

Additionally, the computed property can be written more concisely:

-    singleImageFunction = computed(() => (this.dark() ? EmojiUtils.singleDarkModeEmojiUrlFn : () => ''));
+    singleImageFunction = computed(() => this.dark() 
+        ? EmojiUtils.singleDarkModeEmojiUrlFn 
+        : EmojiUtils.singleLightModeEmojiUrlFn);

Let's verify if the light mode function exists:

#!/bin/bash
# Search for light mode emoji URL function definition
ast-grep --pattern 'singleLightModeEmojiUrlFn = $_' 
rg -A 5 "singleLightModeEmojiUrlFn"

10-11: Verify memory leak prevention after removal of ngOnDestroy

While the transition to inject() pattern is good, please confirm that removing ngOnDestroy doesn't lead to memory leaks.

src/main/webapp/app/exercises/programming/shared/instructions-render/step-wizard/programming-exercise-instruction-step-wizard.component.html (3)

5-5: LGTM! Correct usage of new Angular control flow syntax.

The update from *ngFor to @for aligns with Angular 18 best practices. The syntax is properly implemented with index tracking.


Line range hint 15-24: LGTM! Proper implementation of conditional icon rendering.

The @if directives are correctly used for conditional rendering, replacing *ngIf as per Angular 18 guidelines. The conditions are clear and mutually exclusive.


5-5: Verify the impact of tracking mechanism change.

The tracking mechanism has been changed from track step to track i. While using the index for tracking is valid, it might not be optimal if the steps array undergoes frequent modifications (additions/removals/reordering).

Consider using a unique identifier from the step object for tracking if the steps array is modified dynamically:

-@for (step of steps; let i = $index; track i) {
+@for (step of steps; track step.testIds) {
src/test/javascript/spec/component/emoji/emoji-picker.component.spec.ts (1)

Line range hint 1-48: Verify signal implementation and test coverage

Let's verify the component's signal implementation and ensure comprehensive test coverage.

src/main/webapp/app/core/theme/theme-switch.component.html (1)

5-5: LGTM! Clean implementation of icon and translation.

The implementation correctly uses the FontAwesome icon component and the translation directive.

src/main/webapp/app/shared/dashboards/tutor-participation-graph/progress-bar/progress-bar.component.ts (3)

1-10: LGTM! Import changes align with Angular 18 practices

The migration to use toObservable from @angular/core/rxjs-interop aligns with Angular 18's signal-based reactive programming approach.


Line range hint 11-19: LGTM! Class structure follows Angular guidelines

The component properties and decorators are well-structured and follow Angular's style guide. The removal of OnInit is appropriate as the initialization logic is handled in the constructor.


Line range hint 20-30: Consider investigating the need for manual change detection

While the migration to signals is good, there are two concerns:

  1. The comment "Manually run change detection as it doesn't do it automatically for some reason" suggests an underlying issue that should be investigated rather than worked around.
  2. Setting up subscriptions in the constructor could lead to potential issues with the component's lifecycle.

Let's check if this manual change detection pattern is used elsewhere:

Consider moving the subscription setup to a lifecycle hook and investigating why automatic change detection isn't working:

-constructor(
-    private themeService: ThemeService,
-    private ref: ChangeDetectorRef,
-) {
-    this.themeSubscription = toObservable(this.themeService.currentTheme).subscribe(() => {
-        this.chooseProgressBarTextColor();
-        this.ref.detectChanges();
-    });
-}
+constructor(
+    private themeService: ThemeService,
+    private ref: ChangeDetectorRef,
+) {}
+
+ngAfterViewInit() {
+    this.themeSubscription = toObservable(this.themeService.currentTheme).subscribe(() => {
+        this.chooseProgressBarTextColor();
+        // Consider using markForCheck() instead if using OnPush strategy
+        this.ref.markForCheck();
+    });
+}
src/test/javascript/spec/component/shared/progress-bar.component.spec.ts (1)

8-8: LGTM: Proper mock service configuration

The mock service implementation follows Angular testing best practices and aligns with our guidelines for mocking dependencies.

Also applies to: 18-23

src/test/javascript/spec/component/theme/theme-switch.component.spec.ts (2)

3-9: LGTM! Import statements follow best practices.

The imports are well-organized and follow the guidelines by:

  • Using appropriate testing utilities
  • Leveraging mock services
  • Avoiding full module imports

57-67: LGTM! OS sync tests are comprehensive.

The tests effectively verify:

  • Toggle state changes
  • Correct theme service interactions
  • Boolean state assertions using recommended methods
src/main/webapp/app/app.module.ts (2)

24-24: LGTM: Import statement follows Angular guidelines

The import of ThemeSwitchComponent from the core module follows proper Angular conventions and organizational structure.


45-45: Verify standalone component configuration

The migration to using ThemeSwitchComponent as a standalone component aligns with Angular 18 practices. However, let's verify the component's configuration.

src/main/webapp/app/exercises/programming/shared/instructions-render/service/programming-exercise-plant-uml.service.ts (2)

1-1: LGTM: Modern dependency injection pattern implemented correctly

The switch to functional dependency injection using inject() aligns well with Angular 18's best practices and improves code maintainability.

Also applies to: 15-16


Line range hint 39-45: Verify cache management strategy

The service implements caching with a 1-hour expiration and 100-item limit. For high-traffic applications, we should verify if these limits are appropriate and if memory usage is properly managed.

Also applies to: 59-65

✅ Verification successful

Cache configuration is appropriate

The current cache configuration with 100 items and 1-hour expiration is well-suited for PlantUML diagrams. The sliding expiration and theme-based cache eviction provide effective memory management, while server-side caching adds another optimization layer.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for cache usage patterns and memory management
rg -A 2 "@Cacheable" 
rg "maxCacheCount|maxAge" 

# Look for similar caching implementations
ast-grep --pattern 'class $_ {
  $$$
  @Cacheable($$$)
  $$$
}'

Length of output: 295512

src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts (4)

4-4: LGTM: Import path simplification

The simplified import path improves code maintainability.


28-28: LGTM: Proper service injection in tests

Using TestBed.inject for ThemeService follows Angular testing best practices.


58-65: LGTM: Well-structured parameterized tests

The use of it.each for testing different editor types reduces code duplication and improves test maintainability. The test structure follows Jest best practices.


15-16: LGTM: Improved test organization with signals

Moving the ThemeService declaration to the top level improves test organization. The removal of BehaviorSubject aligns with the migration to signals.

Let's verify the ThemeService is using signals:

src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts (1)

17-17: Verify ThemeService.currentTheme is a signal

Since this property is used within an effect, we need to ensure that ThemeService.currentTheme is actually a signal.

src/test/javascript/spec/service/theme.service.spec.ts (2)

13-13: LGTM: Mock declarations follow best practices

The mock declarations are well-structured and follow Jest best practices with consistent naming conventions.

Also applies to: 39-39


106-109: LGTM: Theme restoration test properly handles signals

The test correctly uses TestBed.flushEffects() to handle signal updates and verifies the expected behavior.

docs/dev/guidelines/client-design.rst (1)

240-240: LGTM! Method name change improves clarity.

The rename from applyThemeExplicitly to applyThemePreference better reflects that this method handles user theme preferences.

src/test/javascript/spec/integration/guided-tour/guided-tour.integration.spec.ts (1)

Line range hint 117-134: Verify mock implementation completeness.

The test uses several mock services and spies. Ensure all necessary methods and properties are properly mocked, especially for theme-related functionality.

src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.html (3)

Line range hint 132-138: LGTM! Proper implementation of Angular signals and new control flow syntax.

The changes correctly implement:

  • Signal-based reactive properties using function calls
  • New @if syntax as per coding guidelines

163-163: LGTM! Consistent use of signal pattern.

The change maintains consistency with the signal-based approach used throughout the component.


Line range hint 1-163: Excellent migration to new Angular control flow syntax!

The template consistently uses the new @if and @for syntax throughout, following the coding guidelines perfectly. This thorough migration will serve as a good example for other components.

src/main/webapp/app/exercises/programming/shared/instructions-render/programming-exercise-instruction.component.ts (2)

14-14: LGTM: Import statements follow Angular 18 practices

The addition of inject and toObservable imports aligns with Angular 18's modern dependency injection patterns and signal-to-observable conversion capabilities.

Also applies to: 43-43


51-52: LGTM: Theme service injection follows Angular 18 best practices

The use of inject() for ThemeService follows Angular 18's recommended dependency injection pattern, improving code maintainability and reducing boilerplate.

src/test/javascript/spec/component/shared/navbar.component.spec.ts (1)

231-246: LGTM! Well-structured breadcrumb test cases.

The breadcrumb test updates follow consistent patterns and properly verify:

  • Translation keys
  • URI formatting
  • Breadcrumb hierarchy

Also applies to: 257-266, 279-288, 299-303, 405-409, 434-438, 479-488, 552-556, 592-596, 627-636, 660-664, 692-701, 737-746, 767-770

src/main/webapp/app/core/theme/theme-switch.component.ts (1)

30-31: Verify the usage of 'input.required' and 'viewChild.required' functions

Please confirm that using input.required<PlacementArray>() and viewChild.required<NgbPopover>('popover') is appropriate in Angular 18. Traditionally, inputs and view children are declared using the @Input() and @ViewChild() decorators and accessed as properties rather than functions. Also, calling this.popover() suggests that popover is a function, which may not be intended.

Also applies to: 49-49, 55-55

src/main/webapp/app/exercises/modeling/manage/example-modeling/example-modeling-submission.component.ts (4)

1-1: Imports updated to include Angular reactive features

The import statement now includes computed, effect, inject, signal, and untracked from @angular/core, which aligns with the adoption of Angular 18 reactive programming practices.


43-43: Using inject for dependency injection enhances code clarity

The use of inject(ThemeService) provides a modern and concise way to inject services in Angular components, aligning with Angular 18 standards.


118-130: Effect correctly updates highlighted elements when theme changes

The effect set up in the constructor ensures that highlightedElements are updated whenever highlightColor changes. The use of untracked prevents unnecessary reactive dependencies, which is appropriate in this context.


478-482: Ensure feedback.referenceId is always defined to prevent runtime errors

When setting highlightedElements, you use feedback.referenceId! with the non-null assertion operator. Verify that feedback.referenceId cannot be null or undefined to prevent potential runtime exceptions.

If there's a possibility that referenceId could be undefined, consider adding a check:

for (const feedback of missedReferencedExampleFeedbacks) {
-    highlightedElements.set(feedback.referenceId!, this.highlightColor());
+    if (feedback.referenceId) {
+        highlightedElements.set(feedback.referenceId, this.highlightColor());
+    }
}

This ensures that only valid referenceId values are used when updating highlightedElements.

@FelixTJDietrich FelixTJDietrich temporarily deployed to artemis-test4.artemis.cit.tum.de November 13, 2024 10:19 — with GitHub Actions Inactive
@FelixTJDietrich FelixTJDietrich changed the title Development: Update theme components to use Angular 18 practices Development: Update theme switcher to use Angular 18 practices Nov 13, 2024
@github-actions github-actions bot removed the core Pull requests that affect the corresponding module label Nov 13, 2024
@ls1intum ls1intum deleted a comment from coderabbitai bot Nov 13, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
src/test/javascript/spec/helpers/mocks/mock-instance.helper.ts (1)

24-29: Add JSDoc documentation for consistency

The function is missing JSDoc documentation unlike the existing mockCodeEditorMonacoViewChildren function. Please add documentation following the same pattern.

+/**
+ * Overwrites the signal-based viewChild queries of the ThemeSwitchComponent so it can be mocked using MockComponent.
+ * Workaround for https://github.com/help-me-mom/ng-mocks/issues/8634.
+ */
 export function mockThemeSwitcherComponentViewChildren() {
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a2abbdd and cfacf8b.

📒 Files selected for processing (2)
  • src/test/javascript/spec/component/shared/navbar.component.spec.ts (19 hunks)
  • src/test/javascript/spec/helpers/mocks/mock-instance.helper.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/javascript/spec/component/shared/navbar.component.spec.ts
🧰 Additional context used
📓 Path-based instructions (1)
src/test/javascript/spec/helpers/mocks/mock-instance.helper.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 (2)
src/test/javascript/spec/helpers/mocks/mock-instance.helper.ts (2)

4-4: LGTM: Import statement follows conventions

The import statement is correctly placed and follows project conventions.


26-28: Verify complete signal-based property coverage

Please verify that all signal-based properties of ThemeSwitchComponent are properly mocked. Currently, only the popover property is mocked.

✅ Verification successful

All signal-based properties of ThemeSwitchComponent are properly mocked.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for signal-based properties in ThemeSwitchComponent
# Expected: Find all properties initialized with signal() to ensure complete mocking

# Search for signal declarations in ThemeSwitchComponent
ast-grep --pattern 'signal<$_>($_)' app/core/theme/theme-switch.component.ts

# Search for ViewChild decorators that might need mocking
ast-grep --pattern '@ViewChild($$$)' app/core/theme/theme-switch.component.ts

Length of output: 334


Script:

#!/bin/bash
# Description: Search for ThemeSwitchComponent file in the codebase
fd 'theme-switch\.component\.ts$'

Length of output: 92


Script:

#!/bin/bash
# Description: Search for signal-based properties and ViewChild decorators in ThemeSwitchComponent
ast-grep --pattern 'signal<$_>($_)' src/main/webapp/app/core/theme/theme-switch.component.ts

ast-grep --pattern '@ViewChild($$$)' src/main/webapp/app/core/theme/theme-switch.component.ts

Length of output: 190

Copy link
Contributor

@eceeeren eceeeren left a comment

Choose a reason for hiding this comment

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

Tested on TS6, everything works as described

@krusche krusche merged commit e0ce21e into develop Nov 16, 2024
20 of 24 checks passed
@krusche krusche deleted the chore/performance/angular-18-signal-migration-example branch November 16, 2024 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
client Pull requests that update TypeScript code. (Added Automatically!) code quality documentation refactoring tests
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

4 participants