-
Notifications
You must be signed in to change notification settings - Fork 352
Development: Improve TypeScript type-safety around participations and more
#11727
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
base: develop
Are you sure you want to change the base?
Development: Improve TypeScript type-safety around participations and more
#11727
Conversation
Development: Improve client type-safetyDevelopment: Improve TypeScript type-safety
Development: Improve TypeScript type-safetyDevelopment: Improve TypeScript type-safety around participations and more
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Also remove the results, as they can have circular structures as well and don't have to be saved here. | ||
| if (copy.templateParticipation) { | ||
| copy.templateParticipation = _omit(copy.templateParticipation, ['exercise', 'results']) as TemplateProgrammingExerciseParticipation; | ||
| if (isTemplateProgrammingExerciseParticipation(copy.templateParticipation)) { | ||
| const { exercise: _ignoredExercise, ...rest } = copy.templateParticipation; | ||
| copy.templateParticipation = rest; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore removal of results before sending programming exercise
The new convertDataFromClient only strips the exercise field from template/solution participations, leaving their results intact despite the comment that these should be removed. Those result objects carry submissions/participations with back-references to the exercise, so sending an updated programming exercise can now include circular structures or a large results payload that the previous _omit(..., ['exercise', 'results']) avoided, potentially breaking save/update calls when template or solution participations contain results.
Useful? React with 👍 / 👎.
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughSystematic refactoring to replace unsafe type casts with runtime type guards throughout the codebase. Introduces type-guard utility functions and validation patterns to safely handle participation types, enum parsing, and property access. Expands enum states for programming submissions and improves type annotations across multiple component files. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (12)
src/main/webapp/app/programming/manage/edit-selected/programming-exercise-edit-selected.component.ts (1)
67-74: Stronger typing forrequestOptionslooks good; a dedicated options type could be a future improvementChanging
requestOptionsfrom an untyped/any-like object toRecord<string, unknown>improves type-safety while preserving behaviour. IfProgrammingExerciseService.updateTimelineexpects a stable, well-defined options shape (e.g. onlynotificationTextand maybe a few others), you might later consider introducing a small interface/type (e.g.ProgrammingExerciseTimelineOptions) instead of a genericRecord, but that’s optional and not required for this PR.Based on learnings, this aligns with the repo’s preference to avoid unsafe type assertions.
src/main/webapp/app/communication/posting-reactions-bar/posting-reactions-bar.component.ts (1)
222-247: Refactor to call signal only once and eliminate redundant null check.The current implementation still calls
sortedAnswerPosts()twice (lines 223 and 228), which doesn't achieve the optimization goal. Additionally, the null check at lines 229-231 is redundant because if line 223's check passes,sortedAnswerPosts()is truthy.Apply this diff to call the signal once with a single guard:
getShowNewMessageIcon(): boolean { + const answers = this.sortedAnswerPosts(); - if (!this.sortedAnswerPosts()) { + if (!answers) { return false; } let showIcon = false; - const answers = this.sortedAnswerPosts(); - if (!answers) { - return false; - } // iterate over all answer posts answers.forEach((answerPost: Posting) => { // check if the answer post is newer than the last read datesrc/main/webapp/app/iris/overview/services/iris-chat.service.ts (1)
335-347: Type assertion is safe here; consider a small type guard for even stronger typingThe change from
as anytoas { mode?: ChatServiceMode }is a clear improvement and keeps behavior identical while tightening the type surface. If you want to push type‑safety a bit further and remove the remaining assertion, you could introduce a narrow type guard aroundmodeand reuse it here, e.g.:function hasChatMode(session: IrisSession): session is IrisSession & { mode?: ChatServiceMode } { return 'mode' in session; } // ... const chatMode = newIrisSession.chatMode ?? (hasChatMode(newIrisSession) ? newIrisSession.mode : undefined) ?? ChatServiceMode.COURSE;This keeps the runtime behavior the same, but fully aligns with the “avoid assertions, use guards” direction of the PR. Based on learnings, this matches the preference for type guards over assertions in the TypeScript codebase.
src/main/webapp/app/shared/data-table/data-table.component.ts (1)
270-276: Eliminate remainingas PagingValueby constructing a properly narrowed valueThe logic is correct and safe, but you can avoid the remaining type assertion by explicitly constructing a
PagingValuefrom the cached string. This keeps behavior while matching the PR’s goal of reducing assertions./** * Get "items per page" setting from local storage. If it does not exist, use the default. */ private getCachedEntitiesPerPage = () => { const cachedValue = this.localStorageService.retrieve<string>(this.perPageCacheKey); if (cachedValue) { - const parsedValue = parseInt(cachedValue, 10) || cachedValue; - if (this.PAGING_VALUES.includes(parsedValue as PagingValue)) { - return parsedValue as PagingValue; - } + const parsedValue: PagingValue = cachedValue === 'all' ? 'all' : parseInt(cachedValue, 10); + if (this.PAGING_VALUES.includes(parsedValue)) { + return parsedValue; + } } return this.DEFAULT_PAGING_VALUE; };This keeps the runtime behavior (only values in
PAGING_VALUESare returned, everything else falls back to the default) while relying on type narrowing instead of assertions. Based on learnings, prefer guards/narrowing overaswhere feasible.src/main/webapp/app/exercise/services/exercise.service.ts (2)
382-389: Consider adding shape validation for parsed category objects.The parsing logic correctly handles both string and object inputs and uses
satisfiesappropriately. However, there's no validation thatcategoryObjhas the expectedcategoryandcolorproperties before passing them to theExerciseCategoryconstructor.If the server sends malformed data, this could silently create invalid ExerciseCategory instances with undefined values.
Apply this diff to add defensive validation:
static parseExerciseCategories(exercise?: Exercise) { if (exercise?.categories) { exercise.categories = exercise.categories.map((category) => { const categoryObj = typeof category === 'string' ? JSON.parse(category) : category; + if (!categoryObj || typeof categoryObj.category !== 'string' || typeof categoryObj.color !== 'string') { + console.warn('Invalid exercise category format:', categoryObj); + return new ExerciseCategory('', ''); + } return new ExerciseCategory(categoryObj.category, categoryObj.color); }) satisfies ExerciseCategory[]; } }
395-400: Consider adding shape validation for consistency.Similar to
parseExerciseCategories, this function would benefit from validating that the parsed JSON has the expected shape before constructingExerciseCategoryinstances.Apply this diff to add defensive validation:
convertExerciseCategoriesAsStringFromServer(categories: string[]): ExerciseCategory[] { return categories.map((category) => { const parsed = JSON.parse(category) as { category: string; color: string }; + if (!parsed || typeof parsed.category !== 'string' || typeof parsed.color !== 'string') { + console.warn('Invalid exercise category format:', parsed); + return new ExerciseCategory('', ''); + } return new ExerciseCategory(parsed.category, parsed.color); }); }src/main/webapp/app/shared/util/fullscreen.util.ts (2)
21-32: Use the correct IE/Edge exit API (msExitFullscreen) instead ofmsRequestFullscreenonDocument
msRequestFullscreenis an element-level API and you already model it onFullscreenElement, but here it’s declared and invoked onFullscreenDocument/Document. The document-level exit method is typicallymsExitFullscreen, so this branch is likely dead on IE/old Edge and slightly misrepresents the API in the type.You can make this clearer and more correct with a small rename:
export function exitFullscreen() { - const docElement = document as FullscreenDocument; + const docElement = document as FullscreenDocument; @@ - } else if (docElement.msRequestFullscreen) { - docElement.msRequestFullscreen(); + } else if (docElement.msExitFullscreen) { + docElement.msExitFullscreen(); } else if (docElement.webkitExitFullscreen) { docElement.webkitExitFullscreen(); } } @@ type FullscreenDocument = Document & { webkitFullscreenElement?: Element | null; mozFullScreenElement?: Element | null; msFullscreenElement?: Element | null; webkitExitFullscreen?: () => void; mozCancelFullScreen?: () => void; - msRequestFullscreen?: () => void; + msExitFullscreen?: () => void; };This mainly affects very old browsers but improves correctness and keeps the custom types aligned with the actual platform API.
Also applies to: 52-59
38-49: Consider tighteningenterFullscreen’s parameter type to leverageFullscreenElement
enterFullscreenstill acceptselement: anyand then assigns it to aFullscreenElement, which effectively reintroduces an unchecked cast at the call sites. Since you now have a dedicatedFullscreenElementtype, you could expose that (or at leastElement) directly in the signature:-export function enterFullscreen(element: any) { - const target: FullscreenElement = element; +export function enterFullscreen(element: FullscreenElement) { + const target = element;This pushes type-safety to the callers and avoids
anywhile still supporting the vendor-prefixed methods defined onFullscreenElement. If updating all call sites is too invasive right now, evenelement: Elementwould be a step forward. Based on learnings, this would further reduce reliance onany.Also applies to: 61-65
src/main/webapp/app/programming/shared/commit-history/commit-history.component.ts (1)
11-17: Type-guarded participation selection looks correct; consider explicit fallbackThe use of
isTemplateProgrammingExerciseParticipation/isSolutionProgrammingExerciseParticipation/isProgrammingExerciseStudentParticipationcorrectly narrows the participation types before accessing programming-specific fields and wiringprogrammingExercise.One minor robustness tweak you might consider (non-blocking): in
loadStudentParticipation, if the guard ever fails,this.participationstays undefined buthandleCommits()still runs and will later accessthis.participation.id. An earlyreturnor explicit error/log in that case would fail faster and more clearly, instead of relying on downstream null errors.Also applies to: 70-85, 99-115
src/main/webapp/app/exercise/exercise-scores/exercise-scores.component.ts (1)
323-341: Safer handling of student vs non-student participations in exports and search
exportNames,searchParticipationFormatter, andsearchTextFromParticipationnow all useisStudentParticipationbefore touching student-specific properties, which prevents misusing template/solution participations and matches the new participation typing.One small, non-blocking edge case:
exportNamesstill uses theindex === 0check to emit the team header. Ifparticipations[0]were ever a non-student participation, the header would never be written even though later rows might be teams. If you ever expect mixed participation arrays here, consider basing the header condition on whether any data rows have been pushed yet instead of the original index.Also applies to: 350-372
src/main/webapp/app/communication/course-notification/course-notification-websocket.service.ts (1)
94-98: Silent drop of notifications with invalid category/status may hinder debuggingUsing
parseCategory/parseViewingStatusand early‑return is a good defensive pattern, but right now invalid payloads are dropped without any logging or metrics. If the backend or WS payloads ever regress, this will be hard to diagnose.Consider at least a debug‑level log or a counter for rejected notifications so issues can be detected and traced without affecting normal behavior.
Also applies to: 103-104
src/main/webapp/app/programming/manage/grading/charts/test-case-distribution-chart.component.ts (1)
110-123: LocalSeriesEntrytype improves safety; consider tightening the output emitter typeThe introduction of the
SeriesEntrytype and explicitly typedweight,weightAndBonus, andpointsobjects removes the need for looseanyusage while still being structurally compatible withNgxChartsMultiSeriesDataEntry. The push/update logic forrelWeight,relScore, andrelPointsremains unchanged and type-safe.As a small follow-up to these type improvements, you could also narrow the output emitter to use the existing
TestCaseColorsalias instead ofany:- @Output() testCaseColorsChange = new EventEmitter<any>(); + @Output() testCaseColorsChange = new EventEmitter<TestCaseColors>();The emitted object already matches that shape, so this should be a pure type-safety win.
Based on learnings, this further reducesanywithout adding runtime checks.Also applies to: 104-147
| private parseCategory(category: unknown): CourseNotificationCategory | undefined { | ||
| if (typeof category === 'number' && CourseNotificationCategory[category] !== undefined) { | ||
| return category as CourseNotificationCategory; | ||
| } | ||
| if (typeof category === 'string' && category in CourseNotificationCategory) { | ||
| return CourseNotificationCategory[category as keyof typeof CourseNotificationCategory]; | ||
| } | ||
| return undefined; | ||
| } | ||
|
|
||
| private parseViewingStatus(status: unknown): CourseNotificationViewingStatus | undefined { | ||
| if (typeof status === 'number' && CourseNotificationViewingStatus[status] !== undefined) { | ||
| return status as CourseNotificationViewingStatus; | ||
| } | ||
| if (typeof status === 'string' && status in CourseNotificationViewingStatus) { | ||
| return CourseNotificationViewingStatus[status as keyof typeof CourseNotificationViewingStatus]; | ||
| } | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
TypeScript numeric enum runtime behavior string keys "0" index access
💡 Result:
Short answer: numeric enums emit both forward (name→number) and reverse (number→name) mappings at runtime, so the compiled enum object has string keys like "0", "1", ... in addition to the member names. That means enum[0] and enum["0"] both look up the same runtime property (the reverse mapping) and return the member name; string enums do not emit reverse mappings. [1][2]
Quick examples / notes:
- TypeScript handbook: numeric enums produce real objects with reverse mapping (e.g. E[0] === "X"). String enums only emit forward mappings. [1]
- Practical consequence: Object.keys(MyNumericEnum) will include numeric-string keys ("0","1",...) first (the reverse mapping), then the names; you can index the enum with either the numeric index or its string form at runtime. [2]
Sources:
[1] TypeScript Handbook — Enums.
[2] LogRocket / articles explaining reverse mappings and Object.keys behavior.
🏁 Script executed:
#!/bin/bash
# Find the file under review
fd "course-notification-websocket.service.ts" -type f
# Also find the enum definitions
fd -e ts -type f | xargs rg -l "CourseNotificationCategory|CourseNotificationViewingStatus" | head -20Repository: ls1intum/Artemis
Length of output: 2628
🏁 Script executed:
#!/bin/bash
# Read the enum definition file
cat -n src/main/webapp/app/communication/shared/entities/course-notification/course-notification-viewing-status.ts
echo "---"
# Also check for the category enum (might be in the main course-notification.ts)
cat -n src/main/webapp/app/communication/shared/entities/course-notification/course-notification.ts | head -100Repository: ls1intum/Artemis
Length of output: 2227
🏁 Script executed:
#!/bin/bash
# Find CourseNotificationCategory enum
fd "course-notification-category" -type f
echo "---"
# Read the service file around lines 134-152
cat -n src/main/webapp/app/communication/course-notification/course-notification-websocket.service.ts | sed -n '130,160p'Repository: ls1intum/Artemis
Length of output: 1440
🏁 Script executed:
#!/bin/bash
# Find CourseNotificationCategory enum
find . -name "course-notification-category*" -type f
echo "---"
# Also search with rg in case it's defined differently
rg "enum CourseNotificationCategory" -A 5Repository: ls1intum/Artemis
Length of output: 1056
Fix numeric-like strings in enum parsing to maintain correct runtime types
The review comment correctly identifies a runtime type safety issue. TypeScript numeric enums produce reverse mappings at runtime, so both CourseNotificationCategory and CourseNotificationViewingStatus have string keys like "0", "1", etc. that map back to the enum member names.
When a numeric-like string such as "0" is passed:
- The check
"0" in CourseNotificationCategoryreturnstrue(reverse mapping exists) CourseNotificationCategory["0"]returns"GENERAL"(a string)- But the return type is
CourseNotificationCategory | undefined(expects a number)
This creates a runtime type mismatch. The suggested fix to add Number.isNaN(Number(category)) is valid:
- if (typeof category === 'string' && category in CourseNotificationCategory) {
- return CourseNotificationCategory[category as keyof typeof CourseNotificationCategory];
- }
+ if (
+ typeof category === 'string' &&
+ Number.isNaN(Number(category)) &&
+ category in CourseNotificationCategory
+ ) {
+ return CourseNotificationCategory[category as keyof typeof CourseNotificationCategory];
+ }Apply the same fix to parseViewingStatus. This rejects numeric-like strings ("0", "1", etc.) while allowing valid enum member names ("GENERAL", "UNSEEN", etc.).
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
src/main/webapp/app/communication/course-notification/course-notification-websocket.service.ts
around lines 134 to 152, the enum parsing functions accept numeric-like strings
(e.g. "0") because TS numeric enums have reverse string keys, causing
CourseNotificationCategory["0"] to return a string and break the expected
numeric enum type; fix both parseCategory and parseViewingStatus by rejecting
numeric-like strings before the string-key check (e.g. ensure typeof category
=== 'string' && Number.isNaN(Number(category)) && category in Enum) so only true
enum member names are accepted and return the proper enum value.
| const channelName = typeof this.conversation === 'object' ? (this.conversation as { name?: string }).name : undefined; | ||
| this.sourceName = channelName ? `a thread in #${channelName} |` : 'a thread in #unknown |'; | ||
| } else { | ||
| this.sourceName = (this.conversation as any)?.name ? `#${(this.conversation as any)?.name} |` : '#unknown |'; | ||
| const channelName = typeof this.conversation === 'object' ? (this.conversation as { name?: string }).name : undefined; | ||
| this.sourceName = channelName ? `#${channelName} |` : '#unknown |'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Simplify channel name extraction using optional chaining.
The typeof check is redundant and problematic:
- Line 124 already guards against
undefined typeof null === 'object'returnstrue(JavaScript quirk), so null is not properly handled- The type assertion
as { name?: string }contradicts this PR's goal of removing unsafe casts
Since this.conversation is already guarded and typed as Conversation, use optional chaining for cleaner, safer code.
Apply this diff:
} else if (this.conversation?.type?.valueOf() === 'channel') {
if (this.isAnswerPost) {
- const channelName = typeof this.conversation === 'object' ? (this.conversation as { name?: string }).name : undefined;
- this.sourceName = channelName ? `a thread in #${channelName} |` : 'a thread in #unknown |';
+ this.sourceName = `a thread in #${this.conversation.name ?? 'unknown'} |`;
} else {
- const channelName = typeof this.conversation === 'object' ? (this.conversation as { name?: string }).name : undefined;
- this.sourceName = channelName ? `#${channelName} |` : '#unknown |';
+ this.sourceName = `#${this.conversation.name ?? 'unknown'} |`;
}Based on learnings, prefer type guards over type assertions for type safety.
🤖 Prompt for AI Agents
In
src/main/webapp/app/communication/forwarded-message/forwarded-message.component.ts
around lines 128 to 132, the current extraction uses a redundant typeof check
and an unsafe type assertion which mishandles null; replace that logic with
optional chaining to read the name safely (e.g. this.conversation?.name) and
remove the typeof and as { name?: string } cast, and if needed add a small type
guard that narrows conversation to the expected Conversation type before
accessing .name so channelName is obtained via optional chaining and defaults
remain unchanged.
| static stringifyExerciseCategories(exercise: Exercise) { | ||
| return exercise.categories?.map((category) => JSON.stringify(category) as unknown as ExerciseCategory); | ||
| return exercise.categories?.map((category) => JSON.stringify(category)) as ExerciseCategory[] | undefined; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Incorrect type assertion creates type system lie.
This function returns string[] | undefined (from JSON.stringify) but asserts the result as ExerciseCategory[] | undefined. This creates a fundamental type mismatch where the type system believes categories is an array of ExerciseCategory objects when it's actually an array of strings.
This defeats the PR's goal of improving type safety and will cause issues when other code expects ExerciseCategory[] but receives string[].
Based on learnings, prefer runtime correctness over type assertions.
Apply this diff to fix the return type:
static stringifyExerciseCategories(exercise: Exercise) {
- return exercise.categories?.map((category) => JSON.stringify(category)) as ExerciseCategory[] | undefined;
+ return exercise.categories?.map((category) => JSON.stringify(category)) as unknown as ExerciseCategory[] | undefined;
}However, the better solution is to acknowledge that the server expects stringified categories but keep proper typing. Consider updating the Exercise model to have a separate field for serialized categories, or update this function's return type to reflect reality:
- static stringifyExerciseCategories(exercise: Exercise) {
- return exercise.categories?.map((category) => JSON.stringify(category)) as ExerciseCategory[] | undefined;
+ static stringifyExerciseCategories(exercise: Exercise): string[] | undefined {
+ return exercise.categories?.map((category) => JSON.stringify(category));
}Then update call sites to handle the type correctly, or create a properly typed DTO for server communication.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| static stringifyExerciseCategories(exercise: Exercise) { | |
| return exercise.categories?.map((category) => JSON.stringify(category) as unknown as ExerciseCategory); | |
| return exercise.categories?.map((category) => JSON.stringify(category)) as ExerciseCategory[] | undefined; | |
| } | |
| static stringifyExerciseCategories(exercise: Exercise): string[] | undefined { | |
| return exercise.categories?.map((category) => JSON.stringify(category)); | |
| } |
🤖 Prompt for AI Agents
In src/main/webapp/app/exercise/services/exercise.service.ts around lines
363-365, the function currently asserts the result of JSON.stringify as
ExerciseCategory[] which is incorrect; change the function to return string[] |
undefined (remove the incorrect type assertion) and update all call sites to
handle serialized categories (or alternatively add a serializedCategories:
string[] field / DTO on the Exercise model and use that for server
communication); ensure no lingering assertions to ExerciseCategory[] remain and
adjust consumers to parse JSON.parse when they need actual ExerciseCategory
objects.
End-to-End (E2E) Test Results Summary
|
||||||||||||||||||
|
There hasn't been any activity on this pull request recently. Therefore, this pull request has been automatically marked as stale and will be closed if no further activity occurs within seven days. Thank you for your contributions. |
Checklist
General
Client
Motivation and Context
Unsafe types often cause bugs.
Description
This pull request introduces several improvements focused on type safety and robustness across various components, particularly in handling different participation and notification types. The main changes include using type guard functions instead of type assertions, refining notification parsing logic, and improving null/undefined checks for safer code execution.
Type Safety and Participation Handling
isStudentParticipationandisProgrammingExerciseStudentParticipationthroughout the codebase, ensuring that operations on participations are only performed when the type is correct. This affects files such asexercise-scores.component.ts,list-of-complaints.component.ts, andexercise-scores-export-button.component.ts. [1] [2] [3] [4] [5] [6] [7] [8] [9]StudentParticipationobjects, preventing errors from invalid types and improving data integrity. [1] [2]Notification Parsing Improvements
parseCategoryandparseViewingStatus) incourse-notification-websocket.service.tsfor notification categories and statuses, ensuring only valid enum values are used and preventing runtime errors from unexpected values. [1] [2]Null/Undefined Handling
posting-reactions-bar.component.tsand channel names inforwarded-message.component.ts, to prevent runtime errors and ensure graceful fallback. [1] [2]Minor Type and API Updates
asassertions tosatisfiesfor better compile-time type checking (e.g., inprogramming-exam-submission.component.ts).API Usage Update
Intl.supportedValuesOfAPI to remove unnecessary type assertions, reflecting modern TypeScript standards.Steps for Testing
TODO, Artemis client should still behave the same
Testserver States
You can manage test servers using Helios. Check environment statuses in the environment list. To deploy to a test server, go to the CI/CD page, find your PR or branch, and trigger the deployment.
Review Progress
Performance Review
Code Review
Manual Tests
Exam Mode Test
Test Coverage
Unchanged
Screenshots
Unchanged
Summary by CodeRabbit
Bug Fixes
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.