Skip to content

Commit 7ae6728

Browse files
authored
fix(select): slot menu-header multiple rendering issue (#316)
#308
1 parent 4f88f58 commit 7ae6728

File tree

3 files changed

+36
-2
lines changed

3 files changed

+36
-2
lines changed

.cursor/rules/implementing-features-fixes.mdc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ description: End-to-end flow for implementing features and bug fixes using AI. E
77

88
Use this checklist for every feature or bug fix. Do not skip steps. Commands assume npm.
99

10+
### 0. Important notes
11+
- You are writing code for a Vue 3 select component.
12+
- Try to think in terms of the component's API and how it should be used.
13+
- When necessary, take a step back and think about the architecture of the component.
14+
1015
### 1. Write or update tests first
1116
- **Where**: colocate tests in `src/*.spec.ts` (see existing examples like `src/Select.spec.ts`).
1217
- **Run tests**: `npm run test`

src/Menu.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ onBeforeUnmount(() => {
143143
left: sharedProps.teleport ? calculateMenuPosition().left : 'unset',
144144
}"
145145
>
146+
<component :is="props.slots['menu-header']" v-if="props.slots['menu-header']" />
147+
146148
<MenuOption
147149
v-for="(option, i) in sharedData.availableOptions.value"
148150
:key="i"
@@ -155,8 +157,6 @@ onBeforeUnmount(() => {
155157
:class="sharedProps.classes?.menuOption"
156158
@select="sharedData.setOption(option)"
157159
>
158-
<component :is="props.slots['menu-header']" />
159-
160160
<template v-if="props.slots.option">
161161
<component
162162
:is="props.slots.option"

src/Select.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,4 +644,33 @@ describe("exposed component methods and refs", () => {
644644

645645
expect(wrapper.emitted("update:modelValue")).toContainEqual([[]]);
646646
});
647+
648+
it("should render menu-header slot only once before options", async () => {
649+
const wrapper = mount(VueSelect, {
650+
props: { modelValue: null, options },
651+
slots: {
652+
"menu-header": () => h("div", { class: "test-menu-header" }, "Menu Header"),
653+
},
654+
});
655+
656+
await openMenu(wrapper);
657+
658+
// Should have exactly one menu header
659+
const menuHeaders = wrapper.findAll(".test-menu-header");
660+
expect(menuHeaders.length).toBe(1);
661+
662+
// Should have the same number of options as provided
663+
const menuOptions = wrapper.findAll("div[role='option']");
664+
expect(menuOptions.length).toBe(options.length);
665+
666+
// Menu header should appear before all options in the DOM
667+
const menu = wrapper.find(".menu");
668+
const menuContent = menu.element.innerHTML;
669+
const headerIndex = menuContent.indexOf("test-menu-header");
670+
const firstOptionIndex = menuContent.indexOf("role=\"option\"");
671+
672+
expect(headerIndex).toBeGreaterThan(-1);
673+
expect(firstOptionIndex).toBeGreaterThan(-1);
674+
expect(headerIndex).toBeLessThan(firstOptionIndex);
675+
});
647676
});

0 commit comments

Comments
 (0)