Files
portal/app/tests/Frontend/SalesPeriodPicker.spec.ts
T
Дмитрий 6416ec3789 @
feat(sales): периоды сегодня/вчера/7/30 дней календарём, пополнения за период, скролл доски наверх

Замечания владельца по сводке и доске.

ПЕРИОДЫ:
- Резолвер понимает today/yesterday/d7/d30; «7 дней» = сегодня и 6 предыдущих.
  Месячные this/prev/prev2 сервер принимает по-прежнему.
- По умолчанию 30 дней.
- ПОЧИНЕНО: выбор «Произвольный» без дат ронял запрос (500). Теперь понятный 422,
  а на фронте период применяется только когда отмечены ОБЕ даты.
- Произвольный выбирается календарём-диапазоном, не руками; порядок дат неважен.

ПОПОЛНЕНИЯ ЗА ПЕРИОД (переиспользован готовый topupsRub):
- Сводка: плашка «Пополнили баланс» рядом с «Σ баланс».
- Мои клиенты: колонка «Пополнил».
  Заодно даёт число, которое видимо меняется при смене периода.

ДОСКА: горизонтальная полоса прокрутки поднята НАД колонками (колонки высокие,
системная полоса уезжала за экран); синхронизация в обе стороны, ResizeObserver
на приезжающие карточки.

Спека §21 (включая §21.4 — как по коду заполняется «Требуют внимания»).
Гейты: Pest 257/257 sales+unit, Vitest 1417, vue-tsc чисто, Larastan 0 в своих. TDD.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 14:29:18 +03:00

97 lines
3.5 KiB
TypeScript

import { mount } from '@vue/test-utils';
import { createVuetify } from 'vuetify';
import { createPinia, setActivePinia } from 'pinia';
import { describe, expect, it, beforeEach } from 'vitest';
import PeriodPicker from '../../resources/js/components/sales/PeriodPicker.vue';
import { useSalesPeriodStore } from '../../resources/js/stores/salesPeriod';
const vuetify = createVuetify();
interface PickerVm {
options: { value: string; label: string }[];
pick: (v: string) => void;
customRange: string[];
applyCustom: () => void;
menuOpen: boolean;
}
function factory() {
const w = mount(PeriodPicker, { global: { plugins: [vuetify] } });
return { w, vm: w.vm as unknown as PickerVm };
}
describe('PeriodPicker', () => {
beforeEach(() => {
setActivePinia(createPinia());
document.body.innerHTML = '';
});
it('предлагает сегодня, вчера, 7 дней, 30 дней и произвольный', () => {
const { vm } = factory();
expect(vm.options.map((o) => o.value)).toEqual(['today', 'yesterday', 'd7', 'd30', 'custom']);
expect(vm.options.map((o) => o.label)).toEqual([
'Сегодня',
'Вчера',
'7 дней',
'30 дней',
'Произвольный период',
]);
});
it('по умолчанию — 30 дней', () => {
factory();
expect(useSalesPeriodStore().kind).toBe('d30');
});
it('выбор готового периода сразу уходит в запрос', () => {
const { vm } = factory();
vm.pick('yesterday');
const store = useSalesPeriodStore();
expect(store.kind).toBe('yesterday');
expect(store.queryParams).toEqual({ period: 'yesterday' });
});
it('произвольный период применяется только после выбора обеих дат', () => {
const { vm } = factory();
vm.pick('custom');
const store = useSalesPeriodStore();
// Одна дата — период ещё не применяем, иначе сервер ответит ошибкой.
vm.customRange = ['2026-07-01'];
vm.applyCustom();
expect(store.kind).not.toBe('custom');
vm.customRange = ['2026-07-01', '2026-07-10'];
vm.applyCustom();
expect(store.queryParams).toEqual({ period: 'custom', from: '2026-07-01', to: '2026-07-10' });
});
it('календарь отдаёт даты в любом порядке — раскладываем от меньшей к большей', () => {
const { vm } = factory();
vm.pick('custom');
vm.customRange = ['2026-07-10', '2026-07-01'];
vm.applyCustom();
expect(useSalesPeriodStore().queryParams).toEqual({
period: 'custom',
from: '2026-07-01',
to: '2026-07-10',
});
});
});
describe('salesPeriod store', () => {
beforeEach(() => setActivePinia(createPinia()));
it('подписи периодов человеческие', () => {
const store = useSalesPeriodStore();
store.setPeriod({ kind: 'today' });
expect(store.label).toBe('Сегодня');
store.setPeriod({ kind: 'd7' });
expect(store.label).toBe('7 дней');
store.setPeriod({ kind: 'custom', from: '2026-07-01', to: '2026-07-10' });
expect(store.label).toBe('01.07.2026 — 10.07.2026');
});
});