80c9ca7e67
Кусок B «витрина прогрева» (спека 2026-07-21 §6), решение владельца — полная летопись: - B1: таблица sales_ad_audience_warming_episodes + модель + WarmingEpisodeRecorder (open/close/record идемпотентно). Схема v8.85, бэкфилл из firm_channels(warming) и боевых СМС, guard по источнику. rls-reviewer OK 8/8. - B2: «Греть»/«Убрать» на площадке открывают/закрывают эпизоды канала. - B4: warmingByProspect считает значки из летописи (live/count вместо массива каналов), тип WarmingBadgeState в sales.ts. - B5: единый компонент WarmingBadges.vue (идёт/грели раньше/×N) в канбане; осиротевший WarmingChannelIcons удалён. Уборка: убраны мёртвые скоупы forYandex/Vk/Mts + их импорт + тест (боевых вызовов нет). cspell: +5 пре-существующих слов CHANGELOG в словарь (apk/cvtjpq/hgq/sar/sca). Проверено: Sales 427/427, composer stan 0, pint/prettier чисто, весь Vue-набор зелёный. B3 (СМС→эпизод) — отдельным коммитом (СМС-блок правит и параллельная сессия). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
55 lines
2.6 KiB
TypeScript
55 lines
2.6 KiB
TypeScript
import { mount } from '@vue/test-utils';
|
||
import { createVuetify } from 'vuetify';
|
||
import { describe, it, expect } from 'vitest';
|
||
import WarmingBadges from '../../resources/js/components/sales/WarmingBadges.vue';
|
||
import type { WarmingBadgeState } from '../../resources/js/api/sales';
|
||
|
||
/**
|
||
* Единый компонент значков витрины прогрева (кусок B, B5). Объединяет бейдж
|
||
* состояния (Греется/Прогрет) и значки каналов с признаком «идёт/грели раньше»
|
||
* (data-live) и счётчиком «×N». Монтируем с реальным Vuetify — как WarmingChannelIcons.spec.
|
||
*/
|
||
const vuetify = createVuetify();
|
||
|
||
function mountBadges(warming: WarmingBadgeState) {
|
||
return mount(WarmingBadges, { props: { warming }, global: { plugins: [vuetify] } });
|
||
}
|
||
|
||
describe('WarmingBadges', () => {
|
||
it('живой канал: «Греется» и яркий значок (data-live=1)', () => {
|
||
const w = mountBadges({ active: true, channels: { yandex: { live: true, count: 1 } } });
|
||
|
||
expect(w.find('[data-testid="warming-state-badge"]').text()).toBe('Греется');
|
||
const y = w.find('[data-testid="warming-badge-yandex"]');
|
||
expect(y.exists()).toBe(true);
|
||
expect(y.attributes('data-live')).toBe('1');
|
||
});
|
||
|
||
it('грели раньше: «Прогрет» и серый значок (data-live=0)', () => {
|
||
const w = mountBadges({ active: false, channels: { yandex: { live: false, count: 1 } } });
|
||
|
||
expect(w.find('[data-testid="warming-state-badge"]').text()).toBe('Прогрет');
|
||
expect(w.find('[data-testid="warming-badge-yandex"]').attributes('data-live')).toBe('0');
|
||
});
|
||
|
||
it('несколько эпизодов канала → «×N» (СМС ×2)', () => {
|
||
const w = mountBadges({ active: false, channels: { sms: { live: false, count: 2 } } });
|
||
|
||
const s = w.find('[data-testid="warming-badge-sms"]');
|
||
expect(s.exists()).toBe(true);
|
||
expect(s.text()).toContain('×2');
|
||
});
|
||
|
||
it('единственный эпизод — счётчик не показываем', () => {
|
||
const w = mountBadges({ active: true, channels: { yandex: { live: true, count: 1 } } });
|
||
|
||
expect(w.find('[data-testid="warming-badge-yandex"]').text()).not.toContain('×');
|
||
});
|
||
|
||
it('пустые каналы — прочерк', () => {
|
||
const w = mountBadges({ active: false, channels: {} });
|
||
|
||
expect(w.find('[data-testid="warming-badges"]').text()).toContain('—');
|
||
});
|
||
});
|