feat(monorepo): migrate vue packages and apply oxlint refactors
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { defineComponent, nextTick, ref } from 'vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { useTemplateRefsList } from '.';
|
||||
|
||||
describe(useTemplateRefsList, () => {
|
||||
it('collects elements rendered with v-for', async () => {
|
||||
const Component = defineComponent({
|
||||
setup() {
|
||||
const items = ref([1, 2, 3]);
|
||||
const { refs, set } = useTemplateRefsList<HTMLDivElement>();
|
||||
return { items, refs, set };
|
||||
},
|
||||
template: `<div v-for="item in items" :key="item" :ref="set">{{ item }}</div>`,
|
||||
});
|
||||
|
||||
const wrapper = mount(Component);
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.refs).toHaveLength(3);
|
||||
expect(wrapper.vm.refs[0]).toBeInstanceOf(HTMLDivElement);
|
||||
expect(wrapper.vm.refs[1]).toBeInstanceOf(HTMLDivElement);
|
||||
expect(wrapper.vm.refs[2]).toBeInstanceOf(HTMLDivElement);
|
||||
});
|
||||
|
||||
it('updates refs when items are added', async () => {
|
||||
const Component = defineComponent({
|
||||
setup() {
|
||||
const items = ref([1, 2]);
|
||||
const { refs, set } = useTemplateRefsList<HTMLDivElement>();
|
||||
return { items, refs, set };
|
||||
},
|
||||
template: `<div v-for="item in items" :key="item" :ref="set">{{ item }}</div>`,
|
||||
});
|
||||
|
||||
const wrapper = mount(Component);
|
||||
await nextTick();
|
||||
expect(wrapper.vm.refs).toHaveLength(2);
|
||||
|
||||
wrapper.vm.items.push(3);
|
||||
await nextTick();
|
||||
expect(wrapper.vm.refs).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('updates refs when items are removed', async () => {
|
||||
const Component = defineComponent({
|
||||
setup() {
|
||||
const items = ref([1, 2, 3]);
|
||||
const { refs, set } = useTemplateRefsList<HTMLDivElement>();
|
||||
return { items, refs, set };
|
||||
},
|
||||
template: `<div v-for="item in items" :key="item" :ref="set">{{ item }}</div>`,
|
||||
});
|
||||
|
||||
const wrapper = mount(Component);
|
||||
await nextTick();
|
||||
expect(wrapper.vm.refs).toHaveLength(3);
|
||||
|
||||
wrapper.vm.items.splice(0, 1);
|
||||
await nextTick();
|
||||
expect(wrapper.vm.refs).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('returns empty array when no elements are rendered', async () => {
|
||||
const Component = defineComponent({
|
||||
setup() {
|
||||
const items = ref<number[]>([]);
|
||||
const { refs, set } = useTemplateRefsList<HTMLDivElement>();
|
||||
return { items, refs, set };
|
||||
},
|
||||
template: `<div><span v-for="item in items" :key="item" :ref="set">{{ item }}</span></div>`,
|
||||
});
|
||||
|
||||
const wrapper = mount(Component);
|
||||
await nextTick();
|
||||
expect(wrapper.vm.refs).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('unwraps component instances to their root elements', async () => {
|
||||
const Child = defineComponent({
|
||||
template: `<span class="child">child</span>`,
|
||||
});
|
||||
|
||||
const Parent = defineComponent({
|
||||
components: { Child },
|
||||
setup() {
|
||||
const items = ref([1, 2]);
|
||||
const { refs, set } = useTemplateRefsList<HTMLSpanElement>();
|
||||
return { items, refs, set };
|
||||
},
|
||||
template: `<div><Child v-for="item in items" :key="item" :ref="set" /></div>`,
|
||||
});
|
||||
|
||||
const wrapper = mount(Parent);
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.refs).toHaveLength(2);
|
||||
expect(wrapper.vm.refs[0]).toBeInstanceOf(HTMLSpanElement);
|
||||
expect(wrapper.vm.refs[0]!.classList.contains('child')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('preserves element order matching v-for order', async () => {
|
||||
const Component = defineComponent({
|
||||
setup() {
|
||||
const items = ref(['a', 'b', 'c']);
|
||||
const { refs, set } = useTemplateRefsList<HTMLDivElement>();
|
||||
return { items, refs, set };
|
||||
},
|
||||
template: `<div v-for="item in items" :key="item" :ref="set" :data-item="item">{{ item }}</div>`,
|
||||
});
|
||||
|
||||
const wrapper = mount(Component);
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.refs[0]!.dataset.item).toBe('a');
|
||||
expect(wrapper.vm.refs[1]!.dataset.item).toBe('b');
|
||||
expect(wrapper.vm.refs[2]!.dataset.item).toBe('c');
|
||||
});
|
||||
|
||||
it('handles complete list replacement', async () => {
|
||||
const Component = defineComponent({
|
||||
setup() {
|
||||
const items = ref([1, 2, 3]);
|
||||
const { refs, set } = useTemplateRefsList<HTMLDivElement>();
|
||||
return { items, refs, set };
|
||||
},
|
||||
template: `<div v-for="item in items" :key="item" :ref="set" :data-item="item">{{ item }}</div>`,
|
||||
});
|
||||
|
||||
const wrapper = mount(Component);
|
||||
await nextTick();
|
||||
expect(wrapper.vm.refs).toHaveLength(3);
|
||||
|
||||
wrapper.vm.items = [4, 5];
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.refs).toHaveLength(2);
|
||||
expect(wrapper.vm.refs[0]!.dataset.item).toBe('4');
|
||||
expect(wrapper.vm.refs[1]!.dataset.item).toBe('5');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { onBeforeUpdate, onMounted, onUpdated, readonly, shallowRef } from 'vue';
|
||||
import type { DeepReadonly, ShallowRef } from 'vue';
|
||||
import type { MaybeElement } from '../unrefElement';
|
||||
import { unrefElement } from '../unrefElement';
|
||||
|
||||
export interface UseTemplateRefsListReturn<El extends Element> {
|
||||
/** Reactive readonly array of collected template refs */
|
||||
refs: DeepReadonly<ShallowRef<El[]>>;
|
||||
/** Ref setter function — bind via `:ref="set"` in templates */
|
||||
set: (el: MaybeElement) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name useTemplateRefsList
|
||||
* @category Component
|
||||
* @description Collects a dynamic list of template refs for use with `v-for`.
|
||||
* Automatically clears the list before each component update and repopulates it
|
||||
* with fresh element references. Handles both plain DOM elements and Vue component
|
||||
* instances (unwraps `$el`).
|
||||
*
|
||||
* Uses a non-reactive buffer internally to collect refs during the render cycle,
|
||||
* then flushes to a `shallowRef` in `onMounted`/`onUpdated` to avoid triggering
|
||||
* recursive update loops.
|
||||
*
|
||||
* @returns {UseTemplateRefsListReturn<El>} An object with a reactive `refs` array and a `set` function
|
||||
*
|
||||
* @example
|
||||
* const { refs, set } = useTemplateRefsList<HTMLDivElement>();
|
||||
* // Template: <div v-for="item in items" :key="item.id" :ref="set" />
|
||||
* // refs.value contains all rendered div elements
|
||||
*
|
||||
* @since 0.0.14
|
||||
*/
|
||||
export function useTemplateRefsList<El extends Element = Element>(): UseTemplateRefsListReturn<El> {
|
||||
const refs = shallowRef<El[]>([]);
|
||||
let buffer: El[] = [];
|
||||
|
||||
const set = (el: MaybeElement) => {
|
||||
const plain = unrefElement(el);
|
||||
|
||||
if (plain)
|
||||
buffer.push(plain as unknown as El);
|
||||
};
|
||||
|
||||
const flush = () => {
|
||||
buffer.sort(documentPositionComparator);
|
||||
refs.value = buffer;
|
||||
};
|
||||
|
||||
onBeforeUpdate(() => {
|
||||
buffer = [];
|
||||
});
|
||||
|
||||
onMounted(flush);
|
||||
onUpdated(flush);
|
||||
|
||||
return {
|
||||
refs: readonly(refs) as DeepReadonly<ShallowRef<El[]>>,
|
||||
set,
|
||||
};
|
||||
}
|
||||
|
||||
function documentPositionComparator(a: Element, b: Element): number {
|
||||
if (a === b) return 0;
|
||||
|
||||
const position = a.compareDocumentPosition(b);
|
||||
|
||||
if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
|
||||
if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user