diff --git a/package.json b/package.json
index b64f621c..de17bea0 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,11 @@
"build-only": "vite build",
"test": "vitest",
"test:run": "vitest run",
+ "test:watch": "vitest watch",
+ "test:coverage": "vitest run --coverage",
+ "test:report": "vitest run --reporter=junit --reporter=verbose",
+ "test:ui": "vitest --ui",
+ "test:debug": "vitest run --inspect-brk",
"type-check": "vue-tsc --noEmit",
"lint": "eslint --cache --fix .",
"format": "prettier --cache --write --ignore-unknown .",
diff --git a/src/components/ActionCard.vue b/src/components/ActionCard.vue
index 104006ce..c1ce2d9f 100644
--- a/src/components/ActionCard.vue
+++ b/src/components/ActionCard.vue
@@ -105,13 +105,18 @@ const copy = async (content: string) => {
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ 查看
+
diff --git a/src/components/plus/device/DeviceSnapshotGroups.vue b/src/components/plus/device/DeviceSnapshotGroups.vue
index cdd105e0..6df1f334 100644
--- a/src/components/plus/device/DeviceSnapshotGroups.vue
+++ b/src/components/plus/device/DeviceSnapshotGroups.vue
@@ -8,6 +8,10 @@ const props = defineProps<{
snapshots: Snapshot[];
checkedRowKeys: number[];
refreshSnapshots: () => Promise;
+ previewSnapshot: {
+ loading: Record;
+ invoke: (row: Snapshot) => unknown;
+ };
}>();
const emit = defineEmits<{
@@ -26,15 +30,14 @@ const {
checkedSet,
snapshotViewedTime,
batchDelete,
+ deleteSnapshot,
previewUrlMap,
previewLoadingMap,
previewErrorMap,
- previewSnapshot,
downloadSnapshotZip,
downloadSnapshotImage,
shareSnapshotZipUrl,
shareSnapshotImageUrl,
- deleteSnapshot,
ensurePreview,
getGroupSnapshotIds,
getActivitySnapshotIds,
@@ -230,15 +233,21 @@ const {
-
-
-
+
+
+
+
+
+
+
+
+ 查看
+
diff --git a/src/components/plus/home/HomeSnapshotGroups.vue b/src/components/plus/home/HomeSnapshotGroups.vue
index ef47681a..39864604 100644
--- a/src/components/plus/home/HomeSnapshotGroups.vue
+++ b/src/components/plus/home/HomeSnapshotGroups.vue
@@ -2,7 +2,6 @@
import { computed, toRef } from 'vue';
import ActionCard from '@/components/ActionCard.vue';
import DeviceControlTools from '@/components/plus/device/DeviceControlTools.vue';
-import SvgIcon from '@/components/SvgIcon.vue';
import { useHomePlus } from '@/composables/plus/useHomePlus';
const props = defineProps<{
@@ -50,7 +49,6 @@ const {
getActivityRemarkKey,
openGroupRemarkModal,
saveGroupRemark,
- goToSnapshot,
getItemShortTimeText,
getItemCreateTimeText,
getItemImportTimeText,
@@ -317,17 +315,9 @@ const {
-
-
-
diff --git a/src/composables/plus/useDevicePlus.ts b/src/composables/plus/useDevicePlus.ts
index e541db1f..8712d02c 100644
--- a/src/composables/plus/useDevicePlus.ts
+++ b/src/composables/plus/useDevicePlus.ts
@@ -32,7 +32,6 @@ export function useDevicePlus(options: UseDevicePlusOptions) {
type SnapshotGroup = ReturnType[number];
type SnapshotActivity = SnapshotGroup['activities'][number];
- const router = useRouter();
const { settingsStore, snapshotImportTime, snapshotViewedTime } =
useStorageStore();
const deviceLink = useStorage('device_link', '');
@@ -137,20 +136,6 @@ export function useDevicePlus(options: UseDevicePlusOptions) {
cacheLimit: previewCacheLimit,
});
- const previewSnapshot = useBatchTask(
- async (row: Snapshot) => {
- await ensureLocalSnapshotData(row);
- snapshotViewedTime[row.id] = Date.now();
- window.open(
- router.resolve({
- name: 'snapshot',
- params: { snapshotId: row.id },
- }).href,
- );
- },
- (row) => row.id,
- );
-
const downloadSnapshotZip = useBatchTask(
async (row: Snapshot) => {
await ensureLocalSnapshotData(row);
@@ -198,30 +183,29 @@ export function useDevicePlus(options: UseDevicePlusOptions) {
const deleteSnapshot = useBatchTask(
async (row: Snapshot) => {
- await new Promise((resolve, reject) => {
+ const confirmed = await new Promise((res) => {
dialog.warning({
- title: '删除',
- content: '是否删除此快照?',
+ title: '删除快照',
+ content: `是否确认删除快照 ID: ${row.id}?此操作不可恢复。`,
+ positiveText: '确认删除',
negativeText: '取消',
- positiveText: '确认',
- onClose: reject,
- onEsc: reject,
- onMaskClick: reject,
- onNegativeClick: reject,
- onPositiveClick: resolve,
+ onPositiveClick: () => res(true),
+ onNegativeClick: () => res(false),
+ onClose: () => res(false),
});
});
-
+ if (!confirmed) return;
await api.deleteSnapshot({ id: row.id });
- await snapshotStorage.removeItem(row.id);
- await screenshotStorage.removeItem(row.id);
- message.success('快照删除成功');
- await options.refreshSnapshots();
+ await Promise.all([
+ snapshotStorage.removeItem(row.id),
+ screenshotStorage.removeItem(row.id),
+ ]);
options.checkedRowKeys.value = options.checkedRowKeys.value.filter(
(id) => id !== row.id,
);
+ message.success('删除成功');
},
- (row) => row.id,
+ (r) => r.id,
);
const batchDelete = useTask(async () => {
@@ -287,15 +271,14 @@ export function useDevicePlus(options: UseDevicePlusOptions) {
checkedSet,
snapshotViewedTime,
batchDelete,
+ deleteSnapshot,
previewUrlMap,
previewLoadingMap,
previewErrorMap,
- previewSnapshot,
downloadSnapshotZip,
downloadSnapshotImage,
shareSnapshotZipUrl,
shareSnapshotImageUrl,
- deleteSnapshot,
ensurePreview,
getGroupSnapshotIds,
getActivitySnapshotIds,
diff --git a/src/composables/plus/useHomePlus.ts b/src/composables/plus/useHomePlus.ts
index b8dea54a..55d389d5 100644
--- a/src/composables/plus/useHomePlus.ts
+++ b/src/composables/plus/useHomePlus.ts
@@ -3,7 +3,6 @@ import { computed, shallowReactive, shallowRef, toValue, watch } from 'vue';
import type { MaybeRefOrGetter, Ref } from 'vue';
import { usePreviewCache } from '@/composables/plus/usePreviewCache';
import { getAppInfo, getDevice } from '@/utils/node';
-import { filterQuery } from '@/utils/others';
import { buildGroupedSnapshots } from '@/utils/plus/snapshotGroup';
import { screenshotStorage } from '@/utils/snapshot';
@@ -13,8 +12,6 @@ interface UseHomePlusOptions {
}
export function useHomePlus(options: UseHomePlusOptions) {
- const route = useRoute();
- const router = useRouter();
const { settingsStore, snapshotImportTime, snapshotViewedTime } =
useStorageStore();
@@ -196,17 +193,6 @@ export function useHomePlus(options: UseHomePlusOptions) {
cacheLimit: previewCacheLimit,
});
- const goToSnapshot = (snapshotId: number) => {
- snapshotViewedTime[snapshotId] = Date.now();
- window.open(
- router.resolve({
- name: 'snapshot',
- params: { snapshotId },
- query: filterQuery(route.query, ['str', 'gkd']),
- }).href,
- );
- };
-
const getItemShortTimeText = (item: Snapshot) =>
dayjs(item.id).format('MM-DD HH:mm:ss');
const getItemCreateTimeText = (item: Snapshot) =>
@@ -245,7 +231,6 @@ export function useHomePlus(options: UseHomePlusOptions) {
getActivityRemarkKey,
openGroupRemarkModal,
saveGroupRemark,
- goToSnapshot,
getItemShortTimeText,
getItemCreateTimeText,
getItemImportTimeText,
diff --git a/src/router.ts b/src/router.ts
index 94591b5d..c9501253 100644
--- a/src/router.ts
+++ b/src/router.ts
@@ -97,12 +97,12 @@ const router = createRouter({
},
{
path: '/selector',
- component: recordModule(() => import('@/views/plus/SelectorPage.vue')),
+ component: recordModule(() => import('@/views/SelectorPage.vue')),
meta: { title: '选择器' },
},
{
path: '/svg',
- component: recordModule(() => import('@/views/plus/SvgPage.vue')),
+ component: recordModule(() => import('@/views/SvgPage.vue')),
meta: { title: 'SVG' },
},
{
diff --git a/src/test/setup.ts b/src/test/setup.ts
new file mode 100644
index 00000000..22bf3ed1
--- /dev/null
+++ b/src/test/setup.ts
@@ -0,0 +1,165 @@
+/* eslint-disable vue/one-component-per-file */
+import { vi } from 'vitest';
+import { defineComponent, h } from 'vue';
+
+globalThis.console = {
+ ...console,
+ warn: vi.fn(),
+ error: vi.fn(),
+};
+
+vi.mock('@/components/SvgIcon.vue', () => ({
+ default: defineComponent({
+ name: 'SvgIconStub',
+ props: {
+ name: { type: String, required: true },
+ },
+ setup(props: { name: string }) {
+ return () => h('span', { 'data-testid': `svg-${props.name}` });
+ },
+ }),
+}));
+
+vi.mock('naive-ui', () => ({
+ NTooltip: defineComponent({
+ name: 'NTooltipStub',
+ setup(_: unknown, { slots }: any) {
+ return () => h('div', [slots.trigger?.(), slots.default?.()]);
+ },
+ }),
+ NButton: defineComponent({
+ name: 'NButtonStub',
+ emits: ['click'],
+ setup(_: unknown, { emit, slots }: any) {
+ return () =>
+ h(
+ 'button',
+ {
+ type: 'button',
+ onClick: () => emit('click'),
+ },
+ slots.default?.(),
+ );
+ },
+ }),
+ NModal: defineComponent({
+ name: 'NModalStub',
+ props: {
+ show: { type: Boolean, default: false },
+ },
+ setup(props: any, { slots }: any) {
+ return () => {
+ if (!props.show) return null;
+ return h('div', { 'data-testid': 'modal' }, slots.default?.());
+ };
+ },
+ }),
+ NSpin: defineComponent({
+ name: 'NSpinStub',
+ setup(_: unknown, { slots }: any) {
+ return () => h('div', { 'data-testid': 'spin' }, slots.default?.());
+ },
+ }),
+ NSpace: defineComponent({
+ name: 'NSpaceStub',
+ setup(_: unknown, { slots }: any) {
+ return () => h('div', { 'data-testid': 'space' }, slots.default?.());
+ },
+ }),
+ // 完美符合 Naive UI 契约的表单桩
+ NCheckbox: defineComponent({
+ name: 'NCheckboxStub',
+ props: {
+ checked: { type: Boolean, default: false },
+ },
+ emits: ['update:checked'],
+ setup(props, { emit }) {
+ return () =>
+ h('input', {
+ type: 'checkbox',
+ checked: props.checked,
+ onChange: (e: Event) =>
+ emit('update:checked', (e.target as HTMLInputElement).checked),
+ });
+ },
+ }),
+ NInput: defineComponent({
+ name: 'NInputStub',
+ props: {
+ value: { type: String, default: '' },
+ },
+ emits: ['update:value'],
+ setup(props, { emit }) {
+ return () =>
+ h('input', {
+ type: 'text',
+ value: props.value,
+ onInput: (e: Event) =>
+ emit('update:value', (e.target as HTMLInputElement).value),
+ });
+ },
+ }),
+ NSelect: defineComponent({
+ name: 'NSelectStub',
+ props: {
+ value: { type: [String, Number], default: '' },
+ options: {
+ type: Array,
+ default: () => [] as any[],
+ },
+ },
+ emits: ['update:value'],
+ setup(props, { emit }) {
+ return () =>
+ h(
+ 'select',
+ {
+ value: props.value as any,
+ onChange: (e: Event) =>
+ emit('update:value', (e.target as HTMLSelectElement).value),
+ },
+ (props.options as any[]).map((option: any) =>
+ h('option', { value: option.value }, option.label),
+ ),
+ );
+ },
+ }),
+ NMessageProvider: defineComponent({
+ name: 'NMessageProviderStub',
+ setup(_: unknown, { slots }: any) {
+ return () =>
+ h('div', { 'data-testid': 'message-provider' }, slots.default?.());
+ },
+ }),
+ NDialogProvider: defineComponent({
+ name: 'NDialogProviderStub',
+ setup(_: unknown, { slots }: any) {
+ return () =>
+ h('div', { 'data-testid': 'dialog-provider' }, slots.default?.());
+ },
+ }),
+}));
+
+vi.mock('@vueuse/core', () => ({
+ useStorage: vi.fn(() => ({
+ value: '',
+ })),
+ useEventListener: vi.fn(() => vi.fn()),
+ useDebounceFn: vi.fn((fn: (...args: unknown[]) => void) => fn),
+}));
+
+vi.mock('@/store/global', () => ({
+ useGlobalStore: vi.fn(() => ({
+ ruleTest: {
+ enabled: false,
+ setEnabled: vi.fn(),
+ },
+ })),
+}));
+
+vi.mock('@/store/storage', () => ({
+ useStorageStore: vi.fn(() => ({
+ snapshots: [],
+ selectedSnapshotId: null,
+ })),
+}));
diff --git a/src/test/utils.ts b/src/test/utils.ts
new file mode 100644
index 00000000..8b07a074
--- /dev/null
+++ b/src/test/utils.ts
@@ -0,0 +1,167 @@
+/* eslint-disable vue/one-component-per-file */
+import {
+ defineComponent,
+ h,
+ type Component,
+ type ComponentPublicInstance,
+ type DefineComponent,
+} from 'vue';
+import { mount, type MountingOptions } from '@vue/test-utils';
+import { expect, vi } from 'vitest';
+
+export const createStubComponent = (
+ name: string,
+ options: {
+ props?: string[];
+ slots?: string[];
+ emits?: string[];
+ render?: (props: Record) => ReturnType;
+ },
+): DefineComponent => {
+ return defineComponent({
+ name: `${name}Stub`,
+ props: options.props,
+ emits: options.emits,
+ setup(props: Record, { slots, emit }: any) {
+ const renderProps = {
+ ...props,
+ emit,
+ };
+
+ if (options.render) {
+ return () => options.render!(renderProps);
+ }
+
+ return () =>
+ h(
+ 'div',
+ {
+ 'data-testid': name
+ .replace(/([A-Z])/g, '-$1')
+ .toLowerCase()
+ .slice(1),
+ },
+ slots.default?.(),
+ );
+ },
+ }) as any;
+};
+
+export const createMockFn = unknown>(
+ mockImplementation?: T,
+) => {
+ return vi.fn(mockImplementation);
+};
+
+export const createSpies = <
+ T extends Record unknown>,
+>(
+ spyMap: T,
+): Record> => {
+ const result: Record> = {};
+ for (const key in spyMap) {
+ result[key] = vi.fn(spyMap[key]);
+ }
+ return result as Record>;
+};
+
+export const mountWithStubs = (
+ component: Component,
+ options?: MountingOptions,
+) => {
+ const defaultStubs = {
+ NTooltip: defineComponent({
+ name: 'NTooltipStub',
+ setup(_: unknown, { slots }: any) {
+ return () => h('div', [slots.trigger?.(), slots.default?.()]);
+ },
+ }),
+ NButton: defineComponent({
+ name: 'NButtonStub',
+ emits: ['click'],
+ setup(_: unknown, { emit, slots }: any) {
+ return () =>
+ h(
+ 'button',
+ {
+ type: 'button',
+ onClick: () => emit('click'),
+ },
+ slots.default?.(),
+ );
+ },
+ }),
+ SvgIcon: defineComponent({
+ name: 'SvgIconStub',
+ props: {
+ name: { type: String, required: false },
+ },
+ setup(props: { name?: string }) {
+ return () => h('span', { 'data-testid': `svg-${props.name}` });
+ },
+ }),
+ };
+
+ return mount(component, {
+ ...options,
+ global: {
+ ...options?.global,
+ stubs: {
+ ...defaultStubs,
+ ...options?.global?.stubs,
+ },
+ },
+ });
+};
+
+export const waitForComponentToMount = async (): Promise => {
+ await new Promise((resolve) => setTimeout(resolve, 0));
+};
+
+export const triggerClick = async (
+ wrapper: ReturnType,
+ selector: string,
+): Promise => {
+ await wrapper.find(selector).trigger('click');
+};
+
+export const expectElementExists = (
+ wrapper: ReturnType,
+ selector: string,
+ exists: boolean = true,
+): void => {
+ expect(wrapper.find(selector).exists()).toBe(exists);
+};
+
+export const expectElementText = (
+ wrapper: ReturnType,
+ selector: string,
+ text: string | RegExp,
+): void => {
+ const element = wrapper.find(selector);
+ expect(element.exists()).toBe(true);
+ if (typeof text === 'string') {
+ expect(element.text()).toBe(text);
+ } else {
+ expect(element.text()).toMatch(text);
+ }
+};
+
+export const expectSpyCalled = (
+ spy: ReturnType,
+ times?: number,
+ ...args: unknown[]
+): void => {
+ if (times !== undefined) {
+ expect(spy).toHaveBeenCalledTimes(times);
+ }
+ if (args.length > 0) {
+ expect(spy).toHaveBeenCalledWith(...args);
+ } else {
+ expect(spy).toHaveBeenCalled();
+ }
+};
+
+export const expectSpyNotCalled = (spy: ReturnType): void => {
+ expect(spy).not.toHaveBeenCalled();
+};
diff --git a/src/types/plus/index.d.ts b/src/types/plus/index.d.ts
index 04909c34..85c1bcdf 100644
--- a/src/types/plus/index.d.ts
+++ b/src/types/plus/index.d.ts
@@ -7,7 +7,7 @@ type FastQueryIndicatorMeta = {
declare module '@gkd-kit/selector' {
// The generic parameter comes from the upstream declaration we are merging.
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+
interface QueryPath {
// Keep the selector path formatting hook in the Plus layer so base typings
// stay close to upstream.
diff --git a/src/views/DevicePage.vue b/src/views/DevicePage.vue
index 7efbdada..49633cc8 100644
--- a/src/views/DevicePage.vue
+++ b/src/views/DevicePage.vue
@@ -534,6 +534,8 @@ const placeholder = `
:pagination="pagination"
:handleSorterChange="handleSorterChange"
:refreshSnapshots="refreshSnapshots"
+ :previewSnapshot="previewSnapshot"
+ :deleteSnapshot="deleteSnapshot"
>
([]);
-
+
diff --git a/src/views/plus/SelectorPage.vue b/src/views/plus/SelectorPage.vue
deleted file mode 100644
index 6d3dafad..00000000
--- a/src/views/plus/SelectorPage.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/src/views/plus/SvgPage.vue b/src/views/plus/SvgPage.vue
deleted file mode 100644
index 069128b6..00000000
--- a/src/views/plus/SvgPage.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/src/views/plus/__tests__/adapter.contract.test.ts b/src/views/plus/__tests__/adapter.contract.test.ts
index 8467e5ed..6d05257d 100644
--- a/src/views/plus/__tests__/adapter.contract.test.ts
+++ b/src/views/plus/__tests__/adapter.contract.test.ts
@@ -1,7 +1,7 @@
/* eslint-disable vue/one-component-per-file */
-import { mount } from '@vue/test-utils';
import { defineComponent, h } from 'vue';
import { describe, expect, it, vi } from 'vitest';
+import { createStubComponent, mountWithStubs } from '@/test/utils';
const spies = vi.hoisted(() => ({
setCheckedRowKeys: vi.fn(),
@@ -11,7 +11,12 @@ const spies = vi.hoisted(() => ({
vi.mock('@/views/home/HomePage.vue', () => ({
default: defineComponent({
name: 'BaseHomePageStub',
- setup(_, { slots }) {
+ setup(
+ _: unknown,
+ {
+ slots,
+ }: { slots: Record ReturnType> },
+ ) {
return () =>
h(
'section',
@@ -31,9 +36,16 @@ vi.mock('@/views/home/HomePage.vue', () => ({
vi.mock('@/views/DevicePage.vue', () => ({
default: defineComponent({
name: 'BaseDevicePageStub',
- setup(_, { slots }) {
+ setup(
+ _: unknown,
+ {
+ slots,
+ }: { slots: Record ReturnType> },
+ ) {
const captureSnapshot = { loading: false, invoke: vi.fn() };
const downloadAllSnapshot = { loading: false, invoke: vi.fn() };
+ const previewSnapshot = { loading: {}, invoke: vi.fn() };
+ const deleteSnapshot = { loading: {}, invoke: vi.fn() };
return () =>
h('section', { 'data-testid': 'base-device-page' }, [
h(
@@ -50,6 +62,8 @@ vi.mock('@/views/DevicePage.vue', () => ({
slots.content?.({
snapshots: [{ id: 10 }, { id: 20 }],
refreshSnapshots: spies.refreshSnapshots,
+ previewSnapshot,
+ deleteSnapshot,
}),
),
]);
@@ -67,7 +81,15 @@ vi.mock('@/components/plus/home/HomeSnapshotGroups.vue', () => ({
updateSnapshots: { type: Function, required: true },
},
emits: ['update:checkedRowKeys'],
- setup(props, { emit }) {
+ setup(
+ props: {
+ checkedRowKeys: number[];
+ snapshots: Array<{ id: number }>;
+ loading: boolean;
+ updateSnapshots: () => void;
+ },
+ { emit }: { emit: (e: string, v: number[]) => void },
+ ) {
return () =>
h(
'button',
@@ -82,12 +104,7 @@ vi.mock('@/components/plus/home/HomeSnapshotGroups.vue', () => ({
}));
vi.mock('@/components/plus/device/DeviceControlTools.vue', () => ({
- default: defineComponent({
- name: 'DeviceControlToolsStub',
- setup() {
- return () => h('div', { 'data-testid': 'device-control-tools' });
- },
- }),
+ default: createStubComponent('DeviceControlTools', {}),
}));
vi.mock('@/components/plus/device/DeviceSnapshotGroups.vue', () => ({
@@ -97,9 +114,18 @@ vi.mock('@/components/plus/device/DeviceSnapshotGroups.vue', () => ({
checkedRowKeys: { type: Array, required: true },
snapshots: { type: Array, required: true },
refreshSnapshots: { type: Function, required: true },
+ previewSnapshot: { type: Object, required: true },
},
emits: ['update:checkedRowKeys'],
- setup(props, { emit }) {
+ setup(
+ props: {
+ checkedRowKeys: number[];
+ snapshots: Array<{ id: number }>;
+ refreshSnapshots: () => void;
+ previewSnapshot: unknown;
+ },
+ { emit }: { emit: (e: string, v: number[]) => void },
+ ) {
return () =>
h(
'button',
@@ -119,58 +145,18 @@ vi.mock('@/components/plus/settings/SettingsModal.vue', () => ({
props: {
show: { type: Boolean, required: true },
},
- setup(props) {
+ setup(props: { show: boolean }) {
return () =>
h('div', { 'data-testid': 'settings-modal' }, `settings:${props.show}`);
},
}),
}));
-vi.mock('@/components/SvgIcon.vue', () => ({
- default: defineComponent({
- name: 'SvgIconStub',
- props: {
- name: { type: String, required: true },
- },
- setup(props) {
- return () => h('span', { 'data-testid': `svg-${props.name}` });
- },
- }),
-}));
-
-const globalStubs = {
- NTooltip: defineComponent({
- name: 'NTooltipStub',
- setup(_, { slots }) {
- return () => h('div', [slots.trigger?.(), slots.default?.()]);
- },
- }),
- NButton: defineComponent({
- name: 'NButtonStub',
- emits: ['click'],
- setup(_, { emit, slots }) {
- return () =>
- h(
- 'button',
- {
- type: 'button',
- onClick: () => emit('click'),
- },
- slots.default?.(),
- );
- },
- }),
-};
-
describe('Plus route adapters', () => {
it('wires HomePage content slot into the base home page', async () => {
const { default: HomePage } =
await import('@/views/plus/home/HomePage.vue');
- const wrapper = mount(HomePage, {
- global: {
- stubs: globalStubs,
- },
- });
+ const wrapper = mountWithStubs(HomePage);
expect(wrapper.find('[data-testid="base-home-page"]').exists()).toBe(true);
expect(wrapper.get('[data-testid="home-snapshot-groups"]').text()).toBe(
@@ -183,11 +169,7 @@ describe('Plus route adapters', () => {
it('wires DevicePage action and content slots into the base device page', async () => {
const { default: DevicePage } = await import('@/views/plus/DevicePage.vue');
- const wrapper = mount(DevicePage, {
- global: {
- stubs: globalStubs,
- },
- });
+ const wrapper = mountWithStubs(DevicePage);
expect(wrapper.find('[data-testid="base-device-page"]').exists()).toBe(
true,
diff --git a/vitest.config.ts b/vitest.config.ts
index 8db4eea0..46b85cf4 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -4,26 +4,57 @@ import process from 'node:process';
import { defineConfig } from 'vitest/config';
import unocss from 'unocss/vite';
import data from 'unplugin-data/vite';
-import { unAutoImport } from './plugins';
+import autoImport from 'unplugin-auto-import/vite';
+import components from 'unplugin-vue-components/vite';
+import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
-export default defineConfig(async () => {
- return {
- plugins: [
- vue(),
- vueJsx(),
- unocss({ inspector: false }),
- await unAutoImport(),
- data(),
- ],
- resolve: {
- alias: {
- '@': process.cwd() + '/src',
- },
+export default defineConfig({
+ plugins: [
+ vue(),
+ vueJsx(),
+ unocss({ inspector: false }),
+ data(),
+ autoImport({
+ dts: process.cwd() + '/auto-import.d.ts',
+ imports: ['vue', 'vue-router', '@vueuse/core'],
+ dirs: [process.cwd() + '/src/store/**'],
+ }),
+ components({
+ include: [/\.[tj]sx?$/, /\.vue$/, /\.vue\?vue/],
+ dts: process.cwd() + '/auto-import-components.d.ts',
+ resolvers: [NaiveUiResolver()],
+ dirs: [],
+ }),
+ ],
+ resolve: {
+ alias: {
+ '@': process.cwd() + '/src',
},
- test: {
- environment: 'happy-dom',
- include: ['src/**/*.test.ts'],
- testTimeout: 10000,
+ },
+ test: {
+ environment: 'happy-dom',
+ include: ['src/**/*.test.ts'],
+ exclude: ['node_modules', 'dist', '.git'],
+ testTimeout: 10000,
+ hookTimeout: 10000,
+ setupFiles: ['src/test/setup.ts'],
+ coverage: {
+ provider: 'v8',
+ reporter: ['text', 'json', 'html'],
+ exclude: [
+ 'node_modules/',
+ 'dist/',
+ '.git/',
+ '**/*.d.ts',
+ '**/main.ts',
+ '**/router.ts',
+ '**/store/*.ts',
+ ],
},
- };
+ globals: true,
+ passWithNoTests: true,
+ outputFile: {
+ junit: './test-results/junit.xml',
+ },
+ },
});