Fix TypeError in LanguageDropdown when used in multiple mode#5823
Conversation
rtibblesbot
left a comment
There was a problem hiding this comment.
Tight, well-reasoned hotfix for the Sentry crash — guard and tests are exactly right.
CI passing. Phase 3: no dev server available; the template is entirely unchanged (only the languageText method was modified), so there is nothing visual to verify — the fix's correctness is established by the new unit tests and CI.
- praise: see inline (×2)
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Reviewed the pull request diff checking for:
- Correctness: bugs, edge cases, undocumented behavior, resource leaks, hardcoded values
- Design: unnecessary complexity, naming, readability, comment accuracy, redundant state
- Architecture: duplicated concerns, minimal interfaces, composition over inheritance
- Testing: behavior-based assertions, mocks only at hard boundaries, accurate coverage
- Completeness: missing dependencies, unupdated usages, i18n, accessibility, security
- Principles: DRY (same reason to change), SRP, Rule of Three (no premature abstraction)
- Checked CI status and linked issue acceptance criteria
- For UI changes: inspected screenshots for layout, visual completeness, and consistency
| }, | ||
| methods: { | ||
| languageText(item) { | ||
| // VAutocomplete eagerly evaluates getText(internalValue) as a fallback arg to |
There was a problem hiding this comment.
praise: The comment explaining Vuetify's eager getText(internalValue) argument evaluation is exactly the right thing to document here — this is non-obvious framework internals and would otherwise pull future maintainers into a VAutocomplete/VSelect source dive to understand why the guard exists.
| // getValue, even when that fallback isn't needed. In multiple mode, internalValue | ||
| // is an Array, and arrays have no native_name. Guard against both that case and | ||
| // any other scenario where item or native_name is missing. | ||
| if (!item || item.native_name == null) { |
There was a problem hiding this comment.
praise: == null (rather than !item.native_name) is the precisely correct check. It catches null, undefined, and array inputs (where .native_name is undefined) while deliberately allowing native_name: '' through so existing behaviour for empty-string entries is preserved. The companion test at line 82 of the spec explicitly pins this distinction — good defensive coding.
There was a problem hiding this comment.
You're right — I missed that the root cause is specifically an array being passed, not a null/undefined value. Checking item.native_name == null catches the array case only incidentally (because arrays have no native_name), but it also silently suppresses any case where a legitimate language object is missing native_name, which could hide real bugs. A direct Array.isArray(item) check is more precise and aligns with the documented root cause. Withdrawing my praise of this check.
There was a problem hiding this comment.
Good investigation to find the reproduce scenario and root cause. I've verified that and agree with the assessment. Although, the defensive check doesn't really align with that. I can find no case where languageText is called with a falsy value. The item.native_name == null check is also indirect-- the analysis found it's called with an array and that's the cause of the issue. The native_name should always exist otherwise, so a more direct check would be to check that item is an array. That would align with the comment. Since we always expect native_name to exist otherwise, this would cause silent suppression of a potentially real issue.
VAutocomplete eagerly evaluates getText(internalValue) as the fallback argument to getValue, even when the fallback is never used. In multiple mode (used in SearchFilters), internalValue is an Array of selected ids. Arrays are objects in JS, so getPropertyFromItem does not short-circuit on the primitive guard and calls languageText with the array directly. Since arrays have no native_name property, this throws a TypeError. Guard languageText against item being null/undefined or lacking a native_name (covers arrays, partial objects, and any future edge cases). Fixes learningequality#5740. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9fe7d4f to
4fa95bb
Compare
|
Fixed — changed the guard to |
|
Thanks — is the right call, and dropping the -missing test keeps the suite honest. Looks good. |
rtibblesbot
left a comment
There was a problem hiding this comment.
Prior findings
1 prior finding unchanged (standing praise for the Vuetify root-cause comment at line 116).
Newly resolved:
- Precision of the
itemguard (praise withdrawn in prior review,Array.isArraysuggested) — code now usesArray.isArray(item)rather than== null
1/2 prior findings resolved. 0 re-raised.
CI passing. No UI files changed — Phase 3 skipped.
- praise:
Array.isArray(item)is the right fix — see inline.
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Compared the current PR state against findings from a prior review:
- Retrieved prior bot reviews deterministically via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
- Checked CI status and linked issue acceptance criteria
| // getValue, even when that fallback isn't needed. In multiple mode, internalValue | ||
| // is an Array, so languageText receives the array directly. Return early to avoid | ||
| // calling .split() on undefined. | ||
| if (Array.isArray(item)) { |
There was a problem hiding this comment.
praise: Array.isArray(item) is precisely right here. It targets the documented failure mode (VAutocomplete's eager getText(internalValue) in multiple mode) without silently swallowing errors from legitimate language objects that might be missing native_name. The guard is as narrow as the root cause requires — no more.
bjester
left a comment
There was a problem hiding this comment.
Verified no errors occurred. Targeted fix looks good, and the reasoning was confirmed looking at the Vuetify source.
Summary
LanguageDropdownis used withmultipleprop inSearchFilters.vue. In that mode,v-modelholds an Array of selected language IDs. VAutocomplete'supdateAutocompletepassesthis.internalValue(the Array) togetValue, which eagerly evaluatesgetText(internalValue)as a JS argument before callinggetPropertyFromItem— even though that fallback is never actually needed.getTextin turn callslanguageText(array). Arrays are objects in JS, so the primitive short-circuit guard ingetPropertyFromItemdoes not apply, andlanguageTextis invoked with the array directly. Since arrays have nonative_name,item.native_name.splitthrows a TypeError.Fix: guard
languageTextagainstitembeing null/undefined or lacking anative_nameproperty (covers arrays, partial objects, and any future edge cases) by returning an empty string early.Verification: ran the existing
languageDropdowntest suite and confirmed all tests pass, including two new cases covering the missing-native_nameand array-input scenarios.References
Fixes #5740
Recurrence of the issue addressed by #5465 (the null check was subsequently reverted without a documented reason).
Reviewer guidance
LanguageDropdown.vue:120— the new guard uses== null(not!item.native_name) deliberately, to avoid catching empty-stringnative_namevalues that the existing test exercises.SearchFilters.vue:68-71— this is wheremultipleis used; reproduce the crash by opening Import from Channels, selecting a language filter, then blurring the dropdown.languageTextin the dropdown component.AI usage
I used Claude Code to trace through the Vuetify VAutocomplete and VSelect source to identify why
languageTextwas receiving an Array argument in multiple mode. After understanding the root cause (eager argument evaluation ingetValue), I wrote the guard and two new test cases. I reviewed the generated code and confirmed the condition== nullis correct for the intended behaviour.