Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/components/Composer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@
id="to"
ref="toLabel"
:model-value="selectTo"
class="select"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nitpick: unrelated, stray edit

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yep sorry for that – I initially wanted to also have the issue fixed of the subject and body jumping when the "To" field is focused vs unfocused. Will continue with that later. :)

:options="selectableRecipients.filter(recipient => !selectTo.some(to => to.email === recipient.email))"
:get-option-key="(option) => option.email"
:taggable="true"
:aria-label-combobox="t('mail', 'Select recipient')"
:filter-by="(option, label, search) => filterOption(option, label, search, 'to')"
:dropdown-should-open="shouldOpenRecipientDropdown"
Comment on lines +55 to +61
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

A new class=\"select\" was added to the To field only. If this class impacts layout/styling/behavior, it can lead to inconsistent rendering versus Cc/Bcc. Either apply the same class consistently to Cc/Bcc selects or remove it if it’s not required for this change.

Suggested change
class="select"
:options="selectableRecipients.filter(recipient => !selectTo.some(to => to.email === recipient.email))"
:get-option-key="(option) => option.email"
:taggable="true"
:aria-label-combobox="t('mail', 'Select recipient')"
:filter-by="(option, label, search) => filterOption(option, label, search, 'to')"
:dropdown-should-open="shouldOpenRecipientDropdown"
:options="selectableRecipients.filter(recipient => !selectTo.some(to => to.email === recipient.email))"
:get-option-key="(option) => option.email"
:taggable="true"
:aria-label-combobox="t('mail', 'Select recipient')"
:filter-by="(option, label, search) => filterOption(option, label, search, 'to')"
:dropdown-should-open="shouldOpenRecipientDropdown"
:dropdown-should-open="shouldOpenRecipientDropdown"

Copilot uses AI. Check for mistakes.
:multiple="true"
:clear-search-on-select="true"
:loading="loadingIndicatorTo"
Expand Down Expand Up @@ -112,6 +114,7 @@
:get-option-key="(option) => option.email"
:no-wrap="false"
:filter-by="(option, label, search) => filterOption(option, label, search, 'cc')"
:dropdown-should-open="shouldOpenRecipientDropdown"
:taggable="true"
:clear-search-on-blur="() => clearOnBlur('cc')"
:append-to-body="false"
Expand Down Expand Up @@ -169,6 +172,7 @@
:filter-by="(option, label, search) => filterOption(option, label, search, 'bcc')"
:options="selectableRecipients.filter(recipient => !selectBcc.some(bcc => bcc.email === recipient.email))"
:get-option-key="(option) => option.email"
:dropdown-should-open="shouldOpenRecipientDropdown"
:taggable="true"
:clear-search-on-blur="() => clearOnBlur('bcc')"
:append-to-body="false"
Expand Down Expand Up @@ -1177,6 +1181,10 @@ export default {
|| (option?.email || '').toLocaleLowerCase().includes(searchInLowerCase)
},

shouldOpenRecipientDropdown({ noDrop, open, search }) {
return !noDrop && open && search.trim() !== ''
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

search.trim() will throw if search is ever null/undefined (component libraries sometimes use non-string sentinel values). Make this defensive by normalizing search to a string first (e.g., (search || '')), so dropdown behavior can’t crash the composer.

Suggested change
return !noDrop && open && search.trim() !== ''
const normalizedSearch = (search || '')
return !noDrop && open && normalizedSearch.trim() !== ''

Copilot uses AI. Check for mistakes.
},

setAlias() {
const previous = this.selectedAlias
if (this.fromAccount && this.fromAlias) {
Expand Down
54 changes: 54 additions & 0 deletions src/tests/unit/components/Composer.vue.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,4 +378,58 @@ describe('Composer', () => {

expect(view.vm.submitButtonTitle).toEqual('Encrypt with Mailvelope and send later Jan 1, 02:00 PM')
})

it('does not open the recipient dropdown on focus without a search term', () => {
const view = shallowMount(Composer, {
propsData: {
isFirstOpen: true,
accounts: [
{
id: 123,
editorMode: 'plaintext',
isUnified: false,
aliases: [],
},
],
},
mocks: {
$route,
},
store,
localVue,
})
Comment on lines +383 to +400
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

Both new tests duplicate the same shallowMount(Composer, …) setup. Consider extracting a small factory helper (e.g., mountComposer() returning the wrapper) to reduce repetition and make future test additions less error-prone.

Copilot uses AI. Check for mistakes.

expect(view.vm.shouldOpenRecipientDropdown({
noDrop: false,
open: true,
search: '',
})).toEqual(false)
})

Comment on lines +406 to +408
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

The new behavior trims the search term, but there’s no test asserting whitespace-only input stays closed (e.g., search: ' '). Add a test for the trim behavior (and ideally also a case where noDrop: true forces false) to cover the added branch conditions.

Suggested change
})).toEqual(false)
})
})).toEqual(false)
expect(view.vm.shouldOpenRecipientDropdown({
noDrop: false,
open: true,
search: ' ',
})).toEqual(false)
})
it('does not open the recipient dropdown when noDrop is true', () => {
const view = shallowMount(Composer, {
propsData: {
isFirstOpen: true,
accounts: [
{
id: 123,
editorMode: 'plaintext',
isUnified: false,
aliases: [],
},
],
},
mocks: {
$route,
},
store,
localVue,
})
expect(view.vm.shouldOpenRecipientDropdown({
noDrop: true,
open: true,
search: 'alice',
})).toEqual(false)
})

Copilot uses AI. Check for mistakes.
it('opens the recipient dropdown once a search term is entered', () => {
const view = shallowMount(Composer, {
propsData: {
isFirstOpen: true,
accounts: [
{
id: 123,
editorMode: 'plaintext',
isUnified: false,
aliases: [],
},
],
},
mocks: {
$route,
},
store,
localVue,
})

expect(view.vm.shouldOpenRecipientDropdown({
noDrop: false,
open: true,
search: 'alice',
})).toEqual(true)
})
})
Loading