diff --git a/app/resources/js/components/projects/BulkActionsBar.vue b/app/resources/js/components/projects/BulkActionsBar.vue
index 4f55bd6c..1ddead55 100644
--- a/app/resources/js/components/projects/BulkActionsBar.vue
+++ b/app/resources/js/components/projects/BulkActionsBar.vue
@@ -1,40 +1,91 @@
-
+
Выбрано: {{ store.selectedIds.size }}
+
+
+
+ 🌍 Регионы…
+
+
+ 📅 Дни сбора…
+
+
+ 🎯 Лимит лидов…
+
+
+
+
Приостановить
Возобновить
+
+
+
Архивировать
+
Снять выбор
+
+ runBulk({ action: 'update_regions', ...p })"
+ />
+ runBulk({ action: 'update_days', ...p })"
+ />
+ runBulk({ action: 'update_limit', ...p })"
+ />
diff --git a/app/tests/Frontend/BulkActionsBar.spec.ts b/app/tests/Frontend/BulkActionsBar.spec.ts
index 63f0ae4e..3e96c454 100644
--- a/app/tests/Frontend/BulkActionsBar.spec.ts
+++ b/app/tests/Frontend/BulkActionsBar.spec.ts
@@ -2,14 +2,31 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount } from '@vue/test-utils';
import { createPinia, setActivePinia } from 'pinia';
import { createVuetify } from 'vuetify';
+import axios from 'axios';
import BulkActionsBar from '../../resources/js/components/projects/BulkActionsBar.vue';
import { useProjectsStore } from '../../resources/js/stores/projectsStore';
-const factory = () =>
- mount(BulkActionsBar, {
- global: { plugins: [createVuetify()] },
+vi.mock('axios');
+
+const defaultStubs = {
+ VDialog: { template: '
' },
+ DevIndexBadge: true,
+ RegionsBulkDialog: true,
+ DaysBulkDialog: true,
+ LimitBulkDialog: true,
+};
+
+const factory = () => {
+ vi.mocked(axios.post).mockResolvedValue({ data: { updated: 0, skipped: [], warnings: [] } });
+ vi.mocked(axios.get).mockResolvedValue({ data: { data: [], meta: { total: 0 } } });
+ return mount(BulkActionsBar, {
+ global: {
+ plugins: [createVuetify()],
+ stubs: defaultStubs,
+ },
});
+};
beforeEach(() => {
setActivePinia(createPinia());
@@ -25,20 +42,20 @@ describe('BulkActionsBar', () => {
expect(wrapper.text()).toContain('2');
});
- it('clicking Pause triggers store.bulkAction("pause") after confirm', async () => {
+ it('clicking Pause triggers store.bulkUpdate("pause") after confirm', async () => {
const store = useProjectsStore();
store.selectedIds.add(1);
- const spy = vi.spyOn(store, 'bulkAction').mockResolvedValue();
+ const spy = vi.spyOn(store, 'bulkUpdate').mockResolvedValue({ updated: 1, skipped: [], warnings: [] });
window.confirm = vi.fn(() => true);
const wrapper = factory();
await wrapper.find('[data-testid="bulk-pause"]').trigger('click');
- expect(spy).toHaveBeenCalledWith('pause');
+ expect(spy).toHaveBeenCalledWith({ action: 'pause' });
});
- it('cancel confirm — bulkAction NOT called', async () => {
+ it('cancel confirm — bulkUpdate NOT called', async () => {
const store = useProjectsStore();
store.selectedIds.add(1);
- const spy = vi.spyOn(store, 'bulkAction').mockResolvedValue();
+ const spy = vi.spyOn(store, 'bulkUpdate').mockResolvedValue({ updated: 0, skipped: [], warnings: [] });
window.confirm = vi.fn(() => false);
const wrapper = factory();
await wrapper.find('[data-testid="bulk-pause"]').trigger('click');
@@ -53,3 +70,61 @@ describe('BulkActionsBar', () => {
expect(store.selectedIds.size).toBe(0);
});
});
+
+describe('BulkActionsBar — extended', () => {
+ it('renders 3 new dialog-open buttons (Regions/Days/Limit)', async () => {
+ const { wrapper } = await (async () => {
+ setActivePinia(createPinia());
+ vi.mocked(axios.post).mockResolvedValue({ data: { updated: 1, skipped: [], warnings: [] } });
+ vi.mocked(axios.get).mockResolvedValue({ data: { data: [], meta: { total: 0 } } });
+ const store = useProjectsStore();
+ store.selectedIds.add(1);
+ store.selectedIds.add(2);
+ const wrapper = mount(BulkActionsBar, {
+ global: {
+ plugins: [createVuetify()],
+ stubs: defaultStubs,
+ },
+ });
+ return { wrapper, store };
+ })();
+ expect(wrapper.find('[data-testid="bulk-regions"]').exists()).toBe(true);
+ expect(wrapper.find('[data-testid="bulk-days"]').exists()).toBe(true);
+ expect(wrapper.find('[data-testid="bulk-limit"]').exists()).toBe(true);
+ });
+
+ it('opens RegionsBulkDialog on regions button click', async () => {
+ setActivePinia(createPinia());
+ vi.mocked(axios.post).mockResolvedValue({ data: { updated: 1, skipped: [], warnings: [] } });
+ vi.mocked(axios.get).mockResolvedValue({ data: { data: [], meta: { total: 0 } } });
+ const store = useProjectsStore();
+ store.selectedIds.add(1);
+ store.selectedIds.add(2);
+ const wrapper = mount(BulkActionsBar, {
+ global: {
+ plugins: [createVuetify()],
+ stubs: defaultStubs,
+ },
+ });
+ await wrapper.find('[data-testid="bulk-regions"]').trigger('click');
+ expect((wrapper.vm as any).regionsOpen).toBe(true);
+ });
+
+ it('keeps existing pause/resume/archive buttons', async () => {
+ setActivePinia(createPinia());
+ vi.mocked(axios.post).mockResolvedValue({ data: { updated: 1, skipped: [], warnings: [] } });
+ vi.mocked(axios.get).mockResolvedValue({ data: { data: [], meta: { total: 0 } } });
+ const store = useProjectsStore();
+ store.selectedIds.add(1);
+ store.selectedIds.add(2);
+ const wrapper = mount(BulkActionsBar, {
+ global: {
+ plugins: [createVuetify()],
+ stubs: defaultStubs,
+ },
+ });
+ expect(wrapper.find('[data-testid="bulk-pause"]').exists()).toBe(true);
+ expect(wrapper.find('[data-testid="bulk-resume"]').exists()).toBe(true);
+ expect(wrapper.find('[data-testid="bulk-archive"]').exists()).toBe(true);
+ });
+});