fix(deals): router в DealsViewRedesign.spec + idempotency guard + watch-test (C8/F3 review fixup)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-16 11:25:49 +03:00
parent 2504f1b9ec
commit 6e35193f3b
3 changed files with 26 additions and 20 deletions
+2 -5
View File
@@ -174,7 +174,7 @@ onMounted(async () => {
});
watch(
() => route?.query?.openId,
() => route.query.openId,
() => openDealFromQuery(),
);
@@ -228,12 +228,10 @@ function openDeal(deal: MockDeal) {
/** Audit C8/F3: deep-link — открыть drawer сделки по ?openId= из URL. */
function openDealFromQuery(): void {
// Guard: route may be undefined when component is mounted without a router
// (e.g. isolated unit tests in DealsViewRedesign.spec.ts).
if (!route) return;
const raw = route.query.openId;
const id = Number(Array.isArray(raw) ? raw[0] : raw);
if (!Number.isInteger(id) || id <= 0) return;
if (selectedDeal.value?.id === id) return;
const deal = dealsState.find((d) => d.id === id);
if (deal) openDeal(deal);
}
@@ -432,7 +430,6 @@ defineExpose({
toggleManagerDraft,
drawerOpen,
selectedDeal,
openDealFromQuery,
});
const leadStatuses = computed(() => leadStatusesStore.statuses);
+12
View File
@@ -305,4 +305,16 @@ describe('DealsView.vue', () => {
const vm = wrapper.vm as unknown as { drawerOpen: boolean };
expect(vm.drawerOpen).toBe(false);
});
it('навигация на /deals?openId= в смонтированном view открывает drawer (watch)', async () => {
const openId = MOCK_DEALS[0].id;
const wrapper = await mountDealsViewAt('/deals');
await flushPromises();
const vm = wrapper.vm as unknown as { drawerOpen: boolean; selectedDeal: { id: number } | null };
expect(vm.drawerOpen).toBe(false);
await wrapper.vm.$router.push(`/deals?openId=${openId}`);
await flushPromises();
expect(vm.drawerOpen).toBe(true);
expect(vm.selectedDeal?.id).toBe(openId);
});
});
+12 -15
View File
@@ -56,13 +56,20 @@ describe('DealsView — redesigned', () => {
});
describe('FilterChip popovers (Sprint 1 C2)', () => {
it('clicking Project chip toggles projectMenuOpen ref to true', async () => {
const wrapper = mount(DealsView, {
function setupWithRouter() {
setActivePinia(createPinia());
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/deals', component: DealsView }] });
router.push('/deals');
return mount(DealsView, {
global: {
plugins: [createPinia(), createVuetify()],
plugins: [router, createVuetify()],
stubs: { DealDetailDrawer: true, NewDealDialog: true, VMenu: { template: '<div><slot name="activator" :props="{}" /><slot /></div>' } },
},
});
}
it('clicking Project chip toggles projectMenuOpen ref to true', async () => {
const wrapper = setupWithRouter();
await new Promise((r) => setTimeout(r, 50));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const vm = wrapper.vm as any;
@@ -75,12 +82,7 @@ describe('FilterChip popovers (Sprint 1 C2)', () => {
});
it('clicking Manager chip toggles managerMenuOpen ref to true', async () => {
const wrapper = mount(DealsView, {
global: {
plugins: [createPinia(), createVuetify()],
stubs: { DealDetailDrawer: true, NewDealDialog: true, VMenu: { template: '<div><slot name="activator" :props="{}" /><slot /></div>' } },
},
});
const wrapper = setupWithRouter();
await new Promise((r) => setTimeout(r, 50));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const vm = wrapper.vm as any;
@@ -93,12 +95,7 @@ describe('FilterChip popovers (Sprint 1 C2)', () => {
});
it('applying project selection updates filterProjects and closes menu', async () => {
const wrapper = mount(DealsView, {
global: {
plugins: [createPinia(), createVuetify()],
stubs: { DealDetailDrawer: true, NewDealDialog: true, VMenu: { template: '<div><slot name="activator" :props="{}" /><slot /></div>' } },
},
});
const wrapper = setupWithRouter();
await new Promise((r) => setTimeout(r, 50));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const vm = wrapper.vm as any;