// @vitest-environment nuxt import { describe, it, expect, beforeEach } from 'vitest'; import { mockNuxtImport } from '@nuxt/test-utils/runtime'; import { effectScope, nextTick, reactive, ref, watch } from 'vue'; import { useSeoOverridesState, useSeoOverrides, useSetSeoOverride } from '../use-seo-overrides'; // реактивный, а не переприсваиваемый: useRoute() отдаётся один раз на setup, // и смена пути должна долетать через реактивность, как в реальном роутере const mockRoute = reactive({ path: '/videos/some-video' }); mockNuxtImport('useRoute', () => () => mockRoute); const countWrites = () => { const writes = ref(0); watch(useSeoOverridesState(), () => { writes.value += 1; }, { flush: 'sync' }); return writes; }; describe('useSetSeoOverride', () => { beforeEach(() => { mockRoute.path = '/videos/some-video'; useSeoOverridesState().value = null; }); it('пишет уточнение для текущего пути', async () => { const scope = effectScope(); scope.run(() => useSetSeoOverride(() => ({ canonicalLocale: 'en' }))); await nextTick(); expect(useSeoOverridesState().value).toEqual({ path: '/videos/some-video', canonicalLocale: 'en' }); scope.stop(); }); it('складывает уточнения от разных источников', async () => { const scope = effectScope(); scope.run(() => { useSetSeoOverride(() => ({ canonicalLocale: 'en' })); useSetSeoOverride(() => ({ alonePage: true })); }); await nextTick(); expect(useSeoOverridesState().value).toEqual({ path: '/videos/some-video', canonicalLocale: 'en', alonePage: true, }); scope.stop(); }); // регрессия: watchEffect, читавший и писавший один и тот же стейт, уходил // в бесконечный цикл на клиенте («Maximum recursive updates exceeded») it('не зацикливается: конечное число записей в стейт', async () => { const writes = countWrites(); const scope = effectScope(); scope.run(() => { useSetSeoOverride(() => ({ canonicalLocale: 'en' })); useSetSeoOverride(() => ({ alonePage: true })); }); await nextTick(); await nextTick(); expect(writes.value).toBeLessThanOrEqual(2); scope.stop(); }); it('повторная запись того же значения стейт не трогает', async () => { const scope = effectScope(); const alonePage = ref(true); scope.run(() => useSetSeoOverride(() => ({ alonePage: alonePage.value }))); await nextTick(); const writes = countWrites(); alonePage.value = true; await nextTick(); expect(writes.value).toBe(0); scope.stop(); }); it('уточнение с другого пути не читается', () => { useSeoOverridesState().value = { path: '/other', alonePage: true }; expect(useSeoOverrides().value).toEqual({}); }); it('смена пути сбрасывает накопленное', async () => { const scope = effectScope(); const canonicalLocale = ref('en'); scope.run(() => useSetSeoOverride(() => ({ canonicalLocale: canonicalLocale.value }))); await nextTick(); useSeoOverridesState().value = { ...useSeoOverridesState().value!, alonePage: true }; mockRoute.path = '/videos/other-video'; canonicalLocale.value = undefined; await nextTick(); expect(useSeoOverridesState().value).toEqual({ path: '/videos/other-video', canonicalLocale: undefined }); scope.stop(); }); });