8cd2ef91d3
Три изолированных модуля по TDD (тест до кода) — заголовок/второй заголовок/текст (creativeLimits.ts), проверка ссылки (urlRule.ts) и проверка картинки (imageRule.ts + imageMeta.ts). Цифры и сообщения — порт 1:1 из app/app/Services/Advertising/CreativeValidator.php (единый источник лимитов Яндекс.Директа). 23 новых теста зелёные. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
105 lines
4.1 KiB
TypeScript
105 lines
4.1 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
||
import {
|
||
validateTitle,
|
||
validateTitle2,
|
||
validateText,
|
||
NARROW_CHARS,
|
||
countNormalAndNarrow,
|
||
} from '../../resources/js/lib/advertising/creativeLimits';
|
||
|
||
/** Строка заданной общей длины из слов не длиннее wordLen (для тестов общего лимита длины). */
|
||
function buildLine(totalLen: number, wordLen: number): string {
|
||
let result = '';
|
||
while (result.length < totalLen) {
|
||
if (result.length > 0) {
|
||
result += ' ';
|
||
}
|
||
const remaining = totalLen - result.length;
|
||
const w = Math.min(wordLen, remaining);
|
||
result += 'а'.repeat(w);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
describe('creativeLimits — validateTitle', () => {
|
||
it('пропускает заголовок ровно 56 символов (слова не длиннее 22)', () => {
|
||
const title = buildLine(56, 20);
|
||
expect(validateTitle(title)).toBeNull();
|
||
});
|
||
|
||
it('отклоняет заголовок длиннее 56 символов', () => {
|
||
const title = buildLine(57, 20);
|
||
expect(validateTitle(title)).toBe('Заголовок длиннее 56 символов');
|
||
});
|
||
|
||
it('пропускает слово ровно 22 символа', () => {
|
||
const title = 'а'.repeat(22);
|
||
expect(validateTitle(title)).toBeNull();
|
||
});
|
||
|
||
it('отклоняет слово длиннее 22 символов', () => {
|
||
const title = 'а'.repeat(23);
|
||
expect(validateTitle(title)).toBe('В заголовке есть слово длиннее 22 символов');
|
||
});
|
||
});
|
||
|
||
describe('creativeLimits — validateTitle2', () => {
|
||
it('пропускает 30 обычных символов', () => {
|
||
const title2 = 'а'.repeat(30);
|
||
expect(validateTitle2(title2)).toBeNull();
|
||
});
|
||
|
||
it('отклоняет 31 обычный символ', () => {
|
||
const title2 = 'а'.repeat(31);
|
||
expect(validateTitle2(title2)).toBe(
|
||
'Второй заголовок длиннее нормы (30 обычных символов + до 15 узких)'
|
||
);
|
||
});
|
||
|
||
it('обычные и узкие считаются раздельно: 30 обычных + 5 узких — ок', () => {
|
||
const title2 = 'а'.repeat(30) + '!'.repeat(5);
|
||
expect(validateTitle2(title2)).toBeNull();
|
||
});
|
||
|
||
it('отклоняет, если узких больше 15 даже при малом числе обычных', () => {
|
||
const title2 = 'а'.repeat(5) + '!'.repeat(16);
|
||
expect(validateTitle2(title2)).toBe(
|
||
'Второй заголовок длиннее нормы (30 обычных символов + до 15 узких)'
|
||
);
|
||
});
|
||
});
|
||
|
||
describe('creativeLimits — validateText', () => {
|
||
it('пропускает 81 обычный символ (слова не длиннее 23)', () => {
|
||
const text = buildLine(81, 20);
|
||
expect(validateText(text)).toBeNull();
|
||
});
|
||
|
||
it('отклоняет 82 обычных символа', () => {
|
||
const text = buildLine(82, 20);
|
||
expect(validateText(text)).toBe(
|
||
'Текст длиннее нормы (81 обычных символов + до 15 узких)'
|
||
);
|
||
});
|
||
|
||
it('пропускает слово ровно 23 символа', () => {
|
||
const text = 'а'.repeat(23);
|
||
expect(validateText(text)).toBeNull();
|
||
});
|
||
|
||
it('отклоняет слово длиннее 23 символов', () => {
|
||
const text = 'а'.repeat(24);
|
||
expect(validateText(text)).toBe('В тексте есть слово длиннее 23 символов');
|
||
});
|
||
});
|
||
|
||
describe('creativeLimits — NARROW_CHARS и countNormalAndNarrow', () => {
|
||
it('содержит все узкие символы норматива', () => {
|
||
expect(NARROW_CHARS).toEqual(['!', ',', '.', ';', ':', '"']);
|
||
});
|
||
|
||
it('считает обычные и узкие раздельно', () => {
|
||
expect(countNormalAndNarrow('ab!c,')).toEqual({ normal: 3, narrow: 2 });
|
||
});
|
||
});
|